はじめに
ふと思い立って勉強を始めた「ゼロから作るDeep Learning❷ーー自然言語処理編」の5章で私がつまずいたことのメモです。
実行環境はmacOS Catalina + Anaconda 2019.10、Pythonのバージョンは3.7.4です。詳細はこのメモの1章をご参照ください。
(このメモの他の章へ:1章 / 2章 / 3章 / 4章 / 5章 / 6章 / 7章 / 8章 / まとめ)
この記事は個人で作成したものであり、内容や意見は所属企業・部門見解を代表するものではありません。
5章 リカレントニューラルネットワーク(RNN)
この章は、リカレントニューラルネットワークの説明です。
5.1 確率と言語モデル
言語モデルの説明と、CBOWを言語モデルに使おうとした時の問題点が解説されています。式5.8が近似になっているのは、CBOWだと単語の並びを無視してしまうからということかと思います。
word2vecは単語の並びを無視してしまうので、分散表現に使うならこの章で学ぶRNNの方が良さそうに思えるのですが、RNNが先に生まれ、語彙数増加と質の向上のために後からword2vecが提案されたとのことで、実際には逆の流れだったというのが興味深いです。
5.2 RNNとは
RNNの解説です。活性化関数としてtanh関数(双曲線正接関数)がでてきますが、なぜかこの本では解説がないので、詳細は「tanh」でググりましょう。
あと少し気になったのは、ミニバッチ学習でデータを終端まで使った時に先頭に戻す対応です。これだとコーパスの末尾と先頭がつながってしまいます。ただ、そもそもこの本ではPTBコーパスを「ひとつの大きな時系列データ」として扱っていて、文の区切りすら意識していません(P.87の中央のサソリマーク部分参照)。そのため、末尾と先頭がつながってしまうことを気にしても意味がないレベルなのかも知れません。
5.3 RNNの実装
実装に当たっては、図5-19と図5-20でバイアス $b$ の後のRepeatノードが省略されているので少し注意が必要です。順伝播はブロードキャストが行われるので図の通りに実装できますが、逆伝播で $db$ を求める際は意識して加算する必要があります。ちょうどこの部分のQAがteratailにもありました(teratail : RNNの逆伝播でdbをaxis=0でsumする理由に関して)。
あと、今回出てきたtanh関数が解説なしで実装されていますが、順伝播は本のコードのように numpy.tanh()
で計算できます。逆伝播はdt = dh_next * (1 - h_next ** 2)
の部分がtanhの微分になりますが、これについては本の終わりにある「付録A sigmoid関数とtanh関数の微分」に詳しい解説があります。
また、P.205で「...(3点ドット)」の話がでてきますが、これはP.34で出てきた「3点リーダー」と同じです。このメモの1章にも書きましたが、3点ドットだと上書きになると覚えるよりも、ndarrayのスライスとビューの関係を理解するのがオススメです。
5.4 時系列データを扱うレイヤの実装
コードの解説が省略されていますが、シンプルなので見れば理解できました。
Time Embeddingレイヤー(common/time_layers.pyのTimeEmbedding
クラス)は単純に $T$ 個のEmbeddingレイヤーをループで処理しているだけです。
Time Affineレイヤー(common/time_layers.pyのTimeAffine
クラス)では、$T$ 回ループする代わりにバッチサイズ $N$ が $T$ 倍になる形に変形して一気に計算し、結果を元の形に変形することで効率化しています。
Time Softmax With Loss レイヤー(common/time_layers.pyのTimeSoftmaxWithLoss
クラス)は本の解説通りなのですが、ignore_label
を使ったマスクが実装されているのが気になりました。正解ラベルが -1の場合は損失も勾配も0にして $L$ を求める際の分母の $T$ からも除外しているのですが、正解ラベルを-1にするような処理は今の所なかったかと思います。この後の章で使うのかも知れないので、とりあえず放置しておきます。
5.5 RNNLMの学習と評価
残念ながら今回の実装ではPTBデータセット全体を使うと良い結果が出ないとのことなので、前章で遊んだ青空文庫の分かち書き済みテキストでの学習もやめておきました。次章で改良するとのことなので、そこで試してみようと思います。
余談ですが、SimpleRnnlm.__init__()
のrn = np.random.randn
というコードを見て、今さらながらPythonは関数を簡単に変数に入れて使えるので便利だなと思いました。C言語だと変数に関数を入れる(関数のエントリポイントを変数に入れる)のに*
とか()
とかたくさんついて複雑で、それを使うのもややこしくて、現役時代はホント苦手でした
5.6 まとめ
なんとか時系列データが取り扱えるようになってきました。
この章は以上です。誤りなどありましたら、ご指摘いただけますとうれしいです。