Refine lab6 assets and report comparison
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@
|
|||||||
!lab4/*
|
!lab4/*
|
||||||
!lab5/report/report.tex
|
!lab5/report/report.tex
|
||||||
!lab5/README.md
|
!lab5/README.md
|
||||||
|
!lab6/README.md
|
||||||
|
|||||||
20
lab6/README.md
Normal file
20
lab6/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Лабораторная работа 6
|
||||||
|
|
||||||
|
## Как получить изображения и данные
|
||||||
|
1. Убедитесь, что зависимости Python уже доступны в системе (скрипт использует только стандартную библиотеку).
|
||||||
|
2. Запустите оптимизатор и генерацию графиков:
|
||||||
|
```bash
|
||||||
|
python lab6/main.py
|
||||||
|
```
|
||||||
|
После выполнения в папке `lab6/report/img` появятся файлы `aco_best_tour.png`, `aco_history.png` и `aco_best_tour.txt`.
|
||||||
|
3. Скопируйте эталонный маршрут из предыдущей лабораторной работы:
|
||||||
|
```bash
|
||||||
|
cp lab3/report/img/optimal_tour.png lab6/report/img/
|
||||||
|
```
|
||||||
|
4. Соберите отчёт (пример для `latexmk`):
|
||||||
|
```bash
|
||||||
|
cd lab6/report
|
||||||
|
latexmk -pdf report.tex
|
||||||
|
```
|
||||||
|
|
||||||
|
Получившиеся изображения и файл с порядком городов используются в отчёте из `lab6/report/report.tex`.
|
||||||
239
lab6/aco.py
Normal file
239
lab6/aco.py
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import math
|
||||||
|
import random
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Sequence, Tuple
|
||||||
|
|
||||||
|
City = Tuple[float, float]
|
||||||
|
Tour = List[int]
|
||||||
|
|
||||||
|
|
||||||
|
def euclidean_distance(c1: City, c2: City) -> float:
|
||||||
|
return math.hypot(c1[0] - c2[0], c1[1] - c2[1])
|
||||||
|
|
||||||
|
|
||||||
|
def build_distance_matrix(cities: Sequence[City]) -> list[list[float]]:
|
||||||
|
size = len(cities)
|
||||||
|
matrix = [[0.0 for _ in range(size)] for _ in range(size)]
|
||||||
|
for i in range(size):
|
||||||
|
for j in range(i + 1, size):
|
||||||
|
dist = euclidean_distance(cities[i], cities[j])
|
||||||
|
matrix[i][j] = matrix[j][i] = dist
|
||||||
|
return matrix
|
||||||
|
|
||||||
|
|
||||||
|
def _write_png(filename: str, pixels: list[list[tuple[int, int, int]]]) -> None:
|
||||||
|
height = len(pixels)
|
||||||
|
width = len(pixels[0]) if height else 0
|
||||||
|
|
||||||
|
def chunk(chunk_type: bytes, data: bytes) -> bytes:
|
||||||
|
return (
|
||||||
|
struct.pack(">I", len(data))
|
||||||
|
+ chunk_type
|
||||||
|
+ data
|
||||||
|
+ struct.pack(">I", zlib.crc32(chunk_type + data) & 0xFFFFFFFF)
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = b"".join(b"\x00" + bytes([c for px in row for c in px]) for row in pixels)
|
||||||
|
png = b"\x89PNG\r\n\x1a\n"
|
||||||
|
ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
|
||||||
|
png += chunk(b"IHDR", ihdr)
|
||||||
|
png += chunk(b"IDAT", zlib.compress(raw, 9))
|
||||||
|
png += chunk(b"IEND", b"")
|
||||||
|
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(png)
|
||||||
|
|
||||||
|
|
||||||
|
def _scale_points(points: Sequence[tuple[float, float]], size: int = 800, margin: int = 20):
|
||||||
|
xs = [p[0] for p in points]
|
||||||
|
ys = [p[1] for p in points]
|
||||||
|
min_x, max_x = min(xs), max(xs)
|
||||||
|
min_y, max_y = min(ys), max(ys)
|
||||||
|
scale_x = (size - 2 * margin) / (max_x - min_x + 1e-9)
|
||||||
|
scale_y = (size - 2 * margin) / (max_y - min_y + 1e-9)
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
int((x - min_x) * scale_x + margin),
|
||||||
|
int((y - min_y) * scale_y + margin),
|
||||||
|
)
|
||||||
|
for x, y in points
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_line(pixels: list[list[tuple[int, int, int]]], p1: tuple[int, int], p2: tuple[int, int], color: tuple[int, int, int]):
|
||||||
|
x1, y1 = p1
|
||||||
|
x2, y2 = p2
|
||||||
|
dx = abs(x2 - x1)
|
||||||
|
dy = -abs(y2 - y1)
|
||||||
|
sx = 1 if x1 < x2 else -1
|
||||||
|
sy = 1 if y1 < y2 else -1
|
||||||
|
err = dx + dy
|
||||||
|
while True:
|
||||||
|
if 0 <= x1 < len(pixels[0]) and 0 <= y1 < len(pixels):
|
||||||
|
pixels[y1][x1] = color
|
||||||
|
if x1 == x2 and y1 == y2:
|
||||||
|
break
|
||||||
|
e2 = 2 * err
|
||||||
|
if e2 >= dy:
|
||||||
|
err += dy
|
||||||
|
x1 += sx
|
||||||
|
if e2 <= dx:
|
||||||
|
err += dx
|
||||||
|
y1 += sy
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_circle(pixels: list[list[tuple[int, int, int]]], center: tuple[int, int], radius: int, color: tuple[int, int, int]):
|
||||||
|
cx, cy = center
|
||||||
|
for y in range(cy - radius, cy + radius + 1):
|
||||||
|
for x in range(cx - radius, cx + radius + 1):
|
||||||
|
if 0 <= x < len(pixels[0]) and 0 <= y < len(pixels):
|
||||||
|
if (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2:
|
||||||
|
pixels[y][x] = color
|
||||||
|
|
||||||
|
|
||||||
|
def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None:
|
||||||
|
ordered = [cities[i] for i in tour] + [cities[tour[0]]]
|
||||||
|
points = _scale_points(ordered)
|
||||||
|
width = height = 820
|
||||||
|
pixels = [[(255, 255, 255) for _ in range(width)] for _ in range(height)]
|
||||||
|
|
||||||
|
for i in range(len(points) - 1):
|
||||||
|
_draw_line(pixels, points[i], points[i + 1], (0, 120, 200))
|
||||||
|
|
||||||
|
# draw cities
|
||||||
|
city_points = _scale_points(cities)
|
||||||
|
for p in city_points:
|
||||||
|
_draw_circle(pixels, p, 4, (200, 50, 50))
|
||||||
|
|
||||||
|
_write_png(save_path, pixels)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_history(best_lengths: Sequence[float], save_path: str) -> None:
|
||||||
|
if not best_lengths:
|
||||||
|
return
|
||||||
|
|
||||||
|
width, height, margin = 820, 400, 20
|
||||||
|
pixels = [[(255, 255, 255) for _ in range(width)] for _ in range(height)]
|
||||||
|
|
||||||
|
n = len(best_lengths)
|
||||||
|
min_len, max_len = min(best_lengths), max(best_lengths)
|
||||||
|
span = max_len - min_len if max_len != min_len else 1
|
||||||
|
|
||||||
|
def to_point(idx: int, value: float) -> tuple[int, int]:
|
||||||
|
x = margin + int((width - 2 * margin) * idx / max(1, n - 1))
|
||||||
|
y = height - margin - int((height - 2 * margin) * (value - min_len) / span)
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
prev = to_point(0, best_lengths[0])
|
||||||
|
for i, v in enumerate(best_lengths[1:], start=1):
|
||||||
|
cur = to_point(i, v)
|
||||||
|
_draw_line(pixels, prev, cur, (30, 30, 30))
|
||||||
|
prev = cur
|
||||||
|
|
||||||
|
_write_png(save_path, pixels)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ACOConfig:
|
||||||
|
cities: Sequence[City]
|
||||||
|
n_ants: int
|
||||||
|
n_iterations: int
|
||||||
|
alpha: float = 1.0
|
||||||
|
beta: float = 5.0
|
||||||
|
rho: float = 0.5
|
||||||
|
q: float = 1.0
|
||||||
|
seed: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ACOResult:
|
||||||
|
best_tour: Tour
|
||||||
|
best_length: float
|
||||||
|
history: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class AntColonyOptimizer:
|
||||||
|
def __init__(self, config: ACOConfig):
|
||||||
|
self.config = config
|
||||||
|
if config.seed is not None:
|
||||||
|
random.seed(config.seed)
|
||||||
|
|
||||||
|
self.cities = config.cities
|
||||||
|
self.dist_matrix = build_distance_matrix(config.cities)
|
||||||
|
n = len(config.cities)
|
||||||
|
self.pheromone = [[1.0 if i != j else 0.0 for j in range(n)] for i in range(n)]
|
||||||
|
|
||||||
|
def _choose_next_city(self, current: int, unvisited: set[int]) -> int:
|
||||||
|
candidates = list(unvisited)
|
||||||
|
weights = []
|
||||||
|
for nxt in candidates:
|
||||||
|
tau = self.pheromone[current][nxt] ** self.config.alpha
|
||||||
|
eta = (1.0 / (self.dist_matrix[current][nxt] + 1e-12)) ** self.config.beta
|
||||||
|
weights.append(tau * eta)
|
||||||
|
|
||||||
|
total = sum(weights)
|
||||||
|
probs = [w / total for w in weights]
|
||||||
|
return random.choices(candidates, weights=probs, k=1)[0]
|
||||||
|
|
||||||
|
def _build_tour(self, start: int) -> Tour:
|
||||||
|
n = len(self.cities)
|
||||||
|
tour = [start]
|
||||||
|
unvisited = set(range(n))
|
||||||
|
unvisited.remove(start)
|
||||||
|
|
||||||
|
current = start
|
||||||
|
while unvisited:
|
||||||
|
nxt = self._choose_next_city(current, unvisited)
|
||||||
|
tour.append(nxt)
|
||||||
|
unvisited.remove(nxt)
|
||||||
|
current = nxt
|
||||||
|
|
||||||
|
return tour
|
||||||
|
|
||||||
|
def _tour_length(self, tour: Sequence[int]) -> float:
|
||||||
|
return sum(
|
||||||
|
self.dist_matrix[tour[i]][tour[(i + 1) % len(tour)]]
|
||||||
|
for i in range(len(tour))
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> ACOResult:
|
||||||
|
best_tour: Tour = []
|
||||||
|
best_length = float("inf")
|
||||||
|
best_history: list[float] = []
|
||||||
|
|
||||||
|
for _ in range(self.config.n_iterations):
|
||||||
|
tours: list[Tour] = []
|
||||||
|
lengths: list[float] = []
|
||||||
|
|
||||||
|
for _ in range(self.config.n_ants):
|
||||||
|
start_city = random.randrange(len(self.cities))
|
||||||
|
tour = self._build_tour(start_city)
|
||||||
|
length = self._tour_length(tour)
|
||||||
|
tours.append(tour)
|
||||||
|
lengths.append(length)
|
||||||
|
|
||||||
|
if length < best_length:
|
||||||
|
best_length = length
|
||||||
|
best_tour = tour
|
||||||
|
|
||||||
|
for i in range(len(self.pheromone)):
|
||||||
|
for j in range(len(self.pheromone)):
|
||||||
|
self.pheromone[i][j] *= 1 - self.config.rho
|
||||||
|
|
||||||
|
for tour, length in zip(tours, lengths):
|
||||||
|
deposit = self.config.q / length
|
||||||
|
for i in range(len(tour)):
|
||||||
|
a, b = tour[i], tour[(i + 1) % len(tour)]
|
||||||
|
self.pheromone[a][b] += deposit
|
||||||
|
self.pheromone[b][a] += deposit
|
||||||
|
|
||||||
|
best_history.append(best_length)
|
||||||
|
|
||||||
|
return ACOResult(best_tour=best_tour, best_length=best_length, history=best_history)
|
||||||
|
|
||||||
|
|
||||||
|
def run_aco(config: ACOConfig) -> ACOResult:
|
||||||
|
optimizer = AntColonyOptimizer(config)
|
||||||
|
return optimizer.run()
|
||||||
36
lab6/main.py
Normal file
36
lab6/main.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from aco import ACOConfig, plot_history, plot_tour, run_aco
|
||||||
|
|
||||||
|
# В списке из 89 городов только 38 уникальных
|
||||||
|
cities = set()
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "../lab3/data.txt"), "r") as file:
|
||||||
|
for line in file:
|
||||||
|
# x и y поменяны местами в визуализациях в методичке
|
||||||
|
_, y, x = line.split()
|
||||||
|
cities.add((float(x), float(y)))
|
||||||
|
cities = list(cities)
|
||||||
|
|
||||||
|
config = ACOConfig(
|
||||||
|
cities=cities,
|
||||||
|
n_ants=50,
|
||||||
|
n_iterations=400,
|
||||||
|
alpha=1.2,
|
||||||
|
beta=5.0,
|
||||||
|
rho=0.5,
|
||||||
|
q=1.0,
|
||||||
|
seed=7,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = run_aco(config)
|
||||||
|
print(f"Лучшая длина: {result.best_length:.2f}")
|
||||||
|
print(f"Лучший тур: {result.best_tour}")
|
||||||
|
|
||||||
|
results_dir = os.path.join(os.path.dirname(__file__), "report", "img")
|
||||||
|
os.makedirs(results_dir, exist_ok=True)
|
||||||
|
|
||||||
|
plot_tour(config.cities, result.best_tour, os.path.join(results_dir, "aco_best_tour.png"))
|
||||||
|
plot_history(result.history, os.path.join(results_dir, "aco_history.png"))
|
||||||
|
|
||||||
|
with open(os.path.join(results_dir, "aco_best_tour.txt"), "w", encoding="utf-8") as f:
|
||||||
|
f.write(" ".join(map(str, result.best_tour)))
|
||||||
2
lab6/report/.gitignore
vendored
2
lab6/report/.gitignore
vendored
@@ -1,6 +1,4 @@
|
|||||||
*
|
*
|
||||||
|
|
||||||
!**/
|
!**/
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!report.tex
|
!report.tex
|
||||||
!img/**/*.png
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
@@ -256,129 +256,61 @@
|
|||||||
\newpage
|
\newpage
|
||||||
\section{Особенности реализации}
|
\section{Особенности реализации}
|
||||||
|
|
||||||
=== Нужно обновить раздел ===
|
В рамках шестой лабораторной работы реализован простой муравьиный алгоритм для решения задачи коммивояжёра. Алгоритм оформлен в модуле \texttt{aco.py} и состоит из следующих компонентов:
|
||||||
|
|
||||||
В рамках работы создана мини-библиотека \texttt{gen.py} для решения задачи коммивояжёра (TSP) генетическим алгоритмом с путевым представлением хромосом. Второй модуль
|
|
||||||
\texttt{expirements.py} организует серийные эксперименты (перебор параметров,
|
|
||||||
форматирование и сохранение результатов).
|
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item \textbf{Кодирование особей}: каждая хромосома представлена как перестановка городов (\texttt{Chromosome = list[int]}), где каждый элемент -- индекс города. Популяция -- список хромосом (\texttt{Population = list[Chromosome]}). Инициализация случайными перестановками без повторений:
|
\item \textbf{Структуры данных}: конфигурация \texttt{ACOConfig} (число муравьёв, количество итераций, параметры $\alpha$, $\beta$, $\rho$ и $q$) и результат \texttt{ACOResult} (лучший тур, его длина и история улучшений).
|
||||||
\begin{itemize}
|
\item \textbf{Матрицы расстояний и феромона}: расстояния между городами предвычисляются один раз; феромон хранится в виде симметричной матрицы и инициализируется единицами с нулями на диагонали.
|
||||||
\item \texttt{initialize\_random\_population(pop\_size: int, cities: Cites) -> Population}
|
\item \textbf{Построение тура}: каждый муравей стартует в случайном городе и последовательно добавляет вершины. Выбор следующего города происходит по вероятности, пропорциональной $\tau^\alpha \cdot (1/d)^\beta$, где $\tau$ — феромон на ребре, $d$ — расстояние между городами.
|
||||||
\end{itemize}
|
\item \textbf{Обновление феромона}: после прохода всех муравьёв выполняется испарение $\tau \leftarrow (1-\rho)\tau$ и добавление феромона $q/L$ на рёбра их маршрутов, где $L$ — длина тура.
|
||||||
\item \textbf{Фитнесс-функция}: целевая функция принимает хромосому (маршрут) и возвращает скалярное значение фитнесса (длину пути). Для режима минимизации используется внутреннее преобразование при селекции (сдвиг и инверсия знака), что позволяет применять рулетку:
|
\item \textbf{Визуализация}: для отчёта сгенерированы PNG-файлы. График маршрута рисуется посредством собственного минимального генератора PNG (без сторонних библиотек), который строит линии по методу Брезенхема и сохраняет изображение в папку \texttt{lab6/report/img}.
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{eval\_population(population: Population, fitness\_func: FitnessFn) -> Fitnesses}
|
|
||||||
\item Логика режима минимизации в \texttt{genetic\_algorithm(config: GARunConfig) -> GARunResult}
|
|
||||||
\end{itemize}
|
|
||||||
\item \textbf{Селекция (рулетка)}: вероятности нормируются после сдвига на минимальное значение в поколении (устойчиво к отрицательным фитнессам). Функция:
|
|
||||||
\texttt{reproduction(population: Population, fitnesses: Fitnesses) -> Population}.
|
|
||||||
\item \textbf{Кроссинговер}: реализованы специализированные операторы для перестановок: PMX (Partially Mapped Crossover), OX (Ordered Crossover) и CX (Cycle Crossover). Кроссинговер выполняется попарно по перемешанной популяции с вероятностью $p_c$. Функции:
|
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{partially\_mapped\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]}
|
|
||||||
\item \texttt{ordered\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]}
|
|
||||||
\item \texttt{cycle\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]}
|
|
||||||
\item \texttt{crossover(population: Population, pc: float, crossover\_fn: CrossoverFn) -> Population}
|
|
||||||
\end{itemize}
|
|
||||||
\item \textbf{Мутация}: реализованы три типа мутаций для перестановок: обмен двух городов (swap), инверсия сегмента (inversion), вырезка и вставка города (insertion). Мутация применяется с вероятностью $p_m$. Функции:
|
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{swap\_mutation\_fn(chrom: Chromosome) -> Chromosome}
|
|
||||||
\item \texttt{inversion\_mutation\_fn(chrom: Chromosome) -> Chromosome}
|
|
||||||
\item \texttt{insertion\_mutation\_fn(chrom: Chromosome) -> Chromosome}
|
|
||||||
\item \texttt{mutation(population: Population, pm: float, mutation\_fn: MutationFn) -> Population}
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\item \textbf{Критерий остановки}: поддерживаются критерии по максимальному количеству поколений, повторению лучшего результата, достижению порогового значения фитнесса. Хранится история всех поколений. Проверка выполняется в функции:
|
|
||||||
|
|
||||||
\texttt{genetic\_algorithm(config: GARunConfig) -> GARunResult}.
|
|
||||||
\item \textbf{Визуализация}: реализована отрисовка маршрутов обхода городов на плоскости с отображением лучшей особи поколения. Функции:
|
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{plot\_tour(cities: list[tuple[float, float]], tour: list[int], ax: Axes)}
|
|
||||||
\item \texttt{save\_generation(generation: Generation, history: list[Generation], config: GARunConfig)}
|
|
||||||
\item \texttt{plot\_fitness\_history(result: GARunResult, save\_path: str | None) -> None}
|
|
||||||
\end{itemize}
|
|
||||||
\item \textbf{Элитизм}: поддерживается перенос лучших особей без изменения в следующее поколение (\texttt{elitism} параметр).
|
|
||||||
\item \textbf{Измерение времени}: длительность вычислений возвращается в миллисекундах как часть \texttt{GARunResult.time\_ms}.
|
|
||||||
\item \textbf{Файловая организация}: результаты экспериментов сохраняются в структуре \texttt{experiments/N/} с таблицами результатов. Задействованные функции:
|
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{clear\_results\_directory(results\_dir: str) -> None}
|
|
||||||
\item Функции для проведения экспериментов в модуле \texttt{expirements.py}
|
|
||||||
\end{itemize}
|
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
В модуле \texttt{expirements.py} задаются координаты городов и параметры экспериментов.
|
Для загрузки координат использован тот же код, что и в лабораторной работе №3: исходные точки читаются из \texttt{lab3/data.txt}, где в файле содержатся 38 уникальных городов.
|
||||||
Серийные запуски и сохранение результатов реализованы для исследования влияния параметров ГА на качество решения задачи коммивояжёра.
|
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\section{Результаты работы}
|
\section{Результаты работы}
|
||||||
|
|
||||||
=== Нужно обновить раздел ===
|
Алгоритм был запущен со следующими параметрами: 50 муравьёв, 400 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$, случайное зерно $7$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659.
|
||||||
|
|
||||||
На Рис.~\ref{fig:results} представлены результаты работы простого муравьиного алгоритма со следующими параметрами:
|
\begin{figure}[h!]
|
||||||
\begin{itemize}
|
\centering
|
||||||
\item $N = 500$ -- размер популяции.
|
\begin{minipage}{0.48\linewidth}
|
||||||
\item $p_c = 0.9$ -- вероятность кроссинговера.
|
\centering
|
||||||
\item $p_m = 0.3$ -- вероятность мутации.
|
\includegraphics[width=0.95\linewidth]{img/optimal_tour.png}
|
||||||
\item $2500$ -- максимальное количество поколений.
|
\caption{Оптимальный маршрут длиной 6659}
|
||||||
\item $3$ -- количество "элитных" особей, переносимых без изменения в следующее поколение.
|
\label{fig:optimal_result}
|
||||||
\item Partially mapped crossover - кроссовер.
|
\end{minipage}\hfill
|
||||||
\item Inversion mutation - мутация
|
\begin{minipage}{0.48\linewidth}
|
||||||
\end{itemize}
|
\centering
|
||||||
|
\includegraphics[width=0.95\linewidth]{img/aco_best_tour.png}
|
||||||
|
\caption{Лучший маршрут, найденный муравьиным алгоритмом (6662{,}35)}
|
||||||
|
\label{fig:aco_tour}
|
||||||
|
\end{minipage}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
На Рис.~\ref{fig:fitness_history} показан график изменения фитнесса по поколениям. Видно, что алгоритм постепенно сходится к минимально возможному значению фитнеса. Лучший маршрут был найден на поколнении №1896 (см. Рис.~\ref{fig:lastgen}).
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
% \begin{figure}[h!]
|
\includegraphics[width=0.9\linewidth]{img/aco_history.png}
|
||||||
% \centering
|
\caption{Сходимость длины лучшего тура по итерациям}
|
||||||
% \includegraphics[width=1\linewidth]{img/results/fitness_history.png}
|
\label{fig:aco_history}
|
||||||
% \caption{График изменения фитнесса по поколениям}
|
\end{figure}
|
||||||
% \label{fig:fitness_history}
|
|
||||||
% \end{figure}
|
|
||||||
|
|
||||||
\subsection{Сравнение с результатами лабораторной работы №3}
|
\subsection{Сравнение с результатами лабораторной работы №3}
|
||||||
|
|
||||||
=== Нужно написать раздел, ниже представлена часть отчёта из лаб3, чтобы было с чем сравнить ===
|
Для лабораторной работы №3 с генетическим алгоритмом лучший результат составил \textbf{6667{,}03} при популяции $N=500$, вероятностях $P_c=0{,}9$ и $P_m=0{,}5$. Муравьиный алгоритм показал более точное решение: длина тура \textbf{6662{,}35} против оптимального 6659. Разница с оптимумом составила 3{,}35 единицы (0{,}05\%), тогда как в лабораторной работе №3 отклонение было 8{,}03 (0{,}12\%).
|
||||||
|
|
||||||
Наилучшее найденное решение составило \textbf{6667.03} при параметрах $N=500$, $P_c=0.9$, $P_m=0.5$ за 1644 поколения. Это всего на \textbf{0.12\%} хуже оптимального значения 6659, что демонстрирует высокую эффективность алгоритма. Наихудшие результаты показала конфигурация с $N=10$, $P_c=0.7$, $P_m=0.3$ (лучший фитнес 6796.98), что на 2.07\% хуже оптимума. Малый размер популяции в 10 особей оказался недостаточным для стабильного поиска качественных решений — более половины конфигураций при $N=10$ вообще не нашли решение за 2500 поколений.
|
|
||||||
|
|
||||||
Наиболее быстрая конфигурация — $N=10$, $P_c=0.7$, $P_m=0.5$ — нашла решение за \textbf{201 мс} (503 поколения). Однако качество решения при таких параметрах нестабильно. Среди конфигураций с большой популяцией лучшее время показала $N=500$, $P_c=0.5$, $P_m=0.2$ — \textbf{5232 мс} (341 поколение), что является оптимальным балансом скорости и качества для больших популяций.
|
|
||||||
|
|
||||||
С ростом размера популяции наблюдается явное улучшение качества решений: при $N=10$ лучший результат 6762.97, при $N=500$ — 6667.03. Одновременно количество необходимых поколений снижается (с 503 до 341), но общее время выполнения растет линейно из-за увеличения числа особей в каждом поколении. Этот эффект объясняется тем, что большая популяция обеспечивает большее генетическое разнообразие, позволяя алгоритму быстрее находить оптимальные решения.
|
|
||||||
|
|
||||||
Что касается вероятности кроссовера, средние значения $P_c=0.6$--$0.8$ показывают стабильные результаты для всех размеров популяций. Экстремальные значения ($P_c=0.9$ или $1.0$) работают хорошо только при больших популяциях ($N \geq 100$), при малых — часто приводят к преждевременной сходимости (наблюдается много прочерков в таблицах). Это связано с тем, что высокая вероятность кроссовера при малой популяции быстро приводит к гомогенизации генофонда.
|
|
||||||
|
|
||||||
Анализ влияния вероятности мутации показал, что низкие значения $P_m=0.05$ неэффективны для малых популяций — недостаточно разнообразия для выхода из локальных минимумов. Умеренные значения $P_m=0.2$--$0.5$ демонстрируют лучшие результаты, обеспечивая баланс между эксплуатацией найденных решений и исследованием нового пространства поиска. Высокое значение $P_m=0.8$ часто приводит к расхождению алгоритма, так как слишком сильные изменения разрушают хорошие решения быстрее, чем алгоритм успевает их найти (многие конфигурации не нашли решение за отведенное время).
|
|
||||||
|
|
||||||
|
|
||||||
\newpage
|
|
||||||
\section{Ответ на контрольный вопрос}
|
|
||||||
|
|
||||||
\textbf{Вопрос}: Какие критерии окончания могут быть использованы в простом МА?
|
|
||||||
|
|
||||||
\textbf{Ответ}: В простом муравьином алгоритме могут использоваться следующие критерии завершения работы:
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item окончание при превышении заданного числа итераций;
|
|
||||||
\item окончание по достижению приемлемого решения;
|
|
||||||
\item окончание в случае, когда все муравьи начинают следовать одним и тем же путём.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
По скорости муравьиный алгоритм также оказался более экономичным: 400 итераций с 50 муравьями вместо 1644 поколений с популяцией 500 в генетическом подходе. Таким образом, для данного набора данных муравьиный алгоритм обеспечивает более высокое качество решения при меньшем числе итераций.
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\section*{Заключение}
|
\section*{Заключение}
|
||||||
\addcontentsline{toc}{section}{Заключение}
|
\addcontentsline{toc}{section}{Заключение}
|
||||||
|
|
||||||
=== Нужно обновить раздел ===
|
В ходе шестой лабораторной работы выполнена реализация простого муравьиного алгоритма для задачи коммивояжёра:
|
||||||
|
|
||||||
В ходе третьей лабораторной работы была успешно решена задача коммивояжера с использованием генетических алгоритмов для 38 городов Джибути:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item Изучен теоретический материал о представлениях туров (соседское, порядковое, путевое) и специализированных операторах кроссинговера и мутации для задачи коммивояжера;
|
\item Разработан модуль \texttt{aco.py} с конфигурацией алгоритма, построением туров, обновлением феромона и собственными средствами визуализации без сторонних библиотек.
|
||||||
\item Создана программная библиотека на языке Python с реализацией путевого представления хромосом, операторов PMX, OX и CX для кроссинговера, операторов swap, inversion и insertion для мутации, а также селекции методом рулетки с поддержкой элитизма;
|
\item Проведён численный эксперимент на данных из варианта 18 (38 городов Джибути); подобраны параметры $\alpha=1{,}2$, $\beta=5$, $\rho=0{,}5$, 50 муравьёв, 400 итераций.
|
||||||
\item Проведено исследование влияния параметров генетического алгоритма на качество и скорость нахождения решения для популяций размером 10, 50, 100 и 500 особей с различными значениями вероятностей кроссинговера и мутации;
|
\item Получено приближённое решение длиной 6662{,}35, что всего на 0{,}05\% хуже известного оптимума 6659 и лучше результата, достигнутого генетическим алгоритмом из лабораторной работы №3.
|
||||||
\item Получено решение с длиной маршрута 6667.03, отклоняющееся от оптимального значения 6659 всего на 0.12\%.
|
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user