简单规则,复杂世界——Boids 与人工智能的共振

一、何为 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 都以其简洁的原理与自然的效果,成为群体行为模拟的重要基础。

4 个赞

前排围观,大佬介绍的很生动

2 个赞

前排围观大佬 :smiley: :wink:

1 个赞