
ZAF 2021年9月28日
<今回のテーマ>
秋の夜長は
コーディング
(あるいは読書)
目次
- 前座 [6:30 - 7:00]
- (新シリーズ)『2021年版 NLP を完全に理解する』第1回
- 第1部 [7:00 - 8:00]
秋の夜長は読書- 論文 "Neural Machine Translation By Jointly Learning To Align and Translate" を精読!
- (目次)
- 第2部 [8:00 - 9:00]
秋の夜長はコーディング- Attention を組み込むための Seq2Seq を導入
- (目次)
- 今日のおわりに
- 総合目次
前座
ポッドキャスト復活!
- ここのところ、ことあるごとに「更新せねば」と言っていた ZAP (ザップ)こと ZENKEI AI ポッドキャスト
- 実は最終更新が(1回、今年の7月に残ってた1話をアップしましたが)
去年の 2020年10月にアップしたシーズン8でした
- ZAF のシーズン8は、 ZAF 2020年8月に対応してます
- 滞っていた理由
- 音声ファイルの編集に、手間をかけていた
- 「あー」とか「うー」とか
- 「チッ」とか「ハーッ」とか「...」とか
- そういうのをカットしつつ、
- それでも自然に話してるように隙間は整えつつ、
- 音声ファイルの編集に、手間をかけていた
- 前回の ZAF 8月を終えて、1つの決断をしました
- 「オーディオのクオリティ」と「リリース」を天秤にかけて
- 「オーディオのクオリティ」<<「リリース」だろう、と
- コンテンツは、みんなに届けて「なんぼ」だろう、と
新しい編集方針
- ZAF イベント(2時間半)から、話題ごとにカットする
- 1本30分を限度として、6〜7本を目処に
- 編集は、この切り出しのみとし、それ以上の修正は行わない
- 基本的に、2時間半の尺に、キューポイントを設定するのみ
- 音量については comp や normalize のみ簡単にかける
いい点
- 編集時間が大幅に減る
- 週末の半日で、ほぼ、終わる
- オーディオに対応したビデオも(比較的簡単に)作れる
ということで、8月末から、即実行!
ということで、8月末から、即実行!
- 8/27 (Fri) からの週:シーズン9 (2020年9月のイベント)
- 9/4 (Sat) からの週:シーズン10 (2020年10月のイベント)
- 9/11 (Sat) からの週:シーズン11 (2020年11月のイベント)
- 9/19 (Sun) からの週:シーズン12 (2020年12月のイベント)
- 9/26 (Sun) からの週:シーズン13 (2021年1月のイベント)
- ほぼ毎日、1話ずつ、公開中!


ポッドキャスト
ところでみなさん、ポッドキャスト、どうやって聞いてますか?
ZENKEI AI ポッドキャストは、 ホームページ以外に、 いろんなポッドキャスト・サービスからも聞くことができます!

|
反響は?
- 今回の決断の理由は、上に書いた通り
コンテンツは
みんなに届けて
「なんぼ」だろう
- で、1ヶ月、ほぼ毎日アップロードし続けて
どれだけ届いたのだろう?
Podcast Freaks
- podcastfreaks.com
- ここは、技術系のポッドキャストのキュレーションをやってるところ
- リストに入れてもらうのは、自己申告みたいなので、 ポッドキャスト始めた頃に登録してもらった
- Tips としては、リストは深夜に1度更新なので、
トップに行きたかったら午前0時直前を狙ってアップデートしたらよい
- ZAF は、今公開はスケジューリングしてますが、 リリース時間はお昼に設定してます
- (つまりは、あんまり気にしてない)
- 実際のところ、このリストがどれほどリスナーへのリーチに 寄与しているのか、よく分からない
- でも、毎日更新する励みにはなる
Podcast Ranking
- podcastranking.jp
- 毎日更新した成果(?)
反響は...よく分からない
ところで YouTube
- これまでのユーチューブのアーカイブは、ユーザーに不親切だったかも...
- (特に ZOOM になった最近は)
1回が2時間半のビデオをドーンと置いて、
それで終わり
- (特に ZOOM になった最近は)
- 元気がある時は、それでもインデックス付けたりしてましたが
- (上に書いた)新しい編集方針の「いい点」
オーディオに対応したビデオも
(比較的簡単に)作れる
- ということで、ユーザー体験向上のため
ZENKEI AI FORUM
SELECTIONS 爆誕!
- ZAF SELECTIONS とは?
- 話題ごとに切り出された、ショートビデオ
- タイトル付けた(ユーチューバーっぽい!)
- (実態は、ポッドキャストと同じ尺に切っただけ...)
- ZENKEI AI FORUM SELECTIONS プレイリスト
- 話題ごとに切り出された、ショートビデオ
- ついでに、ぼくのユーチューブ・チャンネル、サブスクお願いします!
(「チャンネル登録」というのかな?)- 現在のサブスクライバー数は、29人!
- 全然増えませんね
- 「宣伝しないですね」と言われますが、
宣伝するのが苦手なんで... - しかし、最近(やっと)学んだことなんだけど
声を上げないと、やっぱり誰にも気づいてもらえない
(当たり前のことなんだけど...)- 一方で、我が恩師の言葉
(kichiki.wordpress.com/2011/02/20/ぼくが影響を受けた5人から、三人目。/)
「本当に能力があれば機会を与えられることの意味は分かるはず」
- オレは
日々きちんとしていれば、きっと誰かが見てくれている
- あぁ、そう言えば(思い出した)「影響を受けた二人目」として書いた
Bill Evans も、そういうこと言ってたよ
(kichiki.wordpress.com/2011/02/12/ぼくが影響を受けた5人から、二人目。)
- 正確には
Ultimately, I came to the conclusion that
all I must do is take care of the music
even if I do it in a closet.
And if I really do that,
somebody is gonna come to the closet
and open the door and say,
“Hey, we’re looking for you!”
- 正確には
- 一方で、我が恩師の言葉
(kichiki.wordpress.com/2011/02/20/ぼくが影響を受けた5人から、三人目。/)
- ってことで、恥ずかしがらずに、自分の存在を主張していこうと思います。
チャンネル登録、よろしくね!
ZENKEI AI ポッドキャスト、よろしくね!
ZENKEI AI フォーラム、よろしくね!
One More Thing (for 前座)
前座といえば...
アレはどうなったのか、と...
思ってる人も、ちらほらと...
ZAM (ザム)はどうなった?
編集長、出てこいっ
って、思ってますよね
ZAM の現状報告
まとめ - 月刊 ZAM
まとめ - ZAM 季報
号 | 刊行状況 | |
---|---|---|
ZAM 季報 VOL.1 (2021/07) |
![]() |
初版:オンライン公開済み、印刷 |
ZAM 季報 VOL.2 (2021/12) | - | - |
これから、がんばります!
優先順位的には、
- 月刊 ZAM 2021/7 - 既に3名のみなさまから原稿はいただいています
- 月刊 ZAM 2021/4, 6, 8 - 古いものから、順にまとめます
- 月刊 ZAM 2021/9 - それで、今日のイベントのまとめ
- ZAM 季報 VOL.2 - 冬に予定されている「季報」も、準備をすすめたいな、と
みなさんも、原稿を書いてみませんか?
お待ちしてます!!
お待ちしてます!!
前座 目次
- ポッドキャスト復活!
- One More Thing - ZAM もがんばります
第1部
秋の夜長は読書(論文を読もう)
秋の夜長は読書(論文を読もう)
前回(2021年8月)の ZAF で

話題の Transformer について、
チラっとしゃべりました
で、閃きました!
チラっとしゃべりました
で、閃きました!
- そうそう、ぼくが定期的に言ってるヤツですね
- 最初は、なんだっけ?
- そもそも ZENKEI AI セミナーやろうってのも「閃き」だった気がするし...
- ポッドキャストやろう、も...
- 「技術書典」に出よう、本を書こう、も...
- 雑誌を創刊しよう、も...
- なので、みなさんも慣れてきたと思いますが
(新シリーズ)
2021年版
NLP を完全に理解する
2021年版
NLP を完全に理解する
- 要するに、ぼくが、2021年の今すっかり取り残されているので
- Transformer 時代の NLP を
Jeremy がかつて RNN 時代の NLP に於いてやったレベルで
「完全に理解する」
という企画 - 「Jeremy のレベルで」という意味で
つまり「完全に理解する」というのは
🙅 HuggingFace Transformers を使う(だけ)
🙆 Transformer を論文を読んで自分で実装する

これだけは、最低でも完璧に読まないと、
Jeremy レベルにはなれないだろう、と
で、まずは最初の(有名な)
" Attention Is All You Need "


- で、気づいた
- 単に Transformer を実装しても、多分、腑に落ちない
- Jeremy のレベルでの理解にならない
- 「完全に理解」できない
- ということで
構想を立てた!
- (1) RNN を「完全に」理解する
- Language Model
- jeremy は「ニーチェ」でデモ
- 古川さんが、おでんの絵 (by jeremy) で解説してくれた
- (2) Seq2Seq を「完全に」理解する
- jeremy は「Spelling Bee」から「質問に限定したフランス語-英語翻訳」をデモ
- (実際には attention まで含めて解説)
- (3) Seq2Seq への Attention の導入を「完全に」理解する
- 一斉を風靡した BiLSTM with Attention
- jeremy が keras で大変で pytorch に移行した理由の1つ
- attention mechanism には、調べると、いくつかあるみたい
- (4) Transformer を「完全に」理解する
- (5)その応用である BERT, GPT, T5 を「完全に」理解する
本日のお題
- 今日 (ZAF-2109) は、いきなりだけど(3)に飛び込む!
- その心は
秋の夜長は
コーディング
(というか読書) - 「Jeremy のレベルで理解する」には、
論文を正確に読んで、
その内容を正確に実装する、
そういうスキルが欠かせない - 今日は Attention の論文を正確に読むことで、
実際にこのプロセスを体験していきたい
Seq2Seq への Attention の導入
Bahdanau, Cho, Bengio による論文
" Neural Machine Translation By Jointly Learning To Align and Translate "


論文の読み方
- Abstract を読む
- この論文の内容が簡潔にまとまっている
- 全体の構成を見る
- 文章を読む前に、全体の構成を見る
- Intro があるか(普通ある)
Conclusion があるか (普通ある、 Discussions でお茶を濁してるときもある)
アルゴリズムの詳細などがあるか (本文になくても、今回のように Appendix にあったりする) - など、読み始める前の準備
- Introduction と Conclusion を精読する
- この論文が置かれている(歴史的)状況を理解する (課題の背景、文脈、関連する重要な論文などが分かる)
- この論文のスコープがはっきりする (どこまで議論して、どこからは議論しないか)
- そのあと、必要に応じで、必要なところを読み込んでいく
- 実装するためにアルゴリズムが知りたい時は Model の説明のセクションを
- 結果が知りたい時は Results のセクションを
- などなど
Abstract を精読

- Neural machine translation は機械翻訳に対して最近提案された方法
- それまでの統計的な機械翻訳と違って、 1つの neural network を構築し、 パフォーマンスを上げるように調整する
- 最近提案されている neural machine translation の多くは
encoder-decoder に分類される。
encoder は source sentence を1つの固定長ベクトルにエンコードし、
decoder はそれから翻訳を生成する - 本論文で、我々はこの1つの固定長ベクトルを使うことが
パフォーマンスのボトルネックになっていることを示し、
target word を予想するのに重要な部分が source sentence のどこかを、
あからさまな hard segment として部分を構成することなく、
自動的に(ゆるやかに)探索するようなモデルの拡張を提案する - この方法により、 現在の state-of-the-art である phrase-based system と同等の翻訳性能を、 英語からフランス語への翻訳で達成する
- さらに、定性的な解析から、モデルが見つけたこの (soft-)alignments が 我々の直感によく一致していることが分かる
論文の構成を見る
|
|
- 今の目的は、 Seq2Seq に Attention を導入する方法を知りたいということ
- 論文の「encoder-decoder」というのが「Seq2Seq」
- Abstract の 「重要な部分を source sentence のどこかを自動的に(ゆるやかに)探索する」 機構が「Attention」
- あとでわかるが、この論文の中では Attention と言う言葉は出てこなくて、
Alignment と呼ばれている
- 普通なら、Introduction と Conclusion は、最初にしっかり読み込んでから、
本文の詳細を読み進めていくところだが、
今回は「Attention」の計算方法をすぐに読んでいこう - しかし読むと分かるが、この論文の体裁が、 アルゴリズムの説明という形になっていなくて、 その分、読みにくい
Encoder-Decoder の説明


- ここは、この論文の主張ではなくて、既に主張されていたことのおさらい
- NLP における (RNN を使った) Encoder-Decoder は、
画像を扱う CNN による Auto-Encoder とはちょっと違っている- CNN の Encoder-Decoder は、
- Encoder は入力画像をもらって、あるベクトルを出力
- Decoder はそのベクトルをもらって、出力画像を計算
- 処理の流れは1本道
- RNN の Encoder-Decoder (Seq2Seq) は、ここで簡単に説明されている通り
- 入力データは文字列 x = (x1,...,xTx)
- Encoder は RNN で構成され、
- 入力シーケンスから hidden states を計算
ht = f(xt, ht-1)
- 計算された hidden states から1つの context vector を計算
c = q({h1, ..., hTx})
- 入力シーケンスから hidden states を計算
- Decoder も RNN で構成され、
- time t の出力文字 yt の予測確率は
p( yt | {y1, ..., yt-1}, c) = g( yt-1, st, c )
- ここで st は Decoder の RNN の hidden state
- time t の出力文字 yt の予測確率は
- イメージ(Sutskever et al. arxiv: 1409.3215, local copy) より
Sequence to Sequence Learning with Neural Networks- この論文 Sutskever et al. (2014) は、 LSTM を使った Seq2Seq を紹介した論文
- CNN の Encoder-Decoder は、
Context Vector と Alignment Model の説明
![]() |
![]() |
- この Alignment Model ってのが、今回のお目当ての「Attention」
- ポイントは、先の(Attentionのない) Seq2Seq との違いで、
p( yi | {yi, ..., yi-1}, x) = g( yi-1, si, ci )
- 分かりにくいけど、ポイントは
context vector c が
時間 i に依存するもの ci に置き換わった、
ということ - ちなみに、通常の Seq2Seq では c は
Encoder の最後の hidden state htX
が使われる
- ポイントは、出力文字の位置(時間) i には関係ない
- 分かりにくいけど、ポイントは
- この i に依存した context vector ci は
どう計算するのか、というのが右で、
- Encoder の hidden states (これを「Annotations」と呼んでいる)
hj に対する重み付き平均で定義する
ci = Σj=1Tx αij hj
- この重み αij は(重みなので正規化されている必要があって)
αij = softmax( eij )
- 正規化は index j に対して行われる
- この eij を決めるのが Alignment Model a() で
eij = a( si-1, hj )
- Encoder の hidden states (これを「Annotations」と呼んでいる)
- 実は、この論文、ここまでで Decoder と Attention の部分の説明は終わり
- つまり Encoder から Annotation (と呼ばれている hidden states) hj をもらって
- Decoder の hidden state si と
- 直前の Decoder の出力 yi-1 から
- まず Alignment Model a() を使って eij を計算
- それから context vector ci を計算
- それらを Decoder RNN g() に入れて
- 次の出力文字 yi を予想する
Encoder の Bidirectional RNN の説明

- この当時は Bidirectional RNN をきちんと説明しないと通じなかったのかな?
- それなりに詳しく説明してある
- この論文の表式が、やっぱり分かりにくい
- forward と backward の hidden states を concat して
「Annotation」を構成する、と言ってるところ
hj = [ h→jT; h←jT ]T
- これを、分かりやすく書くと(時間のインデックス j は省略して)
h = h→1 h→2 ... h→nh h←1 h←2 ... h←nh
time j での Annotation テンソルは (2, nh) にするよ、ということ (だよね?)
- forward と backward の hidden states を concat して
「Annotation」を構成する、と言ってるところ
- この論文を読んでいて、解読していった、数式の notation
- transpose (転置)の記号 T をベクトルに使うと「縦ベクトル」
- というのは、まぁオッケー
- ダガー † を使うか T (みたいな記号)を使うのか、 はフレーバーがあるけど
- [x; y] は concat を表してるんだね
- 鍵かっこが大事なのかな?
- あと「 ; 」も?
- これ、どの分野の notation なんだろう?
- transpose (転置)の記号 T をベクトルに使うと「縦ベクトル」
困ったときの Appendix
- これでモデルの説明は終わり?
- という論文で、これじゃコーディングできない、と思いましたが
- Appendix (補遺)がありました
- この RNN の説明、ほとんど GRU を説明しなおしてるだけですね
- PyTorch の reference torch.nn.GRU
- PyTorch の reference torch.nn.GRU
- 気になる点が2つ
- (1)RNN (GRU) に Attention を入れる方法について
- (2)proposed updated state s~i の式について
(1)RNN (GRU) に Attention を入れる方法について
- RNN (GRU) に context vector ci を通して Attention を入れる、というのがこの論文の趣旨
- どのように入れるか、については上の式に書いてあるとおり
(再掲) - GRU はモジュールとして(ありものを)使いたいが、
内部に、加算的に、導入されている
E e( yi ) + U si-1
E e( yi ) + U si-1 + C ci
- PyTorch の GRU の使い方としては、入力パラメータは
- input - これは e( yi ) に相当
- h_0 - これは si-1 に相当
C ci の寄与を
si-1 の中に含めてしまいたい- 算数的に(形式的に)いえば
s’i-1 = si-1 + U-1 C ci
- もっと形式論を言うと、
C’ = U-1 C ci
s’i-1 = si-1 + C’ ci
- と解決かなと思うけれど、
- そうすると proposed updated state s~i の式が微妙になる...
(2)proposed updated state s~i の式について
- 上の疑問点の他に、式自体によく分からない点がある
- PyTorch のドキュメントの式
- Bahdanau et al. の式
- 積の記号について
- GRU の「*」は Hadamard 積
- 論文の「○」は element-wise 積
- Hadamard 積とは何か?というと element-wise 積です
(cf. wikipedia)
- ちなみに記号「⊙」も Hadamard 積としてよく使われる (LaTeX の \odot)
- よく分からないのは、この式が結局、何を計算しろと言っているのか、ということ
- 添字は(ベクトルの成分とかではないので)全て省く
- 比べるべきなのは
( hidden state を h に、係数行列を Wに統一して)
r * ( W h ) == W [ r * h ] ?
- ベクトルと行列の成分の足をあからさまに書くと
Σj ri Wij hj == Σj Wij rj hj ?
- 素朴に見ると、この2つの式は別物ですよね
- Bahdanau et al. の著者たちは、実は GRU の開発者でもあるので、
疑わしいのは PyTorch のドキュメントですが- 実際に論文 (arXiv:1406.1078; local copy) を見ると
- 実際に論文 (arXiv:1406.1078; local copy) を見ると
- できること(するべきこと、でも、まだできてないこと)
- PyTorch の実装はどうなっているのか?
- Keras など他の実装はどうなっているのか?
- この両者で、結果に違いは生じるのか?
Alignment Model の詳細
- 曖昧な点が残ってますが、とりあえず進みます、つまり
- GRU の内部には(今は)立ち入らない
- Decoder の hidden state si-1 に context vector ci の補正を(加算的に)入れる
- つまり、残ってるのは context vector ci の計算
- si-1 と hj が与えたれたとして
- eij = a( si-1, hj )
- αij = softmaxj( eij )
- ci = Σj αij hj
- つまり Alignment Model が分かれば良い、と
eij = a( si-1, hj )
- A.1.2 ALIGNMENT MODEL を見ると
- ありました
a( si-1, hj ) = vaT tanh( Wa si-1 + Ua hj )
- ここで以下はパラメータ(学習対象)
- va : n 次元ベクトル
- Wa : (n x n) 次元行列
- Ua : (n x 2n) 次元行列
- 数学的な(というほどではないが)注意点
- 上にもコメントしたけど
- ベクトルが2つ、 x と y があるとき、
ある種類の notation では
xT y
- その心は
- xT は、縦ベクトルを意味する
- y は横ベクトル
- したがって xT y を、ベクトルの要素を羅列して書くと
x1 x2 : : xn y1 y2 . . . . yn = x1 y1 + x2 y2 + ... + xn yn
- まとめると、 Alignment Model のやってることは
a( si-1, hj ) = vaT tanh( Wa si-1 + Ua hj )
- Decoder の(ひとつ前のステップの) hidden states si-1 を n 次元ベクトルに変換
- Encoder の hidden states hj を n 次元ベクトルに変換
- それぞれ足して tanh() を適用(非線形性と、 -1 から 1 に押さえる)
- この n 次元ベクトルの各要素に adjustable parameter を掛ける
- この値(スカラーであることに注意) eij が
- 入力の j 番目の文字と
出力の i 番目の文字の
間の関係を表すパラメータ
- 入力の j 番目の文字と
- この eij は、エネルギー(みたいなもの) という風に見ることもできる
- ありました
第1部「秋の夜長は読書(論文を読もう)」 目次
- (新シリーズ)「2021年版 NLP を完全に理解する」 を開始しよう
- Transformer 論文を読んでみた
- 構想を立ててみた
- 本日のお題:Seq2Seq への Attention の導入の最初の論文を精読しよう
- 論文の読み方
- Abstract を精読
- 論文の構成を見ていこう
- 論文を読む 2.1 節 - Encoder-Decoder の説明
- 論文を読む 3.1 節 - Context Vector と Alignment Model の説明
- 論文を読む 3.2 節 - Encoder の Bidirectional RNN の説明
- 困ったときの Appendix
- 気になる点(1)RNN (GRU) に Attention を入れる方法について
- 気になる点(2)proposed updated state s~i の式について
- Alignment Model の詳細
第2部
秋の夜長はコーディング
秋の夜長はコーディング
さて、実装しましょう!
- 必要な情報が全て出揃ったので、あとは実装するだけですね!
- 決定事項
- 最初に Attention なしの plain な Seq2Seq モデルを実装しましょう
Seq2Seq モデルの実装
- Seq2Seq の構成
arxiv: 1508.04025 (local copy) より- この論文は Stanford のグループのもので
Attention mechanism について(Bahdanau et al. 以外の方法も含め)
比較検討している論文
- この論文は Stanford のグループのもので
パラメータ
|
|
変数、パラメータ
パラメータ
|
|


Encoding 部分

- inp [bs, maxlen_in] に [0, 1, 2, 3] が入っている
- 整数 0, 1, 2, 3, が文字 'A', 'B', 'C', 'D' に対応しているとする
- (文字を数字に置き換えることを Token 化 ( tokenize する)という)
- nn.Embedding によって Token 化された文字列をベクトル列にする
emb_inp = self.enc_emb(inp)
- テンソル emb_inp のサイズは [bs, enc_emb_dim]
- 入力テンソル emb_inp を RNN に食わせて Encoding 処理を行う
enc_outp, hidden = self.enc(emb_inp, hidden)
- nn.GRU は2つの入力をもらい、2つの出力を返す
- Inputs
- input : 入力テンソル( emb_inp が渡される)
今 Encoder は batch_first=True で定義したので
サイズは [bs, enc_emb_dim] - h_0 : 初期の hidden states (optional)( hidden が渡される)
サイズは [d_dir * n_layers, bs, hidden_size] - (ここで d_dir は bidirectional なら 2、 unidirectional なら 1)
- input : 入力テンソル( emb_inp が渡される)
- Outputs
- output : 出力テンソル(全ステップの、最終層の hidden states)( enc_outp に代入)
今 Encoder は batch_first=True で定義したので
サイズは [bs, seq_len, d_dir * hidden_size] - h_n : 全ての層の、最終ステップでの hidden states ( hidden に代入)
サイズは [d_dir * n_layers, bs, hidden_size]
- output : 出力テンソル(全ステップの、最終層の hidden states)( enc_outp に代入)
- 以上で Encoding は終わり(全ての入力文字に対しての計算)
- 上の 3.2 節 - Encoder の Bidirectional RNN の説明
でみたように Bidirectional で Encode する
- 論文では forward と backward を2行にして「Annotation」 hj
と定義していた
h = h→1 h→2 ... h→nh h←1 h←2 ... h←nh
- つまりテンソル h のサイズは [bs, n_layers, 2, hidden_size] みたいなものを想定
- 一方 PyTorch の nn.GRU の出力の hidden のサイズは [d_dir * n_layers, bs, hidden_size]
- 全ての要素は揃っているが、最初の成分が d_dir * n_layers と
まとめられてしまっている
- ソースコードのコメントによると、以下のようにして
普通に reshape できるようだ
hidden.view(d_dir, n_layers, bs, hidden_size)
- ソースコードのコメントによると、以下のようにして
普通に reshape できるようだ
- 論文では forward と backward を2行にして「Annotation」 hj
と定義していた
Encoder から Decoding への引継ぎ

- Encoder の結果を引き継いで、出力文字列を生成するのが Decoder
- Encoder とは異なり、1文字ずつ生成(予測)していく
- その際、次の文字の生成には、直前の予測結果を入力として使う
- teacher forcing というテクニックは、 学習時、正解データを入力として使うことでモデルの学習を助けるもの
- Encoder の結果は、以下の2つ
- hidden: Encoder の
最終ステップの全層の hidden states
サイズは [d_dir * n_layers, bs, hidden_size] - enc_outp: Encoder の
全ステップの、最終層の hidden states
サイズは [bs, seq_len, d_dir * hidden_size]
- hidden: Encoder の
最終ステップの全層の hidden states
- Decoder には、1文字ずつ渡していく
- Decoder の入力パラメータは
- 入力文字列(1文字のみ): dec_inp -> emb_inp
- 初期 hidden states : hidden
- Encoder の hidden を
Decoder の hidden に、きちんと渡す必要がある
- plain な Seq2Seq では(論文にも書いてあった通り)
context vector として最終ステップの Encoder の hidden states を使う - さて、どうやって
bidirectional な Encoder の hidden states と
unidirectional な Decoder の hidden states を
整合させますか?- 要するに、次元を合わせろ、ということ
- Encoder が情報2倍持ってるので、
hidden_size を2倍にするのが自然(だよね?)
- それが、これ
# hidden [2*n_layers, bs, hidden] hidden = hidden.reshape( self.d_dir, self.n_layers, bs, self.hidden_size ).permute(1, 2, 0, 3).reshape( self.n_layers, bs, -1 ) # => hidden [n_layers, bs, 2*hidden]
- (正直、あんまり自信がない。みんな、こんなことやってるのかな?)
- plain な Seq2Seq では(論文にも書いてあった通り)
- つまり、そういうことで、 Decoder のパラメータ設定は、以下のようになってる
d_nh = self.d_dir * hidden_size self.dec_emb = nn.Embedding(dec_num_emb, dec_emb_dim) self.dec = nn.GRU( dec_emb_dim, d_nh, num_layers=n_layers, self.dec_lin = nn.Linear(hidden_size, dec_num_emb)
Decoding の処理

- Decoder には、1文字ずつ渡していく
- Decoder の入力パラメータは
- 入力文字列(1文字のみ): dec_inp -> emb_inp
- 初期 hidden states : hidden
- hidden の引継ぎは、既に見た
- 入力文字列については、1文字目は SOS (start of sentence) をセットする
dec_inp = torch.LongTensor([SOS]*bs).view(1, -1).to(device) # => (1, bs), where L=1 is seq. length
- 1文字ずつ処理していくので、結果を全て保存するように準備
outputs = torch.empty((0, bs, self.dec_num_emb)).to(device)
- 出力文字に対するループの中での処理
- 文字 (token) から word vector にして RNN に渡す
emb_inp = self.dec_emb(dec_inp) dec_outp, hidden = self.dec(emb_inp, hidden) # => dec_outp[1, bs, 2*hidden_size]
- 生の出力は hidden_size のベクトルなので、出力 token のサイズに変換
(これを logit とする。outp = self.dec_lin(dec_outp.squeeze(0))
つまり softmax とって argmax すると token が得られる) - 各ステップの結果を保存
outputs = torch.cat((outputs, outp.view(1, bs, -1)), dim=0)
- 次の入力文字をセットして、ループを終わる
if label is None or (i / maxlen_out) > tf: p = F.softmax(outp, dim=-1) dec_inp = torch.argmax(p, dim=-1).view(1, -1) else: dec_inp = label[:, i].view(1, -1)
- teacher forcing をしない場合は、 outp から token を決めてセット
- teacher forcing する場合は、 正解データ label から次の文字を取り出してセット
- 文字 (token) から word vector にして RNN に渡す
- できた!
- でもまだ Attention なし
Attention の実装
- ご想像の通り、実装はしてみました
- Bahdanau et al. (2015) のものだけでなく
Luong et al. (2015) arxiv: 1508.04025 (local copy) に紹介されている4種類のものまで - で、こちらもご想像の通り、なんか今一、ピンときてないんですね
- Bahdanau et al. (2015) のものだけでなく
コード
class Seq2SeqAttnGRU(nn.Module):
def __init__(
self,
enc_num_emb, enc_emb_dim,
dec_num_emb, dec_emb_dim,
hidden_size, n_layers=2,
bidirectional=False,
attn_type='orig',
only_top=False,
):
'''
attn_types
'orig':
'dot':
'general':
'concat':
'none': no attention
'''
super(Seq2SeqAttnGRU, self).__init__()
# Seq2Seq 部分は省略
# attention
self.attn_type = attn_type
self.only_top = only_top
if self.attn_type != 'none':
if self.attn_type in ('orig', 'general'):
self.W1 = nn.Linear(d_nh, d_nh)
elif self.attn_type == 'concat':
self.W1 = nn.Linear(2*d_nh, d_nh)
self.W2 = nn.Linear(d_nh, d_nh)
self.v = nn.Parameter(torch.randn(d_nh))
if not only_top:
self.Wc = nn.Linear(1, n_layers)
def forward(self, inp, label=None, tf=0.0):
# ENCODING -----------------------------------------------
# Seq2Seq 部分は省略
attns = None
# attention
if self.attn_type != 'none':
enc_outp = enc_outp.permute(1, 0, 2)
if self.attn_type in ['orig', 'general']:
# enc_outp [L, bs, D*nh] (permuted)
# W1 [D*nh, D*nh]
W1enc_outp = self.W1(enc_outp)
# => [L, bs, D*nh]
attns = torch.empty((0, seq_len, bs)).to(device)
# DECODING -----------------------------------------------
for i in range(maxlen_out):
emb_inp = self.dec_emb(dec_inp)
dec_outp, hidden = self.dec(emb_inp, hidden)
# => dec_outp[1, bs, 2*hidden_size]
outp = self.dec_lin(dec_outp.squeeze(0))
# => outp[bs, dec_num_emb]
# attention
if self.attn_type != 'none':
if self.attn_type == 'orig':
# dec_outp [1, bs, D*nh]
# W2 [D*nh, D*nh]
W2dec_outp = self.W2(dec_outp) # => [1, bs, D*nh]
z = torch.tanh(W1enc_outp + W2dec_outp) # => [L, bs, D*nh]
zz = torch.matmul(z, self.v) # => [L, bs]
elif self.attn_type == 'dot':
# enc_outp [L, bs, D*nh] (permuted)
# dec_outp [1, bs, D*nh]
zz = torch.bmm(
enc_outp.permute(1, 0, 2),
dec_outp.permute(1, 2, 0)
).permute(1, 0, 2).squeeze(-1)
# => [L, bs]
elif self.attn_type == 'general':
# W1enc_outp [L, bs, D*nh]
zz = torch.bmm(
W1enc_outp.permute(1, 0, 2),
dec_outp.permute(1, 2, 0)
).permute(1, 0, 2).squeeze(-1)
# => [L, bs]
elif self.attn_type == 'concat':
# enc_outp [L, bs, D*nh] (permuted)
# dec_outp [1, bs, D*nh]
zz = torch.cat((
enc_outp,
dec_outp.expand_as(enc_outp)
), dim=-1)
#print(f'(1) zz {zz.shape}')
# (1) zz torch.Size([16, 128, 512])
# => [L, bs, 2*D*nh]
zz = self.W1(zz)
#print(f'(2) zz {zz.shape}')
# (2) zz torch.Size([16, 128, 256])
# => [L, bs, D*nh]
zz = torch.tanh(zz) # => [L, bs, D*nh]
zz = torch.matmul(zz, self.v) # => [L, bs]
a = torch.softmax(zz, dim=0) # => [L, bs]
#print(f'a {a.shape}')
#a torch.Size([16, 128])
attns = torch.cat((attns, a.unsqueeze(0)), dim=0)
# enc_outp [L, bs, D*nh] (permuted)
d = (enc_outp * a.unsqueeze(-1)).sum(0)
# => [bs, D*nh]
if self.only_top:
hidden[-1] += d
else:
# apply to all layers
d = self.Wc(d.unsqueeze(-1))
# => [bs, D*nh, n_layers]
hidden += d.permute(2, 0, 1)
# hidden [n_layers, bs, 2*hidden]
outputs = torch.cat((outputs, outp.view(1, bs, -1)), dim=0)
if label is None or (i / maxlen_out) > tf:
p = F.softmax(outp, dim=-1)
dec_inp = torch.argmax(p, dim=-1).view(1, -1)
else:
dec_inp = label[:, i].view(1, -1)
return outputs, attns
いくつかの結果
- Seq2Seq の課題として、今回は2つの問題を扱った
- Spelling Bee - 英単語の読みの辞書データを使って、 読みから単語のスペルを予測する問題
- フランス語から英語への翻訳 - 対訳データセット (EU の議事録)を使った翻訳課題
Spelling Bee






フランス語から英語への翻訳




第2部「秋の夜長はコーディング」 目次
今日のおわりに
……
今後の予定
- 次回 ZAF 10月27日開催の予定です。
- ZAF 講演者、 ZAM 執筆者、絶賛、大募集中です!
お気軽にお問い合わせください!
総合目次
- 前座
- 第1部「秋の夜長は読書(論文を読もう)」
- (新シリーズ)「2021年版 NLP を完全に理解する」 を開始しよう
- Transformer 論文を読んでみた
- 構想を立ててみた
- 本日のお題:Seq2Seq への Attention の導入の最初の論文を精読しよう
- 論文の読み方
- Abstract を精読
- 論文の構成を見ていこう
- 論文を読む 2.1 節 - Encoder-Decoder の説明
- 論文を読む 3.1 節 - Context Vector と Alignment Model の説明
- 論文を読む 3.2 節 - Encoder の Bidirectional RNN の説明
- 困ったときの Appendix
- 気になる点(1)RNN (GRU) に Attention を入れる方法について
- 気になる点(2)proposed updated state s~i の式について
- Alignment Model の詳細
- 第2部「秋の夜長はコーディング」
- 今日のおわりに