一、前言:一种不同的“进化”思路
在优化算法的大家庭里,遗传算法(Genetic Algorithm, GA)无疑是明星成员。它通过模拟生物界的“优胜劣汰、交叉变异”来寻找最优解。但你是否想过,我们一定要维护一个庞大的“种群”吗?
今天介绍的 基于群体的增量学习算法(Population-Based Incremental Learning, PBIL) 就给出了一个全新的答案。它属于 分布估计算法(Estimation of Distribution Algorithms, EDA) 的一种,其核心思想是:不再进化具体的“个体”,而是进化生成优秀个体的“概率模型”。
二、PBIL的核心思想:从操作个体到学习蓝图
为了更好地理解,我们来做一个对比:
- 遗传算法 (GA):维护一个由多个解(个体)组成的种群。通过选择、交叉、变异等算子,直接对这些个体进行操作,希望好的基因能够代代相传。
- PBIL算法:它不维护种群,而是维护一个 概率向量(Probability Vector, PV)。这个向量可以看作是生成优秀解的“蓝图”或“模板”。
假设我们要解决一个解为 n 位二进制串的问题。PBIL的概率向量 PV 就是一个 n 维的浮点数数组,其中 PV[i] 代表了在最终解的第 i 个位置上,值为 1 的概率。
算法的目标就是:通过不断从这个概率模型生成样本,并从样本中学习,持续优化这个概率向量,使其最终能够以极高的概率直接生成问题的最优解。
三、算法步骤与核心公式
PBIL的流程非常简洁优雅,主要分为以下几步:
1. 初始化 (Initialization)
创建一个概率向量 PV。在没有任何先验知识的情况下,我们通常将其所有元素初始化为 0.5。
PV = [0.5, 0.5, 0.5, ...]
这表示在每个位置上,生成 0 和 1 的概率是均等的。
2. 生成样本 (Sampling)
根据当前的概率向量 PV,随机生成 N 个候选解(个体)。例如,要生成一个个体的第 i 位,如果 PV[i] 是 0.8,那么该位有80%的概率是 1,20%的概率是 0。
3. 评估 (Evaluation)
计算这 N 个新生成的候选解的适应度值(即它们有多好)。
4. 学习与更新 (Learning & Updating)
这是PBIL的灵魂所在。
首先,从 N 个解中,找到 适应度最高 的那个解,我们称之为 best_individual。
然后,使用这个最好的解来 更新概率向量 PV,使其“朝着”这个最好解的方向移动一点。
更新公式如下:
PVnew[i]=(1−LR)⋅PVold[i]+LR⋅best_individual[i]
PV_{new}[i] = (1 - LR) \cdot PV_{old}[i] + LR \cdot \text{best\_individual}[i]
PVnew[i]=(1−LR)⋅PVold[i]+LR⋅best_individual[i]
其中:
LR是 学习率 (Learning Rate),一个介于0和1之间的小数(如0.1),控制着每次更新的幅度。best_individual[i]是最优解的第i位(值为0或1)。
这个公式的本质是一个加权平均,它把概率向量向当前找到的最优解的方向“拉”了一把。
5. 变异 (Mutation) (可选)
为了防止概率向量过早收敛(即某些值过分接近0或1,导致多样性丧失),可以引入一个小的变异步骤,对概率向量进行轻微的随机扰动。
if (random() < mutation_prob): PV[i]=(1−mutation_shift)⋅PV[i]+mutation_shift⋅random_bit \text{if (random() < mutation\_prob): } PV[i] = (1 - \text{mutation\_shift}) \cdot PV[i] + \text{mutation\_shift} \cdot \text{random\_bit} if (random() < mutation_prob): PV[i]=(1−mutation_shift)⋅PV[i]+mutation_shift⋅random_bit
这相当于给概率向量一个机会,去探索那些当前看起来不是最优的方向。
6. 终止
重复步骤2到5,直到满足终止条件(如达到最大迭代次数,或概率向量已收敛)。
四、经典应用:One-Max问题
One-Max问题是最简单的测试函数之一,其目标是找到一个二进制串,使其包含的 1 的数量最多。对于长度为 n 的串,最优解就是全 1 串,适应度为 n。
使用PBIL解决此问题时:
- 初始
PV全为0.5。 - 生成的样本中,
1越多的个体适应度越高。 - 在更新步骤中,
best_individual中1的位置会使得PV对应位置的概率值增加。 - 经过多次迭代,
PV的所有元素都会趋近于1.0,从而稳定地生成最优解。
完整python代码
import numpy as np
import matplotlib.pyplot as plt
# 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 设置正常显示负号,以防坐标轴负数出错
# --- 1. 参数定义 ---
ONE_MAX_LENGTH = 50 # 问题规模:二进制串的长度
POPULATION_SIZE = 100 # 种群大小:每代生成的个体数量
LEARNING_RATE = 0.05 # 学习率:控制概率向量更新的步长
MUTATION_PROB = 0.02 # 变异概率:对概率向量进行扰动的概率
MUTATION_SHIFT = 0.05 # 变异幅度:扰动的大小
MAX_GENERATIONS = 100 # 最大迭代次数
# --- 2. 适应度函数 (One-Max问题) ---
def evaluate_fitness(individual):
"""计算个体的适应度,即二进制串中'1'的数量。"""
return np.sum(individual)
# --- 3. PBIL算法实现 ---
# 初始化概率向量 (PV)
# 开始时,每个位置为1的概率都是0.5
prob_vector = np.full(ONE_MAX_LENGTH, 0.5)
# 用于记录每代的最优适应度,以便可视化
best_fitness_history = []
avg_fitness_history = []
print("--- PBIL算法开始运行 ---")
# 开始迭代
for generation in range(MAX_GENERATIONS):
# --- 步骤1: 根据概率向量生成种群 ---
# population是一个 (POPULATION_SIZE, ONE_MAX_LENGTH) 的矩阵
# np.random.rand(...) < prob_vector 利用了NumPy的广播机制
# 生成一个随机数矩阵,然后与概率向量逐行比较,得到布尔矩阵,再转为整数0或1
population = (np.random.rand(POPULATION_SIZE, ONE_MAX_LENGTH) < prob_vector).astype(int)
# --- 步骤2: 评估种群中每个个体的适应度 ---
fitness_scores = np.array([evaluate_fitness(ind) for ind in population])
# --- 步骤3: 找到当前种群中的最优个体 ---
best_individual_index = np.argmax(fitness_scores)
best_individual = population[best_individual_index]
best_fitness = fitness_scores[best_individual_index]
# 计算平均适应度
avg_fitness = np.mean(fitness_scores)
# 记录历史数据
best_fitness_history.append(best_fitness)
avg_fitness_history.append(avg_fitness)
# --- 步骤4: 更新概率向量 ---
# 公式: PV_new = (1 - LR) * PV_old + LR * best_individual
prob_vector = (1 - LEARNING_RATE) * prob_vector + LEARNING_RATE * best_individual
# --- 步骤5: 对概率向量进行变异 ---
for i in range(ONE_MAX_LENGTH):
if np.random.rand() < MUTATION_PROB:
# 公式: PV_mutated = (1 - MS) * PV + MS * random_bit
random_bit = np.random.randint(0, 2) # 随机选择0或1
prob_vector[i] = (1 - MUTATION_SHIFT) * prob_vector[i] + MUTATION_SHIFT * random_bit
# 打印当前代的信息
if (generation + 1) % 10 == 0:
print(f"第 {generation + 1:3d} 代: 最优适应度 = {best_fitness:2.0f}/{ONE_MAX_LENGTH}, 平均适应度 = {avg_fitness:5.2f}")
# --- 4. 结果展示 ---
print("\n--- 算法运行结束 ---")
print(f"最终概率向量 (部分): {np.round(prob_vector[:10], 2)}...")
# 根据最终的概率向量生成最终的推荐解
final_solution = (prob_vector > 0.5).astype(int)
final_fitness = evaluate_fitness(final_solution)
print(f"最终解的适应度: {final_fitness}/{ONE_MAX_LENGTH}")
print(f"最终解 (部分): {final_solution[:20]}...")
# --- 5. 可视化 ---
plt.figure(figsize=(10, 6))
plt.plot(best_fitness_history, label='每代最优适应度')
plt.plot(avg_fitness_history, label='每代平均适应度', linestyle='--')
plt.axhline(y=ONE_MAX_LENGTH, color='r', linestyle='-', label=f'理论最优值 ({ONE_MAX_LENGTH})')
plt.title('PBIL算法求解One-Max问题收敛曲线')
plt.xlabel('迭代次数')
plt.ylabel('适应度')
plt.legend()
plt.grid(True)
plt.show()

五、优缺点分析
优点
- 概念简单:相比GA复杂的算子,PBIL的更新规则非常直观。
- 计算高效:省去了维护和操作庞大种群的开销。
- 全局信息利用:概率向量是对历史所有优秀解的统计抽象,隐式地利用了全局信息。
缺点
- 变量无关性假设:基本PBIL假设变量(基因位)间相互独立,难以捕捉变量间的复杂关联(例如,
x[1]和x[5]必须同时为1)。 - 主要用于离散空间:基本形式主要针对二进制编码问题,解决连续问题需要对概率模型进行扩展(如使用高斯分布)。
六、总结
PBIL算法为我们提供了一种“上帝视角”的优化思路:它不关心种群中个体的“爱恨情仇”(交叉),而是通过统计和学习,直接去描绘最优解的“样貌”(概率分布)。这种从操作实例到学习模型的思想转变,正是分布估计算法的魅力所在,也为解决复杂优化问题开辟了新的道路。
23



被折叠的 条评论
为什么被折叠?



