PyTorch 1.0 Tutorials : テキスト : Sequence to Sequence ネットワークと Attention で翻訳 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/13/2018 (1.0.0.dev20181211)
* 本ページは、PyTorch 1.0 Tutorials : Text : TRANSLATION WITH A SEQUENCE TO SEQUENCE NETWORK AND ATTENTION を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
テキスト : Sequence to Sequence ネットワークと Attention で翻訳
このプロジェクトでは、ニューラルネットワークにフランス語から英語へ翻訳することを教えます。
[KEY: > input, = target, < output] > il est en train de peindre un tableau . = he is painting a picture . < he is painting a picture . > pourquoi ne pas essayer ce vin delicieux ? = why not try that delicious wine ? < why not try that delicious wine ? > elle n est pas poete mais romanciere . = she is not a poet but a novelist . < she not not a poet but a novelist . > vous etes trop maigre . = you re too skinny . < you re all alone .
… 様々な成功の度合いへ。
これは単純ですがパワフルな sequence to sequence ネットワーク のアイデアにより可能になります、そこでは 2 つのリカレント・ニューラルネットワークが一つのシーケンスを他の一つに変換するために一緒に動作します。エンコーダ・ネットワークは入力シーケンスをベクトルに濃縮し (= condense)、そしてデコーダ・ネットワークはそのベクトルを新しいシーケンスに展開します。
このモデルを改良するために attention メカニズム を使用します、これはデコーダに入力シーケンスの特定の範囲に渡りフォーカスすることを学習させます。
推薦文書:
貴方が少なくとも PyTorch をインストールして、Python を知り、そして Tensor を理解していることを仮定しています。
そしてまた Sequence to Sequence について知り、それらがどのように動作するかを知ることもまた有用でしょう。
そして更に、これらのトピックを紹介するペーパーを読んでください :
- Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation
- Sequence to Sequence Learning with Neural Networks
- Neural Machine Translation by Jointly Learning to Align and Translate
- A Neural Conversational Model
要件
1 2 3 4 5 6 7 8 9 10 11 12 13 | from __future__ import unicode_literals, print_function, division from io import open import unicodedata import string import re import random import torch import torch.nn as nn from torch import optim import torch.nn.functional as F device = torch.device( "cuda" if torch.cuda.is_available() else "cpu" ) |
データファイルをロードする
このプロジェクトのためのデータは数千の英語 to フランス語翻訳ペアのセットです。
英語 to フランス語・ペアはレポジトリに含めるには大きすぎるので、続ける前に data/eng-fra.txt にダウンロードしてください。ファイルは翻訳ペアのタブ区切りリストです :
I am cold. J'ai froid.
Note: データは ここ からダウンロードして現在のディレクトリにそれを解凍してください。
文字レベル RNN チュートリアルで使用された文字エンコーディングと同様に、言語の各単語を one-hot ベクトル、または (単語のインデックスにおける) 単一の 1 を除いてゼロの巨大ベクトルとして表します。言語に存在するかもしれない数ダースの文字に比べて、多くの多くの単語がありますので、エンコーディング・ベクトルは遥かにより巨大です。けれども言語毎の数千単語を使用するためだけにデータを少し誤魔化して (= cheat) トリムします。
ネットワークの入力とターゲットとして後で使用するために単語毎に一意のインデックスが必要です。これら総てを追跡するために Lang という名前のヘルパークラスを使用します、これは word → index (word2index) と index → word (index2word) 辞書と、稀な単語を後で置き換えるために使用する各単語の数 word2count を持ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | SOS_token = 0 EOS_token = 1 class Lang: def __init__( self , name): self .name = name self .word2index = {} self .word2count = {} self .index2word = { 0 : "SOS" , 1 : "EOS" } self .n_words = 2 # Count SOS and EOS def addSentence( self , sentence): for word in sentence.split( ' ' ): self .addWord(word) def addWord( self , word): if word not in self .word2index: self .word2index[word] = self .n_words self .word2count[word] = 1 self .index2word[ self .n_words] = word self .n_words + = 1 else : self .word2count[word] + = 1 |
ファイルは総て Unicode で、単純化するためにUnicode 文字を ASCII に変え、総てを小文字にして、そして殆どの句読点をトリムします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # Turn a Unicode string to plain ASCII, thanks to def unicodeToAscii(s): return ''.join( c for c in unicodedata.normalize( 'NFD' , s) if unicodedata.category(c) ! = 'Mn' ) # Lowercase, trim, and remove non-letter characters def normalizeString(s): s = unicodeToAscii(s.lower().strip()) s = re.sub(r "([.!?])" , r " \1" , s) s = re.sub(r "[^a-zA-Z.!?]+" , r " " , s) return s |
データファイルを読むためにファイルを行に分割して、それから行をペアに分割します。ファイルは総て英語 → 他の言語ですので、他の言語 → 英語へ翻訳することを望む場合には、ペアを逆にするために reverse フラグを追加しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def readLangs(lang1, lang2, reverse = False ): print ( "Reading lines..." ) # Read the file and split into lines lines = open ( 'data/%s-%s.txt' % (lang1, lang2), encoding = 'utf-8' ).\ read().strip().split( '\n' ) # Split every line into pairs and normalize pairs = [[normalizeString(s) for s in l.split( '\t' )] for l in lines] # Reverse pairs, make Lang instances if reverse: pairs = [ list ( reversed (p)) for p in pairs] input_lang = Lang(lang2) output_lang = Lang(lang1) else : input_lang = Lang(lang1) output_lang = Lang(lang2) return input_lang, output_lang, pairs |
多くのサンプル・センテンスがありそして何某かを迅速に訓練することを望みますので、データセットを比較的短くて単純なセンテンスだけにトリムします。ここでは最大長は 10 単語 (これは終わりの句読点を含みます) で “I am” や “He is” etc (アポストロフィの置き換えについては先に説明しています) の形式に訳せるセンテンスへとフィルタリングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | MAX_LENGTH = 10 eng_prefixes = ( "i am " , "i m " , "he is" , "he s " , "she is" , "she s" , "you are" , "you re " , "we are" , "we re " , "they are" , "they re " ) def filterPair(p): return len (p[ 0 ].split( ' ' )) < MAX_LENGTH and \ len (p[ 1 ].split( ' ' )) < MAX_LENGTH and \ p[ 1 ].startswith(eng_prefixes) def filterPairs(pairs): return [pair for pair in pairs if filterPair(pair)] |
データを準備するための完全なプロセスは :
- テキストファイルを読んで行に分割し、行をペアに分割する
- テキストを正規化し、長さと内容でフィルタをかける
- ペアのセンテンスから単語リストを作成する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def prepareData(lang1, lang2, reverse = False ): input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse) print ( "Read %s sentence pairs" % len (pairs)) pairs = filterPairs(pairs) print ( "Trimmed to %s sentence pairs" % len (pairs)) print ( "Counting words..." ) for pair in pairs: input_lang.addSentence(pair[ 0 ]) output_lang.addSentence(pair[ 1 ]) print ( "Counted words:" ) print (input_lang.name, input_lang.n_words) print (output_lang.name, output_lang.n_words) return input_lang, output_lang, pairs input_lang, output_lang, pairs = prepareData( 'eng' , 'fra' , True ) print (random.choice(pairs)) |
Reading lines... Read 135842 sentence pairs Trimmed to 10853 sentence pairs Counting words... Counted words: fra 4489 eng 2925 ['je suis le plus vieux .', 'i m the oldest .']
Seq2Seq モデル
リカレント・ニューラルネットワーク、または RNN, はシーケンス上で動作するネットワークでそれ自身の出力を続くステップのための入力として使用します。
Sequence to Sequence ネットワーク, または seq2seq ネットワーク, あるいは エンコーダ・デコーダネットワーク はエンコーダとデコーダと呼ばれる 2 つの RNN から成るモデルです。エンコーダは入力シーケンスを読みそして単一のベクトルを出力します、そしてデコーダは出力シーケンスを生成するためにベクトルを読みます。
単一の RNN によるシーケンス予測とは違い、そこでは総ての入力が出力に対応しますが、seq2seq モデルはシーケンス長と順序から解放してくれます、それは (モデルを) 2 つの言語間の翻訳のために理想的なものにします。
センテンス “Je ne suis pas le chat noir” → “I am not the black cat” を考えます。入力センテンスの単語の殆どは出力センテンスで直接の翻訳を持ちますが、少し異なった順序にあります、e.g. “chat noir” と “black cat” です。“ne/pas” 構造のため入力センテンスにはまたもう 1 つの単語があります。入力単語のシーケンスから直接的に正しい翻訳を生成することは難しいでしょう。
seq2seq モデルではエンコーダは単一のベクトルを作成します、これは、理想的には、入力センテンスの「意味」を単一のベクトルにエンコードします — センテンスのある N 次元空間の単一のポイントです。
エンコーダ
seq2seq ネットワークのエンコーダは RNN でこれは入力センテンスからの総ての単語のための何某かの値を出力します。総ての入力単語に対してエンコーダはベクトルと隠れ状態を出力して次の入力単語のためにその隠れ状態を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class EncoderRNN(nn.Module): def __init__( self , input_size, hidden_size): super (EncoderRNN, self ).__init__() self .hidden_size = hidden_size self .embedding = nn.Embedding(input_size, hidden_size) self .gru = nn.GRU(hidden_size, hidden_size) def forward( self , input , hidden): embedded = self .embedding( input ).view( 1 , 1 , - 1 ) output = embedded output, hidden = self .gru(output, hidden) return output, hidden def initHidden( self ): return torch.zeros( 1 , 1 , self .hidden_size, device = device) |
デコーダ
デコーダはもう一つの RNN でこれはエンコーダ出力ベクトルを取り翻訳を作成するために単語のシーケンスを出力します。
単純なデコーダ
最も単純な seq2seq デコーダではエンコーダの最後の出力だけを使用します。この最後の出力は時に コンテキスト・ベクトル と呼ばれます、何故ならばそれはシーケンス全体からのコンテキストをエンコードするからです。この コンテキスト・ベクトルはデコーダの初期隠れ状態として使用されます。
デコーディングの総てのステップで、デコーダは入力トークンと隠れ状態が与えられます。初期入力トークンは start-of-string <SOS> トークンで、そして最初の隠れ状態はコンテキスト・ベクトルです (エンコーダの最後の隠れ状態)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class DecoderRNN(nn.Module): def __init__( self , hidden_size, output_size): super (DecoderRNN, self ).__init__() self .hidden_size = hidden_size self .embedding = nn.Embedding(output_size, hidden_size) self .gru = nn.GRU(hidden_size, hidden_size) self .out = nn.Linear(hidden_size, output_size) self .softmax = nn.LogSoftmax(dim = 1 ) def forward( self , input , hidden): output = self .embedding( input ).view( 1 , 1 , - 1 ) output = F.relu(output) output, hidden = self .gru(output, hidden) output = self .softmax( self .out(output[ 0 ])) return output, hidden def initHidden( self ): return torch.zeros( 1 , 1 , self .hidden_size, device = device) |
訓練してこのモデルの結果を観察することを勧めますが、スペースの節約のために成功目指して一直線に進んで Attention メカニズム を紹介していきます。
Attention デコーダ
コンテキスト・ベクトルだけがエンコーダとデコーダ間で渡されるのであれば、その単一のベクトルはセンテンス全体のエンコーディングの重荷を運びます。
Attention はデコーダ・ネットワークに、デコーダ自身の出力の総てのステップのためにエンコーダの出力の異なる部分に「フォーカス」することを可能にします。最初に attention 重みのセットを計算します。これらは重み付けられた combination を作成するためにエンコーダ出力ベクトルが乗算されます。その結果 (コードでは attn_applied と呼称されます) は入力シーケンスの特定の部分についての情報を含み、そしてデコーダが正しい出力単語を選択する助けとなるはずです。
attention 重みの計算は、入力としてデコーダの入力と隠れ状態を使用して、もう一つの feed-forward 層 attn で成されます。この層を実際に作成して訓練するためには、訓練データには総てのサイズのセンテンスがありますので、それが適用できる最大センテンス長 (入力長、エンコーダ出力のため) を選択しなければなりません。最大長のセンテンスは総ての attention 重みを使用します、その一方でより短いセンテンスは最初に幾つかを使用するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class AttnDecoderRNN(nn.Module): def __init__( self , hidden_size, output_size, dropout_p = 0.1 , max_length = MAX_LENGTH): super (AttnDecoderRNN, self ).__init__() self .hidden_size = hidden_size self .output_size = output_size self .dropout_p = dropout_p self .max_length = max_length self .embedding = nn.Embedding( self .output_size, self .hidden_size) self .attn = nn.Linear( self .hidden_size * 2 , self .max_length) self .attn_combine = nn.Linear( self .hidden_size * 2 , self .hidden_size) self .dropout = nn.Dropout( self .dropout_p) self .gru = nn.GRU( self .hidden_size, self .hidden_size) self .out = nn.Linear( self .hidden_size, self .output_size) def forward( self , input , hidden, encoder_outputs): embedded = self .embedding( input ).view( 1 , 1 , - 1 ) embedded = self .dropout(embedded) attn_weights = F.softmax( self .attn(torch.cat((embedded[ 0 ], hidden[ 0 ]), 1 )), dim = 1 ) attn_applied = torch.bmm(attn_weights.unsqueeze( 0 ), encoder_outputs.unsqueeze( 0 )) output = torch.cat((embedded[ 0 ], attn_applied[ 0 ]), 1 ) output = self .attn_combine(output).unsqueeze( 0 ) output = F.relu(output) output, hidden = self .gru(output, hidden) output = F.log_softmax( self .out(output[ 0 ]), dim = 1 ) return output, hidden, attn_weights def initHidden( self ): return torch.zeros( 1 , 1 , self .hidden_size, device = device) |
Note: 相対位置アプローチを使用することにより長さ制限を回避する attention の他の形式もあります。Effective Approaches to Attention-based Neural Machine Translation の “local attention” について読んでください。
訓練
訓練データの準備
訓練するために、各ペアについて入力 tensor (入力センテンスの単語のインデックス) とターゲット tensor (ターゲット・センテンスの単語のインデックス) が必要です。これらのベクトルを作成する一方で両者のシーケンスに EOS トークンを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def indexesFromSentence(lang, sentence): return [lang.word2index[word] for word in sentence.split( ' ' )] def tensorFromSentence(lang, sentence): indexes = indexesFromSentence(lang, sentence) indexes.append(EOS_token) return torch.tensor(indexes, dtype = torch. long , device = device).view( - 1 , 1 ) def tensorsFromPair(pair): input_tensor = tensorFromSentence(input_lang, pair[ 0 ]) target_tensor = tensorFromSentence(output_lang, pair[ 1 ]) return (input_tensor, target_tensor) |
モデルを訓練する
訓練するためには入力センテンスをエンコーダを通して実行し、そして総ての出力と最新の隠れ状態を追跡します。それから デコーダはその最初の入力として <SOS> トークンが、そしてその最初の隠れ状態としてエンコーダの最後の隠れ状態が与えられます。
“Teacher forcing” は次の入力としてデコーダの推測を使用する代わりに、実際のターゲット出力を各次の入力として使用する概念です。"teacher forcing" の使用はそれをより速く収束させますが、訓練されたネットワークが利用された (= exploit) とき、不安定さを示すかもしれません。
teacher-forced ネットワークの出力を観察することができます、それは首尾一貫した文法により読みますが正しい翻訳からは遠いところを彷徨います - 直感的にはそれは出力文法を表わすことを学習してひとたび教師が最初の 2, 3 の単語をそれに伝えれば、意味を「拾い上げる」ことができます、しかしそれはどのようにセンテンスを作成するかをそもそも翻訳から適切に学習していません。
PyTorch autograd が与えてくれる自由度ゆえに、単純な if ステートメントで "teacher forcing" を使用するか否かをランダムに選択することができます。それを更に使用するためには teacher_forcing_ratio を上向きに調整してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | teacher_forcing_ratio = 0.5 def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length = MAX_LENGTH): encoder_hidden = encoder.initHidden() encoder_optimizer.zero_grad() decoder_optimizer.zero_grad() input_length = input_tensor.size( 0 ) target_length = target_tensor.size( 0 ) encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device = device) loss = 0 for ei in range (input_length): encoder_output, encoder_hidden = encoder( input_tensor[ei], encoder_hidden) encoder_outputs[ei] = encoder_output[ 0 , 0 ] decoder_input = torch.tensor([[SOS_token]], device = device) decoder_hidden = encoder_hidden use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False if use_teacher_forcing: # Teacher forcing: Feed the target as the next input for di in range (target_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) loss + = criterion(decoder_output, target_tensor[di]) decoder_input = target_tensor[di] # Teacher forcing else : # Without teacher forcing: use its own predictions as the next input for di in range (target_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) topv, topi = decoder_output.topk( 1 ) decoder_input = topi.squeeze().detach() # detach from history as input loss + = criterion(decoder_output, target_tensor[di]) if decoder_input.item() = = EOS_token: break loss.backward() encoder_optimizer.step() decoder_optimizer.step() return loss.item() / target_length |
これは、現在時刻と進捗 % が与えられたときに経過時間と残りの見積もり時間をプリントするためのヘルパー関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import time import math def asMinutes(s): m = math.floor(s / 60 ) s - = m * 60 return '%dm %ds' % (m, s) def timeSince(since, percent): now = time.time() s = now - since es = s / (percent) rs = es - s return '%s (- %s)' % (asMinutes(s), asMinutes(rs)) |
訓練プロセス全体はこのように見えます :
- タイマーを開始する
- optimizer と criterion を初期化する
- 訓練ペアのセットを作成する
- プロットのために空の損失配列を開始する
それから train を何回も呼び出して時に進捗 (サンプルの %, ここまでの時間, 見積もり時間) と平均損失をプリントします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def trainIters(encoder, decoder, n_iters, print_every = 1000 , plot_every = 100 , learning_rate = 0.01 ): start = time.time() plot_losses = [] print_loss_total = 0 # Reset every print_every plot_loss_total = 0 # Reset every plot_every encoder_optimizer = optim.SGD(encoder.parameters(), lr = learning_rate) decoder_optimizer = optim.SGD(decoder.parameters(), lr = learning_rate) training_pairs = [tensorsFromPair(random.choice(pairs)) for i in range (n_iters)] criterion = nn.NLLLoss() for iter in range ( 1 , n_iters + 1 ): training_pair = training_pairs[ iter - 1 ] input_tensor = training_pair[ 0 ] target_tensor = training_pair[ 1 ] loss = train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion) print_loss_total + = loss plot_loss_total + = loss if iter % print_every = = 0 : print_loss_avg = print_loss_total / print_every print_loss_total = 0 print ( '%s (%d %d%%) %.4f' % (timeSince(start, iter / n_iters), iter , iter / n_iters * 100 , print_loss_avg)) if iter % plot_every = = 0 : plot_loss_avg = plot_loss_total / plot_every plot_losses.append(plot_loss_avg) plot_loss_total = 0 showPlot(plot_losses) |
結果をプロットする
プロットは、訓練の間にセーブされた損失値の配列 plot_losses を使用して matplotlib で成されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import matplotlib.pyplot as plt plt.switch_backend( 'agg' ) import matplotlib.ticker as ticker import numpy as np def showPlot(points): plt.figure() fig, ax = plt.subplots() # this locator puts ticks at regular intervals loc = ticker.MultipleLocator(base = 0.2 ) ax.yaxis.set_major_locator(loc) plt.plot(points) |
評価
評価は殆ど訓練と同じですが、ターゲットがないので各ステップのためにデコーダの予測を単純に自身に戻して供給します。それが単語を予測するたびにそれを出力文字列に追加してそして EOS トークンを予測する場合にそこで停止します。デコーダの attention 出力もまた後で表示するためにストアします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | def evaluate(encoder, decoder, sentence, max_length = MAX_LENGTH): with torch.no_grad(): input_tensor = tensorFromSentence(input_lang, sentence) input_length = input_tensor.size()[ 0 ] encoder_hidden = encoder.initHidden() encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device = device) for ei in range (input_length): encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden) encoder_outputs[ei] + = encoder_output[ 0 , 0 ] decoder_input = torch.tensor([[SOS_token]], device = device) # SOS decoder_hidden = encoder_hidden decoded_words = [] decoder_attentions = torch.zeros(max_length, max_length) for di in range (max_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) decoder_attentions[di] = decoder_attention.data topv, topi = decoder_output.data.topk( 1 ) if topi.item() = = EOS_token: decoded_words.append( '<eos>' ) break else : decoded_words.append(output_lang.index2word[topi.item()]) decoder_input = topi.squeeze().detach() return decoded_words, decoder_attentions[:di + 1 ] < / eos> |
訓練セットからのランダムなセンテンスを評価して何某かの主観的な質の判断を行なうために入力、ターゲット、そして出力をプリントアウトすることができます :
1 2 3 4 5 6 7 8 9 | def evaluateRandomly(encoder, decoder, n = 10 ): for i in range (n): pair = random.choice(pairs) print ( '>' , pair[ 0 ]) print ( '=' , pair[ 1 ]) output_words, attentions = evaluate(encoder, decoder, pair[ 0 ]) output_sentence = ' ' .join(output_words) print ( '<' , output_sentence) print ('') |
訓練と評価
これらの適当な場所のヘルパー関数により (余分なワークに見えますが、それは複数の実験を実行することをより容易にします) 実際にネットワークを初期化して訓練を開始することができます。
入力センテンスはばっさりとフィルタされたことを思い出してください。この小さなデータセットに対して 256 隠れノードと単一の GRU 層の比較的小さいネットワークを使用できます。MacBook CPU 上およそ 40 分後にある合理的な結果を得られます。
Note: この notebook を実行すれば訓練、カーネルの中断、評価、そして後で訓練を続けることができます。エンコーダとデコーダが初期化されるところの行をコメントアウトして trainIters を再度実行してください。
1 2 3 4 5 | hidden_size = 256 encoder1 = EncoderRNN(input_lang.n_words, hidden_size).to(device) attn_decoder1 = AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p = 0.1 ).to(device) trainIters(encoder1, attn_decoder1, 75000 , print_every = 5000 ) |
1m 43s (- 24m 11s) (5000 6%) 2.8857 3m 22s (- 21m 55s) (10000 13%) 2.3625 5m 1s (- 20m 5s) (15000 20%) 2.0512 6m 39s (- 18m 18s) (20000 26%) 1.7845 8m 19s (- 16m 38s) (25000 33%) 1.6084 9m 59s (- 14m 58s) (30000 40%) 1.4156 11m 39s (- 13m 18s) (35000 46%) 1.2863 13m 18s (- 11m 38s) (40000 53%) 1.1473 14m 58s (- 9m 58s) (45000 60%) 1.0635 16m 37s (- 8m 18s) (50000 66%) 0.9381 18m 17s (- 6m 38s) (55000 73%) 0.8694 19m 57s (- 4m 59s) (60000 80%) 0.8146 21m 36s (- 3m 19s) (65000 86%) 0.7185 23m 15s (- 1m 39s) (70000 93%) 0.6649 24m 55s (- 0m 0s) (75000 100%) 0.6318
1 | evaluateRandomly(encoder1, attn_decoder1) |
> vous etes deprime n est ce pas ? = you re depressed aren t you ? < you re depressed aren t you ?> ils sont eveilles . = they re awake . < they re gone . > ils ne constituent pas une menace . = they re not a threat . < they re not a threat . . . > notre entretien est termine . = we re done talking . < we re done talking . > j ai le double de ton age . = i m twice your age . < i am about the same age . > nous sortons d ici . = we re getting out of here . < we re getting out of here . > tu es incroyablement stupide . = you re unbelievably stupid . < you re incredibly stupid . > tu as droit a ton opinion . = you are entitled to your opinion . < you are entitled to your opinion . > vous n etes plus la bienvenue ici . = you are no longer welcome here . < you re not welcome welcome here . > elle a a peu pres mon age . = she is about my age . < she is about my old age .
Attention を可視化する
attention メカニズムの有用な特質はその高度に解釈可能な出力です。それは入力シーケンスの特定のエンコーダ出力を重み付けるために使用されますので、各タイムステップでネットワークがどこにフォーカスされるかを見ることを想像できます。
列が入力ステップで行が出力ステップである行列として表示される attention 出力を見るには単に plt.matshow(attentions) を実行できます :
1 2 3 | output_words, attentions = evaluate( encoder1, attn_decoder1, "je suis trop froid ." ) plt.matshow(attentions.numpy()) |
より良い視覚体験のために軸とラベルを追加する特別なワークを行ないます :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | def showAttention(input_sentence, output_words, attentions): # Set up figure with colorbar fig = plt.figure() ax = fig.add_subplot( 111 ) cax = ax.matshow(attentions.numpy(), cmap = 'bone' ) fig.colorbar(cax) # Set up axes ax.set_xticklabels([' '] + input_sentence.split(' ') + [ '<eos>' ], rotation = 90 ) ax.set_yticklabels([''] + output_words) # Show label at every tick ax.xaxis.set_major_locator(ticker.MultipleLocator( 1 )) ax.yaxis.set_major_locator(ticker.MultipleLocator( 1 )) plt.show() def evaluateAndShowAttention(input_sentence): output_words, attentions = evaluate( encoder1, attn_decoder1, input_sentence) print ( 'input =' , input_sentence) print ( 'output =' , ' ' .join(output_words)) showAttention(input_sentence, output_words, attentions) evaluateAndShowAttention( "elle a cinq ans de moins que moi ." ) evaluateAndShowAttention( "elle est trop petit ." ) evaluateAndShowAttention( "je ne crains pas de mourir ." ) evaluateAndShowAttention( "c est un jeune directeur plein de talent ." ) < / eos> |
![]() | ![]() |
![]() | ![]() |
input = elle a cinq ans de moins que moi . output = she is five years older than me . . . input = elle est trop petit . output = she is too short .input = je ne crains pas de mourir . output = i m not scared of dying . input = c est un jeune directeur plein de talent . output = he is a very good cook . .
課題
- 異なるデータセットで試してください
- 他の言語ペア
- 人間 → マシン (e.g. IOT コマンド)
- チャット → レスポンス
- 質問 → 回答
- 埋め込みを word2vec または GloVe のような事前訓練された単語埋め込みで置き換える
- より多くの層、より多くの隠れユニット、そしてより多くのセンテンスで試してください。訓練時間と結果を比較してください。
- ペアが同じフレーズ (I am test \t I am test) の 2 つを持つ翻訳ファイルを使用する場合、これをオートエンコーダとして利用できます。これを試してください :
- オートエンコーダとして訓練する
- エンコーダ・ネットワークだけをセーブする
- そこから翻訳のための新しいデコーダを訓練する
以上