はじめに
『ゼロから作るDeep Learning 2――自然言語処理編』の初学者向け【実装】攻略ノートです。『ゼロつく2』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。
本の内容を1つずつ確認しながらゆっくりと組んでいきます。
この記事は、4.2.3項「シグモイド関数と交差エントロピー誤差」の内容です。Sigmoid with Lossレイヤを説明して、Pythonで実装します。
【前節の内容】
【他の節の内容】
【この節の内容】
4.2.3 シグモイド関数と交差エントロピー誤差
・数式による確認
多値分類では、活性化関数にSoftmax関数を用いてスコアを正規化し、交差エントロピー誤差を損失としました。二値分類では、活性化関数にSigmoid関数を用いてスコアを正規化し、交差エントロピー誤差を損失とします。Sigmoid関数については1.2.1項または1巻の3.2節を参照してください。
要素(クラス)数が2つ($H = 2$)のデータが1つ($N = 1$)の場合の交差エントロピー誤差の計算式は、次の式になります(1.3.1項)。
このときone-hot表現のターゲット(教師ラベル)$\mathbf{t} = (t_1, t_2)$の2つ目の要素は、$t_2 = 1 - t_1$と表現できます。$t_1$が1のとき$t_2$は0、$t_1$が0のとき$t_2$は1になりますね。スコアを正規化した値$\mathbf{y} = (y_1, y_2)$は$\sum_{k=1}^2 y_k = 1$なので、こちらも2つ目の要素は$y_2 = 1 - y_1$と表現できます。これは$y_1 + y_2 = 1$の$y_1$を右辺に移項したと解釈できます。
よって上の計算式に代入すると、交差エントロピー誤差は
$t_1$と$y_1$から計算できることが分かります。
つまりCBOWモデルの2つ目の改良点は、Enbedding Dotレイヤ(出力層)でターゲットの単語に関するスコアを計算し、Sigmoid with Lossレイヤ(活性化層と損失層)で確率に変換して損失を求めることです。
ここまでの処理までの処理がSigmoid with Lossレイヤの順伝播になります。
逆伝播については、スコア$\mathbf{s}$に関する勾配$\frac{\partial L}{\partial \mathbf{s}}$を求めます。
この$k$番目の要素は、次の式で計算できます。
ただしバッチデータを入力する場合は、交差エントロピー誤差の計算においてバッチサイズ(ここでは$N$とします)で割る必要があります。そのため勾配においても($\frac{1}{N}$も伝播するため)バッチサイズで割る必要があるため
となります。詳しくは「ソフトマックス関数と交差エントロピー誤差の逆伝播【ゼロつく1のノート(数学)】 - からっぽのしょこ]」を参考にしてください。
次項でEmbedding Dotレイヤを、次々項でSigmoid with Lossレイヤを実装します。
・Sigmoid with Lossレイヤの実装
二値分類で用いるSigmoid with Lossレイヤを実装します。
Sigmoid with Lossレイヤでは、これまでに実装した関数cross_entropy_error()
(1.3.1項)を利用します。そのため関数定義を再実行するか、次の方法で読み込む必要があります。
# 読み込み用の設定 import sys sys.path.append('C://Users//「ユーザー名」//Documents//・・・//deep-learning-from-scratch-2-master') # 実装済みの関数をインポート from common.functions import cross_entropy_error
・処理の確認
前項で計算したスコアs
をSigmoid関数(1.5)により正規化します。
# スコア(Embedding Dotレイヤの出力) print(np.round(s, 3)) # 確率に変換:式(1.5) y = 1 / (1 + np.exp(-s)) print(np.round(y, 3)) print(np.sum(y))
[-0.394 -0.356 -0.503 1.145 2.56 0.097]
[0.403 0.412 0.377 0.759 0.928 0.524]
3.4026629629938796
出力データ(正規化後の値)y
の各要素は、それぞれ入力データ(コンテキスト)ごとの正解ラベルに関する要素に対応しています。そのためy
の総和は1にはなりません。
np.c_[]
を使って、配列を列方向に結合することができます。
# 配列を作成 a = np.array([1, 3, 5, 7]) b = np.array([2, 4, 6, 8]) # 配列を結合 ab = np.c_[a, b] print(ab)
[[1 2]
[3 4]
[5 6]
[7 8]]
この関数で、1 - y
とy
の2列の配列を作成します。
正解の要素はy
の列なので、全ての行(データ)において正解ラベルは1になります。そこでnp.ones_like()
を使って、ターゲットtarget
と同じサイズで全ての要素が1の配列t
を作成して、教師ラベル$\mathbf{t}$とします。
1.3.1項で実装した関数cross_entropy_error
を使って、交差エントロピー誤差を計算します。
# 二値分類用の教師ラベルを作成 t = np.ones_like(target) print(t) # 交差エントロピー誤差を計算:式(1.8) loss = cross_entropy_error(np.c_[1 - y, y], t) print(loss)
[1 1 1 1 1 1]
0.6280905395595118
この値が損失であり、順伝播における最終的な出力です。
続いて逆伝播の処理を確認します。
Sigmoid with Lossレイヤの逆伝播の入力は$\frac{\partial L}{\partial L} = 1$です。バッチデータを入力とする場合は、式(A.4)を更にバッチサイズで割ります。
# 逆伝播の入力 dL = 1 # バッチサイズを取得 batch_size = t.shape[0] print(batch_size) # 勾配を計算:式(A.4) ds = (y - t) * dL / batch_size print(np.round(ds, 3))
6
[-0.1 -0.098 -0.104 -0.04 -0.012 -0.079]
この値がEmbedding Dotレイヤ(出力層)に伝播します。
・実装
処理の確認ができたので、Sigmoid with Lossレイヤをクラスとして実装します。
# Sigmoid with Lossレイヤの実装 class SigmoidWithLoss: # 初期化メソッドの定義 def __init__(self): self.params = [] # パラメータ self.grads = [] # 勾配 self.loss = None # 損失 self.y = None # 正規化後の値 self.t = None # 教師ラベル # 順伝播メソッドの定義 def forward(self, x, t): self.t = t # 教師ラベル # 確率に変換 self.y = 1 / (1 + np.exp(-x)) # シグモイド関数:式(1.5) # 交差エントロピー誤差を計算 self.loss = cross_entropy_error(np.c_[1 - self.y, self.y], self.t) # 1.3.1項 return self.loss # 逆伝播メソッドの定義 def backward(self, dout=1): # バッチサイズを取得 batch_size = self.t.shape[0] # 勾配を計算 dx = (self.y - self.t) * dout / batch_size # :式(A.4) return dx
実装したクラスを試してみましょう。
インスタンスを作成して、順伝播の処理を行います。
# Sigmoid with Lossレイヤのインスタンスを作成 loss_layer = SigmoidWithLoss() # 二値分類用の教師ラベルを作成 t = np.ones_like(target) print(t) # 順伝播メソッドを計算 loss = loss_layer.forward(s, t) print(loss)
[1 1 1 1 1 1]
0.6280905395595118
損失を出力できました。
続いて逆伝播の処理を行います。
# 逆伝播の入力 dL = 1 # 逆伝播メソッドを計算 ds = loss_layer.backward(dout=dL) print(np.round(ds, 3))
[-0.1 -0.098 -0.104 -0.04 -0.012 -0.079]
以上で活性化層と損失層を実装できました。ただしここまでは「正解の単語(データ)」のみを扱ってきました。次項では、「不正解の単語(データ)」サンプリングして損失に加える方法を説明します。
参考文献
- 斎藤康毅『ゼロから作るDeep Learning 2――自然言語処理編』オライリー・ジャパン,2018年.
おわりに
Sigmoid with Lossレイヤの逆伝播の導出は、巻末付録にて詳しく書いてあることもあり飛ばしてしましました。後々Sigmoid関数とSoftmax関数の関係と一緒にまとめるつもりではいます。
【次節の内容】