Section1 入力層~中間層
その前に深層学習の概要
・識別モデル:分類。データがクラスに属する確率を出力
生成モデル:クラスに分類されるデータを生成する
識別関数:分類。確率ではなく、所属するクラスを直接求める
・万能近似定理:ニューラルネットワークはだいたいのものを近似できるので汎用性がある
・入力層、中間層、出力層からなり、特に中間層は複数層に分かれる。4層以上のニューラルネットワークを深層と呼ぶ。
入力層:$x_1,x_2,…,x_i$ のベクトル
動物の識別例でいう、体長、体重、ひげの本数、耳のおおきさ等のデータの集合(確認テストの内容)
*入力は数値であることに留意。数値でないものは何らかの形で数値化する必要がある
中間層:入力に重み$w$との内積を求め、バイアス$b$を加えたものが中間層に渡される
$wX+b$
これに活性化関数で変換され、次の層への入力となる
・実習?(1_1_forward_propagation.ipynb)
ダウンロードしたファイルはパス名を合わせる必要があるので注意
u = np.dot(x, W) + b
入力から中間層へデータを渡す部分
Section2 活性化関数
セクション分け細かすぎません?全体が見えにくい気がします。
・入力層から中間層への入力までは線形の計算しかしないため、このままでは非線形の処理ができない。
中間層において、活性化関数により非線形変換をすることで対応する。
左が線形、右が非線形
・活性化関数の一例
中間層用
ReLU関数
シグモイド関数
ステップ関数
出力層用
ソフトマックス関数
恒等写像
シグモイド関数
・シグモイド関数
$f(u)=\frac{1}{1+e^{-u}}$
・ReLU関数
勾配消失問題をある程度低減
def relu(x)
return np.maxmum(0,x)
・実習と確認テスト(1_1_forward_propagation.ipynb)
z2 = functions.relu(u2)
インポートしたfunctionsからrelu関数を呼んでいる部分
Section3 出力層
・分類の例で考えれば、出力層は各クラスに分類されるそれぞれの確率が出力される
・誤差関数
出力層の結果が、正解からどれだけずれているかを表す関数
出力ではそれぞれのクラスに分類される(犬:0.7、猫:0.2、ネズミ:0.1等)。
この時正解が犬の場合、
もっとも高いものと一致はしているが、確率としては1-0.7=0.3の分外れている。
また、猫、ネズミもそれぞれ0.2、0.1それらしいと判断している分、外れていると考える。
このように正解とのずれを誤差とする。
誤差関数(残差平方和) $E(w)=\frac{1}{2}||(y-d)||^2 $
2乗しているのは、正と負の差をどちらも正にそろえるため
$\frac{1}{2}しているのは、計算上微分したときに係数が消えるため都合がよいから$
・代表的な誤差関数
平均二乗誤差(MSE):一般に回帰問題に使われる
loss = functions.mean_squared_error(d, y)
クロス(交差)エントロピー誤差:一般に分類問題に使われる
$-\sum_{i=1}^Id_ilog{y_i}$
loss = functions.cross_entropy_error(d, y)
# クロスエントロピー
def cross_entropy_error(d, y):
if y.ndim == 1:
d = d.reshape(1, d.size)
y = y.reshape(1, y.size)
# 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
if d.size == y.size:
d = d.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
最初のif内はミニバッチ処理、次のif内はone-hotベクトル用の処理で、式の本質的コードは次の部分
-np.sum(np.log(y[np.arange(batch_size), d] + 1e-7))
小さな数字を足しているのは、0になって対数が無限にマイナスにならないようにするための処理
・活性化関数
出力層での活性化関数は、分類問題では確率とみなせる0~1の値で、各クラスの総和が1になる等の特性が求められる
・問題の種類と活性化関数
恒等写像:回帰
$f(u)=u$
シグモイド関数:2クラス分類
$f(u)=\frac{1}{1+e^-u}$
ソフトマックス関数:多クラス分類
$f(i,u)=\frac{e^{u_i}}{\sum_{k=1}^K e^{u_k}}$
このKはクラス数で、すべてのクラスを足して分母とすることで、総和を1にする
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # オーバーフロー対策
return np.exp(x) / np.sum(np.exp(x))
ソフトマックス関数の本質的な式の右辺を表すコードは次の部分
return np.exp(x) / np.sum(np.exp(x))
if文内は、ミニバッチ処理の時のフォーマットへの対応で、どちらも本質的にはこの式となる
Section4 勾配降下法
・ニューラルネットを学習させる方法
応用的な方法に、確率的勾配降下法やミニバッチ勾配降下法がある
・解析的に解くことは困難なため、誤差関数が小さくなるように繰り返し修正していく
$$w^{(t+1)}=w^{(t)}-\epsilon\Delta E$$
誤差関数の傾き$\epsilon\Delta E$によりパラメータを更新する。$\epsilon$は学習率を調整するハイパーパラメータ
この式に該当するのが下記の最終行
# 確率的勾配降下法
for dataset in random_datasets:
x, d = dataset['x'], dataset['d']
z1, y = forward(network, x)
grad = backward(x, d, z1, y)
# パラメータに勾配適用
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key]
・上記コードの確率的勾配降下法はランダムにデータを選択して学習を行う方法
・オンライン学習:データが入る都度、パラメータの更新(学習)を行う
バッチ学習:まとまったデータを使ってパラメータの更新(学習)を行う
・ミニバッチ勾配降下法
大量のデータを学習する際に、データを小分け(ミニバッチ)に分割し、ミニバッチ単位で誤差を求め、各バッチの誤差の平均をとって学習する
これにより計算をバッチ単位に並列処理を行うことができる
このように同じ処理を並列して行うことをSIMD(Single Instruction Multi Data)並列化という
確認テストの $w^{(t+1)}=w^{(t)}-\epsilon\Delta E$ の図表現では、学習を適用する単位(エポック)がどのタイミングかを正しく理解することが重要と考えられる。
Section5 誤差逆伝搬法
・各パラメータの誤差の傾きをどのように計算するか
各パラメータについて数値微分をしては、計算コストが高くなる。
誤差逆伝搬法は、合成関数の微分により、出力側から順に微分することで、重みwやバイアスbの各パラメータの微分を解析的に求め、計算コストを下げる方法
・連鎖率
パラメータの更新のため複雑な合成関数の微分を求める必要があるが、合成関数はそれぞれの関数の微分の積で表せる。
$\frac{∂ f(g(x))}{∂ x}=\frac{∂ f(x)}{∂ g(x)}\frac{∂ g(x)}{∂ x}$
これにより出力層から順番に微分を求めていくことで、そこまでの計算結果をもとに次の微分を計算することができる。
その前の値を保存している部分をコードにより確認する
# 出力層でのデルタ
delta2 = functions.d_mean_squared_error(d, y)
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0)
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
delta2 で出力層での微分計算を行い、その後 delta1 で中間層の計算を行う際、delta2を利用している
ここでdelta2 は$\frac{∂E}{∂y}$、delta1は$\frac{∂E}{∂u}=\frac{∂E}{∂y}\frac{∂y}{∂u}$
さらに$\frac{∂E}{∂w_{ji}^{(2)}}=\frac{∂E}{∂y}\frac{∂y}{∂u}\frac{∂u}{∂w_{ji}^{(2)}}$を求めている部分は次の部分
grad['W1'] = np.dot(x.T, delta1)
計算は楽になるが、解析的に解くのはやはり大変。ライブラリは優秀
Section1 勾配消失問題
誤差逆伝搬法を使用する際に、伝搬を繰り返すたびに勾配が緩やかになり、パラメータの更新が十分に行われなくなる。
大きな原因として、多くの場合活性化関数の微分が0から1の間をを取るため、それを掛け合わせるごとに小さくなることにある。
sigmoid関数の場合、微分値は0から0.25の間である。
・一般的な対策
勾配消失が起きにくい活性化関数の選択
パラメータの初期値の選択
バッチ正規化
・活性化関数の選択
ReLU関数:正の時は勾配が1であるため勾配消失が起きにくい。また、負のときは勾配が0であるため、不要なパラメータが使われなくなり必要な部分が残される(スパース化)特性がある。
Tanh:微分値が-1から1を取るため、勾配消失が起きにくい。コード上の定義は次の通り
def tanh(x):
return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
# tanhの導関数
def der_tanh(x):
return 1.0 - (tanh(x) ** 2)
・パラメータの初期値の選択
各パラメータの初期値はランダムではあるが、実際にはある法則(正規分布等)をもって定めることが多い。その中で、勾配消失が起きにくい法則がある
Xavier:sigmoid関数等に使用する。正規分布を前のノードの平方根で除算することで分散を小さくする。
sigmoid関数のようなS字型の活性化関数では0から離れると勾配が小さくなることから、より0近くにすることで勾配消失を防ぐ
He:前のノード数nとして、正規分布に$\sqrt{\frac{2}{n}}$をかける
ReLU関数のようなS字型ではない活性化関数に使用する
重みの初期値を0やそれに近い数字にしすぎると各ノード特性に差がなくなり、学習がうまくいかなくなるため適度に分散させる
・バッチ正規化
ミニバッチに分割する際、バッチ単位で正規化を行うことで、データの偏りを低減する
これにより、学習が安定することによる学習速度の上昇や過学習の低減といったメリットも得られる
・演習(2_2_2_vanishing_gradient_modified.ipynb)
sigmoid関数でガウス分布で初期化した場合
学習が進んでいない。
安定して学習ができている。
ReLU関数は勾配消失を低減できるため、当初学習が進んでいないがある程度のところから学習ができている。
ガウス分布の初期化に比べ、当初から効率よく学習できている
・実習(2_3_batch_normalization.ipynb)バッチ正規化
正規化のコード実装を確認
mu = x.mean(axis=0) # 平均
xc = x - mu # xをセンタリング
var = np.mean(xc**2, axis=0) # 分散
std = np.sqrt(var + 10e-7) # スケーリング
xn = xc / std
sigmoid関数を使用し、初期化はXavier
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10,
activation='sigmoid', weight_init_std='Xavier', use_batchnorm=use_batchnorm)
学習できていることを確認
ReLU関数、He初期化に変更して実施
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10,
activation='relu', weight_init_std='he', use_batchnorm=use_batchnorm)
sigmoid、Xavierの組み合わせより、学習がやや早く進んでいる
Section2 学習率最適化手法
学習率の調整を適切に行う手法であり、一般に当初は大きくし、学習が進むのに伴い小さくする
モメンタム、Adagrad、RMSProp、Adamといった手法があり、現在主に使われているのはAdam
・モメンタム
慣性という項目で、前回からの重みの変化に係数をかけて加えるもの
局所最適解に陥りにくい
・Adagrad
勾配(の2乗)を累積していき、学習率を累積したもの(のroot)で除算することで調整する。
除算する値が単純に大きくなっていkため、学習率は単純に小さくなっていく
局所解に陥りやすい欠点がある
・RMSProp
Adagradの改良型。蓄積した勾配情報をどれだけ適用するかを調整するパラメータが追加されている。
局所解に陥りにくく、ハイパーパラメータの調整が必要なケースが少ないというメリットがある
・Adam
モメンタムとRMSPropのいいとこどり
・実習(2_4_optimizer.ipynb)
当初、SDGの学習率0.01で学習を行った
うまく学習できていない。
学習率を1に変更した
学習が遅いながらも進んでいる。
次のモメンタムで行ったところ、やはり初期の学習率が0.01では学習できなかったため、初期学習率を1とした。
基本的なSDGより学習の速度が速くなっている
次にモメンタムを修正しAdaGradを実装する
# 変更しよう
# ===========================================
if i == 0:
h[key] = np.zeros_like(network.params[key])
h[key] += grad[key] * grad[key]
network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key])+1e-4)
# ===========================================
初期学習率が0.01や1ではうまくいかなかったため、0.1に変更
学習ができている
次のRMSPropでは学習率0.01で学習することができた。
学習率の初期値はアルゴリズムによって適切に設定する必要があると考えられる
Section3 過学習
過学習は、訓練データに必要以上に適応してしまい、テストデータ(未知のデータ)に適応できなくなる(汎化性能が下がる)こと
対応策として、正則化とドロップアウトを扱う。
・正則化
ネットワークの自由度を制限して過学習を抑制する
過学習の要因に、重みwが極端に大きくなり、ネットワークが複雑になることがある。評価にwの大きさによる罰則(ペナルティ)を付加する
$E_n(w)+\frac{1}{p}||x||_p$
$||x||_p={(|x_1|^p+…+|x_n|^p)}^{\frac{1}{p}}$
pはノルム(距離)
p=1の時 L1正則化 Lasso回帰 マンハッタン距離、図で見るとひし形の拘束、エッジで最適化されるので重要度の低い特徴量を削減する効果がある
p=2の時 L2正則化 Ridge回帰 ユークリッド距離、図で見ると2乗なので円の拘束
・ドロップアウト
学習のたびに、ランダムでノード間の接続を削除して学習する
データが同じでも、学習するネットワークが変化することで、データにノイズを加えて水増しするのと同様な効果が得られる。
・実習(2_5_overfiting.ipynb)
オーバーフィッティングした様子
訓練データには100%近い精度だが、テストデータは精度が上がっていない
L2正規化
訓練データへの過学習が抑えられている。テストデータへのスコアがあがっていないが、元データが過学習用にデータ数が限定されていることもあってか、パラメータの修正だけでは向上しなかった
正規化の強度を調整すると、強くしすぎたときは学習が進まない様子が見られた
L1正規化
重みが削減される効果があるため、極端な変動がみられる
ドロップアウト
初期値ではドロップアウト率が高く、学習が進まない様子であった
ドロップアウト率を調整して、過学習を抑えて学習させることができた。
Section4 畳み込みニューラルネットの概念
次元的なつながりのあるデータ(画像や時系列的なつながりのある音声等)を扱うときの定番
NNは入力情報の順番や並びを考慮しないため、そのような関連性のある情報を抽出する
・一般的構造
入力層 →(畳み込み層*n→プーリング層)*m → 全結合層 → 出力層
・LeNet
CNNの原型といわれるネットワーク。10個のクラスの多クラス分類
処理の巻枯れ
3232の画像を入力
2828の画像6枚に畳み込み(44の6種のフィルタ)
1414の画像6枚にサブサンプリング
1010の画像16枚に畳み込み()
55の画像16枚にサブサンプリング(400)
全結合層(400→120→84)
出力層 10 (gausstan connections)
・畳み込み層
入力(画像)にフィルター(重みのパターン)をずらしながら適用し、出力を得る
フィルターの特性にどれだけ適合するかの情報を得られる
パディング:出力サイズを調整する等の目的のため、入力画像の周囲にパディングを設けることがある。パディング内のデータは0で埋めたり、近傍値で埋めたりする
スライド:フィルターをずらす量
チャンネル:フィルターの種類
・実習(2_6_simple_convolution_network.ipynb)
各層を設定している部分
# レイヤの生成
self.layers = OrderedDict()
self.layers['Conv1'] = layers.Convolution(self.params['W1'], self.params['b1'], conv_param['stride'], conv_param['pad'])
self.layers['Relu1'] = layers.Relu()
self.layers['Pool1'] = layers.Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = layers.Affine(self.params['W2'], self.params['b2'])
self.layers['Relu2'] = layers.Relu()
self.layers['Affine2'] = layers.Affine(self.params['W3'], self.params['b3'])
image to column:畳み込み処理を高速に行う工夫
画像をフィルタを適用する単位で、ベクトルに並べなおしておく
・プーリング層
畳み込みとの違いは、重みがないこと。フィルターのような範囲の平均またはMaxを取る
微妙なずれなどを吸収できる
・実習(2_6_simple_convolution_network.ipynb)
MNIST(手書きの数字のデータセット)を認識する
手書きの数字を適切に分類できている
Section5 最新のCNN
・AlexNet
2012年ILSVRC,畳み込みによる物体認識
人間の手で特徴量を決めていたものを初めて自動化
3つの畳み込み層、2つのプーリング層、3つの全結合層から構成
入力:224×224 出力:1000クラス分類
画像の上下を分けて学習し、最後に全結合する
活性化関数ReLU関数、データ拡張、ドロップアウト等を導入
Global Max Pooling → チャンネルごとに最大値の値1つに圧縮
Global Avarage pooling → チャンネルごとに平均の値1つに圧縮
情報が大量に減るがなぜかうまく認識するらしい・・・
・実習(2_8_deep_convolution_net.ipynb)
多層の畳み込みネットワーク
# レイヤの生成===========
self.layers = []
self.layers.append(layers.Convolution(self.params['W1'], self.params['b1'],
conv_param_1['stride'], conv_param_1['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Convolution(self.params['W2'], self.params['b2'],
conv_param_2['stride'], conv_param_2['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Pooling(pool_h=2, pool_w=2, stride=2))
self.layers.append(layers.Convolution(self.params['W3'], self.params['b3'],
conv_param_3['stride'], conv_param_3['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Convolution(self.params['W4'], self.params['b4'],
conv_param_4['stride'], conv_param_4['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Pooling(pool_h=2, pool_w=2, stride=2))
self.layers.append(layers.Convolution(self.params['W5'], self.params['b5'],
conv_param_5['stride'], conv_param_5['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Convolution(self.params['W6'], self.params['b6'],
conv_param_6['stride'], conv_param_6['pad']))
self.layers.append(layers.Relu())
self.layers.append(layers.Pooling(pool_h=2, pool_w=2, stride=2))
self.layers.append(layers.Affine(self.params['W7'], self.params['b7']))
self.layers.append(layers.Relu())
self.layers.append(layers.Dropout(0.5))
self.layers.append(layers.Affine(self.params['W8'], self.params['b8']))
self.layers.append(layers.Dropout(0.5))
self.last_layer = layers.SoftmaxWithLoss()
MNISTで98%以上の正答率がでている