chainer 使用範例 教學

找了半天找不到中文的教學,底子不好的我,只找到一篇日文著作看起來比較清楚,順便當作練習日文來邊翻譯邊學習chainer嚕。

此為翻譯文章,非本人著作,同時本人也不是專業翻譯,翻錯是正常的。

來源:

【機械学習】ディープラーニング フレームワークChainerを試しながら解説してみる。

使用現在熱門的深度學習的框架:Chainer來做辨識手寫數字的範例程式碼。這邊想稍微記錄一些關於程式碼內容相關的解說。

(本內容的程式碼的全文已經在GitHub上上傳了。[建議使用PC觀看])

總而言之、因為安裝上非常簡單,推薦Chainer搭配Python來使用,即使是實作封閉代碼也相當不錯。

而文章裡面是使用這種感覺的神經網路模型來試試看。
nn_structure6.png

主要的參考網站有:
Chainer官網
Chainer的GitHub連結
Chainer的教程和文件

1. 安裝

首先,就先安裝吧,安裝內容是根據Chainer的GitHub上的”Requirements” 中寫的必要軟體和library。

pip install chainer

執行它。
就這樣安裝完了。超簡單!以前在將Caffe安裝在Mac時遇到的苦戰就像假的一樣。:smile:

假如,安裝有問題的話cvl-robot著作的「DeepLearningライブラリのChainerがすごい、らしい」中將必要的library等等的安裝內容詳細記錄下來了,十分方便好用。

 

2.入手範例程式碼

下方連結的GitHub目錄中有一份完成的手寫數字辨識的範例檔:train_mnist.py。這個範例程式嘗試使用Chainer中的順向傳播神經網路來做分類
https://github.com/pfnet/chainer/tree/master/examples/mnist
┗ train_mnist.py

我(原文作者)想為在這個程式碼上加點註解,還有將其中一部份的流程用圖像表示。

3.來看看範例檔

這次,我(原文作者)的環境是用Macbook Air(OS X ver10.10.2)來測試,可能會因為環境有點不一樣,而會有輸出後有點差異,就適當的參考一下就好,另外這邊因為環境的關係是用GPU來計算,CPU相關的地方的程式碼就省略不寫了。

(翻譯者我是用ubuntu 16,測試可用)

 

3-1.準備

首先就是先將有需要的library安裝一下。

import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import fetch_mldata
from chainer import cuda, Variable, FunctionSet, optimizers
import chainer.functions  as F
import sys

plt.style.use('ggplot')

再來是將各種變數定義和設定。

# 使用隨機梯度下降法來學習的時候,每一次的批量大小
batchsize = 100

# 反覆學習的次數
n_epoch   = 20

# 中間層的數量
n_units   = 1000

通過使用Scikit Learn將MNIST中的手寫數字檔案下載下來。

# MNIST中的手寫數字資料下載
# #HOME/scikit_learn_data/mldata/mnist-original.mat にキャッシュされる
print 'fetch MNIST dataset'
mnist = fetch_mldata('MNIST original')
# mnist.data : 70,000件的784位元的矢量數據
mnist.data   = mnist.data.astype(np.float32)
mnist.data  /= 255     # 轉換成0-1的資料

# mnist.target : 正解資料
mnist.target = mnist.target.astype(np.int32)

把其中三個來出來畫畫看。

# 將手寫數字資料繪圖功能
def draw_digit(data):
    size = 28
    plt.figure(figsize=(2.5, 3))

    X, Y = np.meshgrid(range(size),range(size))
    Z = data.reshape(size,size)   # convert from vector to 28x28 matrix
    Z = Z[::-1,:]             # flip vertical
    plt.xlim(0,27)
    plt.ylim(0,27)
    plt.pcolor(X, Y, Z)
    plt.gray()
    plt.tick_params(labelbottom="off")
    plt.tick_params(labelleft="off")

    plt.show()

draw_digit(mnist.data[5])
draw_digit(mnist.data[12345])
draw_digit(mnist.data[33456])

資料為28×28, 784位元的矢量數據。

digits-compressor.png

將資料集分成學習用和檢驗用兩份。

# 學習用資料為N個、檢驗用資料則設定為剩下的數量
N = 60000
x_train, x_test = np.split(mnist.data,   [N])
y_train, y_test = np.split(mnist.target, [N])
N_test = y_test.size

3.2 模型定義

最後是模型定義啦。這邊開始才是重點,開始使用Chainer的類別和方法。

# Prepare multi-layer perceptron model
# 多層感知模型設定
# 輸入 784次元、輸出 10次元
model = FunctionSet(l1=F.Linear(784, n_units),
                    l2=F.Linear(n_units, n_units),
                    l3=F.Linear(n_units, 10))

 

 

輸入的手寫數字資料為784位元,所以輸入的因子就是784個,這次中間層我們設定是1000。輸出則是想要辨識出來的十個數字(0-9)。下方是這次模組的圖解。

nn_structure6.png

順向傳播的構造為下方定義的forward()方法。

# Neural net architecture
# 神經網路構造
def forward(x_data, y_data, train=True):
    x, t = Variable(x_data), Variable(y_data)
    h1 = F.dropout(F.relu(model.l1(x)),  train=train)
    h2 = F.dropout(F.relu(model.l2(h1)), train=train)
    y  = model.l3(h2)
    # 根據多類別分類的計算誤差方法:softmax方法
    # 使用交叉熵方法來將誤差導出
    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

這邊來說明下使用的各個方法。

Chainer的做法為將資料排列變換為Chainer中的Variable類別對象

x, t = Variable(x_data), Variable(y_data)

激活方法不是使用SIG模組方法,而是使用F.relu()方法。

F.relu(model.l1(x))

這個F.relu()是正規化方法 – Rectified Linear Unit function

f(x)=max(0,x)f(x)=max(0,x)

也就是說,

relu-compressor.png

這種感覺。
畫圖碼在這裡。

# F.relu測試
x_data = np.linspace(-10, 10, 100, dtype=np.float32)
x = Variable(x_data)
y = F.relu(x)

plt.figure(figsize=(7,5))
plt.ylim(-2,10)
plt.plot(x.data, y.data)
plt.show()

是個簡單的方法。這個主要是為了計算量小、要求速度的情況設計

再來,這個relu()的輸出是要求F.dropout()作為輸入。

F.dropout(F.relu(model.l1(x)),  train=train)

F.dropout()是Dropout: A Simple Way to Prevent Neural Networks from Overfitting中推薦的計算方式,可以防止系統中間層過度學習。

稍微來試試看吧。

# dropout(x, ratio=0.5, train=True) テスト
# x: 輸入值
# ratio: 輸出為0的機率
# train: False的情況下x會保持原樣返回
# return: ratioの確率で0を、1−ratioの確率で,x*(1/(1-ratio))の値を返す

n = 50
v_sum = 0
for i in range(n):
    x_data = np.array([1,2,3,4,5,6], dtype=np.float32)
    x = Variable(x_data)
    dr = F.dropout(x, ratio=0.6,train=True)

    for j in range(6):
        sys.stdout.write( str(dr.data[j]) + ', ' )
    print("")
    v_sum += dr.data

# 輸出的平均與x_data大致一致
sys.stdout.write( str((v_sum/float(n))) )
output
2.5, 5.0, 7.5, 0.0, 0.0, 0.0, 
2.5, 5.0, 7.5, 10.0, 0.0, 15.0, 
0.0, 5.0, 7.5, 10.0, 12.5, 15.0, 
      ・・・
0.0, 0.0, 7.5, 10.0, 0.0, 0.0, 
2.5, 0.0, 7.5, 10.0, 0.0, 15.0, 
[ 0.94999999  2.29999995  3.          3.5999999   7.25        5.69999981]

 

將該陣列[1,2,3,4,5,6]傳入F.dropout()方法,現在ratio是指dropout機率,ratio設定為0.6,也就是60%的機率會dropout並且輸出 0 。也就是40%機率會輸出值,這個時候,因為輸出值降低到只有40%,為了補足所以將值要乘上2.5後再輸出,也就是

(0×0.6+2.5×0.4)=1

然後用平均過後的新數值更新為舊數值,而上面的例子中最後一行是輸出的平均,將[1,2,3,4,5,6]輸入後經過五十回反覆計算後得到的最相近值。

在經過一層同樣的程序,最後輸出的輸出值就是y了

    h2 = F.dropout(F.relu(model.l2(h1)), train=train)
    y  = model.l3(h2)

使用最後輸出的值和F.softmax_cross_entropy()方法來導出誤差,然後用和 F.accuracy()方法來計算出精確程度。

    # 根據多類別分類的計算誤差方法:softmax方法
    # 使用交叉熵方法來將誤差導出
    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

ソフトマックス関数ですが、

%e6%9c%aa%e5%91%bd%e5%90%8d

のように定義される関数で、この関数を挟むことでy1,,y10の10個の出力の総和が1となり、出力を確率として解釈することが可能になります。
なぜexp()exp⁡()関数が使われているかというと、値がマイナスにならないように、ということと自分は理解しています。

おなじみexp()exp⁡()関数は

exp-compressor (1).png
のような形なので、マイナスの値を取りません。これにより値がマイナスにならず、かつ総和が1ということになり、確率と解釈できるということですね。
さっきのソフトマックス関数の出力値Ykを用いて交差エントロピー関数は

未命名.png

と表現されます。

Chainerのコードで言うと、
https://github.com/pfnet/chainer/blob/master/chainer/functions/softmax_cross_entropy.py
にある、

def forward_cpu(self, inputs):
        x, t = inputs
        self.y, = Softmax().forward_cpu((x,))
        return -numpy.log(self.y[xrange(len(t)), t]).sum(keepdims=True) / t.size,

に相当します。

また、F.accuracy(y, t)は出力と、教師データを照合して正答率を返しています。

3.3 Optimizer的設定

現在,模組決定好後該移到訓練的步驟了。

這邊最優化方式是使用Adam。

# Setup optimizer
optimizer = optimizers.Adam()
optimizer.setup(model.collect_parameters())

Adam的相關介紹可以到echizen_tm寫的30分でわかるAdam裡了解。

4.訓練的執行與結果

以上的準備後,透過小批量的學習來執行手寫數字的辨識,來看看這樣的辨識結果準度如何。

train_loss = []
train_acc  = []
test_loss = []
test_acc  = []

l1_W = []
l2_W = []
l3_W = []

# Learning loop
for epoch in xrange(1, n_epoch+1):
    print 'epoch', epoch

    # training
    # 將依序的N個值用隨機的方式重新排列
    perm = np.random.permutation(N)
    sum_accuracy = 0
    sum_loss = 0
    # 將0~N個資料進行批量學習
    for i in xrange(0, N, batchsize):
        x_batch = x_train[perm[i:i+batchsize]]
        y_batch = y_train[perm[i:i+batchsize]]

        # 梯度初始化
        optimizer.zero_grads()
        # 通過正向傳播將誤差和準確度計算出來
        loss, acc = forward(x_batch, y_batch)
        # 通過逆向傳播計算梯度
        loss.backward()
        optimizer.update()

        train_loss.append(loss.data)
        train_acc.append(acc.data)
        sum_loss     += float(cuda.to_cpu(loss.data)) * batchsize
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize

    # 將訓練資料的誤差值與準確度表示出來
    print 'train mean loss={}, accuracy={}'.format(sum_loss / N, sum_accuracy / N)

    # evaluation
    # 將測試資料的誤差與準確度計算出後用來了解概括上的性能
    sum_accuracy = 0
    sum_loss     = 0
    for i in xrange(0, N_test, batchsize):
        x_batch = x_test[i:i+batchsize]
        y_batch = y_test[i:i+batchsize]

        # 通過正向傳播將誤差和準確度計算出來
        loss, acc = forward(x_batch, y_batch, train=False)

        test_loss.append(loss.data)
        test_acc.append(acc.data)
        sum_loss     += float(cuda.to_cpu(loss.data)) * batchsize
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize

    # 將測試資料的誤差值與準確度表示出來
    print 'test  mean loss={}, accuracy={}'.format(sum_loss / N_test, sum_accuracy / N_test)

    # 將學習後的參數記錄起來
    l1_W.append(model.l1.W)
    l2_W.append(model.l2.W)
    l3_W.append(model.l3.W)

# 將準確度與誤差畫成圖像
plt.figure(figsize=(8,6))
plt.plot(range(len(train_acc)), train_acc)
plt.plot(range(len(test_acc)), test_acc)
plt.legend(["train_acc","test_acc"],loc=4)
plt.title("Accuracy of digit recognition.")
plt.plot()

每一層epoch的摘要結果如下。20回學習得到98.5%左右的高準確度辨識。

output
epoch 1
train mean loss=0.278375425202, accuracy=0.914966667456
test  mean loss=0.11533634907, accuracy=0.964300005436
epoch 2
train mean loss=0.137060894324, accuracy=0.958216670454
test  mean loss=0.0765812527167, accuracy=0.976100009084
epoch 3
train mean loss=0.107826075749, accuracy=0.966816672881
test  mean loss=0.0749603212342, accuracy=0.97770000577
epoch 4
train mean loss=0.0939164237926, accuracy=0.970616674324
test  mean loss=0.0672153823725, accuracy=0.980000005364
epoch 5
train mean loss=0.0831089563683, accuracy=0.973950009048
test  mean loss=0.0705943618687, accuracy=0.980100004673
epoch 6
train mean loss=0.0752325405277, accuracy=0.976883343955
test  mean loss=0.0732760328815, accuracy=0.977900006771
epoch 7
train mean loss=0.0719517664274, accuracy=0.977383343875
test  mean loss=0.063611669606, accuracy=0.981900005937
epoch 8
train mean loss=0.0683009948514, accuracy=0.978566677173
test  mean loss=0.0604036964733, accuracy=0.981400005221
epoch 9
train mean loss=0.0621755663728, accuracy=0.980550010701
test  mean loss=0.0591542539285, accuracy=0.982400006652
epoch 10
train mean loss=0.0618313539471, accuracy=0.981183344225
test  mean loss=0.0693172766063, accuracy=0.982900006175
epoch 11
train mean loss=0.0583098273944, accuracy=0.982000010014
test  mean loss=0.0668152360269, accuracy=0.981600006819
epoch 12
train mean loss=0.054178619228, accuracy=0.983533344865
test  mean loss=0.0614466062452, accuracy=0.982900005579
epoch 13
train mean loss=0.0532431817259, accuracy=0.98390001148
test  mean loss=0.060112986485, accuracy=0.98400000751
epoch 14
train mean loss=0.0538122716064, accuracy=0.983266676267
test  mean loss=0.0624165921964, accuracy=0.983300005198
epoch 15
train mean loss=0.0501562882114, accuracy=0.983833344777
test  mean loss=0.0688113694015, accuracy=0.98310000658
epoch 16
train mean loss=0.0513108611095, accuracy=0.984533343514
test  mean loss=0.0724038232205, accuracy=0.982200007439
epoch 17
train mean loss=0.0471463404785, accuracy=0.985666677058
test  mean loss=0.0612579581685, accuracy=0.983600008488
epoch 18
train mean loss=0.0460166006556, accuracy=0.986050010125
test  mean loss=0.0654888718335, accuracy=0.984400007725
epoch 19
train mean loss=0.0458772557077, accuracy=0.986433342795
test  mean loss=0.0602016936944, accuracy=0.984400007129
epoch 20
train mean loss=0.046333729005, accuracy=0.986433343093
test  mean loss=0.0621869922416, accuracy=0.985100006461

各個批量的辨識準確度和誤差繪成圖後如下。紅色是訓練資料,藍色是測試資料

nn_result-compressor.png

以前有寫過一個【機械学習】k-nearest neighbor method(k最近傍法)を自力でpythonで書いて、手書き数字の認識をする的文章,同樣是手寫數字判斷,那個時候的準確度大概是97%左右,這個準確度稍微更高了一些些。

這個Chainer全部是用Python進行操作,Pythonista也是非常令人開心的框架。另外,這個應該還不能算是”Deep” learning,僅能算是 前饋神經網路,如果可以,最近應該會再寫一篇關於「深度」的文章。

5.驗證

我們來試試看辨識100個手寫數字影像。

隨機抽出100影像資料來測試後,結果是基本上都答對了,試了幾回100個影像辨識測試,看到了一個錯誤的辨識結果,將這個例子貼在下面,不知為什麼,人就是會有想測試的心態(笑)。

mnist_ans2-compressor.png
(※ 2行3列的4被辨識成9了)

plt.style.use('fivethirtyeight')
def draw_digit3(data, n, ans, recog):
    size = 28
    plt.subplot(10, 10, n)
    Z = data.reshape(size,size)   # convert from vector to 28x28 matrix
    Z = Z[::-1,:]             # flip vertical
    plt.xlim(0,27)
    plt.ylim(0,27)
    plt.pcolor(Z)
    plt.title("ans=%d, recog=%d"%(ans,recog), size=8)
    plt.gray()
    plt.tick_params(labelbottom="off")
    plt.tick_params(labelleft="off")


plt.figure(figsize=(15,15))

cnt = 0
for idx in np.random.permutation(N)[:100]:

    xxx = x_train[idx].astype(np.float32)
    h1 = F.dropout(F.relu(model.l1(Variable(xxx.reshape(1,784)))),  train=False)
    h2 = F.dropout(F.relu(model.l2(h1)), train=False)
    y  = model.l3(h2)
    cnt+=1
    draw_digit3(x_train[idx], cnt, y_train[idx], np.argmax(y.data))
plt.show
2 Comments

Add a Comment

發佈留言必須填寫的電子郵件地址不會公開。