lab6
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
# Attention!
|
|
||||||
|
|
||||||
lab5 is fully AI generated slop.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Лабораторная работа 6
|
|
||||||
|
|
||||||
## Как получить изображения и данные
|
|
||||||
1. Убедитесь, что установлен Python 3 c библиотекой `matplotlib` (остальное берётся из стандартной библиотеки). При необходимости установите её командой:
|
|
||||||
```bash
|
|
||||||
pip install matplotlib
|
|
||||||
```
|
|
||||||
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`.
|
|
||||||
37
lab6/aco.py
37
lab6/aco.py
@@ -24,19 +24,14 @@ def build_distance_matrix(cities: Sequence[City]) -> list[list[float]]:
|
|||||||
|
|
||||||
|
|
||||||
def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None:
|
def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None:
|
||||||
ordered = [cities[i] for i in tour] + [cities[tour[0]]]
|
x = [cities[i][0] for i in tour]
|
||||||
xs, ys = zip(*ordered)
|
y = [cities[i][1] for i in tour]
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(7, 7))
|
fig, ax = plt.subplots(figsize=(7, 7))
|
||||||
ax.plot(xs, ys, "-o", color="#1f77b4", markersize=4, linewidth=1.5)
|
ax.plot(x + [x[0]], y + [y[0]], "k-", linewidth=1)
|
||||||
city_xs, city_ys = zip(*cities)
|
ax.plot(x, y, "ro", markersize=4)
|
||||||
ax.scatter(city_xs, city_ys, s=18, color="#d62728", zorder=5)
|
|
||||||
|
|
||||||
ax.set_xlabel("X")
|
ax.axis("equal")
|
||||||
ax.set_ylabel("Y")
|
|
||||||
ax.set_title("Маршрут тура")
|
|
||||||
ax.set_aspect("equal", adjustable="box")
|
|
||||||
ax.grid(True, linestyle="--", alpha=0.3)
|
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
fig.savefig(save_path, dpi=220)
|
fig.savefig(save_path, dpi=220)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
@@ -46,14 +41,16 @@ def plot_history(best_lengths: Sequence[float], save_path: str) -> None:
|
|||||||
if not best_lengths:
|
if not best_lengths:
|
||||||
return
|
return
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(8, 3.8))
|
iterations = list(range(len(best_lengths)))
|
||||||
ax.plot(best_lengths, color="#111111", linewidth=1.4)
|
|
||||||
ax.set_xlabel("Итерация")
|
fig, ax = plt.subplots(figsize=(10, 6))
|
||||||
ax.set_ylabel("Длина лучшего тура")
|
ax.plot(iterations, best_lengths, linewidth=2, color="blue")
|
||||||
ax.set_title("Сходимость ACO")
|
|
||||||
ax.grid(True, linestyle="--", alpha=0.4)
|
ax.set_xlabel("Итерация", fontsize=12)
|
||||||
fig.tight_layout()
|
ax.set_ylabel("Длина лучшего тура", fontsize=12)
|
||||||
fig.savefig(save_path, dpi=220)
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
fig.savefig(save_path, dpi=150, bbox_inches="tight")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@@ -153,7 +150,9 @@ class AntColonyOptimizer:
|
|||||||
|
|
||||||
best_history.append(best_length)
|
best_history.append(best_length)
|
||||||
|
|
||||||
return ACOResult(best_tour=best_tour, best_length=best_length, history=best_history)
|
return ACOResult(
|
||||||
|
best_tour=best_tour, best_length=best_length, history=best_history
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_aco(config: ACOConfig) -> ACOResult:
|
def run_aco(config: ACOConfig) -> ACOResult:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ cities = list(cities)
|
|||||||
config = ACOConfig(
|
config = ACOConfig(
|
||||||
cities=cities,
|
cities=cities,
|
||||||
n_ants=50,
|
n_ants=50,
|
||||||
n_iterations=400,
|
n_iterations=50,
|
||||||
alpha=1.2,
|
alpha=1.2,
|
||||||
beta=5.0,
|
beta=5.0,
|
||||||
rho=0.5,
|
rho=0.5,
|
||||||
@@ -29,7 +29,9 @@ print(f"Лучший тур: {result.best_tour}")
|
|||||||
results_dir = os.path.join(os.path.dirname(__file__), "report", "img")
|
results_dir = os.path.join(os.path.dirname(__file__), "report", "img")
|
||||||
os.makedirs(results_dir, exist_ok=True)
|
os.makedirs(results_dir, exist_ok=True)
|
||||||
|
|
||||||
plot_tour(config.cities, result.best_tour, os.path.join(results_dir, "aco_best_tour.png"))
|
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"))
|
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:
|
with open(os.path.join(results_dir, "aco_best_tour.txt"), "w", encoding="utf-8") as f:
|
||||||
|
|||||||
1
lab6/report/.gitignore
vendored
1
lab6/report/.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
!**/
|
!**/
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!report.tex
|
!report.tex
|
||||||
|
!img/**/*.png
|
||||||
BIN
lab6/report/img/aco_best_tour.png
Normal file
BIN
lab6/report/img/aco_best_tour.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
lab6/report/img/aco_history.png
Normal file
BIN
lab6/report/img/aco_history.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
lab6/report/img/best_lab3.png
Normal file
BIN
lab6/report/img/best_lab3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
lab6/report/img/optimal_tour.png
Normal file
BIN
lab6/report/img/optimal_tour.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -256,62 +256,115 @@
|
|||||||
\newpage
|
\newpage
|
||||||
\section{Особенности реализации}
|
\section{Особенности реализации}
|
||||||
|
|
||||||
Код решения собран в модуле \texttt{lab6/aco.py}. Ниже приведены ключевые элементы реализации с небольшими листингами (язык Python) и пояснениями.
|
Код решения собран в модуле \texttt{lab6/aco.py}. Реализация использует объектно-ориентированный подход с явной типизацией через современные аннотации типов Python (PEP 604). Ниже приведены ключевые элементы реализации с сигнатурами функций и пояснениями.
|
||||||
|
|
||||||
\subsection{Структуры данных и инициализация}
|
\subsection{Структуры данных конфигурации и результата}
|
||||||
Конфигурация алгоритма и структура результата оформлены через \texttt{dataclass}; в конфиге задаются параметры $\alpha$, $\beta$, $\rho$, $q$, число муравьёв и итераций, а также зерно генератора случайных чисел:
|
Конфигурация алгоритма оформлена через \texttt{@dataclass} и включает все параметры, влияющие на поведение ACO:
|
||||||
\begin{lstlisting}[language=Python]
|
\begin{lstlisting}[language=Python]
|
||||||
@dataclass
|
@dataclass
|
||||||
class ACOConfig:
|
class ACOConfig:
|
||||||
cities: Sequence[City]
|
cities: Sequence[City] # список координат городов
|
||||||
n_ants: int
|
n_ants: int # число муравьев
|
||||||
n_iterations: int
|
n_iterations: int # число итераций
|
||||||
alpha: float = 1.0
|
alpha: float = 1.0 # влияние феромона
|
||||||
beta: float = 5.0
|
beta: float = 5.0 # влияние эвристики (1/расстояние)
|
||||||
rho: float = 0.5
|
rho: float = 0.5 # коэффициент испарения
|
||||||
q: float = 1.0
|
q: float = 1.0 # константа для отложения феромона
|
||||||
seed: int | None = None
|
seed: int | None = None # зерно ГСЧ (воспроизводимость)
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
При создании \texttt{AntColonyOptimizer} матрица расстояний вычисляется один раз, а феромон инициализируется единицами (с нулями на диагонали), чтобы не допускать самопереходов.
|
Результат работы алгоритма представлен структурой:
|
||||||
|
|
||||||
\subsection{Построение и оценка тура}
|
|
||||||
Каждый муравей стартует в случайном городе и расширяет маршрут, используя вероятностный выбор следующей вершины, где вес ребра определяется как $\tau^\alpha \cdot (1/d)^\beta$:
|
|
||||||
\begin{lstlisting}[language=Python]
|
\begin{lstlisting}[language=Python]
|
||||||
def _choose_next_city(self, current: int, unvisited: set[int]) -> int:
|
@dataclass
|
||||||
candidates = list(unvisited)
|
class ACOResult:
|
||||||
weights = []
|
best_tour: Tour # индексы городов в порядке обхода
|
||||||
for nxt in candidates:
|
best_length: float # длина лучшего маршрута
|
||||||
tau = self.pheromone[current][nxt] ** self.config.alpha
|
history: List[float] # история длин по итерациям
|
||||||
eta = (1.0 / (self.dist_matrix[current][nxt] + 1e-12)) ** self.config.beta
|
\end{lstlisting}
|
||||||
weights.append(tau * eta)
|
|
||||||
return random.choices(candidates, weights=weights, k=1)[0]
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
Длина тура вычисляется как сумма евклидовых расстояний между последовательными городами, включая возврат в исходную точку.
|
\subsection{Класс AntColonyOptimizer и инициализация}
|
||||||
|
Основная логика инкапсулирована в классе \texttt{AntColonyOptimizer}, который принимает конфигурацию при создании:
|
||||||
\subsection{Обновление феромона}
|
|
||||||
После завершения итерации выполняется испарение и добавление феромона $q/L$ на рёбра маршрутов всех муравьёв. Короткие маршруты оставляют более сильный след и начинают доминировать в вероятностном выборе:
|
|
||||||
\begin{lstlisting}[language=Python]
|
\begin{lstlisting}[language=Python]
|
||||||
for i in range(len(self.pheromone)):
|
class AntColonyOptimizer:
|
||||||
for j in range(len(self.pheromone)):
|
def __init__(self, config: ACOConfig)
|
||||||
self.pheromone[i][j] *= 1 - self.config.rho
|
\end{lstlisting}
|
||||||
|
|
||||||
for tour, length in zip(tours, lengths):
|
В конструкторе выполняются следующие действия:
|
||||||
deposit = self.config.q / length
|
\begin{itemize}
|
||||||
for i in range(len(tour)):
|
\item инициализация генератора случайных чисел через \texttt{random.seed(config.seed)} для обеспечения воспроизводимости экспериментов;
|
||||||
a, b = tour[i], tour[(i + 1) % len(tour)]
|
\item вычисление матрицы расстояний между всеми городами с помощью \texttt{build\_distance\_matrix};
|
||||||
self.pheromone[a][b] += deposit
|
\item создание матрицы феромона размером $n \times n$, где все недиагональные элементы инициализируются единицами, а диагональные — нулями (для предотвращения самопереходов).
|
||||||
self.pheromone[b][a] += deposit
|
\end{itemize}
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
\subsection{Загрузка данных и визуализация}
|
\subsection{Построение тура муравьём}
|
||||||
Координаты городов считываются из \texttt{lab3/data.txt}; в файле содержатся 38 уникальных точек. Для визуализации используется \texttt{matplotlib}, что позволяет сохранить исходную ориентацию системы координат (ось $Y$ направлена вверх) и избежать инверсии рисунка. Функция \texttt{plot\_tour} строит ломаную линию обхода, подсвечивает вершины и сохраняет результат в \texttt{lab6/report/img}. График сходимости \texttt{plot\_history} отображает изменение лучшей длины тура по итерациям с сеткой и подписями осей.
|
Каждый муравей строит полный гамильтонов цикл, начиная со случайно выбранного стартового города. Ключевой метод выбора следующего города:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def _choose_next_city(self, current: int,
|
||||||
|
unvisited: set[int]) -> int
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Метод реализует вероятностный выбор на основе формулы:
|
||||||
|
\[
|
||||||
|
p_{ij} = \frac{[\tau_{ij}]^\alpha \cdot [\eta_{ij}]^\beta}{\sum_{k \in \text{unvisited}} [\tau_{ik}]^\alpha \cdot [\eta_{ik}]^\beta}
|
||||||
|
\]
|
||||||
|
где $\tau_{ij}$ — уровень феромона на ребре $(i,j)$, а $\eta_{ij} = 1/d_{ij}$ — эвристическая привлекательность (обратная величина расстояния). К расстоянию добавляется малая константа $10^{-12}$ для численной стабильности при делении. Финальный выбор осуществляется через \texttt{random.choices} с вычисленными вероятностями.
|
||||||
|
|
||||||
|
Построение полного тура выполняет метод:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def _build_tour(self, start: int) -> Tour
|
||||||
|
\end{lstlisting}
|
||||||
|
Начиная со стартового города, муравей последовательно выбирает следующие непосещённые города до тех пор, пока множество \texttt{unvisited} не станет пустым.
|
||||||
|
|
||||||
|
Вычисление длины построенного тура:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def _tour_length(self, tour: Sequence[int]) -> float
|
||||||
|
\end{lstlisting}
|
||||||
|
Метод суммирует расстояния между последовательными городами в туре, включая замыкающее ребро от последнего города к первому, используя предвычисленную матрицу расстояний.
|
||||||
|
|
||||||
|
\subsection{Основной цикл алгоритма}
|
||||||
|
Главный метод запуска оптимизации:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def run(self) -> ACOResult
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
На каждой из \texttt{n\_iterations} итераций выполняются следующие шаги:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Построение туров}: каждый из \texttt{n\_ants} муравьёв создаёт свой маршрут, начиная со случайного города. Вычисляется длина каждого маршрута, и глобально лучший тур обновляется при обнаружении более короткого.
|
||||||
|
\item \textbf{Испарение феромона}: все элементы матрицы феромона умножаются на $(1 - \rho)$, моделируя естественное испарение. Это предотвращает неограниченный рост концентрации феромона и позволяет алгоритму «забывать» плохие решения.
|
||||||
|
\item \textbf{Отложение феромона}: для каждого муравья вычисляется вклад $\Delta\tau = q/L$, где $L$ — длина его маршрута. Этот вклад добавляется симметрично на оба направления каждого ребра в туре. Таким образом, короткие маршруты откладывают больше феромона.
|
||||||
|
\item \textbf{Запись истории}: лучшая на данный момент длина добавляется в список \texttt{history} для последующего анализа сходимости.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
По завершении всех итераций метод возвращает \texttt{ACOResult} с лучшим найденным туром, его длиной и историей оптимизации.
|
||||||
|
|
||||||
|
\subsection{Точка входа}
|
||||||
|
Для удобства использования предоставлена функция верхнего уровня:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def run_aco(config: ACOConfig) -> ACOResult
|
||||||
|
\end{lstlisting}
|
||||||
|
Она создаёт экземпляр оптимизатора и запускает алгоритм, возвращая результат.
|
||||||
|
|
||||||
|
\subsection{Визуализация}
|
||||||
|
Модуль включает две функции для визуализации результатов средствами \texttt{matplotlib}:
|
||||||
|
|
||||||
|
Функция построения графика маршрута:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def plot_tour(cities: Sequence[City], tour: Sequence[int],
|
||||||
|
save_path: str) -> None
|
||||||
|
\end{lstlisting}
|
||||||
|
Отображает города в виде точек и соединяет их ломаной линией в порядке обхода, включая возврат к начальной точке. Используется соотношение сторон \texttt{aspect="equal"} для сохранения геометрии, сетка для лучшей читаемости координат. Результат сохраняется в PNG с разрешением 220 DPI.
|
||||||
|
|
||||||
|
Функция построения графика сходимости:
|
||||||
|
\begin{lstlisting}[language=Python]
|
||||||
|
def plot_history(best_lengths: Sequence[float],
|
||||||
|
save_path: str) -> None
|
||||||
|
\end{lstlisting}
|
||||||
|
Строит линейный график изменения длины лучшего найденного тура по итерациям. Позволяет визуально оценить скорость сходимости и стабильность алгоритма.
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\section{Результаты работы}
|
\section{Результаты работы}
|
||||||
|
|
||||||
Алгоритм был запущен со следующими параметрами: 50 муравьёв, 400 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$, случайное зерно $7$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659.
|
Алгоритм был запущен со следующими параметрами: 50 муравьёв, 50 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659.
|
||||||
|
|
||||||
\begin{figure}[h!]
|
\begin{figure}[h!]
|
||||||
\centering
|
\centering
|
||||||
@@ -336,11 +389,45 @@ for tour, length in zip(tours, lengths):
|
|||||||
\label{fig:aco_history}
|
\label{fig:aco_history}
|
||||||
\end{figure}
|
\end{figure}
|
||||||
|
|
||||||
\subsection{Сравнение с результатами лабораторной работы №3}
|
\subsection{Сравнение с результатами лабораторной работы~№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\%).
|
Для лабораторной работы №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\%).
|
||||||
|
|
||||||
По скорости муравьиный алгоритм также оказался более экономичным: 400 итераций с 50 муравьями вместо 1644 поколений с популяцией 500 в генетическом подходе. Таким образом, для данного набора данных муравьиный алгоритм обеспечивает более высокое качество решения при меньшем числе итераций.
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\begin{minipage}{0.48\linewidth}
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.95\linewidth]{img/best_lab3.png}
|
||||||
|
\caption{Лучший маршрут из лабораторной работы №3 (ГА): длина 6667{,}03}
|
||||||
|
\label{fig:lab3_best}
|
||||||
|
\end{minipage}\hfill
|
||||||
|
\begin{minipage}{0.48\linewidth}
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.95\linewidth]{img/aco_best_tour.png}
|
||||||
|
\caption{Лучший маршрут лабораторной работы №6 (МА): длина 6662{,}35}
|
||||||
|
\label{fig:lab6_best}
|
||||||
|
\end{minipage}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.43\linewidth]{img/optimal_tour.png}
|
||||||
|
\caption{Оптимальный маршрут длиной 6659}
|
||||||
|
\label{fig:optimal_comparison}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Ответ на контрольный вопрос}
|
||||||
|
|
||||||
|
\textbf{Вопрос}: Какие критерии окончания могут быть использованы в простом МА?
|
||||||
|
|
||||||
|
\textbf{Ответ}: В простом муравьином алгоритме могут использоваться следующие критерии завершения работы:
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item окончание при превышении заданного числа итераций;
|
||||||
|
\item окончание по достижению приемлемого решения;
|
||||||
|
\item окончание в случае, когда все муравьи начинают следовать одним и тем же путём.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\section*{Заключение}
|
\section*{Заключение}
|
||||||
|
|||||||
Reference in New Issue
Block a user