Sponsored Link

Category Archives: プログラム

(10) cuda-convnetのレポート表示

(9) cuda-convnetを試してみる のトレーニング結果をレポート表示させてみます。
これも cuda-convnet が提供している機能です。

(1) cost functionの出力値

[user@linux]$ SAVEDATA=../save/ConvNet__2014-07-02_23.29.41
[user@linux]$ python shownet.py -f $SAVEDATA --show-cost=logprob

20140702_05

(2) error rate

[user@linux]$ python shownet.py -f $SAVEDATA --show-cost=logprob --cost-idx=1

20140702_06

(3) learned filters (conv1 layer)

conv1 とは、ネットワーク定義ファイルの中で当該レイヤーに付けた名前です。
先の実行時に指定したネットワーク定義ファイルはこれです。
–layer-def=./example-layers/layers-19pct.cfg

[user@linux]$ python shownet.py -f $SAVEDATA --show-filters=conv1

20140702_07

(4) learned filters (conv2 layer)

[user@linux]$ python shownet.py -f $SAVEDATA --show-filters=conv2

20140702_08

(5) learned filters (conv3 layer)

[user@linux]$ python shownet.py -f $SAVEDATA --show-filters=conv3

20140702_09

(6) learned filters (fc10 layer)

–channels=64 は、直前レイヤー(pool3)の channel数を指定します。

[user@linux]$ python shownet.py -f $SAVEDATA --show-filters=fc10 --channels=64

20140702_10

(7) 分類結果のうち8個をランダム表示

[user@linux]$ python shownet.py -f $SAVEDATA --show-preds=probs

正解したものは赤い線不正解のものは青い線 で表示されます。
線の長さは出力ユニットの出力値のはず…
20140702_11

ランダムなので毎回表示される画像が変わります。
20140702_12

(8) 分類結果のうち8個をランダム表示(エラーのみ)

–show-preds に加えて –only-errors=1 を指定すると、誤認識した結果のみを表示できます。

[user@linux]$ python shownet.py -f $SAVEDATA --show-preds=probs --only-errors=1

20140702_13

(9) cuda-convnetを試してみる

NVIDIA GTX-650の載ったマシンがあるので cuda-convnet なるGPU実装版NeuralNetプログラムを動かしてみる。ホームページはこちら(↓)
https://code.google.com/p/cuda-convnet/

トレーニングデータセットとして MNIST は提供されていないので…
今回は同ホームページで提供されている CIFAR-10 データセットを使用する。

(1) cuda-convnetをインストール

インストール手順はこちらに書かれている。
今回インストールするマシンには CUDA 6.0 , SDK 4.2 をインストールしてある。
この辺りのゴタゴタに3時間以上も費やしてしまった…
20140702_01

(2) データセットをダウンロード

データセットのダウンロードはこちらから。
20140702_02

(3) プログラムを実行

プログラムの実行手順はこちらに書かれている。
20140702_03

(4) 実行結果

今回は 10 epochs 指定で実行してみた。

[user@linux]$ python convnet.py --data-path=../data/cifar-10/ --save-path=../save --test-range=6 --train-range=1-5 --layer-def=./example-layers/layers-19pct.cfg --layer-params=./example-layers/layer-params-19pct.cfg --data-provider=cifar --test-freq=13

10epoch目の結果はこんな感じになった。
error rate = 0.366100 (accuracy=73.4%)
・1epochの処理に要する時間はおよそ 40秒前後 → octaveと比較するとめちゃくちゃ速い!

------------------------------------------------------- 
Layer 'conv1' weights[0]: 2.856662e-02 [1.153782e-04] 
Layer 'conv1' biases: 9.223316e-03 [3.505556e-06] 
Layer 'conv2' weights[0]: 1.143599e-02 [2.939290e-05] 
Layer 'conv2' biases: 7.934627e-03 [3.020574e-06] 
Layer 'conv3' weights[0]: 1.032112e-02 [2.550705e-05] 
Layer 'conv3' biases: 7.986603e-03 [2.903473e-06] 
Layer 'fc10' weights[0]: 2.028455e-03 [1.244750e-04] 
Layer 'fc10' biases: 2.140154e-01 [1.096559e-04] 
-------------------------------------------------------
Saved checkpoint to ../save/ConvNet__2014-07-02_23.33.24
======================================================= (8.310 sec)
10.1... logprob:  1.094434, 0.383200 (6.475 sec)
10.2... logprob:  1.132439, 0.391000 (6.473 sec)
10.3... logprob:  1.077382, 0.378500 (6.475 sec)
10.4... logprob:  1.107526, 0.386300 (6.474 sec)
10.5... logprob:  1.072043, 0.371800 
======================Test output======================
logprob:  1.077383, 0.366100 
------------------------------------------------------- 

ちなみにうちのGPUの性能はこんな感じです。

Device 0: "GeForce GTX 650"
  CUDA Driver Version / Runtime Version          6.0 / 6.0
  CUDA Capability Major/Minor version number:    3.0
  Total amount of global memory:                 1023 MBytes (1073020928 bytes)
  ( 2) Multiprocessors, (192) CUDA Cores/MP:     384 CUDA Cores
  GPU Clock rate:                                1058 MHz (1.06 GHz)
  Memory Clock rate:                             2500 Mhz
  Memory Bus Width:                              128-bit
  L2 Cache Size:                                 262144 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096)
  Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  2048
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 1 copy engine(s)
  Run time limit on kernels:                     No
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  Device supports Unified Addressing (UVA):      Yes
  Device PCI Bus ID / PCI location ID:           1 / 0

近日中に cuda-convnet上でMNISTをトレーニングしてみよう。

(7) 隠れ層のsigmoidをtanhにしてみる

sigmoidの代わりにtanh

活性化関数は sigmoid の他に tanh も使用するらしい。
入力値に対して、出力値が一定範囲内に制限されるならば、どんな関数でもよいらしい。

両関数の入力に対する出力をグラフにしてみた。tanhの方が縦に長く、横に短いようだ。
x=0付近の変化に敏感に反応するということか?

octave:1> x = [-10:0.1:10];
octave:2> y_sigmoid = 1./(1+exp(-x));
octave:3> y_tanh    = tanh(x);
octave:4> figure;
octave:5> plot(x,y_sigmoid,'b');
octave:6> hold on;
octave:7> plot(x,y_tanh,'r');
octave:8> 
octave:8> title('activation function');
octave:9> xlabel('IN');
octave:10> ylabel('OUT');
octave:11> h = legend ({'sigmoid', 'tanh'}, 'location', 'east');
octave:12> grid on;

20140824_02

実験結果

シンプル構成初版では、全層の活性化関数をsigmoidとしていた。
http://nnet.dogrow.net/?p=43
今回は、隠れ層をtanh, 出力層をsigmoid にしてMNIST自動認識を実行してみる。

(1) 3層 [784]-[16]-[10] 88.4%(-0.6%)

octave:1> NNET_control([784 16 10], 1)
EPOCH No.1
[ 0]  929 /  980 ( 94.8%)
[ 1] 1101 / 1135 ( 97.0%)
[ 2]  904 / 1032 ( 87.6%)
[ 3]  754 / 1010 ( 74.7%)
[ 4]  880 /  982 ( 89.6%)
[ 5]  773 /  892 ( 86.7%)
[ 6]  881 /  958 ( 92.0%)
[ 7]  926 / 1028 ( 90.1%)
[ 8]  815 /  974 ( 83.7%)
[ 9]  873 / 1009 ( 86.5%)
Total  8836 / 10000 ( 88.4%)

(2) 3層 [784]-[24]-[10] 88.8%(-1.1%)

octave:2> NNET_control([784 24 10], 1)
EPOCH No.1
[ 0]  951 /  980 ( 97.0%)
[ 1] 1113 / 1135 ( 98.1%)
[ 2]  873 / 1032 ( 84.6%)
[ 3]  870 / 1010 ( 86.1%)
[ 4]  839 /  982 ( 85.4%)
[ 5]  769 /  892 ( 86.2%)
[ 6]  877 /  958 ( 91.5%)
[ 7]  911 / 1028 ( 88.6%)
[ 8]  835 /  974 ( 85.7%)
[ 9]  847 / 1009 ( 83.9%)
Total  8885 / 10000 ( 88.8%)

(3) 3層 [784]-[32]-[10] 81.8%(-8.8%)

octave:4> NNET_control([784 48 10], 1)
EPOCH No.1
[ 0]  949 /  980 ( 96.8%)
[ 1] 1118 / 1135 ( 98.5%)
[ 2]  924 / 1032 ( 89.5%)
[ 3]    5 / 1010 (  0.5%) ←???
[ 4]  908 /  982 ( 92.5%)
[ 5]  781 /  892 ( 87.6%)
[ 6]  882 /  958 ( 92.1%)
[ 7]  906 / 1028 ( 88.1%)
[ 8]  822 /  974 ( 84.4%)
[ 9]  884 / 1009 ( 87.6%)
Total  8179 / 10000 ( 81.8%)

(4) 3層 [784]-[64]-[10] 89.6%(-1.2%)

octave:5> NNET_control([784 64 10], 1)
EPOCH No.1
[ 0]  945 /  980 ( 96.4%)
[ 1] 1114 / 1135 ( 98.1%)
[ 2]  900 / 1032 ( 87.2%)
[ 3]  915 / 1010 ( 90.6%)
[ 4]  883 /  982 ( 89.9%)
[ 5]  735 /  892 ( 82.4%)
[ 6]  902 /  958 ( 94.2%)
[ 7]  903 / 1028 ( 87.8%)
[ 8]  814 /  974 ( 83.6%)
[ 9]  851 / 1009 ( 84.3%)
Total  8962 / 10000 ( 89.6%)

(5) 3層 [784]-[128]-[10] 83.9%(-7.6%)

octave:6> NNET_control([784 128 10], 1)
EPOCH No.1
[ 0]  964 /  980 ( 98.4%)
[ 1] 1115 / 1135 ( 98.2%)
[ 2]  944 / 1032 ( 91.5%)
[ 3]  915 / 1010 ( 90.6%)
[ 4]  903 /  982 ( 92.0%)
[ 5]  809 /  892 ( 90.7%)
[ 6]  886 /  958 ( 92.5%)
[ 7]  938 / 1028 ( 91.2%)
[ 8]   24 /  974 (  2.5%) ←???
[ 9]  891 / 1009 ( 88.3%)
Total  8389 / 10000 ( 83.9%)

(6) 4層 [784]-[256]-[64]-[10] 72.3%(-19.6%)

octave:7> NNET_control([784 256 64 10], 1)
EPOCH No.1
[ 0]    1 /  980 (  0.1%) ←???
[ 1] 1113 / 1135 ( 98.1%)
[ 2]  905 / 1032 ( 87.7%)
[ 3]    0 / 1010 (  0.0%) ←???
[ 4]  842 /  982 ( 85.7%)
[ 5]  789 /  892 ( 88.5%)
[ 6]  885 /  958 ( 92.4%)
[ 7]  932 / 1028 ( 90.7%)
[ 8]  888 /  974 ( 91.2%)
[ 9]  875 / 1009 ( 86.7%)
Total  7230 / 10000 ( 72.3%)

すべてのレイヤー構成で全sigmoid版のスコアを下回った…
tanhの使い方が間違っているのか?

プログラムのソースコード

前版からの変更は以下の7ファイルのみ。

(1) NNET_control.m

function NNET_control( num_unit_of_each_layer, num_EPOCH )

  % 学習画像・ラベル、テスト画像・ラベルをファイルから読み込み
  [train_img, train_lbl] = load_MNIST( '../data/train-images-idx3-ubyte', '../data/train-labels-idx1-ubyte' );
  [test_img,  test_lbl ] = load_MNIST( '../data/t10k-images-idx3-ubyte',  '../data/t10k-labels-idx1-ubyte'  );

  % 各画像データを 0.0~1.0の範囲に正規化
  train_img = train_img / 255;
  test_img  = test_img  / 255;

  % 指定された層数、ユニット数でニューラルネットワークを作成
  nn = NNET_setup( num_unit_of_each_layer );

  % 指定EPOCH回数だけ繰り返す
  for epoch=1 : num_EPOCH

    % 学習実行
    nn = NNET_learn( nn, train_img, train_lbl );

    % テスト実行
    result = NNET_test( nn, test_img, test_lbl );

    % テスト結果を表示
    printf('\nEPOCH No.%d\n', epoch);
    for i=1: 10
      printf('[%2d] %4d / %4d (%5.1f%%) \n', i-1, result(i,2), result(i,1), result(i,2)/result(i,1)*100);
    end
    sum_result = sum(result,1);
    printf('Total %5d / %5d (%5.1f%%) \n', sum_result(2), sum_result(1), sum_result(2)/sum_result(1)*100);
    fflush(1);
  end
end

(2) NNET_setup.m

function nn = NNET_setup( num_unit_of_each_layer )

  % 乱数生成器を初期化
  rand('seed', 0);

  % 指定された層数を取得
  num_layer = numel( num_unit_of_each_layer );

  % 全層を初期化
  for i=2 : num_layer

    % 現層と前層のユニット数を取得
    num_unit_pre = num_unit_of_each_layer( i - 1 );
    num_unit     = num_unit_of_each_layer( i );

    % 各結合線の荷重を -1~1の一様分布乱数で初期化
    nn.layer{i}.weight = -1 + rand( num_unit, num_unit_pre ) * 2;

    % バイアスを初期化
    nn.layer{i}.bias = zeros( num_unit, 1 );

    % 活性化関数を登録
    if i==num_layer
      % 出力層であればsigmoid
      nn.layer{i}.actfunc  = @act_sigmoid;
      nn.layer{i}.dactfunc = @act_sigmoid_d;
    else
      % 隠れ層であればtanh
      nn.layer{i}.actfunc  = @act_tanh;
      nn.layer{i}.dactfunc = @act_tanh_d;
    end
  end
end

(3) NNET_propagation_forward.m

function nn = NNET_propagation_forward( nn, train_img )

  % 入力層の出力値を記憶
  nn.layer{1}.out = train_img(:);   % [n0][1]

  % 全層について順伝播を実行
  for i=2 : numel(nn.layer)

    % a = Σwz + bias    w[n1][n0] z[n0][1] bias[n1][1]
    nn.layer{i}.actprm = nn.layer{i}.weight * nn.layer{i-1}.out + nn.layer{i}.bias;

    % out = sigmoid(a)   out[n1][1]
    nn.layer{i}.out = nn.layer{i}.actfunc( nn.layer{i}.actprm );
  end
end

(4) NNET_propagation_back.m

function nn = NNET_propagation_back( nn, train_lbl )

  % 層数を取得
  num_layer = numel(nn.layer);

  % 出力層で検出された誤差量と逆伝播する勾配の初期値を算出
  [err, nn.layer{num_layer}.grad] = lossfunc(nn.layer{num_layer}.out, train_lbl);

  % 全層について誤差逆伝播を実行
  for i=num_layer : -1 : 2

    % 直前層の各ニューロンに伝播する勾配を算出
    %  δout                    
    %  ----- = w x h'(a)        
    %  δin                     
    %           |       δout|  
    %  grad = Σ|gout x -----|  
    %           |       δin |  

    % 配列要素数の同じ2パラメータを先に計算  grad[n1][1] out[n1][1]
    derr = nn.layer{i}.grad .* nn.layer{i}.dactfunc(nn.layer{i}.out);

    % Σ(w・derr)  w[n1][n0] derr[n1][1] grad[n0][1]
    nn.layer{i-1}.grad = nn.layer{i}.weight' * derr;

    % 結合荷重の修正量を算出    
    %  δE                      
    %  ---- = grad・h'(a)・out  
    %  δw                      
    % IN側ユニット-OUT側ユニットの組み合わせごとに算出 out[n0][1] derr[n1][1] dw[n1][n0]
    nn.layer{i}.dweight = derr * nn.layer{i-1}.out';

    % バイアスの修正量を算出
    nn.layer{i}.dbias = derr;
  end
end

(5) act_sigmoid.m

function y = act_sigmoid( x )
  y = 1 ./ (1 + exp(-x));   % sigmoid
end

(6) act_tanh.m

function y = act_tanh( x )
  y = tanh(x);
end

(7) act_tanh_d.m

function y = act_tanh_d( x )
  y = 1 - x.^2;         % tanh
end

00004_simpleNN_04

(5) EPOCH数を増やして正解率上昇

実験内容

シンプル構成初版では、1EPOCHで90%前後の正解率だった。
http://nnet.dogrow.net/?p=43

今回は EPOCH数を増やして正解率の向上を目論む。
対象は、シンプル構成初版の実験で 90.8%(1EPOCH)のスコアを出した [784]-[64]-[10]の層構成とする。
シンプル構成初版プログラムからの変更は、学習とテストを指定EPOCH数分繰り返すようにしただけ。

実験結果

初回の正解率が 90.8%、34回目には 96.5%まで上昇した。
http://nnet.dogrow.net/wp-content/uploads/2013/10/20131006_01.png

EPOCH No.1
[ 0]  949 /  980 ( 96.8%) 
[ 1] 1109 / 1135 ( 97.7%) 
[ 2]  907 / 1032 ( 87.9%) 
[ 3]  901 / 1010 ( 89.2%) 
[ 4]  898 /  982 ( 91.4%) 
[ 5]  745 /  892 ( 83.5%) 
[ 6]  898 /  958 ( 93.7%) 
[ 7]  929 / 1028 ( 90.4%) 
[ 8]  859 /  974 ( 88.2%) 
[ 9]  888 / 1009 ( 88.0%) 
Total  9083 / 10000 ( 90.8%)
EPOCH No.34
[ 0]  966 /  980 ( 98.6%) 
[ 1] 1120 / 1135 ( 98.7%) 
[ 2]  991 / 1032 ( 96.0%) 
[ 3]  974 / 1010 ( 96.4%) 
[ 4]  956 /  982 ( 97.4%) 
[ 5]  860 /  892 ( 96.4%) 
[ 6]  929 /  958 ( 97.0%) 
[ 7]  983 / 1028 ( 95.6%) 
[ 8]  923 /  974 ( 94.8%) 
[ 9]  953 / 1009 ( 94.4%) 
Total  9655 / 10000 ( 96.5%)

プログラムのソースコード

シンプル構成初版からの変更点は下記の2点のみ。
(1)コマンドライン引数でEPOCH数を指定可能とした。
(2)学習、テストを指定EPOCH数分繰り返すようにした。

NNET_control.m ※シンプル構成初版からの変更はこの1ファイルのみ。

function NNET_control( num_unit_of_each_layer, num_EPOCH )

  % 学習画像・ラベル、テスト画像・ラベルをファイルから読み込み
  [train_img, train_lbl] = load_MNIST( '../data/train-images-idx3-ubyte', '../data/train-labels-idx1-ubyte' );
  [test_img,  test_lbl ] = load_MNIST( '../data/t10k-images-idx3-ubyte',  '../data/t10k-labels-idx1-ubyte'  );

  % 各画像データを 0.0~1.0の範囲に正規化
  train_img = train_img / 255;
  test_img  = test_img  / 255;

  % 指定された層数、ユニット数でニューラルネットワークを作成
  nn = NNET_setup( num_unit_of_each_layer );

  % 正解率記録用領域を確保
  accuracy = zeros(num_EPOCH,1);

  % 指定EPOCH回数だけ繰り返す
  for epoch=1 : num_EPOCH

    % 学習実行
    nn = NNET_learn( nn, train_img, train_lbl );

    % テスト実行
    result = NNET_test( nn, test_img, test_lbl );

    % テスト結果を表示
    printf('\nEPOCH No.%d\n', epoch);
    for i=1: 10
      printf('[%2d] %4d / %4d (%5.1f%%) \n', i-1, result(i,2), result(i,1), result(i,2)/result(i,1)*100);
    end
    sum_result = sum(result,1);
    printf('Total %5d / %5d (%5.1f%%) \n', sum_result(2), sum_result(1), sum_result(2)/sum_result(1)*100);
    fflush(1);

    % 正解率をCSVファイルに出力
    if exist('accuracy.csv','file')~=0
      delete('accuracy.csv');
    end
    accuracy(epoch) = sum_result(2)/sum_result(1);  % 正解率
    csvwrite('accuracy.csv', accuracy);

  end
end

シンプル構成初版のプログラムソースコードはこちら。
http://nnet.dogrow.net/?p=43

00002_simpleNN_02

(4) シンプル構成の初版は正解率91%

プログラムの仕様

ニューラルネットワークの入門書に載っている普通のシンプルなネットワーク構成で実装してみた。
プログラムの仕様は以下の通り。
(1) MNIST画像データ60,000枚を学習
(2) MNISTテストデータ 10,000枚をテスト
(3) 入力層のユニット数は28×28の784個
(4) 出力層のユニット数は数字0~9に対応する10個
(5) 隠れ層の層数と各層のユニット数は引数で任意に指定可能
(6) 数字別に正解率を表示
(7) 各層の活性化関数はSigmoid
(8) 誤差関数は二乗和誤差
(9) Epoch数は1
(10) 1学習データの学習ごとにパラメーターを更新

プログラムの実行結果

適当に選んだ全9パターンの層構成を試した結果は以下のようになった。
この中では 4層 [784]-[256]-[64]-[10] の正解率 91.9% が最も高かった。

(1) 3層 [784]-[16]-[10] 89.0%

octave:10> tic;NNET_control([784 16 10]);toc;
[ 0]  938 /  980 ( 95.7%)
[ 1] 1109 / 1135 ( 97.7%)
[ 2]  888 / 1032 ( 86.0%)
[ 3]  892 / 1010 ( 88.3%)
[ 4]  878 /  982 ( 89.4%)
[ 5]  719 /  892 ( 80.6%)
[ 6]  889 /  958 ( 92.8%)
[ 7]  896 / 1028 ( 87.2%)
[ 8]  803 /  974 ( 82.4%)
[ 9]  890 / 1009 ( 88.2%)
Total  8902 / 10000 ( 89.0%)
Elapsed time is 63.3441 seconds.

(2) 3層 [784]-[24]-[10] 89.9%

octave:7> tic;NNET_control([784 24 10]);toc;
[ 0]  949 /  980 ( 96.8%)
[ 1] 1109 / 1135 ( 97.7%)
[ 2]  907 / 1032 ( 87.9%)
[ 3]  907 / 1010 ( 89.8%)
[ 4]  887 /  982 ( 90.3%)
[ 5]  708 /  892 ( 79.4%)
[ 6]  882 /  958 ( 92.1%)
[ 7]  925 / 1028 ( 90.0%)
[ 8]  858 /  974 ( 88.1%)
[ 9]  856 / 1009 ( 84.8%)
Total  8988 / 10000 ( 89.9%)
Elapsed time is 65.8959 seconds.

(3) 3層 [784]-[32]-[10] 90.0%

octave:6> tic;NNET_control([784 32 10]);toc;
[ 0]  950 /  980 ( 96.9%)
[ 1] 1108 / 1135 ( 97.6%)
[ 2]  885 / 1032 ( 85.8%)
[ 3]  905 / 1010 ( 89.6%)
[ 4]  894 /  982 ( 91.0%)
[ 5]  718 /  892 ( 80.5%)
[ 6]  895 /  958 ( 93.4%)
[ 7]  945 / 1028 ( 91.9%)
[ 8]  828 /  974 ( 85.0%)
[ 9]  869 / 1009 ( 86.1%)
Total  8997 / 10000 ( 90.0%)
Elapsed time is 71.066 seconds.

(4) 3層 [784]-[48]-[10] 90.6%

octave:9> tic;NNET_control([784 48 10]);toc;
[ 0]  955 /  980 ( 97.4%)
[ 1] 1117 / 1135 ( 98.4%)
[ 2]  909 / 1032 ( 88.1%)
[ 3]  931 / 1010 ( 92.2%)
[ 4]  920 /  982 ( 93.7%)
[ 5]  746 /  892 ( 83.6%)
[ 6]  892 /  958 ( 93.1%)
[ 7]  917 / 1028 ( 89.2%)
[ 8]  824 /  974 ( 84.6%)
[ 9]  847 / 1009 ( 83.9%)
Total  9058 / 10000 ( 90.6%)
Elapsed time is 74.398 seconds.

(5) 3層 [784]-[64]-[10] 90.8%

octave:1> tic;NNET_control([784 64 10]);toc;
[ 0]  949 /  980 ( 96.8%)
[ 1] 1109 / 1135 ( 97.7%)
[ 2]  907 / 1032 ( 87.9%)
[ 3]  901 / 1010 ( 89.2%)
[ 4]  898 /  982 ( 91.4%)
[ 5]  745 /  892 ( 83.5%)
[ 6]  898 /  958 ( 93.7%)
[ 7]  929 / 1028 ( 90.4%)
[ 8]  859 /  974 ( 88.2%)
[ 9]  888 / 1009 ( 88.0%)
Total  9083 / 10000 ( 90.8%)
Elapsed time is 81.1507 seconds.

(6) 3層 [784]-[96]-[10] 90.4%

octave:4> tic;NNET_control([784 96 10]);toc;
[ 0]  953 /  980 ( 97.2%)
[ 1] 1114 / 1135 ( 98.1%)
[ 2]  910 / 1032 ( 88.2%)
[ 3]  915 / 1010 ( 90.6%)
[ 4]  851 /  982 ( 86.7%)
[ 5]  720 /  892 ( 80.7%)
[ 6]  888 /  958 ( 92.7%)
[ 7]  937 / 1028 ( 91.1%)
[ 8]  865 /  974 ( 88.8%)
[ 9]  888 / 1009 ( 88.0%)
Total  9041 / 10000 ( 90.4%)
Elapsed time is 112.633 seconds.

(7) 3層 [784]-[128]-[10] 91.5%

octave:5> tic;NNET_control([784 128 10]);toc;
[ 0]  951 /  980 ( 97.0%)
[ 1] 1112 / 1135 ( 98.0%)
[ 2]  931 / 1032 ( 90.2%)
[ 3]  897 / 1010 ( 88.8%)
[ 4]  879 /  982 ( 89.5%)
[ 5]  784 /  892 ( 87.9%)
[ 6]  892 /  958 ( 93.1%)
[ 7]  927 / 1028 ( 90.2%)
[ 8]  868 /  974 ( 89.1%)
[ 9]  904 / 1009 ( 89.6%)
Total  9145 / 10000 ( 91.5%)
Elapsed time is 152.437 seconds.

(8) 4層 [784]-[256]-[32]-[10] 91.5%

octave:12> tic;NNET_control([784 256 32 10]);toc;
[ 0]  957 /  980 ( 97.7%)
[ 1] 1098 / 1135 ( 96.7%)
[ 2]  929 / 1032 ( 90.0%)
[ 3]  901 / 1010 ( 89.2%)
[ 4]  876 /  982 ( 89.2%)
[ 5]  781 /  892 ( 87.6%)
[ 6]  899 /  958 ( 93.8%)
[ 7]  942 / 1028 ( 91.6%)
[ 8]  862 /  974 ( 88.5%)
[ 9]  902 / 1009 ( 89.4%)
Total  9147 / 10000 ( 91.5%)
Elapsed time is 301.459 seconds.

(9) 4層 [784]-[256]-[64]-[10] 91.9%

octave:11> tic;NNET_control([784 256 64 10]);toc;
[ 0]  960 /  980 ( 98.0%)
[ 1] 1109 / 1135 ( 97.7%)
[ 2]  904 / 1032 ( 87.6%)
[ 3]  882 / 1010 ( 87.3%)
[ 4]  909 /  982 ( 92.6%)
[ 5]  786 /  892 ( 88.1%)
[ 6]  910 /  958 ( 95.0%)
[ 7]  936 / 1028 ( 91.1%)
[ 8]  874 /  974 ( 89.7%)
[ 9]  921 / 1009 ( 91.3%)
Total  9191 / 10000 ( 91.9%)
Elapsed time is 309.785 seconds.

プログラムのソースコード

(1) NNET_control.m

function NNET_control( num_unit_of_each_layer )

  % 学習画像・ラベル、テスト画像・ラベルをファイルから読み込み
  [train_img, train_lbl] = load_MNIST( 'train-images-idx3-ubyte', 'train-labels-idx1-ubyte' );
  [test_img,  test_lbl ] = load_MNIST( 't10k-images-idx3-ubyte',  't10k-labels-idx1-ubyte'  );

  % 各画像データを 0.0~1.0の範囲に正規化
  train_img = train_img / 255;
  test_img  = test_img  / 255;

  % 指定された層数、ユニット数でニューラルネットワークを作成
  nn = NNET_setup( num_unit_of_each_layer );

  % 学習実行
  nn = NNET_learn( nn, train_img, train_lbl );

  % テスト実行
  result = NNET_test( nn, test_img, test_lbl );

  % テスト結果を表示
  for i=1: 10
    printf('[%2d] %4d / %4d (%5.1f%%) \n', i-1, result(i,2), result(i,1), result(i,2)/result(i,1)*100);
  end
  sum_result = sum(result,1);
  printf('Total %5d / %5d (%5.1f%%) \n', sum_result(2), sum_result(1), sum_result(2)/sum_result(1)*100);
end

(2) load_MNIST.m

function [image label] = load_MNIST( image_file, label_file )
  %//////////////////////////////////////////////////////////////
  % 画像をロード
  % ファイルからロードした画像を image[28][28][60000]の配列で出力
  fid = fopen(image_file,'r','b');
  magic_number      = fread(fid, 1, 'int32');
  number_of_items   = fread(fid, 1, 'int32');
  number_of_rows    = fread(fid, 1, 'int32');
  number_of_columns = fread(fid, 1, 'int32');
  img               = fread(fid, [number_of_rows*number_of_columns number_of_items],'uint8');
  image = permute(reshape(img, number_of_rows, number_of_columns,number_of_items),[2 1 3]);
  fclose(fid);

  %//////////////////////////////////////////////////////////////
  % ラベルをロード
  % ファイルからロードしたラベルを label[10][60000]の配列で出力
  fid = fopen(label_file,'r','b');
  magic_number    = fread(fid, 1,               'int32');
  number_of_items = fread(fid, 1,               'int32');
  lbl             = fread(fid, number_of_items, 'uint8');
  idx             = [1:number_of_items]';
  lblidx          = lbl * number_of_items + idx;
  label           = zeros(number_of_items,10);
  label(lblidx)   = 1;
  label           = label';
  fclose(fid);
end

(3) NNET_setup.m

function nn = NNET_setup( num_unit_of_each_layer )

  % 乱数生成器を初期化
  rand('seed', 0);

  % 指定された層数を取得
  num_layer = numel( num_unit_of_each_layer );

  % 全層を初期化
  for i=2 : num_layer

    % 現層と前層のユニット数を取得
    num_unit_pre = num_unit_of_each_layer( i - 1 );
    num_unit     = num_unit_of_each_layer( i );

    % 各結合線の荷重を -1~1の一様分布乱数で初期化
    nn.layer{i}.weight = -1 + rand( num_unit, num_unit_pre ) * 2;

    % バイアスを初期化
    nn.layer{i}.bias = zeros( num_unit, 1 );
  end
end

(4) NNET_learn.m

function nn = NNET_learn( nn, train_img, train_lbl )

  % 学習データ数を取得
  num_data = size( train_img, 3 );

  % 学習データをシャッフル
  randvector = randperm( num_data );
  train_img  = train_img(:,:,randvector);
  train_lbl  = train_lbl(:,randvector);

  % 全学習画像を1個ずつ学習させる
  for i=1 : num_data
    % 順伝播
    nn = NNET_propagation_forward( nn, train_img(:,:,i) );

    % 誤差逆伝播
    nn = NNET_propagation_back( nn, train_lbl(:,i) );

    % パラメーター更新
    nn = NNET_update( nn, 0.05 );
  end
end

(5) NNET_propagation_forward.m

function nn = NNET_propagation_forward( nn, train_img )

  % 入力層の出力値を記憶
  nn.layer{1}.out = train_img(:);   % [n0][1]

  % 全層について順伝播を実行
  for i=2 : numel(nn.layer)

    % a = Σwz + bias    w[n1][n0] z[n0][1] bias[n1][1]
    nn.layer{i}.actprm = nn.layer{i}.weight * nn.layer{i-1}.out + nn.layer{i}.bias;

    % out = sigmoid(a)   out[n1][1]
    nn.layer{i}.out = sigmoid( nn.layer{i}.actprm );
  end
end

(6) NNET_propagation_back.m

function nn = NNET_propagation_back( nn, train_lbl )

  % 層数を取得
  num_layer = numel(nn.layer);

  % 出力層で検出された誤差量と逆伝播する勾配の初期値を算出
  [err, nn.layer{num_layer}.grad] = lossfunc(nn.layer{num_layer}.out, train_lbl);

  % 全層について誤差逆伝播を実行
  for i=num_layer : -1 : 2

    % 直前層の各ニューロンに伝播する勾配を算出
    %  δout                    
    %  ----- = w x h'(a)        
    %  δin                     
    %           |       δout|  
    %  grad = Σ|gout x -----|  
    %           |       δin |  

    % 配列要素数の同じ2パラメータを先に計算  grad[n1][1] out[n1][1]
    derr = nn.layer{i}.grad .* dsigmoid(nn.layer{i}.out);

    % Σ(w・derr)  w[n1][n0] derr[n1][1] grad[n0][1]
    nn.layer{i-1}.grad = nn.layer{i}.weight' * derr;

    % 結合荷重の修正量を算出    
    %  δE                      
    %  ---- = grad・h'(a)・out  
    %  δw                      
    % IN側ユニット-OUT側ユニットの組み合わせごとに算出 out[n0][1] derr[n1][1] dw[n1][n0]
    nn.layer{i}.dweight = derr * nn.layer{i-1}.out';

    % バイアスの修正量を算出
    nn.layer{i}.dbias = derr;
  end
end

(7) NNET_update.m

function nn = NNET_update( nn, ratio )

  % 全層について結合荷重とバイアスを更新
  for i=2: numel(nn.layer)
    nn.layer{i}.weight = nn.layer{i}.weight - ratio * nn.layer{i}.dweight;
    nn.layer{i}.bias   = nn.layer{i}.bias   - ratio * nn.layer{i}.dbias;
  end
end

(8) NNET_test.m

function result = NNET_test( nn, test_img, test_lbl )

  % テストデータ数を取得
  num_data = size(test_img, 3);

  % テスト結果の記録領域を初期化
  result = zeros(size(test_lbl,1),2);

  % 全学習画像をテスト実行
  for i=1 : num_data
    % 順伝播
    nn = NNET_propagation_forward( nn, test_img(:,:,i) );

    % 自動識別結果(出力層の出力値)を取得
    result_lbl = nn.layer{numel(nn.layer)}.out;
    [~, idx_cor] = max(test_lbl(:,i));    % 期待値を取得
    [~, idx_res] = max(result_lbl);       % 判定結果を取得

    % テスト結果を記憶
    result(idx_cor ,1) = result(idx_cor ,1) + 1;    % 数字別のテスト数+1
    if idx_cor == idx_res
      result(idx_cor ,2) = result(idx_cor ,2) + 1;  % 数字別の正解数+1
    end
  end
end

(9) sigmoid.m

function y = sigmoid( x )
  y = 1 ./ (1 + exp(-x));
end

(10) dsigmoid.m

function y = dsigmoid( x )
  y = x .* (1 - x);
end

(11) lossfunc.m

function [err grad] = lossfunc( out, lbl )

  % 伝播する誤差の初期値を算出
  grad  = out - lbl;   % out[n1][1] lbl[n1][1]

  % 誤差関数種別は二乗和誤差とする。
  %        1              2         
  % err = ---Σ(out - lbl)          
  %        2                        
  err = grad' * grad / 2;
end

00001_simpleNN