AI Notebook 6 — Генетические и эволюционные методы
Оптимизация: эволюционные стратегии и имитация отжига.
Edit on GitHubИсточник:
pr_Python/Pr_Ai/Notebook6.ipynb
Дисциплина «Искусственный интеллект»
Рабочая тетрадь № 6.
Петрушенко Александр Дмитриевич
Разработка заданий
Теоретический материал – Эволюционные методы
Эволюционные методы
Эволюционные методы относятся к числу эффективных средств решения задач оптимизации и структурного синтеза проектных решений. Они основаны на использовании принципов оптимального приспособления организмов в живой природе к условиям окружающей среды. К числу эволюционных относятся методы генетические, колонии муравьев, поведения толпы. Наиболее развиты и востребованы в настоящее время генетические алгоритмы. По мере развития техники и технологий растет доля сложных задач проектирования и управления, для решения которых применение традиционных методов проблематично. Поэтому все большее внимание уделяется применению методов искусственного интеллекта. Генетические алгоритмы Для применения ГА необходимо:
-
выделить совокупность свойств объекта, характеризуемых внутренними параметрами и влияющих на его полезность, т.е. выделить множество управляемых параметровX=(x_1,x_2,…,x_n) среди x_i могут быть величины различных типов (real, integer, Boolean, enumeration). Наличие нечисловых величин (enumeration) обусловливает возможность решения задач не только параметрической, но и структурной оптимизации;
-
сформулировать количественную оценку полезности вариантов объекта — функцию полезности F. Если в исходном виде задача многокритериальна, то такая формулировка означает выбор скалярного (обобщенного) критерия;
-
представить вектор X в форме хромосомы — записи следующего вида:
Этапы генетического алгоритма могут быть представлены в следующем виде:
for (k=0; k<G; k++)
{ for (j=0; j<N; j++)
{ Выбор родительской пары хромосом;
Кроссовер;
Мутации;
Оценка функции полезности F потомков;
Селекция;
}
Замена текущего поколения новым;
}1.1.1 Пример
Задача:
Пусть дана начальная популяция из четырех хромосом с двумя генами x и y. Показатель качества хромосомы оценивается функцией Z. При равном качестве хромосом предпочтение отдается хромосоме с большим номером. На каждом этапе хромосома a с высшим качеством порождает четыре новых хромосомы 𝑏1, 𝑐1, 𝑏2, 𝑐2, обмениваясь генами с двумя хромосомами b и c более низкого качества по указанной схеме:
Последняя хромоcома (с низшим качеством) выбывает из популяции. Найти максимальный показатель качества хромосомы в популяции и общее качество популяции после четырех этапов эволюции.
#функция качества хромосомы
def qZ(x, y):
return (x - 3 * y + 1) / (3 * x ** 2 + 3 * y ** 2 + 1)
#сумма качества хромосом
def qSumZ(Z):
return sum(Z)
def exchangeScheme(oldX, oldY, sortedId):
x = [0 for i in range(4)]
y = [0 for i in range(4)]
x[2] = oldX[sortedId[2]]
x[3] = oldX[sortedId[2]]
x[0] = oldX[sortedId[0]]
x[1] = oldX[sortedId[1]]
y[0] = oldY[sortedId[2]]
y[1] = oldY[sortedId[2]]
y[2] = oldY[sortedId[0]]
y[3] = oldY[sortedId[1]]
return x, y
def sorting(Z):
sortedId = sorted(range(len(Z)), key=lambda k: Z[k])
return sortedId
#шаг эволюции
def evoStep(x, y, z):
_, minId = min((value, id) for (id, value) in enumerate(z))
x = x[:]
y = y[:]
z = z[:]
x.pop(minId)
y.pop(minId)
z.pop(minId)
return x, y, z
#шаги эволюции (конечная функция), по умолчанию 4 шага
def evoSteps(x, y, steps_num=4):
results = []
for i in range(steps_num):
arrZ = [qZ(k, y[i]) for i, k in enumerate(x)]
x, y, z = evoStep(x, y, arrZ)
x, y = exchangeScheme(x, y, sorting(z))
results.append([x, y, qSumZ(arrZ), arrZ])
return x, y, results
#объявление массивов хромосом
x = [-2, -1, 0, 1]
y = [-2, -1, 0, 1]
results = evoSteps(x, y)
for i in range(len(results[2])):
print(f'max_{i + 1}_step: {results[2][i][2]}')
qualityArrZ = []
for i in range(len(results[2])):
qualityArrZ += results[2][i][3]
print(f'max Z: {max(qualityArrZ)}')Задание:
Выполните по вариантам соответственно реализацию генетического алгоритма в соответствии с приложенными начальными данными.
#функция качества хромосомы
def qZ(x, y):
return (x - 3 * y + 1) / (3 * x ** 2 + y ** 2 + 1)
#сумма качества хромосом
def qSumZ(Z):
return sum(Z)
def exchangeScheme(oldX, oldY, sortedId):
x = [0 for i in range(4)]
y = [0 for i in range(4)]
x[2] = oldX[sortedId[2]]
x[3] = oldX[sortedId[2]]
x[0] = oldX[sortedId[0]]
x[1] = oldX[sortedId[1]]
y[0] = oldY[sortedId[2]]
y[1] = oldY[sortedId[2]]
y[2] = oldY[sortedId[0]]
y[3] = oldY[sortedId[1]]
return x, y
def sorting(Z):
sortedId = sorted(range(len(Z)), key=lambda k: Z[k])
return sortedId
#шаг эволюции
def evoStep(x, y, z):
_, minId = min((value, id) for (id, value) in enumerate(z))
x = x[:]
y = y[:]
z = z[:]
x.pop(minId)
y.pop(minId)
z.pop(minId)
return x, y, z
#шаги эволюции (конечная функция), по умолчанию 4 шага
def evoSteps(x, y, steps_num=4):
results = []
for i in range(steps_num):
arrZ = [qZ(k, y[i]) for i, k in enumerate(x)]
x, y, z = evoStep(x, y, arrZ)
x, y = exchangeScheme(x, y, sorting(z))
results.append([x, y, qSumZ(arrZ), arrZ])
return x, y, results
#объявление массивов хромосом
x = [-2, -1, 0, 2]
y = [-2, 0, -1, 1]
results = evoSteps(x, y)
for i in range(len(results[2])):
print(f'max_{i + 1}_step: {results[2][i][2]}')
qualityArrZ = []
for i in range(len(results[2])):
qualityArrZ += results[2][i][3]
print(f'max Z: {max(qualityArrZ)}')Вывод:
max_1_step: 2.2941176470588234
max_2_step: 0.9714285714285713
max_3_step: 4.823529411764706
max_4_step: 5.828571428571428
max Z: 2.01.2. Теоретический материал – Метод имитации отжига
Алгоритм отжига – это метод оптимизации, который называется отжигом, или симуляцией восстановления (Simulated annealing). Как ясно из названия, метод поиска моделирует процесс восстановления. Восстановление – это физический процесс, который заключается в нагреве и последующем контролируемом охлаждении субстанции. В результате получается прочная кристаллическая структура, которая отличается от структуры с дефектами, образующейся при быстром беспорядочном охлаждении. Структура здесь представляет собой кодированное решение, а температура используется для того, чтобы указать, как и когда будут приниматься новые решения.
Алгоритм имитации отжига включает следующие этапы:
Метод отжига может быть эффективным при решении задач различных классов, требующих оптимизации. Ниже приводится их краткий список:
-
создание пути;
-
реконструкция изображения;
-
назначение задач и планирование;
-
размещение сети;
-
глобальная маршрутизация;
-
обнаружение и распознавание визуальных объектов;
-
разработка специальных цифровых фильтров.
Поскольку метод отжига представляет собой процесс генерации случайных чисел, поиск решения с использованием данного алгоритма может занять значительное время. В некоторых случаях алгоритм вообще не находит решение или выбирает не самое оптимальное. Алгоритм отжига как способ выполнения процедур поиска и оптимизации. Данный метод является аналогом процесса нагревания тела до состояния плавления с последующим постепенным охлаждением. При высоких температурах поиск ведется по всему диапазону. При снижении температуры диапазон поиска уменьшается до небольшой области вокруг текущего решения.
Рассмотрим решение задачи поиска оптимального маршрута на графе методом имитации отжига Для этого, представим формальную постановку задачи и рассмотрим пример, который иллюстрирует алгоритм решения.
Итак, необходимо Найти длину гамильтонова цикла 𝑆4 в полном графе 𝐾6 после четырех циклов решения задачи методом отжига. Даны расстояния 𝐿𝑖,𝑗 между вершинами. Даны также: начальная последовательность вершин 𝐿0, последовательность замен вершин 𝑍 и выпавшие при этом вероятности перехода 𝑃𝑘, 𝑘 = 1, . . . , 4.
Переход на худшее (∆𝑆𝑘 = 𝑆𝑘 − 𝑆𝑘−1 > 0) решение допустим, если 𝑃∗ = 100где снижение температуры происходит по закону 𝑇𝑘+1 = 0.5𝑇𝑘 от 𝑇1 = 100.
1.2.1 Пример
Задача:
Итак, начальные условия задачи представляют собой следующий граф с
расстояниями между ребрами:
import networkx as nx
from math import e
distances = [(1, 2, 20),
(1, 3, 40),
(1, 4, 42),
(1, 5, 33),
(1, 6, 21),
(2, 3, 26),
(2, 4, 38),
(2, 5, 42),
(2, 6, 17),
(3, 4, 22),
(3, 5, 43),
(3, 6, 21),
(4, 5, 27),
(4, 6, 22),
(5, 6, 26)] #длины рёбер
V = [1, 4, 5, 2, 6, 3, 1] #последовательность прохождения маршрута
Z = [(3, 4),
(4, 6),
(5, 2),
(6, 2)] #последовательность замен вершин
P = [49, 54, 43, 54] #случайные числа, выпавшие в процессе счёта
T = 100 #начальная температура
#функция вероятности
def probability(delta, T):
return 100 * e ** (-delta / T)
#функция изменения температуры
def reductTemp(prevT):
nextT = .5 * prevT
return nextT
graph = nx.Graph() #создание пустого графа
graph.add_weighted_edges_from(distances) #добавление весов рёбер
#отрисовка графа с заданными вершинами
nx.draw_kamada_kawai(graph, node_color='#fb7258', node_size=2000, with_labels=True)Вывод:
<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
#вычисление длины ребра
def edgeLength(i, j, distances, roundTrip=True):
if roundTrip:
return max([(item[2] if (item[0] == i and item[1] == j) or (item[1] == i and item[0] == j) else -1)
for item in distances])
else:
return max([(item[2] if (item[0] == i and item[1] == j) else -1) for item in distances])
#вычисление длины маршрута
def routeLength(V, distances):
edges = []
for i in range(len(V) - 1):
edges.append(edgeLength(V[i], V[i + 1], distances))
return sum(edges)
#одна перестановка в пути
def routeOneReplacement(arrV, Z, replacementByName=True):
decrement = 1 if replacementByName else 0
arrV[Z[0] - decrement], arrV[Z[1] - decrement] = arrV[Z[1] - decrement], arrV[Z[0] - decrement]
return arrV
#перестановка в пути
def routeReplacement(V, Z):
for z in Z:
V = routeOnereplacement(V, z)
return V
#выбор нужного пути методом отжига
def chooseRoute(distances, V, Z, T, P):
sumLength = routeLength(V, distances) #нахождение длины пути
arrSum = [sumLength] #массив сумм длин
#циклы методом отжига
for i in range(len(Z)):
newV = routeOneReplacement(V[:], Z[i]) #новый маршрут после перестановки
newS = routeLength(newV, distances) #длина нового маршрута
arrSum.append(newS)
deltaS = newS - sumLength #разница между длиной нового и старого маршрутов
#в случае, если разница между длинами больше 0, то вычисляется вероятность
if deltaS > 0:
p = probability(deltaS, T) #подсчёт вероятности
#если заданная вероятность попадает в интервал от 0 до p, то новый маршрут выбирается
if p > P[i]:
V = newV
sumLength = newS
else:
V = newV
sumLength = newS
T = reductTemp(T) #вычисление температуры
return V, arrSum
#отрисовка графа по заданному маршруту
def drawRouteGraph(distances, bestRoute):
newDistances = []
#прохождение по вектору
for i in range(len(bestRoute) - 1):
for distance in distances:
if distance[0] == bestRoute[i] and distance[1] == bestRoute[i + 1] or distance[1] == bestRoute[i] and distance[0] == bestRoute[i + 1]:
newDistances.append(distance)
graph = nx.Graph() #создание пустого графа
graph.add_weighted_edges_from(newDistances) #добавление весов рёбер
#отрисовка графа с заданными вершинами
nx.draw_kamada_kawai(graph, node_color='#fb7258', node_size=2000, with_labels=True)
bestRoute, arrLength = chooseRoute(distances, V, Z, T, P)
print(f'Лучший выбранный маршрут: {bestRoute}')
print(f'Длина лучшего выбранного маршрута: {routeLength(bestRoute, distances)}')
print(f'Длины всех рассмотренных маршрутов: {arrLength}')
drawRouteGraph(distances, bestRoute) #отрисовка лучшего маршрутаВывод:
Лучший выбранный маршрут: [1, 6, 2, 3, 4, 5, 1]
Длина лучшего выбранного маршрута: 146
Длины всех рассмотренных маршрутов: [189, 209, 186, 146, 166]<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
Задание
Найти длину гамильтонова цикла S4 в полном графе K6 после четырех циклов решения задачи методом отжига по вариантам ниже.
distances = [(1, 2, 24),
(1, 3, 41),
(1, 4, 36),
(1, 5, 22),
(1, 6, 19),
(2, 3, 21),
(2, 4, 33),
(2, 5, 33),
(2, 6, 14),
(3, 4, 27),
(3, 5, 39),
(3, 6, 23),
(4, 5, 20),
(4, 6, 20),
(5, 6, 19)] #длины рёбер
V = [1, 3, 4, 5, 6, 2, 1] #последовательность прохождения маршрута
Z = [(3, 4),
(4, 6),
(5, 2),
(6, 2)] #последовательность замен вершин
P = [33, 82, 51, 76] #случайные числа, выпавшие в процессе счёта
T = 100 #начальная температура
#функция вероятности
def probability(delta, T):
return 100 * e ** (-delta / T)
#функция изменения температуры
def reductTemp(prevT):
nextT = .5 * prevT
return nextT
#вычисление длины ребра
def edgeLength(i, j, distances, roundTrip=True):
if roundTrip:
return max([(item[2] if (item[0] == i and item[1] == j) or (item[1] == i and item[0] == j) else -1)
for item in distances])
else:
return max([(item[2] if (item[0] == i and item[1] == j) else -1) for item in distances])
#вычисление длины маршрута
def routeLength(V, distances):
edges = []
for i in range(len(V) - 1):
edges.append(edgeLength(V[i], V[i + 1], distances))
return sum(edges)
#одна перестановка в пути
def routeOneReplacement(arrV, Z, replacementByName=True):
decrement = 1 if replacementByName else 0
arrV[Z[0] - decrement], arrV[Z[1] - decrement] = arrV[Z[1] - decrement], arrV[Z[0] - decrement]
return arrV
#перестановка в пути
def routeReplacement(V, Z):
for z in Z:
V = routeOnereplacement(V, z)
return V
#выбор нужного пути методом отжига
def chooseRoute(distances, V, Z, T, P):
sumLength = routeLength(V, distances) #нахождение длины пути
arrSum = [sumLength] #массив сумм длин
#циклы методом отжига
for i in range(len(Z)):
newV = routeOneReplacement(V[:], Z[i]) #новый маршрут после перестановки
newS = routeLength(newV, distances) #длина нового маршрута
arrSum.append(newS)
deltaS = newS - sumLength #разница между длиной нового и старого маршрутов
#в случае, если разница между длинами больше 0, то вычисляется вероятность
if deltaS > 0:
p = probability(deltaS, T) #подсчёт вероятности
#если заданная вероятность попадает в интервал от 0 до p, то новый маршрут выбирается
if p > P[i]:
V = newV
sumLength = newS
else:
V = newV
sumLength = newS
T = reductTemp(T) #вычисление температуры
return V, arrSum
#отрисовка графа по заданному маршруту
def drawRouteGraph(distances, bestRoute):
newDistances = []
#прохождение по вектору
for i in range(len(bestRoute) - 1):
for distance in distances:
if distance[0] == bestRoute[i] and distance[1] == bestRoute[i + 1] or distance[1] == bestRoute[i] and distance[0] == bestRoute[i + 1]:
newDistances.append(distance)
graph = nx.Graph() #создание пустого графа
graph.add_weighted_edges_from(newDistances) #добавление весов рёбер
#отрисовка графа с заданными вершинами
nx.draw_kamada_kawai(graph, node_color='#fb7258', node_size=2000, with_labels=True)
bestRoute, arrLength = chooseRoute(distances, V, Z, T, P)
print(f'Лучший выбранный маршрут: {bestRoute}')
print(f'Длина лучшего выбранного маршрута: {routeLength(bestRoute, distances)}')
print(f'Длины всех рассмотренных маршрутов: {arrLength}')
drawRouteGraph(distances, bestRoute) #отрисовка лучшего маршрутаВывод:
Лучший выбранный маршрут: [1, 6, 5, 4, 3, 2, 1]
Длина лучшего выбранного маршрута: 130
Длины всех рассмотренных маршрутов: [145, 158, 183, 130, 146]<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.