先生、旧姓で呼んでもいいですか?

雑記です。勉強したことなど

単純な例から

Deep Learningってまず名前がかっけえし、なんだかすごいらしい。そう思ってから数年がたち、んじゃ勉強してみるか、と思って青い本「深層学習」を買ったり「deep learning 入門」でググったブログを読んだりしたけど、全然ピンとこなかった。

それで、理論だけじゃ理解できないから実際に有名どころのライブラリをダウンロードしてそのチュートリアルを写経して、たしかに文字認識してる!すげ〜ってなったけど、いまひとつ煮え切らないのだった。なぜだろうか。それでもだらだらとブログを読み漁っていると、こんな記事を見かけた。

hi-king.hatenablog.com


この記事では、まずはじめに、1950年代後半にローゼンブラッドが提案して1969年にミンスキーがダメ出ししたパーセプトロンを、2016年現在ブイブイ流行ってるDeep Learningライブラリ・Chainerで実装してる。

パーセプトロンは二層のネットワーク(PRML的に言えば一層)でクラス分類するんだけど、とりあえず俺の理解では、スッと直線を引いて、お前はこっち、君はあっちと分けられるデータしか分類できないって感じだった(線形のことを直線というと数学的に高貴な人にめちゃくちゃ怒られるけど、庶民にとってはそれでいいじゃないかと思う)。

とにかく、その記事がとてもいいなって思ったのは、データと、それに対応するラベル(答え)の規則がシンプルでわかりやすい。AND(かつ)って規則は、次のような話だった。

データが 0 かつ 0 なら、ラベルは 0
データが 1 かつ 0 なら、ラベルは 0
データが 0 かつ 1 なら、ラベルは 0
データが 1 かつ 1 なら、ラベルは 1

人間だったら、なんとなくこの規則は理解できる。けど、マシンはこの規則ことは一切知らないし、理解させることもできない(人間がこう来たらこう返せと条件を教えない限り)。だから、学習させて、 任意のデータを得たとき適切なラベルを返すような関数をつくるしかない。それが、この記事のはじめで説明されたことだ。

でも、さっきも言ったとおりパーセプトロンには欠陥があった。ミンスキーは次の場合(XOR)は実現できないじゃん!と指摘したのだった。

データが 0 かつ 0 なら、ラベルは 0
データが 1 かつ 0 なら、ラベルは 1
データが 0 かつ 1 なら、ラベルは 1
データが 1 かつ 1 なら、ラベルは 0

人間だったなら、このデータとラベルの対応を、なんとなく理解できる。だけど、マシンはそれを知らない。そして、パーセプトロンという方法を使っても、適切な関数を作ることができない(なんとなく、直線をスッと引いて、お前はこっち、君はあっちと分類できなさそうじゃん?)。

そこで、二層だったパーセプトロンを多層にする手法と、活性化関数と呼ばれる0~1の微妙な値を返す関数が導入される。それは多層パーセプトロンと呼ばれる。Deep Learnig は、この多層パーセプトロンを問題に合わせて色々と改良した手法の体系を指すらしい。

ローゼンブラッドがパーセプトロンを多層にしなかったり、活性化関数を導入しなかった理由は、きっと、そうなったパーセプトロンをどうやって学習させたらいいのかわかんなかったからだ。でも、それは誤差逆伝搬法というラメルハートとマクレランドの発想の逆転で解決した。かくして、ミンスキーが指摘した問題点は破られた。

それが、この記事で二番目に解説されていることだった。

で、ここから発想が飛躍する。

まてよと。そもそも、多層パーセプトロンは、ANDとかXORとかの規則を表現しているわけではない。ただただ、データに対応するラベルを返しているだけだ。ということは、人間が勝手に作った適当な規則でも、データに対応するラベルを返すことができるはずじゃないか?

人間が直面する問題は、ANDやXORのように、明快な規則があるわけではない。言葉にできない規則によって世の中は動いていることが多い。もし、その規則を数式で表現できるなら...

それで、この記事に載ってるソースコードを次のように書き換えてみた。

gistac8de526ab7905c944ab

はじめに、参考にした記事と同じようにXORの規則を学習させてみる。

datas_and_labels = [
 [np.array([0,0]),np.array([0])],
 [np.array([0,1]),np.array([1])],
 [np.array([1,0]),np.array([1])],
 [np.array([1,1]),np.array([0])],
]*10000
data_size,label_size = 2, 2 

左がデータで、右がそれに対応するラベルだ。
このデータとラベルのセットを10000個作って、上のソースを動かしてみると、

---epoch 39996---
source label:  0, data: [0 0]
predict label: 0, error: 0.00168514251709
---epoch 39997---
source label:  1, data: [0 1]
predict label: 1, error: 0.00123429298401
---epoch 39998---
source label:  1, data: [1 0]
predict label: 1, error: 0.00123333930969
---epoch 39999---
source label:  0, data: [1 1]
predict label: 0, error: 0.00130653381348

となる。
sourceは元々のラベルで、predictはこのネットワークが学習して予測したラベル。
だから、一致していたら、正しく予測されているということ。
errorは間違いの度合いみたいなものだと思うけど、勉強不足でよくわからない。
ともかく、これでXORは学習できてることがわかった。
ちなみに、このセットの個数が10000じゃなくて1000だと、わりと予測を間違える。

今度は、XORとかの規則はなしに、適当にラベルをふって学習させてみる。

datas_and_labels = [
    [np.array([0,0,0]),np.array([0])],
    [np.array([1,0,0]),np.array([1])],
    [np.array([0,1,0]),np.array([0])],
    [np.array([0,0,1]),np.array([1])],
    [np.array([1,1,0]),np.array([0])],
    [np.array([1,0,1]),np.array([1])],
    [np.array([0,1,1]),np.array([0])],
    [np.array([1,1,1]),np.array([1])],
]*1000
data_size,label_size = 3, 2  

別に規則はなく、単に0と1を交互にラベリングしただけ。
これでどうなるか。

---epoch 7992---
source  label: 0, data: [0 0 0]
predict label: 0, error: 0.0037796497345
---epoch 7993---
source  label: 1, data: [1 0 0]
predict label: 1, error: 0.00383639335632
---epoch 7994---
source  label: 0, data: [0 1 0]
predict label: 0, error: 0.000137805938721
---epoch 7995---
source  label: 1, data: [0 0 1]
predict label: 1, error: 0.00469827651978
---epoch 7996---
source  label: 0, data: [1 1 0]
predict label: 0, error: 0.0032320022583
---epoch 7997---
source  label: 1, data: [1 0 1]
predict label: 1, error: 0.000639677047729
---epoch 7998---
source  label: 0, data: [0 1 1]
predict label: 0, error: 0.00304293632507
---epoch 7999---
source  label: 1, data: [1 1 1]
predict label: 1, error: 0.00445556640625

おお!ちゃんと元のラベルと予測のラベルが一致してる!!
でも、0と1の2値だったから、たまたまうまくいったのかもしれない。

これならどうだ。

datas_and_labels = [
    [np.array([0,0,0,0]),np.array([0])],
    [np.array([1,0,0,0]),np.array([1])],
    [np.array([0,1,0,0]),np.array([1])],
    [np.array([0,0,1,0]),np.array([1])],
    [np.array([0,0,0,1]),np.array([1])],
    [np.array([1,1,0,0]),np.array([2])],
    [np.array([1,0,1,0]),np.array([2])],
    [np.array([1,0,0,1]),np.array([2])],
    [np.array([0,1,1,0]),np.array([2])],
    [np.array([0,1,0,1]),np.array([2])],
    [np.array([0,0,1,1]),np.array([2])],
    [np.array([1,1,1,0]),np.array([3])],
    [np.array([1,1,0,1]),np.array([3])],
    [np.array([1,0,1,1]),np.array([3])],
    [np.array([0,1,1,1]),np.array([3])],
    [np.array([1,1,1,1]),np.array([4])],
]*1000     
data_size,label_size = 4, 5  

適当に、データに入ってる1の個数をラベルにしてるだけ。
だから[0, 0, 0, 0]は0だし、[1, 1, 1, 1]は4になってる。
それでも、

---epoch 15984---
source  label: 0, data: [0 0 0 0]
predict label: 0, error: 0.0368700027466
---epoch 15985---
source  label: 1, data: [1 0 0 0]
predict label: 1, error: 0.0263843536377
---epoch 15986---
source  label: 1, data: [0 1 0 0]
predict label: 1, error: 0.0256023406982
---epoch 15987---
source  label: 1, data: [0 0 1 0]
predict label: 1, error: 0.0246782302856
---epoch 15988---
source  label: 1, data: [0 0 0 1]
predict label: 1, error: 0.0246057510376
---epoch 15989---
source  label: 2, data: [1 1 0 0]
predict label: 2, error: 0.0296049118042
---epoch 15990---
source  label: 2, data: [1 0 1 0]
predict label: 2, error: 0.029851436615
---epoch 15991---
source  label: 2, data: [1 0 0 1]
predict label: 2, error: 0.0298862457275
---epoch 15992---
source  label: 2, data: [0 1 1 0]
predict label: 2, error: 0.030113697052
---epoch 15993---
source  label: 2, data: [0 1 0 1]
predict label: 2, error: 0.0300631523132
---epoch 15994---
source  label: 2, data: [0 0 1 1]
predict label: 2, error: 0.0303115844727
---epoch 15995---
source  label: 3, data: [1 1 1 0]
predict label: 3, error: 0.0494899749756
---epoch 15996---
source  label: 3, data: [1 1 0 1]
predict label: 3, error: 0.0499367713928
---epoch 15997---
source  label: 3, data: [1 0 1 1]
predict label: 3, error: 0.0556311607361
---epoch 15998---
source  label: 3, data: [0 1 1 1]
predict label: 3, error: 0.0617532730103
---epoch 15999---
source  label: 4, data: [1 1 1 1]
predict label: 4, error: 0.112795829773

ちゃんと分類してるっぽい!すげえ!
当たり前の人には、何騒いでるんだ。だろうけど、本当に学習されるんだ。感動。
わからないことだらけだけど、確かにそうなるのは、すごい。
んじゃ、このソースコードを拡張して、MNISTとかやってみたらどうなるんだろ。