はじめに
「プログラミング」学習初手『ゼロから作るDeep Learning』民のための実装攻略ノートです。『ゼロつく1』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。
関数やクラスとして実装される処理の塊を細かく分解して、1つずつ処理を確認しながらゆっくりと組んでいきます。
この記事は、6章「学習に関するテクニック」全体に関わる内容になります。Batch NormレイヤとDropoutレイヤを組み込んだ多層ニューラルネットワーククラスをPythonで実装します。
【前節の内容】
【他の節の内容】
【この節の内容】
6.b 拡張版多層ニューラルネットワーククラスの実装
前項のニューラルネットワークのクラスに、Batch NormalizationとDropoutの機能を追加して実装します。前項と併せて読んでください。
・インスタンスの作成時の引数:__init__
次の3つの引数を追加します。
use_dropout
は、Dropoutを行うかの設定です。True
かFalse
を指定します。詳細は「6.4.3:Dropout【ゼロつく1のノート(実装)】 - からっぽのしょこ」を確認してください。dropout_ratio
は、ニューロンの消失割合です。use_batchnorm
は、Batch Normalizationを行うかの設定です。True
かFalse
を指定します。詳細は「6.3:Batch Normalization【ゼロつく1のノート(実装)】 - からっぽのしょこ」を確認してください。
・レイヤの作成:__init__
「Affineレイヤ」→「Batch Normレイヤ」→「活性化レイヤ」→「Dropoutレイヤ」→「Affineレイヤ」→・・・→「最終層のAffineレイヤ」→「ソフトマックス関数と損失関数レイヤ」の順番に構成します。
Batch NormレイヤとDropoutレイヤは、それぞれ引数にTrue
が指定されたときのみ作成します。
・推論(順伝播)メソッド:predict
Batch NormalizationとDropoutは、訓練時のみ行う処理です。そこで訓練なのかどうかを引数train_flg
がTrue
かFalse
かで管理します。そのため順伝播メソッド.forward()
がtrain_flg
引数を持つBatch Normレイヤまたは(or
)Dropoutレイヤと、持たないAffineレイヤ・活性化レイヤとで条件分岐します。
・誤差逆伝播法による勾配計算メソッド:gradient
Batch Normalizationのハイパーパラメータに関する微分の計算処理を追加します。最終層(第hidden_layer_num + 1
層)にはBatch Normレイヤがないので、use_batchnorm
引数がTrue
かつ(and
)i < hidden_layer_num + 1
のときのみ処理します。Batch Normレイヤの逆伝播については、「Batch Normレイヤの逆伝播【ゼロつく1のノート(数学)】 - からっぽのしょこ」を確認してください。
・多層ニューラルネットワーククラスの実装
これが最後です!
# 多層ニューラルネットワークの実装 class MultiLayerNetExtend: # インスタンス変数の定義 def __init__(self, input_size, hidden_size_list, output_size, activation='relu', weight_init_std='relu', weight_decay_lambda=0, use_dropout=False, dropout_ratio=0.5, use_batchnorm=False): self.input_size = input_size # 入力層のニューロン数 self.output_size = output_size # 出力層のニューロン数 self.hidden_size_list = hidden_size_list # 中間層のニューロン数のリスト self.hidden_layer_num = len(hidden_size_list) # 中間層の数 self.use_dropout = use_dropout # Dropoutの可否 self.weight_decay_lambda = weight_decay_lambda # Weight decayの係数 self.use_batchnorm = use_batchnorm # Batch Normalizationの可否 # パラメータの初期値を設定 self.params = {} # 初期化 self.__init_weight(weight_init_std) # 初期化メソッド ## ニューラルネットワークを生成 # レイヤ作成用にディクショナリ変数に活性化関数を格納 activation_layer = {'sigmoid': Sigmoid, 'relu': Relu} # 1層ずつインスタンスをディクショナリ変数に格納 self.layers = OrderedDict() # 順番付き辞書を作成(初期化) for idx in range(1, self.hidden_layer_num + 1): # (`+1`は未満に対応するため) # Affineレイヤ self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)]) # Batch Normレイヤ if self.use_batchnorm: self.params['gamma' + str(idx)] = np.ones(hidden_size_list[idx-1]) # 調整用の標準偏差 self.params['beta' + str(idx)] = np.zeros(hidden_size_list[idx-1]) # 調整用の平均 self.layers['BatchNorm' + str(idx)] = BatchNormalization(self.params['gamma' + str(idx)], self.params['beta' + str(idx)]) # 活性化レイヤ self.layers['Activation_function' + str(idx)] = activation_layer[activation]() # Dropoutレイヤ if self.use_dropout: self.layers['Dropout' + str(idx)] = Dropout(dropout_ratio) # 最終層のAffineレイヤ idx = self.hidden_layer_num + 1 self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)]) # 最終層の活性化レイヤ:(不要な場合があるので分けておく) self.last_layer = SoftmaxWithLoss() # 重みの初期化メソッドの定義 def __init_weight(self, weight_init_std): # 全層のニューロン数のリストを作成 all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size] # 各層のパラメータを設定 for idx in range(1, len(all_size_list)): # 重みの初期値の標準偏差の設定:引数に数値を指定した場合はこのまま scale = weight_init_std # 特定の初期値を指定した場合の処理:(6.2節) if str(weight_init_std).lower() in ('relu', 'he'): # 'ReLU'か'He'を指定した場合 scale = np.sqrt(2.0 / all_size_list[idx-1]) # Heの初期値を使用 elif str(weight_init_std).lower() in ('sigmoid', 'xavier'): # `Sigmoid`か`Xavier`を指定した場合 scale = np.sqrt(1.0 / all_size_list[idx-1]) # Xavierの初期値を使用 # 値を生成 self.params['W' + str(idx)] = scale * np.random.randn(all_size_list[idx-1], all_size_list[idx]) self.params['b' + str(idx)] = np.zeros(all_size_list[idx]) # 推論(順伝播)メソッドの定義 def predict(self, x, train_flg=False): # 1層ずつ順伝播メソッドによる処理 for key, layer in self.layers.items(): if "Dropout" in key or "BatchNorm" in key: # DropoutレイヤかBach Normレイヤのとき x = layer.forward(x, train_flg) else: # Affineレイヤか活性化レイヤのとき x = layer.forward(x) return x # 損失関数メソッドの定義 def loss(self, x, t, train_flg=False): # ニューラルネットワークの処理(最終層の活性化抜き) y = self.predict(x, train_flg) # 荷重減衰:(6.4節) weight_decay = 0 # 値を初期化 for idx in range(1, self.hidden_layer_num + 2): # 重みを複製 W = self.params['W' + str(idx)] # Weight decay(荷重減衰)を計算 weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2) # 交差エントロピー誤差にWeight decayを加算 return self.last_layer.forward(y, t) + weight_decay # 認識精度の測定メソッドの定義 def accuracy(self, x, t): # ニューラルネットワークの処理(最終層の活性化抜き) y = self.predict(x, train_flg=False) # 推論結果を抽出(最大値のインデックスを取得) if y.ndim == 2: # 2次元配列(バッチデータ)のとき y = np.argmax(y, axis=1) t = np.argmax(t, axis=1) elif y.ndim == 1: # 1次元配列(データが1つ)のとき y = np.argmax(y) t = np.argmax(t) # 正解率を計算 accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy # 誤差逆伝播法による勾配計算メソッドの定義 def gradient(self, x, t): # 順伝播(交差エントロピー誤差)を計算 self.loss(x, t, train_flg=True) # 逆伝播(勾配)を計算 dout = 1 # 逆伝播の入力 dout = self.last_layer.backward(dout) # 最終層 layers = list(self.layers.values()) # その他の層を後ろから処理 layers.reverse() for layer in layers: dout = layer.backward(dout) # 計算結果をディクショナリ変数に格納 grads = {} for idx in range(1, self.hidden_layer_num + 2): # (`+2`は未満に対応するためと最終レイヤの分) grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.params['W' + str(idx)] grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db if self.use_batchnorm and idx != self.hidden_layer_num + 1: # 最終層以外のとき grads['gamma' + str(idx)] = self.layers['BatchNorm' + str(idx)].dgamma grads['beta' + str(idx)] = self.layers['BatchNorm' + str(idx)].dbeta return grads
これまで組んだプログラミングが問題なく回れば、これで6章の内容の全て実装完了です!お疲れ様でした。
以上でニューラルネットワークの内容は終了です。次は畳み込みニューラルネットワークを扱います。
参考文献
おわりに
5章は数式の行間埋めで大変でしたが、6章は解説の穴埋めで大変でした。もう少し本文に説明を載せてほしかった。あでも載ってない手法も実装されてるのはありがたかったです。
それとは別に色々あってモチベが低水準です。勉強仲間ほしい。
【次節の内容】