Вы находитесь на странице: 1из 23

МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ

федеральное государственное автономное образовательное учреждение


высшего образования
«Северный (Арктический) федеральный университет имени М.В. Ломоносова»

Высшая школа информационных технологий и автоматизированных систем


(наименование высшей школы / филиала / института / колледжа)

КОНТРОЛЬНАЯ РАБОТА

По дисциплине Интеллектуальные системы и технологии

На тему Решение задачи коммивояжера при помощи генетического алгоритма

Выполнил обучающийся:
Широхов Игорь Владимирович
(Ф.И.О.)

Направление подготовки:
09.03.02 Информационные системы и технологии
(код и наименование)

Курс: 5
Группа: 353818

Руководитель:
Р.А. Воронцов, ассистент
(Ф.И.О. руководителя, должность / уч. степень / звание)

Отметка о зачете
(отметка прописью) (дата)

Руководитель Р.А. Воронцов


(подпись руководителя) (инициалы, фамилия)

Архангельск 2022
МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
федеральное государственное автономное образовательное учреждение
высшего образования
«Северный (Арктический) федеральный университет имени М.В. Ломоносова»

Высшая школа информационных технологий и автоматизированных систем


(наименование высшей школы / филиала / института / колледжа)

ЗАДАНИЕ НА КОНТРОЛЬНУЮ РАБОТУ

п
по Интеллектуальные системы и технологии
(наименование дисциплины)
студенту ВШИТиАС высшей школы 5 курса 353818 группы
Широхов Игорь Владимирович
(фамилия, имя, отчество студента)
09.03.02 «Информационные системы и технологии»
(код и наименование направления подготовки/специальности)
ТЕМА: Решение задачи коммивояжера при помощи генетического
алгоритма Вариант № 30

ИСХОДНЫЕ ДАННЫЕ:
Необходимо разработать программную реализацию генетического алгоритма,
решающего задачу коммивояжера на основе матриц расстояний. В качестве оператора
скрещивания должно быть использовано циклическое скрещивание

Руководитель работы ассистент Р.А. Воронцов


(должность) (подпись) (инициалы, фамилия)

Архангельск 2022
ЛИСТ ДЛЯ ЗАМЕЧАНИЙ
ОГЛАВЛЕНИЕ

Введение...........................................................................................................................5
1 Описание задачи и метода ее решения.......................................................................6
2 Решение задачи коммивоежера.................................................................................10
Заключение.....................................................................................................................17
Список использованных источников...........................................................................18
Приложение А (обязательное) Код генетического алгоритма..................................19
ВВЕДЕНИЕ

В рамках данной контрольной работы необходимо разработать программу,


которая с помощью генетического алгоритма и на основе матрицы расстояний
будет решать задачу коммивояжера.
Задача коммивояжера является классической оптимизационной задачей.
Дано множество из n городов и матрица расстояний между ними. Цель
коммивояжера – объехать все эти города по кратчайшему пути. Причем в каждом
городе он должен побывать один раз и свой путь закончить в том же городе,
откуда начал.
При решении такой задачи можно использовать различные операторы
скрещивания. В данной работе выберем циклическое скрещивание. А количество
городов будет равно 10.
Разработка будет осуществляться на языке Python, в редакторе Microsoft
Visual Studio Code.

5
1 ОПИСАНИЕ ЗАДАЧИ И МЕТОДА ЕЁ РЕШЕНИЯ

Генетические алгоритмы применяются при разработке программного


обеспечения, в системах искусственного интеллекта, оптимизации, в
искусственных нейронных сетях и других отраслях знаний.
Генетические алгоритмы – это семейство поисковых алгоритмов, идеи
которых подсказаны принципами эволюции в природе.
Имитируя процессы естественного отбора и воспроизводства, генетические
алгоритмы могут находить высококачественные решения задач, включающих
поиск, оптимизацию и обучение.
Генетические алгоритмы реализуют упрощенный вариант дарвиновской
эволюции. Эволюция сохраняет популяцию особей, отличающихся друг от друга.
Те, кто лучше приспособлен к окружающей среде, имеют больше шансов на
выживание, размножение и передачу своих признаков следующему поколению.
Так популяция от поколения к поколению становится все более приспособленной
к окружающей среде и встающим на ее пути трудностям.
Разработка алгоритма состоит из следующих шагов:
1. инициализация, или выбор исходной популяции хромосом;
2. оценка приспособленности хромосом в популяции;
3. проверка условия остановки алгоритма;
4. селекция хромосом;
5. применение генетических операторов;
6. формирование новой популяции;
7. выбор «наилучшей» хромосомы.
Шаги выполняются по схеме, представленной на рисунке 1.

6
Рисунок 1 – Последовательность работы алгоритма

Генетический алгоритм начинается с популяции случайно сгенерированных


потенциальных решений, для которых вычисляется функция приспособленности.
Алгоритм выполняет цикл, в котором последовательно применяются операторы
отбора и скрещивания, после чего приспособленность элементов популяции
пересчитывается. Цикл продолжается, пока не выполнено условие остановки,
после чего лучший индивидуум в текущей популяции считается решением.
Функция приспособленности представляет проблему, которую мы пытаемся
решить. Цель генетического алгоритма – найти элементы, для которых оценка,
вычисляемая функцией приспособленности, максимальна. Если в какой-то задаче
нужен минимум, то при вычислении приспособленности следует инвертировать
найденное значение, например умножив его на –1.
В отличие от традиционных алгоритмов поиска, генетические алгоритмы
анализируют только значение, возвращенное функцией приспособленности.

7
Поэтому они могут работать с функциями, которые трудно или невозможно
продифференцировать.
В задаче коммивояжера имеется десять городов, расстояния между
которыми представлены матрицей расстояний, состоящей из 10 столбов и 10
строк, где на пересечении строки и столбца находится расстояние между
соответствующими городами.
Решение представим в виде перестановки чисел от 1 до 10, отображающей
последовательность посещения городов. А значение целевой функции будет
равно стоимости всей поездки, вычисленной в соответствии с матрицей.
Все элементы будем записывать в виде списка, состоящего из 11 элементов,
так как необходимо посетить 10 городов и вернуться в начальный пункт. Пример
показан на рисунке 2.

Рисунок 2 – Пример элемента

В данной работе первую популяцию будем выбирать случайным образом.


Функция приспособленности будет считать расстояние, пройденное между всеми
городами. А наилучшей будет хромосома с наименьшим показателем данной
функции. Для селекции хромосом будем использовать метод колеса рулетки.
Колесо разбито на сектора, где каждый из них отображает процент вероятности
селекции хромосомы. Для ее расчета необходимо посчитать долю значения
функции приспособленности и выразить ее в процентах.
Чем больше сектор, тем больше вероятность «победы» соответствующей
хромосомы. Поэтому вероятность выбора данной хромосомы прямо
пропорциональна значению ее функции приспособленности, но элемент
случайности все равно присутствует. Слабо приспособленные хромосомы могут
быть отобраны, хотя вероятность этого невысокая.

8
В нашей задаче необходимо найти минимальный путь, это говорит о том,
что значение функции оценки должно быть минимальным. Поэтому для решения
поставленной задачи необходимо будет инвертировать ее значение.
Цель генетических алгоритмов – найти оптимальное решение некоторой
задачи. Если дарвиновская эволюция развивает популяцию отдельных особей, то
генетические алгоритмы развивают популяцию потенциальных решений данной
задачи.
В результате решения задачи коммивояжера должен быть представлен путь,
имеющий наименьшую длину, которая была получена в ходе работы
генетического алгоритма.

9
2 РЕШЕНИЕ ЗАДАЧИ КОММИВОЕЖЕРА

Полный исходный код разработанного генетического алгоритма


представлен в приложении А.
Рассмотрим разработанное решение.
Основной алгоритм использует функции, отвечающие за подсчет различных
параметров. Для начала необходима функция, которая будет генерировать первое
поколение. Функция «first_population» создает элемент, состоящий из 11 чисел.
Первое и последнее число равны друг другу, потому что по условию задачи путь
должен закончиться в том же городе, в котором начался. Остальные числа
перемешаны случайным образом (листинг 1).
Листинг 1 – Функция генерации первой популяции
# генерация первой популяции
def first_population():
first = []
for i in range(0, 10):
el = [i for i in range(1, 11)]
random.shuffle(el)
el.append(el[0])
first.append(el)
return first

Так же понадобятся функции для расчета функции приспособленности и


селекции хромосом. Селекция хромосом производится методом рулетки. В
качестве функции приспособленности нам необходимо искать длину пройденного
пути. Как объяснялось выше необходимо будет его инвертировать. Поэтому
функция будет возвращать не саму длину пути, а 1 деленную на длину пути.
Таким образом, чем короче будет путь, тем больше будет значение функции. Так
для наименьшего пути будет выделен больший сектор круга.
Функции приспособленности и вероятности селекции представлены на
листинге 2.
Листинг 2 – Функции приспособленности и вероятности селекции
# Получаем значение приспособленности для элемента
def f(el):
dist = 0
for i in range(0,10):
10
dist +=matrix[el[i]-1][el[i+1]-1]
return dist
# получаем значения приспособленности для всех элементов в популяции
def elements_f_values(elements):
mass=[]
for element in elements:
value=f(element)
mass.append(value)
return mass

# Получаем вероятность селекции


def v(el,sumf):
return el/sumf*100

# Получаем вероятность селекции для всех элементов в массиве


def v_elements(f_elements):
mass=[]
for element in f_elements:
val=v(1/element,sum([(1/x) for x in f_elements]))
mass.append(val)
return mass

Далее рассмотрим функцию, представляющую описание метода рулетки. В


данной функции хромосомы распределяются по кругу, то есть колесу рулетки.
После чего случайным образом выбирается число. В зависимости от того, в какой
сектор попадает это число, та хромосома попадает в родительский пул.
Исходный код данной функции представлен на листинге 3.
Листинг 3 – Функция селекции по методу рулетки
# Селекция методом рулетки
def roulette(v_elements):
mas = []
sum_el = 0
for i in v_elements:
sum_el += i
mas.append(sum_el)
new_parents = []
for i in range(0, 10):
num = random.randint(0,100)
for pos in mas:
if pos >=num:
el_index = mas.index(pos)
new_parents.append(el_index)
break
return new_parents

11
После формирования родительского пула – из него формируются пары.
Пары образуются случайным образом так, чтобы в них не было одинаковых
элементов.
Функция «createPara» представляет собой описание формирования пар из
родительского пула. В данной функции в цикле формируются уникальные пары
без повторов, пока их число не станет кратным 5.
Исходный код данной функции представлен на листинге 4.
Листинг 4 – Функция формирования пар
# Формирование пар
def createPara(elements):
paras = []
while len(paras)<5:
para = []
choice1 = random.randint(0,9)
choice2 = random.randint(0,9)
paraex1 = elements[choice1]
paraex2 = elements[choice2]
if paraex1 != paraex2:
count = 0
for el in paras:
if (el[0] == paraex1 and el[1] == paraex2) or
(el[0]==paraex2 and el[1]==paraex1):
count += 1
if count==0:
para.append(paraex1)
para.append(paraex2)
paras.append(para)
return paras

За формирование новой популяции отвечает функция «crossCycle». При


формировании потомков, в соответствии с заданием – используется циклическое
скрещивание. В качестве параметров данной функции выступают – список с
парами родителей и популяция хромосом. Далее для каждой пары родителей
выполняется циклическое скрещивание следующим путем – сначала в левую
незанятую позицию первого потомка ставится элемент из самой левой позиции
первого родителя, при этом идет проверка на конфликт с уже проставленными в
первом потомке элементами. Затем в самую правую незанятую позицию ставится
самый правый элемент второго родителя, также с проверкой на конфликт. В
случае конфликта, берется следующий по счет элемент родителя и также
12
проверяется на конфликт. Так продолжается до тех пор, пока все позиции, кроме
шестой – не будут заняты. Шестая позиция заполняется оставшимся элементом,
который не был поставлен в потомка. По аналогии формируется второй потомок,
но родители меняются местами. В итоге - на выходе получаем список потомков.
Исходный код функции формирования потомков путем циклического
скрещивания представлен на листинге 5.
Листинг 5 – Функция формирования потомков
# Формирование потомков
def crossCycle(paras, population):
childrens = []
for item in paras:
par1 = population[item[0]-1]
par2 = population[item[1]-1]
ch1=[par1[0],0,0,0,0,0,0,0,0,0,par1[10]]
ch2=[par2[0],0,0,0,0,0,0,0,0,0,par2[10]]

for i in range(1,5):
count_p1c1=0
for it in ch1:
if it == par1[i]:
count_p1c1 += 1

count_p1c2=0
for it in ch2:
if it == par2[i]:
count_p1c2 += 1

if count_p1c1==0:
ch1[i]=par1[i]
else:
for s in range(i+1,i+5):
if par1[s] not in ch1:
ch1[i]=par1[s]
break

if count_p1c2==0:
ch2[i]=par2[i]
else:
for s in range(i+1,i+5):
if par2[s] not in ch2:
ch2[i]=par2[s]
break

count_p2c1=0
for it in ch1:
if it == par2[10-i]:

13
count_p2c1 += 1

count_p2c2=0
for it in ch2:
if it == par1[10-i]:
count_p2c2 += 1

if count_p2c1 ==0:
ch1[10-i]=par2[10-i]
else:
for d in range(10-i,1,-1):
if par2[d] not in ch1:
ch1[10-i]=par2[d]
break

if count_p2c2 ==0:
ch2[10-i]=par1[10-i]
else:
for d in range(10-i,1,-1):
if par1[d] not in ch2:
ch2[10-i]=par1[d]
break

ch1it5 = 0
for ile in par1:
if ile not in ch1:
ch1it5=ile
ch1[5]=ch1it5

ch2it5 = 0
for ile in par1:
if ile not in ch2:
ch2it5=ile
ch2[5]=ch2it5
childrens.extend([ch1,ch2])
return childrens

Для остановки процесса будем использовать следующее условие: если на


протяжении четырех последних поколений не наблюдается заметных улучшений,
то процесс завершается и выводится результат. Реализуем это путем запоминания
наилучшей приспособленности, достигнутой в каждом поколении, и сравнения
наилучшего текущего значения со значениями в предыдущих поколениях. Если
разница несущественная или ухудшается, то условие выхода считается
выполненным.
Код условия остановки процесса представлен на листинге 6.
Листинг 6 – Условие остановки процесса
14
w=first_population()
fin_f_values=elements_f_values(w)
m_f_values=[]
massive=[]
massive.append(min(fin_f_values))
m_f_values.append(w[fin_f_values.index(min(fin_f_values))])
for generationCounter in range(1,50):
if len(massive)>=5:
last_index=len(massive)-1
print(len(massive))
last1_mean = massive[last_index]
last2_mean = massive[last_index - 1]
last3_mean = massive[last_index - 2]
last4_mean = massive[last_index - 3]
print(last1_mean, last2_mean, last3_mean, last4_mean)
if (1/last4_mean - 1/last3_mean) + (1/last3_mean -
1/last2_mean) + (1/last2_mean - 1/last1_mean) < 0.01:
new_population = salesman(w)
w[:]=new_population
fin_f_values=elements_f_values(w)
freshFitn=(min(fin_f_values))
massive.append(freshFitn)
m_f_values.append(w[fin_f_values.index(min(fin_f_values)
)])
else:

print("Кратчайший путь
-",m_f_values[massive.index(min(massive))],"Длина -",min(massive))
break
else:
new_population = salesman(w)
w[:]=new_population
fin_f_values=elements_f_values(w)
freshFitn=(min(fin_f_values))
massive.append(freshFitn)
m_f_values.append(w[fin_f_values.index(min(fin_f_values))])

Если условие не выполнено, то алгоритм запускается заново, то есть


формируется новое поколение. Это описано в функции «salesman», код которой
представлен в листинге 7.
Листинг 7 – Функция «salesman»
def salesman(old_population):
el_f = elements_f_values(old_population)
el_v = v_elements(el_f)
ro = roulette(el_v)
pa = createPara(ro)
new_population = crossCycle(pa,old_population)
return new_population

15
Для расчетов нам необхома матрица расстояний. Её будем хранить в виде
массива, представленного на рисунке 3.

Рисунок 3 – Матрица расстояний

В итоге расчетов получен результат, который показан на рисунке 4.

Рисунок 4 – Результат работы.

При разных запусках могут получаться значения, отличающиеся от


представленного выше, так как некоторые параметры получаются случайным
образом. Но все результаты будут приближены к полученному.

16
ЗАКЛЮЧЕНИЕ

В результате выполнения контрольной работы был разработан генетический


алгоритм, решающий задачу коммивояжера на основе матрицы расстояний. В
качестве оператора скрещивания было использовано циклическое скрещивание.
В результате работы алгоритма на выходе получаем последовательность
городов, при посещении которых путь будет минимальным. Данный результат не
всегда будет самым минимальным из всех возможных. Это особенность
генетического алгоритма и селекции хромосом методом рулетки. Но результат
при каждом цикле работы алгоритма будет приближенным к минимальному.
Таким образом можно сделать вывод, что цели контрольной работы были
достигнуты, а задача коммивояжера решена.

17
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

1. Алешко, Р.А. Генетические алгоритмы [Текст]: метод. указ. для лаб.


занятий по дисциплине ««Интеллектуальные информационные системы» /
Архангельский государственный технический университет; сост. Р.А. Алешко,
И.В. Бачурин– Архангельск: Изд-во Арханг. гос. техн. ун-та, 2010. – 26 с.
2. Вирсански, Э. Генетические алгоритмы на Python / пер. с англ. А. А.
Слинкина. [Текст]: учеб. пособ. / Э. Вирсански – М.: ДМК Пресс, 2020. – 286 с.:
3. Панченко, Т. В. Генетические алгоритмы [Текст]: учеб. пособ. / под
ред.Ю. Ю. Тарасевича. — Астрахань: Издательский дом «Астраханский
университет»,2007. — 87 [3] с.

18
ПРИЛОЖЕНИЕ А
(обязательное)
Код генетического алгоритма
import random

matrix = [
[0,3,2,1,7,8,9,1,2,9],
[3,0,1,5,7,3,2,1,5,7],
[2,1,0,1,5,7,4,1,2,5],
[1,5,1,0,2,8,4,4,3,9],
[7,7,5,2,0,9,2,2,1,8],
[8,3,7,8,9,0,1,3,9,3],
[9,2,4,4,2,1,0,5,2,1],
[1,1,1,4,2,3,5,0,5,3],
[2,5,2,3,1,9,2,5,0,9],
[9,7,5,9,8,3,1,3,9,0],
]

# генерация первой популяции


def first_population():
first = []
for i in range(0, 10):
el = [i for i in range(1, 11)]
random.shuffle(el)
el.append(el[0])
first.append(el)
return first

def f(el):
dist = 0
for i in range(0,10):
dist +=matrix[el[i]-1][el[i+1]-1]
return dist

def elements_f_values(elements):
mass=[]
for element in elements:
value=f(element)
mass.append(value)
return mass

def v(el,sumf):
return el/sumf*100

def v_elements(f_elements):
mass=[]
for element in f_elements:
val=v(1/element,sum([(1/x) for x in f_elements]))
mass.append(val)
return mass
19
def roulette(v_elements):
mas = []
sum_el = 0
for i in v_elements:
sum_el += i
mas.append(sum_el)
new_parents = []
for i in range(0, 10):
num = random.randint(0,100)
for pos in mas:
if pos >=num:
el_index = mas.index(pos)
new_parents.append(el_index)
break
return new_parents

def createPara(elements):
paras = []
while len(paras)<5:
para = []
choice1 = random.randint(0,9)
choice2 = random.randint(0,9)
paraex1 = elements[choice1]
paraex2 = elements[choice2]
if paraex1 != paraex2:
count = 0
for el in paras:
if (el[0] == paraex1 and el[1] == paraex2) or
(el[0]==paraex2 and el[1]==paraex1):
count += 1
if count==0:
para.append(paraex1)
para.append(paraex2)
paras.append(para)
return paras

def crossCycle(paras, population):


childrens = []
for item in paras:
par1 = population[item[0]-1]
par2 = population[item[1]-1]
ch1=[par1[0],0,0,0,0,0,0,0,0,0,par1[10]]
ch2=[par2[0],0,0,0,0,0,0,0,0,0,par2[10]]

for i in range(1,5):
count_p1c1=0
for it in ch1:
if it == par1[i]:
count_p1c1 += 1

count_p1c2=0
20
for it in ch2:
if it == par2[i]:
count_p1c2 += 1

if count_p1c1==0:
ch1[i]=par1[i]
else:
for s in range(i+1,i+5):
if par1[s] not in ch1:
ch1[i]=par1[s]
break

if count_p1c2==0:
ch2[i]=par2[i]
else:
for s in range(i+1,i+5):
if par2[s] not in ch2:
ch2[i]=par2[s]
break

count_p2c1=0
for it in ch1:
if it == par2[10-i]:
count_p2c1 += 1

count_p2c2=0
for it in ch2:
if it == par1[10-i]:
count_p2c2 += 1

if count_p2c1 ==0:
ch1[10-i]=par2[10-i]
else:
for d in range(10-i,1,-1):
if par2[d] not in ch1:
ch1[10-i]=par2[d]
break

if count_p2c2 ==0:
ch2[10-i]=par1[10-i]
else:
for d in range(10-i,1,-1):
if par1[d] not in ch2:
ch2[10-i]=par1[d]
break

ch1it5 = 0
for ile in par1:
if ile not in ch1:
ch1it5=ile
ch1[5]=ch1it5

21
ch2it5 = 0
for ile in par1:
if ile not in ch2:
ch2it5=ile
ch2[5]=ch2it5
childrens.extend([ch1,ch2])
return childrens

def salesman(old_population):
el_f = elements_f_values(old_population)
el_v = v_elements(el_f)
ro = roulette(el_v)
pa = createPara(ro)
new_population = crossCycle(pa,old_population)
return new_population

w=first_population()
fin_f_values=elements_f_values(w)
m_f_values=[]
massive=[]
massive.append(min(fin_f_values))
m_f_values.append(w[fin_f_values.index(min(fin_f_values))])
for generationCounter in range(1,50):
if len(massive)>=5:
last_index=len(massive)-1
print(len(massive))
last1_mean = massive[last_index]
last2_mean = massive[last_index - 1]
last3_mean = massive[last_index - 2]
last4_mean = massive[last_index - 3]
print(last1_mean, last2_mean, last3_mean, last4_mean)
if (1/last4_mean - 1/last3_mean) + (1/last3_mean -
1/last2_mean) + (1/last2_mean - 1/last1_mean) < 0.01:
new_population = salesman(w)
w[:]=new_population
fin_f_values=elements_f_values(w)
freshFitn=(min(fin_f_values))
massive.append(freshFitn)
m_f_values.append(w[fin_f_values.index(min(fin_f_values)
)])
else:

print("Кратчайший путь
-",m_f_values[massive.index(min(massive))],"Длина -",min(massive))
break
else:
new_population = salesman(w)
w[:]=new_population
fin_f_values=elements_f_values(w)

22
freshFitn=(min(fin_f_values))
massive.append(freshFitn)
m_f_values.append(w[fin_f_values.index(min(fin_f_values))])

23

Вам также может понравиться