デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す。https://qiita.com/yaju

ディープラーニング(深層学習)を理解してみる(勾配降下法:計算方法)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

幾つかの人工知能関連の本やWebサイトを見ても、数式やプログラムのソースリストは記載されていても、数学が苦手な自分が理解できるようになるまでの説明が無い、そんな中でも下記2つの本(Kindle)がまだ理解できそうな感じで参考になりそうである。

  

基礎的な知識から、やっと実際の計算方法に入っていきます。

勾配降下法

あとで書く

リンゴとミカン

あとで書く

ディープラーニング(深層学習)を理解してみる(勾配降下法:ベクトル、内積、微分、偏微分)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

幾つかの人工知能関連の本やWebサイトを見ても、数式やプログラムのソースリストは記載されていても、数学が苦手な自分が理解できるようになるまでの説明が無い、そんな中でも下記2つの本(Kindle)がまだ理解できそうな感じで参考になりそうである。

  

計算方法に入る前にベクトル等の基礎知識を先に説明していきます。

ベクトル

ベクトルとは大きさと向きを持つ量と定義されます。
f:id:Yaju3D:20171001213101p:plain

ベクトルの矢を座標平面上に置くことで座標のように表現できます。矢の始点を原点に置き、矢の終点の座標でそのベクトルを表します。これをベクトルの成分表示といいます。
成分表示されたベクトル a で下図のように表現します。また3D座標上でもz軸方向を追加して同じように表現します。
f:id:Yaju3D:20171001225146p:plain

下図のように矢印がたくさんある図を見たことがありませんか?
各データは、どこかの一点のデータを基準にして、大きさと向きを持つベクトルとなります。
f:id:Yaju3D:20171001213359p:plain

内積

ベクトルにも足し算と引き算があるように掛け算も…といいたいところですが、微妙に違うのが内積です。内積だとベクトル同士を掛け算っぽくするのに結果はベクトルではなくスカラという1つの値となります。内積以外にも外積がありますが、ここでは説明しません。

過去に3Dの勉強用に書いた記事がありますので、興味があれば読んでみて見ください。

内積というのは2つのベクトルの“近さ”を表しているという程度に理解しておけばいいです。
2つのベクトルの近さを表すのにcos\thetaが使われます。

f:id:Yaju3D:20171001231022p:plain

高校までの数学だと「内積の値がcos\thetaを使って表される」と教わるが、逆にcos\thetaの値が内積によって定義されるのだ」と考えるようにするといいでしょう。

cos\thetaの値は2つのベクトルの向きが反対なら「-1」、同じであれば「1」となる。 f:id:Yaju3D:20171002005747p:plain

(ア)2つのベクトルが反対向きのときに内積は最小値
(イ)2つのベクトルが平行でないとき、内積は平行の場合の中間の値
(ウ)2つのベクトルが同じ向きのときに内積は最大値

そして、(ア)の内積が最小値というのが、勾配降下法の基本原理となります。

手書き数字認識でどの数字か分類できるのは、より似ているかで判断しています。
方向が似ているベクトルを「似ている」と判断するなら、2つのベクトルが似ていると内積は大きくなるのです。
f:id:Yaju3D:20171002005805p:plain

内積の計算

平面(2次元)
f:id:Yaju3D:20171002005819p:plain

立方空間(3次元)
f:id:Yaju3D:20171002005835p:plain

内積は2次元や3次元に限らず、何次元のベクトルについても計算することができます。現実世界では4次元や10次元ベクトルで表せるものは存在しないですが、数学上は計算できるところが数学のおもしろいところでもあります。

この内積が何次元(多次元)であっても計算できるということが重要なポイントです。
リンゴとミカンの値段を求める際は2変数です、それにブドウを付け加えると3変数になり、レモンも付け加えると4変数になります。変数が多くなることを多変数と言います。この多変数というのは機械学習における多次元と同じ意味を持っています。

参照

微分

微分とは、ある関数の各点における傾き(変化の割合)のことです。
そして勾配降下法においては、傾きの最小値を求めることが目的です。 f:id:Yaju3D:20171007211313p:plain

最小値

微分において、傾きが水平になる 0 が最小値といいたいところですが、下図右のように最大値においても水平になり 0 となります。
f:id:Yaju3D:20171008132102p:plain

このことは、勾配降下法においては厄介な性質になります。
f:id:Yaju3D:20170917223314p:plain

導関数

関数 y=x^{2}微分して得られた導関数は指数を係数にした {y}'=2x となります。
微分の定義式では極限の記号\displaystyle \lim_{h \to 0}を使って、x の変化量 h を限りなく 0 に近づけます。

\displaystyle \lim_{h \to 0}\frac{f(x+h)-f(x)}{h}={f}'(x)

※現代の数学では、0 で割る(分母が0)ことは禁止事項とされているので、極限まで 0 に近づければいいという屁理屈です。

ちなみに、'の名称は「プライム」で、\limの名称は「リミット」と読み、極限(limit)を取る記号です。

sci-pursuit.com

偏微分

関数 y=x^{2} は変数が1つでしたが、リンゴとミカンの値段を求める場合は2変数による関数z=f(x,y)となります。このように変数が2つ以上の関数を多変数関数といいます。
多変数関数でも微分法を適用できます。ただし変数が複数あるので、どの変数について微分するかを明示しなければなりません。この意味で、ある特定の変数について微分することを偏微分(partial derivative)といいます。
例えば、2変数x,yから成り立つ関数を考えてみましょう。変数xだけに着目してyは定数と考える微分を「xについての偏微分」と呼び、次の記号で表します。

\displaystyle\frac{\partial z}{\partial x}=\frac{\partial f(x,y)}{\partial x}=\lim_{x \to 0}\frac{f(x+\Delta x,y)-f(x,y)}{\Delta x}

yについての偏微分も同様です。

\displaystyle\frac{\partial z}{\partial y}=\frac{\partial f(x,y)}{\partial y}=\lim_{y \to 0}\frac{f(x,y+\Delta y)-f(x,y)}{\Delta y}

ちなみに、\partialの名称は「デル」、\Deltaの名称は「デルタ」です。

関数 z=x^{2} + y^{2}偏微分すると指数を係数にした \displaystyle\frac{\partial z}{\partial x}=2x\displaystyle\frac{\partial z}{\partial y}=2y となります。

remedics.air-nifty.com

勾配降下法に適用

上述で説明してきたベクトルと内積偏微分が勾配降下法にどのように使われるのか?

勾配降下法は、少しずつ下りながら、場所ごとに最も急な勾配を探すことになります。
P0から最も勾配の急な点P1の位置を求める。その位置P1から更に最も勾配の急な点P2の位置を求める。このように繰り返すことで最も早く最小点にたどり着くようになります。 f:id:Yaju3D:20170924211620p:plain

関数z=f(x,y)において、xをΔxだけ、yをΔyだけ変化させたとき、関数z=f(x,y)の変化は下記式となります。
\displaystyle \Delta z = f(x + {\Delta x},y + {\Delta y}) - f(x,y)
これを近似公式から次の関係が成立します。
\displaystyle \Delta z = \frac{\partial f(x,y)}{\partial x}{\Delta x} + \frac{\partial f(x,y)}{\partial y}{\Delta y}

f:id:Yaju3D:20171010235143p:plain

そして、これは2つのベクトルの内積を表す式になっているのです。 f:id:Yaju3D:20171010234406p:plain

ベクトルが反対向きになっている時に、内積が最小値となっている。 f:id:Yaju3D:20171011001024p:plain f:id:Yaju3D:20171011001230p:plain

ベクトルが反対向きは、-1となり、勾配降下法の基本式は次のように表せます。(\etaは正の小さな定数)
\displaystyle({\Delta x}, {\Delta y})=-\eta\left(\frac{\partial f(x,y)}{\partial x}, \frac{\partial f(x,y)}{\partial y} \right)

ちなみに、\etaはイータと読むギリシャ文字です。ローマ字のiに対応します。
なぜループカウンタ変数のほとんどに “i”が使用されるのか? - Qiita

重要なのは、内積は何次元(何変数)のベクトルについても計算することができます。

\displaystyle({\Delta x_1}, {\Delta x_2}, \cdots, {\Delta x_n})=-\eta\left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \cdots, \frac{\partial f}{\partial x_n} \right)

式が長いので数学のベクトル解析で用いられるハミルトン演算子\nablaのナブラを使って省略します。
\displaystyle \nabla f = \left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \cdots, \frac{\partial f}{\partial x_n} \right)

2変数関数 z=f(x,y)の勾配降下法の基本式は次のように表せます。
\displaystyle({\Delta x}, {\Delta y}) = -\eta\nabla f(x,y)

3変数関数 z=f(x,y,z)の勾配降下法の基本式は次のように表せます。
\displaystyle({\Delta x}, {\Delta y}, {\Delta z}) = -\eta\nabla f(x,y,z)

次回は計算方法について説明していきます。

参照

ディープラーニング(深層学習)を理解してみる(勾配降下法:最急降下法と確率的勾配降下法)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

勾配降下法をどうして使うのかは理解できたのですが、その計算方法がまだ理解が足りてなくて、微分の本とかを読んでいました。
数学は苦手なんですが、理解はしたい。おまじないとかそういうルールだからとかで済ましたくないんですよね。

勾配降下法の他にも最急降下法とか確率的勾配降下法の用語がありますので、この違いも理解したい。
今回は下記2つの本(Kindle)を参考にしている。

  

勾配降下法

f:id:Yaju3D:20170917211137p:plain
勾配には、傾斜の程度・斜面という意味があります。
上図では1変数による関数y=f(x)でグラフは放物線となります。

リンゴとミカンの値段を求める場合は2変数による関数z=f(x,y)となり下図のようにワイングラスの底のような形になります。ちなみに3変数以上は図で表わすのは困難です。
f:id:Yaju3D:20170924173848p:plain
下図は関数のグラフの一部を拡大し、斜面に見立てた図となります。その斜面上のある点Pにピンポン玉を置き、そっと手を放すと玉は最も急な斜面を選んで転がり始めます。
f:id:Yaju3D:20170924181321p:plain

この操作を何回も繰り返せば、ピンポン玉は最短な経路をたどってグラフの底、すなわち関数の最小値にたどり着くはずです。この玉の動き(誤差関数という坂道、つまり「勾配」を下へ下へと「降下」していく)を真似たのが勾配降下法です。
f:id:Yaju3D:20170924183956p:plain

正確には最小値を探す場合を勾配降下法(gradient descent mothod)と言い、最大値を探す場合を勾配上昇法(gradient ascent mothod)と言います。descentは、SQLで降順にソートさせる時に略称(desc)で、order by column descと使いますね。

最短で下るには、少しずつ下りながら、場所ごとに最も急な勾配を探すことになります。
P_0から最も勾配の急な点P_1の位置を求める。その位置P_1から更に最も勾配の急な点P_2の位置を求める。このように繰り返すことで最も早く最小点にたどり着くようになります。
f:id:Yaju3D:20170924211620p:plain

数値解析の分野では勾配降下法を最急降下法と呼びますが、勾配降下法の中にもいくつかの方法が存在します。

最急降下法とは

最急降下法は学習データのすべての誤差の合計を取ってからパラメーターを更新します。学習データが多いと計算コストがとても大きくなってしまいます。また、学習データが増えるたびに全ての学習データで再学習が必要となってしまいます。

確率的勾配降下法とは

確率的勾配降下法は学習データをシャッフルした上で学習データの中からランダムに1つを取り出して誤差を計算し、パラメーターを更新をします。勾配降下法ほどの精度は無いが増えた分だけの学習データのみで再学習する(重みベクトルの初期値は前回の学習結果を流用)ため再学習の計算量が圧倒的に低くなります。
運が良ければ最急降下法よりも早く最適解にたどり着けますが、運が悪ければいつまで経っても適した答えを導けません。

ミニバッチ確率的勾配降下法とは

ミニバッチ確率的勾配降下法最急降下法確率的勾配降下法の間を取ったような形となります。 最急降下法では時間がかかりすぎ、確率的勾配降下法では一つ一つのデータにかなり揺さぶられることになるので、学習データの中からランダムにいくつかのデータを取り出して誤差を計算、パラメーターを更新をします。このときの一回に取り出すデータの数をバッチサイズと呼びます。

最急降下法確率的勾配降下法の違い

パラメーター更新の式

最急降下法のパラメーター更新の式
\theta_j := \theta_j - \eta\sum_{i=1}^n(f_{\theta}({x}^{(i)}) - y^{(i)}){x_j}^{(i)}

確率的勾配降下法のパラメーター更新の式
\theta_j := \theta_j - \eta(f_{\theta} ({x}^{(k)}) - y^{(k)}){x_j}^{(k)}
(式中のkは、パラメーター更新毎にランダムに選ばれたインデックス)

大きな違いとして、確率的勾配降下法ではシグマ\sum (1~nまで合計)が取れています。
その分、計算コストは少なくなっていることが分かりますよね。

最小値へのたどり着き方

最急降下法がほぼ直線的に最小値にたどり着くのに対し、確率的勾配降下法はランダムなのでくねくねしながら最小値にたどり着きます。
f:id:Yaju3D:20170925010546p:plain

局所解に陥る可能性

誤差関数が素直なカーブのみならいいですが、下図のようにいったん下がってまた上がるみたいなうねうねした場合があります。本当は④が最小値なのに別の小さな谷(これを局所解といいます)に捕まってしまうことがあります。
f:id:Yaju3D:20170917223314p:plain

確率的勾配降下法はこのうねうねのいろいろなところから勾配を下ろうとするため、最急降下法よりも局所解に陥る可能性が小さくなります。

定数η(イータ)は学習係数と呼ばれます。このηは人が移動する際の「歩幅」と見立てられます。このηで決められた値に従って次に移動する点が決められるからです。その歩幅が大きいと最小値に達しても、それを飛び越えてしまう危険があります(下図左)。歩幅が小さいと、極小値で停留してしまう危険があります(下図右)。
f:id:Yaju3D:20170925010945p:plain

次回は、勾配降下法を実際に計算してみます。

参照

ディープラーニング(深層学習)を理解してみる(勾配降下法)

はじめに

機械学習をやる上では「勾配降下法」を理解しておきたい。
「勾配降下法」で検索すると自分が書いた記事が見つかります。 yaju3d.hatenablog.jp

資料を元に書いた記事なので当時はよく理解していたわけではないですが、今、読み返すとふむふむと言ったところです。今回はもっと深く追求していきます。

機械学習は最適解を求めるのが目的です。
上記記事の八百屋の件でも、条件が当てはまる最小の値をひたすら繰り返し計算すれば求めることが出来るのですが、これを出来るだけ少ない計算で最小値を見つけるようにしたい。ここで「勾配降下法」を使うことになるわけです。

解けない連立方程式

どうせなので八百屋の問題を使います。

例題 1

リンゴ1個とミカン3個を買うと190円、リンゴ3個とミカン1個を買うと330円するようです。
リンゴ2個とミカン4個を買うといくらになるでしょうか? f:id:Yaju3D:20160417225841p:plain

今回は数式で表してみます。次の連立方程式となります。

\displaystyle \begin{cases}
 a + 3b &=& 190 \\ 
3a +  b &=& 330 \\
\end{cases}

加減法で計算します。

\displaystyle \begin{eqnarray}
   3a + 9b &=& 570 \\ 
-) 3a +  b &=& 330 \\
\\
         8b &=& 240 \\ 
      b &=& 30 
\end{eqnarray}
\displaystyle \begin{eqnarray}
a + 3b &=& 190 \\  
a + 3\times30 &=& 190 \\
a &=& 190-90 \\
a &=& 100
\end{eqnarray}


a=100b=30 と分かりましたので、値を入れると答えが 320 と求まります。

f:id:Yaju3D:20160417230109p:plain f:id:Yaju3D:20160417230118p:plain

例題 2

例題 1 に対し、更にリンゴ5個とミカン7個を買うと660円を追加した場合
リンゴ2個とミカン4個を買うといくらになるでしょうか?
f:id:Yaju3D:20160417230234p:plain

先程と同様に連立方程式を書いてみます。

\displaystyle \begin{cases}
 a + 3b &=& 190 \\ 
3a +  b &=& 330 \\
5a + 7b &=& 660
\end{cases}


先程の答えの a=100b=30 を入れても答えが 100\times5 + 30\times7 = 500 + 210 = 710となり答えの 660にはなりません。

f:id:Yaju3D:20160417230455p:plain

中学校で勉強する連立方程式は、未知数(パラメータの数)が2個であれば、方程式の数も2本でなければなりません。例題 2は、方程式の数が多すぎるということになります。 とは言え、実際の世の中の現象は、このようにデータがバラバラなケースのほうが多いですよね。
そこで出来るだけ近い値である近似値を求めることにするわけです。

近似値を求める

今回、近似値を求めるのには最小2乗法を使います。

最小二乗法とは、モデル関数を f(x) とするとき、

\displaystyle \sum_{i=1}^n\{y_i-f(x)\}^2

が最小となるような f(x) を求めることである。 sci-pursuit.com

下図に合わせて、最小二乗法の式を変更してみます。

x_i がリンゴの個数、y_i がミカンの個数、z_i が合計値とする。

a がリンゴの価格、b がミカンの価格の最小値を求める。

\displaystyle \frac{1}{3}\sum_{i=1}^n\{(ax_i + by_i)-z_i\}^2

最後に平均しているので3で割っていますが、最小値を求める上で平均ではなく合計したままでも、最小二乗法の公式通りに2で割っても最終的に求まる結果は変わらないです。

f:id:Yaju3D:20160417230639p:plain
条件が当てはまる最小の値をひたすら繰り返し計算する。
f:id:Yaju3D:20160417230656p:plain f:id:Yaju3D:20160417230833p:plain

たくさん計算した結果、最小の値として、a=90b=30 が求まりました。

f:id:Yaju3D:20160417231507p:plain

少ない計算で最小値を求める

最小二乗法では1回の計算で(一発で)パラメータを求める方式です。データを追加する度に再度計算し直すことになります。少ないデータ量の時はいいですが、データ量が膨大になるとシステムの対応が難しくなってきます。

もし、データを追加するたびにパラメータを更新していく方式ができるなら、変化点だけの少ない計算で最小値を求めることができるようになります。
f:id:Yaju3D:20160417230848p:plain
それが、「勾配降下法」という方法となります。

続きは後日書きます。

ディープラーニング(深層学習)を理解してみる(TensorFlow Playgroundを試す)

はじめに

前回はパーセプトロンを多層にすることで、線型分離可能でない問題を解けることを学びました。
yaju3d.hatenablog.jp

これを視覚として見ながら学びたいということで見つけたのが「TensorFlow Playground」となります。
f:id:Yaju3D:20170624220010p:plain

しかし、初見では何がなんだか分からないので「TensorFlow Playground」のことを書いている幾つかのブログを参考に学んでいきます。

TensorFlow Playgroundとは

TensorFlow Playgroundは、A Neural Network Playgroundとも呼ばれ、Daniel SmilkovさんとShan Carterさんが開発したニューラルネットワークの仕組みを理解するための教育コンテンツです。

TensorFlow Playgroundの日本語訳を書いてくれているのが「TensorFlow Playgroundの仕組み」です。

目的

下図のように青とオレンジの正しい区切りの位置を見つけるということです。
直線だと簡単に分離できる問題もあれば、円形という複雑な問題もあるわけです。
f:id:Yaju3D:20170604230608p:plain

使い方

例題 Circle(円)、Gaussian(ガウシアン)、Exclusive or(排他or:XOR)、Spiral(螺旋)
f:id:Yaju3D:20170626230724p:plain

全体レイアウト

f:id:Yaju3D:20170625120546p:plain

  • ①は、DATA(データ)は例題となります。例題は4つあり初期値は円となっています。
  • ②は、FEATURES(特徴)は、入力するデータの特徴となります。特徴は7つあり初期値は上2つとなっています。
  • ③は、HIDDEN LAYERS(隠れ層)は中間層となります。中間層の数を「+」と「-」ボタンで増減でき初期値は2段となっています。
  • ④は、neurons(ニューロン)は、各隠れ層のノードとなります。
    ※各ノードをマウスオーバーすると⑥で拡大して見れます。ニューロンは重みの影響を受け、重みは線の太さと色で表されます。
    • ④-1が先頭の中間層で初期値は4ニューロンとなります。
    • ④-2が最後の中間層で初期値は2ニューロンとなります。
  • ⑤は、再生ボタンとなります。決定したモデルを実際に動かします。
  • ⑥は、出力結果となります。
  • ⑦は、Test loss(誤差(テスト))とTraining loss(誤差(トレーニング))の数値となります。

実際に動かしてみよう

デフォルト状態で再生してみます。右側の出力結果の平面上に点在したオレンジと青の点々に沿うようにヒートマップの色が変化していく様子が見れるはずです。 そして点々の色とヒートマップの色の分布が互いに近づいてくるに従って誤差の数値が小さくなる様子も確認できるはずです。

青枠⑦のTest loss(誤差(テスト))とTraining loss(誤差(トレーニング))の数値を見ます。これが少ないほど良い結果になります。
Epochが50くらいで図上では分離されていますが、誤差の数値的には0.022とまだあります。
Epochが100くらいで誤差の数値的には0.008くらいまで下がります。
Epochが500くらいまでいくと誤差が0.0001まで下がります。

パラメーターをいじってみる

④-1の先頭の中間層を4ニューロンから3ニューロンに減らしてみます。
f:id:Yaju3D:20170625134326p:plain
これだとあまり変化なく、Epochが500くらいまでいくと誤差が0.0001まで下がります。
さらに2層まで減らすとEpochが1000までいっても収束しません。

今度は、中間層を1つ増やしてみます。
f:id:Yaju3D:20170625145117p:plain
Epochが200くらいまでいくと誤差が0.0001まで下がるので、収束は速くなります。

各パラメーターの説明

TensorFlow Playgroundの日本語訳「TensorFlow Playgroundの仕組み」から一部抜粋します。

上側パラメーター
  • Epoch(エポック)は、学習させる回数です。用意された全トレーニングデータをニューラルネットワークに投入しパラメータを更新、修正し終えると機械学習の1サイクル(1エポック)が完了します。 統計モデルの種類が分類の場合、トレーニングデータは500個なので、この500個全部をトレーニングに使い切ると1エポック完了したことになります。
  • Learning rate(学習率)は、一度の学習でどの程度重みやバイアスの値を修正するかの率を表します。学習率が大きいと1度に修正される重み、バイアスの量が大きくなります。 小さくすると重み、バイアスの値を1度に少しだけ修正しようとします。
  • Activation(活性化関数)は、ニューロンの出力をフィルタリングします。tanh、ReLU、Sigmoid(シグモイド関数)、Liner(線形関数)があります。
  • Regularization(正則化)は、過学習を防ぐための仕組みです。None, L1, L2 があります。
  • Regularization rate(正則化項)は、正則化されたニューラルネットワークに対して正則化項を加えることで、より過学習を防ぐようになります。
  • Problem type(統計モデルの種類)は、「Classification(分類)」と「Regression(回帰)」の2種類があります。
    • Classification(分類)は、入力した画像が猫であるか猫でないかを0/1で判断する。分類の図は点々がブルー(+1)かオレンジ(-1)かの二通りの出力結果のデータの分布となります。
    • Regression(回帰)は、得点の値について-1,1だけのとびとびの離散的な値だけでなく、 その間の-0.529や0.29039などの-1から+1までの連続的な中間値も考慮に入れます。入力した画像が猫である確率を求める場合に使います。回帰の図は分類と同様に点々がブルー(+1)かオレンジ(-1)であることには変わりありませんが、白味がかった-1と+1の間の中間の値のデータも含まれます。
左側パラメーター
  • Ratio of training to test data(トレーニングデータの割合)は、「Classification(分類)」が500個、「Regression(回帰)」が1200個のデータのサンプル数となっていますので、例えばトレーニングデータの割合を60%とすると、500個のデータサンプルの内300個をトレーニングデータに、 200個のデータをテストデータに回します。
  • Noise(ノイズ)は、円やガウシアンなどの選択したデータセットに対してかけるノイズ(0〜50まで5刻みで設定)で数値は%単位です。
  • Batch size(バッチサイズ)は、1回の学習(重み、バイアスの更新)を何個のトレーニングデータで実施するかを決める数値です。例えば1エポックで500回学習とした場合にバッチサイズが25にすると、1エポックで20回学習することになります。
  • REGENARATE(再生成)は、データを再生成します。
右側下パラメーター

f:id:Yaju3D:20170626231602p:plain

過学習

過学習が発生すると、十分に小さなTraining loss(誤差(トレーニング))に対して、Test loss(誤差(テスト))が小さくならない現象が発生します。TensorFlow Playgroundで意図的に過学習を起こすことは簡単です。画面左側の「Ratio of training to test data(トレーニングデータの割合)」を10%まで下げてみます。
f:id:Yaju3D:20170626234708p:plain

スライド

例題

Problem type(統計モデルの種類)の「Classification(分類)」だけ他の例題を自分なりにやってみました。
これが正しいわけでもなく、再更新すると違う結果になったりすることもあります。

Gaussian(ガウシアン)

一番単純です。Activation(活性化関数)をLiner(線形関数)にしています。
f:id:Yaju3D:20170626232335p:plain

Exclusive or(排他or:XOR)

XORは線形分離不可能な問題です。特徴量にX1X2があるのでそのまま使っています。
f:id:Yaju3D:20170626233038p:plain

別方法として特徴量は先頭2つでやってみました。Activation(活性化関数)をReLUにすると直角的になります。
f:id:Yaju3D:20170627215236p:plain

Spiral(螺旋)

特徴量を全て使っています。過学習を防ぐためRegularization(正則化)をL1、Regularization rate(正則化項)を0.001にしてます。
なかなかTest loss(誤差(テスト))が下がらなくて、0.008が限界でした。
f:id:Yaju3D:20170626233502p:plain

特徴量を減らして、中間層を増やしてみました。Test loss(誤差(テスト))は、0.006になりました。
f:id:Yaju3D:20170627234331p:plain

参照

ディープラーニング(深層学習)を理解してみる(パーセプトロンと論理演算)

はじめに

TensorFlow関連の記事を書いている割には、ディープラーニング(深層学習)について理解度が足りてないということもあって基礎的なところから理解してみようと思いました。幸い、ここ1年で本やブログ記事が増えてきたので助かります。

参考にした下記の2つ本となります。あと、この本の内容を元にした幾つかのブログの記事などです。
自分なりに理解したことをまとめて書いていきますが、詳細はリンク先を参照してください。

線形と非線形

機械学習をやる上で「線形」と「非線形」という言葉が出てきますので、先に説明しておきます。

線形

簡単に言えば、直線的な線のことです。
一次関数「例 y = ax + b」のように定数倍と足し算・引き算で表されたものとなります。

【追記 2017/07/21】 y = ax + bは、線形ではないとのことです、線形の定義では原点を通るとのこと。 そもそも線形ってどういうこと - Qiita
linear function 数学などに関する備忘録/ウェブリブログ

非線形

線形以外で曲線的や変化が激しい線のことです。
二次関数「例 y = x2」のように放物線などで表されたものです。

イメージ

左図が線形、右図が非線形となります。「★」と「○」を区分ける際に直線で区分けるのは簡単ですが、曲線的でないと区分けることが出来ないのは難しいです。
f:id:Yaju3D:20170604200125p:plain

ディープラーニング(深層学習)とは

ディープラーニング機械学習の一種で、神経細胞を模したパーセプトロン(ニューロンモデル)と言う小さな計算機をたくさん用意し多層にして構築したものです。よって、多層構造のニューラルネットワークを用いた機械学習と言えます。 ※パーセプトロンニューラルネットワークの基本単位となります。

現在のディープラーニングに到るまでを順を追って書いています。

形式ニューロン

f:id:Yaju3D:20170528144340p:plain f:id:Yaju3D:20170528154127p:plain
左図が本物の神経細胞(ニューロン) で、右図が形式ニューロンです。
形式ニューロンWikipediaから引用します。

形式ニューロン(けいしきニューロン、英: formal neuron)や Threshold Logic Unit とは、1943年に神経生理学者・外科医であるウォーレン・マカロックと論理学者・数学者であるウォルター・ピッツが発表した最初の人工ニューロン(英: artificial neuron)[1]。伝達関数としてはヘヴィサイドの階段関数を使い、入出力の値は 0 または 1 の二値だけをとる。

簡単に説明すると、入力が2つあり各入力に対して重みが掛け算され、その値が閾値を超えれば出力は「1」、そうでなければ出力は「0」となります。 たとえば、入力が(1,0)、重みが(0.5, 0.7)だとすると、1×0.5 + 0×0.7 = 0.5 を計算して閾値と比較します。

形式ニューロンの場合、入力が「0」か「1」、重みが「実数」、出力が「0」か「1」となります。

パーセプトロン

心理学者のドナルド・ヘッブが、1949年に学習や記憶を進めるのは集中力や根性でなくシナプス(神経細胞同士の接合部)であると生理学的な提起をしました。「ヘッブの法則」というもので、シナプス(神経細胞同士の接合部)は、よく使うものほど強化され使わなくなると徐々に結合が弱くなります。

このヘップの法則を形式ニューロンのような神経回路モデルにあてはめることができないかということから考え出されたのが「パーセプトロン」となります。

単純パーセプトロン

f:id:Yaju3D:20170528193545p:plain
1958年にローゼンブラットが提案したパーセプトロンは形式ニューロンを発展させ、いくつか並列に組み合わせてから出力ニューロンで束ねるという2層(入力層と出力層)の構造をとることで、入力と出力のペアを学習することを示しました。

形式ニューロンと違う点としては、入力は「実数」であることと学習という過程があります。
トレーニングデータを複数を用意することによって、間違った答えを出力した場合、ある計算式に従って重みやバイアスの値を変化させます。
f:id:Yaju3D:20170604230608p:plain

基本的には線型分離可能な問題しか解くことができない(ミンスキーとパパートにより指摘)という欠点があります。

余談ですが、ローゼンブラットが提案したパーセプトロン非線形分離問題も扱える3層でしたが、その後の研究は中間層を省いた2層による単純パーセプトロンの研究が進んだため、ミンスキーとパパートの共著「パーセプトロン」(1969年出版)による批判も単純パーセプトロンだけに当てはまるものだったのです。パーセプトロンの熱は一気に冷めてしまい、パーセプトロンは線形分離可能問題しか扱えないというレッテルが貼られたのでした。

多層パーセプトロン

世の中の解きたい問題は大抵パーセプトロン(線型分離可能な問題)では解けないタイプの問題であったため、パーセプトロンの研究は廃れていきました。それ以降は人工知能の研究は学習型ではなく、知識を扱うプロダクションルール型に移行していった。 しかし、約15年後の1986年に認知心理学者ラメルハートが再発見した3層モデルのバックプロパゲーション(誤差逆伝播学習法)を発表したことで、再び脚光を浴びたのである。
再発見とは、1967年に甘利俊一先生が隠れ層のある3層以上の計算モデルを既に発表していた。
f:id:Yaju3D:20170528195430p:plain

単純パーセプトロンに真ん中の層を追加しました。この層を隠れ層(hidden layer)または中間層と呼びます。実はこれだけで線形分離可能な問題しか解けないという欠点を解決できたのです。
単純パーセプトロンでは重みを調整するのが1層で簡単だったのですが、今回は2層でそれぞれの層の調整するのが難しという問題がありました。しかし、誤差逆伝搬(所望の出力結果になるよう誤差を出力層から入力層に向かった逆方向に重みを調整するもの)と呼ばれる方法を使うこと良い重みの調整方法を手に入れることができたため、3層構造でも学習可能になったのです。

f:id:Yaju3D:20170528222639p:plain
再びニューラルネットワークのブームが訪れ、あらゆるものに応用が試みられて3層より層を深くして学習効果を上げることも試されました。 しかし、バックプロパゲーションは4層以上にすると満足な精度が得られませんでした。
バックプロパゲーションを多層にすると勾配消失と過学習が発生することで、うまく学習できなくなるのです。

当時は、学習時間が非常にかかることや過学習を防ぐための理論がなく、サポートベクターマシン(SVM)など他の手法が注目されていたこともあり、再びニューラルネットワークは冬の時代を迎えたのでした。

ディープラーニング

冬の時代にも着々と進められた技術的進歩の中で、2006年にカナダのトロント大学のジェフリー・ヒントン教授が「ディープビリーフネットワーク」を発表した。これは層が深くなっても学習できていた。
f:id:Yaju3D:20170529004111p:plain
従来のニューラルネットワーク(左図)はパラメーターの更新を4層以上一度にやっていたが、ディープビリーフネットワーク(右図)では層毎に学習が完了してから次の層を付け足したことにある。1層が終わってから2層目と順に学習し、全ての学習が終わってから全層を結合してバックプロパゲーションをかけたのである。
2006年にヒントン教授の論文では層が深いニューラルネットを総称して「ディープネットワーク」と呼び、2007年にアンドリュー・ング氏が論文で「高次元データの階層的な表現の学習」に「ディープラーニング」という言葉を用いた。

2012年にアンドリュー・ング氏が畳込みニューラルネットワーク(CNN)で1000万本のYouTubeビデオ画像を学習し、ディープラーニングが学習した猫の画像を発表した。以降、ディープラーニングは爆発的なブームとなり、全てのものに応用が試みられるようになったのである。 f:id:Yaju3D:20170604203231p:plain f:id:Yaju3D:20170529010051p:plain

現在のディープラーニングには「畳込みニューラルネットワーク」、「再起型ニューラルネットワーク」、「積層自己符号化器」、「ディープビリーフネットワーク」、「ディープボルツマンマシン」など多くの種類があります。

畳込みニューラルネットワーク(CNN)

現在、画像分類の分野で主流となっているのは「畳込みニューラルネットワーク(CNN)」です。この畳込みニューラルネットワークは、福島邦彦先生の「ネオコグニトロン」が元になっています。

f:id:Yaju3D:20170529013306p:plain
畳込みニューラルネットワークは、その名の通り畳込み(コンボリューション:Convolution)を行って情報を圧縮していくネットワークで、5層(入力層、畳込み層、プーリング層、全結合層、出力層)から構成されています。畳込み層とプーリング層は複数繰り返して深い層を形成し、その後の全結合層も同様に何層か続きます。

畳込み層では、カーネルという小さな正方形をフィルターとして適用しながら、入力された画像をより小さい画像に変換します。
f:id:Yaju3D:20170604222728p:plain f:id:Yaju3D:20160417025404g:plain
引用元:http://deeplearning.stanford.edu/wiki/index.php/Feature_extraction_using_convolution

プーリング層では、畳込み層から出力された特徴マップを縮小します。最大値プーリングの場合、注目領域の最大値を特徴マップの値とします。
f:id:Yaju3D:20170611164450p:plain
引用元: http://cs231n.github.io/convolutional-networks/#pool

画像認識での話ですが、プーリングは位置と回転に不変性を与えます。ある領域についてプーリングを行うと、画像が数ピクセルだけ移動や回転をしてもその出力はほぼ同じになります。それは最大プーリングが、微妙なピクセルの違いを無視して同じ値を抽出してきてくれるからです。
自然言語処理における畳み込みニューラルネットワークを理解する

浅いレイヤーは単純なパーツ(小さい部品より具体的なもの)を学習、深いレイヤーはパーツを組み合わせた全体の学習を行います。
f:id:Yaju3D:20170604205436p:plain

qiita.com

ネオコグニトロン

1979年に福島邦彦先生が後のディープラーニング計算に使われる「ネオコグニトロン」を発表した。現在のパターン認識を行うディープラーニングの構造は、ネオコグニトロンそのものであり、S細胞をコンボリューション、C細胞をプーリングと呼んでいるだけである。 ディープラーニングとの違いは学習の仕方で、ディープラーニングバックプロパゲーションを採用しており、ネオコグニトロンは新しい入力パターンに反応する細胞がないときは新しく細胞を加えるという「add-if silent」学習則を採用している。
ネオコグニトロンは当初特許出願されていたものの、予算が厳しく特許権を放棄した。特許権を継続していれば今頃は莫大な収入を得ていたかも知れませんが、特許権が無いことでディープラーニングが開花したのかも。

パーセプトロンと論理演算

人間の脳は神経細胞(ニューロン)のネットワークで構成されています。この最小単位の神経細胞(ニューロン)を模倣したモデルがパーセプトロンとなります。このモデルは入力値が閾値を超えた場合に「1」を出力(この場合を「ニューロンが発火する」と表現)し、そうでない場合は「0」を出力します。このように入力値から出力値を生むまでの計算を活性化関数と呼びます。

活性化関数

活性化関数とはニューロンが入力から出力を生むまでの計算のことで、活性化関数には非線形関数が用いられます。線形の活性化関数では非線形分離できないためです。
パーセプトロンでは活性化関数にステップ関数(入力に対してある閾値を境に階段(ステップ)のように出力が1か0か決まる)を使っていた。
f:id:Yaju3D:20170603024904p:plain

ニューラルネットワークは学習をする際に微分を含む計算を行うため、微分するとゼロになるステップ関数は都合が悪いことからシグモイド曲線(sigmoid)などが使用されている。

シグモイド曲線は入力した値を0から1の間に収めてくれる関数の1つです。多くの自然界に存在する事柄は、このようなS字曲線を取ります。欠点として1に近づくほど1そのものにならない性質があります。
f:id:Yaju3D:20170602002203p:plain

論理演算

コンピュータの演算には四則演算(+、-、×、÷)のほかに論理演算(AND、OR、NOT、XOR)があります。論理演算とは、2つ以上の1または0入力値に対して、1つの演算結果(1または0)を出力する演算のことです。
論理演算と四則演算の大きな違いは、論理演算が2進数の1桁(=1bit)を対象としていることと、演算結果が決して桁上がりしないことです。コンピュータは、内部的にすべてのデータを2進数で取り扱っているからこそ、コンピュータの世界では論理演算が重要なのです。

清水亮著「はじめての深層学習(ディープラーニング)プログラミング」の「2.2 論理演算を学習をさせてみる」として、Chainerを使用して論理演算の「AND」と「OR」と「NAND」および「XOR」を学習させています。
※初版本を購入したため、ソースリストにも誤りがありました。参照:お詫びと訂正

前回の記事「Chainer ver2.xをWindowsにインストールしてみた」でChainerを入れたこともあって、そのまま利用しています。tensorflow版は「tensorflow 論理演算」で検索してみて下さい。

仕組みそのものを知りたい方は「ゼロから作るDeep Learning」の本が「Python + numpy」のみで説明しています。参照:ゼロから作るDeep Learning 2章「パーセプトロン」

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable, optimizers,Chain

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            l1 = L.Linear(2, 1),
            )
    def __call__(self, x):
        # h = self.l1(x)
        # sigmoid function
        h = F.sigmoid(self.l1(x))
        return h
    
model = Model()
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)

x = Variable(np.array([[0,0],[0,1],[1,0],[1,1]], dtype=np.float32))
# OR
t = Variable(np.array([[0],[1],[1],[1]], dtype=np.float32))
# AND
#t = Variable(np.array([[0],[0],[0],[1]], dtype=np.float32))
# NAND
#t = Variable(np.array([[1],[1],[1],[0]], dtype=np.float32))

for i in range(0, 3000):
    optimizer.zero_grads()
    y = model(x)
    loss = F.mean_squared_error(y, t)
    loss.backward()
    optimizer.update()
    
    print("loss:", loss.data)

print(y.data)
#print(y.data.round())

「OR」の実行結果 四捨五入すれば上から[ 0, 1, 1, 1]となります。

︙
loss: 0.01094332616776228
loss: 0.01093930285423994
[[ 0.15879413]
 [ 0.90393192]
 [ 0.90352207]
 [ 0.99786234]]

このソースコードでは「OR」が実行されるようになっています。「AND」または「NAND」を実行するには対象コメントを外して、他のところは逆にコメントを付けてください。あと、急に「NAND」が出てきましたが何故かは後述します。

f:id:Yaju3D:20170601010201p:plain f:id:Yaju3D:20170601010218p:plain f:id:Yaju3D:20170601010235p:plain

この中に「XOR」が無いのには理由があります。上図が示すように「OR」、「AND」、「NAND」については1本の判別直線で表現することが出来ます。つまり単純パーセプトロンで線型分離可能となっているわけです。

f:id:Yaju3D:20170601011628p:plain

ところが「XOR」の場合は非線形(もしくは2本の判別直線)にしないと判別することが出来ません。 これを判別できるようにするには、単純パーセプトロンの中間層(隠れ層)を追加して多層パープセトロンにすればいいのです。
f:id:Yaju3D:20170605010116p:plain

修正したのは上側部分で変数l2の追加と変数tをXORに変更したくらいです。

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable, optimizers,Chain

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            l1 = L.Linear(2, 2),
            l2 = L.Linear(2, 1),
            )
    def __call__(self, x):
        # sigmoid function
        h = F.sigmoid(self.l1(x))
        return self.l2(h)
    
model = Model()
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)

x = Variable(np.array([[0,0],[0,1],[1,0],[1,1]], dtype=np.float32))
# XOR
t = Variable(np.array([[0],[1],[1],[0]], dtype=np.float32))

for i in range(0, 3000):
    optimizer.zero_grads()
    y = model(x)
    loss = F.mean_squared_error(y, t)
    loss.backward()
    optimizer.update()
    
    print("loss:", loss.data)

print(y.data)
#print(y.data.round())

「XOR」の実行結果 四捨五入すれば上から[ 0, 1, 1, 0]となります。

︙
loss: 0.010527125559747219
loss: 0.01044147927314043
[[ 0.05130053]
 [ 0.8998214 ]
 [ 0.89989913]
 [ 0.13812399]]

NANDについて

余談ではあるのですが、「NAND」は「NOT + AND」の組合せたもので[ 1, 1, 1, 0]となります。
ソフトウェア側だとだから何だと思われるのですが、ハードウェア側の論理回路から見ると、すべての論理演算(OR,AND,NOT,XOR)はNAND回路を組み合わせれば出来てしまうのです。同様に「NOR」でもすべての論理演算が作れます。

詳しくは下記サイトを見て下さい。

最後に

脳を真似たモデルが最終的には凄い結果を出せるようになったわけで、生物って凄いなーとつくづく思います。

人間の身体全体が60兆個の細胞でできている中で、脳細胞は140億個と言われています。
PCのメモリ容量が2GB(160億ビット)として2値(0か1)なのでデータ量は2160億通り、脳のニューロンモデルが2値(発火しないか発火する)とすると2140億通りとすると、もう充分ではないかと思えるわけですが、実際はグリア細胞というニューロン細胞以外の神経細胞のために出力はディジタルではなくアナログ信号に近いとのことです。

生物は奥が深い。

この資料は良かったです。ディープラーニングが人工ニューロンから計算グラフに変わってきている。

参照

Chainer ver2.xをWindowsにインストールしてみた

はじめに

2017/02/23に「Chainer」の大幅アップデートが公開されました。
気になっていたサンプルプログラムがChainer用だったこともあり、インストールしてみました。
Anacondaを使用しているのでパッケージ管理システム(conda)によりChainer用環境で作成しています。

環境

Python数値計算環境「Anaconda」のインストール

下記サイトを参考にしてください。自分は、「C:\Anaconda3」をインストール先にしました。 yaju3d.hatenablog.jp

AnacondaのChainer用環境を作成

Anaconda3 (64-bit)メニュー配下から「Anaconda Prompt」を選択します。 Chainer用環境を作成するため、コマンドプロンプト上で下記のコマンドを入力します。
名前は「chainenv」にしました。envは環境(environment)の略称 ※(ユーザー)のところは各自違います。

(C:\Anaconda3) C:\Users\(ユーザー)> conda create --name=chainenv python=3.5

環境が作成されていきます、途中のProceedの確認は「y」を入力します。

Fetching package metadata .........
Solving package specifications: ..........

Package plan for installation in environment C:\Anaconda3\envs\chainenv:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    python-3.5.3               |                0        30.8 MB

The following NEW packages will be INSTALLED:

    pip:            9.0.1-py35_1
    python:         3.5.3-0
    setuptools:     27.2.0-py35_1
    vs2015_runtime: 14.0.25123-0
    wheel:          0.29.0-py35_0

Proceed ([y]/n)? y

Fetching packages ...
python-3.5.3-0 100% |###############################| Time: 0:00:35 906.77 kB/s
Extracting packages ...
[      COMPLETE      ]|##################################################| 100%
Linking packages ...
[      COMPLETE      ]|##################################################| 100%
#
# To activate this environment, use:
# > activate chainenv
#
# To deactivate this environment, use:
# > deactivate chainenv
#
# * for power-users using bash, you must source
#

以下のコマンドで作った環境の一覧を表示できます。

(C:\Anaconda3) C:\Users\(ユーザー)>conda info -e
# conda environments:
#
chainenv                 C:\Anaconda3\envs\chainenv
tensorenv                C:\Anaconda3\envs\tensorenv
tensorenv2               C:\Anaconda3\envs\tensorenv2
root                  *  C:\Anaconda3

作成した環境を有効にするには下記のコマンドを入力します。
Windows版はsourceコマンドが無いため、activateからとなります。

(C:\Anaconda3) C:\Users\(ユーザー)>activate chainenv

これで先頭に「(chainenv)」が付くようになります。

(chainenv) C:\Users\(ユーザー)>

ちなみに作成した環境から戻るには下記のコマンドを入力します。

(chainenv) C:\Users\(ユーザー)> deactivate

Chainer ver2.xのインストール

※chainer 2.0.0a1はアルファ版なので –preを入れないとchainer1.21.0が入ります。

(chainenv) C:\Users\(ユーザー)>pip install chainer --pre
Collecting chainer
  Downloading chainer-2.0.0a1.tar.gz (260kB)
    100% |################################| 266kB 426kB/s
Requirement already satisfied: filelock in c:\anaconda3\lib\site-packages (from chainer)
Requirement already satisfied: nose in c:\anaconda3\lib\site-packages (from chainer)
Requirement already satisfied: numpy>=1.9.0 in c:\anaconda3\lib\site-packages (from chainer)
Collecting protobuf (from chainer)
  Using cached protobuf-3.2.0-py2.py3-none-any.whl
Requirement already satisfied: six>=1.9.0 in c:\anaconda3\lib\site-packages (from chainer)
Requirement already satisfied: setuptools in c:\anaconda3\envs\chainenv\lib\site-packages\setuptools-27.2.0-py3.5.egg (from protobuf->chainer)
Building wheels for collected packages: chainer
  Running setup.py bdist_wheel for chainer ... done
  Stored in directory: C:\Users\hirap_000\AppData\Local\pip\Cache\wheels\e6\9a\0b\c4e3932853d1c6837b9528f7a98a74e5e22a9fafb4f00376d4
Successfully built chainer
Installing collected packages: protobuf, chainer
Successfully installed chainer-2.0.0a1 protobuf-3.2.0

Jupyter ipykernelのインストール

rootにJupyter Notebookが入っているんだから、Jupyter Notebookのインストール不要にならないかと調べてみましたが、結論からするとインストールが必要と思われます。
インストールしないで下記サイトで環境を切り替えようとしましたが、そもそも「chainenv」が表示されませんでした。

qiita.com

Jupyterの設定ファイル生成

rootに戻ってから実行してください。

jupyter notebook --generate-config

C:\Users(ユーザー)\.jupyterフォルダに「jupyter_notebook_config.py」という設定ファイルが生成されます。
jupyter_notebook_config.pyの末尾に下記を追加します。自分の場合はインストール先環境フォルダ(C:/Anaconda3/envs/)を指定しています。

c.EnvironmentKernelSpecManager.conda_env_dirs = [ 'C:/Anaconda3/envs/' ]

今回はJupyter Notebookのインストールの代わりに、下記サイトを参考にJupyter Notebookのカーネルのみをインストールしました。
参照:Python2, Python3 を切り替えて jupyter notebook を使う|熱血エンジニアのブログ

(C:\Anaconda3) C:\Users\(ユーザー)>activate chainenv
(chainenv) C:\Users\(ユーザー)>conda install jupyter ipykernel

Jupyterの起動

(tensorenv) C:\Users\(ユーザー)>jupyter notebook

Jupyter上でchainenvが表示され切り替え出来るようになっています。 f:id:Yaju3D:20170322015143p:plain

動作テスト

動作テストとして下記サイトの実験用のソースコードを動かしてみました。古いバージョンのChainerで作られているため、2点修正しています。
1. FunctionSetをChainに変更
2. model.collect_parameters() を modelのみに変更 qiita.com

#!/usr/bin/env python
# coding: utf-8

__author__ = 'k_morishita'

"""
N bit の 2進数で表される 2**N 個の数字を 2**N個のOutputのどれかを1にするということを学習させる。
"""

import numpy as np
from chainer import cuda, Function, Chain, gradient_check, Variable, optimizers
import chainer.functions as F


def forward(model, x_data, y_data):
    x = Variable(x_data)
    t = Variable(y_data)
    if hasattr(model, "l2"):
        h1 = model.l1(x)
        y = model.l2(h1)
    else:
        y = model.l1(x)
    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

def generate_training_cases(n_bit):
    x_data = []
    t_data = []
    output_len = 2**n_bit
    for i in range(output_len):
        x_data.append(list((int(x) for x in ("0"*n_bit + bin(i)[2:])[-n_bit:])))
        t_data.append(i)
    return np.array(x_data, dtype=np.float32), np.array(t_data, dtype=np.int32)

def main(n_bit, h1_size):
    if h1_size > 0:
        model = Chain(
            l1=F.Linear(n_bit, h1_size),
            l2=F.Linear(h1_size, 2**n_bit)
        )
    else:
        model = Chain(
            l1=F.Linear(n_bit, 2**n_bit)
        )
    optimizer = optimizers.SGD()
    optimizer.setup(model)
    x_data, t_data = generate_training_cases(n_bit)
    for epoch in range(10000):
        optimizer.zero_grads()
        loss, accuracy = forward(model, x_data, t_data)
        loss.backward()
        if epoch % 100 == 0:
            print("epoch: %s, loss: %s, accuracy: %s" % (epoch, loss.data, accuracy.data))
        if accuracy.data == 1:
            break
        optimizer.update()
    print("epoch: %s, loss: %s, accuracy: %s" % (epoch, loss.data, accuracy.data))
    return epoch, accuracy.data

if __name__ == '__main__':
    fp = open("result.txt", "w")
    fp.write("N\tH1\tepoch\taccuracy\n")
    for n_bit in range(4, 5):
        for h1_size in [0, n_bit, n_bit * 2, n_bit * 4, 2**n_bit]:
            epoch, accuracy = main(n_bit, h1_size)
            fp.write("%s\t%s\t%s\t%s\n" % (n_bit, h1_size, epoch, accuracy))

実行結果

epoch: 0, loss: 3.287376642227173, accuracy: 0.125
epoch: 100, loss: 3.181671142578125, accuracy: 0.125
︙
epoch: 4167, loss: 1.5223073959350586, accuracy: 1.0
epoch: 0, loss: 3.0385913848876953, accuracy: 0.0625
epoch: 100, loss: 2.8190338611602783, accuracy: 0.0
︙
epoch: 2538, loss: 0.8443756103515625, accuracy: 1.0
epoch: 0, loss: 2.8999969959259033, accuracy: 0.125
epoch: 100, loss: 2.708193063735962, accuracy: 0.1875
︙
epoch: 1319, loss: 1.2255289554595947, accuracy: 1.0
epoch: 0, loss: 3.109937906265259, accuracy: 0.0625
epoch: 100, loss: 2.705632209777832, accuracy: 0.125
︙
epoch: 1130, loss: 1.066486120223999, accuracy: 1.0
epoch: 0, loss: 2.917494773864746, accuracy: 0.125
epoch: 100, loss: 2.641525983810425, accuracy: 0.125
︙
epoch: 981, loss: 1.286677360534668, accuracy: 1.0

最後に

Chainerも公開されているプログラムが多いので、これでいろいろ試してみます。