iPX社員によるブログ

iPX社員が"社の動向"から"自身の知見や趣味"、"セミナーなどのおすすめ情報"に至るまで幅広い話題を投下していくブログ。社の雰囲気を感じ取っていただけたら幸いです。

深層学習

初めに

ご無沙汰しております、iPX界の深層学習部(非公式)部長の小川です。
今回はDeep Learning(以下DL)につきまして記そうと思います。
既にありふれている情報で、インターネットを汚す事は大変恐縮してしまいますが、取り組み内容をアピールする場として発信させていただけたらと思います。

これまで道具を使う為の自己研鑽は多く積んできました、そして物体認識や深層強化学習にトライし、一定の結果も残せたと思います。

今後はエッジデバイスから始まり、DLが量産品に導入されるケースも増えてくると思います。
近い未来に問われるであろうDLの品質保証をどう担保していくか。
今一度基礎に立ち返り理論への理解を深める事、それが私に求められる次なる自己研鑽なのであります。

記事方針

記事の内容は書籍を元に構成しております、正確な情報は是非書籍をお手にとっていただけたらと思います(参考書籍の項をご参照ください)。
ここでは新入社員向けに噛み砕けるだけ噛み砕き説明する事に徹します。
よって事実とは異なる点があることを予めご了承願います。
また、私自身も未熟至る為、数式や説明が間違っている可能性が有ります点も併せてご容赦いただけたら幸いです。

記事中のプログラムはpython3で実装しています。

DLの目的

ある関数を近似(まね)することです。
利点としては下記があると考えています。

  • 既知の計算量が膨大な関数を近似することで高速化する
  • 未知の関数を近似することで実装が困難な処理を実現する

これら恩恵を受ける為には道具と使い方を学ぶ必要が有ります。
具体的にはパーセプトロン、活性化関数、多層ネットワークといった枠組みや、順伝搬や誤差逆伝播法などの枠組みの使い方です。

ニューラルネットワーク

ここではニューラルネットワークの構築に必要となるいくつかの枠組みを一つづつ知っていきます。

パーセプトロン

いくつかの入力をまとめて一つの出力を返す枠組みです。
登場人物は下記3名です。

名称 役割
ユニットさん 入力とか出力のあの丸(○)の部分
重みさん 重みさんを良しなに変化させる事が学習
バイアスさん 微調整

数式

u_j = \sum_{i=1}^I w_{ji} x_i + b_j
行列版

u = Wx+b
魚のような記号はΣ(シグマ)で、直訳すると総和です。
プログラムではsum変数に計算結果をループで足す処理に当たります。

pythonプログラムにて行列演算ライブラリのnumpyを用いて表現してみます。

import numpy as np

I = 4
J = 1
x = np.random.rand(I)
W = np.random.rand(J,I)
b = np.random.rand(J)

# for文版
u = .0
for i in range(I):
    u += W[0,i] * x[i]
else:
    u += b[0]
print(u)

# 行列版
u_d = np.dot(W,x)+b
print(u_d) 

numpyの演算を使うととっても楽でした。

活性化関数

パーセプトロンはあくまで複数の入力に重みとバイアスを加えて1つの出力を行う枠組みでした。
出力にある変換を行う活性化関数が知られています。
DLでは非線形関数(1本の直線ではない)を適用することがお決まりです。

名称 特徴
ロジスティック関数さん 範囲が0~1  f(u)=\frac{1}{1+e^{-u}}
双曲線正接関数さん 範囲が-1~1  f(u) = \tanh(u)
正規化線形関数(ReLU)さん マイナスが無い  f(u) = \max(u,0)
恒等写像さん そのまま  f(u) = u

pythonにてグラフ描画ライブラリのmatplotlibを用いて関数の形を可視化してみます。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-5., 5., .1)

def logistic(u):
    return 1/(1+np.exp(-u))

def hyperbolic(u):
    return np.tanh(u)

def relu(u):
    return np.maximum(u,0)

plt.plot(x, logistic(x), label='logistic', linestyle='--')
plt.plot(x, hyperbolic(x), label='hyperbolic', linestyle='--')
plt.plot(x, relu(x), label='relu', linestyle='--')

plt.legend()
plt.xlim(-5.2, 5.2)
plt.ylim(-1.2, 1.2)
plt.title('activation functions')
plt.show()

f:id:ipx-writer:20180521015559p:plain

いずれも確かに直線ではない事と、データ範囲と形に特徴がある事が分かりました。

多層ネットワーク

パーセプトロンと、活性化関数を組み合わせて、ユニットを横にも積んで多層化します。
多層化することでニューラルネットワークの表現力が高まります。
層の上から計算していき、最終的な出力を求める事を順伝搬と呼びます。

ネットワークの作成と順伝搬をpythonで表現してみます。
書籍「ゼロから作るDeep Learning - Pythonで学ぶディープラーニングの理論と実装」の例を参考にしました。

from functools import reduce
import numpy as np
import matplotlib.pyplot as plt

class layer:
    def __init__(self, W, b, f):
        self.W = W
        self.b = b
        self.f = f

def logistic(u):
    return 1/(1+np.exp(-u))

def identity(u):
    return u

layer1 = layer(np.array([[.1, .3, .5], [.2, .4, .6]]), np.array([.1, .2, .3]), logistic)
layer2 = layer(np.array([[.1, .4], [.2, .5], [.3, .6]]), np.array([.1, .2]), logistic)
layer3 = layer(np.array([[.1,.3], [.2, .4]]), np.array([.1, .2]), identity)
network = [layer1, layer2, layer3]

x = np.array([1., .5])
y = reduce(lambda z, l: l.f(np.dot(z, l.W) + l.b), network, x)
print(y)    # output:[ 0.31682708  0.69627909]

1つ目の層は入力層、最後の層は出力層と呼ばれます。
また、各層の出力をzと置き、任意の総数Lと置くと多層ネットワークは下記に一般化出来ます。

 u^{(l+1)} = W^{(l+1)} z^{(l)}+b^{(l+1)}

 z^{(l+1)} = f(u^{(l+1)})

 y≡z^{(L)}


≡(合同)は左辺を右辺で定義する意味です。

以降は書籍「深層学習」に則り、ネットワークのパラメータ全てを成分に持つベクトルwを定義し、下記で表す事とします。

 y(x;w)

学習

ここまでで多層ネットワークを定義し、順伝搬することで出力yを得ることが出来るようになりました。
次に必要な事は多層ネットワークの枠組みを用いて、重みを調整する事、即ち学習です。
任意のデータから適切な重みを調整する為にはいくつか覚えることが必要です。

  • 教師(訓練)データ
  • 誤差
  • 回帰
  • 分類
  • (時間切れ... TBD)

教師(訓練)データ

DLではデータから任意の関数を近似することが目的でした、ここでデータとは入力と出力の組み合わせとなっており、教師データと呼ばれます。

誤差

全ての教師データの入力と出力(答え)と、ニューラルネットワークの出力が一致すれば、学習が完了したということになります。
そこで教師データと、現在のニューラルネットワークがどれだけかけ離れているかを表す指標が誤差と呼ばれます。
つまり学習とは誤差を返す誤差関数の結果が最小とする事です。

回帰

DLでは解きたい問題によって出力が変わります。
回帰問題では連続値の推定が問題設定となります。
例えば、ある入力から適切なモーターのトルク値を出力するような問題は、この回帰問題です。
回帰問題の場合は誤差関数として二乗誤差がよく用いられます。
目標出力(答え)をdと置くと、下記数式で表せます。

 ||d-y(x;w)||^2

全教師データに適用する場合は、1/2を掛ける事で、微分計算の際に二乗と相殺されるようにするのが一般的です。
回帰問題の場合はこの二乗誤差関数のwを最小化することが学習タスクとなります。

 E(w) = \frac{1}{2} \sum_{n=1}^{N}||d_n - y(x_n;w)||^{2}

分類

分類問題では離散値(不連続な数値)の推定となります。
例えば、手書きした0~9の文字画像から、手書きされた数値が何であるかを推定するような問題は、この分類問題です。

二値分類

先ずは分類の中でも2つに分ける二値分類から理解していきます。
簡単の為、出力ユニットは1つで、0-1の値が0.5から上下どちらにあるかで二値の内どちらかを表現することにします。
∈は右辺が左辺に含まれるという意味です。

 d ∈ \{0,1\}

入力xが1であることを表すために、事後確率モデルを用いることが一般的です。
書籍「深層学習」に則り、下記で表すことにします。

 p(d=1|x)

DLでは事後確率をモデル化する為にニューラルネットワークを用います。
ここで2つの波線は近似を意味します。

 p(d=1|x) \approx y(x;w)

1の場合、0の場合も含めて事後確率をモデル化する場合は、べき乗を使うことで、1つの式で表すことが出来ます。
dが1の場合は右辺式中の左因子が、dが0の場合は右因子が計算されます、これはプログラムでいうところの条件演算に似ていますね。

 p(d|x) = p(d=1|x)^{d}p(d=0|x)^{1-d}

入力データxが上記の確率分布を最もよく整合する事を定める為に、最尤推定を行う事を考えます。

最尤推定

尤度(もっともらしさ)を求める考え方です。
尤度関数L(θ)を計算し、出力を最大とするθを推定します。
寄り道になりますが、よく見る例として確率分布が正規分布に従っているという仮定の元、下記の数式が出てきます。

 f(x) = \frac{1}{\sqrt{2\pi\sigma^{2}}} \exp(-\frac{1}{2}\frac{(x-\mu)^{2}}{\sigma^{2}})

複数データの確率分布を考える場合、各データに相関が無い場合は各データが独立同一分布であると言います。
独立である事で、全体の確率は確率密度の積に等しいと考える事が出来ます。
よって下記数式で表せます。
Πは総乗という意味です、Σの掛け算バージョンです。

 P(x_1, x_2, ..., x_n) = \prod_{i=1}^{n} \frac{1}{\sqrt{2\pi\sigma^{2}}} \exp(-\frac{1}{2}\frac{(x-\mu)^{2}}{\sigma^{2}})

正規分布の場合は平均μと、偏差σを求めたい確率変数として、尤度関数を定義します。

 L(\mu,\sigma) = \prod_{i=1}^{n} \frac{1}{\sqrt{2\pi\sigma^{2}}} \exp(-\frac{1}{2}\frac{(x-\mu)^{2}}{\sigma^{2}})

最尤推定ではL(μ,σ)が最大となるような確率変数μ、σを求める事がタスクとなります。

ここまで正規分布を例として、尤度関数の成り立ちを追ってきました。
今回はニューラルネットワークによって事後確率を表現することが目的となります。
よって尤度関数は下記と考えることが出来ます。

 L(w) = \prod_{n=1}^{N} p(d_n|x_n;w)

前項の二値分類より、下記の数式に置き換えて考えることが出来ます。

 L(w) = \prod_{n=1}^{N} \{y(x_n;w)\}^{d_n} \{1 - y(x_n;w)\}^{1-d_n}

ここまでで、二値分類における尤度関数を定義しました。
この尤度関数にもうひと手間加えて、誤差関数を定義していきます。
ここでいきなりlog(対数)が登場します。

単調性

logには単調性という性質があります。
単調性の中で、単調増加と呼ばれる性質は下記数式におけるxが増加した場合にyも増加するという性質です。

 y = f(x)

この性質から、定義した尤度関数にlogを適用しても、尤度関数として成り立つと考えることが出来ます。

 L(w) = \log [\prod_{n=1}^{N} \{y(x_n;w)\}^{d_n} \{1 - y(x_n;w)\}^{1-d_n} ]

次に下記のlogの公式から累乗と総乗を除外することが出来ます。

 \log_a MN = \log_a M + \log_a N

 \log_a M^{k} = k\log_a M

変形して累乗と総乗を除外した尤度関数は下記となります。

 L(w) = \sum_{n=1}^{N} [d_n \log y(x_n;w) + (1-d_n) \log \{1-y(x_n;w)\}^{1-d_n} ]

ここで尤度関数はL(w)を最大にすることが目的でした。
誤差関数では誤差を最小にすることが目的なので、符号を逆転する必要が有ります。

 E(w) = -\sum_{n=1}^{N} [d_n \log y(x_n;w) + (1-d_n) \log \{1-y(x_n;w)\}^{1-d_n} ]

これで二値分類における誤差関数が定義出来ました。

条件付き確率

早速誤差関数を使って学習の為の勉強に入って行きたいところですが、活性化関数には何を選択すれば良いでしょうか?
ロジスティック関数を用いると仮定して、事後確率モデルとマッチしているかを確認してみます。
条件付き確率の公式より、事後確率は下記事前確率で計算されると見なすことが出来ます。

 p(d=1|x) = \frac{p(x,d=1)}{p(x,d=0) + p(x,d=1)}

上記がuのロジスティック関数と同義であることを確認するために、下記の仮定を置きます。
ここで log x は底にネイピア数(e)が省略されているものとします。

 u ≡ \log \frac{p(x,d=1)}{p(x,d=0)}

logの公式より、上記式を変形します

 x = a^{y}

 y = \log_a x

 \frac{p(x,d=1)}{p(x,d=0)} = e^{u}

次に条件付き確率の公式より変形した式を、p(x,d=0)で割り、更に変形していきます。

 \frac{\frac{p(x,d=1)}{p(x,d=0)}} {\frac{p(x,d=0) + p(x,d=1)}{p(x,d=0)}}

約分します。

 \frac{\frac{p(x,d=1)}{p(x,d=0)}} {1+ \frac{p(x,d=1)}{p(x,d=0)}}

logの公式で変形した式を当てはめます。

 \frac{e^{u}} {1+ e^{u}}

さらにeuで割ります。

 \frac{\frac{e^{u}}{e^{u}}} {\frac{1+ e^{u}}{e^{u}}}

約分します。

 \frac{1} {\frac{1}{e^{u}} + 1}

逆数の公式により、式を変形します。

 \frac{1} {1 + e^{-u}}

上記はロジスティック関数さんと同じ式なので、事後確率はuのロジスティック関数だということが分かりました。

 p(d=1|x) = \frac{1} {1 + e^{-u}}

活性化関数にはロジスティック関数を用いれば良さそうです。

最後に

大変申し訳ございません、力尽きました。
実際に二値分類の学習を行うコードまで載せたかったですが、書籍「深層学習」の P17-18 への理解に時間が取られました。
学習を行うためには、微分法やチェーンルール、勾配や、勾配降下法、そして誤差逆伝播法を学ぶ必要が有ります。
次回は上記を載せた上で、学習を実施するpythonプログラムを載せた内容をゴールと定めて、執筆を頑張りたいと思います。

参考にさせていただきました書籍の著者様、わかりやすい記事を公開してくださった皆様に感謝を申し上げます。

参考書籍

参考サイト