一步一步教你用 Matplotlib 保存 GIF 動圖

寫在前面:爲了可視化機器學習過程,而且保存下來,因此想直接利用 Matplotlib.animation 保存動圖,期間參考了好多資料,過程比較艱辛,因此想記錄下來。固然,此文還參考了好多網上的其它文章,再此一併感謝那些熱愛分享的 coder,而且參考資料中給出連接。全部代碼整理到GitHubphp

先上效果! html

0. 前期準備

安裝 NumPyMatplotlib。具體安裝直接上官網即可,遇到什麼問題在網上基本能夠搜到答案的,這裏就不介紹了。這裏要簡單的使用 NumPy 生成一些測試數據,若是對 NumPy 不熟悉,參考 NumPy 官方快速入門教程(譯),若是對 Matplotlib 不熟悉,參考Matplotlib 基本操做。固然若是要可以保存 GIF 還須要一個工具 ImageMagick,按照官方指導安裝就好,確保在命令中輸入 magick 有響應。若是使用 PyCharm 之類的 IDE 請將環境變量配置到全局,避免在 IDE 中找不到命令。python

1. 繪製基本動圖

請確保已經安裝了 ImageMagick 而且可用的狀況下再繼續,否則代碼跑步起來。git

這裏採用兩種方式繪製動圖github

1.1 重置重繪

重置重繪主要是每次更新原來圖形的值來達到繪製動圖的效果。算法

  • 導入基本庫
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
複製代碼
  • 生成數據,畫出原始圖
fig, ax = plt.subplots()

x = np.arange(0, 2 * np.pi, 0.01)
line0 = ax.plot(x, np.cos(x))
line, = ax.plot(x, np.sin(x))
複製代碼

注意,這裏申明的 line,, 不能少,好像是爲了更新值時類型匹配。沒深究,但願知道的能夠指點一下。macos

  • 定義初始函數和跟新函數
def init():
    line.set_ydata(np.sin(x))
    return line,

def animate(i):
    line.set_ydata(np.sin(x + i / 10.0))
    return line,
複製代碼

其實就是更新一下 Y 座標的值。bash

  • 執行動畫
animation = animation.FuncAnimation(fig=fig, func=animate, frames=100, init_func=init, interval=20, blit=False)
複製代碼

這個函數的參數能夠看源碼,以及官網的介紹,這裏就是每一個 20\ ms 繪製一幀,總共有 100 幀。markdown

  • 保存 GIF
animation.save('resetvalue.gif', writer='imagemagick')
複製代碼

這裏就是直接保存成 GIF 格式就行了。app

  • 顯示動圖
plt.show()
複製代碼

生成圖片的效果如圖所示:

1.2 擦除重繪

與上一種方法比較,這種就是不利用上一次的任何座標,直接擦除,而後再 plot 圖形上去。

  • 導入基本庫
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
複製代碼
  • 生成數據,畫出原始圖
fig, ax = plt.subplots()

x = np.arange(0, 2 * np.pi, 0.01)
ax.plot(x, np.cos(x))
複製代碼

這裏就沒有那個要求了,由於這種方式不依賴於前面的圖形。

  • 定義初始函數和跟新函數
def init():
    return ax.plot(x, np.sin(x))


def animate(i):
    try:
        ax.lines.pop(1)
    except Exception:
        pass
    line = ax.plot(x, np.sin(x + i / 10.0), 'r')
    return line,

複製代碼

初始化沒什麼好說的,其實也能夠不初始化,時間間隔過短效果基本是看不出來的。下面介紹一下 ax.lines.pop(1) 這句「擦除」函數。這裏的 lines能夠理解爲存儲 plot 上來的圖像棧,前面 plot 了一個餘弦函數,在初始化的時候繪製了第二條,因此索引是 1 的正弦函數被 pop 了而後進行下一條繪製。因而執行 line = ax.plot(x, np.sin(x + i / 10.0), 'r')

  • 後續
animation = animation.FuncAnimation(fig=fig, func=animate, frames=100, init_func=init, interval=20, blit=False)
animation.save('redraw.gif', writer='imagemagick')
plt.show()
複製代碼

沒區別

生成圖片的效果如圖所示:

2. 機器學習過程可視化

前面介紹了的知識基本夠用了,但終究不是實操。若是你不是學習機器學習的,其實有上面的基礎就能夠了,這一節能夠跳過。可是,若是你想學機器學習的話,這裏提供一個小例子讓你更加清晰的理解機器學習的過程當中的數據變化。不過這裏只專一於繪製,機器學習的部分參考從 TensorFlow 入門機器學習

一樣仍是拿線性迴歸做爲例子。

原始代碼

# coding: utf-8
from __future__ import print_function
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.interpolate import spline

train_X = np.linspace(0, 10, 50)
noise = np.random.normal(0, 1, train_X.shape)
train_Y = train_X * 1 - 2 + noise

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

W = tf.Variable(-1., name="weight")
b = tf.Variable(1., name="bias")

activation = tf.add(tf.multiply(X, W), b)

learning_rate = 0.0001

cost = tf.reduce_sum(tf.pow(activation - Y, 2))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

training_epochs = 20
display_step = 10

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(training_epochs):
        for (x, y) in zip(train_X, train_Y):
            sess.run(optimizer, feed_dict={X: x, Y: y})
        if epoch < 10 or epoch % display_step == 0:
            c_tmp = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
            W_tmp = sess.run(W)
            b_tmp = sess.run(b)
            activation_tmp = sess.run(activation, feed_dict={X: train_X})
            print("Epoch: %04d" % (epoch + 1), "cost=", "{:.9f}".format(c_tmp), "W=", W_tmp, "b=", b_tmp)
    print("Optimization Finished!")
    print("cost=", sess.run(cost, feed_dict={X: train_X, Y: train_Y}), "W=", sess.run(W), "b=", sess.run(b))
複製代碼

上面的代碼就不解釋了,爲了方便測試,把迭代次數調的比較小。接下來咱們在上面的基礎上進行擴充。

首先進行可視化,首先把咱們以爲有用的數據提取出來吧。由於通過測試,前面的變化幅度比較大,爲了圖示明顯,刻意進行非均勻採樣。

c_trace = []
W_trace = []
b_trace = []
activation_trace = []

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(training_epochs):
        for (x, y) in zip(train_X, train_Y):
            sess.run(optimizer, feed_dict={X: x, Y: y})
        if epoch < 10 or epoch % display_step == 0:
            c_tmp = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
            W_tmp = sess.run(W)
            b_tmp = sess.run(b)
            activation_tmp = sess.run(activation, feed_dict={X: train_X})
            print("Epoch: %04d" % (epoch + 1), "cost=", "{:.9f}".format(c_tmp), "W=", W_tmp, "b=", b_tmp)
            c_trace.append(c_tmp)
            W_trace.append(W_tmp)
            b_trace.append(b_tmp)
            activation_trace.append(activation_tmp)
    print("Optimization Finished!")
    print("cost=", sess.run(cost, feed_dict={X: train_X, Y: train_Y}), "W=", sess.run(W), "b=", sess.run(b))
複製代碼

參考前面的小例子,把數據填進去,作出動圖來。

fig, ax = plt.subplots()
l1 = ax.scatter(train_X, train_Y, color='red', label=r'$Original\ data$')
ax.set_xlabel(r'$X\ data$')
ax.set_ylabel(r'$Y\ data$')


def update(i):
    try:
        ax.lines.pop(0)
    except Exception:
        pass
    line, = ax.plot(train_X, activation_trace[i], 'g--', label=r'$Fitting\ line$', lw=2)
    return line,


ani = animation.FuncAnimation(fig, update, frames=len(activation_trace), interval=100)
ani.save('linearregression.gif', writer='imagemagick')

plt.show()
複製代碼

效果以下圖所示:

接着把 Cost 函數也加上來而且在最後顯示。

def update(i):
    try:
        ax.lines.pop(0)
    except Exception:
        pass
    line, = ax.plot(train_X, activation_trace[i], 'g--', label=r'$Fitting\ line$', lw=2)
    if i == len(activation_trace) - 1:
        twinax = ax.twinx()
        twinax.plot(np.linspace(0, 10, np.size(c_trace)), c_trace, 'b', label='Cost line', lw=2)
    return line,
複製代碼

能夠看到,線條十分鋒利,這時就可使用 spline平滑過渡一下。

def update(i):
    try:
        ax.lines.pop(0)
    except Exception:
        pass
    line, = ax.plot(train_X, activation_trace[i], 'g--', label=r'$Fitting\ line$', lw=2)
    if i == len(activation_trace) - 1:
        xnew = np.linspace(0, 10, np.max(c_trace) - np.min(c_trace))
        smooth = spline(np.linspace(0, 10, np.size(c_trace)), c_trace, xnew)
        twinax = ax.twinx()
        twinax.set_ylabel(r'Cost')
        twinax.plot(xnew, smooth, 'b', label=r'$Cost\ line$', lw=2)
    return line,
複製代碼

其實就是對 [0, 10] 這個區間進行採樣。添加 np.max(c_trace) - np.min(c_trace) 個點來繪製這線條。

加上圖例。

def update(i):
    try:
        ax.lines.pop(0)
    except Exception:
        pass
    line, = ax.plot(train_X, activation_trace[i], 'g--', label=r'$Fitting\ line$', lw=2)
    plt.legend(handles=[l1, line], loc='upper center')
    if i == len(activation_trace) - 1:
        ax.text(6, -2, 'Cost: %s' % c_trace[i], fontdict={'size': 16, 'color': 'r'})
        xnew = np.linspace(0, 10, np.max(c_trace) - np.min(c_trace))
        smooth = spline(np.linspace(0, 10, np.size(c_trace)), c_trace, xnew)
        twinax = ax.twinx()
        twinax.set_ylabel(r'Cost')
        costline, = twinax.plot(xnew, smooth, 'b', label=r'$Cost\ line$', lw=2)
        plt.legend(handles=[l1, line, costline], loc='upper center')
    return line,
複製代碼

下面把數據細節處理下。

learning_rate = 0.001

training_epochs = 500
display_step = 40
複製代碼

能夠看到,Cost 函數並不是嚴格遞減的,咱們採用的是梯度降低算法求最優,因此問題出在學習率,具體爲何也是一個機器學習中應該注意的問題。另外你們還能夠試試繼續把學習率調大看看會發生什麼有趣的事情?

咱們把學習率調整到 0.0001 將會獲得如下結果:

其實你觀察輸出可能並不怎麼符合原始函數。並且在不斷調整訓練參數的時候會發現擬合程度彷佛也無法每次後很好。緣由其實在於加的干擾,至於爲何干擾會形成這樣,就不在本文的討論範圍了。好了,到這裏你應該能夠繪製本身的 GIF了吧。

源碼詳見 GitHub

3. 總結

花時間把小細節搞懂就是節省時間,其實眼前的問題能夠引伸出其它更多值得思考的問題。多聯繫,多思考。

4. 參考資料

相關文章
相關標籤/搜索