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
问题原因
- ArtistAnimation报错:
- 代码仅生成单帧图像存入
images列表,ArtistAnimation需要多帧连续数据才能生成动画,单帧会触发索引越界; - 3D散点对象
Path3DCollection与旧版matplotlib的ArtistAnimation内部属性检查不兼容,导致_offset_zordered属性缺失报错。
- 代码仅生成单帧图像存入
- 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()
关键修改说明
- 重构PSO函数:新增
all_generations列表记录每一代粒子的位置,为动画提供帧数据;同时修复了原代码中pbest_position未正确更新的问题(原代码直接赋值列表,导致个体最优随粒子位置同步变化)。 - FuncAnimation正确使用:定义
update函数作为动画的更新逻辑,每帧更新散点的3D坐标,而不是传入列表。 - 优化散点更新方式:初始化时仅创建一次散点对象,通过修改
_offsets3d属性更新数据,避免重复创建Artist对象,提升动画效率。 - 动画保存适配:MP4格式推荐使用
ffmpeg作为writer(需提前安装:pip install ffmpeg-python或系统级安装ffmpeg),若生成GIF可切换为writer='pillow'。
内容的提问来源于stack exchange,提问作者zerf
相关产品推荐
相关产品推荐

