E資格の受験資格を得るために必要な日本ディープラーニング協会認定講座であるラビットチャレンジの中で出題される演習問題をベースに、E資格の深層学習パートの例題と回答例をまとめました。
- 入力層~中間層
- 中間層で使われる活性化関数
- 出力層
- 勾配降下法(最急降下法)
- 誤差逆伝播法
- 勾配消失問題
- 勾配消失問題の解決法
- 学習率最適化手法
- 過学習
- CNN (Convolutional Neural Network)
- AlexNet
- 最後に
入力層~中間層
Q1
パーセプトロンの構造を表す以下の図式に、として動物の種類を分類するのための入力データを書き入れてみてください。(無限通りの解答があります)
解答例
この解答例では、動物の分類に役立ちそうな特徴として、以下の画像の4つを選択しました。
Q2
パーセプトロンの入力、重み、バイアスから計算される総入力を求める計算をPythonで書いてみてください。
解答例
以下に解答例となるPythonスクリプトを示します。
の部分では、によって2次元ベクトルを3次元空間へ写像しています。
W = np.array([[1, 3, 5], [2, 4, 6]]) x = np.array([[2], [8]]) b = np.array([[6], [3], [1]]) u = W.T.dot(x) + b
中間層で使われる活性化関数
活性化関数(activation function)とは、入力の総和から次の層への出力の大きさを決める非線形の関数です。
ステップ関数
- 閾値を超えたら発火する関数であり、出力は常に1か0。
- ステップ関数を用いた場合には線形分離可能なものしか学習できなかった。
- 現在ではほとんど使われていない。
シグモイド関数
- 0から1の間を緩やかに変化する関数。
- シグモイド関数の微分は0から0.25の値を取るため、誤差逆伝播法において隠れ層(中間層)を遡るにつれて勾配が小さくなり勾配消失問題を引き起こす可能性がある。
ReLU関数
- 現在最もよく使われている活性化関数。
- 勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。
- ReLUの派生として、Leaky ReLUやParametric ReLU, Randomized ReLUなどがあるが、どれが一番良いかを一概に決めることは出来ない。
Q3
線形と非線形の違いを図に書いて簡易的に説明してみてください。
解答例
線形な関数は、
- 加法性:
- 斉次性:
非線形な関数は、加法性および斉次性を満たさない。
Q4
ReLU関数をPythonにて実装してみてください。
解答例
import numpy as np def relu(x) return np.maximum(0, x)
出力層
損失関数(loss function)
損失関数とは、予測と訓練データの正解の誤差を定量的に測定する為の関数です。
学習フェーズにおいて、最適なパラメータに近づけるための指標として使われています。
損失関数には様々な種類が存在しており、これを使わなければいけないというような決まりはありません。 回帰や分類といったタスクの種類や、データの性質などによって利用されている損失関数が異なる場合があります。
平均二乗誤差(MSE)
予測値と訓練データの正解ラベルとの残差平方和。
Q5
MSEでは、なぜ予測値と正解ラベルの差をそのまま使わずに2乗しているか答えてみてください。 加えて、残差平方和をする理由も答えてみてください。
解答例
2乗する理由
- 2乗しない場合には各ラベルの誤差に正負両方の値が発生し、全体の誤差を上手く表すことが出来ない。解決方法として、残差の絶対値を取る平均絶対誤差(MAE)やMSEなどが存在する。
する理由
- ネットワークの学習時に行う誤差逆伝播の計算に置いて損失関数の微分が用いられる。2次式の微分で発生する係数を打ち消し計算式を簡単にする。
出力層で使われる活性化関数
活性化関数については、中間層で使われる活性化関数で説明しましたが、実は中間層と出力層では使われる活性化関数が異なっています。
出力層と中間層の違い
値の強弱
- 中間層の活性化関数は、ベクトルの要素間の比率を非線形変換によって変化する。
- 出力層の活性化関数は、ベクトルの要素間の比率を一定に保ったまま変換する。
確率出力
- 分類問題における出力層の活性化関数は、出力となるベクトルの要素を確立として表すためにその総和を1とする。
回帰 | 二値分類 | 多クラス分類 | |
---|---|---|---|
出力層で使われる代表的な活性化関数 | 恒等写像 | シグモイド関数 | ソフトマックス関数 |
代表的な損失関数 | 平均二乗誤差(MSE) | 交差エントロピー誤差(cross entropy loss) | 交差エントロピー誤差(cross entropy loss) |
Q6
は、総入力のベクトルおよびクラスラベルを引数として、データがクラスに属する確率をスカラで返す関数になっています。
一方、実際の場面で使われるのは以下の式(1)の様に、総入力のベクトルを引数として、データが各クラスに属する確率をベクトルで返す関数です。
上記式(1)をPythonで実装してみてください。
解答例
2行目のu = u - np.max(u)
は3行目の計算時に発生し得るオーバーフローの回避を目的とした実装です。
def softmax(u): u = u - np.max(u) return np.exp(u) / np.sum(np.exp(u))
オーバーフロー回避について説明するにあたって、まずnumpyで扱う浮動小数について説明していきます。
浮動小数について
numpy
の浮動小数を表すnumpy.float64
型はの範囲を表すことが出来ます。
以下のWikipediaページにてDouble-precision examplesから、数値例を見てみると浮動小数の値の表し方が理解しやすいです。
Softmax関数はネイピア数の指数が使われていますが、例えば、Softmax関数の引数の入力ベクトル要素の1つに1000があったとすると関数内部でが作られ、numpy.float64
の最大値を超えて正しく計算ができない桁あふれ(オーバーフロー)が発生してしまいます。
入力値が1000を超えるというのはよくあることなので、このままではオーバーフローが原因でSoftmax関数を使える場面がかなり限定的になってしまうことになります。
以上を踏まえて、オーバーフロー回避方法について以下に説明します。
オーバーフロー回避
以下の式の通り、分子と分母のネイピア数の指数の値に同じ値を加算しても式自体の値には変化がありません。
そこで、ネイピア数の指数からを引く処理をしてあげることで、Softmax関数の戻り値を変えずにオーバフローを回避することが出来ることになります。
勾配降下法(最急降下法)
バッチ勾配降下法とも呼ばれます。
損失関数が最小となるを数値的に求める手法の1つであり、以下の式で定義されます。
学習1エポックで更新されるの大きさは学習率によって指定されます。
Q7
において損失関数MSEが最小となるを勾配降下法で求める関数をPythonで実装してみてください。
なお、引数として、入力データとなる行列、イテレーション回数上限、学習率を取るように実装してみてください。
解答例
問題の条件では、式変形によってを得ることが出来るため、以下の関数が解答例例となります。
この式変形が出来る理由が分からない場合は、基礎的な機械学習アルゴリズムの原理に説明があるので読んでみてください。
def gradient_decent(X_train, max_iter, eta): w = np.zeros(X_train.shape[1]).reshape(-1,1) for _ in range(max_iter): w_prev = np.copy(w) u_hat = sigmoid(np.dot(X_train, w)) grad = np.dot(X_train.T, (u_hat - u_train)) w -= eta * grad if np.allclose(w, w_prev): return w return w
確率的勾配降下法(SGD)
ランダムに抽出したサンプルの誤差を最小化する手法です。
勾配降下法と比較したSGDメリットは以下の通りです。 - データが冗長な場合の計算コスト軽減 - 望まない局所極小解に収束するリスク軽減 - オンライン学習が可能 - 学習開始後に継続してデータを集めて学習に利用できる - 勾配降下法はオンライン学習に対してバッチ学習と呼ばれる
Q8
オンライン学習について説明してみてください。
解答例
学習データが入ってくるたびにパラメータを更新し、学習を進めていく方法のことです。 画像を訓練データとする深層学習では特にデータサイズが大きくなるため、1度に全データをメモリに展開する必要のあるバッチ学習ではメモリ容量不足が発生しやすく、オンライン学習の利用が良く用いられています。
ミニバッチ勾配降下法
オンライン学習の要素をバッチ勾配降下法に取り込んだ方法です。ランダムに分割したデータの集合(ミニバッチ)に属するサンプルの平均誤差を最小化します。
深層学習においては、最も一般的な勾配降下法です。
ミニバッチ勾配降下法のメリットは以下の通りです。
- 並列処理により処理時間を短縮できる
- メモリ消費量を押さえられる
誤差逆伝播法
誤差勾配の計算
勾配を求める方法として、以下の様に微小範囲における変化量を求める数値微分という方法があります。
この方法では、各層のパラメータの数だけ同じ計算を繰り返し行う必要があり計算負荷が高いため、実際には誤差逆伝播法が用いられています。
誤差逆伝播法とは
計算結果(誤差)から微分を逆算することで不要な再帰的計算を避けて各パラメータの微分を算出することが出来る方法です。
Q9
以下のソースコードでは、SGDによって誤差を最小化する際に誤差逆伝播法を用いており、出力層の微分delta2
の値を1つ前の層の微分delta1
の計算に再利用しています。
出力層の微分delta2
を再利用しているコードを抜きだして答えてみてください。
解答例
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
Q10
微分に対応するPythonコードをQ9で示したソースコードから抜き出して、以下の表の空白を埋めてみてください。
なお、ソースコードで実装されているニューラルネットワークは以下の画像の構成になっています。
微分 | Pythonコード |
---|---|
|
|
解答例
微分 | Pythonコード |
---|---|
|
delta2 = d_mean_squared_error(d, y) |
grad['W2'] = np.dot(z1.T, delta2) |
|
delta1 = np.dot(delta2, W2.T) * d_sigmoid(z1) |
勾配消失問題
誤差を逆伝播しながら誤差を各パラメータで微分した値を求めていくにあたり、逆伝播で層を遡るにつれて勾配の値がどんどんと小さくなっていきやがて消えてしまうという問題は勾配消失問題と呼ばれています。
Q11
連鎖率の原理を使って、以下の式のを求めてみてください。
解答例
この計算は、誤差逆伝播法の中で沢山でてくる計算でしたね。回答は以下の通りです。
Q12
シグモイド関数の導関数は、入力が0のときに最大値を取ります。その最大値を答えてみてください。
解答例
答えは、0.25です。
解説
シグモイド関数の導関数は、となります。
をとおき、tで微分したものを0とおくと、のときにが最大になることが分かります。
以下の通り、となるため、シグモイド関数の導関数はのとき最大値0.25を取ることが分かりました。
活性化関数にシグモイド関数を選択すると中間層1つを誤差逆伝播する度に勾配が最大でも0.25倍になってしまうため、中間層が増えると容易に勾配消失が発生してしまうことが分かりますね。
勾配消失問題の解決法
- 活性化関数の選択
- 例えばReLU関数を選択した場合、勾配が0または1となるため、活性化関数による勾配消失問題を防ぐことが出来る。また予測に役に立たないパラメータは勾配を0とすることでスパース化に役立つ。
- 重みの初期値設定
- 活性化関数に合わせた重みの初期値設定が存在する。
- シグモイド関数およびReLU関数には、Xavierの初期値を用いることで、勾配消失を防ぎつつ活性化関数を通した後の値の分布が広がる(表現力の抑制を防げる)。
- バッチ正規化
- 学習速度向上および過学習抑制などが期待できる。
Q13
重みの初期値を0に設定すると、どのような問題が発生するか答えてみてください。
解答例
重みの初期値を0に設定すると、誤差逆伝播において全ての重みの値が均一に更新されるため、多数の重みパラメータを持つ意味がなくなってしまいます。
Q14
深層学習では一般に必要とするデータ数が多く、メモリなどの都合ですべてをまとめてバッチで計算することができません。
以下のコードは、データを少数のまとまりに分けて計算を行うミニバッチ学習を実装しています。■および▲に当てはまる内容を答えてみてください。
def train(data_x, data_t, n_epoch, batch_size): ''' data_x: training data (features) data_t: training data (labels) n_epoch: number of epochs batch_size: mini batch size ''' n = len(data_x) for epoch in range(n_epoch) shuffle_idx = np.random. permutation(N) for i in range(0, n, batch_size): i_end = i + batch_size batch_x, batch_t = data_x[■], data_t[▲] _update(batch_x, batch_t)
解答例
バッチサイズの分だけデータを取り出す処理が回答として入ります。
def train(data_x, data_t, n_epoch, batch_size): ''' data_x: training data (features) data_t: training data (labels) n_epoch: number of epochs batch_size: mini batch size ''' n = len(data_x) for epoch in range(n_epoch) shuffle_idx = np.random. permutation(N) for i in range(0, n, batch_size): i_end = i + batch_size batch_x, batch_t = data_x[i:i_end], data_t[i:i_end] _update(batch_x, batch_t)
学習率最適化手法
学習率が大きい場合には、重みが最適値にいつまでもたどり着かずに発散してしまう可能性があります。
一方、学習率の値が小さい場合には、発散することはなくとも収束するまでに長い時間を要します。また、大局的最適値ではなく局所的最適値に収束しやすくなります。
そこで、重みの更新量を状況によって変化させる各種の学習率最適化手法が考案されてきました。
Q15
モーメンタム・AdaGrad・RMSPropの特徴について、それぞれ簡潔に説明してみてください。
解答例
現在定番の手法として用いられているAdamは、以下の通り、モーメンタムとRMSPropを組み合わせたものです。
手法 | 特徴 |
---|---|
モーメンタム | 今までの動き(慣性)を考慮することで、勾配降下法の振動を移動平均と同じ理屈で抑える手法 |
AdaGrad | 今までの更新量が大きくなるにつれて、現在の更新量を小さくすることで勾配降下法の振動を抑制するという考え方をパラメータごとに適用した手法 |
RMSProp | AdaGradに対して、過去の勾配の影響がどんどん減っていく機能を追加した手法 |
Adam | 移動平均で振動を抑制するモーメンタム と 学習率を調整して振動を抑制するRMSPropを組み合わせた手法 |
過学習
過学習の原因の1つとして、一部の特徴量を過大評価してしまっている場合、つまり重みが大きくなりすぎてしまっている場合があります。
その対策として、損失関数に正則化項を加えることで重みが大きくなりすぎないように抑えて過学習を抑制する正則化という方法があります。
しかし、ニューラルネットワークのモデルが複雑になってくると正則化だけでは過学習の対応が困難になってきます。そこでニューロンをランダムに消去しながら学習することで過学習を抑制するドロップアウトという手法が良く用いられています。
正則化のイメージ
L1正則化で行われていることを3Dでイメージしてみると、以下の図のようになります。
正則化項の持つ角が、最小化する式にうっすらと反映されているのが見えます。この角で最小化されることが多いため、重要でない重みの値が0とみなされスパース化が行われます。
次に、L2正則化で行われていることを3Dでイメージしてみると、以下の図のようになります。
L1正則化と同様に、正則化項を加算することによって最小化する式は誤差関数から変形して最小値も変わっていますが、最小値を取った際に重みが0となりやすいような形にはなっていないことが分かります。
Q16
以下の図は上記3Dイメージの等高線を描画したものです。L1正則化を表しているグラフはどちらであるか答えてみてください。
解答例
右のグラフがL1正則化を表しています。
L1ノルムを用いるラッソ正則化がL1正則化と呼ばれています。
Q17
以下のPythonコードは、L2正則化を適用した場合にパラメータの更新を行うプログラムです。
あるパラメータparam
と正則化がないときにそのパラメータに伝播される誤差の勾配grad
が与えられたとします。
最終的な勾配を計算する■に当てはまるコードを以下の4つから選んでみてください。
(1) np.sum(param**2)
(2) np.sum(param)
(3) param**2
(4) param
def ridge(param, grad, rate): ''' param: target parameter grad: gradients to param rate: lasso coefficient ''' grad += rate * ■
解答例
答えは、(4)です。
リッジ正則化を行うために損失関数に正則化項を加えた式は以下の様になります。
上記の式に置いて、第1項の微分がPythonコードでいうgrad
に当たり、第2項の微分が今回の解答となります。
というわけで、第2項を微分してみると、となるため、穴埋めで■にあたる部分はつまりparam
が解答となります。
def ridge(param, grad, rate): ''' param: target parameter grad: gradients to param rate: lasso coefficient ''' grad += rate * param
Q18
以下のPythonコードは、L1正則化を適用した場合にパラメータの更新を行うプログラムです。
あるパラメータparam
と正則化がないときにそのパラメータに伝播される誤差の勾配grad
が与えられたとします。
最終的な勾配を計算する■に当てはまるコードを以下の4つから選んでみてください。
(1) np.maximum(param, 0)
(2) np.minimum(param, 0)
(3) np.sign(param)
(4) np.abs(param)
def ridge(param, grad, rate): ''' param: target parameter grad: gradients to param rate: lasso coefficient ''' x = rate * ■ grad += rate * x
解答例
答えは(3)です。
ラッソ正則化を行うために損失関数に正則化項を加えた式は以下の様になります。
つまり、Q17で行ったのと同じように第2項をで微分したいわけです。皆さんやり方は分かりますか?
L1ノルムの微分なので、以下のグラフような絶対値のついた関数の微分を考える必要があります。
このグラフより、絶対値の影響での座標から横軸を基準にグラフが折り返されていることが分かります。
この関数はなめらかでないため微分不可能です。微分を行うためには、右微分と左微分を行う必要があります。
以上より、の符号によって、の微分は+1または-1になることが分かりました。
つまり、第2項のでの偏微分は以下の式で表すことが出来ます。
したがって、穴埋めで■にあたる部分はベクトル要素の符号に応じて1, 0, -1を返すnp.sign(param)
が解答となります。
def ridge(param, grad, rate): ''' param: target parameter grad: gradients to param rate: lasso coefficient ''' x = rate * np.sign(param) grad += rate * x
CNN (Convolutional Neural Network)
画像データや音声データなどの入力データの形に意味があるデータの学習によく使われる手法です。
畳み込み層とプーリング層(サブサンプリング層)によって形状に意味を持たせた特徴量を行列として作成し、Flatten, Global Max Pooling, Global Average Poolingなどによって1次元ベクトルに変換した後は全結合層と呼ばれる形状を意識しない通常のDNNの処理が行われていきます。
Q19
サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを答えてみてください。
このときストライドとパディングを1とします。
解答例
以下の公式で畳み込み後の出力サイズを計算することが出来ます。
よって、答えは以下の通り、7×7となります。
AlexNet
画像内の物体認識の精度を競う国際大会ILSVRCにおいて2012年に優勝を果たした際に使われたAlexNetのアーキテクチャをCNNの代表例として以下に示します。
上記アーキテクチャの中では以下の内容が含まれています。
- 3つの畳み込み層
- 2つのMaxプーリング層
- 3つの全結合層
- ドロップアウト
最後に
浮動小数についてに記載した符号・指数部・仮数部といったbitレベルで、numpy.float64型自体の理解をE資格で問われることはないだろうとは思います。
だた、機械学習アルゴリズムの実装を自分で行う場合にはメモリを意識したプログラミングができると意図しないエラーで困ることが減るんじゃないかと思いました。
今回の記事はここまで。Kazuki Igetaでした😃