You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Matplotlib保存粒子群优化动画报错问题求助

PSO算法动画保存报错的解决方案

问题现象

运行PSO代码时算法逻辑正常,但动画保存环节持续报错:

  • 使用ArtistAnimation保存时,触发两个错误:
    AttributeError: 'Path3DCollection' object has no attribute '_offset_zordered'
    IndexError: list index out of range
    
  • 改用FuncAnimation时,执行以下代码:
    animated_image = animation.FuncAnimation(fig, images, frames=list(range(1,500)), interval=500, blit=False, repeat=True)
    
    报错:
    TypeError: 'list' object is not callable
    

问题原因

  1. ArtistAnimation报错:
    • 代码仅生成单帧图像存入images列表,ArtistAnimation需要多帧连续数据才能生成动画,单帧会触发索引越界;
    • 3D散点对象Path3DCollection与旧版matplotlib的ArtistAnimation内部属性检查不兼容,导致_offset_zordered属性缺失报错。
  2. FuncAnimation报错:
    • FuncAnimation的第二个参数要求是可调用的更新函数,但你传入了存储图像的images列表,列表不属于可调用对象,因此触发类型错误。

修复后完整代码

import random
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import linspace as ls

# 适应度函数(Rastrigin函数)
def fitness_function(x1, x2):
    z = -20*np.exp(-0.2*(np.sqrt(0.5*(x1**2+x2**2)))) - np.exp(0.5*(np.cos(2*np.pi*x1)+np.cos(2*np.pi*x2))) + np.exp(1) + 20
    return z

# 更新粒子速度
def update_velocity(particle, velocity, pbest, gbest):
    num_particle = len(particle)
    new_velocity = np.zeros(num_particle)
    r1 = random.uniform(0, 1.0)
    r2 = random.uniform(0, 1.0)
    w = 1
    c1 = 1.47
    c2 = 1.62
    for i in range(num_particle):
        new_velocity[i] = w*velocity[i] + c1*r1*(pbest[i]-particle[i]) + c2*r2*(gbest[i]-particle[i])
    return new_velocity

# 更新粒子位置
def update_position(particle, velocity):
    return particle + velocity

# 重构PSO,返回每一代的粒子位置
def pso_2d(population, dimension, position_min, position_max, generation, fitness_criterion):
    particles = [[random.uniform(position_min, position_max) for _ in range(dimension)] for _ in range(population)]
    pbest_position = [p.copy() for p in particles]
    pbest_fitness = [fitness_function(p[0], p[1]) for p in particles]
    gbest_index = np.argmin(pbest_fitness)
    gbest_position = pbest_position[gbest_index].copy()
    velocity = [[0.0 for _ in range(dimension)] for _ in range(population)]
    
    # 记录每一代的粒子位置
    all_generations = [particles.copy()]
    
    for t in range(generation):
        if np.average(pbest_fitness) <= fitness_criterion:
            break
        for n in range(population):
            velocity[n] = update_velocity(particles[n], velocity[n], pbest_position[n], gbest_position)
            particles[n] = update_position(particles[n], velocity[n])
        
        pbest_fitness = [fitness_function(p[0], p[1]) for p in particles]
        # 更新个体最优
        for n in range(population):
            if pbest_fitness[n] < fitness_function(pbest_position[n][0], pbest_position[n][1]):
                pbest_position[n] = particles[n].copy()
        gbest_index = np.argmin(pbest_fitness)
        gbest_position = pbest_position[gbest_index].copy()
        
        all_generations.append(particles.copy())
    
    print('全局最优位置: ', gbest_position)
    print('最优适应度值: ', min(pbest_fitness))
    print('平均个体最优适应度: ', np.average(pbest_fitness))
    print('迭代代数: ', t+1)
    return all_generations

# 参数设置
population = 100
dimension = 2
position_min = -10
position_max = 10
generation = 500
fitness_criterion = 1e-8

# 运行PSO,获取每一代粒子位置
all_particles = pso_2d(population, dimension, position_min, position_max, generation, fitness_criterion)

# 绘制3D背景
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
x = ls(position_min, position_max, 80)
y = ls(position_min, position_max, 80)
X, Y = np.meshgrid(x, y)
Z = fitness_function(X, Y)
ax.plot_wireframe(X, Y, Z, color='r', linewidth=0.2)

# 初始化散点(只创建一次)
initial_particles = all_particles[0]
x_data = [p[0] for p in initial_particles]
y_data = [p[1] for p in initial_particles]
z_data = [fitness_function(p[0], p[1]) for p in initial_particles]
scatter = ax.scatter3D(x_data, y_data, z_data, c='b')

# 定义动画更新函数
def update(frame):
    particles = all_particles[frame]
    x_new = np.array([p[0] for p in particles])
    y_new = np.array([p[1] for p in particles])
    z_new = np.array([fitness_function(p[0], p[1]) for p in particles])
    # 更新散点数据
    scatter._offsets3d = (x_new, y_new, z_new)
    return scatter,

# 创建动画
ani = animation.FuncAnimation(fig, update, frames=len(all_particles), interval=50, blit=False, repeat=True)

# 保存动画(MP4需要ffmpeg,GIF用pillow)
# ani.save('./pso_animation.gif', writer='pillow', dpi=100)
ani.save('./pso_animation.mp4', writer='ffmpeg', dpi=100)

plt.show()

关键修改说明

  1. 重构PSO函数:新增all_generations列表记录每一代粒子的位置,为动画提供帧数据;同时修复了原代码中pbest_position未正确更新的问题(原代码直接赋值列表,导致个体最优随粒子位置同步变化)。
  2. FuncAnimation正确使用:定义update函数作为动画的更新逻辑,每帧更新散点的3D坐标,而不是传入列表。
  3. 优化散点更新方式:初始化时仅创建一次散点对象,通过修改_offsets3d属性更新数据,避免重复创建Artist对象,提升动画效率。
  4. 动画保存适配:MP4格式推荐使用ffmpeg作为writer(需提前安装:pip install ffmpeg-python或系统级安装ffmpeg),若生成GIF可切换为writer='pillow'。

内容的提问来源于stack exchange,提问作者zerf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:13:11