本博客封面由
ChatGPT + DALL·E 2
共同创作而成。
前言
本篇是智能算法(Python复现)专栏的第六篇文章,主要是对蚁群算法的补充。
在上篇博客中对蚁群算法的原理进行了简单的介绍,并用python
实现蚁群算法在求函数极值方面的应用,但仔细研读一番发现重点没有突出来,而且与原始TSP
问题中的蚁群算法又较大区别,感觉只涉及到蚁群算法的外壳,并没有触及灵魂。
因此,在本篇中对蚁群算法在求函数极值方面的应用进行一个详细补充,并尽可能的触及蚁群算法的灵魂。
论文参考:应用蚁群算法求解函数所有极值(知网)
1. 改进方法
设一维函数
y
=
f
(
x
)
y=f(x)
y=f(x)的定义域为
[
a
,
b
]
[a, b]
[a,b]。
(1)
将区间
[
a
,
b
]
[a, b]
[a,b]划分成若干个长度相等的子区间,这些子区间记为
{
I
1
,
I
2
,
.
.
.
,
I
n
}
\{I_1,I_2,...,I_n\}
{I1,I2,...,In},取区间
I
i
I_i
Ii的中点记为
x
i
x_i
xi;
(2)
设与区间
I
i
I_i
Ii相邻的区间为
I
i
+
1
I_{i+1}
Ii+1,假设
I
i
I_i
Ii和
I
i
+
1
I_{i+1}
Ii+1之间有一条虚拟的边
e
(
I
i
+
1
,
I
i
+
1
)
e(I_{i+1}, I_{i+1})
e(Ii+1,Ii+1),该边的权重(虚拟距离)和
f
(
x
i
)
−
f
(
x
i
+
1
)
f(x_i) - f(x_{i+1})
f(xi)−f(xi+1)的大小有关,
f
(
x
i
)
−
f
(
x
i
+
1
)
f(x_i) - f(x_{i+1})
f(xi)−f(xi+1)越大,蚂蚁从区间
I
i
I_i
Ii转移到区间
I
i
+
1
I_{i+1}
Ii+1的可能性就越大;
(3)
蚂蚁从区间
I
i
I_i
Ii转移到区间
I
i
+
1
I_{i+1}
Ii+1后,要留下信息素,所留信息素的大小是一个与
f
(
x
i
)
−
f
(
x
i
+
1
)
f(x_i) - f(x_{i+1})
f(xi)−f(xi+1)有关的量,区间
I
i
+
1
I_{i+1}
Ii+1的信息素越多,就越吸引相邻区间蚂蚁向其转移;
(4)
蚂蚁经过许多次转移之后,有的区间含有许多蚂蚁,而有的区间则不含蚂蚁。那些含有蚂蚁的区间正是包含极值点的区间,不含蚂蚁的区间,不大可能包含极值点;
(5)
取出包含蚂蚁的区间,并将它们重新细化,重复上述搜索过程,直到细化后的区间足够小。
最后,蚂蚁都停留在了极值点附近,蚂蚁所在的区间的中点位置正是极值点的位置。
2. 蚁群初始化
首先将问题的定义域
[
a
,
b
]
[a, b]
[a,b]进行
n
n
n等分,等分后的各区间长度为
σ
=
b
−
a
n
\sigma = \frac {b-a} {n}
σ=nb−a各区间记为
{
I
1
,
I
2
,
.
.
.
,
I
n
}
\{I_1,I_2,...,I_n\}
{I1,I2,...,In},),其中
I
i
(
i
=
1
,
2
,
.
.
,
n
)
I_i(i=1,2,..,n)
Ii(i=1,2,..,n)表示第
i
i
i个区间,则
I
i
=
[
a
+
(
i
−
1
)
σ
,
a
+
i
σ
]
I_i=[a+(i-1)\sigma, a+i\sigma]
Ii=[a+(i−1)σ,a+iσ],表示区间
I
i
I_i
Ii的左右两个端点,区间
I
i
I_i
Ii的中点位置记为
x
i
x_i
xi,则
x
i
=
a
+
(
i
−
1
2
)
σ
x_i=a+(i-\frac {1} {2})\sigma
xi=a+(i−21)σ 假设蚁群中共有
m
m
m只蚂蚁,记为
{
a
1
,
a
2
,
.
.
.
,
a
n
}
\{a_1,a_2,...,a_n\}
{a1,a2,...,an}一般情况下,
m
≥
n
m \geq n
m≥n。在初始时,为每只蚂蚁随机分配一个区间,蚂蚁的位置即为区间中点的位置。
τ
i
(
t
)
\tau _i(t)
τi(t)表示
t
t
t时刻子区间
i
i
i上的信息素,初始时各个子区间的信息素浓度相同,默认为1.0
;
Δ
τ
i
(
t
)
\Delta \tau _i(t)
Δτi(t)表示
t
t
t时刻子区间
i
i
i上的信息素增量,初始时各个子区间的信息素增量也相同,默认为0
。
3. 蚂蚁移动策略
假设
n
e
i
g
h
b
o
r
(
I
i
)
neighbor(I_i)
neighbor(Ii)表示与区间
I
i
I_i
Ii邻近的区间集合,则对于一维函数
n
e
i
g
h
b
o
r
(
I
i
)
=
{
{
I
i
+
1
}
,
i
=
1
{
I
i
−
1
,
I
i
+
1
}
,
i
=
2
,
3
,
.
.
.
,
n
−
1
{
I
i
−
1
}
,
i
=
n
neighbor(I_i) = \begin{cases} \{I_{i+1}\}, & i=1\\ \{I_{i-1}, I_{i+1}\}, & i=2,3,...,n-1\\ \{I_{i-1}\}, & i=n \end{cases}
neighbor(Ii)=⎩
⎨
⎧{Ii+1},{Ii−1,Ii+1},{Ii−1},i=1i=2,3,...,n−1i=n 位于区间
I
i
I_i
Ii的蚂蚁向其邻近区间
I
j
I_j
Ij进行转移,假设
I
i
I_i
Ii和
I
j
I_j
Ij之间有一条虚拟的边
e
(
I
i
,
I
j
)
e(I_i,I_j)
e(Ii,Ij),该边的权重为
∣
f
(
x
i
)
−
f
(
x
j
)
∣
\bigg| f(x_i) - f(x_j) \bigg|
f(xi)−f(xj)
,则启发函数
η
i
j
=
∣
f
(
x
i
)
−
f
(
x
j
)
∣
\eta_{ij} = \bigg| f(x_i) - f(x_j) \bigg|
ηij=
f(xi)−f(xj)
设蚂蚁
a
k
a_k
ak当前处于区间
I
i
I_i
Ii,若
f
(
x
i
)
−
f
(
x
j
)
>
0
f(x_i) - f(x_j) > 0
f(xi)−f(xj)>0,表示当前区间
I
i
I_i
Ii上的值较大,蚂蚁
a
k
a_k
ak就可以转移到其邻近区间
I
j
I_j
Ij;否则,蚂蚁不向其转移。设用
a
l
l
o
w
e
d
k
allowed_k
allowedk表示蚂蚁
a
k
a_k
ak下一步可以转移的子区间的集合。
用
p
i
j
k
(
t
)
p_{ij}^k(t)
pijk(t)表示第
t
t
t次循环蚂蚁
a
k
a_k
ak从子区间
I
i
I_i
Ii转移到子区间
I
j
I_j
Ij的概率,模仿基本蚁群算法,定义蚂蚁
a
k
a_k
ak的转移概率如下:
p
i
j
k
(
t
)
=
{
τ
j
α
(
t
)
η
i
j
β
∑
h
∈
a
l
l
o
w
e
d
k
τ
j
α
(
t
)
η
i
h
β
,
j
∈
a
l
l
o
w
e
d
k
0
,
j
∉
a
l
l
o
w
e
d
k
p_{ij}^k(t) = \begin{cases} \frac {\tau_j^{\alpha}(t) \ \eta_{ij}^{\beta}} {\sum_{h\in allowed_k} \tau_j^{\alpha}(t) \ \eta_{ih}^{\beta}}, & j \in allowed_k\\ \\ 0, & j \notin allowed_k \end{cases}
pijk(t)=⎩
⎨
⎧∑h∈allowedkτjα(t) ηihβτjα(t) ηijβ,0,j∈allowedkj∈/allowedk 其中,
α
\alpha
α为信息启发式因子(信息素重要程度因子),
β
\beta
β为期望启发式因子(启发函数重要程度因子),
τ
j
\tau _j
τj为子区间
I
i
{I_i}
Ii的信息素浓度。
4. 信息素更新
如果蚂蚁
a
k
a_k
ak依概率从子区间
I
i
I_i
Ii转移到了子区间
I
j
I_j
Ij,那么蚂蚁
a
k
a_k
ak就会在区间留下信息素,所留信息素的量用
Δ
τ
j
k
(
t
)
\Delta \tau_j^{k}(t)
Δτjk(t)表示,则
Δ
τ
j
k
(
t
)
=
C
(
f
(
x
i
)
−
f
(
x
j
)
)
\Delta \tau_j^{k}(t) = C\bigg( f(x_i) - f(x_j) \bigg)
Δτjk(t)=C(f(xi)−f(xj)) 其中,
C
C
C表示一个常量,
f
(
x
i
)
−
f
(
x
j
)
f(x_i) - f(x_j)
f(xi)−f(xj)越大,表示蚂蚁
a
k
a_k
ak留下的信息素就越多,就越吸引其他蚂蚁向区间
I
j
I_j
Ij转移。
所有转移到区间
I
j
I_j
Ij的蚂蚁都要留下相应的信息素,假设在第
t
t
t次循环,有
b
b
b只蚂蚁转移到了区间
I
j
I_j
Ij,则这
b
b
b只蚂蚁在区间
I
j
I_j
Ij上所留信息素总和为
Δ
τ
j
(
t
)
=
∑
c
=
1
b
Δ
τ
j
c
(
t
)
\Delta \tau_j(t) = \sum_{c=1} ^{b} \Delta \tau_j^{c}(t)
Δτj(t)=c=1∑bΔτjc(t) 所有蚂蚁完成一次邻近区间转移后,要对信息素进行更新处
理。区间
I
j
I_j
Ij的信息素浓度
τ
j
\tau_j
τj更新为
τ
j
(
t
+
1
)
=
(
1
−
1
ρ
)
τ
j
(
t
)
+
Δ
τ
j
(
t
)
\tau_j(t+1) = (1-1\rho)\tau_j(t) + \Delta \tau_j(t)
τj(t+1)=(1−1ρ)τj(t)+Δτj(t) 其中,
ρ
\rho
ρ表示信息素挥发系数。
5. 缩小蚁群搜索空间
函数值较小的区间含有的信息素会较多,更容易吸引蚂蚁向其转移。经过一些循环,当所有蚂蚁都停止转移的时候,蚁群分布就会出现这样的特点:所有蚂蚁都分布在极值点较小的区间,而其他区间则没有蚂蚁,即含有蚂蚁的区间包含极小值点。
取出这些包含蚂蚁的区间,并将这些区间重新细化,在下一次循环时,让蚁群在这些区间重新搜索极小值点。如此循环下去,蚁群的搜索范围就会越来越小,当细化后的区间足够小的时候,所有蚂蚁就会停留在极值点附近,蚂蚁所停留的区间的中点位置就是极值点的位置。
论文中设了一个阈值,毕竟不可能无限细分下去,当区间步长小于阈值时,即停止细分区间,搜索结束。在本文的实现中并没有采取论文中的操作,而是在初始时直接采取一个较小的划分区间步长,这样搜索出的最优解很接近于理论最优值。
6. 代码实现
# -*- coding:utf-8 -*-
# Author: xiayouran
# Email: youran.xia@foxmail.com
# Datetime: 2023/5/6 14:59
# Filename: ant_colony_optimization.py
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from base_algorithm import BaseAlgorithm
__all__ = ['AntColonyOptimization']
class Ant:
def __init__(self):
self.id = None # 蚂蚁所在区间的id
self.eta = None # 启发式信息, 存放本区间与其左右两个区间的函数差值
self.tran_prob = None # 状态转移概率
class Interval:
def __init__(self):
self.position = None # 该区间上的中点
self.tau = 1. # 信息素浓度值
self.delta_tau = 0. # 信息素浓度增量
self.count = 0 # 此区间上蚂蚁的数量
class AntColonyOptimization(BaseAlgorithm):
def __init__(self, population_size=100, max_iter=200, alpha=1.5, beta=0.8, rho=0.3, epsilon=1e-4,
q=1.0, step=0.01, x_range=(0, 5), seed=10086):
super(AntColonyOptimization, self).__init__()
self.__population_size = population_size # 蚂蚁种群大小
self.__max_iter = max_iter # 最大迭代次数
self.__alpha = alpha # 信息素重要程度因子
self.__beta = beta # 启发函数重要程度因子
self.__rho = rho # 信息素蒸发系数
self.__epsilon = epsilon # 停止搜索门限
self.__q = q # 信息素释放增量系数, 常量C
self.__step = step # 子区间间隔
self.__x_range = x_range # 变量x的定义域
self.__population = [] # 蚁群
self.__interval_set = [] # 区间集合(对应TSP问题中的city)
self.__tabu = [] # 禁忌表
self.__seed = seed
self.optimal_solution = None
np.random.seed(seed)
def init_interval_set(self):
for left_point in np.arange(*self.__x_range, self.__step):
interval = Interval()
interval.position = left_point + self.__step / 2 # 区间中点
self.__interval_set.append(interval)
if len(self.__interval_set) > self.__population_size:
tmp_size = self.__population_size
self.__population_size = int(np.ceil(len(self.__interval_set) / self.__population_size) * self.__population_size)
print("Suggest a larger value for population_size, the value for population_size has been "
"changed from {} to {}".format(tmp_size, self.__population_size))
def init_tabu(self):
self.__tabu = np.zeros(shape=(len(self.__interval_set), 2)) # 初始禁忌表, 当前点与左右两坐标
# TSP中的禁忌表的shape是(m, n), 其中m为蚂蚁数量, n为城市数量, 确保每只蚂蚁仅能访问城市一次
def update_eta(self, ant):
index = ant.id
interval = self.__interval_set[index]
ant.eta = []
if index == 0:
ant.eta.append(0)
ant.eta.append(self.problem_function(interval.position) - self.problem_function(
interval.position + self.__step))
elif index == len(self.__interval_set) - 1:
ant.eta.append(self.problem_function(interval.position) - self.problem_function(
interval.position - self.__step))
ant.eta.append(0)
else:
ant.eta.append(self.problem_function(interval.position) - self.problem_function(
interval.position - self.__step)) # 当前区间(中点)与左邻居区间(中点)的差值
ant.eta.append(self.problem_function(interval.position) - self.problem_function(
interval.position + self.__step)) # 当前区间(中点)与右邻居区间(中点)的差值
def update_tabu(self, ant):
index = ant.id
if ant.eta[0] > 0:
self.__tabu[index, 0] = 1 # 表示左子区间值较小, 可以跳转
if ant.eta[1] > 0:
self.__tabu[index, 1] = 1 # 表示右子区间值较小, 可以跳转
def init_population(self):
# 初始化区间集合
self.init_interval_set()
# 初始化禁忌表
self.init_tabu()
for i in range(self.__population_size):
index = np.random.choice(range(len(self.__interval_set))) # 随机选择一个区间
# index = i
interval = self.__interval_set[index]
interval.count += 1
ant = Ant()
ant.id = index
# 更新eta
self.update_eta(ant)
# 更新禁忌表tabu
self.update_tabu(ant)
# 更新蚂蚁的状态转移概率
ant.tran_prob = interval.tau * ant.eta[0] / (interval.tau * ant.eta[0] + interval.tau * ant.eta[1]) # 蚂蚁向左区间跳转概率
self.__population.append(ant)
def skip_left(self, ant):
index = ant.id
interval = self.__interval_set[index]
interval.count -= 1 # 此区间蚂蚁数-1
left_interval = self.__interval_set[index - 1]
left_interval.count += 1 # 左区间蚂蚁数+1
ant.id -= 1 # 蚂蚁跳转至左区间
left_interval.delta_tau = left_interval.delta_tau + self.__q * ant.eta[0]
def skip_right(self, ant):
index = ant.id
interval = self.__interval_set[index]
interval.count -= 1 # 此区间蚂蚁数-1
right_interval = self.__interval_set[index + 1]
right_interval.count += 1 # 右区间蚂蚁数+1
ant.id += 1 # 蚂蚁跳转至右区间
right_interval.delta_tau = right_interval.delta_tau + self.__q * ant.eta[1]
def search_local_optimal_solution(self):
flag = np.ones(self.__population_size)
while np.sum(flag):
for i, ant in enumerate(self.__population):
index = ant.id
if self.__tabu[index, 0] and not self.__tabu[index, 1]:
# 蚂蚁可以向左区间跳转
self.skip_left(ant)
elif not self.__tabu[index, 0] and self.__tabu[index, 1]:
# 蚂蚁可以向右区间跳转
self.skip_right(ant)
elif self.__tabu[index, 0] and self.__tabu[index, 1]:
# 两个区间都可以跳转, 计算一下蚂蚁的状态转移概率
if ant.tran_prob > np.random.rand():
# 蚂蚁向左区间跳转
self.skip_left(ant)
else:
# 蚂蚁向右区间跳转
self.skip_right(ant)
else:
flag[i] = 0 # 表示此蚂蚁不再进行跳转了
self.update_eta(ant) # 更新eta
self.update_tabu(ant) # 更新禁忌表
for interval in self.__interval_set:
# 更新区间上的信息素
interval.tau = (1 - self.__rho) * interval.tau + interval.delta_tau
def print_local_optimal_solution(self):
print('local optimal solution:')
local_optimal_solution = {}
best_point = np.inf
for ant in self.__population:
index = ant.id
if not local_optimal_solution.get(index, ''):
local_optimal_solution[index] = (self.__interval_set[index].position,
self.problem_function(self.__interval_set[index].position))
print(local_optimal_solution[index])
if best_point > local_optimal_solution[index][1]:
best_point = local_optimal_solution[index][1]
self.optimal_solution = local_optimal_solution[index]
def solution(self):
self.init_population()
self.search_local_optimal_solution()
self.print_local_optimal_solution()
print('the optimal solution is', self.optimal_solution)
if __name__ == "__main__":
algo = AntColonyOptimization()
algo.solution()
蚁群算法可视化效果如下:
代码仓库:IALib[GitHub]
本篇代码已同步至【智能算法(Python
复现)】专栏专属仓库:IALib
运行IALib
库中的ACO
算法:
git clone git@github.com:xiayouran/IALib.git
cd examples
python main.py -algo aco