使用Matplotlib创建动图
最近在实验中需要利用来生成对抗网络(generative adversary network)来伪造相应数据,在查阅资料的过程中发现在训练过程中使用动图来表示生成器迭代次数效果图对于实验很有帮助. 例如下图生成正弦函数示意图:
但是,在Jupyter Notebook
一个循环当中简单的调用pyplot.plot(x, y)
只会在当前单元(cell)下不断产生新的图像,达不到预期效果. 经过一番资料查找,找到了如下的解决方案.
IPython Display
iPython
通常和Jupyter Notebook
在一块,作为一个通用的科学计算发行包绑定在一块,而iPython
内置了许多魔法指令(magic command)和包函数可以用来帮助达到我们的目的.
首先为了能够在Jupyter Notebook
单元中产生图像,并且将该图像保存在文档中,需要在当前单元中使用魔法指令:%matplotlib inline
. 这样当前matplotlib
绘图后端被设置为inline
,从而可以直接在当前单元下展示图像. 接着我们想要在同一个单元下反复画图,这样才能产生一种迭代效果.,因此我们还需要从iPython
中导入display
模块,对当前单元进行调整:
- 首先在循环开始前,需要先清空当前图像(figure)图像中的所有信息,因此我们需要调用
plt.clf()
. - 然后我们想要当前单元只产生一个图像,因此需要将其他的产生的图像清空,这时候需要使用
display.clear_output(wait=True)
. 其中设置选项wait=True
是为了等到下一个图像出现时才清除前一个图像,这样可以让图像之前的转化更加平滑. - 最后我们需要展示当前图像,这时候使用
display.disply(plt.gcf())
来展示当前图像. 其中display.display(*objs)
在所有前端上显示目标,而plt.gcf()
表示当前图像.
完整的实例代码如下:
ximport matplotlib.pyplot as plt
from IPython import display
import numpy as np
%matplotlib inline
iter = 10
for i in range(iter):
plt.clf()
display.clear_output(wait=True)
x = np.linspace(0.24*np.pi, 512)
y = np.sin((i+1)/30.*x)
plt.plot(x, y)
display.display(plt.gcf())
plt.pause(1) # 中途暂停来提供观察时间
可以发现该方法展示过程并不是很流畅,可以发现明显的刷新时间,这是因为其原理就是在同一个位置不断进行画图. 除了不流畅的感观以外,也无法将该过程保存为一个动图.
Matplotlib Animation
一个更好的方法是使用matplotlib
中提供的animation
模块来进行动图制作. matplotliib
对于动画提供三种方法:
Animation
一个基础的动画类,用来表示一个动画对象.FuncAnimation
使用函数来创建一个动画类. 该方法通过调用一个函数func
来绘制每一帧图像,综合起来得到一个动画.ArtistAnimation
使用预先设定好的Artist
类来创建Animation
类.
在这里我们使用FuncAnimation
来达到我们的目的,其API如下所示:
xxxxxxxxxx
FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)
在本文中我们主要关注三个参数:fig
, func
和frames
. 其余参数的含义可以参考官方文档. fig
表示进行操作(调整大小,画图)的图像目标,func
为对每一帧进行画图的函数,FuncAnimation
会调用func
来为每一帧进行画图,其API如下
xxxxxxxxxx
def func(frame, *fargs) -> iterable_of_artists
fargs
对应FuncAnimation
中的参数 fargs
. frames
可以为整数,迭代器、生成器或None,它将被迭代(整数会被转换成range
),其每个参数将作为func
中的frame
被调用来生成每一帧图片.
有了以上知识,我们就可以使用FuncAnimation
来改写上述循环来获取迭代动图了. 首先创建一个新的图像对象,获取其操作句柄:
xxxxxxxxxx
fig, ax = plt.subplots()
ax.set_xlim([0, 60])
ax.set_ylim([-1, 1])
line, = ax.plot([],[]) # 创建一个空图做为起始目标
接着定义更新每一帧的函数func
:
xxxxxxxxxx
x = np.linspace(0.24*np.pi, 512)
def update(frame):
y = np.sin((frame+1)/30.*x)
line.set_data(x, y)
return line,
最后使用FuncAnimation
创建相应的animation
对象:
xxxxxxxxxx
animation = FuncAnimation(fig, update, frames=10)
为了在Jupyter Notebook
中展示相应的动图,我们首先需要将matplotlib
的后端设置成交互模式,%matplotlib notebook
然后使用plt.show()
进行展示即可. 全部代码如下所示:
xxxxxxxxxx
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
%matplotlib notebook
fig, ax = plt.subplots()
ax.set_xlim([0, 200])
ax.set_ylim([-1, 1])
line, = ax.plot([],[])
x = np.linspace(0.24*np.pi, 512)
def update(frame):
y = np.sin((frame+1)/30.*x)
line.set_data(x, y)
return line,
ani = FuncAnimation(fig, update, frames=10)
plt.show()
为了保存该图像,可以直接调用Animation
对象中的save
方法,在这里我们将其保存为一个gif
文件,因此使用PilloWriter
进行编码. 具体代码如下:
xxxxxxxxxx
from matplotlib import animation
filename = r"ani.gif"
ani.save(filename, writer=ani.PillowWriter(fps=3))
最后结果如下所示
参考资料
https://matplotlib.org/stable/api/animation_api.html
https://holypython.com/how-to-save-matplotlib-animations-the-ultimate-guide/
评论
发表评论