2017年度 MI/CS実験第2 第4ラウンド
山
匡 (MIコース)
「作ってわかる深層学習」
2回目スライド資料
スケジュール
第1回 (1/10): ガイダンス、ニューラルネット概論と肩慣らし
第2回 (1/15): 誤差逆伝播のミニ講義と実装
第3回 (1/17): 手書き文字認識のテスト
第4回 (1/22): 自己符号化器のミニ講義と実装
第5回 (1/24): 実装
第6回 (1/29): 実装
パーセプトロン
パーセプトロン
Rosenblat (1958)パーセプトロン
Rosenblat (1958)世界で最初のニューラルネット
第一次ブームの火付け役
3層構造 (入力・中間・出力)
教師付学習機械
教師信号
パーセプトロンの特徴
中間層に多数のニューロン
入力層∼中間層の結合は
スパースかつランダムで
固定
→ (教師付)学習
教師信号
中間層∼出力層の結合重みを
教師信号を使って変化させる
教師信号
教師付学習
文脈信号 (
x) と教師信号 (z)のペア
n種類
x
(i)を入力層に入れると
z
(i)
を出力
するように重みを変える
→ (教師付)学習
誤差
を定義して、これを減らす
E =
!
n
1
2
"
"z
(teacher)
n
− z
(output)
n
"
"
2
D = {(x
(1)
, z
(1)
)
, · · · , (x
(
n)
, z
(
n)
)
}
ベクトル
学習ルール (パーセプトロン則)
E =
!
n
1
2
"
"z
(teacher)
n
− z
(output)
n
"
"
2
誤差
dw
(hidden)
dt
= −
∂E
∂w
(hidden)
計算する式
w
(hidden)
(
t + 1) = w
(hidden)
(
t) − η
∂E
∂w
(hidden)
= w
(hidden)
(
t) − η∇E
w
(hidden)エポック数に
関して離散化
ベクトル
学習係数
w
(hidden)
(
t + 1) = w
(hidden)
(
t) − η
!
n
∂E
n
∂w
(hidden)
= w
(hidden)
(
t) − η
!
n
∇E
n,w
(hidden)個々のサンプル
に関して書くと
E =
!
n
E
n
E
n=
1
2
!
i"
z
(teacher)n i− z
(output)n i#
2学習ルール (パーセプトロン則)
w
(hidden)
(
t + 1) = w
(hidden)
(
t) − η
!
n
∂E
n
∂w
(hidden)
= w
(hidden)
(
t) − η
!
n
∇E
n,w
(hidden)個々のサンプル
に関して書くと
E =
!
n
E
n
def⎛
⎜
⎜
⎝
∂En ∂w0..
.
∂En ∂wl−1⎞
⎟
⎟
⎠
中間層∼出力層の結合一つ一つ
よって、任意の
w
ij
(hidden)
に対して、
を計算
∂E
n
∂w
ij
(hidden)
すれば良い。
∂E
n
∂w
ij
(hidden)
の計算
∂E
n
∂w
(hidden)
ij
=
∂E
n
∂z
(output)
n
i
∂z
(output)
n
i
∂w
(hidden)
ij
E
n=
1
2
!
i"
z
(teacher)n i− z
(output)n i#
2∂E
n∂z
(output)n i= −
!
z
(teacher)n i− z
(output)n i"
z
(output)n i= f
⎛
⎝
#
jw
(hidden) ijz
(hidden)n j⎞
⎠
∂z (output)n i ∂w(hidden) ij = f′ ⎛ ⎝# j w(hidden) ij z (hidden)n j ⎞ ⎠ z(hidden)n jf (x) =
1
1 +
e
−x
f
′
(
x) = f (x) (1 − f (x))
まとめると
∂E
n∂w
hidden ij= −
!
z
(teacher)n i− z
(output)n i"
z
(output)n i!
1
− z
i(output)n"
z
(hidden)n jw
(hidden) ij(
t + 1) = w
(hidden) ij(
t) − η
!
n∂E
n∂w
(hidden) ij= w
(hidden) ij(
t) + η
!
n"
z
(teacher)n i− z
(output)i n#
z
(output)n i"
1
− z
(output)n i#
z
(hidden)n jとなるので、更新式は
となる。
勾配降下法
という名前がついている
コード
Layer *output_layer = &network -> layer [ network -> n - 1 ]; Layer *hidden_layer = &network -> layer [ network -> n - 2 ]; Connection *c = &network -> connection [ network -> n - 2 ]; for ( int i = 0; i < c -> n_post; i++ ) {
for ( int j = 0; j < c -> n_pre; j++ ) { double o = output_layer -> z [ i ]; double d = ( z [ i ] - o ) * o * ( 1 - o );
c -> dw [ j + c -> n_pre * i ] += Eta * d * ( hidden_layer -> z [ j ] ); } }
w
(hidden) ij(
t + 1) = w
(hidden) ij(
t) − η
!
n∂E
n∂w
(hidden) ij= w
(hidden) ij(
t) + η
!
n"
z
(teacher)n i− z
(output) n i#
z
(output)n i"
1
− z
(output)n i#
z
(hidden)n jdouble updateByPerceptronRule ( Network *network, Neuron z [ ] );
質問は?
例: XORの学習
x
1x
0z
0
0
0
0
1
1
1
0
1
1
1
0
1個
128個
2個
Network network; createNetwork ( &network, 3, rng ); createLayer ( &network, 0, 2 ); createLayer ( &network, 1, 128 ); createLayer ( &network, 2, 1 );createConnection ( &network, 0, sparse_random ); createConnection ( &network, 1, uniform_random );
double uniform_random ( Network *n, const int i, const int j ) {
return 1. - 2. * sfmt_genrand_real2 ( &n -> rng ); }
double sparse_random ( Network *n, const int i, const int j ) {
return ( sfmt_genrand_real2 ( &n -> rng ) < 0.5 ) ? uniform_random ( n, i, j ) : 0.;
}
コード
void createConnection ( Network *network, const int layer_id, double ( *func ) ( Network *, const int, const int ) )
{
Connection *connection = &network -> connection [ layer_id ];
const int n_pre = network -> layer [ layer_id ] . n + 1; // +1はバイアスの分 const int n_post = ( layer_id == network -> n - 1 ) ? 1 : network -> layer [ layer_id + 1 ] . n;
connection -> w = ( Weight * ) malloc ( n_pre * n_post * sizeof ( Weight ) ); for ( int i = 0; i < n_post; i++ ) {
for ( int j = 0; j < n_pre; j++ ) {
connection -> w [ j + n_pre * i ] = func ( network, i, j ); }
}
connection -> dw = ( Weight * ) malloc ( n_pre * n_post * sizeof ( Weight ) ); for ( int i = 0; i < n_post; i++ ) {
for ( int j = 0; j < n_pre; j++ ) { connection -> dw [ j + n_pre * i ] = 0.; }
}
connection -> n_pre = n_pre; connection -> n_post = n_post; }
void deleteConnection ( Network *network, const int layer_id ) {
Connection *connection = &network -> connection [ layer_id ]; free ( connection -> w );
free ( connection -> dw ); }
Neuron x [ 4 ][ 2 ] = { { 0., 0. }, { 0., 1. }, { 1., 0. }, { 1., 1. } }; Neuron z [ 4 ][ 1 ] = { { 0. } , { 1. } , { 1. } , { 0.} };
const int number_of_training_data = 4; // Training
double error = 1.0; // arbitrary large number const double Epsilon = 0.001; // tolerant error rate int i = 0;
while ( error > Epsilon ) { error = 0.;
initializeDW ( &network );
for ( int j = 0; j < number_of_training_data; j++ ) {
//int k = ( int ) ( number_of_training_data * sfmt_genrand_real2 ( &rng ) );
int k = j;
setInput ( &network, x [ k ] ); forwardPropagation ( &network, sigmoid );
error += updateByPerceptronRule ( &network, z [ k ] ); }
updateW ( &network ); printf ( "%d %f\n", i, error ); i++;
}
// Test
Layer *output_layer = &network . layer [ network. n - 1 ]; const int n = output_layer -> n;
for ( int i = 0; i < number_of_training_data; i++ ) { setInput ( &network, x [ i ] );
forwardPropagation ( &network, sigmoid ); for ( int j = 0; j < n; j++ ) {
fprintf ( stderr, "%f%s", output_layer -> z [ j ], ( j == n - 1 ) ? "\n" : " " );
} }
void initializeDW ( Network *network ) {
Connection *c = &network -> connection [ network -> n - 2 ]; for ( int i = 0; i < c -> n_post; i++ ) {
for ( int j = 0; j < c -> n_pre; j++ ) { c -> dw [ j + c -> n_pre * i ] = 0.; }
} }
void updateW ( Network *network ) {
Connection *c = &network -> connection [ network -> n - 2 ]; for ( int i = 0; i < c -> n_post; i++ ) {
for ( int j = 0; j < c -> n_pre; j++ ) {
c -> w [ j + c -> n_pre * i ] += c -> dw [ j + c -> n_pre * i ]; }
} }
コンパイルして実行
[ya000836@purple99 ~/dl]$ gcc -O3 -std=c99 -Wall -I SFMT-src-1.5.1 -D SFMT_MEXP=19937 -o perceptron perceptron.c SFMT-src-1.5.1/SFMT.c [ya000836@purple99 ~/dl]$ ./perceptron 0 0.879170 1 0.836272 2 0.766234 3 0.658244 4 0.543239 : 4302001 0.001000 4302002 0.001000 4302003 0.001000 4302004 0.001000 4302005 0.001000 # of epochs = 4302006 0.022023 0.977739 0.977538 0.022691
}
学習結果
}
エポック番号と誤差
誤差の減り方を見る
[ya000836@purple99 ~/dl]$ ./perceptron > perceptron.dat 0.022023 0.977739 0.977538 0.022691 [ya000836@purple99 ~/dl]$ gnuplot G N U P L O T
Version 4.6 patchlevel 6 last modified September 2014 Build System: Darwin x86_64
Copyright (C) 1986-1993, 1998, 2004, 2007-2014 Thomas Williams, Colin Kelley and many others gnuplot home: http://www.gnuplot.info faq, bugs, etc: type "help FAQ"
immediate help: type "help" (plot window: hit 'h') Terminal type set to 'x11'
gnuplot> plot 'perceptron.dat'
ボーナス課題 1
2層のパーセプトロンでXORは学習できるか?
単純パーセプトロン
学習できる→具体的にネットワークを呈示せよ
学習できない→なぜなのか理由を考察せよ
ヒント: 「線形分離」についてググれ
色々試すこと
・中間層のニューロン数を変える
・収束までのエポック数を調べる
多層パーセプトロン
多層パーセプトロン
XORのためには中間層のニューロンは何個必要か?
2個で必要十分
パーセプトロンの問題
中間層にたくさんのニューロンが必要
→ 入力信号を区別可能にするため
解決策:
入力層∼中間層の結合も変化させる
→誤差逆伝播
誤差逆伝播
w
i j
(l −1)
(t + 1) = w
i j
(l −1)
(t) − η
!
n
∂E
n
∂w
i j
(l −1)
∂E
n
∂w
i j
(l −1)
=
∂E
n
∂z
i
(l )
∂z
(l )
i
∂w
i j
(l −1)
層
l-1から層lへの結合の重みの更新式は同様に
上の層で決まる値
∂z
i(l )∂w
i j(l −1)= z
(l )i!
1 − z
(l )i"
z
(l −1) j誤差逆伝播
∂E
n∂z
(l ) i= −
!
z
(teacher)n i−
z
(l ) i"
層
lが出力層の場合はパーセプトロン則と同じで
∂E
n
∂z
(l )
i
=
!
k
∂E
n
∂z
(l +1)
k
∂z
(l +1)
k
∂z
(l )
i
それ以外の場合は
さらに上の層で決まる値
誤差逆伝播
上の層から下の層に向かって順番に計算すれば、
∂E
n
∂z
(l +1)
k
は計算済み (出力層の場合は
∂En ∂z(l ) i = − ! z(teacher)n i −z (l ) i ")
∂E
n
∂z
(l )
i
=
!
k
∂E
n
∂z
(l +1)
k
∂z
(l +1)
k
∂z
(l )
i
(再掲)
z(l+1) k = f ! " i w(l) kiz (l) i #∂z
(l +1)
k
∂z
(l )
i
= f
′
!
"
i
w
(l )
ki
z
(l )
i
#
w
(l )
ki
= z
k
(l +1)
$
1 − z
(l +1)
k
%
w
(l )
ki
ここで
かつfはシグモイド
誤差逆伝播
w
i j(l −1)(t + 1) = w
i j(l −1)(t) + η
!
n⎛
⎝
∂E
n∂z
(l )i⎞
⎠
&
z
i(l )&
1 − z
(l )i'
z
j(l −1)'
∂E
n∂z
(l ) i=
⎧
⎨
⎩
$
z
(teacher)n i−
z
(l ) i%
l
is output layer
&
k ∂En ∂z(l +1) kz
(l +1) k$
1 − z
(l +1)k%
w
(l ) kiotherwise
まとめると、
とってもきれいな式が導出できる
「デルタ」という名前がついている
質問は?
何層あってもこの方法は使える→多層パーセプトロン
コード
関数updateByBackPropagationを実装しよう
typedef double Neuron, Delta, Weight;
typedef struct { Neuron *z; Delta *delta; int n; } Layer;
void createLayer ( Network *network, const int layer_id, const int number_of_neurons )
{ :
layer -> delta = ( Delta * ) malloc ( ( number_of_neurons + bias ) * sizeof ( Delta ) );
for ( int i = 0; i < layer -> n; i++) { layer -> delta [ i ] = 0.; } if ( bias ) { layer -> delta [ layer -> n ] = 0.; } // バイアス初期化 }
1. デルタの定義を追加
void deleteLayer ( Network *network, const int layer_id ) {
:
free ( layer -> delta ); }
2. デルタの確保と初期化
コード
あとはperceptron.cの
updateByPerceptronRuleを
updateByBackPropagationに
変更する
レポート課題 2
色々試すこと
・中間層のニューロン数を変える (中間層2個でいいか?)
・収束までのエポック数を調べる
updateByBackPropagationを実装して上記を試せ
コードの雛形はdl/code/bp.cにある
手書き文字認識
XORはあまりにもtoy problemなので、もう少しましな何か
手書き文字認識
MNISTデータベース
・手書き文字画像のデータベース+ラベル付き
・画像は28x28ピクセル256階調グレースケール
・ラベルは 0 から 9 の数字 (そりゃそうだ)
詳しくは付録B
・60000個のトレーニングデータ
・10000個のテストデータ
必ずデータを分ける
データの読み書き
code/mnist.{c, h}を用意した。
code/mnist_test.cをまず試す。
code/mnist/以下にデータがあるのでフォルダ毎コピー。
make mnist_test
mkdir png
./mnist_test
./pngの下にpngファイルが作成される。
手書き文字のテスト
レポート課題 3
誤差逆伝播法を実装した3層のパーセプトロンで、
手書き文字の学習と認識を行え。
やり方はテキストを参照。
以下では多少補足を。
バッチ学習の問題点
E =
!
n
1
2
"
"z
(teacher)
n
− z
(output)
n
"
"
2
誤差 (再掲)
全トレーニングデータに関する和
XORのときはn=4だった。
MNISTではn=60000。←計算が終わらない
学習が局所解に落ちる可能性が高い。
勾配降下法の性質
「最も良い解」はすごく少ない。
「そこそこ良い解」はたくさんある。
勾配降下法は初期値に依存するので、「最も良い解」
の近くから始めない限り「そこそこ良い解」に捕まる。
確率的勾配法とミニバッチ
確率的に勾配を降りることで、「そこそこ良い解」から
脱出できるようにする。
× 全トレーニングデータを使う
◎
トレーニングデータからいくつかをランダムに選んで使う
(バッチ)
(ミニバッチ)
エポック毎にミニバッチを作り直す
ミニバッチのサイズをどうするかに依存
極端な例: サイズ 1 (1データ毎に重みを更新)
ミニバッチへの布石がここに…
while ( error > Epsilon ) { error = 0.;
initializeDW ( &network );
for ( int j = 0; j < number_of_training_data; j++ ) {
//int k = ( int ) ( number_of_training_data * sfmt_genrand_real2 ( &rng ) );
int k = j;
setInput ( &network, x [ k ] ); forwardPropagation ( &network, sigmoid );
error += updateByBackPropagation ( &network, z [ k ] ); }
:
while ( error > Epsilon ) { error = 0.;
initializeDW ( &network );
for ( int j = 0; j < MINI_BATCH_SIZE; j++ ) {
int k = ( int ) ( number_of_training_data * sfmt_genrand_real2 ( &rng ) );
setInput ( &network, x [ k ] ); forwardPropagation ( &network, sigmoid );
error += updateByBackPropagation ( &network, z [ k ] ); }