42
1 2018 年度 MI/CS 実験第二「作ってわかる深層学習」 (1) 自己符号化器 山﨑 匡 * 2018 9 5 概要 本実験では、現在世の中を席巻している深層学習の理論を学び、C 言語で実際に実装します。最初の ニューラルネットであるパーセプトロンの実装から始めて誤差逆伝播へと進み、深層学習の一つである自己 符号化器を実装します。手書き文字の学習を実際に行い、認識精度を検討します。実際に自分でプログラミ ングすることで、理解を深めることを目的としています。 目次 1 はじめに 3 1.1 実験の内容 ............................................. 3 1.2 参考文献 .............................................. 3 1.3 実験の進め方 ............................................ 4 1.4 成績のつけ方 ............................................ 4 1.5 メーリングリスト ......................................... 5 1.6 この実験を始めるにあたって ................................... 5 1.7 では… ............................................... 5 2 初めてのニューラルネット 6 2.1 ニューラルネットとは何か .................................... 6 2.2 ニューラルネットのプログラム .................................. 7 2.3 順伝播 (Forward Propagation) .................................. 10 2.4 試してみよう ............................................ 11 2.5 [課題 1] ネットワークの状態を出力する .............................. 12 3 パーセプトロン 13 3.1 パーセプトロンとは何か ...................................... 13 3.2 勾配降下法による学習 ....................................... 13 3.3 試してみよう ............................................ 17 3.4 [ボーナス課題 1] 単純パーセプトロン ............................... 19 4 多層パーセプトロン 20 * 情報数理工学プログラム 准教授。連絡先: [email protected], 研究室: http://numericalbrain.org/

2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

  • Upload
    ngonga

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

1

2018年度 MI/CS実験第二「作ってわかる深層学習」(1) 自己符号化器

山﨑 匡 ∗

2018年 9月 5日

概要

本実験では、現在世の中を席巻している深層学習の理論を学び、C 言語で実際に実装します。最初のニューラルネットであるパーセプトロンの実装から始めて誤差逆伝播へと進み、深層学習の一つである自己符号化器を実装します。手書き文字の学習を実際に行い、認識精度を検討します。実際に自分でプログラミングすることで、理解を深めることを目的としています。

目次

1 はじめに 3

1.1 実験の内容 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.2 参考文献 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.3 実験の進め方 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.4 成績のつけ方 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.5 メーリングリスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.6 この実験を始めるにあたって . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.7 では… . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 初めてのニューラルネット 6

2.1 ニューラルネットとは何か . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2.2 ニューラルネットのプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.3 順伝播 (Forward Propagation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.4 試してみよう . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.5 [課題 1] ネットワークの状態を出力する . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 パーセプトロン 13

3.1 パーセプトロンとは何か . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3.2 勾配降下法による学習 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3.3 試してみよう . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

3.4 [ボーナス課題 1] 単純パーセプトロン . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4 多層パーセプトロン 20

∗ 情報数理工学プログラム 准教授。連絡先: [email protected], 研究室: http://numericalbrain.org/

Page 2: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 2

4.1 中間層にニューロンは何個必要か? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.2 誤差逆伝播 (Back Propagation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.3 [課題 2] バックプロパゲーションの実装 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

5 手書き文字の学習と認識 24

5.1 MNISTデータベース . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5.2 バッチ学習と確率的勾配降下法 (Stochastic Gradient Descent) . . . . . . . . . . . . . . . . . 25

5.3 [課題 3] 多層パーセプトロンによる手書き文字認識 . . . . . . . . . . . . . . . . . . . . . . . . 25

5.4 [ボーナス課題 2] 中間層での情報表現 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

5.5 [ボーナス課題 3] Fashion-MNIST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

6 自己符号化器 29

6.1 自己符号化器とは何か . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

6.2 汎化と過学習 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

6.3 深層化と勾配消失問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

6.4 積層自己符号化器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

6.5 積層自己符号化器のコーディング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

6.6 事前学習 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

6.7 [課題 4] 積層自己符号化器による手書き文字認識 . . . . . . . . . . . . . . . . . . . . . . . . . 34

7 [ボーナス課題 4] 自由演技 35

7.1 活性化関数の変更 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.2 他のデータへの適用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.3 トレーニングデータの前処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.4 ドロップアウト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.5 勾配降下法の改良 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

7.6 計算の並列化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

7.7 それ以外の何でも . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

8 おわりに 37

付録 A skel.cのソースコード 38

付録 B MNISTデータセットの使い方 41

Page 3: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 3

1 はじめに世はまさに人工知能ブーム*1。人工知能が話題にならない日はありません。現在のブームを支えているのは深層学習と呼ばれる機械学習技術で、現在の人工知能イコール深層学習と言っても過言ではありません。深層学習に関する論文は日々量産され、深層学習を用いた様々な応用例がメディアを賑わしています。さて、深層学習というのは実際のところどういう技術でしょうか。これが脳の神経回路を模したニューラルネットの仲間で、非常に多くの層を重ねたものである、ということは聞いたことがあるかもしれません。中には深層学習のライブラリ*2を使ってちょっとしたプログラムを書いたことがある人もいるでしょう。でも、その背景にある数理や実装を知ってる人はどれくらいいるでしょう。いや、いいんですよ別に。もし我々がメディア/経営社会に所属していたなら、深層学習の実装なんかできなくても許されるというか、ライブラリさえ使えれば卒研くらいはなんとかなるでしょう*3。けど我々はMI/CSに所属していて、計算機の使い方を勉強しているわけです。つまり、我々はライブラリを使う側ではなく作って提供する側の人間です。よって、ライブラリを作るために必要な知識と技術、すなわち教科書レベルの数理を理解し、かつそれを実装に落とし込む能力が必要です。

1.1 実験の内容

そこで本実験では、実際に深層学習のプログラムを一から作成します。使用言語は正々堂々と C 言語です*4。一般的に深層学習は自己符号化器*5と畳み込みネットワークに大別されますが、本実験では前者を扱います。自己符号化器は教師信号が不要なので色々自動的に学習できるし、アイデアも面白いからです。一方、単に深層学習器を実装するだけでなく、そこに至った歴史についても学びます。具体的には最初のニューラルネットであるパーセプトロンと、それを多層にした際の学習則である誤差逆伝播を実装します。それぞれの実装後は、手書き文字の学習を実際に試して、認識精度がどの程度かを検討します。大まかなスケジュールは以下の通りです。

1. 第 1回 (10/03 水): ガイダンス、ニューラルネット概論と肩慣らし2. 第 2回 (10/08 月): 誤差逆伝播のミニ講義と実装3. 第 3回 (10/10 水): 手書き文字認識のテスト4. 第 4回 (10/15 月): 自己符号化器のミニ講義と実装5. 第 5回 (10/17 水): 実装6. 第 6回 (10/22 月): 実装7. 第 7回 (10/24 水): 実装

1.2 参考文献

本実験ではいくつかの文献を参考にしています。

1. 岡谷貴之「深層学習」講談社 機械学習プロフェッショナルシリーズ 2015年 4月

*1 ちなみに 2000年台は人工知能というのは口に出すのも恥ずかしいキーワードでした。隔世の感がありますね。*2 我が国では PFNが開発している Chainerが有名ですね。*3 別に disっているわけではありません。念のため。*4 まあでも C99。*5 オートエンコーダ。

Page 4: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 4

2. Ian Goodfellow, Yoshua Bengio, Aaron Courville. Deep Learning. MIT Press. 2016/11.

前者は日本語で書かれた深層学習の最初の教科書です。名著。後者は 2016年末時点の決定版。日本語版も出版されました。手を動かす系の入門書では、

1. 斎藤康毅「ゼロから作る Deep Learning」 オライリー・ジャパン 2016年 9月2. 瀧雅人「これならわかる深層学習入門」講談社 機械学習スタートアップシリーズ 2017年 10月

が良さそうです。前者は Python だから C 言語でごりごり書くよりも敷居が低い。後者は岡谷先生の本を 2

倍丁寧にした本。内容はほぼ同じなので、岡谷先生の本でまず勉強して、わからないところをこの本で補うと良い。原理はともかくとりあえず C言語で動くコードと説明が欲しい場合は

1. 小高知宏「機械学習と深層学習 C言語によるシミュレーション」オーム社 2016年 5月

がいいかもしれないけど、この資料があれば不要。

1.3 実験の進め方

実験は、一人でやらずに他の人と相談しながら進めて下さい。一人でできるほど甘くないです。分からないことがあれば、実験中は TA か私をつかまえて質問してください。それ以外の時間は後述のメーリングリストに投げるか、オフィスアワー*6に訪ねてきて下さい。ただし、レポートにまとめるときは必ず一人で、自分

の言葉でまとめて下さい。コピペは断固として認めません。もしコピペが発覚した場合、あなたの人生を狂わせる出来事が起こるでしょう。あらかじめ警告しておきます*7。

1.4 成績のつけ方

成績は出席とレポートでつけます。実験ですので出席を重視します。必ず出席してください。病気等でやむを得ない場合は事前に連絡してくれれば考慮します。無断欠席は認めません。レポート課題と大まかな成績の関係は

• 誤差逆伝播まで → 可• 手書き文字認識まで → 良• 自己符号化器まで → 優• ボーナス課題もやる → 秀 (ただし出来映えによる)

です。提出日時と場所は追って指示します。

*6 火曜日を除く平日 12:00–13:00。西 4 号館 6 階リフレッシュルーム。要するに昼ご飯食べてるからそのときに。なんなら昼ご飯持参でも。歓迎します。

*7 これまでに何人もの人生を狂わせてきました。

Page 5: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 5

1.5 メーリングリスト

全ての連絡はメーリングリストで行います。講義ページにて登録できるので、かならず連絡のつくメールアドレスを登録してください。講義ページの URLは

http://numericalbrain.org/lectures/deep-learning/

です。

1.6 この実験を始めるにあたって

この実験は昨年度から始めました。私は 2013年から「ロボットシミュレーション」という課題で実験第二に参戦していましたが、2015年初頭には既に深層学習の実験をする構想がありました。ちょうど岡谷先生の「深層学習」が 2015年の 4月に出版され、当時日本語で書かれた唯一の深層学習の教科書であり内容は難しめだったので、これを 3年生向けに咀嚼して構成すれば良いだろうと考えたからです。あれから幾星霜、テニュアトラック審査だの NEDOプロだのポスト「京」だの改組だのと様々なイベントがあり、毎年準備しようと思いつつもついつい忙しさにかまけて放置してしまい、実際に実験を始めるまでには至りませんでした。そうこうしている間に深層学習の世界はとてつもないスピードで動いていて、今や深層学習の教科書は有象無象を含めて星の数ほど存在しています。Goodfellow の決定版「Deep Learning」も出版されましたし、日本語でも良い入門書が何冊も出版されています。ウェブ上の資料も玉石混合です。なので、いまさらやるの? って感じが満載ですが、でもまあMI/CSには深層学習をやる授業が他にないので、これだけ盛り上がってるのだから 1 つくらいそういう授業があってもいいでしょう。Later is better

than never。

1.7 では…

よろしくお願い致します。

Page 6: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 6

2 初めてのニューラルネット2.1 ニューラルネットとは何か

こういうネットワークである (図 1)。漠然と知ってるものとして進めるよ。

図 1 ニューラルネットの概略図。

ネットワークはいくつかの層の階層である。各層はいくつかのニューロンを含み、連続する層間のニューロン同士は結合していて、各結合には重みが割り当てられている。以上。一番下の層を入力層、一番上の層を出力層、途中の層を中間層 (もしくは隠れ層) と呼ぶ。古典的な多層パーセプトロンでは中間層は 1層しかない。深層にすると中間層の数が増える。各ニューロンは、その前の層のニューロンから入力を受け取り、その次の層のニューロンに出力を渡す。層

lのニューロン iの出力を z(l)i とすると、

z(l)i = f

∑j

w(l−1)ij z

(l−1)j

(1)

である。ここで、w(l−1)ij は層 l − 1のニューロン j から層 l のニューロン iへの結合の重み、f は活性化関数

と呼ばれるもので、例えばシグモイド関数

f(x) =1

1 + e−x(2)

や ReLU (Rectified Linear Unit)

f(x) =

{x x > 0

0 x ≤ 0(3)

が用いられる (図 2)。最後に、wの値は実数、z の値は 0以上の実数である。

Page 7: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 7

図 2 シグモイド関数 (実線) と ReLU (点線) の形状。

2.2 ニューラルネットのプログラム

以上のことをストレートにプログラムする。雛形は skel.cにある。開いてコードを眺めながら以下を読むと良い。付録 Aにも掲載してある。まずデータ構造から。ネットワークは層と結合からなるので、

13 typedef struct { Layer *layer; Connection *connection; sfmt_t rng; int n; } Network;

となる。ここで nはネットワークに含まれる層の総数、rngは乱数のシードである。層はニューロンからなるので、

12 typedef struct { Neuron *z; int n; } Layer;

となる。ここで nは層に含まれるニューロンの総数である。結合は重みの情報を持つので、

11 typedef struct { Weight *w; int n_pre; int n_post; } Connection;

となる。ここで n preは前側の層のニューロン数、n postは後側の層のニューロン数である。結合の重みとニューロンの活動は実数なので、

10 typedef double Neuron, Weight;

となる。乱数はMersenne Twisterを使うので、

7 extern void sfmt_init_gen_rand(sfmt_t * sfmt, uint32_t seed);

8 extern double sfmt_genrand_real2(sfmt_t * sfmt);

としておく。次は関数。まずネットワークを作る関数 createNetworkを作成する。インタフェースは

void createNetwork ( Network *network, const int number_of_layers, const sfmt_t rng );

とする。ここで number of layersはネットワークに含まれる層の総数である。この関数は

18 void createNetwork ( Network *network, const int number_of_layers, const sfmt_t rng )

19 {

20 network -> layer = ( Layer * ) malloc ( number_of_layers * sizeof ( Layer ) );

Page 8: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 8

21 network -> connection = ( Connection * ) malloc ( number_of_layers * sizeof ( Connection ) );

22 network -> n = number_of_layers;

23 network -> rng = rng;

24 }

となる。必要な数の層と結合を作成し、層の数と乱数のシードを格納する。あわせて作成したネットワークを削除する関数 deleteNetworkも作成する。インタフェースは

void deleteNetwork ( Network *network );

とする。こちらは簡単で、

26 void deleteNetwork ( Network *network )

27 {

28 free ( network -> layer );

29 free ( network -> connection );

30 }

となる。同様に、層を作る関数 createLayerを作成する。インタフェースは

void createLayer ( Network *network, const int layer_id, const int number_of_neurons );

とする。ここで、layer idは作成する層の番号、number of neuronsはその層に含まれるニューロンの数である。こんな感じになる。

void createLayer ( Network *network, const int layer_id, const int number_of_neurons )

{

Layer *layer = &network -> layer [ layer_id ];

layer -> n = number_of_neurons;

layer -> z = ( Neuron * ) malloc ( number_of_neurons * sizeof ( Neuron ) );

for ( int i = 0; i < layer -> n; i++) { layer -> z [ i ] = 0.; }

}

同様に層を削除する関数 deleteLayerは、

void deleteLayer ( Network *network, const int layer_id );

というインタフェースで

46 void deleteLayer ( Network *network, const int layer_id )

47 {

48 Layer *layer = &network -> layer [ layer_id ];

49 free ( layer -> z );

50 }

という感じ。· · · という感じなのだが、出力層を除く全ての層に、常に +1を出力するバイアスと呼ばれるニューロンを

1個ずつ用意する。よって createLayerは実際には

32 void createLayer ( Network *network, const int layer_id, const int number_of_neurons )

33 {

34 Layer *layer = &network -> layer [ layer_id ];

35

36 layer -> n = number_of_neurons;

37

38 int bias = ( layer_id < network -> n - 1 ) ? 1 : 0; // 出力層以外はバイアスを用意39

Page 9: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 9

40 layer -> z = ( Neuron * ) malloc ( ( number_of_neurons + bias ) * sizeof ( Neuron ) );

41 for ( int i = 0; i < layer -> n; i++) { layer -> z [ i ] = 0.; }

42

43 if ( bias ) { layer -> z [ layer -> n ] = +1.; } // バイアス初期化44 }

ここで、出力層の layer idは n− 1であることに注意する。また、layer -> nにはバイアスニューロンの個数を含めていないことにも注意する。さらに、結合を作る関数 createConnectionを

void createConnection ( Network *network, const int layer_id, double ( *func ) ( Network *, const int

, const int ) );

というインタフェースで作成する。ここで、layer idは結合の前側の層の番号、*funcは重みを割り当てる関数へのポインタである。結合重みは全部 +1だったり一様乱数だったり {−1,+1}の 2値の乱数だったり、様々な割り当て方が考えられるので、関数の外から与えられるようにする。中身は

52 void createConnection ( Network *network, const int layer_id, double ( *func ) ( Network *, const int

, const int ) )

53 {

54 Connection *connection = &network -> connection [ layer_id ];

55

56 const int n_pre = network -> layer [ layer_id ] . n + 1; // +1はバイアスの分57 const int n_post = ( layer_id == network -> n - 1 ) ? 1 : network -> layer [ layer_id + 1 ] . n;

58

59 connection -> w = ( Weight * ) malloc ( n_pre * n_post * sizeof ( Weight ) );

60

61 for ( int i = 0; i < n_post; i++ ) {

62 for ( int j = 0; j < n_pre; j++ ) {

63 connection -> w [ j + n_pre * i ] = func ( network, i, j );

64 }

65 }

66 connection -> n_pre = n_pre;

67 connection -> n_post = n_post;

68 }

となる。ここで、n preは結合の前層のニューロン数、n postは後層のニューロン数である。出力層は結合を持たないので、n postは 1となる。作成した結合の概要は図 3のようになる。最後に、作成した結合を削除する関数 deleteConnectionは

void deleteConnection ( Network *network, const int layer_id );

というインタフェースで、

70 void deleteConnection ( Network *network, const int layer_id )

71 {

72 Connection *connection = &network -> connection [ layer_id ];

73 free ( connection -> w );

74 }

という内容になる。これで、あとは

double all_to_all ( Network *n, const int i, const int j ) { return 1.; }

int main ( void )

{

sfmt_t rng;

Page 10: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 10

図 3 結合の概要図。結合の番号 (l − 1)は下の層のそれ (l − 1)と一致させる。下の層のニューロン数をn pre、上の層を n postとする。ただし n preにはバイアスニューロン (+1で示した丸) の個数も含む。ニューロン j からニューロン iへの結合の添字は j + n pre * iとなる。

sfmt_init_gen_rand ( &rng, getpid ( ) );

// 作るNetwork network;

createNetwork ( &network, 3, rng );

createLayer ( &network, 0, 2 );

createLayer ( &network, 1, 2 );

createLayer ( &network, 2, 1 );

createConnection ( &network, 0, all_to_all );

createConnection ( &network, 1, all_to_all );

// 消すdeleteConnection ( &network, 1 );

deleteConnection ( &network, 0 );

deleteLayer ( &network, 2 );

deleteLayer ( &network, 1 );

deleteLayer ( &network, 0 );

deleteNetwork ( &network );

return 0;

}

というようなコードを書けば、3層のネットワークで、入力層 (層 0) の 2個、中間層 (層 1) に 2個、出力層(層 2) に 3個のニューロンをそれぞれ割り当て、ニューロン間は重み +1で全結合とするものが作成できる。メモリリークすると気持ち悪いので、作ったら必ず最後に消すこと。

2.3 順伝播 (Forward Propagation)

ネットワークを作ったら、次はネットワークに入力を与える関数を作る。インタフェースは

void setInput ( Network *network, Neuron x [ ] );

とする。ここで、xが与える入力のベクトルである。xの大きさは入力層のニューロン数と等しいと常に仮定する。関数の中身はストレートに

76 void setInput ( Network *network, Neuron x [ ] )

77 {

78 Layer *input_layer = &network -> layer [ 0 ];

79 for ( int i = 0; i < input_layer -> n; i++ ) {

80 input_layer -> z [ i ] = x [ i ];

81 }

82 }

Page 11: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 11

となる。例えば、入力層のニューロンが 2個で、入力として x = (0, 1)を与えたければ、

Neuron x [] = { 0., 1. };

setInput ( &network, x );

とすればよい。入力を与えたら、入力層から出力層に向かって順番にニューロンの活動を計算していく。この過程を順伝播

(Forward Propagation)と言う。関数 forwardPropagation を作成しよう。

void forwardPropagation ( Network *network, double ( *activation ) ( double ) );

ここで、*activationは活性化関数 f(x)へのポインタである。結合関数同様、自由に変更できるように外部から与えるようにする。関数の中身は

84 void forwardPropagation ( Network *network, double ( *activation ) ( double ) )

85 {

86 for ( int i = 0; i < network -> n - 1; i++ ) {

87 Layer *l_pre = &network -> layer [ i ];

88 Layer *l_post = &network -> layer [ i + 1 ];

89 Connection *c = &network -> connection [ i ];

90

91 for ( int j = 0; j < c -> n_post; j++ ) {

92 Neuron u = 0.;

93 for ( int k = 0; k < c -> n_pre; k++ ) {

94 u += ( c -> w [ k + c -> n_pre * j ] ) * ( l_pre -> z [ k ] );

95 }

96 l_post -> z [ j ] = activation ( u );

97 }

98 }

99 }

となる。例えば

double sigmoid ( double x ) { return 1. / ( 1. + exp ( - x ) ); }

:

forwardPropagation ( &network, sigmoid );

とすれば、活性化関数としてシグモイド関数を用い、式 (1)を次々と計算して、層 i (l pre) のニューロンから層 i+ 1 (l post) のニューロンへと活動を伝播させていく。

2.4 試してみよう

skel.cをコンパイルして実行しよう。

[ya000836@purple99 ~/dl]$ gcc -O3 -std=c99 -Wall -I SFMT-src-1.5.1 -D SFMT_MEXP=19937 -o skel skel.c

SFMT-src-1.5.1/SFMT.c

ここで、-O3は一番アグレッシブな最適化をかけるオプション、-std=c99は C99でコンパイルするためのオプション、-Wall は全ての警告を表示するためのオプション、-I SFMT-src-1.5.1 -D SFMT MEXP=19937

はMersenne Twisterのためのオプション、SFMT.cはMersenne Twisterの関数を含むファイルである。コンパイルが終了するとバイナリ skelが生成されるので、実行する。

[ya000836@purple99 ~/dl]$ ./skel

[ya000836@purple99 ~/dl]$

入力として x = (0, 1)を与えている。何も表示されないが、実際何も表示していないので、これは正しい動作である。

Page 12: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 12

2.5 [課題 1] ネットワークの状態を出力する

出力が何も無いと動作確認もデバッグもできないので、ネットワークの状態を出力する関数 dumpを作成せよ。インタフェースは

void dump ( Network * );

とし、少なくとも以下の情報を出力する。フォーマットは自由で良い。

• 層の総数• 各層のニューロン数• 各層間の結合重み• 各層のニューロンの活動

例えば skel.cの場合、中間層のニューロンの活動は (0.9, 0.9)、出力層のニューロンの活動は 0.9となるはずである。どういう関数を作成してどういう出力が得られるのか、そして正しく動作している確認ができたかどうかをレポートに記載せよ。

Page 13: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 13

3 パーセプトロン3.1 パーセプトロンとは何か

世界で最初のニューラルネットは、心理学者の Rosenblattが 1958年*8に発表したパーセプトロン (Per-

ceptron) だと言われている (Rosenblatt M. The Perceptron: A probabilistic model for information

storage and organization in the brain. Psychological Review 65:386–408, 1958)。パーセプトロンは入力層・中間層・出力層の 3層からなるネットワークであり、一般に中間層のニューロン数が大きく、入力層と中間層の間の結合は疎で重みはランダムである (図 4)。パーセプトロンは教師付学習機械であり、入力ベクトル

図 4 パーセプトロンの概略図。

xと教師ベクトル zのペアの集合:

D = {(x(1), z(1)), · · · , (x(n), z(n))} (4)

をもらって学習を行い、学習後は任意の入力 x(i) に対してペアとなるベクトル z(i) を出力する。ここで、学習とは、結合の重みを変化させることであり、特にパーセプトロンの場合は中間層と出力層の重みのみを変化させる。パーセプトロンの発表から第 1次ニューロブームが始まった*9。

3.2 勾配降下法による学習

例として、2入力 1出力の関数 XORをパーセプトロンに学習させてみよう。z = XOR(x1, x0)は以下の入出力関係を持つのは覚えているよね。

x1 x0 z

0 0 0

0 1 1

1 0 1

1 1 0

中間層のニューロンは 128 個もあればよくて、入力層から中間層への結合は、確率 1/2 で存在させ重みは[−1,+1)の一様分布、中間層から出力層への結合の重みは [−1,+1)の一様分布としよう。ネットワークの作

*8 なので、ニューラルネットの研究というのは実はかなり古くから存在する。*9 そしてミンスキーとパパートが終わらせた (Minsky M, Papert S. Perceptrons: An Introduction to Computational

Geometry. MIT Press, 1972)。

Page 14: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 14

成は

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.;

}

である。学習部分のルーチンは以下のようになる。

1 Neuron x [ 4 ][ 2 ] = { { 0., 0. }, { 0., 1. }, { 1., 0. }, { 1., 1. } };

2 Neuron z [ 4 ][ 1 ] = { { 0. } , { 1. } , { 1. } , { 0.} };

3 const int number_of_training_data = 4;

4

5 // Training

6 double error = 1.0; // arbitrary large number

7 const double Epsilon = 0.001; // tolerant error rate

8 int i = 0;

9 while ( error > Epsilon ) {

10 error = 0.;

11 initializeDW ( &network );

12 for ( int j = 0; j < number_of_training_data; j++ ) {

13 //int k = ( int ) ( number_of_training_data * sfmt_genrand_real2 ( &rng ) );

14 int k = j;

15 setInput ( &network, x [ k ] );

16 forwardPropagation ( &network, sigmoid );

17 error += updateByPerceptronRule ( &network, z [ k ], diff_sigmoid ); // 活性化関数の微分が必要18 }

19 updateW ( &network );

20 printf ( "%d␣%f\n", i, error );

21 i++;

22 }

23 fprintf ( stderr, "#␣of␣epochs␣=␣%d\n", i );

1,2行目は入力ベクトル xと教師ベクトル zの定義である。3行目はデータの個数の定義。9 行目からが本番。正解とネットワークの出力との誤差が定数 Epsilon 以下になるまで重みを更新する。この繰り返しの回数をエポックと呼ぶ。各エポックでは、入力を順番にセットし (15行目)、順伝播させ (16行目)、重みを更新する (17行目)。ここで、重みに更新には活性化関数の微分が必要となる (後述)。これを 4種類の入力全てについて繰り返す (12行目)。これをバッチという。バッチ内で誤差を累積して (17行目)、バッチが終わる度に誤差の値を表示する (20行目)。ここで注意: 実際に重みを更新するのは各バッチの最後に 1回である。バッチ内では重みの更新値を累積する。そのためにバッチの最初に変数を初期化し (initializeDW)、バッチの最後で重みを更新する (updateW)。このために、まず更新値を累積するための変数が必要なので、Connectionの定義を

typedef struct { Weight *w; Weight *dw; int n_pre; int n_post; } Connection;

Page 15: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 15

と変更する。関数 createConnection、deleteConnectionも1 void createConnection ( Network *network, const int layer_id, double ( *func ) ( Network *, const int

, const int ) )

2 {

3 :

4 connection -> dw = ( Weight * ) malloc ( n_pre * n_post * sizeof ( Weight ) );

5 for ( int i = 0; i < n_post; i++ ) {

6 for ( int j = 0; j < n_pre; j++ ) {

7 connection -> dw [ j + n_pre * i ] = 0.;

8 }

9 }

10 :

11 }

12

13 void deleteConnection ( Network *network, const int layer_id )

14 {

15 :

16 free ( connection -> dw );

17 }

と修正する。最後に関数 initializeDWと updateWを1 void initializeDW ( Network *network )

2 {

3 Connection *c = &network -> connection [ network -> n - 2 ];

4 for ( int i = 0; i < c -> n_post; i++ ) {

5 for ( int j = 0; j < c -> n_pre; j++ ) {

6 c -> dw [ j + c -> n_pre * i ] = 0.;

7 }

8 }

9 }

10

11 void updateW ( Network *network )

12 {

13 Connection *c = &network -> connection [ network -> n - 2 ];

14 for ( int i = 0; i < c -> n_post; i++ ) {

15 for ( int j = 0; j < c -> n_pre; j++ ) {

16 c -> w [ j + c -> n_pre * i ] += c -> dw [ j + c -> n_pre * i ];

17 }

18 }

19 }

として作成する。学習が終わったらテストを行う。1 Layer *output_layer = &network . layer [ network. n - 1 ];

2 const int n = output_layer -> n;

3 for ( int i = 0; i < number_of_training_data; i++ ) {

4 setInput ( &network, x [ i ] );

5 forwardPropagation ( &network, sigmoid );

6 for ( int j = 0; j < n; j++ ) {

7 fprintf ( stderr, "%f%s", output_layer -> z [ j ], ( j == n - 1 ) ? "\n" : "␣" );

8 }

9 }

個々の入力に対して順伝播させて出力層のニューロンの活動を計算し、それが正解とどの程度近いかを表示する。

いよいよここからが本番。関数 updateByPerceptronRuleを作成する。

Page 16: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 16

まず通常の 2乗誤差を考える。

E =∑n

1

2

∥∥∥z(teacher)n − z(output)n∥∥∥2 (5)

ここで、z(output)n はバッチ内の入力 xn に対する出力層のニューロンの出力ベクトル、z(teacher)n は教師ベクトルである。この 2 乗誤差を減少させるように、各エポックで中間層~出力層の結合重み w(hidden) =

(w0, · · · , wl−1)T(lは結合の要素数)を変化させる。つまり

dw(hidden)

dt= − ∂E

∂w(hidden)(6)

を計算する。左辺をエポックに関して離散化して移項すると、

w(hidden)(t+ 1) = w(hidden)(t)− η∂E

∂w(hidden)

= w(hidden)(t)− η∇Ew(hidden)

(7)

となる。ここで tはエポックの番号、η は適当な小さな実数で学習係数、∇Ew(hidden) は

∇Ew(hidden)def=

∂E∂w0

...∂E

∂wl−1

(8)

である (lは結合の要素数)。一方、バッチ内の 1サンプルあたりの誤差を En と書くと、E =

∑n

En より、式 (7)は

w(hidden)(t+ 1) = w(hidden)(t)− η∑n

∂En

∂w(hidden)

= w(hidden)(t)− η∑n

∇En,w(hidden)

(9)

と書ける。ここで、

∇En,w(hidden)def=

∂En

∂w0

...∂En

∂wl−1

(10)

である。よって、任意の結合 w(hidden)ij に関して、 ∂En

∂w(hidden)ij

が計算できれば良い。

まず、

∂En

∂w(hidden)ij

=∂En

∂z(output)ni

∂z(output)ni

∂w(hidden)ij

(11)

である。ここで、En =

1

2

∑i

(z(teacher)ni − z

(output)ni

)2(12)

より∂En

∂z(output)ni

= −(z(teacher)ni − z

(output)ni

)(13)

であり、かつ

z(output)ni = f

∑j

w(hidden)ij z

(hidden)nj

(14)

Page 17: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 17

より合成関数の微分を用いて

∂z(output)ni

∂w(hidden)ij

= f ′

∑j

w(hidden)ij z

(hidden)nj

z(hidden)nj (15)

である。特に f(x)がシグモイド関数の場合、

f ′(x) = f(x) (1− f(x)) (16)

なので (各自確認せよ)、∂z

(output)ni

∂w(hidden)ij

= z(output)ni

(1− z

(output)ni

)z(hidden)nj (17)

となる。まとめると、式 (11)は

∂En

∂whiddenij

= −(z(teacher)ni − z

(output)ni

)z(output)ni

(1− z

(output)ni

)z(hidden)nj (18)

となり、重みの更新式は、

w(hidden)ij (t+ 1) = w

(hidden)ij (t)− η

∑n

∂En

∂w(hidden)ij

= w(hidden)ij (t) + η

∑n

(z(teacher)ni − z

(output)ni

)z(output)ni

(1− z

(output)ni

)z(hidden)nj

(19)

となる。これで導出完了!あとはこれを使って実装を行う。1バッチ内の各入力に対して

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 ) * diff_activation ( o );

c -> dw [ j + c -> n_pre * i ] += Eta * d * ( hidden_layer -> z [ j ] );

}

}

を計算して、変数 c -> dwに差分 ∂En

∂w(hidden) を累積する。また、関数 diff activationは活性化関数の微分である。活性化関数がシグモイド関数の場合、

double diff_sigmoid ( double z ) { return z * ( 1. - z ); } // f’(x) = f(x) ( 1 - f(x) ) = z ( 1 - z

)

と定義しておき、関数 updateByPerceptronRuleへの引数とする。

3.3 試してみよう

コードは perceptron.cである。コンパイルして実行しよう。

[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

Page 18: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 18

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

と表示され、誤差は徐々に減少し、最終的な出力は 0.02, 0.98, 0.98, 0.02とほぼ 0, 1, 1, 0と等しくなっていることから、XORが正しく計算できていることがわかる。さらに誤差の減り方をプロットしてみると、

[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’

図 5のようなグラフが表示され、誤差は単調に減少していることがわかる。

図 5 誤差の減少の様子。横軸はエポック数、縦軸が誤差の値。

Page 19: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 19

中間層のニューロン数を変えて、収束までのエポック数がどう変わるかを調べよ。課題 2で使う値なので記録しておくこと。

3.4 [ボーナス課題 1] 単純パーセプトロン

単純パーセプトロンとは、中間層の無い、入力層と出力層の 2層からなるパーセプトロンである。入力層と出力層の間の結合をパーセプトロン学習によって更新する。単純パーセプトロンで XORの学習ができるだろうか。できるならば、そのときのネットワークの構造を図示し、なぜそのネットワークで XORが計算できるのかを示せ。できないならば、なぜできないのか、その理由を述べよ。ヒントとして、「線形分離」というキーワードでググると良い。

Page 20: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 20

4 多層パーセプトロン4.1 中間層にニューロンは何個必要か?

前章では中間層に 128 個ニューロンを用いたパーセプトロンで XOR を学習させてみた。また、中間層のニューロンを減らしていくと、学習できなくなることも試してわかったことと思う。

図 6 XORを計算する最小のパーセプトロンの例。

実際のところ、中間層にはニューロンはいくつあれば十分だろうか? 図 6 のパーセプトロンは中間層に 2

個のニューロンを持ち、XORを計算できる (確認せよ)。そしてこれ以上は減らせないため、最小のニューロン数は 2個である。このように、真に必要なニューロン数はずっと少ないにも関わらず、パーセプトロンでは高次元へのランダムな写像を必要とするために、ある程度の数のニューロンが必要になる。

4.2 誤差逆伝播 (Back Propagation)

この問題を解決するには、入力層から中間層へのランダムな写像も学習によって変更すれば良いと考えられる。その手法の代表例が誤差逆伝播 (Back Propagation)であり、第 2次ニューロブームの火付け役となった。誤差逆伝播の方法を説明する。層 l − 1から層 lへの結合 w

(l−1)ij を、

w(l−1)ij (t+ 1) = w

(l−1)ij (t)− η

∑n

∂En

∂w(l−1)ij

(20)

で更新するのはパーセプトロンと同様である。そして、

∂En

∂w(l−1)ij

=∂En

∂z(l)i

∂z(l)i

∂w(l−1)ij

(21)

Page 21: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 21

である。ここで、 ∂En

∂z(l)i

は、lが出力層の場合はパーセプトロンと同様に

∂En

∂z(l)i

= −(z(teacher)ni − z

(l)i

)(22)

である。それ以外の場合は、∂En

∂z(l)i

=∑k

∂En

∂z(l+1)k

∂z(l+1)k

∂z(l)i

(23)

である。なぜなら、層 l のニューロン iが結合によって層 l + 1のニューロン k に影響を与えるからである。出力層から入力層に向かって順番に計算するならば、 ∂En

∂z(l+1)k

は既に計算済みであり、また

z(l+1)k = f

(∑i

w(l)ki z

(l)i

)(24)

より、活性化関数がシグモイド関数だという仮定の下、

∂z(l+1)k

∂z(l)i

= f ′

(∑i

w(l)ki z

(l)i

)w

(l)ki

= z(l+1)k

(1− z

(l+1)k

)w

(l)ki

(25)

となる。最後に式 (21)に戻って、 ∂z(l)i

∂w(l−1)ij

もパーセプトロンと同様に

∂z(l)i

∂w(l−1)ij

= z(l)i

(1− z

(l)i

)z(l−1)j (26)

である。以上をまとめると、

w(l−1)ij (t+ 1) = w

(l−1)ij (t) + η

∑n

(∂En

∂z(l)i

)(z(l)i

(1− z

(l)i

)z(l−1)j

)(27)

∂En

∂z(l)i

=

(z(teacher)ni − z

(l)i

)lは出力層∑

k∂En

∂z(l+1)k

z(l+1)k

(1− z

(l+1)k

)w

(l)ki それ以外

(28)

となる。増分の符号を反転していることに注意すること。これで導出完了!ところで、式 (28)で定義される値をデルタと呼ぶ。誤差逆伝播はデルタの値を上から下に向かって連鎖するので、チェインルールとも呼ぶ。

ようやく、関数 updateByBackPropagationを作成する準備が整った。デルタの値を保持する必要があるので、データ構造を拡張しよう。まずデルタの定義が必要なので、

typedef double Neuron, Delta, Weight;

とする。それからデルタの値を保持するために、

typedef struct { Neuron *z; Delta *delta; int n; } Layer;

Page 22: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 22

として各層に値を持たせよう。初期化は関数 createLayerに

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.; } // バイアス初期化

を追加する。関数 deleteLayerで

free ( layer -> delta );

するのも忘れずに。

4.3 [課題 2] バックプロパゲーションの実装

バックプロパゲーションを実装し、XORの学習を試してその性能を評価せよ。評価軸としては、

1. 認識精度2. 収束までのエポック数

の 2つがある。それぞれを調べ、パーセプトロンの場合と比較検討せよ。コードの雛形は bp.cに用意しておいた。

ヒント:

こんなコードになるはず。

double updateByBackPropagation ( Network *network, Neuron z [ ], double ( *diff_activation ) ( double

) )

{

const double Eta = 0.1;

// 誤差の計算 (出力用途のみ)

double error = 0.;

{

Layer *l = &network -> layer [ network -> n - 1 ];

for ( int j = 0; j < l -> n; j++ ) {

error += 0.5 * ( ( l -> z [ j ] - z [ j ] ) * ( l -> z [ j ] - z [ j ] ) );

}

}

// 出力層のデルタの計算{

int i = network -> n - 1;

Layer *l = &network -> layer [ i ];

for ( int j = 0; j < l -> n; j++ ) {

Neuron o = l -> z [ j ];

l -> delta [ j ] = /* ここを埋める */

}

}

// 本体for ( int i = network -> n - 2; i >= 0; i-- ) { // 上の層から下の層に向かって繰り返すLayer *l = &network -> layer [ i ];

Connection *c = &network -> connection [ i ];

Layer *l_next = &network -> layer [ i + 1 ];

// 重みの更新の計算for ( int j = 0; j < c -> n_post; j++ ) {

for ( int k = 0; k < c -> n_pre; k++ ) {

Page 23: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 23

c -> dw [ k + c -> n_pre * j ] += /* ここを埋める */

}

}

// 一つ下の層のデルタの初期化for ( int k = 0; k < c -> n_pre; k++ ) { l -> delta [ k ] = 0.; }

// 一つ下の層のデルタの計算/*

ここを埋める*/

}

return error;

}

Page 24: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 24

5 手書き文字の学習と認識5.1 MNISTデータベース

MNISTデータベース*10は手書き数字のデータベースであり、ニューラルネットのベンチマークとして広く用いられている。ラベル付けされた 60,000個のトレーニングデータと 10,000個のテストデータからなる。1

つの画像データは 28× 28ピクセルの、0から 1までの値を取る 256階調のグレースケール画像であり、ラベルは 0から 9までの整数である。画像の例を図 7に示す。データフォーマットは当該データベースのウェブ

図 7 MNISTデータセットの一部を画像に変換したもの。左から 5, 0, 5, 9, 7である。

サイトにあるので、データを読み書きするコードを作成してもらう課題もありだとは思うが、時間が足りないので私が作成しておいた。mnist.{h,c}がそれである。具体的なコードの書き方は付録 Aで説明している。mnist.c の main 関数は#if 0 されている。かわりに mnist test.c というラッパーが用意されているので、これをコンパイルして使う。

[ya000836@purple99 ~/dl]$ gcc -O3 -std=c99 -Wall -o mnist_test mnist_test.c mnist.c -lgd

[ya000836@purple99 ~/dl]$ mkdir png

[ya000836@purple99 ~/dl]$ ./mnist_test

5

0

5

9

7

:

[ya000836@purple99 ~/dl]$ ls ./png

0.png

1000.png

10000.png

11000.png

12000.png

:

データを 1つずつ読みながら画像に変換して./png以下に保存し、そのときのラベルを標準出力に表示している。そういうわけで、使い方は mnist.cの main local関数に書いてあるので読んで理解すること。

CEDで試すときの注意� �画像を生成するために libgdを使っているが、CEDにはインストールされていないので、~ya000836/usr以下にインストールした。よってコンパイル時にヘッダファイルとライブラリへのパスを通さないといけないし、実行時にもライブラリのパスを指定する必要がある。code/Makefileには mnist testをmakeするための記述があるので、Makefileごとコピーして、makemnist testすればコンパイルできる。また実行時のライブラリのパスを通すために、

[ya000836@purple99 ~/dl]$ source ~ya000836/dl/environment.csh (csh系の人)

[ya000836@purple99 ~/dl]$ source ~ya000836/dl/environment.sh (bash系の人)

すること。中で LD LIBRARY PATHを設定している。� �*10 http://yann.lecun.com/exdb/mnist/

Page 25: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 25

5.2 バッチ学習と確率的勾配降下法 (Stochastic Gradient Descent)

XORのときは入力パターンは 4種類しかなかったので、重みの更新は 4種類全てに関する誤差を使って計算できたが、MNIST ではトレーニングデータは 60000 個あるので、それを全部使うと計算時間が膨大になる*11。また全データを使って更新する場合、問題が難しくなると、重みが最適解に到達せず、局所解にトラップされてしまうことが多い。そのような問題を回避するために、全トレーニングデータをまとめてバッチで更新するのではなく、いくつかのデータをランダムにピックアップして、その小さなバッチ (ミニバッチと呼ぶ)で更新し、エポック毎に毎回違うミニバッチを用いる、という方法が考えられる。極端なケースは、ミニバッチとして 1つのデータだけを用いるものである。つまり毎回ランダムにデータを 1つ選択して逐次重みを更新する。ミニバッチを用いるとエポック毎に毎回トレーニングデータが変わるため、勾配降下法は確率的になる。よってミニバッチを用いた勾配降下法を確率的勾配降下法 (Stochastic Gradient Descent; SGD) と呼ぶ。

5.3 [課題 3] 多層パーセプトロンによる手書き文字認識

多層パーセプトロンを使って手書き文字認識をやらせてみよう。XOR版の多層パーセプトロンを以下のように拡張する。

1. 入力層のニューロン数は 28 × 28。この数字は mnist.hの中で MNIST IMAGE SIZEとして定義されている。

2. 出力層のニューロン数は 10。この数字は MNIST LABEL SIZEとして定義されている。3. 中間層のニューロン数はとりあえず 32。4. main関数の先頭で mnist initializeする。

double **training_image, **test_image;

int *training_label, *test_label;

mnist_initialize ( &training_image, &training_label, &test_image, &test_label );

5. main関数の最後で mnist finalizeする。

mnist_finalize ( training_image, training_label, test_image, test_label );

6. まず最初に全トレーニングデータを 1 つずつ流して学習するようにしよう。こんな風にして始めれば良い。

1 // Training

2 for ( int i = 0; i < MNIST_TRAINING_DATA_SIZE; i++ ) {

3 initializeDW ( &network );

4 double error = 0.;

5 for ( int j = 0; j < 1; j++ ) {

6 //int k = ( int ) ( MNIST_TRAINING_DATA_SIZE * sfmt_genrand_real2 ( &rng ) );

7 int k = i; // Use all training data one by one sequentially

8 setInput ( &network, training_image [ k ] );

9 forwardPropagation ( &network, sigmoid );

j,kの回りがごちゃごちゃしているが、これはミニバッチでやるための布石なので許して。

*11 しかし順伝播なので自明に並列化でき高速化できる。

Page 26: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 26

7. 教師信号について。提供されているラベルデータは 0から 9のスカラーなので、それを大きさ 10のベクトルに変換する。具体的にはラベルが n のとき、

z[k] =

{1 n = k

0 それ以外

とする。コード的には kが正解ラベルのとき、

1 double z [ MNIST_LABEL_SIZE ] = { 0. };

2 z [ training_label [ k ] ] = 1.;

3 error += updateByBackPropagation ( &network, z );

とすれば良い。8. 最後にテストデータを使って正解率を計算させる。

1 // Evaluatation

2 {

3 Layer *output_layer = &network . layer [ network . n - 1 ];

4 const int n = output_layer -> n;

5 int correct = 0;

6 for ( int i = 0; i < MNIST_TEST_DATA_SIZE; i++ ) {

7 setInput ( &network, test_image [ i ] );

8 forwardPropagation ( &network, sigmoid );

9 int maxj = 0;

10 double maxz = 0.;

11 for ( int j = 0; j < n; j++ ) {

12 if ( output_layer -> z [ j ] > maxz ) { maxz = output_layer -> z [ j ]; maxj = j; }

13 }

14 correct += ( maxj == test_label [ i ] );

15 }

16 fprintf ( stderr, "success_rate␣=␣%f\n", ( double ) correct / MNIST_TEST_DATA_SIZE );

17 }

テスト画像を入れて、出力層の最大出力のニューロンの番号とラベルを比較する。10000画像のうち何個正解したかをカウントし、正解率を出力する。

以上のセッティングで試すと正解率は 90%程度となる。これでも十分な気がするが、正解率をどこまであげられるか検討せよ。

• 中間層のニューロン数• ミニバッチの大きさ• エポック数• その他

色々試して最終的に正解率をいくつまで上げられたかを、試した手法とともにレポートに記載せよ。

5.4 [ボーナス課題 2] 中間層での情報表現

学習が成功した場合、中間層では入力の情報が適切に表現されていると考えられる。中間層のニューロンが何を表現しているか? は、入力層からの結合の重みをプロットすれば視覚的に観察できる。mnist.c のmnist generate png関数を参考にして視覚化してレポートに掲載し、かつ何が表現されているように見えるかを考察せよ。

Page 27: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 27

5.5 [ボーナス課題 3] Fashion-MNIST

素の MNISTは 3層のパーセプトロンでも 90%近い正解率となるので、深層化する御利益を感じにくい。もう少し難しいデータセットで評価する必要がある。

Fasion-MNIST (https://github.com/zalandoresearch/fashion-mnist)

は MNIST 互換のデータセットであり、ファイルを差し替えるだけで済むので簡単である。これを使って正解率を検討せよ。さらに、次の章で説明する深層自己符号化器との性能を比較せよ。ファイルは./fasihon-mnistの下に一式置いてある。

図 8 Fashion-MNISTのデータセットの一部 (ウェブサイトより)

ラベルの数字と実際の衣類の関係は以下の通り。

数字 衣類の種類0 T-shirt/top

1 Trouser

2 Pullover

3 Dress

4 Coat

5 Sandal

6 Shirt

7 Sneaker

8 Bag

9 Ankle boot

Page 28: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 28

ここまでで前半終了。お疲れ様でした。

後半に続く。

Page 29: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 29

6 自己符号化器6.1 自己符号化器とは何か

図 9のような 3層のネットワークを考える。入力層と出力層のニューロン数が等しく、入力されたものをそのまま出力するように重みを更新する。このように、入力そのものを出力するネットワークを自己符号化器(オートエンコーダ)と呼ぶ。

図 9 自己符号化器の例。

ポイントは、中間層のニューロン数を入力層のそれより少なくすることである。中間層のニューロン数が入力層のそれと等しいかそれより多ければ、自明に入力と出力を一致させられる。中間層のニューロン数を絞ることで、中間層で入力信号のうまい表現 (符号化)を学習する。

6.2 汎化と過学習

うまい表現とは何か? 例えば JPEGという画像フォーマットがある。自然画像をフーリエ変換すると、ごく少数の基底だけで十分な精度で元画像を再現できるため、そのような基底を残して残り全てを捨てることで、データサイズを大幅に小さくできる。ニューラルネットの働きも同様である。前章でやったのは、MNIST

データセット中のトレーニングデータから、手書き数字をうまく表現する基底を抽出し、中間層のニューロンの活動で表現する、ということである。一度そのようなうまい表現が獲得できれば、未知のデータセット (MNISTの場合はテストデータ)に対してもうまく働くことが期待できる。このような能力を汎化という。逆に、トレーニングデータに完璧にチューニングしてしまうと、テストデータに対する汎化性は減少してしまう。このような状況を過学習と呼ぶ*12。ニューラルネットのゴールは、この汎化能力をいかに持たせるか? である。

6.3 深層化と勾配消失問題

そこで登場するのが深層化、という考え方である。もし中間層に入力のうまい表現が得られたら、今度は中間層の活動に対する自己符号化器を構成し、そのうまい表現を獲得したらどうだろうか (図 10)。それを繰り返すことで、最終的に入力のとてもうまい表現が獲得される、と期待される。

*12 要するに学習しすぎた、ってことですね。

Page 30: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 30

図 10 深層化された自己符号化器の例。灰色のボックス内が中間層の活動に対する自己符号化器である。全体で 5層のネットワークになる。

ところがそうは問屋が卸さなくて、誤差逆伝播法そのものは層を 3層以上にしても破綻なく動作するので、原理的にはいくらでもネットワークを深く出来るが、実際には数値的に限界があり、学習が進まなくなる。デルタの計算にシグモイド関数 f(x) =

1

1 + e−xの微分 f ′(x) = f(x) (1− f(x))が出てきた。f(x)は 0か

ら 1の値を取り、f ′(x)は 0から 1/4までの値を取る。f ′(x)の値は層を伝播するので、一層伝播する度にデルタの値は必ず良くても 1/4 倍されてしまう。ということはネットワークが深くなればなるほど、デルタの値、すなわち誤差の勾配は指数的に 0に近づいてしまうので、入力層に近づくほど重みの更新が非常に困難になる。これを勾配消失問題と呼ぶ。深層学習は基本的にこの勾配消失問題との戦いである。今の深層学習では活性化関数に ReLU

f(x) =

{x x > 0

0 x ≤ 0(3)

を用いることが多いが、その理由は ReLUの微分は

f ′(x) =

{1 x > 0

0 x ≤ 0(29)

なので*13、勾配が消失しないからである。こういう工夫を積み重ねることで、現在の深層学習に辿り着く。

6.4 積層自己符号化器

自己符号化器を深層化すること自体は良いアイデアなので、後はそれをどうやって深層化するか、を考えよう。一つの方法は、一層ずつ順番に層を追加していくことである (図 11)。このような自己符号化器を積層自己符号化器 (スタックドオートエンコーダ)と呼ぶ。重みの更新を考えるのは、常に出力層側の 3層である。入力層~中間層の結合重みをWとし、中間層~出

*13 原点で不連続なので厳密には微分は定義できないが、とりあえず動く。

Page 31: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 31

図 11 積層自己符号化器の構成法。(A) 第 1ステップは通常の 3層の自己符号化器を構成する。ただし、中間層~出力層の結合重みは入力層~中間層のそれを転置したものとする。よってエポックの先頭で転置を取ってコピーし、誤差逆伝播をする。次のエポックでも同様にまず転置を取ってコピーし、それから誤差逆伝播をする。ここで、転置を取れないバイアスからの結合はそのまま利用することに注意する。(B)

第 1ステップで十分学習を行った後、第 2ステップはもう 1層追加し、第 1ステップでの中間層 (新入力層)の活動を出力する自己符号化器を構成する。第 1ステップでの入力層 (旧入力層)に入力をセットして活動を順伝播させ、新入力層の活動を教師信号だと思って誤差逆伝播をすればよい。以上の手順を繰り返すことで、ネットワークをいくらでも深くできる。

力層の重みを W̃と書く。結合重みは誤差逆伝播で更新するが、エポックの最初に毎回必ず

W̃def= WT (30)

とする。ここで T は転置の記号を表す。この条件を tied weightと呼ぶ。このように積層していけば、重みの更新は 3層間だけで行われるので、勾配はほぼ消失しない。

6.5 積層自己符号化器のコーディング

積層自己符号化器を構成するための手順をコードに落とし込んでいく。この実験では、1 層深くする度に新しくネットワークを作ることにしよう。既存のネットワークに対して

reallocしてもいいけど複雑になるので、馬鹿馬鹿しいかも知れないけど新しく作って必要な分をコピーする*14。以下の関数が必要になる。

1.void copyConnection ( Network *network_src, int layer_id_src, Network *network_dst, int

layer_id_dst );

ネットワーク network src の層 layer id src の結合重みを、ネットワーク network dst の層layer id dstの結合にコピーする。コピー元とコピー先の結合の本数は一致していないといけないことに注意すること。

*14 腕に覚えのある人は reallocするようにデータ構造から作り直してもいいし、私も Chainerのように連結リストを使いたくなっている。

Page 32: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 32

2.void copyConnectionWithTranspose ( Network *network_src, int layer_id_src, Network *

network_dst, int layer_id_dst );

copyConnectionと同じ動作をするが、コピーするときに転置を取る。転置を取るので、結合の向きが反転することに注意すること。またバイアスはコピーしなくて良いことにも注意すること *15。

3.double updateByBackPropagationPartial ( Network *network, Neuron z [ ], double ( *

diff_activation ) ( double ) );

誤差逆伝播をする関数 updateByBackPropagation と同じ動作をするが、逆伝播を先頭の 3 層で止める。

以上の関数が準備できれば、

int main ( void )

{

:

// network1を作成Network network1;

createNetwork ( &network1, 3, rng );

createLayer ( &network1, 0, MNIST_IMAGE_SIZE );

createLayer ( &network1, 1, 128 );

createLayer ( &network1, 2, MNIST_IMAGE_SIZE );

createConnection ( &network1, 0, uniform_random );

createConnection ( &network1, 1, uniform_random );

copyConnectionWithTranspose ( &network1, 0, &network1, 1 ); // tied weight

// Training

for ( int i = 0; i < MNIST_TRAINING_DATA_SIZE; i++ ) {

initializeDW ( &network1 );

double error = 0.;

// エポックの先頭で毎回転置を取ってコピーcopyConnectionWithTranspose ( &network1, 0, &network1, 1 );

for ( int j = 0; j < 1; j++ ) {

//int k = ( int ) ( MNIST_TRAINING_DATA_SIZE * sfmt_genrand_real2 ( &rng ) );

int k = i; // Use all training data one by one sequentially

setInput ( &network1, training_image [ k ] );

forwardPropagation ( &network1, sigmoid );

// 入力層の活動を教師信号としてupdateByBackPropagationPartial

error += updateByBackPropagationPartial ( &network1, network1 . layer [ 0 ] . z, diff_sigmoid

);

}

updateW ( &network1 );

}

// network2を作成Network network2;

createNetwork ( &network2, 4, rng );

createLayer ( &network2, 0, MNIST_IMAGE_SIZE );

createLayer ( &network2, 1, 128 );

createLayer ( &network2, 2, 64 );

createLayer ( &network2, 3, 128 );

createConnection ( &network2, 0, uniform_random );

createConnection ( &network2, 1, uniform_random );

createConnection ( &network2, 2, uniform_random );

*15 バイアスまで含めると転置を取れないので。バイアス以外のニューロンの結合のみ、転置取ってコピーしてください。

Page 33: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 33

copyConnection ( &network1, 0, &network2, 0 ); // network1の重みをコピーcopyConnectionWithTranspose ( &network2, 1, &network2, 2 ); // tied weight

// network1は不要なので消すdeleteConnection ( &network1, 1 );

deleteConnection ( &network1, 0 );

deleteLayer ( &network1, 2 );

deleteLayer ( &network1, 1 );

deleteLayer ( &network1, 0 );

deleteNetwork ( &network1 );

// Training

:

return 0;

}

というようなコードが書ける。

6.6 事前学習

最後に、自己符号化器を使って再度手書き文字認識にチャレンジしよう。やりかたは簡単で、積層自己符号化器の下半分 (エンコーダ側)だけを使って、その後ろに全結合させた出力層を追加する。そして誤差逆伝播で重みの更新を行う (図 12)。

図 12 事前学習済み認識器の構成法。(A) 積層自己符号化器の構成が終わったところ。(B) Aの出力層を削除し、かわりに全結合させた認識用の出力層を合体させる。例えばMNIST の場合はこの層に 10 個のニューロンが含まれる。このネットワークに対して改めて通常の誤差逆伝播を行い、全ての重みを更新して認識器を構成する。

既に述べたとおり、勾配法は最適解に近いところから出発すればうまくその解に収束するが、そうでないと別の局所解にトラップされてしまう。自己符号化器ではうまい表現を持つように事前に学習済みである。これを事前学習と呼び、このおかげで最適解に近いところから出発できると考えられる。積層自己符号化器の性能の良さの理由の一つだと考えられている。

Page 34: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 34

6.7 [課題 4] 積層自己符号化器による手書き文字認識

実際にやってみよ。何層にして、各層に何個ニューロンを用意して、活性化関数を何にして、ミニバッチのサイズをいくつにして、何エポックやって、最終的に誤差がどれくらいになって、深層化する前と比べてどれくらい良くなった (あるいは悪くなった)かを議論せよ*16。

以上で深層学習の最初の論文と言われている、

Hinton GE, Salakhutdinov RR. Reducing the Dimensionality of Data with Neural Networks. Sci-

ence 313:504-507 (2006).

に追いついた。

*16 やるとわかるけど、簡単に 95% くらいの正解率が出る。なので、MNIST は実はとても簡単なベンチマークだということがわかるし、MNISTで性能が出たっていう論文は眉に唾して読む必要があることもわかる。こういうのは手を動かしてみて初めてわかることだと思う。

Page 35: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 35

7 [ボーナス課題 4] 自由演技以上が、いわゆる深層学習の片方である、自己符号化器の核の部分である。後は周辺部分を散策しよう。精度を上げたり計算時間を短縮するために、様々な手法が提案・開発されている。好きなものを好きなだけ実際に試してみよ。

7.1 活性化関数の変更

深層化された自己符号化器で、勾配消失を防ぐために ReLUを使ってみよ。ReLUを使うと重みの更新式が変わるので、まず更新式を導出し、それを実装して試すこと。

7.2 他のデータへの適用

他にも様々なデータセットがあるので使ってみよ。例えば

1. CIFAR-10 (https://www.cs.toronto.edu/~kriz/cifar.html)

2. ImageNet (http://www.image-net.org/)

3. Caltech 101 (http://www.vision.caltech.edu/Image Datasets/Caltech101/)

等が有名である。ファイルフォーマット等は各ウェブサイトを参考にすること。

7.3 トレーニングデータの前処理

トレーニングデータにはタスクとは関係無い情報や、統計的な偏りが含まれている可能性がある。それを事前に乗り除いておくと学習がスムーズに進行すると考えられる。ミニバッチ内のサンプル xn に線形変換を行い、成分毎の平均と分散を揃える。xn の各成分*17の値を xn,i

と書き、µi

def=

1

N

∑n

xn,i (平均) (31)

σidef=

√1

N

∑n

(xn,i − µi)2

(標準偏差) (32)

を計算して、xn,i ←

xn,i − µi

σi(33)

とする。こうすると各成分の平均が 0、分散が 1となる。このような処理を正規化と呼ぶ。正規化の有無による収束までのエポック数や精度を議論せよ。またより高度な前処理として、白色化というものがある。余力があればこれについても調べて実装してみよ。

7.4 ドロップアウト

トレーニング中にニューロンを確率的に選別して学習する。中間層の各層と入力層のニューロンを確率 pでランダムに選出し、それ以外を無視して順伝播・逆伝播を行う。ニューロンの選出はミニバッチ毎に行う。選

*17 例えばMNISTの場合は各ピクセル。

Page 36: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 36

出確率は pは各層で異なっていても良い。これをドロップアウトと呼ぶ。トレーニング終了後は、全てのニューロンを使って順伝播を行う。その際、ドロップアウトの対象にした層の全てのニューロンの重みを、選出に使った確率の値を使って p倍する。これにより、推論時のニューロン数がトレーニング時と比べて 1/p倍になっていることを補償する。ドロップアウトは、トレーニング時のネットワークの自由度を強制的に小さくし、過学習を防ぐことを目的としている。試してみよ。

7.5 勾配降下法の改良

誤差逆伝播に使う学習係数 η は定数で固定していたが、これを変化させることで学習を高速化したり局所解に落ちるのを防いだりすることができる。元々の学習則は

w(l−1)ij (t+ 1) = w

(l−1)ij (t)− η

∑n

∂En

∂w(l−1)ij

(eq:bp1)

であった。モーメンタム法はそのさらに 1エポック前の値も使って

w(l−1)ij (t+ 1) = w

(l−1)ij (t) + µw

(l−1)ij (t− 1)− (1− µ)η

∑n

∂En

∂w(l−1)ij

(eq:bp1)

と重みを更新する。µは定数で比較的 1に近い 0.99から 0.5程度の値を取る。この方法を試してみよ。

7.6 計算の並列化

ミニバッチ内での誤差の計算はサンプル毎に独立に行えるし、基本的な演算はベクトルの積和なので、並列化が比較的容易である。OpenMP、MPI、CUDA、OpenCLなんでもいいので、並列計算によってトレーニングを高速化せよ。

7.7 それ以外の何でも

自分で調べて試したいものを試すと良い。全て評価の対象にする。

Page 37: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 37

8 おわりに以上です。お疲れ様でした。いかがでしたでしょうか。この実験は昨年度から新しく始めたものです。昨年度はいくつか不具合がありましたが、先輩方がレポートに多くの感想・コメントを書いてくれたおかげで、今年度は色々改善されています。是非、レポートには実験の感想やコメント、今後に向けての改善案等も書いてください*18。これからも内容をブラッシュアップし、より良い実験になるよう工夫していきたいと思います。

*18 ネガティブな事を書いたからと言って成績を下げるようなことは絶対にしませんので。

Page 38: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 38

付録 A skel.cのソースコード

ソースコード 1 skel.c

1 #include <stdio.h>

2 #include <stdlib.h>

3 #include <math.h>

4 #include <unistd.h>

5 #include <SFMT.h>

6

7 extern void sfmt_init_gen_rand(sfmt_t * sfmt, uint32_t seed);

8 extern double sfmt_genrand_real2(sfmt_t * sfmt);

9

10 typedef double Neuron, Weight;

11 typedef struct { Weight *w; int n_pre; int n_post; } Connection;

12 typedef struct { Neuron *z; int n; } Layer;

13 typedef struct { Layer *layer; Connection *connection; sfmt_t rng; int n; } Network;

14

15 double all_to_all ( Network *n, const int i, const int j ) { return 1.; }

16 double sigmoid ( double x ) { return 1. / ( 1. + exp ( - x ) ); }

17

18 void createNetwork ( Network *network, const int number_of_layers, const sfmt_t rng )

19 {

20 network -> layer = ( Layer * ) malloc ( number_of_layers * sizeof ( Layer ) );

21 network -> connection = ( Connection * ) malloc ( number_of_layers * sizeof ( Connection ) );

22 network -> n = number_of_layers;

23 network -> rng = rng;

24 }

25

26 void deleteNetwork ( Network *network )

27 {

28 free ( network -> layer );

29 free ( network -> connection );

30 }

31

32 void createLayer ( Network *network, const int layer_id, const int number_of_neurons )

33 {

34 Layer *layer = &network -> layer [ layer_id ];

35

36 layer -> n = number_of_neurons;

37

38 int bias = ( layer_id < network -> n - 1 ) ? 1 : 0; // 出力層以外はバイアスを用意39

40 layer -> z = ( Neuron * ) malloc ( ( number_of_neurons + bias ) * sizeof ( Neuron ) );

41 for ( int i = 0; i < layer -> n; i++) { layer -> z [ i ] = 0.; }

42

43 if ( bias ) { layer -> z [ layer -> n ] = +1.; } // バイアス初期化44 }

45

46 void deleteLayer ( Network *network, const int layer_id )

47 {

48 Layer *layer = &network -> layer [ layer_id ];

49 free ( layer -> z );

50 }

51

52 void createConnection ( Network *network, const int layer_id, double ( *func ) ( Network *, const int

, const int ) )

53 {

54 Connection *connection = &network -> connection [ layer_id ];

Page 39: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 39

55

56 const int n_pre = network -> layer [ layer_id ] . n + 1; // +1はバイアスの分57 const int n_post = ( layer_id == network -> n - 1 ) ? 1 : network -> layer [ layer_id + 1 ] . n;

58

59 connection -> w = ( Weight * ) malloc ( n_pre * n_post * sizeof ( Weight ) );

60

61 for ( int i = 0; i < n_post; i++ ) {

62 for ( int j = 0; j < n_pre; j++ ) {

63 connection -> w [ j + n_pre * i ] = func ( network, i, j );

64 }

65 }

66 connection -> n_pre = n_pre;

67 connection -> n_post = n_post;

68 }

69

70 void deleteConnection ( Network *network, const int layer_id )

71 {

72 Connection *connection = &network -> connection [ layer_id ];

73 free ( connection -> w );

74 }

75

76 void setInput ( Network *network, Neuron x [ ] )

77 {

78 Layer *input_layer = &network -> layer [ 0 ];

79 for ( int i = 0; i < input_layer -> n; i++ ) {

80 input_layer -> z [ i ] = x [ i ];

81 }

82 }

83

84 void forwardPropagation ( Network *network, double ( *activation ) ( double ) )

85 {

86 for ( int i = 0; i < network -> n - 1; i++ ) {

87 Layer *l_pre = &network -> layer [ i ];

88 Layer *l_post = &network -> layer [ i + 1 ];

89 Connection *c = &network -> connection [ i ];

90

91 for ( int j = 0; j < c -> n_post; j++ ) {

92 Neuron u = 0.;

93 for ( int k = 0; k < c -> n_pre; k++ ) {

94 u += ( c -> w [ k + c -> n_pre * j ] ) * ( l_pre -> z [ k ] );

95 }

96 l_post -> z [ j ] = activation ( u );

97 }

98 }

99 }

100

101 int main ( void )

102 {

103 sfmt_t rng;

104 sfmt_init_gen_rand ( &rng, getpid ( ) );

105

106 // 作る107 Network network;

108 createNetwork ( &network, 3, rng );

109 createLayer ( &network, 0, 2 );

110 createLayer ( &network, 1, 2 );

111 createLayer ( &network, 2, 1 );

112 createConnection ( &network, 0, all_to_all );

113 createConnection ( &network, 1, all_to_all );

114

115 // 試す

Page 40: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 40

116 Neuron x [ ] = { 0., 1. };

117 setInput ( &network, x );

118 forwardPropagation ( &network, sigmoid );

119 // dump ( &network );

120

121 // 消す122 deleteConnection ( &network, 1 );

123 deleteConnection ( &network, 0 );

124 deleteLayer ( &network, 2 );

125 deleteLayer ( &network, 1 );

126 deleteLayer ( &network, 0 );

127 deleteNetwork ( &network );

128

129 return 0;

130 }

Page 41: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 41

付録 B MNISTデータセットの使い方まずウェブサイトは

THE MNIST DATABASE of handwritten digits

http://yann.lecun.com/exdb/mnist/

であり、必要な情報は全てここにある。用意されているデータセットは全部で 4種類。

1. train-images-idx3-ubyte.gz トレーニングデータの画像2. train-labels-idx1-ubyte.gz トレーニングデータのラベル3. t10k-images-idx3-ubyte.gz テストデータの画像4. t10k-labels-idx1-ubyte.gz テストデータのラベル

画像は 28× 28ピクセルの 256階調グレースケール、ラベルは 0から 9までのいずれかの数字である。トレーニングデータは 60000個、テストデータは 10000個の画像とラベルからなる。データフォーマットはウェブサイトに記載されている。

1. TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[offset] [type] [value] [description]

0000 32 bit integer 0x00000801(2049) magic number (MSB first)

0004 32 bit integer 60000 number of items

0008 unsigned byte ?? label

0009 unsigned byte ?? label

........

xxxx unsigned byte ?? label

[value]の値は 0から 9。2. TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset] [type] [value] [description]

0000 32 bit integer 0x00000803(2051) magic number

0004 32 bit integer 60000 number of images

0008 32 bit integer 28 number of rows

0012 32 bit integer 28 number of columns

0016 unsigned byte ?? pixel

0017 unsigned byte ?? pixel

........

xxxx unsigned byte ?? pixel

[value]の値は 0から 255。3. TEST SET LABEL FILE (t10k-labels-idx1-ubyte):

[offset] [type] [value] [description]

0000 32 bit integer 0x00000801(2049) magic number (MSB first)

0004 32 bit integer 10000 number of items

0008 unsigned byte ?? label

0009 unsigned byte ?? label

........

xxxx unsigned byte ?? label

[value]の値は 0から 9。

Page 42: 2018 年度 MI/CS 実験第二「作ってわかる深層学習」numericalbrain.org/wp-content/uploads/dl0905.pdf · 1 2018 年度mi/cs 実験第二「作ってわかる深層学習」

2018年度 MI/CS実験第二「作ってわかる深層学習」 42

4. TEST SET IMAGE FILE (t10k-images-idx3-ubyte):

[offset] [type] [value] [description]

0000 32 bit integer 0x00000803(2051) magic number

0004 32 bit integer 10000 number of images

0008 32 bit integer 28 number of rows

0012 32 bit integer 28 number of columns

0016 unsigned byte ?? pixel

0017 unsigned byte ?? pixel

........

xxxx unsigned byte ?? pixel

[value]の値は 0から 255。

このフォーマットに従ってデータを読めば良い。また、最上位ビット (MSB) が先頭に来る。例えばトレーニングデータの画像ファイル (train-images-idx3-ubyte) の magic を読むことを考える。

Magicは先頭の 4バイト (= 32ビット)であり値は 0x00000803(2051)である。バイナリ形式で読む必要があるので、ファイル名が filenameだとして

FILE *file = fopen ( filename, "rb" );

としてファイルをオープンする。fopenの引数が"rb"であることに注意する。“b”がバイナリ形式でオープンする引数である。そして、

1 { // get the magic number

2 unsigned char buf [ 4 ];

3 fread ( buf, sizeof ( unsigned char ), 4, file );

4 int magic = buf [ 3 ] + 256 *( buf [ 2 ] + 256 *( buf [ 1 ] + 256 *( buf [ 0 ] )));

5 if ( MNIST_DEBUG ) { printf ( "%d\n", magic ); }

6 if ( MNIST_IMAGE_FILE_MAGIC != magic ) { fprintf ( stderr, "err␣magic\n" ); exit ( 1 ); }

7 }

とする。まず 4バイト freadし、MSB が先頭に来ることを考えて magicの値を構成する。念のため得られた値が正しいことを確認する。その先のデータも同様にして読んでいけば良い。こちらで用意したコードは mnist.{c,h}で、画像の値は 0–255の整数ではなく [0, 1]の倍精度浮動小数点数として保存する。