データ分析ガチ勉強アドベントカレンダー 16日目。
今日からは少しディープラーニングの勉強。
ここ数年間、深層学習用ライブラリも猛烈に整備され、誰でも簡単にディープラーニングを使えるようになりました。
その一方で、整備されすぎて、魔法の箱だという認識も多いですよね。 けれど、深層学習と言えど、しているのはほとんど線形代数と微積分を組み合わせた数値計算です。
だったら自分で作れるのでは? というわけで、仕組みを理解するために、0からスクラッチで作ることにしました。
尚、勉強にはプロフェッショナルシリーズの深層学習を利用しています。
爆速で技術が進む深層学習界隈では少々obsoleteかもしれませんが*1、きちんと基礎の基礎を知るにはいい本だと思います。詳しい計算方法を学びたい人は、どうぞ。(線形代数と偏微分の知識が必要です。)
- 作者: 岡谷貴之
- 出版社/メーカー: 講談社
- 発売日: 2015/04/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (13件) を見る
作るニューラルネットワーク
下図のような基本的な3層ニューラルネットワーク。
- 全結合
- 活性化関数 : 隠れ層 : Sigmoid関数, 出力層 : ソフトマックス関数
- ミニバッチ処理を採用
の構成で作ってみる
ニューラルネットの設計
ニューラルネットには下記の要素が必要。
ネットワーク構成 : 何段構成にするとだとか、どういう計算を施すか解か、どこの層をつなぐとかを考えなければならない。今回は3段の単純なネットワークだけど、実際は用途によって様々な構成が出来る。Network Zooなどが、ネットワーク構成の参考になる。けど、今でも試行錯誤
- 重みのアップデート方法 : ニューラルネットの学習を進める上で重要な要素。
活性化関数も、試行錯誤で作られている事が多い。 近年よく使われるのはReLU等。活性化関数に関しては、以前記事にしたことがあるのでそちらを参考に。
順伝搬計算
入力(ベクトル)から、線形変換と活性化関数による非線形計算をしながら出力層の値を取り出す。 最終的に各値のスコアが出てくるので、回帰ならそのまま用いればいいし、分類ならスコアが最も高いものを選ぶ。
逆伝搬計算
出力値から、誤差を出すことが出来る。その誤差を元にして、各層の重みを調整していく。 このためには微分の知識が必要になってくる。出力層から入力層計算を進めるため、誤差逆伝搬と呼ばれる。 誤差にはクロスエントロピー関数を用いる事が多い
重みの更新
逆誤差伝搬を行うことで、誤差からの各重みが出せる。最もシンプルなのは、単に誤差を一回の微分値で調整する方法(勾配降下法)。確率的勾配法(SGD)などもこれを元にしている。 この更新方法も様々に考えられていて、もう少し高度なモメンタムや、学習率を自動的に更新してくれるAdam, AdaGradなどもある。
(重みの更新式は学習率)
実装
以上を踏まえて、実装をしてみた。いつものように
ネットワークの設計
必要なネットワークの構成と、活性化関数、順伝搬計算、逆伝搬計算を、実装
class NN: def __init__(self, num_input, num_hidden, num_output, learning_rate): self.num_input = num_input self.num_hidden = num_hidden self.num_output = num_output self.learning_rate = learning_rate self.w_input2hidden = np.random.random((self.num_hidden, self.num_input)) self.w_hidden2output = np.random.random((self.num_output, self.num_hidden)) self.b_input2hidden = np.ones((self.num_hidden)) self.b_hidden2output = np.ones((self.num_output)) ##活性化関数(シグモイド関数) def activate_func(self, x): return 1/(1+np.exp(-x)) ##活性化関数の微分 def dactivate_func(self,x): return self.activate_func(x)*(1-self.activate_func(x)) ##ソフトマックス関数 def softmax_func(self,x): C = x.max() f = np.exp(x-C)/np.exp(x-C).sum() return f ##順伝播計算 def forward_propagation(self, x): u_hidden = np.dot(self.w_input2hidden, x) + self.b_input2hidden z_hidden = self.activate_func(u_hidden) u_output = np.dot(self.w_hidden2output, z_hidden) + self.b_hidden2output z_output = self.softmax_func(u_output) return u_hidden, u_output, z_hidden, z_output ##逆伝播でdeltaを求める def backward_propagation(self,t,u_hidden,z_output): t_vec = np.zeros(len(z_output)) t_vec[t] = 1 delta_output = z_output - t_vec delta_hidden = np.dot(delta_output, self.w_hidden2output * self.dactivate_func(u_hidden)) return delta_hidden, delta_output ##wに関するgradient def calc_gradient(self,delta,z): dW = np.zeros((len(delta), len(z))) for i in range(len(delta)): for j in range(len(z)): dW[i][j] = delta[i] * z[j] return dW # update(SGD) def update_weight(self,w0,gradE): return w0 - self.learning_rate*gradE
学習
ランダムにデータを選ぶことで、確率的勾配法にしてみた。
def train(nn, iteration,savefig=False): epoch = 0 for epoch in range(iteration+1): grad_i2h = 0 grad_h2o = 0 gradbias_i2h = 0 gradbias_h2o = 0 rand = randint(0,len(data),100) for r in rand: u_hidden, u_output, z_hidden, z_output = nn.forward_propagation(data[r]) delta_hidden, delta_output = nn.backward_propagation(target[r], u_hidden, z_output) grad_i2h += nn.calc_gradient(delta_hidden, data[r]) grad_h2o += nn.calc_gradient(delta_output, z_hidden) gradbias_i2h += delta_hidden gradbias_h2o += delta_output nn.w_input2hidden = nn.update_weight(nn.w_input2hidden, grad_i2h / len(rand)) nn.w_hidden2output = nn.update_weight(nn.w_hidden2output, grad_h2o / len(rand)) nn.b_input2hidden = nn.update_weight(nn.b_input2hidden, gradbias_i2h / len(rand)) nn.b_hidden2output = nn.update_weight(nn.b_hidden2output, gradbias_h2o / len(rand))
実行
sklearnでデータセットを用意し、実際に分類できるかを分離平面で表してみている。 自信度によって色の濃さを調整しているので、学習が進んでいくと、きちんと分類されて、濃くなっていくはず。。。!
#データの用意 num_cls = 5 data,target = make_blobs(n_samples=1000, n_features=2, centers=num_cls) # Neural Netの用意 nn = NN(num_input=2,num_hidden=20,num_output=num_cls,learning_rate=0.2) # 学習 train(nn, iteration=500, savefig=True) plt.figure(figsize=(5,5)) #色の用意 base_color = ["red","blue","green","yellow","cyan", "pink","brown","gray","purple","orange"] colors = [base_color[label] for label in target] # 教師データのプロット plt.scatter(data[:,0],data[:,1],color=colors,alpha=0.5) print("plotting...") xx = np.linspace(-15,15,100) yy = np.linspace(-15,15,100) for xi in xx: for yi in yy: _,_,_,z_output = nn.forward_propagation((xi, yi)) cls = np.argmax(z_output) #softmaxのスコアの最大のインデックス score = np.max(z_output) plt.plot(xi, yi, base_color[cls],marker="x",alpha=s) print(".",end="") plt.xlim=(-15,15) plt.ylim=(-15,15) plt.show() print("finish plotting")
実行結果
以下が、300イテレーション回したときの分離平面の様子である。
また、学習が進んでいく様子もアニメーションにしてみた。(画像が粗くてすみません)
うまく学習が進んでいる様子が見て取れます。
まとめ
今回は実際に作ることで、ニューラルネットの仕組みを理解してみた。 実際に自分で作るとどこで非線形な要素が生まれるのかとか、誤差をどう学習に反映させているのかというのが分かるようになるので、勉強になります。魔法の箱なんかじゃなく、ちゃんとした学習器として理解できたように思います。0空作ればここから柔軟にいろいろと発展させていくことも可能ですしね。
今回は式からプログラムを書き起こしたが、最近は↓のようなスクラッチで書く人のためのとても良い本も売っているので、そちらも参考にしてみてはいかがでしょうか。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
- 作者: 斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (18件) を見る
また、この辺の基礎を知っている人は、正直ライブラリを使ったほうが早いです。 明日からは、深層学習用ライブラリをいくつか触っていきたいと思います。(KerasかPytorch) ではでは!
*1:言うても2-3年前までは最先端