放牧代码和思想
专注自然语言处理、机器学习算法
    This thing called love. Know I would've. Thrown it all away. Wouldn't hesitate.

使用Matplotlib和Imagemagick实现算法可视化与GIF导出

目录

ML可视化学习 

最近在学习一些基础的ML算法,比起枯燥的公式,我更喜欢写Python实现,再通过Matplotlib这个强大的作图库可视化出来。比如最简单的感知机梯度下降算法,给定数据集,用不同的颜色和形状,可以很方便地画出来:

上图描述了数据集[[(3, 3), 1], [(4, 3), 1], [(1, 1), -1]]。

同样,对于每次更新的分离超平面,也可以画出来:

上图描述了以下分离超平面:

   w     b
[3, 3] 1
[2, 2] 0
[1, 1] -1
[0, 0] -2
[3, 3] -1
[2, 2] -2
[1, 1] -3

那么问题来了,感知机学习算法是一个错误驱动的算法,分离超平面是一个不断更新的过程,上图虽然将中间过程画了出来,但是有些超平面重叠在了一起,怎么办呢?我一开始觉得好办,给每条线加个标记不就行了。借助上面的输出就能判断哪个平面在先哪个平面在后,于是就有了下图:

如你所见,标记也重叠在一起了。

这时,我发现matplotlib还支持动画,于是现学现卖,有了下面的故事。

matplotlib动画入门

# -*- coding:utf-8 -*-
# Filename: sin.py
# Author:hankcs
# Date: 2015/1/30 22:41
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

# first set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

# initialization function: plot the background of each frame
def init():
    line.set_data([], [])
    return line,

# animation function.  this is called sequentially
def animate(i):
    x = np.linspace(0, 2, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    line.set_data(x, y)
    return line,

# call the animator.  blit=true means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=200, interval=20, blit=True)
plt.show()

首先新建了图片、坐标和一条空白的线作为全局变量。然后init方法是一个初始化的方法,什么都不干。animate方法中的参数i表示当前帧数,通过正弦函数接受i生成了坐标集合,并且更新到线条中去。接下来新建了anim对象,几个参数的名称都很好懂,最后一个blit方法是告诉matplotlib记得在每帧之前擦除init方法返回的那些图元。

运行效果如图:

也许你会好奇上图是怎么来的,用其他工具截图吗?不,在万能的Python面前,导出gif是小菜一碟。

导出GIF

安装ImageMagick

ImageMagick是一个类似于编码器的工具,下载地址:http://www.imagemagick.org/script/binary-releases.php 

配置matplotlib

先看看自己的配置文件放在了哪里:

import matplotlib
matplotlib.matplotlib_fname()

会输出类似一个路径,用文本文件打开这个文件,编辑末尾的:

animation.convert_path: '"C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe"'

记得取消“animation.convert_path”前面的注释。这样应该就配置好了,接下来用一句话就可以导出gif:

anim.save('perceptron.gif', fps=2, writer='imagemagick')

在我的机器上,意外地发生了错误:

UserWarning: imagemagick MovieWriter unavailable
warnings.warn ("% s MovieWriter unavailable" writer%)

后来单步了一下,发现是matplotlib解析配置文件的逻辑有bug:

如图,正确的位置是C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe,可是上图解析后前后都多了一个\号。我的解决方法是编辑C:\Program Files\Python27\Lib\site-packages\matplotlib\__init__.py,在1101行加一句:

rcParams['animation.convert_path'] = 'C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe'

强行覆盖错误的配置。

接下来就能正常工作了。

感知机梯度下降算法可视化

感知机算法代码

终于到了最激动人心的时刻了,有了上述知识,就可以完美地可视化这个简单的算法:

# -*- coding:utf-8 -*-
# Filename: train2.1.py
# Author:hankcs
# Date: 2015/1/30 16:29
import copy
from matplotlib import pyplot as plt
from matplotlib import animation

training_set = [[(3, 3), 1], [(4, 3), 1], [(1, 1), -1]]
w = [0, 0]
b = 0
history = []


def update(item):
    """
    update parameters using stochastic gradient descent
    :param item: an item which is classified into wrong class
    :return: nothing
    """
    global w, b, history
    w[0] += 1 * item[1] * item[0][0]
    w[1] += 1 * item[1] * item[0][1]
    b += 1 * item[1]
    print w, b
    history.append([copy.copy(w), b])
    # you can uncomment this line to check the process of stochastic gradient descent


def cal(item):
    """
    calculate the functional distance between 'item' an the dicision surface. output yi(w*xi+b).
    :param item:
    :return:
    """
    res = 0
    for i in range(len(item[0])):
        res += item[0][i] * w[i]
    res += b
    res *= item[1]
    return res


def check():
    """
    check if the hyperplane can classify the examples correctly
    :return: true if it can
    """
    flag = False
    for item in training_set:
        if cal(item) <= 0:
            flag = True
            update(item)
    # draw a graph to show the process
    if not flag:
        print "RESULT: w: " + str(w) + " b: " + str(b)
    return flag


if __name__ == "__main__":
    for i in range(1000):
        if not check(): break

    # first set up the figure, the axis, and the plot element we want to animate
    fig = plt.figure()
    ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
    line, = ax.plot([], [], 'g', lw=2)
    label = ax.text([], [], '')

    # initialization function: plot the background of each frame
    def init():
        line.set_data([], [])
        x, y, x_, y_ = [], [], [], []
        for p in training_set:
            if p[1] > 0:
                x.append(p[0][0])
                y.append(p[0][1])
            else:
                x_.append(p[0][0])
                y_.append(p[0][1])

        plt.plot(x, y, 'bo', x_, y_, 'rx')
        plt.axis([-6, 6, -6, 6])
        plt.grid(True)
        plt.xlabel('x')
        plt.ylabel('y')
        plt.title('Perceptron Algorithm (www.hankcs.com)')
        return line, label


    # animation function.  this is called sequentially
    def animate(i):
        global history, ax, line, label

        w = history[i][0]
        b = history[i][1]
        if w[1] == 0: return line, label
        x1 = -7
        y1 = -(b + w[0] * x1) / w[1]
        x2 = 7
        y2 = -(b + w[0] * x2) / w[1]
        line.set_data([x1, x2], [y1, y2])
        x1 = 0
        y1 = -(b + w[0] * x1) / w[1]
        label.set_text(history[i])
        label.set_position([x1, y1])
        return line, label

    # call the animator.  blit=true means only re-draw the parts that have changed.
    print history
    anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(history), interval=1000, repeat=True,
                                   blit=True)
    plt.show()
    anim.save('perceptron.gif', fps=2, writer='imagemagick')

可视化

可见超平面被误分类点所吸引,朝着它移动,使得两者距离逐步减小,直到正确分类为止。通过这个动画,是不是对感知机的梯度下降算法有了更直观的感悟呢?

Reference

http://ipythonnb4jpnexp.blogspot.jp/2013/09/gif-matplotlib1.html

http://mytrix.me/2013/08/matplotlib-animation-tutorial/

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 使用Matplotlib和Imagemagick实现算法可视化与GIF导出

评论 10

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #3

    安装imagemagick后在目录C:\Program Files\ImageMagick-7.0.7-Q16\下没有convert.exe文件啊

    jie7年前 (2017-11-24)回复
  2. #2

    这个最后怎么保存gif文件?不知道这个文件保存到哪里了?哥哥

    geekczt8年前 (2016-07-16)回复
    • 我找到了,我在anim.save(‘c://perceptron.gif,fps=2,writer=’imagemagick”);就这样在C盘下面找到了。东西还是非常好的,感觉非常不错。

      geekczt8年前 (2016-07-16)回复
      • 我怎找不到,请告知怎么找

        mercury global4年前 (2019-12-15)回复
  3. #1

    animation.convert_path: ‘”C:Program FilesImageMagick-6.9.0-Q16convert.exe”‘

    改为

    animation.convert_path: C:Program FilesImageMagick-6.9.0-Q16convert.exe

    就可以了。

    xxx8年前 (2015-12-24)回复
    • 按照导出 GIF 的步骤把东西都配置了可还是报错..

      Traceback (most recent call last):
      File “animation.py”, line 67, in
      animation.save(‘../figures/rain.gif’, writer=’imagemagick’, fps=40, dpi=72)
      File “/Applications/anaconda/lib/python2.7/site-packages/matplotlib/animation.py”, line 810, in save
      writer.grab_frame(**savefig_kwargs)
      File “/Applications/anaconda/lib/python2.7/site-packages/matplotlib/animation.py”, line 230, in grab_frame
      dpi=self.dpi, **savefig_kwargs)
      File “/Applications/anaconda/lib/python2.7/site-packages/matplotlib/figure.py”, line 1565, in savefig
      self.canvas.print_figure(*args, **kwargs)
      File “/Applications/anaconda/lib/python2.7/site-packages/matplotlib/backend_bases.py”, line 2232, in print_figure
      **kwargs)
      File “/Applications/anaconda/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py”, line 519, in print_raw
      fileobj.write(renderer._renderer.buffer_rgba())
      IOError: [Errno 32] Broken pipe

      不知道出了什么问题

      Carl8年前 (2016-03-18)回复
      • 我解决啦谢谢,原来是安装的时候没用 ports(我是 mac) 许多 dependence 没装,ImageMagic 没装好

        Carl8年前 (2016-03-18)回复

我的作品

HanLP自然语言处理包《自然语言处理入门》