一、何为 Boids 算法
Boids 算法(全称 Bird-oid objectS,意为“类鸟群对象”)由 Craig Reynolds 于 1986 年提出,最初用于模拟鸟群、鱼群等自然群体的协同运动。
该算法属于“人工生命”(Artificial Life)研究的重要成果之一,它通过为每个个体(boid)设定一组简单的局部规则——如分离、对齐和聚合——使得整个系统无需集中控制,便能自组织地产生协调而逼真的群体行为。这种由局部交互引发的整体协调效应,正是一种典型的智能“涌现”现象。
类似地,在自然界中,单个神经元并不具备意识,而当数百亿个神经元在大脑皮层中相互作用时,复杂的意识活动便由此涌现。
人工智能的发展亦呈现出相同的规律:当模型的参数量、数据规模或计算复杂度超过某个临界阈值时,系统往往会展现出先前未被显式训练的能力——仿佛在简单规则与庞大结构的交织中,自发孕育出新的智能特性。
二、Boids 算法的规则
Boids 最初的规则,仅仅三条:
- a.分离
每个个体,都不能距离周围的个体太近,如果离得近了,就会产生相反方向的排斥力使得远离。
- b.对齐
获取周围个体的平均速度,以及角度方向,并将 自身的速度 调整为 平均速度 以及 自身角度 调整为 平均偏向角度
- c.凝聚
计算周围个体的中心位置,并生成一个趋向中心的力
这些规则都是作用于个体,每个个体只需要遵循自身的规则,群体就会显现出复杂的规则。
三、代码试试
人生苦短,我用 python 让我们使用 python 来试试看;
我们先以二维平面来,每个个体有一个坐标 [dx,dy]
分离实现
def separation(i, pos):
neighbors, diff, dist = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
force = -np.sum(diff[neighbors] / (dist[neighbors][:, None] + 1e-6), axis=0)
return force
diff 意思是 i 和邻居对其产生的向量,理解为力的方向及大小 pos[j] - pos[i] ;
dist 意思是 i 和邻居的二维坐标距离,理解为距离;
dist 越大,力就越小;
sum 是向量和,最终 force 是一个力总的方向;
对齐实现
def alignment(i, pos, vel):
neighbors, _, _ = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
avg_vel = np.mean(vel[neighbors], axis=0)
return avg_vel - vel[i]
mean 意思是取平均值;
速度 = 方向 x 模长 = 速度的方向 x 速度的大小;
vel 包含了速度的方向,又包含了速度的大小; vel[i] = [v_x, v_y]
凝聚实现
def cohesion(i, pos):
neighbors, _, _ = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
center = np.mean(pos[neighbors], axis=0)
return center - pos[i]
pos 代表邻居的位置;
center - pos[i] 得到趋向力方向以及大小;
同时还需要额外的信息
#鸟群数量
b_numbers = 50
#空间大小
s_width,s_hight = 20,20
#感知半径
perception_radius = 2
#最大速度
max_speed = 2
# 分离权重, 对齐权重,凝聚权重
# 三种作用对个体影响的大小(是不是想起 pid 算法)
w_s = 1.2
w_a = 1.0
w_c = 0.8
最终个体的运行受三个影响
v(i) = vel[i] + w_s*separation(i) + w_a*alignment(i) + w_c*cohesion(i)
全代码
import matplotlib.animation as animation
import numpy as np
#鸟群数量
b_numbers = 50
#空间大小
s_width,s_hight = 100,100
#感知半径
perception_radius = 5
#最大速度
max_speed = 4
# 分离权重, 对齐权重,凝聚权重
# 三种作用对个体影响的大小(是不是想起 pid 算法)
w_s = 0.3
w_a = 0.8
w_c = 1.0
np.random.seed(0)
pos = np.random.rand(b_numbers, 2) * [s_width, s_hight]
vel = (np.random.rand(b_numbers, 2) - 0.5) * max_speed
def limit_speed(v, max_speed):
# 计算速度的摸,即大小(按行)
speed = np.linalg.norm(v)
if speed > max_speed:
return v / speed * max_speed
return v
def get_neighbors(i, pos):
diff = pos - pos[i]
# 计算方向的模,即距离(按列)
dist = np.linalg.norm(diff, axis=1)
neighbors = (dist > 0) & (dist < perception_radius)
return neighbors, diff, dist
def avoid_walls(i, pos):
margin = 5 # 离墙多近开始转向
turn_factor = 0.3 # 转向强度
force = np.zeros(2)
x, y = pos[i]
if x < margin:
force[0] = turn_factor
elif x > s_width - margin:
force[0] = -turn_factor
if y < margin:
force[1] = turn_factor
elif y > s_hight - margin:
force[1] = -turn_factor
return force
def separation(i, pos):
neighbors, diff, dist = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
# 距离越近,排斥越强
force = -np.sum(diff[neighbors] / (dist[neighbors][:, None] + 1e-6), axis=0)
return force
def alignment(i, pos, vel):
neighbors, _, _ = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
avg_vel = np.mean(vel[neighbors], axis=0)
return avg_vel - vel[i]
def cohesion(i, pos):
neighbors, _, _ = get_neighbors(i, pos)
if not np.any(neighbors):
return np.zeros(2)
center = np.mean(pos[neighbors], axis=0)
return center - pos[i]
# 每只的下一步
def step(pos, vel):
new_vel = np.zeros_like(vel)
noise_strength = 0.05
for i in range(b_numbers):
f_sep = separation(i, pos)
f_ali = alignment(i, pos, vel)
f_coh = cohesion(i, pos)
f_wall = avoid_walls(i, pos) # 墙壁回避力
# 速度更新
v = vel[i] + w_s*f_sep + w_a*f_ali + w_c*f_coh + f_wall
# 轻微随机扰动,让群体不太死板
noise = (np.random.rand(2) - 0.5) * 2 * noise_strength
v = v + noise
new_vel[i] = limit_speed(v, max_speed)
# 更新位置
new_pos = pos + new_vel
# 这里不再用周期取模,因为我们有避墙机制
# 可以直接限定在边界范围(防止数值越界)
new_pos[:, 0] = np.clip(new_pos[:, 0], 0, s_width)
new_pos[:, 1] = np.clip(new_pos[:, 1], 0, s_hight)
return new_pos, new_vel
# -----------------------
# 动画
# -----------------------
fig, ax = plt.subplots()
def update(frame):
global pos, vel
pos, vel = step(pos, vel)
ax.clear()
ax.quiver(
pos[:, 0], pos[:, 1],
vel[:, 0], vel[:, 1],
angles='xy', scale_units='xy', scale=1
)
ax.set_xlim(0, s_width)
ax.set_ylim(0, s_hight)
ax.set_title("Boids Simulation")
ani = animation.FuncAnimation(fig, update, frames=800, interval=30)
plt.show()
反复调整,但还是难以达到预期效果,大家也手动尝试一下,感受一下乐趣,算法奇妙。
四、写在后面
Boids 算法源于对自然界中群体行为的观察——例如鸟群飞行、鱼群游动等现象。通过分析个体与个体之间的动态关系,以及整体对单个个体的影响,Boids 模型用简单的局部规则(分离、对齐、聚合) 成功模拟出了复杂而逼真的群体运动。
随着研究与应用的深入,Boids 算法在多年的演化中衍生出诸多变体,不再局限于最初的三条基本规则,而是扩展出诸如障碍物避让、领导者跟随、捕食者规避、目标追踪等更丰富的行为机制。
如今,这些改进后的 Boids 算法已广泛应用于多个领域——从游戏和影视特效中的群体动画,到机器人编队控制与无人机协同飞行,再到群体智能与分布式系统建模,Boids 都以其简洁的原理与自然的效果,成为群体行为模拟的重要基础。



