※日本ディープラーニング協会のDeep Learning資格試験(E資格)受験に必要な、協会認定の講座プログラム( https://ai999.careers/rabbit/ )がある。
※本プログラム受講に際し、レポート作成はWebやBlogなどで作成する必要があるとのことで、Qiitaで作成することとした。
※個人的な備忘録兼レポート作成用であり、網羅的にカバーするものではありません。ご了承ください。
深層学習前半については、入力層~中間層、活性化関数、出力層、勾配降下法、誤差逆伝搬法、勾配喪失問題、学習率最適化手法、過学習、CNN概念、最新CNNの各項目についてそれぞれ100文字以上で要点をまとめるのに加え、実装演習結果キャプチャーまたはサマリーと考察、確認テストなど自身の考察結果が必要とのことである。
#深層学習の基礎
入力値から出力値に変換する数学モデルにおいて、比較的シンプルな数式(多項式とか)を用いるのではなく、中間層を複数用いることで複雑な関係を数学的にモデル化するもの。関係性が複雑で、非線形性が強くても予測精度の高いモデル構築が可能と期待される。下図にその全体像が示される。
以下に各項目ごとに記載する。ただし、Pythonコードはある程度まとめたものを示す。
###入力層~中間層
入力層$x_i$は入力として与える特徴量である。例えば動物分類問題であれば、体長、体重、足の長さ、耳の大きさ、ひげの本数をそれぞれ$x_1\ldots x_5$、それぞれに対する重み$w_1\ldots w_5$との線形結合にバイアス$b$を加えたものが総入力となる。
###活性化関数
活性化関数$f(u)$は次の層への出力の大きさを決める非線形の関数であり、中間層においてはReLU関数などが、出力層においては恒等写像(回帰問題)、シグモイド関数(2値分類)、ソフトマックス関数(多クラス分類)が用いられる。
###出力層
回帰問題では出力層では入力データに対応する数学モデルによる予測値を出力する。分類問題では入力データが各クラスに属する確率を出力することになる。回帰問題における誤差関数は機械学習における線形回帰など同様に最小二乗和としたり、分類問題においてはクロスエントロピーが一般的に用いられている。
以下に中間層1つの場合の順伝播回帰問題、Pythonコードの例を示す。
import numpy as np
def relu(x):
return np.maximum(0, x) #0かxの大きい方を返す→正のxに対してx、負のxに対して0を返す
# 入力値
x = np.array([2, 3])
# 重み
W1 = np.array([
[0.1, 0.3, 0.5],
[0.2, 0.4, 0.6]
])
W2 = np.array([
[0.1, 0.4],
[0.2, 0.3],
[0.5, 0.3]
])
# バイアス
b1 = np.array([0.1, 0.2, 0.5])
b2 = np.array([1,2])
# 総入力
u1 = np.dot(x, W1) + b1
# 中間層出力
z1 = relu(u1)
# 出力層
u2 = np.dot(z1,W2) + b2
# 出力層の総出力(予測値)
y = u2
# 既知の出力値(目標出力)
d= np.array([2, 4])
# 既知データと予測データとの平均二乗誤差
sq=np.square(d-y)
loss = np.mean(sq)/2
###勾配降下法
勾配降下法は、機械学習レポートのロジスティック回帰の部分にて議論したように解析的に求めるのが難しい逆行列を解く際に用いられるラインサーチ法の一種である(以下参照)。
https://qiita.com/hipearm/items/a5f02945862c2b0833fe
例えば、平均二乗誤差$E=\frac{1}{N}\Sigma (y(\boldsymbol{w}) -d)^2$や最尤推定$E=-\log(L(\boldsymbol{w}))$を最小化する重みとバイアスの値を求めるため, 誤差関数$E(\boldsymbol{w})$をより小さくするアップデートパラメータ$\boldsymbol{w^{(t+1)}}=\boldsymbol{w^{(t)}}-\epsilon \nabla E(\boldsymbol{w})$を探索する。誤差が最小化する方向は勾配$\nabla(E(\boldsymbol{w})) $で与えられるが、その方向へ$E(\boldsymbol{w})$を最小とする点を求めるための係数が必要である。これを学習率$\epsilon$で表現し任意に(あるいは工夫して)設定される。
すべてのデータを用いずに確率的に選択した(毎回異なる)データに対して$\boldsymbol{w^{(t+1)}}=\boldsymbol{w^{(t)}}-\epsilon \nabla E(\boldsymbol{w})$ の計算を行う場合を確率的勾配降下法、一部のまとまったデータを選択する場合はミニバッチ勾配降下法と呼ぶ。また、モデルにその都度データを与えて学習させる方法をオンライン学習と呼ぶ。一部データ(イタレーション毎に異なる)を用いた勾配計算は計算時間の短縮、局所解からの脱出に有効である。
###誤差逆伝搬法
勾配$\nabla(E(\boldsymbol{w})) $はそれぞれの重みやバイアスの微小変化に応じた誤差関数の変化に1次近似(差分近似)できることから、誤差や重みを少しだけ変化させたときの順伝播計算を実施し誤差関数の変化を求めることで得ることが可能である。しかしながら、大規模DNNモデルでは重みやバイアスの数の順伝播計算が最低でも必要で繰り返し計算が増え計算時間が膨大になる。
そこで、微分の連鎖律を活用して出力層に近い方から逐次微分値を求めることで計算時間を圧倒的に短縮できることが可能な誤差逆伝播法が使われている(下図)。例えば、$\frac{\partial E}{\partial w^{(2)}}$を求めるには、出力層に近い方からの偏微分値$\frac{\partial E}{\partial y}$, $\frac{\partial y}{\partial u^{(2)}}$, $\frac{\partial u^{(2)}}{\partial w^{(2)}}$の積を求めることで計算できる。
最適化問題においては勾配をいかに効率的に求めるかといったアイディアが技術開発の核心という印象をうけているが、深層学習においては誤差逆伝搬法(これを適用可能とする中間層での微分可能な、勾配喪失しにくい関数の適用含め)といった核心部分のノウハウが普及実用化に大きく貢献したと思われた。
以下に誤差逆伝播のPythonコード例を示す。
def init_network():
network = {}
network['W1'] = np.array([
[0.1, 0.3, 0.5],
[0.2, 0.4, 0.6]
])
network['W2'] = np.array([
[0.1, 0.4],
[0.2, 0.5],
[0.3, 0.6]
])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['b2'] = np.array([0.1, 0.2])
return network
# 順伝播
def forward(network, x):
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
u1 = np.dot(x, W1) + b1
z1 = relu(u1)
u2 = np.dot(z1, W2) + b2
y = u2
return y, z1
# 誤差逆伝播
def backward(x, d, z1, y):
grad = {}
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
# 出力層でのデルタ
delta2 = y-d #誤差関数をyで微分した結果 dE/dy
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0) # dE/dy * dy/db2=dE/dy*1=delta2
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2) # dE/dy * dy/dW2=delta2*z1
# 中間層でのデルタ
delta1 = np.dot(delta2, W2.T) * np.where(z1>0, 1, 0) #dE/dy * dy/du2 * du/dw
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
return grad
# 訓練データ
x = np.array([[1.0, 5.0]]) # parameter
# 目標出力
d = np.array([[2, 7]]) # data
# 学習率
learning_rate = 0.1
network = init_network()
y, z1 = forward(network, x)
grad = backward(x, d, z1, y)
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key]
###勾配喪失問題
また、微分値を出力側から掛けていくにあたって、どこかで感度が非常に小さい(例えば$\frac{\partial E}{\partial u^{(2)}}$≒0)と、それよりも入力側の偏微分値$\frac{\partial E}{\partial w^{(1)}}$などが0に近くなり重みやバイアスが更新されないといった問題が生じ得る。活性化関数にSigmoid関数を使うと、微分の最大値は$\left. \frac{\partial \sigma(x)}{\partial x} \right|_{x=0}=0.25$で、中間層が多いと必然的に偏微分値が小さくなる。
こうした問題の対応として(そもそも誤差関数に対して重み値の感度が期待されるが、数学モデルに改善の余地がある問題に対して)、活性化関数をReLU関数に変更する(微分値は1か0になる)、活性化関数に応じた重み初期値設定の工夫(重みが均一にならないように、$\sqrt\frac{1}{n}$の標準偏差を使うXavierの初期化→Sigmoid関数、$\sqrt\frac{2}{n}$の標準偏差をもつHe初期化→ReLU関数)、バッチ正規化(ミニバッチ内データの正規化による入力値のコンディショニング)がある。
なお、中間層のノードがたくさんある一般的な深層学習モデルにおいては重みのバリエーションがないと同じ中間層の出力、勾配値、重み更新となり複数ノードが意味をなさなくなると考えられ、重みが均一にならないような初期値の与え方が重要と理解した。
###学習率最適化
学習率$\epsilon$の設定、あるいは、重み更新法においては様々な工夫がある。手法によって誤差関数と重みがどのように変化していくかを比較すると、より速く大局解へ向かう最適な手法の判断が可能。講義では、モメンタム、AdaGrad、RMSProp、Adamと手法が紹介され、一期前の重みや勾配などの高次の情報も加味して収束を速く安定させた工夫と解釈された。
経験的にAdamが良いとのことで、初期値付近で勾配がほとんどない場合でもなめらかに大局解へ進んでいく。
講義を拝聴した範囲では、勾配降下法における学習率の調整というよりも、ニュートン法や共役勾配法のように、勾配降下法より効果的なサーチ手法(Optimizer)の選択と認識した。深層学習にて用いられるこうしたOptimizerは共役勾配法などとくらべて非常にエンジニア的な手法(やってみて良ければ使用する)という印象をうけた。
最適化問題の教科書や学習率最適化に関するWebsiteを参照。
https://www.kyoritsu-pub.co.jp/kenpon/bookDetail/9784320017863
https://qiita.com/m-hayashi/items/dab9d2f61c46df0a3a0a
提供いただいたipynbフォーマットのコードもたいへん参考になるが、以下、ゼロから作るDeep Learningより提供されたコードは、Optimizerをクラス指定し選択しやすい書き方になっていた。
以下参照
参考にSGDとMomentum部分を一部抜粋。Momentumは速度項vと、どの程度減速させるかを決めるパラメータ"momentum"がかかっている。
import numpy as np
class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
###過学習
機械学習や最適化問題一般に共通するが、学習に用いるデータに対して最適化されても、検証データに対する誤差が大きくなる場合は過学習である。入力データが少ないのに自由度が高い、パラメータが多い場合に起こりやすい。
過学習を防ぐ方法として、数学モデルの見直しもあるが、L1正則化(ラッソ回帰), L2正則化(リッジ回帰)、ドロップアウトなどが用いられる。
L1正則化を用いるとスパースな最適モデル(限られた重みがNon-zero)に近づき、L2正則化を用いるとスムースな最適モデル(重みの大小バリエーションが大きくない)に近づく。ドロップアウトは一部のレイヤーのアウトプットを学習時にランダムで0に落とす手法である。
#畳み込みニューラルネットワーク
###CNN概念
音声、画像、動画等の時間、空間等の連続データに対する深層学習手法。畳み込み層やプーリング層を組み合わせて次元のつながりを保つ特徴量の抽出が行われ、出力に近い側にて通常の深層学習と同様の1次元の全結合した処理が実施される。
畳み込み層ではMatrix Kernelを用いたフィルタリング処理を行う。画像のエッジ抽出やスムージング処理にも用いられるものと同様の処理である。一般に、複数のフィルターとのコンボリューション結果にバイアスを加えたものを各中間層にて出力し、それぞれのフィルター要素の数値とバイアス値は最適化パラメータの対象で更新されていく。
入出力での画像サイズを同じにするために、フィルタサイズに応じて入力画像のエッジに0などのデータを埋めることをパディングという(Fast Fourier Transformでサンプル数が2の階乗と一致しない場合に0で埋めるパディングとの関連でイメージできる)。
たとえば、5x5の入力、3x3のフィルタでストライド2、パディング1だとパディング含めたデータが7x7、データを1つづつ飛ばすので出力データのサイズは3x3となる。
なおストライドはフィルター計算する入力値の間隔、チャンネルは、フィルターの数(畳み込み層の出力の数)のことである。
最大値を返すフィルタや平均値を返すフィルタのことをプーリング層と呼ぶ。畳み込み層と異なり、あらかじめ決められたフィルタ(平均値であれば、各要素が1/N, Nはフィルタの要素数)やオペレーション(最大値)によって計算される。
###最近のCNN
最近のCNN手法の例として、よりチャネル数が多いAlexNetモデルが紹介された。
Global Max PoolingやGloba Averge Poolingは、出力に近い側で複数チャネルの中間層から1次元の層に変換するときに各チャネルの最大値や平均値を返す手法であり、全チャネルを1次元化する手法よりも性能がよいとのことである。これは、各チャネルの特徴を1つの代表値にしていくようで、特徴を生かしやすいモデルと思われた。
なお、以下の本(ゼロから作るDeep Learning)の7章は、非常に参考になった。
https://www.oreilly.co.jp/books/9784873117584/
https://ml4a.github.io/ml4a/jp/convnets/