SlideShare a Scribd company logo
深層学習 Day4 レポート
小川成
深層学習
Section1) 強化学習
1-1 強化学習とは
●強化学習
長期的に報酬を最大化できるように環境のなかで行動を選択できるエージェントを作ることを目
標とする機械学習の一分野
*行動の結果として与えられる利益(報酬)をもとに、行動を決定する原理を改善していく仕組み
1-2 強化学習の応用例
●マーケティングの場合
環境: 会社の販売促進部
エージェント: プロフィールと購入履歴に基づいて、キャンペーンメールを送る顧客を決めるソフト
ウェア
行動: 顧客ごとに送信、非送信のふたつの行動を選ぶ
報酬: キャンペーンのコストという負の報酬とキャンペーンで生み出されると推測される売上とい
う正の報酬を受ける
1-3 探索と利用のトレードオフ
●強化学習の前提
環境について事前に完璧な知識があれば、最適な行動を予測し決定することは可能
*どのような顧客にキャンペーンメールを送信すると、どのような行動を行うのかが既知である状
況など
⇒強化学習の場合、上記仮定は成り立たないとする。不完全な知識を元に行動しながら、データ
を収集。最適な行動を見つけていく
●探索と利用のトレードオフ
・過去のデータで、ベストとされる行動のみを常に取り続ければ他にもっとベストな行動を見つけ
ることはできない⇒探索が足りない状態
・一方未知の行動のみを常に取り続ければ、過去の経験が活かせない⇒利用が足りない状態
*上記両者はトレードオフの関係にある
1-4 強化学習のイメージ
1-5 強化学習の差分
●強化学習と通常の教師あり、教師なし学習との違い
目標が違う
・教師なし、あり学習では、データに含まれるパターンを見つけ出すおよびそのデータから予測す
ることが目標
・強化学習では、優れた方策を見つけることが目標
●強化学習の歴史
・冬の時代があったが、計算速度の進展により大規模な状態をもつ場合の、強化学習を可能とし
つつある
・関数近似法と、Q 学習を組み合わせる手法の登場
●Q 学習
行動価値関数を、行動する毎に更新することにより学習を進める方法
●関数近似法
価値関数や方策関数を関数近似する手法のこと
1-6 行動価値関数
●価値関数
価値を表す関数としては、状態価値関数と行動価値関数の 2 種類がある
・ある状態の価値に注目する場合は、状態価値関数
・状態と価値を組み合わせた価値に注目する場合は、行動価値関数
1-7 方策関数
●方策関数
方策ベースの強化学習手法において、ある状態でどのような行動を採るのかの確率を与える関
数のこと
1-8 方策勾配法
●方策反復法
方策をモデル化して最適化する手法
*J とは方策の良さのことであり、定義しなければならない
●定義方法
平均報酬や割引報酬和を用いる
上記の定義に対応して、行動価値関数:Q(s,a)の定義を行い。方策勾配定理が成り立つ。
Section2) Alpha Go
●Alpha Go の学習
1、教師あり学習による RollOutPolicy と PolicyNet の学習
2、強化学習による PolicyNet の学習
3、強化学習による ValueNet の学習
●モンテカルロ木探索
コンピュータ囲碁ソフトでは現在もっとも有効とされている探索法。
*囲碁では盤面の価値や勝率予想値を出すのが困難であるとされてきた。そこで、盤面評価値
に頼らず末端評価値、つまり勝敗のみを使って探索を行うことができないか、という発想で生まれ
た探索法である。
*囲碁の場合、他のボードゲームと違い最大手数はマスの数でほぼ限定されるため、末端局面
に到達しやすい。これを利用して現局面から末端局面まで PlayOut と呼ばれるランダムシミュレー
ションを多数回行い、その勝敗を集計して着手の優劣を決定する。
*また、該当手のシミュレーション回数が一定数を超えたら、その手を着手したあとの局面をシミ
ュレーション開始局面とするよう、探索木を成長させる。この探索木の成長を行うというのがモン
テカルロ木探索の優れているところである。モンテカルロ木探索はこの木の成長を行うことによっ
て、一定条件下において探索結果は最善手を返すということが理論的に証明されている。
●AlphaGo(Lee) と AlphaGoZero の違い
1、教師あり学習を一切行わず、強化学習のみで作成
2、特徴入力からヒューリスティックな要素を排除し、石の配置のみにした
3、PolicyNet と ValueNet を1つのネットワークに統合した
4、Residual Net(後述)を導入した
5、モンテカルロ木探索から RollOut シミュレーションをなくした
●Alpha Go Zero の学習法
Alpha Go の学習は自己対局による教師データの作成、学習、ネットワークの更新の3ステップで
構成される
*自己対局による教師データの作成: 現状のネットワークでモンテカルロ木探索を用いて自己対
局を行う。まず 30 手までランダムで打ち、そこから探索を行い勝敗を決定する。自己対局中の各
局面での着手選択確率分布と勝敗を記録する。教師データの形は(局面、着手選択確率分布、
勝敗)が1セットとなる。
*学習: 自己対局で作成した教師データを使い学習を行う。Network の Policy 部分の教師に着
手選択確率分布を用い、Value 部分の教師に勝敗を用いる。損失関数は Policy 部分は
CrossEntropy、Value 部分は平均二乗誤差。
*ネットワークの更新: 学習後、現状のネットワークと学習後のネットワークとで対局テストを行い、
学習後のネットワークの勝率が高かった場合、学習後のネットワークを現状のネットワークとする。
Section3) 軽量化・高速化技術
●分散深層学習とは
•深層学習は多くのデータを使用したり、パラメータ調整のために多くの時間を使用したりするた
め、高速な計算が求められる。
•複数の計算資源(ワーカー)を使用し、並列的にニューラルネットを構成することで、効率の良い
学習を行いたい。
•データ並列化、モデル並列化、GPU による高速技術は不可欠である。
3-1 データ並列
•親モデルを各ワーカーに子モデルとしてコピー
•データを分割し、各ワーカーに計算させる
●データ並列化:同期型
・データ並列化は各モデルのパラメータの合わせ方で、同期型か非同期型か決まる。
●データ並列化:非同期型
●同期型と非同期型の比較
•処理のスピードは、お互いのワーカーの計算を待たない非同期型の方が早い。
•非同期型は最新のモデルのパラメータを利用できないので、学習が不安定になりやすい。
•現在は同期型の方が精度が良いことが多いので、主流となっている。
3-2 モデル並列
•親モデルを各ワーカーに分割し、それぞれのモデルを学習させる。全てのデータで学習が終わ
った後で、一つのモデルに復元。
•モデルが大きい時はモデル並列化を、データが大きい時はデータ並列化をすると良い。
•モデルのパラメータ数が多いほど、スピードアップの効率も向上する。
3-3GPU
●GPU による高速化
•GPGPU (General-purpose on GPU)
元々の使用目的であるグラフィック以外の用途で使用される GPU の総称
•CPU
高性能なコアが少数
複雑で連続的な処理が得意
•GPU
比較的低性能なコアが多数
簡単な並列処理が得意
ニューラルネットの学習は単純な行列演算が多いので、高速化が可能
●GPGPU 開発環境
•CUDA
GPU 上で並列コンピューティングを行うためのプラットフォーム
NVIDIA 社が開発している GPU のみで使用可能。
Deep Learning 用に提供されているので、使いやすい
•OpenCL
オープンな並列コンピューティングのプラットフォーム
NVIDIA 社以外の会社(Intel, AMD, ARM など)の GPU からでも使用可能。
Deep Learning 用の計算に特化しているわけではない。
•Deep Learning フレームワーク(Tensorflow, Pytorch)内で実装されているので、使用する際は指
定すれば良い
3-4 量子化
●モデルの軽量化
モデルの精度を維持しつつパラメータや演算回数を低減する手法の総称
*高メモリ負荷
*高い演算性能が求められる
*通常は低メモリ
*低演算性能での利用が必要とされる
●モデルの軽量化の利用
モデルの軽量化はモバイル, IoT 機器において有用な手法
モバイル端末や IoT はパソコンに比べ性能が大きく劣る
*主に計算速度と搭載されているメモリ
*モデルの軽量化は計算の高速化と省メモリ化を行うためモバイル,IoT 機器と相性が良い手法
●軽量化の手法
代表的な手法として下記の 3 つがある
・量子化
・蒸留
・プルーニング
●量子化(Quantization)
ネットワークが大きくなると大量のパラメータが必要なり学習や推論に多くのメモリと演算処理が
必要である。そこで、通常のパラメータの 64 bit 浮動小数点を 32 bit など下位の精度に落とすこ
とでメモリと演算処理の削減を行う。
●量子化の利点と欠点
・利点:計算の高速化 省メモリ化
・欠点:精度の低下
●計算の高速化
倍精度演算(64 bit)と単精度演算(32 bit)は演算性能が大きく違うため、量子化により精度を落と
すことによりより多くの計算をすることができる。
●省メモリ化
ニューロンの重みを浮動小数点の bit 数を少なくし有効桁数を下げることで、ニューロンのメモリ
サイズを小さくすることができ、多くのメモリを消費するモデルのメモリ使用量を抑えることができ
る。
●極端な量子化
(例)表現できる値が 0,1 の 1 bit の場合:
a = 0.1 が真値の時、関数 y(x) = ax を近似する場合を考える際、学習によって a が 0.1 を得る
必要がある。しかし、量子化によって a が表現できる値が 0,1 のため求められる式は y(x) = 0,
y(x) =x のようになり、誤差の大きな式になってしまう。
⇒量子化する際は極端に精度が落ちない程度に量子化をしなければならない
3-5 蒸留
●蒸留
精度の高いモデルはニューロンの規模が大きいため、推論に多くのメモリと演算処理が必要。そ
こで、規模の大きなモデルを一度作成し、そこから軽量なモデルの作成を行う
●モデルの簡約化
学習済みの精度の高いモデルの知識を軽量なモデルへ継承させること。
⇒知識の継承により、軽量でありながら複雑なモデルに匹敵する精度のモデルを得ることが期待
できる
●教師モデルと生徒モデル
蒸留は教師モデルと生徒モデルの 2 つで構成される
*教師モデル:予測精度の高い、複雑なモデルやアンサンブルされたモデル
*生徒モデル:教師モデルをもとに作られる軽量なモデル
●蒸留のプロセス
・教師モデルの重みを固定し生徒モデルの重みを更新していく
・誤差は教師モデルと生徒モデルのそれぞれの誤差を使い重みを更新していく
●蒸留の利点
蒸留によって少ない学習回数でより精度の良いモデルを作成することができる
3-6 プルーニング
●プルーニング
ネットワークが大きくなると大量のパラメータが必要となるが、すべてのニューロンの計算が精度
に寄与しているわけではない。
⇒プルーニングではモデルの精度に寄与が少ないニューロンを削減することでモデルの軽量化、
高速化を目指す
●計算の高速化
寄与の少ないニューロンの削減を行いモデルの圧縮を行う
●ニューロンの削減
重みが閾値以下の場合ニューロンを削減し、再学習を行う
*閾値を高くするとニューロンは大きく削減できるが、精度も減少する
Section4) 応用技術
4-1 MobileNet
●MobileNet
*論文タイトル
MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
*提案手法
・ディープラーニングモデルは精度は良いが、その分ネットワークが深くなり計算量が増える
・計算量が増えると、多くの計算リソースが必要で、お金がかかってしまう
・ディープラーニングモデルの軽量化・高速化・高精度化を実現
●一般的な畳み込みレイヤー
入力特徴マップ(チャネル数):H×W×C
畳込みカーネルのサイズ:K×K×C
出力チャネル数(フィルタ数):M
ストライド1でパディングを適用した場合の畳み込み計算の計算量: H×W×K×K×C×M
⇒MobileNets は Depthwise Convolution と Pointwise Convolution の組み合わせで軽量化を実現
●Depthwise Convolution
入力マップのチャネルごとに畳み込みを実施
出力マップをそれらと結合(入力マップのチャネル数と同じになる)
通常の畳み込みカーネルは全ての層にかかっていることを考えると計算量が大幅に削減可能
各層ごとの畳み込みなので層間の関係性は全く考慮されない。通常は PW 畳み込みとセットで使
うことで解決 計算量: H×W×C×K×K
●Pointwise Convolution
1 x 1 conv とも呼ばれる(正確には 1 x 1 x c)
入力マップのポイントごとに畳み込みを実施
出力マップ(チャネル数)はフィルタ数分だけ作成可能(任意のサイズが指定可能)
計算量は H×W×C×M
●MobileNet のアーキテクチャ
・Depthwise Separable Convolution という手法を用いて計算量を削減している。通常の畳込みが
空間方向とチャネル方向の計算を同時に行うのに対して、Depthwise Separable Convolution では
それらを Depthwise Convolution と Pointwise Convolution と呼ばれる演算によって個別に行う。
・Depthwise Convolition はチャネル毎に空間方向へ畳み込む。すなわち、チャネル毎に
DK×DK×1のサイズのフィルターをそれぞれ用いて計算を行うため、その計算量は(い)となる。
・次にDepthwise Convolutionの出力をPointwise Convolutionによってチャネル方向に畳み込む。
すなわち、出力チャネル毎に1×1×M サイズのフィルターをそれぞれ用いて計算を行うため、そ
の計算量は(う)となる。
(い) H×W×C×K×K
(う) H×W×C×M
4-2 DenseNet
*論文タイトル
Densely Connected Convolutional Networks. G. Huang et., al. 2016
●Dense Convolutional Network(DenseNet)
畳込みニューラルネットワーク(以下、CNN)アーキテクチャの一種である。ニューラルネットワーク
では層が深くなるにつれて、学習が難しくなるという問題があったが、Residual Network(以下、
ResNet)などの CNN アーキテクチャでは前方の層から後方の層へアイデンティティ接続を介して
パスを作ることで問題を対処した。DenseBlock と呼ばれるモジュールを用いた、DenseNet もその
ようなアーキテクチャの一つである。
●DenseNet と ResNet の違い
・DenseBlock では前方の各層からの出力全てが後方の層への入力として用いられる
・RessidualBlock では前 1 層の入力のみ後方の層へ入力
*DenseNet 内で使用される DenseBlock と呼ばれるモジュールでは成⻑率(Growth Rate)と呼ば
れるハイパーパラメータが存在する。
*DenseBlock 内の各ブロック毎に k 個ずつ特徴マップのチャネル数が増加していく時、k を成⻑
率と呼ぶ
4-3 Layer 正規化/Instance 正規化
●Batch Norm Layer
レイヤー間を流れるデータの分布を、ミニバッチ単位で平均が 0、分散が 1 になるように正規化
Batch Normalization はニューラルネットワークにおいて学習時間の短縮や初期値への依存低減、
過学習の抑制など効果がある。
●Batch Norm の問題点
Batch Size が小さい条件下では、学習が収束しないことがあり、代わりに Layer Normalization な
どの正規化手法が使われることが多い。
●Batch Norm 以外の正規化
*Batch Norm
ミニバッチに含まれる sample の同一チャネルが同一分布に従うよう正規化
*Layer Norm
それぞれの sample の全ての pixels が同一分布に従うよう正規化
*Instance Nrom
さらに channel も同一分布に従うよう正規化
4-4Wavenet
●Wavenet
生の音声波形を生成する深層学習モデル
Pixcel CNN(高解像度の画像を精密に精製できる手法)を音声に応用したもの
●Wavenet のメインアイデア
・時系列データに対して畳み込みを適用する
・Dilated convolution:
層が深くなるにつれて畳み込むリンクを離す
受容野を簡単に増やすことができる
*確認テスト
深層学習を用いて結合確率を学習する際に、効率的に学習が行えるアーキテクチャを提案したこ
とが WaveNet の大きな貢献の 1 つである。提案された新しい Convolution 型アーキテクチャは
(あ)と呼ばれ、結合確率を効率的に学習できるようになっている。
・Dilated causal convolution
・Depthwise separable convolution
・Pointwise convolution
・Deconvolution
Dilated causal convolution
(あ)を用いた際の大きな利点は、単純な Convolution layer と比べて(い)ことである。
・パラメータ数に対する受容野が広い
・受容野あたりのパラメータ数が多い
・学習時に並列計算が行える
・推論時に並列計算が行える
パラメータ数に対する受容野が広い
物体検知の基礎とセマンティックセグメンテーション
Section1 Introduction
●広義の物体認識タスク
Section2 代表的データセット
・データ選択学習のために重要
・データセット
*Box/画像が少ない→映っているものが少ない
●VOC12
・VOC は Visual Object Classes の略
・主要貢献者が 2012 年に亡くなったことに伴いコンペも終了
●ILSVRC17
・ILSVRC は ImageNet Scale Visual Recognition Challenge の略
・コンペは 2017 年に終了しているが、後継は Open Images Challenge
・ImageNet(21,841 クラス/1400 万枚以上)のサブセット
●MS COCO18
・COCO は Common Object in Context の略
・物体位置推定に対する新たな評価指標 mAPCOCO を提案
●OICOD18
・ILSVRC や MS COCO とは異なる annotation process
・Open Images V4(6000 クラス以上/900 万枚以上)のサブセット
Section3 評価指標
●Confusion Matrix
● IoU
物体検出において物体位置の予測精度を評価する指標(Intersection over Union)。Area of
Overlap / Area of Union にて計算する。Confusion Matrix の要素で表現すると、TP / (TP + FP +
FN)となる。Jaccard 係数とも呼ばれる。
●AP
判定の confidence の閾値を変化させると、一般的に Precision も Recall も変化していくが、これら
2 つの指標はトレードオフの関係にある場合がある。そこで Precision・Recall を加味した AP
(Average Precision)という指標が用いられる。クラスラベルごとに計算される。
●mAP
AP がクラスラベルごとに別々に計算される指標であるのに対し、mAP はクラスラベルごとに平均
して計算される。MS COCO で導入された
●FPS
Flames per Second の略で、検出精度に加え検出速度を考慮した指標。
Section4 物体検知の大枠
●マイルストーン
2012 年 AlexNet の登場を皮切りに、時代は SIFT から DCNN へ。2013 年以降、ベースネットワー
クとして 2014-15 年に VGG Net, Google Lenet、2016 年に Res Net、2016-17 年に Inception-
ResNet、DenseNet、2018 年に MobileNet、AmoebaNet が出てきた。さらに、物体検知のフレームワ
ークとして、YOLO などが出てきた。
●フレームワーク
1 段階検出器や 2 段階検出器がある。
1 段階検出器では、候補領域の検出とクラス推定を同時に行う。精度は高い。計算量は小さくて、
推論も速い。
2 段階検出器は、候補領域の検出とクラス推定を別々に行う。相対的に精度は高い。計算量は大
きく、推定も遅い。
Section5 SSD: Single Shot Detector
●Single shot Detector
1 段階検出器。検出したい物体に対して、default box なる適当な BB を用意する。この Default
box が優れた BB になるように変形していくよう学習していく。また、confidence も出力する。
●VGG16 畳み込みと全結合層あわせて 16 層。これを SSD に利用する。
●SSD のネットワークアーキテクチャ
●SSD のデフォルトボックス数
●特徴マップからの出力
●その他の工夫
*Non-Maximum Suppression
*Hard Negative Mining
Section5 Semantic Segmentation
●Semantic Segmentation における問題
入力値と同じサイズの出力の各ピクセルに対して判定を行いたいが、Convolution と Pooling を繰
り返すうちに解像度が下がってしまう
●FCN(Fully Convolutional Network)
その物体らしさを表すヒートマップを作成する
●Deconvolution/Transposed convolution
Up Sampling の方法
●輪郭情報の補完
Pooling によりローカルな情報(≒輪郭)が失われていく
⇒低レイヤーPooling 層の出力を element-wise addition することでローカルな情報を補完してか
ら Up-sampling
●U-Net
●Dilated Convolution
Convolution の段階で受容野を広げる工夫
Seq2seq
●Seq2seq とは?
系列(Sequence)を入力として、系列を出力するもの-
*Encoder-Decoder モデルとも呼ばれる –
*入力系列が Encode(内部状態に変換)され、内部状態から Decode(系列に変換)する –
*実応用上も、入力・出力共に系列情報なものは多い –
翻訳 (英語→日本語) –
音声認識 (波形→テキスト) –
チャットボット (テキスト→テキスト)
●Seq2Seq の理解に必要な材料
要は言語モデルをふたつ連結した形になっている-
*RNN の理解
RNN の動作原理
LSTM などの改良版 RNN の理解
*言語モデルの理解
●言語モデル
・言語モデルは単語の並びに確率を与える-
⇒単語の並びに対して尤度(それがどれだけ起こり得るか)、すなわち、文章として自然かを確率
で評価する
例) You say goodbye → 0.092 (自然)
You say good die → 0.00000032 (不自然)
*数式的には同時確率を事後確率に分解して表せる
・時刻 t-1 までの情報で、時刻 t の事後確率を求めることが目標 →これで同時確率が計算できる
●RNN x 言語モデル
・RNN は系列情報を内部状態に変換することができる
・文章の各単語が現れる際の同時確率は、事後確率で分解できる
⇒したがって、事後確率を求めることが RNN の目標になる
・言語モデルを再現するように RNN の重みが学習されていれば、ある時点の次の単語を予測す
ることができる
⇒先頭単語を与えれば文章を生成することも可能
●Seq2seq の鍵
Transformer
―Self-Attention(自己注意機構)に焦点を当ててー
●ニューラル機械翻訳の問題点
長さに弱い
*翻訳元の文の内容をひとつのベクトルで表現
⇒文長が長くなると表現力が足りなくなる –
*文長と翻訳精度の関係性
●Attention は何をしているのか
Attention は辞書オブジェクト
*query(検索クエリ)に一致する key を索引し、対応する value を取り出す操作であると見做すこ
とができる。これは辞書オブジェクトの機能と同じである。
●Transformer
2017 年 6 月に登場
RNN を使わない –
⇒必要なのは Attention だけ –
当時の SOTA をはるかに少ない計算量で実現
●Attention
注意機構には二種類ある
●Transformer-Encoder
自己注意機構(Self-Attention)により文脈を考慮して各単語をエンコードしている。
●Position-Wise Feed-Forward Networks
位置情報を保持したまま順伝播を行う仕組みにより各 Attention 層の出力を決定している
*線形変換 → ReLu → 線形変換の 2 層の全結合 NN の構造
●Scaled dot product attention
全単語に関する Attention をまとめて計算する
●Multi-Head attention
8 個の Scaled Dot Product Attention の出力を Concat し、それぞれのヘッドが、異なる種類の情
報を収集する。
●Transformer-Decoder
Encoder と同じく 6 層構造で、各層で Self-Attention と Encoder-Decoder attention の 2 種類の注
意機構を実装している。注意機構の仕組みは Encoder とほぼ同じ。
●Add & Norm
Decoder の各層に 3 つ、Encoder の各層にも 2 つずつ存在する。
*Add (Residual Connection)
入出力の差分を学習させる手法であるが、実装上は出力に入力をそのまま加算させる機構
⇒学習・テストエラーを低減させる効果がある。
*Norm (Layer Normalization)
各層においてバイアスを除く活性化関数への入力を平均0、分散1に正規化する手法
⇒学習を高速化させる効果がある。
●Position Encoding
RNN を用いないので単語列の語順情報を追加する必要があるため、単語の位置情報の分散表
現に加算してエンコードする仕組み
●まとめ
GAN
●生成器と識別器を競わせて学習する生成&識別モデル
*Generator: 乱数からデータを生成
*Discriminator: 入力データが真データ(学習データ)であるかを識別
●2 プレイヤーのミニマックスゲーム
*人が自分の勝利する確率を最大化する作戦を取る
*もう一人は相手が勝利する確率を最小化する作戦を取る
●DCGAN(Deep Convolutional GAN)
GAN を利用した画像生成モデルであり、いくつかの構造制約により生成品質を向上
*Generator
•Pooling 層の代わりに転置畳み込み層を使用
•最終層は tanh、その他は ReLU 関数で活性化
*Discriminator
•Pooling 層の代わりに畳み込み層を使用
•Leaky ReLU 関数で活性化
*共通事項
•中間層に全結合層を使わない
•バッチノーマライゼーションを適用
●DCGAN のネットワーク構造
*Generator
転置畳み込み層により乱数を画像にアップサンプリング
*Discriminator
畳み込み層により画像から特徴量を抽出し、最終層を sigmoid 関数で活性化
演習 Sequence-to-Sequence (Seq2Seq) モデル
Sequence-to-Sequence (Seq2Seq) モデルは、系列を入力として系列を出力するモデルです。
入力系列をRNNで固定長のベクトルに変換(= Encode)し、そのベクトルを用いて系列を出力(=
Decode)することから、Encoder-Decoder モデルとも呼ばれます。
RNNの代わりにLSTMやGRUでも可能です。
機械翻訳のほか、文書要約や対話生成にも使われます。
今回は機械翻訳を例にとって解説していきます。
編集するにはダブルクリックするか Enter キーを押してください
Collecting wheel==0.34.2
Downloading wheel-0.34.2-py2.py3-none-any.whl (26 kB)
Installing collected packages: wheel
Attempting uninstall: wheel
Found existing installation: wheel 0.37.0
Uninstalling wheel-0.37.0:
Successfully uninstalled wheel-0.37.0
ERROR: pip's dependency resolver does not currently take into account all the packages that a
tensorflow 2.7.0 requires tensorboard~=2.6, but you have tensorboard 2.2.2 which is incompati
tensorflow 2.7.0 requires tensorflow-estimator<2.8,~=2.7.0rc0, but you have tensorflow-estima
Successfully installed wheel-0.34.2
WARNING: The following packages were previously imported in this runtime:
[wheel]
You must restart the runtime in order to use newly installed versions.
RESTART RUNTIME
%pip install "wheel==0.34.2"
from os import path
from wheel.pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag
platform = '{}{}-{}'.format(get_abbr_impl(), get_impl_ver(), get_abi_tag())
accelerator = 'cu80' if path.exists('/opt/bin/nvidia-smi') else 'cpu'
!pip install -q http://download.pytorch.org/whl/{accelerator}/torch-0.4.0-{platform}-linux_x86_64.w
import torch
print(torch.__version__)
print(torch.cuda.is_available())
ERROR: HTTP error 403 while getting http://download.pytorch.org/whl/cu80/torch-0.4.0-cp37-c
ERROR: Could not install requirement torch==0.4.0 from http://download.pytorch.org/whl/cu80/t
1.10.0+cu111
True
! wget https://www.dropbox.com/s/9narw5x4uizmehh/utils.py
! mkdir images data
# data取得
! wget https://www.dropbox.com/s/o4kyc52a8we25wy/dev.en -P data/
! wget https://www.dropbox.com/s/kdgskm5hzg6znuc/dev.ja -P data/
! wget https://www.dropbox.com/s/gyyx4gohv9v65uh/test.en -P data/
! wget https://www.dropbox.com/s/hotxwbgoe2n013k/test.ja -P data/
! wget https://www.dropbox.com/s/5lsftkmb20ay9e1/train.en -P data/
! wget https://www.dropbox.com/s/ak53qirssci6f1j/train.ja -P data/
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/hotxwbgoe2n013k/test.ja [following]
--2021-12-22 23:57:39-- https://www.dropbox.com/s/raw/hotxwbgoe2n013k/test.ja
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc7e6f5a2abd38b86c96369cba22.dl.dropboxusercontent.com/cd/0/inline/BcXpPf
--2021-12-22 23:57:39-- https://uc7e6f5a2abd38b86c96369cba22.dl.dropboxusercontent.com/cd/
Resolving uc7e6f5a2abd38b86c96369cba22.dl.dropboxusercontent.com (uc7e6f5a2abd38b86c96369cb
Connecting to uc7e6f5a2abd38b86c96369cba22.dl.dropboxusercontent.com (uc7e6f5a2abd38b86c963
HTTP request sent, awaiting response... 200 OK
Length: 27793 (27K) [text/plain]
Saving to: ‘data/test.ja’
test.ja 100%[===================>] 27.14K --.-KB/s in 0.02s
2021-12-22 23:57:39 (1.51 MB/s) - ‘data/test.ja’ saved [27793/27793]
--2021-12-22 23:57:39-- https://www.dropbox.com/s/5lsftkmb20ay9e1/train.en
Resolving www.dropbox.com (www.dropbox.com)... 162.125.3.18, 2620:100:6034:18::a27d:5412
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/5lsftkmb20ay9e1/train.en [following]
--2021-12-22 23:57:40-- https://www.dropbox.com/s/raw/5lsftkmb20ay9e1/train.en
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc7582c47753e0ad9ee8ea4130e8.dl.dropboxusercontent.com/cd/0/inline/BcX2_J
--2021-12-22 23:57:40-- https://uc7582c47753e0ad9ee8ea4130e8.dl.dropboxusercontent.com/cd/
Resolving uc7582c47753e0ad9ee8ea4130e8.dl.dropboxusercontent.com (uc7582c47753e0ad9ee8ea413
Connecting to uc7582c47753e0ad9ee8ea4130e8.dl.dropboxusercontent.com (uc7582c47753e0ad9ee8e
HTTP request sent, awaiting response... 200 OK
Length: 1701356 (1.6M) [text/plain]
Saving to: ‘data/train.en’
train.en 100%[===================>] 1.62M --.-KB/s in 0.1s
2021-12-22 23:57:40 (12.4 MB/s) - ‘data/train.en’ saved [1701356/1701356]
--2021-12-22 23:57:40-- https://www.dropbox.com/s/ak53qirssci6f1j/train.ja
Resolving www.dropbox.com (www.dropbox.com)... 162.125.3.18, 2620:100:601b:18::a27d:812
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/ak53qirssci6f1j/train.ja [following]
--2021-12-22 23:57:41-- https://www.dropbox.com/s/raw/ak53qirssci6f1j/train.ja
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc20f19ec7a1a9d6d90e6737f64a.dl.dropboxusercontent.com/cd/0/inline/BcWqUV
--2021-12-22 23:57:41-- https://uc20f19ec7a1a9d6d90e6737f64a.dl.dropboxusercontent.com/cd/
(
Resolving uc20f19ec7a1a9d6d90e6737f64a.dl.dropboxusercontent.com (uc20f19ec7a1a9d6d90e6737f
Connecting to uc20f19ec7a1a9d6d90e6737f64a.dl.dropboxusercontent.com (uc20f19ec7a1a9d6d90e6
HTTP request sent, awaiting response... 200 OK
Length: 2784447 (2.7M) [text/plain]
Saving to: ‘data/train.ja’
train.ja 100%[===================>] 2.66M --.-KB/s in 0.07s
2021-12-22 23:57:41 (36.2 MB/s) - ‘data/train.ja’ saved [2784447/2784447]
! ls data
dev.en	 dev.ja	 test.en test.ja train.en train.ja
import random
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
from nltk import bleu_score
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.nn.utils.rnn import pad_packed_sequence, pack_padded_sequence
from utils import Vocab
# デバイスの設定
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.manual_seed(1)
random_state = 42
print(torch.__version__)
1.10.0+cu111
英語-日本語の対訳コーパスである、Tanaka Corpus (
http://www.edrdg.org/wiki/index.php/Tanaka_Corpus )を使います。

今回はそのうちの一部分を取り出したsmall_parallel_enja: 50k En/Ja Parallel Corpus for Testing
SMT Methods ( https://github.com/odashi/small_parallel_enja )を使用します。
train.enとtrain.jaの中身を見てみましょう。
1.データセットの準備
! head -10 data/train.en
i can 't tell who will arrive first .
many animals have been destroyed by men .
i 'm in the tennis club .
emi looks happy .
please bear this fact in mind .
she takes care of my children .
we want to be international .
you ought not to break your promise .
when you cross the street , watch out for cars .
i have nothing to live for .
! head -10 ./data/train.ja
誰 が 一番 に 着 く か 私 に は 分か り ま せ ん 。
多く の 動物 が 人間 に よ っ て 滅ぼ さ れ た 。
私 は テニス 部員 で す 。
エミ は 幸せ そう に 見え ま す 。
この 事実 を 心 に 留め て お い て 下さ い 。
彼女 は 私 たち の 世話 を し て くれ る 。
私 達 は 国際 人 に な り た い と 思 い ま す 。
約束 を 破 る べ き で は あ り ま せ ん 。
道路 を 横切 る とき は 車 に 注意 し なさ い 。
私 に は 生き 甲斐 が な い 。
それぞれの文章が英語-日本語で対応しているのがわかります。
1.1データの読み込みと単語の分割
def load_data(file_path):
    # テキストファイルからデータを読み込むメソッド
    data = []
    for line in open(file_path, encoding='utf-8'):
        words = line.strip().split()  # スペースで単語を分割
        data.append(words)
    return data
train_X = load_data('./data/train.en')
train_Y = load_data('./data/train.ja')
# 訓練データと検証データに分割
train_X, valid_X, train_Y, valid_Y = train_test_split(train_X, train_Y, test_size=0.2, random_state
この時点で入力と教師データは以下のようになっています
print('train data', train_X[0])
print('valid data', valid_X[0])
train data ['where', 'shall', 'we', 'eat', 'tonight', '?']
valid data ['you', 'may', 'extend', 'your', 'stay', 'in', 'tokyo', '.']
データセットに登場する各単語にIDを割り振る
1.2単語辞書の作成
# まず特殊トークンを定義しておく
PAD_TOKEN = '<PAD>'  # バッチ処理の際に、短い系列の末尾を埋めるために使う (Padding)
BOS_TOKEN = '<S>'  # 系列の始まりを表す (Beggining of sentence)
EOS_TOKEN = '</S>'  # 系列の終わりを表す (End of sentence)
UNK_TOKEN = '<UNK>'  # 語彙に存在しない単語を表す (Unknown)
PAD = 0
BOS = 1
EOS = 2
UNK = 3
MIN_COUNT = 2  # 語彙に含める単語の最低出現回数 再提出現回数に満たない単語はUNKに置き換えられる
# 単語をIDに変換する辞書の初期値を設定
word2id = {
    PAD_TOKEN: PAD,
    BOS_TOKEN: BOS,
    EOS_TOKEN: EOS,
    UNK_TOKEN: UNK,
    }
# 単語辞書を作成
vocab_X = Vocab(word2id=word2id)
vocab_Y = Vocab(word2id=word2id)
vocab_X.build_vocab(train_X, min_count=MIN_COUNT)
vocab_Y.build_vocab(train_Y, min_count=MIN_COUNT)
vocab_size_X = len(vocab_X.id2word)
vocab_size_Y = len(vocab_Y.id2word)
print('入力言語の語彙数:', vocab_size_X)
print('出力言語の語彙数:', vocab_size_Y)
入力言語の語彙数: 3725
出力言語の語彙数: 4405
2.テンソルへの変換
まずはモデルが文章を認識できるように、文章を単語IDのリストに変換します
2.1 IDへの変換
def sentence_to_ids(vocab, sentence):
    # 単語(str)のリストをID(int)のリストに変換する関数
    ids = [vocab.word2id.get(word, UNK) for word in sentence]
    ids += [EOS]  # EOSを加える
    return ids
train_X = [sentence_to_ids(vocab_X, sentence) for sentence in train_X]
train_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in train_Y]
valid_X = [sentence_to_ids(vocab_X, sentence) for sentence in valid_X]
valid_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in valid_Y]
この時点で入力と教師データは以下のようになっている
print('train data', train_X[0])
print('valid data', valid_X[0])
train data [132, 321, 28, 290, 367, 12, 2]
valid data [8, 93, 3532, 36, 236, 13, 284, 4, 2]
データセットからバッチを取得するデータローダーを定義します
この際、長さの異なる複数の系列をバッチで並列に扱えるように、短い系列の末尾を特定
のシンボル( <PAD> など)でパディングし、バッチ内の系列の長さを最長のものに合わせ
る
(batch_size, max_length)のサイズの行列を得るが、実際にモデルを学習させるときには、
バッチをまたいで各時刻ごとに進めていくので、転置して(max_length, batch_size)の形に
変える
(batch_first=Trueのオプションを使う場合は不要)
2.2 DataLoaderの定義
def pad_seq(seq, max_length):
    # 系列(seq)が指定の文長(max_length)になるように末尾をパディングする
    res = seq + [PAD for i in range(max_length - len(seq))]
    return res    
class DataLoader(object):
    def __init__(self, X, Y, batch_size, shuffle=False):
        """
        :param X: list, 入力言語の文章(単語IDのリスト)のリスト
        :param Y: list, 出力言語の文章(単語IDのリスト)のリスト
        :param batch_size: int, バッチサイズ
        :param shuffle: bool, サンプルの順番をシャッフルするか否か
        """
        self.data = list(zip(X, Y))
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.start_index = 0
        
        self.reset()
    
    def reset(self):
        if self.shuffle:  # サンプルの順番をシャッフルする
            self.data = shuffle(self.data, random_state=random_state)
        self.start_index = 0  # ポインタの位置を初期化する
    
    def __iter__(self):
        return self
    def __next__(self):
        # ポインタが最後まで到達したら初期化する
        if self.start_index >= len(self.data):
            self.reset()
            raise StopIteration()
        # バッチを取得
        seqs_X, seqs_Y = zip(*self.data[self.start_index:self.start_index+self.batch_size])
        # 入力系列seqs_Xの文章の長さ順(降順)に系列ペアをソートする
        seq_pairs = sorted(zip(seqs_X, seqs_Y), key=lambda p: len(p[0]), reverse=True)
        seqs_X, seqs_Y = zip(*seq_pairs)
        # 短い系列の末尾をパディングする
        lengths_X = [len(s) for s in seqs_X]  # 後述のEncoderのpack_padded_sequenceでも用いる
        lengths_Y = [len(s) for s in seqs_Y]
        max_length_X = max(lengths_X)
        max_length_Y = max(lengths_Y)
        padded_X = [pad_seq(s, max_length_X) for s in seqs_X]
        padded_Y = [pad_seq(s, max_length_Y) for s in seqs_Y]
        # tensorに変換し、転置する
        batch_X = torch.tensor(padded_X, dtype=torch.long, device=device).transpose(0, 1)
        batch_Y = torch.tensor(padded_Y, dtype=torch.long, device=device).transpose(0, 1)
        # ポインタを更新する
        self.start_index += self.batch_size
        return batch_X, batch_Y, lengths_X
EncoderとDecoderのRNNを定義します。
3.モデルの構築
導入:PackedSequence
PyTorchのRNNでは、可変長の系列のバッチを効率よく計算できるように系列を表現する
PackedSequence というクラスを用いることができます。
入力バッチのテンソルをこの PackedSequence のインスタンスに変換してからRNNに入力すること
で、パディング部分の計算を省略することができるため、効率的な計算が可能になります。
PackedSequence を作成するには、まず、系列長の異なるバッチに対してパディングを行なってく
ださい。
ここで、パディングを行う前に各サンプルの系列長( lengths )を保存しておきます。
# 系列長がそれぞれ4,3,2の3つのサンプルからなるバッチを作成
batch = [[1,2,3,4], [5,6,7], [8,9]]
lengths = [len(sample) for sample in batch]
print('各サンプルの系列長:', lengths)
print()
# 最大系列長に合うように各サンプルをpadding
_max_length = max(lengths)
padded = torch.tensor([pad_seq(sample, _max_length) for sample in batch])
print('paddingされたテンソル:n', padded)
padded = padded.transpose(0,1) # (max_length, batch_size)に転置
print('padding & 転置されたテンソル:n', padded)
print('padding & 転置されたテンソルのサイズ:n', padded.size())
print()
各サンプルの系列長: [4, 3, 2]
paddingされたテンソル:
tensor([[1, 2, 3, 4],
[5, 6, 7, 0],
[8, 9, 0, 0]])
padding & 転置されたテンソル:
tensor([[1, 5, 8],
[2, 6, 9],
[3, 7, 0],
[4, 0, 0]])
padding & 転置されたテンソルのサイズ:
torch.Size([4, 3])
次に、パディングを行ったテンソル( padded )と各サンプルの元々の系列長( lengths )を
torch.nn.utils.rnn.pack_padded_sequence という関数に与えると、
 data と batch_sizes という要素
を持った PackedSequence のインスタンス( packed )が作成できます。
data : テンソルの PAD 以外の値のみを保有するベクトル
batch_sizes : 各時刻で計算が必要な(= PAD に到達していない)バッチの数を表すベクトル
# PackedSequenceに変換(テンソルをRNNに入力する前に適用する)
packed = pack_padded_sequence(padded, lengths=lengths) # 各サンプルの系列長も与える
print('PackedSequenceのインスタンス:n', packed) # テンソルのPAD以外の値(data)と各時刻で計算が必要な
print()
PackedSequenceのインスタンス:
PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 4]), batch_sizes=tensor([3, 3, 2, 1]), s
こうして得られた PackedSequence のインスタンスをRNNに入力します。(ここでは省略)
RNNから出力されたテンソルは PackedSeauence のインスタンスのままなので、後段の計算につな
ぐために torch.nn.utils.rnn.pad_packed_sequence の関数によって通常のテンソルに戻します。
# PackedSequenceのインスタンスをRNNに入力する(ここでは省略)
output = packed
# テンソルに戻す(RNNの出力に対して適用する)
output, _length = pad_packed_sequence(output)  # PADを含む元のテンソルと各サンプルの系列長を返す
print('PADを含む元のテンソル:n', output)
print('各サンプルの系列長:', _length)
PADを含む元のテンソル:
tensor([[1, 5, 8],
[2, 6, 9],
[3, 7, 0],
[4, 0, 0]])
各サンプルの系列長: tensor([4, 3, 2])
今回はEncoder側でバッチを処理する際に、 pack_padded_sequence 関数によってtensorを
PackedSequence に変換し、処理を終えた後に pad_packed_sequence 関数によってtensorに戻すとい
う処理を行います。
Encoder
class Encoder(nn.Module):
    def __init__(self, input_size, hidden_size):
        """
        :param input_size: int, 入力言語の語彙数
        :param hidden_size: int, 隠れ層のユニット数
        """
        super(Encoder, self).__init__()
        self.hidden_size = hidden_size
        self.embedding = nn.Embedding(input_size, hidden_size, padding_idx=PAD)
        self.gru = nn.GRU(hidden_size, hidden_size)
    def forward(self, seqs, input_lengths, hidden=None):
        """
        :param seqs: tensor, 入力のバッチ, size=(max_length, batch_size)
        :param input_lengths: 入力のバッチの各サンプルの文長
        :param hidden: tensor, 隠れ状態の初期値, Noneの場合は0で初期化される
        :return output: tensor, Encoderの出力, size=(max_length, batch_size, hidden_size)
        :return hidden: tensor, Encoderの隠れ状態, size=(1, batch_size, hidden_size)
        """
        emb = self.embedding(seqs) # seqsはパディング済み
        packed = pack_padded_sequence(emb, input_lengths) # PackedSequenceオブジェクトに変換
        output, hidden = self.gru(packed, hidden)
        output, _ = pad_packed_sequence(output)
        return output, hidden
今回はDecoder側ではパディング等行わないので、通常のtensorのままRNNに入力して問題あり
ません。
Decoder
class Decoder(nn.Module):
    def __init__(self, hidden_size, output_size):
        """
        :param hidden_size: int, 隠れ層のユニット数
        :param output_size: int, 出力言語の語彙数
        :param dropout: float, ドロップアウト率
        """
        super(Decoder, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.embedding = nn.Embedding(output_size, hidden_size, padding_idx=PAD)
        self.gru = nn.GRU(hidden_size, hidden_size)
        self.out = nn.Linear(hidden_size, output_size)
    def forward(self, seqs, hidden):
        """
        :param seqs: tensor, 入力のバッチ, size=(1, batch_size)
        :param hidden: tensor, 隠れ状態の初期値, Noneの場合は0で初期化される
        :return output: tensor, Decoderの出力, size=(1, batch_size, output_size)
        :return hidden: tensor, Decoderの隠れ状態, size=(1, batch_size, hidden_size)
        """
        emb = self.embedding(seqs)
        output, hidden = self.gru(emb, hidden)
        output = self.out(output)
        return output, hidden
上で定義したEncoderとDecoderを用いた、一連の処理をまとめるEncoderDecoderのクラスを
定義します。
ここで、Decoder側の処理で注意する点があります。
RNNでは、時刻 の出力を時刻 の入力とすることができるが、この方法でDecoderを学習
させると連鎖的に誤差が大きくなっていき、学習が不安定になったり収束が遅くなったりする
問題が発生します。
この問題への対策としてTeacher Forcingというテクニックがあります。
これは、訓練時には
Decoder側の入力に、ターゲット系列(参照訳)をそのまま使うというものです。
これにより
学習が安定し、収束が早くなるというメリットがありますが、逆に評価時は前の時刻に
Decoderが生成したものが使われるため、学習時と分布が異なってしまうというデメリットも
あります。
EncoderDecoder
t t + 1
Teacher Forcingの拡張として、ターゲット系列を入力とするか生成された結果を入力とするか
を確率的にサンプリングするScheduled Samplingという手法があります。
ここではScheduled Samplingを採用し、一定の確率に基づいてターゲット系列を入力とするか
生成された結果を入力とするかを切り替えられるようにクラスを定義しておきます。
class EncoderDecoder(nn.Module):
    """EncoderとDecoderの処理をまとめる"""
    def __init__(self, input_size, output_size, hidden_size):
        """
        :param input_size: int, 入力言語の語彙数
        :param output_size: int, 出力言語の語彙数
        :param hidden_size: int, 隠れ層のユニット数
        """
        super(EncoderDecoder, self).__init__()
        self.encoder = Encoder(input_size, hidden_size)
        self.decoder = Decoder(hidden_size, output_size)
    def forward(self, batch_X, lengths_X, max_length, batch_Y=None, use_teacher_forcing=False):
        """
        :param batch_X: tensor, 入力系列のバッチ, size=(max_length, batch_size)
        :param lengths_X: list, 入力系列のバッチ内の各サンプルの文長
        :param max_length: int, Decoderの最大文長
        :param batch_Y: tensor, Decoderで用いるターゲット系列
        :param use_teacher_forcing: Decoderでターゲット系列を入力とするフラグ
        :return decoder_outputs: tensor, Decoderの出力, 
            size=(max_length, batch_size, self.decoder.output_size)
        """
        # encoderに系列を入力(複数時刻をまとめて処理)
        _, encoder_hidden = self.encoder(batch_X, lengths_X)
        
        _batch_size = batch_X.size(1)
        # decoderの入力と隠れ層の初期状態を定義
        decoder_input = torch.tensor([BOS] * _batch_size, dtype=torch.long, device=device) # 最初の
        decoder_input = decoder_input.unsqueeze(0)  # (1, batch_size)
        decoder_hidden = encoder_hidden  # Encoderの最終隠れ状態を取得
        # decoderの出力のホルダーを定義
        decoder_outputs = torch.zeros(max_length, _batch_size, self.decoder.output_size, device=dev
        # 各時刻ごとに処理
        for t in range(max_length):
            decoder_output, decoder_hidden = self.decoder(decoder_input, decoder_hidden)
            decoder_outputs[t] = decoder_output
            # 次の時刻のdecoderの入力を決定
            if use_teacher_forcing and batch_Y is not None:  # teacher forceの場合、ターゲット系列を
                decoder_input = batch_Y[t].unsqueeze(0)
            else:  # teacher forceでない場合、自身の出力を用いる
                decoder_input = decoder_output.max(-1)[1]
                
        return decoder_outputs
4.1 損失関数の定義
基本的にはクロスエントロピーを損失関数として用いますが、パディングを行うと短い系列の
末尾には <PAD> トークンが入るため、この部分の損失を計算しないように、マスクをかけます。
4.訓練
mce = nn.CrossEntropyLoss(size_average=False, ignore_index=PAD) # PADを無視する
def masked_cross_entropy(logits, target):
    logits_flat = logits.view(-1, logits.size(-1)) # (max_seq_len * batch_size, output_size)
    target_flat = target.view(-1) # (max_seq_len * batch_size, 1)
    return mce(logits_flat, target_flat)
/usr/local/lib/python3.7/dist-packages/torch/nn/_reduction.py:42: UserWarning: size_average a
warnings.warn(warning.format(ret))
4.2学習
# ハイパーパラメータの設定
num_epochs = 10
batch_size = 64
lr = 1e-3  # 学習率
teacher_forcing_rate = 0.2  # Teacher Forcingを行う確率
ckpt_path = 'model.pth'  # 学習済みのモデルを保存するパス
model_args = {
    'input_size': vocab_size_X,
    'output_size': vocab_size_Y,
    'hidden_size': 256,
}
# データローダを定義
train_dataloader = DataLoader(train_X, train_Y, batch_size=batch_size, shuffle=True)
valid_dataloader = DataLoader(valid_X, valid_Y, batch_size=batch_size, shuffle=False)
# モデルとOptimizerを定義
model = EncoderDecoder(**model_args).to(device)
optimizer = optim.Adam(model.parameters(), lr=lr)
実際に損失関数を計算する関数を定義します。
def compute_loss(batch_X, batch_Y, lengths_X, model, optimizer=None, is_train=True):
    # 損失を計算する関数
    model.train(is_train)  # train/evalモードの切替え
    
    # 一定確率でTeacher Forcingを行う
    use_teacher_forcing = is_train and (random.random() < teacher_forcing_rate)
    max_length = batch_Y.size(0)
    # 推論
    pred_Y = model(batch_X, lengths_X, max_length, batch_Y, use_teacher_forcing)
    
    # 損失関数を計算
    loss = masked_cross_entropy(pred_Y.contiguous(), batch_Y.contiguous())
    
    if is_train:  # 訓練時はパラメータを更新
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    batch_Y = batch_Y.transpose(0, 1).contiguous().data.cpu().tolist()
    pred = pred_Y.max(dim=-1)[1].data.cpu().numpy().T.tolist()
    return loss.item(), batch_Y, pred
ここで、Loss以外に、学習の進捗を確認するためにモデルの性能を評価する指標として、BLEU
を計算します。
BLEUは機械翻訳の分野において最も一般的な自動評価基準の一つで、予め用意した複数の参照
訳と、機械翻訳モデルが出力した訳のn-gramのマッチ率に基づく指標です。
NLTK (Natural Language Tool Kit) という自然言語処理で用いられるライブラリを用いて簡単に
計算することができます。
def calc_bleu(refs, hyps):
    """
    BLEUスコアを計算する関数
    :param refs: list, 参照訳。単語のリストのリスト (例: [['I', 'have', 'a', 'pen'], ...])
    :param hyps: list, モデルの生成した訳。単語のリストのリスト (例: ['I', 'have', 'a', 'pen'])
    :return: float, BLEUスコア(0~100)
    """
    refs = [[ref[:ref.index(EOS)]] for ref in refs] # EOSは評価しないで良いので切り捨てる, refsのほ
    hyps = [hyp[:hyp.index(EOS)] if EOS in hyp else hyp for hyp in hyps]
    return 100 * bleu_score.corpus_bleu(refs, hyps)
それではモデルの訓練を行います。
# 訓練
best_valid_bleu = 0.
for epoch in range(1, num_epochs+1):
    train_loss = 0.
    train_refs = []
    train_hyps = []
    valid_loss = 0.
    valid_refs = []
    valid_hyps = []
    # train
    for batch in train_dataloader:
        batch_X, batch_Y, lengths_X = batch
        loss, gold, pred = compute_loss(
            batch_X, batch_Y, lengths_X, model, optimizer, 
            is_train=True
            )
        train_loss += loss
        train_refs += gold
        train_hyps += pred
    # valid
    for batch in valid_dataloader:
        batch_X, batch_Y, lengths_X = batch
        loss, gold, pred = compute_loss(
            batch_X, batch_Y, lengths_X, model, 
            is_train=False
            )
        valid_loss += loss
        valid_refs += gold
        valid_hyps += pred
    # 損失をサンプル数で割って正規化
    train_loss = np.sum(train_loss) / len(train_dataloader.data)
    valid_loss = np.sum(valid_loss) / len(valid_dataloader.data)
    # BLEUを計算
    train_bleu = calc_bleu(train_refs, train_hyps)
    valid_bleu = calc_bleu(valid_refs, valid_hyps)
    # validationデータでBLEUが改善した場合にはモデルを保存
    if valid_bleu > best_valid_bleu:
        ckpt = model.state_dict()
        torch.save(ckpt, ckpt_path)
        best_valid_bleu = valid_bleu
    print('Epoch {}: train_loss: {:5.2f}  train_bleu: {:2.2f}  valid_loss: {:5.2f}  valid_bleu: {:2
            epoch, train_loss, train_bleu, valid_loss, valid_bleu))
        
    print('-'*80)
Epoch 1: train_loss: 52.59 train_bleu: 3.26 valid_loss: 48.74 valid_bleu: 3.84
--------------------------------------------------------------------------------
Epoch 2: train_loss: 44.58 train_bleu: 7.45 valid_loss: 44.76 valid_bleu: 6.72
--------------------------------------------------------------------------------
Epoch 3: train_loss: 40.13 train_bleu: 11.27 valid_loss: 42.15 valid_bleu: 9.85
--------------------------------------------------------------------------------
Epoch 4: train_loss: 37.31 train_bleu: 14.23 valid_loss: 41.00 valid_bleu: 12.86
--------------------------------------------------------------------------------
Epoch 5: train_loss: 35.31 train_bleu: 16.19 valid_loss: 40.17 valid_bleu: 13.08
--------------------------------------------------------------------------------
Epoch 6: train_loss: 32.98 train_bleu: 19.08 valid_loss: 40.02 valid_bleu: 15.19
--------------------------------------------------------------------------------
Epoch 7: train_loss: 31.52 train_bleu: 21.25 valid_loss: 39.79 valid_bleu: 14.28
--------------------------------------------------------------------------------
Epoch 8: train_loss: 30.29 train_bleu: 22.93 valid_loss: 40.38 valid_bleu: 16.53
--------------------------------------------------------------------------------
Epoch 9: train_loss: 29.20 train_bleu: 24.53 valid_loss: 40.28 valid_bleu: 15.87
--------------------------------------------------------------------------------
Epoch 10: train_loss: 28.09 train_bleu: 26.31 valid_loss: 40.38 valid_bleu: 15.97
--------------------------------------------------------------------------------
5.評価
# 学習済みモデルの読み込み
ckpt = torch.load(ckpt_path) # cpuで処理する場合はmap_locationで指定する必要があります。
model.load_state_dict(ckpt)
model.eval()
EncoderDecoder(
(encoder): Encoder(
(embedding): Embedding(3725, 256, padding_idx=0)
(gru): GRU(256, 256)
)
(decoder): Decoder(
(embedding): Embedding(4405, 256, padding_idx=0)
(gru): GRU(256, 256)
(out): Linear(in_features=256, out_features=4405, bias=True)
)
)
def ids_to_sentence(vocab, ids):
    # IDのリストを単語のリストに変換する
    return [vocab.id2word[_id] for _id in ids]
def trim_eos(ids):
    # IDのリストからEOS以降の単語を除外する
    if EOS in ids:
        return ids[:ids.index(EOS)]
    else:
        return ids
# テストデータの読み込み
test_X = load_data('./data/dev.en')
test_Y = load_data('./data/dev.ja')
test_X = [sentence_to_ids(vocab_X, sentence) for sentence in test_X]
test_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in test_Y]
test_dataloader = DataLoader(test_X, test_Y, batch_size=1, shuffle=False)
# 生成
batch_X, batch_Y, lengths_X = next(test_dataloader)
sentence_X = ' '.join(ids_to_sentence(vocab_X, batch_X.data.cpu().numpy()[:-1, 0]))
sentence_Y = ' '.join(ids_to_sentence(vocab_Y, batch_Y.data.cpu().numpy()[:-1, 0]))
print('src: {}'.format(sentence_X))
print('tgt: {}'.format(sentence_Y))
output = model(batch_X, lengths_X, max_length=20)
output = output.max(dim=-1)[1].view(-1).data.cpu().tolist()
output_sentence = ' '.join(ids_to_sentence(vocab_Y, trim_eos(output)))
output_sentence_without_trim = ' '.join(ids_to_sentence(vocab_Y, output))
print('out: {}'.format(output_sentence))
print('without trim: {}'.format(output_sentence_without_trim))
src: show your own business .
tgt: 自分 の 事 を しろ 。
out: 仕事 の の を な 。 。
without trim: 仕事 の の を な 。 。 </S> </S> </S> </S> </S> </S> </S> </S> </S> </S> </S> <
# BLEUの計算
test_dataloader = DataLoader(test_X, test_Y, batch_size=1, shuffle=False)
refs_list = []
hyp_list = []
for batch in test_dataloader:
    batch_X, batch_Y, lengths_X = batch
    pred_Y = model(batch_X, lengths_X, max_length=20)
    pred = pred_Y.max(dim=-1)[1].view(-1).data.cpu().tolist()
    refs = batch_Y.view(-1).data.cpu().tolist()
    refs_list.append(refs)
    hyp_list.append(pred)
bleu = calc_bleu(refs_list, hyp_list)
print(bleu)
15.922503605584462
テストデータに対して新たな文を生成する際、これまでは各時刻で最も確率の高い単語を正解
として採用し、次のステップでの入力として使っていました。
ただ、本当にやりたいのは、文
全体の尤度が最も高くなるような文を生成することです。そのため、ただ近視眼的に確率の高
い単語を採用していくより、もう少し大局的に評価していく必要があります。
Beam Searchでは、各時刻において一定の数 のそれまでのスコア(対数尤度など)の高い文を
保持しながら選択を行っていきます。
図はSlack上のものを参照してください。
Beam Search
K
class BeamEncoderDecoder(EncoderDecoder):
    """
    Beam Searchでdecodeを行うためのクラス
    """
    def __init__(self, input_size, output_size, hidden_size, beam_size=4):
        """
        :param input_size: int, 入力言語の語彙数
        :param output_size: int, 出力言語の語彙数
        :param hidden_size: int, 隠れ層のユニット数
        :param beam_size: int, ビーム数
        """
        super(BeamEncoderDecoder, self).__init__(input_size, output_size, hidden_size)
        self.beam_size = beam_size
    def forward(self, batch_X, lengths_X, max_length):
        """
        :param batch_X: tensor, 入力系列のバッチ, size=(max_length, batch_size)
        :param lengths_X: list, 入力系列のバッチ内の各サンプルの文長
        :param max_length: int, Decoderの最大文長
        :return decoder_outputs: list, 各ビームのDecoderの出力
        :return finished_scores: list of float, 各ビームのスコア
        """
        _, encoder_hidden = self.encoder(batch_X, lengths_X)
        # decoderの入力と隠れ層の初期状態を定義
        decoder_input = torch.tensor([BOS] * self.beam_size, dtype=torch.long, device=device)
        decoder_input = decoder_input.unsqueeze(0)  # (1, batch_size)
        decoder_hidden = encoder_hidden
        # beam_sizeの数だけrepeatする
        decoder_input = decoder_input.expand(1, beam_size)
        decoder_hidden = decoder_hidden.expand(1, beam_size, -1).contiguous()
        k = beam_size
        finished_beams = []
        finished_scores = []
        prev_probs = torch.zeros(beam_size, 1, dtype=torch.float, device=device)  # 前の時刻の各ビー
        output_size = self.decoder.output_size
        # 各時刻ごとに処理
        for t in range(max_length):
            # decoder_input: (1, k)
            decoder_output, decoder_hidden = self.decoder(decoder_input[-1:], decoder_hidden)
            # decoder_output: (1, k, output_size)
            # decoder_hidden: (1, k, hidden_size)
            decoder_output_t = decoder_output[-1]  # (k, output_size)
            log_probs = prev_probs + F.log_softmax(decoder_output_t, dim=-1)  # (k, output_size)
            scores = log_probs  # 対数尤度をスコアとする
            # スコアの高いビームとその単語を取得
            flat_scores = scores.view(-1)  # (k*output_size,)
            if t == 0:
                flat_scores = flat_scores[:output_size]  # t=0のときは後半の同じ値の繰り返しを除外
            top_vs, top_is = flat_scores.data.topk(k)
            beam_indices = top_is / output_size  # (k,)
            word_indices = top_is % output_size  # (k,)
            
            # ビームを更新する
            _next_beam_indices = []
            _next_word_indices = []
            for b, w in zip(beam_indices, word_indices):
                if w.item() == EOS:  # EOSに到達した場合はそのビームは更新して終了
                    k -= 1
                    beam = torch.cat([decoder_input.t()[b], w.view(1,)])  # (t+2,)
                    score = scores[b, w].item()
                    finished_beams.append(beam)
                    finished_scores.append(score)
                else:   # それ以外の場合はビームを更新
                    _next_beam_indices.append(b)
                    _next_word_indices.append(w)
            if k == 0:
                break
            # tensornに変換
            next_beam_indices = torch.tensor(_next_beam_indices, device=device)
            next_word_indices = torch.tensor(_next_word_indices, device=device)
            # 次の時刻のDecoderの入力を更新
            decoder_input = torch.index_select(
                decoder_input, dim=-1, index=next_beam_indices)
            decoder_input = torch.cat(
                [decoder_input, next_word_indices.unsqueeze(0)], dim=0)
    
            # 次の時刻のDecoderの隠れ層を更新
            decoder_hidden = torch.index_select(
                decoder_hidden, dim=1, index=next_beam_indices)
            # 各ビームの対数尤度を更新
            flat_probs = log_probs.view(-1)  # (k*output_size,)
            next_indices = (next_beam_indices + 1) * next_word_indices
            prev_probs = torch.index_select(
                flat_probs, dim=0, index=next_indices).unsqueeze(1)  # (k, 1)
        # すべてのビームが完了したらデータを整形
        decoder_outputs = [[idx.item() for idx in beam[1:-1]] for beam in finished_beams]
        
        return decoder_outputs, finished_scores
# 学習済みモデルの読み込み
beam_size = 3
beam_model = BeamEncoderDecoder(**model_args, beam_size=beam_size).to(device)
beam_model.load_state_dict(ckpt)
beam_model.eval()
BeamEncoderDecoder(
(encoder): Encoder(
(embedding): Embedding(3725, 256, padding_idx=0)
(gru): GRU(256, 256)
)
(decoder): Decoder(
(embedding): Embedding(4405, 256, padding_idx=0)
(gru): GRU(256, 256)
(out): Linear(in_features=256, out_features=4405, bias=True)
)
)
test_dataloader = DataLoader(test_X, test_Y, batch_size=1, shuffle=False)
src: show your own business .
tgt: 自分 の 事 を しろ 。
out: 仕事 の の を な 。 。
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-53-3a5868882ca6> in <module>()
13
14 # beam decode
---> 15 outputs, scores = beam_model(batch_X, lengths_X, max_length=20)
16 # scoreの良い順にソート
17 outputs, scores = zip(*sorted(zip(outputs, scores), key=lambda x: -x[1]))
1 frames
<ipython-input-47-e193eb075a34> in forward(self, batch_X, lengths_X, max_length)
78 # 次の時刻のDecoderの入力を更新
79 decoder_input = torch.index_select(
---> 80 decoder_input, dim=-1, index=next_beam_indices)
81 decoder_input = torch.cat(
82 [decoder_input, next_word_indices.unsqueeze(0)], dim=0)
RuntimeError: "index_select_out_cuda_impl" not implemented for 'Float'
SEARCH STACK OVERFLOW
# 生成
batch_X, batch_Y, lengths_X = next(test_dataloader)
sentence_X = ' '.join(ids_to_sentence(vocab_X, batch_X.data.cpu().numpy()[:-1, 0]))
sentence_Y = ' '.join(ids_to_sentence(vocab_Y, batch_Y.data.cpu().numpy()[:-1, 0]))
print('src: {}'.format(sentence_X))
print('tgt: {}'.format(sentence_Y))
# 普通のdecode
output = model(batch_X, lengths_X, max_length=20)
output = output.max(dim=-1)[1].view(-1).data.cpu().tolist()
output_sentence = ' '.join(ids_to_sentence(vocab_Y, trim_eos(output)))
print('out: {}'.format(output_sentence))
# beam decode
outputs, scores = beam_model(batch_X, lengths_X, max_length=20)
# scoreの良い順にソート
outputs, scores = zip(*sorted(zip(outputs, scores), key=lambda x: -x[1]))
for o, output in enumerate(outputs):
    output_sentence = ' '.join(ids_to_sentence(vocab_Y, output))
    print('out{}: {}'.format(o+1, output_sentence))    
Practical PyTorch: Translation with a Sequence to Sequence Network and Attention
Translation with a Sequence to Sequence Network and Attention
Encoder-decoderモデルとTeacher Forcing,Scheduled Sampling,Professor Forcing
Sequence-to-Sequence Learning as Beam-Search Optimization
参考文献
error 0 秒 完了時間: 9:10
演習 Transformerモデル
TransformerはRNNやCNNを使用せず、Attentionのみを用いるSeq2Seqモデルです。
並列計算が可能なためRNNに比べて計算が高速な上、Self-Attentionと呼ばれる機構を用いるこ
とにより、局所的な位置しか参照できないCNNと異なり、系列内の任意の位置の情報を参照す
ることを可能にしています。
その他にもいくつかの工夫が加えられており、翻訳に限らない自然言語処理のあらゆるタスク
で圧倒的な性能を示すことが知られています。
原論文:Attention is All You Need
参考実装:https://github.com/jadore801120/attention-is-all-you-need-pytorch
! wget https://www.dropbox.com/s/9narw5x4uizmehh/utils.py
! mkdir images data
# data取得
! wget https://www.dropbox.com/s/o4kyc52a8we25wy/dev.en -P data/
! wget https://www.dropbox.com/s/kdgskm5hzg6znuc/dev.ja -P data/
! wget https://www.dropbox.com/s/gyyx4gohv9v65uh/test.en -P data/
! wget https://www.dropbox.com/s/hotxwbgoe2n013k/test.ja -P data/
! wget https://www.dropbox.com/s/5lsftkmb20ay9e1/train.en -P data/
! wget https://www.dropbox.com/s/ak53qirssci6f1j/train.ja -P data/
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/hotxwbgoe2n013k/test.ja [following]
--2021-12-23 00:17:55-- https://www.dropbox.com/s/raw/hotxwbgoe2n013k/test.ja
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc1b9d6949965b566084ca8708b6.dl.dropboxusercontent.com/cd/0/inline/BcXlDS
--2021-12-23 00:17:55-- https://uc1b9d6949965b566084ca8708b6.dl.dropboxusercontent.com/cd/
Resolving uc1b9d6949965b566084ca8708b6.dl.dropboxusercontent.com (uc1b9d6949965b566084ca870
Connecting to uc1b9d6949965b566084ca8708b6.dl.dropboxusercontent.com (uc1b9d6949965b566084c
HTTP request sent, awaiting response... 200 OK
Length: 27793 (27K) [text/plain]
Saving to: ‘data/test.ja’
test.ja 100%[===================>] 27.14K --.-KB/s in 0.01s
2021-12-23 00:17:56 (2.50 MB/s) - ‘data/test.ja’ saved [27793/27793]
--2021-12-23 00:17:56-- https://www.dropbox.com/s/5lsftkmb20ay9e1/train.en
Resolving www.dropbox.com (www.dropbox.com)... 162.125.3.18, 2620:100:6018:18::a27d:312
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/5lsftkmb20ay9e1/train.en [following]
--2021-12-23 00:17:56-- https://www.dropbox.com/s/raw/5lsftkmb20ay9e1/train.en
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uc047ddf19b09076ac96b4f9ca77.dl.dropboxusercontent.com/cd/0/inline/BcXXAZ
// / /
--2021-12-23 00:17:56-- https://uc047ddf19b09076ac96b4f9ca77.dl.dropboxusercontent.com/cd/
Resolving uc047ddf19b09076ac96b4f9ca77.dl.dropboxusercontent.com (uc047ddf19b09076ac96b4f9c
Connecting to uc047ddf19b09076ac96b4f9ca77.dl.dropboxusercontent.com (uc047ddf19b09076ac96b
HTTP request sent, awaiting response... 200 OK
Length: 1701356 (1.6M) [text/plain]
Saving to: ‘data/train.en’
train.en 100%[===================>] 1.62M --.-KB/s in 0.07s
2021-12-23 00:17:57 (24.3 MB/s) - ‘data/train.en’ saved [1701356/1701356]
--2021-12-23 00:17:57-- https://www.dropbox.com/s/ak53qirssci6f1j/train.ja
Resolving www.dropbox.com (www.dropbox.com)... 162.125.3.18, 2620:100:6018:18::a27d:312
Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/raw/ak53qirssci6f1j/train.ja [following]
--2021-12-23 00:17:57-- https://www.dropbox.com/s/raw/ak53qirssci6f1j/train.ja
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://uca94af4146e7ae80b015cf2a49a.dl.dropboxusercontent.com/cd/0/inline/BcXKJC
--2021-12-23 00:17:57-- https://uca94af4146e7ae80b015cf2a49a.dl.dropboxusercontent.com/cd/
Resolving uca94af4146e7ae80b015cf2a49a.dl.dropboxusercontent.com (uca94af4146e7ae80b015cf2a
Connecting to uca94af4146e7ae80b015cf2a49a.dl.dropboxusercontent.com (uca94af4146e7ae80b015
HTTP request sent, awaiting response... 200 OK
Length: 2784447 (2.7M) [text/plain]
Saving to: ‘data/train.ja’
train.ja 100%[===================>] 2.66M --.-KB/s in 0.08s
2021-12-23 00:17:58 (32.3 MB/s) - ‘data/train.ja’ saved [2784447/2784447]
import time
import numpy as np
from sklearn.utils import shuffle
from sklearn.model_selection import train_test_split
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
from nltk import bleu_score
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from utils import Vocab
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.manual_seed(1)
random_state = 42
print(torch.__version__)
1.10.0+cu111
PAD = 0
UNK = 1
BOS = 2
EOS = 3
PAD_TOKEN = '<PAD>'
UNK_TOKEN = '<UNK>'
BOS_TOKEN = '<S>'
EOS_TOKEN = '</S>'
1.データセット
def load_data(file_path):
    """
    テキストファイルからデータを読み込む
    :param file_path: str, テキストファイルのパス
    :return data: list, 文章(単語のリスト)のリスト
    """
    data = []
    for line in open(file_path, encoding='utf-8'):
        words = line.strip().split()  # スペースで単語を分割
        data.append(words)
    return data
train_X = load_data('./data/train.en')
train_Y = load_data('./data/train.ja')
# 訓練データと検証データに分割
train_X, valid_X, train_Y, valid_Y = train_test_split(train_X, train_Y, test_size=0.2, random_state
# データセットの中身を確認
print('train_X:', train_X[:5])
print('train_Y:', train_Y[:5])
train_X: [['where', 'shall', 'we', 'eat', 'tonight', '?'], ['i', 'made', 'a', 'big', 'mistake
train_Y: [['今夜', 'は', 'どこ', 'で', '食事', 'を', 'し', 'よ', 'う', 'か', '。'], ['僕', 'は
MIN_COUNT = 2  # 語彙に含める単語の最低出現回数
word2id = {
    PAD_TOKEN: PAD,
    BOS_TOKEN: BOS,
    EOS_TOKEN: EOS,
    UNK_TOKEN: UNK,
    }
vocab_X = Vocab(word2id=word2id)
vocab_Y = Vocab(word2id=word2id)
vocab_X.build_vocab(train_X, min_count=MIN_COUNT)
vocab_Y.build_vocab(train_Y, min_count=MIN_COUNT)
vocab_size_X = len(vocab_X.id2word)
vocab_size_Y = len(vocab_Y.id2word)
def sentence_to_ids(vocab, sentence):
    """
    単語のリストをインデックスのリストに変換する
    :param vocab: Vocabのインスタンス
    :param sentence: list of str
    :return indices: list of int
    """
    ids = [vocab.word2id.get(word, UNK) for word in sentence]
    ids = [BOS] + ids + [EOS]  # EOSを末尾に加える
    return ids
train_X = [sentence_to_ids(vocab_X, sentence) for sentence in train_X]
train_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in train_Y]
valid_X = [sentence_to_ids(vocab_X, sentence) for sentence in valid_X]
valid_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in valid_Y]
class DataLoader(object):
    def __init__(self, src_insts, tgt_insts, batch_size, shuffle=True):
        """
        :param src_insts: list, 入力言語の文章(単語IDのリスト)のリスト
        :param tgt_insts: list, 出力言語の文章(単語IDのリスト)のリスト
        :param batch_size: int, バッチサイズ
        :param shuffle: bool, サンプルの順番をシャッフルするか否か
        """
        self.data = list(zip(src_insts, tgt_insts))
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.start_index = 0
        
        self.reset()
    
    def reset(self):
        if self.shuffle:
            self.data = shuffle(self.data, random_state=random_state)
        self.start_index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        def preprocess_seqs(seqs):
            # パディング
            max_length = max([len(s) for s in seqs])
            data = [s + [PAD] * (max_length - len(s)) for s in seqs]
            # 単語の位置を表現するベクトルを作成
            positions = [[pos+1 if w != PAD else 0 for pos, w in enumerate(seq)] for seq in data]
            # テンソルに変換
            data_tensor = torch.tensor(data, dtype=torch.long, device=device)
            position_tensor = torch.tensor(positions, dtype=torch.long, device=device)
            return data_tensor, position_tensor            
        # ポインタが最後まで到達したら初期化する
        if self.start_index >= len(self.data):
            self.reset()
            raise StopIteration()
        # バッチを取得して前処理
        src_seqs, tgt_seqs = zip(*self.data[self.start_index:self.start_index+self.batch_size])
        src_data, src_pos = preprocess_seqs(src_seqs)
        tgt_data, tgt_pos = preprocess_seqs(tgt_seqs)
        # ポインタを更新する
        self.start_index += self.batch_size
        return (src_data, src_pos), (tgt_data, tgt_pos)
TransformerのモデルもEncoder-Decoderモデルの構造になっています。
EncoderとDecoderは
Positional Encoding: 入出力の単語のEmbedding時に単語の位置情報を埋め込む
Scaled Dot-Product Attention: 内積でAttentionを計算し、スケーリングを行う
Multi-head Attention: Scaled Dot-Product Attentionを複数のヘッドで並列化する
Position-Wise Feed Forward Network: 単語列の位置ごとに独立して処理を行う
など、いく
つかのモジュールから構成されているため、それぞれのモジュールを個別に定義していき
ます。
2.各モジュールの定義
Transformerは系列の処理にRNNを使用しないので、そのままでは単語列の語順を考慮すること
ができません。
そのため、入力系列の埋め込み行列に単語の位置情報を埋め込むPosition Encodingを加算しま
す。
Positional Encodingの行列 の各成分は次式で表されます。


ここで は単語の位置を、 は成分の次元を表して
います。
① Position Encoding
P E
P = sin(pos/ )
E(pos,2i)
10000
2i/dmodel
P = cos(pos/ )
E(pos,2i+1)
10000
2i/dmodel
pos i
Positional Encodingの各成分は、波長が から に幾何学的に伸びる正弦波に対応し
ます。
2π 10000 ∗ 2π
def position_encoding_init(n_position, d_pos_vec):
    """
    Positional Encodingのための行列の初期化を行う
    :param n_position: int, 系列長
    :param d_pos_vec: int, 隠れ層の次元数
    :return torch.tensor, size=(n_position, d_pos_vec)
    """
    # PADがある単語の位置はpos=0にしておき、position_encも0にする
    position_enc = np.array([
        [pos / np.power(10000, 2 * (j // 2) / d_pos_vec) for j in range(d_pos_vec)]
        if pos != 0 else np.zeros(d_pos_vec) for pos in range(n_position)])
    position_enc[1:, 0::2] = np.sin(position_enc[1:, 0::2])  # dim 2i
    position_enc[1:, 1::2] = np.cos(position_enc[1:, 1::2])  # dim 2i+1
    return torch.tensor(position_enc, dtype=torch.float)
ちなみに、Position Encodingを可視化すると以下のようになります。
pe = position_encoding_init(50, 256).numpy()
plt.figure(figsize=(16,8))
sns.heatmap(pe, cmap='Blues')
plt.show()
縦軸が単語の位置を、横軸が成分の次元を表しており、濃淡が加算される値です。
ここでは最大系列長を50、隠れ層の次元数を256としました。
ソース・ターゲット注意機構と自己注意機構
Attentionは一般に、queryベクトルとkeyベクトル
の類似度を求めて、その正規化した重みをvalueベクトルに適用して値を取り出す処理を行いま
す。
一般的な翻訳モデルで用いられるAttentionはソース・ターゲット注意機構と呼ばれ、この場合
queryはDecoderの隠れ状態(Target)、keyはEncoderの隠れ状態(Source)、valueもEncoderの隠れ
状態(Source)で表現されるのが一般的です。モデル全体の図では、右側のDecoderブロックの中
央にあるAttentionがこれに相当します。
Transformerでは、このソース・ターゲット注意機構に加えて、query,key,valueを同じ系列内で
定義する自己注意機構を用います。これにより、ある単語位置の出力を求める際にあらゆる位
置を参照できるため、局所的な位置しか参照できない畳み込み層よりも良い性能を発揮できる
と言われています。モデル全体の図では、左側のEncoderブロックと右側のDecoderブロックの
下部にあるAttentionがこれに当たります。
② Multihead Attention
Transformerでは、Scaled Dot-Product Attentionと呼ばれるAttentionを、複数のヘッドで並列に
扱うMulti-Head Attentionによって、Source-Target-AttentionとSelf-Attentionを実現します。
Attentionには、注意の重みを隠れ層 1 つのフィードフォワードネットワークで求めるAdditive
Attentionと、注意の重みを内積で求めるDot-Product Attentionが存在します。 一般に、Dot-
Product Attentionのほうがパラメータが少なく高速であり、Transformerでもこちらを使いま
す。
Tranformerではさらなる工夫として、query( )とkey( )の内積をスケーリング因子 で除
算します。
これは、 (keyベクトルの次元数)が大きい場合に内積が大きくなりすぎて逆伝播のsoftmax
の勾配が極端に小さくなることを防ぐ役割を果たします。
Scaled Dot-Product Attention
Q K dk
−
−
√
Attention(Q, K, V ) = softmax( )V
QK
T
dk
√
dk
class ScaledDotProductAttention(nn.Module):
    
    def __init__(self, d_model, attn_dropout=0.1):
        """
        :param d_model: int, 隠れ層の次元数
        :param attn_dropout: float, ドロップアウト率
        """
        super(ScaledDotProductAttention, self).__init__()
        self.temper = np.power(d_model, 0.5)  # スケーリング因子
        self.dropout = nn.Dropout(attn_dropout)
        self.softmax = nn.Softmax(dim=-1)
    def forward(self, q, k, v, attn_mask):
        """
        :param q: torch.tensor, queryベクトル, 
            size=(n_head*batch_size, len_q, d_model/n_head)
        :param k: torch.tensor, key, 
            size=(n_head*batch_size, len_k, d_model/n_head)
        :param v: torch.tensor, valueベクトル, 
            size=(n_head*batch_size, len_v, d_model/n_head)
        :param attn_mask: torch.tensor, Attentionに適用するマスク, 
            size=(n_head*batch_size, len_q, len_k)
        :return output: 出力ベクトル, 
            size=(n_head*batch_size, len_q, d_model/n_head)
        :return attn: Attention
            size=(n_head*batch_size, len_q, len_k)
        """
        # QとKの内積でAttentionの重みを求め、スケーリングする
        attn = torch.bmm(q, k.transpose(1, 2)) / self.temper  # (n_head*batch_size, len_q, len_k)
        # Attentionをかけたくない部分がある場合は、その部分を負の無限大に飛ばしてSoftmaxの値が0にな
        attn.data.masked_fill_(attn_mask, -float('inf'))
        
        attn = self.softmax(attn)
        attn = self.dropout(attn)
        output = torch.bmm(attn, v)
        return output, attn
TransformerではAttentionを複数のヘッドで並列に行うMulti-Head Attentionを採用していま
す。
複数のヘッドでAttentionを行うことにより、各ヘッドが異なる部分空間を処理でき、精度が向
上するとされています。
Multi-Head Attention
class MultiHeadAttention(nn.Module):
    def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        """
        :param n_head: int, ヘッド数
        :param d_model: int, 隠れ層の次元数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param dropout: float, ドロップアウト率
        """
        super(MultiHeadAttention, self).__init__()
        self.n_head = n_head
        self.d_k = d_k
        self.d_v = d_v
        # 各ヘッドごとに異なる重みで線形変換を行うための重み
        # nn.Parameterを使うことで、Moduleのパラメータとして登録できる. TFでは更新が必要な変数はtf.
        self.w_qs = nn.Parameter(torch.empty([n_head, d_model, d_k], dtype=torch.float))
        self.w_ks = nn.Parameter(torch.empty([n_head, d_model, d_k], dtype=torch.float))
        self.w_vs = nn.Parameter(torch.empty([n_head, d_model, d_v], dtype=torch.float))
        # nn.init.xavier_normal_で重みの値を初期化
        nn.init.xavier_normal_(self.w_qs)
        nn.init.xavier_normal_(self.w_ks)
        nn.init.xavier_normal_(self.w_vs)
        self.attention = ScaledDotProductAttention(d_model)
        self.layer_norm = nn.LayerNorm(d_model) # 各層においてバイアスを除く活性化関数への入力を平均
        self.proj = nn.Linear(n_head*d_v, d_model)  # 複数ヘッド分のAttentionの結果を元のサイズに写
        # nn.init.xavier_normal_で重みの値を初期化
        nn.init.xavier_normal_(self.proj.weight)
        
        self.dropout = nn.Dropout(dropout)
    def forward(self, q, k, v, attn_mask=None):
        """
        :param q: torch.tensor, queryベクトル, 
            size=(batch_size, len_q, d_model)
        :param k: torch.tensor, key, 
            size=(batch_size, len_k, d_model)
        :param v: torch.tensor, valueベクトル, 
            size=(batch_size, len_v, d_model)
        :param attn_mask: torch.tensor, Attentionに適用するマスク, 
            size=(batch_size, len_q, len_k)
        :return outputs: 出力ベクトル, 
            size=(batch_size, len_q, d_model)
        :return attns: Attention
            size=(n_head*batch_size, len_q, len_k)
            
        """
        d_k, d_v = self.d_k, self.d_v
        n_head = self.n_head
        # residual connectionのための入力 出力に入力をそのまま加算する
        residual = q
        batch_size, len_q, d_model = q.size()
        batch_size, len_k, d_model = k.size()
        batch_size, len_v, d_model = v.size()
        # 複数ヘッド化
        # torch.repeat または .repeatで指定したdimに沿って同じテンソルを作成
        q_s = q.repeat(n_head, 1, 1) # (n_head*batch_size, len_q, d_model)
        k_s = k.repeat(n_head, 1, 1) # (n_head*batch_size, len_k, d_model)
        v_s = v.repeat(n_head, 1, 1) # (n_head*batch_size, len_v, d_model)
        # ヘッドごとに並列計算させるために、n_headをdim=0に、batch_sizeをdim=1に寄せる
        q_s = q_s.view(n_head, -1, d_model) # (n_head, batch_size*len_q, d_model)
        k_s = k_s.view(n_head, -1, d_model) # (n_head, batch_size*len_k, d_model)
        v_s = v_s.view(n_head, -1, d_model) # (n_head, batch_size*len_v, d_model)
        # 各ヘッドで線形変換を並列計算(p16左側`Linear`)
        q_s = torch.bmm(q_s, self.w_qs)  # (n_head, batch_size*len_q, d_k)
        k_s = torch.bmm(k_s, self.w_ks)  # (n_head, batch_size*len_k, d_k)
        v_s = torch.bmm(v_s, self.w_vs)  # (n_head, batch_size*len_v, d_v)
        # Attentionは各バッチ各ヘッドごとに計算させるためにbatch_sizeをdim=0に寄せる
        q_s = q_s.view(-1, len_q, d_k)   # (n_head*batch_size, len_q, d_k)
        k_s = k_s.view(-1, len_k, d_k)   # (n_head*batch_size, len_k, d_k)
        v_s = v_s.view(-1, len_v, d_v)   # (n_head*batch_size, len_v, d_v)
        # Attentionを計算(p16.左側`Scaled Dot-Product Attention * h`)
        outputs, attns = self.attention(q_s, k_s, v_s, attn_mask=attn_mask.repeat(n_head, 1, 1))
        # 各ヘッドの結果を連結(p16左側`Concat`)
        # torch.splitでbatch_sizeごとのn_head個のテンソルに分割
        outputs = torch.split(outputs, batch_size, dim=0)  # (batch_size, len_q, d_model) * n_head
        # dim=-1で連結
        outputs = torch.cat(outputs, dim=-1)  # (batch_size, len_q, d_model*n_head)
        # residual connectionのために元の大きさに写像(p16左側`Linear`)
        outputs = self.proj(outputs)  # (batch_size, len_q, d_model)
        outputs = self.dropout(outputs)
        outputs = self.layer_norm(outputs + residual)
        return outputs, attns
単語列の位置ごとに独立して処理する2層のネットワークであるPosition-Wise Feed Forward
Networkを定義します。
③ Position-Wise Feed Forward Network
class PositionwiseFeedForward(nn.Module):
    """
    :param d_hid: int, 隠れ層1層目の次元数
    :param d_inner_hid: int, 隠れ層2層目の次元数
    :param dropout: float, ドロップアウト率
    """
    def __init__(self, d_hid, d_inner_hid, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
        # window size 1のconv層を定義することでPosition wiseな全結合層を実現する.
        self.w_1 = nn.Conv1d(d_hid, d_inner_hid, 1)
        self.w_2 = nn.Conv1d(d_inner_hid, d_hid, 1)
        self.layer_norm = nn.LayerNorm(d_hid)
        self.dropout = nn.Dropout(dropout)
        self.relu = nn.ReLU()
    def forward(self, x):
        """
        :param x: torch.tensor,
            size=(batch_size, max_length, d_hid)
        :return: torch.tensor,
            size=(batch_size, max_length, d_hid) 
        """
        residual = x
        output = self.relu(self.w_1(x.transpose(1, 2)))
        output = self.w_2(output).transpose(2, 1)
        output = self.dropout(output)
        return self.layer_norm(output + residual)
TransformerではAttentionに対して2つのマスクを定義します。
一つはkey側の系列のPADトークンに対してAttentionを行わないようにするマスクです。
④ Masking
def get_attn_padding_mask(seq_q, seq_k):
    """
    keyのPADに対するattentionを0にするためのマスクを作成する
    :param seq_q: tensor, queryの系列, size=(batch_size, len_q)
    :param seq_k: tensor, keyの系列, size=(batch_size, len_k)
    :return pad_attn_mask: tensor, size=(batch_size, len_q, len_k)
    """
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    pad_attn_mask = seq_k.data.eq(PAD).unsqueeze(1)   # (N, 1, len_k) PAD以外のidを全て0にする
    pad_attn_mask = pad_attn_mask.expand(batch_size, len_q, len_k) # (N, len_q, len_k)
    return pad_attn_mask
_seq_q = torch.tensor([[1, 2, 3]])
_seq_k = torch.tensor([[4, 5, 6, 7, PAD]])
_mask = get_attn_padding_mask(_seq_q, _seq_k)  # 行がquery、列がkeyに対応し、key側がPAD(=0)の時刻だ
print('query:n', _seq_q)
print('key:n', _seq_k)
print('mask:n', _mask)
query:
tensor([[1, 2, 3]])
key:
tensor([[4, 5, 6, 7, 0]])
mask:
tensor([[[False, False, False, False, True],
[False, False, False, False, True],
[False, False, False, False, True]]])
もう一つはDecoder側でSelf Attentionを行う際に、各時刻で未来の情報に対するAttentionを行
わないようにするマスクです。
def get_attn_subsequent_mask(seq):
    """
    未来の情報に対するattentionを0にするためのマスクを作成する
    :param seq: tensor, size=(batch_size, length)
    :return subsequent_mask: tensor, size=(batch_size, length, length)
    """
    attn_shape = (seq.size(1), seq.size(1))
    # 上三角行列(diagonal=1: 対角線より上が1で下が0)
    subsequent_mask = torch.triu(torch.ones(attn_shape, dtype=torch.uint8, device=device), diagonal
    subsequent_mask = subsequent_mask.repeat(seq.size(0), 1, 1)
    return subsequent_mask
_seq = torch.tensor([[1,2,3,4]])
_mask = get_attn_subsequent_mask(_seq)  # 行がquery、列がkeyに対応し、queryより未来のkeyの値が1で他
print('seq:n', _seq)
print('mask:n', _mask)
seq:
tensor([[1, 2, 3, 4]])
mask:
tensor([[[0, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 0, 1],
[0, 0, 0, 0]]], device='cuda:0', dtype=torch.uint8)
Encoder
これまで定義してきたサブレイヤーを統合して、Encoderを定義します。
EncoderではSelf AttentionとPosition-Wise Feed Forward Networkからなるブロックを複数層繰
り返すので、ブロックのクラスEncoderLayerを定義した後にEncoderを定義します。
3. モデルの定義
class EncoderLayer(nn.Module):
    """Encoderのブロックのクラス"""
    def __init__(self, d_model, d_inner_hid, n_head, d_k, d_v, dropout=0.1):
        """
        :param d_model: int, 隠れ層の次元数
        :param d_inner_hid: int, Position Wise Feed Forward Networkの隠れ層2層目の次元数
        :param n_head: int, ヘッド数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param dropout: float, ドロップアウト率
        """
        super(EncoderLayer, self).__init__()
        # Encoder内のSelf-Attention
        self.slf_attn = MultiHeadAttention(
            n_head, d_model, d_k, d_v, dropout=dropout)
        # Postionwise FFN
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner_hid, dropout=dropout)
    def forward(self, enc_input, slf_attn_mask=None):
        """
        :param enc_input: tensor, Encoderの入力, 
            size=(batch_size, max_length, d_model)
        :param slf_attn_mask: tensor, Self Attentionの行列にかけるマスク, 
            size=(batch_size, len_q, len_k)
        :return enc_output: tensor, Encoderの出力, 
            size=(batch_size, max_length, d_model)
        :return enc_slf_attn: tensor, EncoderのSelf Attentionの行列, 
            size=(n_head*batch_size, len_q, len_k)
        """
        # Self-Attentionのquery, key, valueにはすべてEncoderの入力(enc_input)が入る
        enc_output, enc_slf_attn = self.slf_attn(
            enc_input, enc_input, enc_input, attn_mask=slf_attn_mask)
        enc_output = self.pos_ffn(enc_output)
        return enc_output, enc_slf_attn
class Encoder(nn.Module):
    """EncoderLayerブロックからなるEncoderのクラス"""
    def __init__(
            self, n_src_vocab, max_length, n_layers=6, n_head=8, d_k=64, d_v=64,
            d_word_vec=512, d_model=512, d_inner_hid=1024, dropout=0.1):
        """
        :param n_src_vocab: int, 入力言語の語彙数
        :param max_length: int, 最大系列長
        :param n_layers: int, レイヤー数
        :param n_head: int, ヘッド数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param d_word_vec: int, 単語の埋め込みの次元数
        :param d_model: int, 隠れ層の次元数
        :param d_inner_hid: int, Position Wise Feed Forward Networkの隠れ層2層目の次元数
        :param dropout: float, ドロップアウト率        
        """
        super(Encoder, self).__init__()
        n_position = max_length + 1
        self.max_length = max_length
        self.d_model = d_model
        # Positional Encodingを用いたEmbedding
        self.position_enc = nn.Embedding(n_position, d_word_vec, padding_idx=PAD)
        self.position_enc.weight.data = position_encoding_init(n_position, d_word_vec)
        # 一般的なEmbedding
        self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=PAD)
        # EncoderLayerをn_layers個積み重ねる
        self.layer_stack = nn.ModuleList([
            EncoderLayer(d_model, d_inner_hid, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
    def forward(self, src_seq, src_pos):
        """
        :param src_seq: tensor, 入力系列, 
            size=(batch_size, max_length)
        :param src_pos: tensor, 入力系列の各単語の位置情報,
            size=(batch_size, max_length)
        :return enc_output: tensor, Encoderの最終出力, 
            size=(batch_size, max_length, d_model)
        :return enc_slf_attns: list, EncoderのSelf Attentionの行列のリスト
        """
        # 一般的な単語のEmbeddingを行う
        enc_input = self.src_word_emb(src_seq)
        # Positional EncodingのEmbeddingを加算する
        enc_input += self.position_enc(src_pos)
        enc_slf_attns = []
        enc_output = enc_input
        # key(=enc_input)のPADに対応する部分のみ1のマスクを作成
        enc_slf_attn_mask = get_attn_padding_mask(src_seq, src_seq)
        # n_layers個のEncoderLayerに入力を通す
        for enc_layer in self.layer_stack:
            enc_output, enc_slf_attn = enc_layer(
                enc_output, slf_attn_mask=enc_slf_attn_mask)
            enc_slf_attns += [enc_slf_attn]
        return enc_output, enc_slf_attns
Deocoderも同様にSelf Attention, Source-Target Attention, Position-Wise Feed Forward Network
からなるブロックを複数層繰り返ので、ブロックのクラスDecoderLayerを定義した後に
Decoderを定義します。
Decoder
class DecoderLayer(nn.Module):
    """Decoderのブロックのクラス"""
    def __init__(self, d_model, d_inner_hid, n_head, d_k, d_v, dropout=0.1):
        """
        :param d_model: int, 隠れ層の次元数
        :param d_inner_hid: int, Position Wise Feed Forward Networkの隠れ層2層目の次元数
        :param n_head: int, ヘッド数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param dropout: float, ドロップアウト率
        """
        super(DecoderLayer, self).__init__()
        # Decoder内のSelf-Attention
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        # Encoder-Decoder間のSource-Target Attention
        self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        # Positionwise FFN
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner_hid, dropout=dropout)
    def forward(self, dec_input, enc_output, slf_attn_mask=None, dec_enc_attn_mask=None):
        """
        :param dec_input: tensor, Decoderの入力, 
            size=(batch_size, max_length, d_model)
        :param enc_output: tensor, Encoderの出力, 
            size=(batch_size, max_length, d_model)
        :param slf_attn_mask: tensor, Self Attentionの行列にかけるマスク, 
            size=(batch_size, len_q, len_k)
        :param dec_enc_attn_mask: tensor, Soutce-Target Attentionの行列にかけるマスク, 
            size=(batch_size, len_q, len_k)
        :return dec_output: tensor, Decoderの出力, 
            size=(batch_size, max_length, d_model)
        :return dec_slf_attn: tensor, DecoderのSelf Attentionの行列, 
            size=(n_head*batch_size, len_q, len_k)
        :return dec_enc_attn: tensor, DecoderのSoutce-Target Attentionの行列, 
            size=(n_head*batch_size, len_q, len_k)
        """
        # Self-Attentionのquery, key, valueにはすべてDecoderの入力(dec_input)が入る
        dec_output, dec_slf_attn = self.slf_attn(
            dec_input, dec_input, dec_input, attn_mask=slf_attn_mask)
        # Source-Target-AttentionのqueryにはDecoderの出力(dec_output), key, valueにはEncoderの出力
        dec_output, dec_enc_attn = self.enc_attn(
            dec_output, enc_output, enc_output, attn_mask=dec_enc_attn_mask)
        dec_output = self.pos_ffn(dec_output)
        return dec_output, dec_slf_attn, dec_enc_attn
class Decoder(nn.Module):
    """DecoderLayerブロックからなるDecoderのクラス"""
    def __init__(
            self, n_tgt_vocab, max_length, n_layers=6, n_head=8, d_k=64, d_v=64,
            d_word_vec=512, d_model=512, d_inner_hid=1024, dropout=0.1):
        """
        :param n_tgt_vocab: int, 出力言語の語彙数
        :param max_length: int, 最大系列長
        :param n_layers: int, レイヤー数
        :param n_head: int, ヘッド数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param d_word_vec: int, 単語の埋め込みの次元数
        :param d_model: int, 隠れ層の次元数
        :param d_inner_hid: int, Position Wise Feed Forward Networkの隠れ層2層目の次元数
        :param dropout: float, ドロップアウト率        
        """
        super(Decoder, self).__init__()
        n_position = max_length + 1
        self.max_length = max_length
        self.d_model = d_model
        # Positional Encodingを用いたEmbedding
        self.position_enc = nn.Embedding(
            n_position, d_word_vec, padding_idx=PAD)
        self.position_enc.weight.data = position_encoding_init(n_position, d_word_vec)
        # 一般的なEmbedding
        self.tgt_word_emb = nn.Embedding(
            n_tgt_vocab, d_word_vec, padding_idx=PAD)
        self.dropout = nn.Dropout(dropout)
        # DecoderLayerをn_layers個積み重ねる
        self.layer_stack = nn.ModuleList([
            DecoderLayer(d_model, d_inner_hid, n_head, d_k, d_v, dropout=dropout)
            for _ in range(n_layers)])
    def forward(self, tgt_seq, tgt_pos, src_seq, enc_output):
        """
        :param tgt_seq: tensor, 出力系列, 
            size=(batch_size, max_length)
        :param tgt_pos: tensor, 出力系列の各単語の位置情報,
            size=(batch_size, max_length)
        :param src_seq: tensor, 入力系列, 
            size=(batch_size, n_src_vocab)
        :param enc_output: tensor, Encoderの出力, 
            size=(batch_size, max_length, d_model)
        :return dec_output: tensor, Decoderの最終出力, 
            size=(batch_size, max_length, d_model)
        :return dec_slf_attns: list, DecoderのSelf Attentionの行列のリスト 
        :return dec_slf_attns: list, DecoderのSelf Attentionの行列のリスト
        """
        # 一般的な単語のEmbeddingを行う
        dec_input = self.tgt_word_emb(tgt_seq)
        # Positional EncodingのEmbeddingを加算する
        dec_input += self.position_enc(tgt_pos)
        # Self-Attention用のマスクを作成
        # key(=dec_input)のPADに対応する部分が1のマスクと、queryから見たkeyの未来の情報に対応する部
        dec_slf_attn_pad_mask = get_attn_padding_mask(tgt_seq, tgt_seq)  # (N, max_length, max_leng
        dec_slf_attn_sub_mask = get_attn_subsequent_mask(tgt_seq)  # (N, max_length, max_length)
        dec_slf_attn_mask = torch.gt(dec_slf_attn_pad_mask + dec_slf_attn_sub_mask, 0)  # ORをとる
        # key(=dec_input)のPADに対応する部分のみ1のマスクを作成
        dec_enc_attn_pad_mask = get_attn_padding_mask(tgt_seq, src_seq)  # (N, max_length, max_leng
        dec_slf_attns, dec_enc_attns = [], []
        dec_output = dec_input
        # n_layers個のDecoderLayerに入力を通す
        for dec_layer in self.layer_stack:
            dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                dec_output, enc_output,
                slf_attn_mask=dec_slf_attn_mask,
                dec_enc_attn_mask=dec_enc_attn_pad_mask)
            dec_slf_attns += [dec_slf_attn]
            dec_enc_attns += [dec_enc_attn]
        return dec_output, dec_slf_attns, dec_enc_attns
class Transformer(nn.Module):
    """Transformerのモデル全体のクラス"""
    def __init__(
            self, n_src_vocab, n_tgt_vocab, max_length, n_layers=6, n_head=8,
            d_word_vec=512, d_model=512, d_inner_hid=1024, d_k=64, d_v=64,
            dropout=0.1, proj_share_weight=True):
        """
        :param n_src_vocab: int, 入力言語の語彙数
        :param n_tgt_vocab: int, 出力言語の語彙数
        :param max_length: int, 最大系列長
        :param n_layers: int, レイヤー数
        :param n_head: int, ヘッド数
        :param d_k: int, keyベクトルの次元数
        :param d_v: int, valueベクトルの次元数
        :param d_word_vec: int, 単語の埋め込みの次元数
        :param d_model: int, 隠れ層の次元数
        :param d_inner_hid: int, Position Wise Feed Forward Networkの隠れ層2層目の次元数
        :param dropout: float, ドロップアウト率        
        :param proj_share_weight: bool, 出力言語の単語のEmbeddingと出力の写像で重みを共有する       
        """
        super(Transformer, self).__init__()
        self.encoder = Encoder(
            n_src_vocab, max_length, n_layers=n_layers, n_head=n_head,
            d_word_vec=d_word_vec, d_model=d_model,
            d_inner_hid=d_inner_hid, dropout=dropout)
        self.decoder = Decoder(
            n_tgt_vocab, max_length, n_layers=n_layers, n_head=n_head,
            d_word_vec=d_word_vec, d_model=d_model,
            d_inner_hid=d_inner_hid, dropout=dropout)
        self.tgt_word_proj = nn.Linear(d_model, n_tgt_vocab, bias=False)
        nn.init.xavier_normal_(self.tgt_word_proj.weight)
        self.dropout = nn.Dropout(dropout)
        assert d_model == d_word_vec  # 各モジュールの出力のサイズは揃える
        if proj_share_weight:
            # 出力言語の単語のEmbeddingと出力の写像で重みを共有する
            assert d_model == d_word_vec
            self.tgt_word_proj.weight = self.decoder.tgt_word_emb.weight
    def get_trainable_parameters(self):
        # Positional Encoding以外のパラメータを更新する
        enc_freezed_param_ids = set(map(id, self.encoder.position_enc.parameters()))
        dec_freezed_param_ids = set(map(id, self.decoder.position_enc.parameters()))
        freezed_param_ids = enc_freezed_param_ids | dec_freezed_param_ids
        return (p for p in self.parameters() if id(p) not in freezed_param_ids)
    def forward(self, src, tgt):
        src_seq, src_pos = src
        tgt_seq, tgt_pos = tgt
        src_seq = src_seq[:, 1:]
        src_pos = src_pos[:, 1:]
        tgt_seq = tgt_seq[:, :-1]
        tgt_pos = tgt_pos[:, :-1]
        enc_output, *_ = self.encoder(src_seq, src_pos)
        dec_output, *_ = self.decoder(tgt_seq, tgt_pos, src_seq, enc_output)
        seq_logit = self.tgt_word_proj(dec_output)
        return seq_logit
4. 学習
def compute_loss(batch_X, batch_Y, model, criterion, optimizer=None, is_train=True):
    # バッチの損失を計算
    model.train(is_train)
    
    pred_Y = model(batch_X, batch_Y)
    gold = batch_Y[0][:, 1:].contiguous()
#     gold = batch_Y[0].contiguous()
    loss = criterion(pred_Y.view(-1, pred_Y.size(2)), gold.view(-1))
    if is_train:  # 訓練時はパラメータを更新
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    gold = gold.data.cpu().numpy().tolist()
    pred = pred_Y.max(dim=-1)[1].data.cpu().numpy().tolist()
    return loss.item(), gold, pred
MAX_LENGTH = 20
batch_size = 64
num_epochs = 15
lr = 0.001
ckpt_path = 'transformer.pth'
max_length = MAX_LENGTH + 2
model_args = {
    'n_src_vocab': vocab_size_X,
    'n_tgt_vocab': vocab_size_Y,
    'max_length': max_length,
    'proj_share_weight': True,
    'd_k': 32,
    'd_v': 32,
    'd_model': 128,
    'd_word_vec': 128,
    'd_inner_hid': 256,
    'n_layers': 3,
    'n_head': 6,
    'dropout': 0.1,
}
# DataLoaderやモデルを定義
train_dataloader = DataLoader(
    train_X, train_Y, batch_size
    )
valid_dataloader = DataLoader(
    valid_X, valid_Y, batch_size, 
    shuffle=False
    )
model = Transformer(**model_args).to(device)
optimizer = optim.Adam(model.get_trainable_parameters(), lr=lr)
criterion = nn.CrossEntropyLoss(ignore_index=PAD, size_average=False).to(device)
/usr/local/lib/python3.7/dist-packages/torch/nn/_reduction.py:42: UserWarning: size_average a
warnings.warn(warning.format(ret))
def calc_bleu(refs, hyps):
    """
    BLEUスコアを計算する関数
    :param refs: list, 参照訳。単語のリストのリスト (例: [['I', 'have', 'a', 'pen'], ...])
    :param hyps: list, モデルの生成した訳。単語のリストのリスト (例: [['I', 'have', 'a', 'pen'], .
    :return: float, BLEUスコア(0~100)
    """
    refs = [[ref[:ref.index(EOS)]] for ref in refs]
    hyps = [hyp[:hyp.index(EOS)] if EOS in hyp else hyp for hyp in hyps]
    return 100 * bleu_score.corpus_bleu(refs, hyps)
# 訓練
best_valid_bleu = 0.
for epoch in range(1, num_epochs+1):
    start = time.time()
    train_loss = 0.
    train_refs = []
    train_hyps = []
    valid_loss = 0.
    valid_refs = []
    valid_hyps = []
    # train
    for batch in train_dataloader:
        batch_X, batch_Y = batch
        loss, gold, pred = compute_loss(
            batch_X, batch_Y, model, criterion, optimizer, is_train=True
            )
        train_loss += loss
        train_refs += gold
        train_hyps += pred
    # valid
    for batch in valid_dataloader:
        batch_X, batch_Y = batch
        loss, gold, pred = compute_loss(
            batch_X, batch_Y, model, criterion, is_train=False
            )
        valid_loss += loss
        valid_refs += gold
        valid_hyps += pred
    # 損失をサンプル数で割って正規化
    train_loss /= len(train_dataloader.data) 
    valid_loss /= len(valid_dataloader.data) 
    # BLEUを計算
    train_bleu = calc_bleu(train_refs, train_hyps)
    valid_bleu = calc_bleu(valid_refs, valid_hyps)
    # validationデータでBLEUが改善した場合にはモデルを保存
    if valid_bleu > best_valid_bleu:
        ckpt = model.state_dict()
        torch.save(ckpt, ckpt_path)
        best_valid_bleu = valid_bleu
    elapsed_time = (time.time()-start) / 60
    print('Epoch {} [{:.1f}min]: train_loss: {:5.2f}  train_bleu: {:2.2f}  valid_loss: {:5.2f}  val
            epoch, elapsed_time, train_loss, train_bleu, valid_loss, valid_bleu))
    print('-'*80)
Epoch 1 [0.9min]: train_loss: 77.36 train_bleu: 4.76 valid_loss: 41.38 valid_bleu: 11.05
--------------------------------------------------------------------------------
Epoch 2 [0.9min]: train_loss: 39.36 train_bleu: 12.29 valid_loss: 32.24 valid_bleu: 17.38
--------------------------------------------------------------------------------
Epoch 3 [0.9min]: train_loss: 31.95 train_bleu: 18.02 valid_loss: 28.10 valid_bleu: 21.90
--------------------------------------------------------------------------------
Epoch 4 [0.9min]: train_loss: 28.15 train_bleu: 21.87 valid_loss: 25.57 valid_bleu: 24.93
--------------------------------------------------------------------------------
Epoch 5 [0.9min]: train_loss: 25.73 train_bleu: 24.60 valid_loss: 24.25 valid_bleu: 26.82
--------------------------------------------------------------------------------
Epoch 6 [0.9min]: train_loss: 23.88 train_bleu: 26.96 valid_loss: 23.01 valid_bleu: 28.97
--------------------------------------------------------------------------------
Epoch 7 [0.9min]: train_loss: 22.47 train_bleu: 28.57 valid_loss: 22.12 valid_bleu: 30.13
--------------------------------------------------------------------------------
Epoch 8 [0.9min]: train_loss: 21.29 train_bleu: 30.27 valid_loss: 21.55 valid_bleu: 30.78
--------------------------------------------------------------------------------
Epoch 9 [0.9min]: train_loss: 20.27 train_bleu: 31.53 valid_loss: 20.98 valid_bleu: 31.65
--------------------------------------------------------------------------------
Epoch 10 [0.9min]: train_loss: 19.38 train_bleu: 32.81 valid_loss: 20.37 valid_bleu: 33.01
--------------------------------------------------------------------------------
Epoch 11 [0.9min]: train_loss: 18.59 train_bleu: 34.06 valid_loss: 19.93 valid_bleu: 33.98
--------------------------------------------------------------------------------
Epoch 12 [0.9min]: train_loss: 17.87 train_bleu: 35.15 valid_loss: 19.85 valid_bleu: 34.05
--------------------------------------------------------------------------------
Epoch 13 [0.9min]: train_loss: 17.23 train_bleu: 36.06 valid_loss: 19.33 valid_bleu: 34.94
--------------------------------------------------------------------------------
Epoch 14 [0.9min]: train_loss: 16.66 train_bleu: 36.94 valid_loss: 19.16 valid_bleu: 35.43
--------------------------------------------------------------------------------
Epoch 15 [0.9min]: train_loss: 16.13 train_bleu: 37.70 valid_loss: 19.09 valid_bleu: 35.67
--------------------------------------------------------------------------------
5. 評価
def test(model, src, max_length=20):
    # 学習済みモデルで系列を生成する
    model.eval()
    
    src_seq, src_pos = src
    batch_size = src_seq.size(0)
    enc_output, enc_slf_attns = model.encoder(src_seq, src_pos)
        
    tgt_seq = torch.full([batch_size, 1], BOS, dtype=torch.long, device=device)
    tgt_pos = torch.arange(1, dtype=torch.long, device=device)
    tgt_pos = tgt_pos.unsqueeze(0).repeat(batch_size, 1)
    # 時刻ごとに処理
    for t in range(1, max_length+1):
        dec_output, dec_slf_attns, dec_enc_attns = model.decoder(
            tgt_seq, tgt_pos, src_seq, enc_output)
        dec_output = model.tgt_word_proj(dec_output)
        out = dec_output[:, -1, :].max(dim=-1)[1].unsqueeze(1)
        # 自身の出力を次の時刻の入力にする
        tgt_seq = torch.cat([tgt_seq, out], dim=-1)
        tgt_pos = torch.arange(t+1, dtype=torch.long, device=device)
        tgt_pos = tgt_pos.unsqueeze(0).repeat(batch_size, 1)
    return tgt_seq[:, 1:], enc_slf_attns, dec_slf_attns, dec_enc_attns
def ids_to_sentence(vocab, ids):
    # IDのリストを単語のリストに変換する
    return [vocab.id2word[_id] for _id in ids]
def trim_eos(ids):
    # IDのリストからEOS以降の単語を除外する
    if EOS in ids:
        return ids[:ids.index(EOS)]
    else:
        return ids
# 学習済みモデルの読み込み
model = Transformer(**model_args).to(device)
ckpt = torch.load(ckpt_path)
model.load_state_dict(ckpt)
<All keys matched successfully>
# テストデータの読み込み
test_X = load_data('./data/dev.en')
test_Y = load_data('./data/dev.ja')
test_X = [sentence_to_ids(vocab_X, sentence) for sentence in test_X]
test_Y = [sentence_to_ids(vocab_Y, sentence) for sentence in test_Y]
生成
test_dataloader = DataLoader(
    test_X, test_Y, 1,
    shuffle=False
    )
src, tgt = next(test_dataloader)
src_ids = src[0][0].cpu().numpy()
tgt_ids = tgt[0][0].cpu().numpy()
print('src: {}'.format(' '.join(ids_to_sentence(vocab_X, src_ids[1:-1]))))
print('tgt: {}'.format(' '.join(ids_to_sentence(vocab_Y, tgt_ids[1:-1]))))
preds, enc_slf_attns, dec_slf_attns, dec_enc_attns = test(model, src)
pred_ids = preds[0].data.cpu().numpy().tolist()
print('out: {}'.format(' '.join(ids_to_sentence(vocab_Y, trim_eos(pred_ids)))))
src: show your own business .
tgt: 自分 の 事 を しろ 。
out: 自分 の 問題 を <UNK> し て くれ 。
BLEUの評価
# BLEUの評価
test_dataloader = DataLoader(
    test_X, test_Y, 128,
    shuffle=False
    )
refs_list = []
hyp_list = []
for batch in test_dataloader:
    batch_X, batch_Y = batch
    preds, *_ = test(model, batch_X)
    preds = preds.data.cpu().numpy().tolist()
    refs = batch_Y[0].data.cpu().numpy()[:, 1:].tolist()
    refs_list += refs
    hyp_list += preds
bleu = calc_bleu(refs_list, hyp_list)
print(bleu)
25.0518239115383
check 1 秒 完了時間: 9:34

More Related Content

PPTX
ニューラルネットワークの理論
PDF
マルコフ連鎖モンテカルロ法入門-1
PPTX
【DL輪読会】DiffRF: Rendering-guided 3D Radiance Field Diffusion [N. Muller+ CVPR2...
PPTX
PyTorch, PixyzによるGenerative Query Networkの実装
PDF
強化学習その3
PDF
Deep Learningによる画像認識革命 ー歴史・最新理論から実践応用までー
PDF
【DL輪読会】HyperDiffusion: Generating Implicit Neural Fields withWeight-Space Dif...
PPTX
Graph LSTM解説
ニューラルネットワークの理論
マルコフ連鎖モンテカルロ法入門-1
【DL輪読会】DiffRF: Rendering-guided 3D Radiance Field Diffusion [N. Muller+ CVPR2...
PyTorch, PixyzによるGenerative Query Networkの実装
強化学習その3
Deep Learningによる画像認識革命 ー歴史・最新理論から実践応用までー
【DL輪読会】HyperDiffusion: Generating Implicit Neural Fields withWeight-Space Dif...
Graph LSTM解説

What's hot (20)

PDF
画像認識のための深層学習
PDF
道路網における経路探索のための前処理データ構造
PDF
ベイズ統計入門
PDF
混合ガウスモデルとEMアルゴリスム
PDF
PRML学習者から入る深層生成モデル入門
PPTX
【DL輪読会】Towards Understanding Ensemble, Knowledge Distillation and Self-Distil...
PPTX
マルコフ連鎖モンテカルロ法 (2/3はベイズ推定の話)
PDF
Transformer メタサーベイ
PDF
全力解説!Transformer
PPTX
深層学習の非常に簡単な説明
PDF
モンテカルロサンプリング
PDF
論文紹介「A Perspective View and Survey of Meta-Learning」
PDF
方策勾配型強化学習の基礎と応用
PDF
TokyoNLP#7 きれいなジャイアンのカカカカ☆カーネル法入門-C++
PDF
テンソル多重線形ランクの推定法について(Estimation of Multi-linear Tensor Rank)
PDF
基礎からのベイズ統計学 輪読会資料 第4章 メトロポリス・ヘイスティングス法
PPTX
【DL輪読会】High-Resolution Image Synthesis with Latent Diffusion Models
PPTX
[DL輪読会]Set Transformer: A Framework for Attention-based Permutation-Invariant...
PDF
深層自己符号化器+混合ガウスモデルによる教師なし異常検知
PDF
SSII2020 [OS2] 限られたデータからの深層学習 (オーガナイザーによる冒頭の導入)
画像認識のための深層学習
道路網における経路探索のための前処理データ構造
ベイズ統計入門
混合ガウスモデルとEMアルゴリスム
PRML学習者から入る深層生成モデル入門
【DL輪読会】Towards Understanding Ensemble, Knowledge Distillation and Self-Distil...
マルコフ連鎖モンテカルロ法 (2/3はベイズ推定の話)
Transformer メタサーベイ
全力解説!Transformer
深層学習の非常に簡単な説明
モンテカルロサンプリング
論文紹介「A Perspective View and Survey of Meta-Learning」
方策勾配型強化学習の基礎と応用
TokyoNLP#7 きれいなジャイアンのカカカカ☆カーネル法入門-C++
テンソル多重線形ランクの推定法について(Estimation of Multi-linear Tensor Rank)
基礎からのベイズ統計学 輪読会資料 第4章 メトロポリス・ヘイスティングス法
【DL輪読会】High-Resolution Image Synthesis with Latent Diffusion Models
[DL輪読会]Set Transformer: A Framework for Attention-based Permutation-Invariant...
深層自己符号化器+混合ガウスモデルによる教師なし異常検知
SSII2020 [OS2] 限られたデータからの深層学習 (オーガナイザーによる冒頭の導入)
Ad

Similar to 深層学習Day4レポート(小川成) (20)

DOCX
レポート深層学習Day4
PDF
東大大学院 電子情報学特論講義資料「深層学習概論と理論解析の課題」大野健太
PPTX
【最新ではありません。再度URL送付しています→https://www.slideshare.net/ssuserf4860b/day-250965207...
PDF
Deep learning入門
PPTX
Hello deeplearning!
PPTX
ラビットチャレンジレポート 深層学習Day4
PDF
20150930
PDF
Deep Learning技術の今
PDF
深層学習入門
PDF
ディープラーニング最近の発展とビジネス応用への課題
PPTX
DNNの曖昧性に関する研究動向
PDF
03_深層学習
PDF
[DL輪読会]Shaping Belief States with Generative Environment Models for RL
PPTX
2017-05-30_deepleaning-and-chainer
PDF
深層学習(岡本孝之 著) - Deep Learning chap.1 and 2
PPTX
深層強化学習入門
PDF
2014/5/29 東大相澤山崎研勉強会:パターン認識とニューラルネットワーク,Deep Learningまで
PPTX
[DL輪読会]Meta-Learning Probabilistic Inference for Prediction
PPTX
Cvim saisentan-6-4-tomoaki
PDF
強化学習とは (MIJS 分科会資料 2016/10/11)
レポート深層学習Day4
東大大学院 電子情報学特論講義資料「深層学習概論と理論解析の課題」大野健太
【最新ではありません。再度URL送付しています→https://www.slideshare.net/ssuserf4860b/day-250965207...
Deep learning入門
Hello deeplearning!
ラビットチャレンジレポート 深層学習Day4
20150930
Deep Learning技術の今
深層学習入門
ディープラーニング最近の発展とビジネス応用への課題
DNNの曖昧性に関する研究動向
03_深層学習
[DL輪読会]Shaping Belief States with Generative Environment Models for RL
2017-05-30_deepleaning-and-chainer
深層学習(岡本孝之 著) - Deep Learning chap.1 and 2
深層強化学習入門
2014/5/29 東大相澤山崎研勉強会:パターン認識とニューラルネットワーク,Deep Learningまで
[DL輪読会]Meta-Learning Probabilistic Inference for Prediction
Cvim saisentan-6-4-tomoaki
強化学習とは (MIJS 分科会資料 2016/10/11)
Ad

深層学習Day4レポート(小川成)