ニューラルネットワーク
ディープラーニングを学ぶ上で最も基礎となる概念がこのニューラルネットワークだ。
これは、脳のニューロンとかシナプスとかをモデルにできたものだそうだ。
まあ、その辺はここではあまり触れないことにする。Wikipediaとかを見れば良いと思う。
ニューラルネットワークを調べると最も出てくるのが以下の図だろう。
この図の各円をユニット(またはニューロン)とよぶ。
最も左側のユニットに入力を入れ、最も右側のユニットからを取り出す。
そして、真ん中の矢印やユニットがに相当する。
この図を見てみるとわかるように、縦に四列、ユニットが並んでいる。
このようなニューラルネットワークの各列を層と呼ぶ。
最も左の層が入力層、右の層が出力層。それ以外の真ん中にある二列の層は中間層(隠れ層)と呼ぶ。
また、各矢印は脳のシナプスに相当する。
よく見ると、各層のユニットが、次の層のすべてのユニットがシナプスでつながっていることがわかる。
このような層を全結合層と呼ぶ。
実際にはこんなに綺麗に矢印を引かなければならないわけではなく、ユニット同士を複雑に絡ませた矢印を引いたネットワークでもよい(例えば中間層のものを再帰させてループさせたりとか…)。
ただ、上のような全結合層は計算も説明も楽なので、最初はこのような全結合層で説明していこう。
さて、上の図を例に定式化していこう。
入力層のユニットは三つ、出力層のユニットも三つなので、入力、出力は共に3次元のベクトル、もしくはの行列であるといえる。
これを次のように定義する。
,
この入力・出力はニューラルネットワーク全体のものである。
各ユニットにもそれぞれ入力と出力がある。
これを、とという文字を当てる。ここは文献によって結構違う文字が多いので混乱しないように。
これも、上の,と同じように、添字をつけて分類することにする。
さらに、層を区別するために例えば第l層の場合文字の右上にのように添える。
例えば、層の番目のユニットの入力はとなる。
入力、出力と同様に層におけるユニットの入力もベクトル表記として、上の図のユニットを表現しよう。
, , ,
出力も同様である。
ここで、入力、出力である。
各ユニットの入力は活性化関数を通して出力へ向かう。この活性化関数がどんな形をしているのかはとりあえず置いといて、これは次式で表せる。
また、各ユニットの出力から、重みを乗じてから、次の層の入力へと向かう。つまり、以下のようになる。
この重みはユニットの出力から次の層のユニットの入力に結ばれる矢印に設定される。
教師あり学習のディープラーニングでは、このを上手いこと設定(最適化)することが目的である。
以上2つは、ベクトルと行列表記で次のように表現できる。
なお、この2つ目の式にはバイアスを足すことが多い。つまり、次式のように表すことができる。
活性化関数はいろいろな関数が選ばれるが、とりあえず、以下の4つを紹介する。
シグモイド関数:
双曲線正接関数:
ReLU:
恒等関数:
これで大体のニューラルネットワークと、順伝播は説明終わった。
簡単にプログラムを作ってみよう
まず、Layerクラスをつくろう。
nUnitにその層のユニット数を、actFuncにはその層の活性化関数を渡す。
ユニットの入力、出力の初期値は0とする。
class Layer(object): def __init__(self,nUnit,actFunc='identity'): self.nUnit =nUnit self.inUnit =np.zeros((nUnit)) #ユニットの入力u self.outUnit =np.zeros((nUnit)) #ユニットの出力v self.actFunc =actFunc def activate(self): #活性化 if self.actFunc == "sigmoid": #シグモイド関数 self.outUnit = 1/(1+np.exp(-self.inUnit)) elif self.actFunc == "tanh": #双曲線正接関数 self.outUnit = np.tanh(self.inUnit) elif self.actFunc == "ReLU": #ReLU self.outUnit = np.where(self.inUnit <= 0.0, 0.0, self.inUnit) else: #恒等関数 self.outUnit = self.inUnit
次に、Weightクラスを作る。
今後説明するが、重みは全て同じ値にするのは良くないことがわかっている。
そのため、重みの初期値はランダムな値にし、バイアスは0とする。
引数としては、まず、nUnit1は出力のユニット数、nUnit2は入力のユニット数である。
また、randomWidthは重みの初期値の範囲である。
class Weight(object): def __init__(self,nUnit1,nUnit2,randomWidth = 1): self.weight = np.random.rand(nUnit2,nUnit1)*randomWidth self.bias = np.zeros((nUnit2))
最後に、上の2つのクラスをまとめて、一つのニューラルネットワークを作ろう。
引数については、nUnitsが各層のユニット数。actFuncsは各層の活性化関数を与える。
また、calcForwardPropagation関数では、順伝播の計算をする。
この関数のinVecは最初の入力とする。
N_UNITS=[3,4,4,3] ACT_FUNCS=['identity','ReLU','ReLU','identity'] class NeuralNetwork(object): def __init__(self,nUnits=N_UNITS, actFuncs=ACT_FUNCS, randomWidth=1): self.nLayer =len(nUnits) self.nUnits=nUnits self.actFuncs=actFuncs self.layers =[Layer (self.nUnits[i], self.actFuncs[i]) for i in range(self.nLayer) ] self.weights =[Weight(self.nUnits[i], self.nUnits[i+1],randomWidth) for i in range(self.nLayer-1)] def calcForwardPropagation(self,inVec): # 順伝播 self.layers[0].inUnit=inVec for i in range(self.nLayer - 1): self.layers[i].activate() self.layers[i+1].inUnit = np.dot(self.weights[i].weight,self.layers[i].outUnit)+self.weights[i].bias self.layers[-1].activate() return self.layers[-1].outUnit
これで、ニューラルネットワークは完成だ。
完成したものは下に上げる。
1_ForwardPropagation.py - Google ドライブ
適当に入力[1,1,1]を与えると、[6.49604473 6.02350685 5.87241463]と帰ってきた。
重みはランダムだからやるたびに値は変わるし、この値自体に意味はまったくない。
次は誤差(損失)関数について話していこう