最新のリカレント ニューラル ネットワークの動作: 機械翻訳

コラム: ニューラルネットワーク再発ディレクトリ

機械翻訳

機械翻訳は、コンピューター技術を使用してテキストをある言語から別の言語に自動的に翻訳するプロセスです。機械翻訳テクノロジーは、言語の壁を解決し、異なる言語間のコミュニケーションを容易にすることを目的としています。機械翻訳に使用されるアルゴリズムには、統計的機械翻訳、ニューラル機械翻訳などが含まれます。機械翻訳技術は大きく進歩しましたが、言語の違い、曖昧さ、多義性など、依然として多くの課題があります。



データセット

データセットの読み取り

この記事のデータセットは、Tatoeba プロジェクトの二か国語文ペア (英語: フランス語) から取得しています: http://www.manythings.org/anki/

def read_data_nmt():
    """载入“英语-法语”数据集"""
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join('fra.txt'), 'r',
             encoding='utf-8') as f:
        return f.read()

raw_text = read_data_nmt()
print(raw_text[:75])

ここに画像の説明を挿入

データの前処理

後の切断を容易にするためにスペースを置き換えます

#@save
def preprocess_nmt(text):
    """预处理“英语-法语”数据集"""
    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '

    # 使用空格替换不间断空格
    # 使用小写字母替换大写字母
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    # 在单词和标点符号之间插入空格
    out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)

text = preprocess_nmt(raw_text)
print(text[:80])

見出し語化

#@save
def tokenize_nmt(text, num_examples=None):
    """词元化“英语-法语”数据数据集"""
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target

source, target = tokenize_nmt(text)
source[:]

チャートを描く

#@save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
    """绘制列表长度对的直方图"""
    d2l.set_figsize()
    _, _, patches = d2l.plt.hist(
        [[len(l) for l in xlist], [len(l) for l in ylist]])
    d2l.plt.xlabel(xlabel)
    d2l.plt.ylabel(ylabel)
    for patch in patches[1].patches:
        patch.set_hatch('/')
    d2l.plt.legend(legend)

show_list_len_pair_hist(['source', 'target'], '# tokens per sequence',
                        'count', source, target);

ここに画像の説明を挿入

定義用語集

import collections
class Vocab:
    """Vocabulary for text."""
    def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
        """Defined in :numref:`sec_text_preprocessing`"""
        if tokens is None:
            tokens = []
        if reserved_tokens is None:
            reserved_tokens = []
        # Sort according to frequencies
        counter = count_corpus(tokens)
        self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                   reverse=True)
        # The index for the unknown token is 0
        self.idx_to_token = ['<unk>'] + reserved_tokens
        self.token_to_idx = {
    
    token: idx
                             for idx, token in enumerate(self.idx_to_token)}
        for token, freq in self._token_freqs:
            if freq < min_freq:
                break
            if token not in self.token_to_idx:
                self.idx_to_token.append(token)
                self.token_to_idx[token] = len(self.idx_to_token) - 1

    def __len__(self):
        return len(self.idx_to_token)

    def __getitem__(self, tokens):
        if not isinstance(tokens, (list, tuple)):
            return self.token_to_idx.get(tokens, self.unk)
        return [self.__getitem__(token) for token in tokens]

    def to_tokens(self, indices):
        if not isinstance(indices, (list, tuple)):
            return self.idx_to_token[indices]
        return [self.idx_to_token[index] for index in indices]

    @property
    def unk(self):  # Index for the unknown token
        return 0

    @property
    def token_freqs(self):  # Index for the unknown token
        return self._token_freqs

def count_corpus(tokens):
    """Count token frequencies.

    Defined in :numref:`sec_text_preprocessing`"""
    # Here `tokens` is a 1D list or 2D list
    if len(tokens) == 0 or isinstance(tokens[0], list):
        # Flatten a list of token lists into a list of tokens
        tokens = [token for line in tokens for token in line]
    return collections.Counter(tokens)

このコードは、テキストの語彙を構築するために使用される Vocab と呼ばれるクラスを定義します。各メソッドとプロパティについては以下で説明します。

init (self、tokens=None、min_freq=0、reserved_tokens=None): コンストラクター、ボキャブラリーを作成します。パラメータ tokens はテキスト内のすべての単語を含むリスト、min_freq はテキスト内の単語の最小出現数、reserved_tokens は予約語リストです。コンストラクターは、まず count_corpus() 関数を使用して単語の出現数をカウントし、単語を出現頻度の高い順に並べ替えます。次に、事前に定義された単語を語彙に追加し、単語の出現順序に従って単語からインデックスへの辞書 token_to_idx を構築し、出現頻度に従って単語へのインデックスからリスト idx_to_token を構築します。出現回数が min_freq 未満の単語については、直接スキップし、語彙に追加しません。最終語彙では、インデックス 0 が出現頻度の高い単語、インデックス 1 ~ n-1 が出現頻度の高い単語、インデックス n 以降の値が出現頻度の低い単語に対応します。

len (self): 語彙のサイズ、つまり単語の総数を返します。

getitem (self, tokens): 単語または単語リストに従って、トークンは対応するインデックスまたはインデックス リストを返します。トークンが単語の場合、対応するインデックスを返します。tokens が単語のリストである場合、 getitem () メソッドはその中の単語ごとに再帰的に呼び出され、インデックスのリストを返します。

to_tokens(self, indices): インデックスまたはインデックス リストのインデックスに従って、対応する単語または単語リストを返します。Indexes がインデックスの場合、対応する単語を返します。indices がインデックスのリストである場合、to_tokens() メソッドはその中の各インデックスに対して再帰的に呼び出され、単語のリストを返します。

unk: 属性は、単語のインデックス (0) を返します。

token_freqs: 属性。高頻度から低頻度にソートされた単語のリストを返します。このプロパティは語彙の作成時に初期化されます。

count_corpus(tokens): Vocab クラスの外部で定義された関数で、単語の頻度をカウントするために使用されます。パラメータ トークンは、テキストのすべての単語を含むリスト (おそらく 2 次元リスト) です。トークンが 2 次元リストの場合は、まず 1 次元リストに変換します。この関数は、collections.Counter() 関数を使用して、テキスト内で各単語が出現する回数をカウントし、辞書を返します。

src_vocab = Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)

充填

#@save
def truncate_pad(line, num_steps, padding_token):
    """截断或填充文本序列"""
    if len(line) > num_steps:
        return line[:num_steps]  # 截断
    return line + [padding_token] * (num_steps - len(line))  # 填充

truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])

組織データセット

#@save
def build_array_nmt(lines, vocab, num_steps):
    """将机器翻译的文本序列转换成小批量"""
    lines = [vocab[l] for l in lines]#token to id
    lines = [l + [vocab['<eos>']] for l in lines]# 加上eos代表结束
    array = torch.tensor([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])# 转换为数组
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)#有效长度
    return array, valid_len

この関数は、機械翻訳されたテキスト シーケンスをミニバッチに変換します。入力行はテキスト シーケンスを含むリスト、vocab は語彙オブジェクト、num_steps は各シーケンスに含まれるトークンの最大数です。この関数は 2 つのテンソルを返します。1 つ目は num_steps トークンのシーケンスを含むテンソルで、2 つ目は各シーケンスの有効長です。シーケンスの場合、その有効長は、先頭から数えた最初のフィラーの前のトークンの数です。

この関数は、まずテキストの各シーケンスを語彙内の整数のリストに変換し、次に各シーケンスにトークンを追加します。次に、すべてのシーケンスを長さ num_steps になるように切り詰めるか、パディングします。シーケンスに num_steps トークンの後にまだトークンがある場合、残りのトークンは切り捨てられます。シーケンスのトークンが num_steps よりも少ない場合、パディング トークンがシーケンスの最後に追加されます。最後に、関数は各シーケンスの有効長を計算し、2 つのテンソルを返します。

#@save
from torch.utils import data
def load_array(data_arrays, batch_size, is_train=True):
    """Construct a PyTorch data iterator.

    Defined in :numref:`sec_linear_concise`"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

def load_data_nmt(batch_size, num_steps, num_examples=600):
    """返回翻译数据集的迭代器和词表"""
    text = preprocess_nmt(read_data_nmt())
    source, target = tokenize_nmt(text, num_examples)
    src_vocab = Vocab(source, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    tgt_vocab = Vocab(target, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
    tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
    data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
    data_iter = load_array(data_arrays, batch_size)
    return data_iter, src_vocab, tgt_vocab
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
    print('X:', X.type(torch.int32))
    print('X的有效长度:', X_valid_len)
    print('Y:', Y.type(torch.int32))
    print('Y的有效长度:', Y_valid_len)
    break

ここに画像の説明を挿入

エンコーダ/デコーダのアーキテクチャ

前のセクションで説明したように、機械翻訳は、入力と出力が可変長のシーケンスであるシーケンス変換モデルの中心的な問題です。このタイプの入出力を処理するには、2 つの主要コンポーネントを備えたアーキテクチャを設計できます。最初のコンポーネントはエンコーダーです。入力として可変長のシーケンスを受け取り、それを固定形状のエンコードされた状態に変換します。2 番目のコンポーネントはデコーダです。デコーダは、固定形状のエンコードされた状態を可変長シーケンスにマッピングします。これはエンコーダ/デコーダ アーキテクチャと呼ばれ、次の図に示されています。
ここに画像の説明を挿入
英語からフランス語への機械翻訳を例として考えてみましょう。「They」「are」「watching」「.」という英語の入力シーケンスが与えられたとします。まず、この「エンコーダ/デコーダ」アーキテクチャは、可変長の入力シーケンスを「状態」にエンコードし、次にその状態をトークンごとにデコードして、出力として変換されたシーケンスを生成します:" Ils" "regordent" "。「エンコーダ/デコーダ」アーキテクチャは、後続の章でさまざまなシーケンス変換モデルを形成するための基礎となるため、このセクションでは、このアーキテクチャを後のコード実装のためのインターフェイスに変換します。

エンコーダ

from torch import nn
#@save
class Encoder(nn.Module):
    """编码器-解码器架构的基本编码器接口"""
    def __init__(self, **kwargs):
        super(Encoder, self).__init__(**kwargs)

    def forward(self, X, *args):
        raise NotImplementedError

デコーダ

#@save
class Decoder(nn.Module):
    """编码器-解码器架构的基本解码器接口"""
    def __init__(self, **kwargs):
        super(Decoder, self).__init__(**kwargs)

    def init_state(self, enc_outputs, *args):
        raise NotImplementedError

    def forward(self, X, state):
        raise NotImplementedError

エンコーダとデコーダを結合する

#@save
class EncoderDecoder(nn.Module):
    """编码器-解码器架构的基类"""
    def __init__(self, encoder, decoder, **kwargs):
        super(EncoderDecoder, self).__init__(**kwargs)
        self.encoder = encoder
        self.decoder = decoder

    def forward(self, enc_X, dec_X, *args):
        enc_outputs = self.encoder(enc_X, *args)
        dec_state = self.decoder.init_state(enc_outputs, *args)
        return self.decoder(dec_X, dec_state)

シーケンス間学習 (seq2seq)

エンコーダ/デコーダ アーキテクチャの設計原則に従って、リカレント ニューラル ネットワーク エンコーダは可変長シーケンスを入力として受け取り、それを固定形状の隠れ状態に変換します。言い換えれば、入力シーケンスの情報は、RNN エンコーダーの隠れた状態にエンコードされます。出力シーケンスのトークンを継続的に生成するために、別の RNN デコーダは、入力シーケンスのエンコードされた情報と、出力シーケンスですでに確認または生成されたトークンに基づいて次のトークンを予測します。以下の図は、機械翻訳におけるシーケンス間学習に 2 つのリカレント ニューラル ネットワークを使用する方法を示しています。
ここに画像の説明を挿入
図中、特定の「」はシーケンス終了トークンを示します。出力シーケンスがこのトークンを生成すると、モデルは予測を停止します。RNN デコーダの初期化タイム ステップでは、2 つの具体的な設計上の決定があります。まず、特定の「」は、デコーダへの入力シーケンスの最初のトークンであるシーケンス開始トークンを示します。第 2 に、デコーダの隠れ状態がリカレント ニューラル ネットワーク エンコーダの最終的な隠れ状態で初期化されます。たとえば、(Sutskever et al.、2014)の設計では、入力シーケンスの符号化情報がデコーダに供給されて出力シーケンスを生成するという設計に基づいています。他の設計 (Cho et al.、2014) では、図 9.7.1 に示すように、エンコーダーの最終的な隠れ状態が、タイム ステップごとにデコーダーへの入力シーケンスの一部として含まれます。ラベルを元の出力シーケンスにすることができ、予測の位置をソース シーケンス トークン """Ils""regardent""." から新しいシーケンス トークン "Ils""regardent"" に移動します。 「」。

エンコーダ

詳細コード

#@save
class Seq2SeqEncoder(Encoder):
    """用于序列到序列学习的循环神经网络编码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqEncoder, self).__init__(**kwargs)
        # 嵌入层
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
                          dropout=dropout)

    def forward(self, X, *args):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X)
        # 在循环神经网络模型中,第一个轴对应于时间步
        X = X.permute(1, 0, 2)
        # 如果未提及状态,则默认为0
        output, state = self.rnn(X)
        # output的形状:(num_steps,batch_size,num_hiddens)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state

このコードは、シーケンス間学習のためのリカレント ニューラル ネットワーク エンコーダーを定義します。エンコーダは、埋め込み層と多層リカレント ニューラル ネットワークで構成されます。具体的な説明は以下の通りです。

class Seq2SeqEncoder(Encoder):: Encoder クラスを継承する Seq2SeqEncoder という名前のクラスを定義します。

def init (self、vocab_size、embed_size、num_hiddens、num_layers、dropout=0、**kwargs): 次のパラメータを受け入れるクラスの初期化関数を定義します。

vocab_size: 語彙のサイズ。
embed_size: 埋め込みベクトルの次元。
num_hiddens: リカレント ニューラル ネットワークの隠れ状態の次元。
num_layers: リカレント ニューラル ネットワークの層の数。
ドロップアウト: ドロップアウトの確率、デフォルトは 0 です。
**kwargs: 追加パラメータ。
super(Seq2SeqEncoder, self) .init (**kwargs): 親クラス Encoder の初期化関数を呼び出し、パラメータを渡します。

self.embedding = nn.Embedding(vocab_size, embed_size): 入力シーケンス内の各単語を埋め込みベクトルに変換する埋め込み層を定義します。

self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=dropout): num_layers 個の GRU 層を含む多層リカレント ニューラル ネットワークを定義します。GRU 層の入力次元は embed_size、出力次元は num_hiddens で、ドロップアウト層も含まれており、ドロップアウトの確率はドロップアウトです。

def forward(self, X, *args): : 入力シーケンス X およびその他のパラメーター (*args) を受け入れるクラスの前方伝播関数を定義します。

X = self.embedding(X): 入力シーケンス X 内の各単語を埋め込みベクトルに変換します。

X = X.permute(1, 0, 2): 入力シーケンス X の次元を (batch_size, num_steps, embed_size) から (num_steps, batch_size, embed_size) に変換します。これは、リカレント ニューラル ネットワーク モデルで最初の軸がタイム ステップに対応するようにするために行われます。

Output, state = self.rnn(X): 変換された入力シーケンス X を計算のために多層リカレント ニューラル ネットワークに渡し、出力と状態を返します。このうち、出力の形状は (num_steps、batch_size、num_hiddens) であり、各タイム ステップの出力を示し、状態の形状は (num_layers、batch_size、num_hiddens) で、最後のタイム ステップの状態を示します。

出力、状態を返す: 出力と状態を返します。

X.permute
PyTorch では、X.permute(dims) はテンソルの次元を再配置するために使用できるテンソル メソッドです。dims は、元のテンソルの各次元について、新しいテンソルのどこに配置されるかを表す整数のタプルです。たとえば、テンソルの次元が (3,4,5) で dims=(1,0,2) である場合、新しいテンソルの最初の次元は元のテンソルの 2 番目の次元であり、2 番目の次元は元のテンソルの 2 番目の次元であることを意味します。は元のテンソルの 1 次元目、3 次元は元のテンソルの 3 次元目です。
具体的には、X.permute(1, 0, 2) は、テンソル X の 1 次元と 2 次元を交換し、3 次元は変更しないことを意味します。コードでは、この操作は形状の入力シーケンス テンソル X の次元 (batch_size、num_steps、embed_size) を調整するために使用されます。これにより、最初の次元がタイム ステップになり、2 番目の次元がバッチ サイズになり、次のようになります。ループ ニューラル ネットワークの入力要件。

なぜ X = X.permute(1, 0, 2) なのか

リカレント ニューラル ネットワークでは、入力データの次元が次の要件を満たす必要があります。

  1. 最初の次元は時間ステップです。
  2. 2 番目の次元はバッチ サイズです。

したがって、形状 (batch_size、num_steps、embed_size) の入力シーケンス X の場合、(num_steps、batch_size、embed_size) にサイズ変更する必要があります。

コードでは、X.permute(1, 0, 2) を呼び出すことで次元の調整を実現しています。具体的には、X.permute(1, 0, 2) は、X の次元を (batch_size, num_steps, embed_size) から (num_steps, batch_size, embed_size) に変換することを意味します。つまり、最初の次元 (num_steps) と 2 番目の次元 (batch_size) )交換用。この目的は、入力シーケンス X の各タイム ステップを入力として取得し、各タイム ステップのすべてのサンプル (batch_size) をバッチとして処理することです。そうすることで、GPU の並列計算能力を最大限に活用し、トレーニング効率を向上させることができます。

encoder = Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16,
                         num_layers=2)
encoder.eval()
X = torch.zeros((4, 7), dtype=torch.long)
output, state = encoder(X)
output.shape

ここに画像の説明を挿入

埋め込み層

自然言語処理では、埋め込み層 (Embedding Layer) は、離散的な単語や文字を連続的なベクトル表現にマッピングするための一般的な手法です。埋め込み層は各単語または文字を固定長ベクトルとして表すことができ、これらのベクトルは通常、埋め込みベクトルまたは単語埋め込み (Word Embedding) と呼ばれます。ベクトルを埋め込むと、単語または文字間の意味論的および連想的な関係をキャプチャでき、ニューラル ネットワーク モデルへの入力として使用できます。

PyTorch では、nn.Embedding は、整数でエンコードされた単語または文字を連続ベクトルにマップする組み込みの埋め込み層クラスです。nn.Embedding への入力は形状のテンソル (batch_size、seq_length) であり、各要素は語彙または文字インデックスを表す整数です。nn.Embedding の出力は形状のテンソル (batch_size、seq_length、embedding_size) であり、各要素は単語または文字の埋め込みベクトルを表すベクトルです。embedding_size は、指定された埋め込みベクトルの次元です。

上記のコードでは、 nn.Embedding を使用して、サイズ vocab_size の語彙からの単語を次元 embed_size のベクトル表現にエンコードする埋め込み層を作成します。Seq2SeqEncoder の forward メソッドでは、入力シーケンス X が埋め込み層に渡されて、各単語のインデックスが埋め込みベクトルにエンコードされます。出力テンソル形状は (batch_size, num_steps, embed_size) です。ここで、num_steps はシーケンスのタイム ステップ数、batch_size はシーケンスのバッチ サイズです。

埋め込み層の実装は非常に簡単で、重み行列E ∈ R v × d E \in \mathbb{R}^{v \times d} を使用できます。ERv × d、各入力ワードxi x_iバツ私は対応する埋め込みベクトルei ∈ R d e_i \in \mathbb{R}^dにマッピングされますe私はRd . 具体的には、サイズn × mn \times mn×mXXX、ここでnnnはサンプルサイズを表します、mmm は入力の長さを表し、埋め込み層の出力行列E ∈ R n × m × d E \in \mathbb{R}^{n \times m \times d} を表しますERn × m × d は次のように計算できます。

E i , j , : = EX i , j E_{i,j,:} = E_{X_{i,j}}Ei j :=Eバツ j

其中 E i , j , : E_{i,j,:} Ei j :出力行列EEを示しますE 中第 i i サンプルしてみましたjjthj 個の入力位置X i , j X_{i,j}に対応する埋め込みベクトルバツ j入力行列XXを示しますX 中第 i i サンプルしてみましたjjthj個の位置に対応する単語インデックス。

この式は埋め込み層で使用されます。仮説EEEは形状( V , d ) (V,d)( V d ) VVのテンソルV は語彙のサイズです、dddは埋め込みベクトルの次元です。XXXは形状( n , m ) (n,m)( n m )の整数テンソルnnnはバッチサイズ、mmmはシーケンスの長さです。次にE i , j , : E_{i,j,:}Ei j :語彙内のインデックスがX i , j X_{i,j}であることを示しますバツ j単語の埋め込みベクトル。ここでiii はバッチ内のサンプル インデックスを表します、jjj はシーケンス内の位置インデックスを表します。したがって、この式の意味は次のとおりです。バッチ内のiiiサンプルとシーケンス内のjj 番目j位置にある場合、埋め込み層は単語X i , j X_{i,j}バツ j単語埋め込みベクトルEX i , j に変換 E_{X_{i,j}}Eバツ j

深層学習モデルでは、通常、埋め込み層がモデルの最初の層として使用されます。モデルのトレーニング中に、埋め込み層の重み行列をバックプロパゲーションを通じて更新して、モデルの損失関数を最小限に抑えることができます。一方、テスト時には、事前トレーニングされた埋め込み層を使用して入力をベクトル表現に変換し、それを予測用のモデルに入力できます。

デコーダ

class Seq2SeqDecoder(Decoder):
    """用于序列到序列学习的循环神经网络解码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqDecoder, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
                          dropout=dropout)
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, *args):
        return enc_outputs[1]

    def forward(self, X, state):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X).permute(1, 0, 2)
        # 广播context,使其具有与X相同的num_steps
        context = state[-1].repeat(X.shape[0], 1, 1)
        X_and_context = torch.cat((X, context), 2)
        output, state = self.rnn(X_and_context, state)
        output = self.dense(output).permute(1, 0, 2)
        # output的形状:(batch_size,num_steps,vocab_size)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state

このコードは、シーケンス間学習の巡回ニューラル ネットワーク デコーダー Seq2SeqDecoder を定義します。その役割は、入力シーケンスをターゲット シーケンスに変換することです。

デコーダの構造はエンコーダの構造と似ていますが、入力シーケンスの埋め込みベクトルとエンコーダの出力ベクトルがデコーダの入力として連結される点が異なります。

具体的な説明は以下の通りです。

init関数: デコーダを初期化します。エンコーダーと同様に、埋め込み層、リカレント ニューラル ネットワーク層、および全結合層で構成されます。埋め込み層と巡回ニューラル ネットワーク層の入力次元は、embed_size + num_hiddens、つまり、現在のタイム ステップの入力と前のタイム ステップの出力ベクトルを特徴次元で結合したものです。全結合層の出力次元は、ターゲット語彙サイズ vocab_size です。

init_state 関数: デコーダの初期状態を初期化します。デコーダの初期状態はエンコーダの出力から取得する必要があるため、ここでは init_state 関数が実装されており、エンコーダの最後の隠れ状態をデコーダの初期状態として取得します。

forward関数:デコーダの前方計算処理。まず、埋め込み層を介して入力シーケンス X に対して単語埋め込みが実行され、次に、時間ステップ次元が最初の次元となるように、特徴次元に対して次元置換が実行されます。次に、ブロードキャストにより、エンコーダの最後の隠れ状態コンテキストが X タイムステップ次元にわたって繰り返され、特徴次元上の X と結合されて X_and_context が取得されます。X_and_context を巡回ニューラル ネットワーク層に入力して、デコーダーの出力ベクトル出力と最終的な隠れ状態状態を取得します。最後に、出力は全結合層を通じてターゲット語彙サイズの出力ベクトルに変換され、次元が置き換えられるため、時間ステップ次元は 2 次元に復元され、デコーダと最終的な隠し状態ステートが返されます。

上記のモデルの構造を図に示します。
ここに画像の説明を挿入

損失関数

各タイム ステップで、デコーダは出力トークンの確率分布を予測します。言語モデルと同様に、分布はソフトマックスを使用して取得でき、クロスエントロピー損失関数を計算することで最適化できます。前のセクションで、異なる長さのシーケンスを同じ形状のミニバッチにロードできるように、特定のフィラー トークンがシーケンスの末尾に追加されることを思い出してください。ただし、損失関数の計算からフィラー トークンの予測を除外する必要があります。

これを行うには、以下の sequence_mask 関数を使用して、無関係な項目をゼロにすることでマスクし、無関係な予測の後続の計算にゼロを乗算し、結果がゼロになるようにします。たとえば、2 つのシーケンスの有効長 (フィラー トークンを除く) がそれぞれ 1 と 2 である場合、最初のシーケンスの最初の項目と 2 番目のシーケンスの最初の 2 つの項目の後の残りの項目はゼロにクリアされます。

#@save
def sequence_mask(X, valid_len, value=0):
    """在序列中屏蔽不相关的项"""
    maxlen = X.size(1)
    mask = torch.arange((maxlen), dtype=torch.float32,
                        device=X.device)[None, :] < valid_len[:, None]
    X[~mask] = value
    return X

X = torch.tensor([[1, 2, 3], [4, 5, 6]])
sequence_mask(X, torch.tensor([1, 2]))

これは、シーケンス内の無関係な項目をマスクするための関数であり、主にシーケンス損失関数を計算するときにフィラー項目を除外するために使用されます。その入力は 3 つのパラメータで構成されます。

X: マスクされるシーケンス、形状は (batch_size、sequence_length、embedding_size);
valid_len: 各シーケンスの有効な長さ、形状は (batch_size,);
value: 無関係な項目を置換するために使用される値、デフォルトは 0 。
この関数では、まず X.size(1) を通じてシーケンスの最大長 maxlen を取得し、次に torch.arange() を使用して形状 (1, maxlen) のテンソルを作成します。これは、配列内の各位置の添字を表します。シーケンス。数量の dtype およびデバイスは X と同じです。次に、ブロードキャスト メカニズムを使用して、valid_len を形状のテンソル (batch_size、maxlen) に変換します。各行の最初の valid_len[i] 要素は True で、残りは False です。これは、i 番目の位置より前の要素を意味します。という順番で効果的です。最後に、X[~mask] を value に設定して、無関係な項目を指定された値に置き換えることで、マスクされたシーケンスが取得されます。最後に、関数はマスクされたシーケンスを返します。

ブロードキャスト メカニズム
ブロードキャスト (ブロードキャスト) は PyTorch の重要なメカニズムであり、テンソルの加算と減算、乗算と除算、要素ごとの演算など、2 つのテンソル間で異なる形状の一部の演算を可能にします。2 つのテンソルの形状が異なる場合、特定のルールを満たしていれば、それらをブロードキャストして同じ形状にし、対応する操作を実行できます。
ブロードキャストのルールは次のとおりです。

  1. 2 つのテンソルの次元が異なる場合は、次元が同じになるまで、小さい方のテンソルの形状の先頭に 1 が付加されます。
  2. 2 つのテンソルが特定の次元に沿って異なるサイズを持ち、そのうちの 1 つがサイズ 1 である場合、その次元に沿ってそのテンソルを拡張して、もう一方のテンソルと同じサイズにすることができます。
  3. 2 つのテンソルの次元のサイズが異なり、どちらのサイズも 1 でない場合、ブロードキャストは失敗し、操作はエラーで失敗します。

たとえば、a + b を実行する場合、a の形状が (3, 4)、b の形状が (4,) である場合、ブロードキャスト ルールに従って、b を (1, 4) に拡張して比較できます。加算すると、結果の形状は (3, 4) になります。このようにして、データを明示的にコピーすることなく、異なる形状の 2 つのテンソルに対して対応する操作を実行できるため、コードの可読性と効率が大幅に向上します。

#@save
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
    """带遮蔽的softmax交叉熵损失函数"""
    # pred的形状:(batch_size,num_steps,vocab_size)
    # label的形状:(batch_size,num_steps)
    # valid_len的形状:(batch_size,)
    def forward(self, pred, label, valid_len):
        weights = torch.ones_like(label)
        weights = sequence_mask(weights, valid_len)
        self.reduction='none'
        unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(
            pred.permute(0, 2, 1), label)
        weighted_loss = (unweighted_loss * weights).mean(dim=1)
        return weighted_loss

これは、マスクされたソフトマックス クロスエントロピー損失関数として知られる、PyTorch の組み込みクロスエントロピー損失関数 nn.CrossEntropyLoss から継承されたクラスです。このクラスの役割は、ラベル シーケンスのパディング部分を無視できる損失関数を実装することです。

具体的には、このクラスの forward 関数は、モデルによって予測された結果 pred、実ラベル シーケンス label、および有効シーケンス長 valid_len の 3 つの入力を受け入れます。このうち、pred の形状は (batch_size, num_steps, vocab_size) であり、モデルが各タイム ステップで各カテゴリを予測する確率を示し、ラベルの形状は (batch_size, num_steps) で、実際のラベル シーケンス (カテゴリ番号) を示します。 ); valid_len 形状は (batch_size,) で、各サンプルの有効長を示します。損失を計算するときは、まずラベルと同じ形状のテンソル重みを作成し、以前に実装された sequence_mask 関数を呼び出して無効な位置を設定します重み (シーケンス長の後) の値は 0 に設定されます。次に、PyTorch の組み込みクロスエントロピー損失関数を使用して、重み付けされていない損失を計算します。ここでは、ラベルの形状と一致するように、pred の形状を (batch_size, vocab_size, num_steps) から (batch_size, num_steps, vocab_size) に変換する必要があります。 。最後に、タイムステップ次元全体で損失を平均し、それを返します。各サンプルの実効長は損失の計算時にすでに考慮されているため、返す前に実効長で割る必要はありません。

注意:unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(pred.permute(0, 2, 1), label)

クロスエントロピー損失関数を計算する場合、入力 pred は通常、(batch_size, num_steps, vocab_size) の形状を持つ 3 次元テンソルです。ここで、batch_size はバッチ サイズ、num_steps はタイム ステップ数、vocab_size は語彙のサイズ。ラベル label の形状は (batch_size, num_steps) であり、2 次元テンソルです。クロスエントロピー損失関数を計算するときは、pred と label の最後の次元を揃える必要があります。つまり、 nn.CrossEntropyLoss を呼び出してクロスエントロピー損失を計算できるように、 pred の最後の次元を最初の次元に移動します。 。
したがって、コードは pred.permute(0, 2, 1) を使用して pred の最後の 2 次元を (batch_size, vocab_size, num_steps) の形状に交換し、それを nn.CrossEntropyLoss の forward メソッドに渡して Cross を計算します。エントロピー損失。

訓練

#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
        if type(m) == nn.GRU:
            for param in m._flat_weights_names:
                if "weight" in param:
                    nn.init.xavier_uniform_(m._parameters[param])

    net.apply(xavier_init_weights)
    net.to(device)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)
    loss = MaskedSoftmaxCELoss()
    net.train()
    animator = d2l.Animator(xlabel='epoch', ylabel='loss',
                     xlim=[10, num_epochs])
    for epoch in range(num_epochs):
        timer = d2l.Timer()
        metric = d2l.Accumulator(2)  # 训练损失总和,词元数量
        for batch in data_iter:
            optimizer.zero_grad()
            X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
            bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
                          device=device).reshape(-1, 1)
            dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 强制教学
            Y_hat, _ = net(X, dec_input, X_valid_len)
            l = loss(Y_hat, Y, Y_valid_len)
            l.sum().backward()      # 损失函数的标量进行“反向传播”
            d2l.grad_clipping(net, 1)
            num_tokens = Y_valid_len.sum()
            optimizer.step()
            with torch.no_grad():
                metric.add(l.sum(), num_tokens)
        if (epoch + 1) % 10 == 0:
            animator.add(epoch + 1, (metric[0] / metric[1],))
    print(f'loss {
      
      metric[0] / metric[1]:.3f}, {
      
      metric[1] / timer.stop():.1f} '
        f'tokens/sec on {
      
      str(device)}')
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

ここに画像の説明を挿入

予測する

ここに画像の説明を挿入

#@save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,
                    device, save_attention_weights=False):
    """序列到序列模型的预测"""
    # 在预测时将net设置为评估模式
    net.eval()
    src_tokens = src_vocab[src_sentence.lower().split(' ')] + [
        src_vocab['<eos>']]
    enc_valid_len = torch.tensor([len(src_tokens)], device=device)
    src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])
    # 添加批量轴
    enc_X = torch.unsqueeze(
        torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)
    enc_outputs = net.encoder(enc_X, enc_valid_len)
    dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)
    # 添加批量轴
    dec_X = torch.unsqueeze(torch.tensor(
        [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)
    output_seq, attention_weight_seq = [], []
    for _ in range(num_steps):
        Y, dec_state = net.decoder(dec_X, dec_state)
        # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入
        dec_X = Y.argmax(dim=2)
        pred = dec_X.squeeze(dim=0).type(torch.int32).item()
        # 保存注意力权重(稍后讨论)
        if save_attention_weights:
            attention_weight_seq.append(net.decoder.attention_weights)
        # 一旦序列结束词元被预测,输出序列的生成就完成了
        if pred == tgt_vocab['<eos>']:
            break
        output_seq.append(pred)
    return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq

このコードはシーケンスツーシーケンス (seq2seq) モデルの予測を実装しており、ソース言語の文が与えられると、トレーニングされた seq2seq モデルを使用してターゲット言語の文に翻訳されます。

具体的な手順は次のとおりです。

まず、seq2seq モデルを評価モード (net.eval()) に設定します。

ソース言語の文を前処理します。対応する語彙インデックスに変換し、ターミネータ "" を追加し、最後に truncate_pad 関数を使用して、指定された長さ num_steps まで切り詰めるかパディングします。

処理された原言語シーケンス enc_X を seq2seq モデルのエンコーダに入力し、エンコーダの出力 enc_outputs と enc_X の実効長 enc_valid_len を取得します。

エンコーダの出力 enc_outputs と enc_valid_len を seq2seq モデルのデコーダの init_state 関数に入力し、デコーダの隠し状態 dec_state を初期化します。

デコーダの入力を開始文字「」に初期化し、それを最初のタイム ステップの入力に追加します。

予測を行うために num_steps 回繰り返します。

a) デコーダの入力 dec_X と現在の隠れ状態 dec_state を seq2seq モデルのデコーダに入力し、現在のタイム ステップの出力 Y と新しい隠れ状態 dec_state を取得します。

b) Y のソフトマックス出力に従って、次のタイム ステップの入力として最も高い確率を持つ単語を選択します。つまり、Y.argmax(dim=2) を使用して次のタイム ステップの入力 dec_X を取得します。

c) dec_Xをint型に変換し、出力シーケンスoutput_seqの次のタイムステップの予測値として保存します。予測値がターミネータ「」の場合は予測を停止します。

d) アテンションの重みを保存する必要がある場合 (save_attention_weights=True)、アテンションの重み net.decoder.attention_weights をattention_weight_seq に保存します。

Output_seq の予測値をターゲット言語の文に変換し、 tgt_vocab.to_tokens 関数を使用して予測値をターゲット言語の語彙に変換します。アテンションの重みを保存する必要がある場合は、attention_weight_seq も返されます。

このコードの主な役割は、トレーニングされた seq2seq モデルを翻訳予測に使用する方法を示すことです。ここでの予測は、ターゲット言語の文全体を一度に予測するのではなく、段階的に実行されることに注意してください。各タイム ステップで、モデルは次の単語のみを予測し、それを次のタイム ステップの入力として使用します。

詳細コード:

enc_X = torch.unsqueeze(torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)

このコード行の機能は、入力 src_tokens を PyTorch のテンソル形式に変換し、最初の次元に次元を追加すること、つまり、バッチの次元を増やすことです。具体的には、src_tokens は入力シーケンスを含む Python リストであり、リスト内の各要素は入力シーケンス内の単語を表す整数です。torch.tensor(src_tokens, dtype=torch.long, device=device) は、src_tokens を PyTorch テンソル形式に変換し、データ型を length、デバイスを device として指定します。torch.unsqueeze 関数は、最初の次元に次元を追加し、形状のテンソル (num_steps,) を形状のテンソル (1, num_steps) に変換します。結果として得られる enc_X は、形状 (1, num_steps) のテンソルであり、サイズ 1 のバッチ内の入力シーケンスを表します。

予測シーケンスの評価

予測された配列を実際のラベル配列と比較することで評価できます。Papineni et al., 2002 によって提案された BLEU (二言語評価理解) は、最初は機械翻訳の結果を評価するために使用されましたが、現在では多くのアプリケーションの出力シーケンスの品質を測定するために広く使用されています。原則として、予測シーケンス内のメタグラム (n グラム) について、BLEU の評価は、この n グラムがラベル シーケンスに出現するかどうかです。

BLEU (Bilingual Evaluation Understudy) は、機械翻訳の分野で一般的に使用される評価指標で、機械翻訳の結果と人による翻訳結果の類似性を比較することを目的としています。

BLEUは、機械翻訳結果の品質を評価するもので、計算方法には、機械翻訳結果と参考訳文との単語の重複を考慮し、機械翻訳結果が高いほど優れていることと、機械翻訳結果を考慮することの2つの側面があります。翻訳結果と参考訳文の語順情報が一致しているかどうか。

BLEU アルゴリズムの計算方法は比較的複雑で、まず機械翻訳結果を異なる n グラムに従って複数のフレーズに分割し、次に基準翻訳での各フレーズの出現数と機械内での出現数を計算します。これらの N グラムの精度値を考慮して、最終的な BLEU スコアを取得します。

一般的に使用される n-gram 範囲は 1 ~ 4 です。つまり、unigram、bigram、trigram、および fourgram のスコアをそれぞれ計算し、最終的にこれら 4 つのスコアを考慮して BLEU スコアを計算します。一般に、BLEU スコアが高いほど機械翻訳の結果は優れていますが、具体的な評価は他の要素と組み合わせて考慮する必要があります。

計算式

BLEU (Bilingual Evaluation Understudy) は、翻訳結果の精度や流暢性などの要素を統合した、機械翻訳結果を自動評価するための指標です。具体的には、翻訳結果と参照回答の類似度を計算し、0 から 1 までのスコアを与えます。スコアが高いほど、翻訳結果が優れていることを示します。BLEUの計算式は以下のとおりです。

BLEU ⁡ = BP ⁡ ⋅ exp ⁡ ( ∑ n = 1 N wn log ⁡ pn ) \operatorname{BLEU} = \operatorname{BP}\cdot\exp\left(\sum_{n=1}^Nw_n\log p_n\右)ブルー=血圧経験値(n = 1Nwログ_p

ここで、BP ⁡ \オペレーター名{BP}BPはフレーズペナルティ項目です、wn w_nwですnタプルの重みwn = 1 N w_n=\frac{1}{N}w=N1)、pn p_npですnタプルの精度。

nタプルの精度は、 nnに表示される翻訳結果を指します。n タプル内の単語は、参照回答と同じnタプルnタプル内のワード数の比率具体的には、参照回答の各nnnタプル、翻訳結果に出現する回数を計算し、その回数の最小値を分子として、nnnタプルの合計数が分母として使用され、最後にこれらの比率が加算されてnnnタプル精度pn p_np

フレーズペナルティBP ⁡ \operatorname{BP}BP は、フレーズを使用しすぎてスコアが高すぎる翻訳結果を避けるために導入されています。その計算式は次のとおりです。

BP ⁡ = { 1 、 c > re 1 − rc の場合、それ以外の場合 \operatorname{BP} = \begin{cases} 1, & \text{if }c>r \\ e^{1-\frac{r}{ c}}, & \text{その他} \end{cases}血圧={ 1 e1 crcの場合 >rそれ以外の場合

その中には、cccは翻訳結果の総単語数、rrrは、参照回答の総単語数です。翻訳結果の単語数が参照回答の単語数より多い場合、ペナルティ項BP ⁡ \operatorname{BP}BPは 1 に等しく、それ以外の場合はBP ⁡ \operatorname{BP}BPはe 1 − rce^{1-\frac{r}{c}}に等しいe1 cr

pn p_npBLEUインジケーターを計算する際のn-gramの精度を計算するためのインジケーターです。これは、予測シーケンス内の N グラムが参照シーケンスに出現する頻度を表します。

具体的には、基準訳文が m 文あると仮定すると、n ごとに、予測シーケンスと基準訳文の n グラム数は次の式で計算できます。

count ⁡ n = ∑ i : ci , … , i + n − 1 ∈ c min ⁡ ( freq ⁡ ( ci , … , i + n − 1 ) , max ⁡ j = 1 m freq ⁡ ( rj , … , j + n − 1 ) ) \operatorname{count}n = \sum{\boldsymbol{i}:\boldsymbol{c}{\boldsymbol{i}, \ldots, \boldsymbol{i}+n-1}\in \boldsymbol {c}} \min(\operatorname{freq}(\boldsymbol{c}{\boldsymbol{i}, \ldots, \boldsymbol{i}+n-1}), \max_{\boldsymbol{j}=1 }^{m} \operatorname{freq}(\boldsymbol{r}_{\boldsymbol{j}, \ldots, \boldsymbol{j}+n-1}))カウントn=:c i+n1cmin ( freq ( c i ,+n1 ) j =1最大メートル周波数( rj ,, j +n1))

ここで、c \boldsymbol{c}cは予測されたシーケンス、rj \boldsymbol{r}_jrjジェイジェイですj参考翻訳。freq ⁡ ( x ) \operatorname{freq}(\boldsymbol{x})freq ( x )は n グラムx \boldsymbol{x}シーケンス内でx が出現する頻度、 ii私はすべてc \boldsymbol{c}と一緒ですcの n-gram が一致する場所のインデックス。

この式は、BLEU メトリクスのカウント関数であり、機械翻訳出力に N グラムが出現する回数を表します。その中には、ccc は機械翻訳の出力シーケンス、r 1 , … , rm r_1,\ldots,r_mr1rメートル参照翻訳のシーケンスを表します、i \boldsymbol{i}i は、考えられるすべての n グラムを走査するためのポインタです。freq ⁡ ( ci , … , i + n − 1 ) \operatorname{freq}(\boldsymbol{c}{\boldsymbol{i}, \ldots, \boldsymbol{ i }+n-1})周波数( c i ,+n1 )は、n グラムが機械翻訳の出力に出現する回数を示します。freq ⁡ ( rj , … , j + n − 1 ) \operatorname{freq}(\boldsymbol{r}{\boldsymbol{j}, \ ldots、\boldsymbol{j}+n-1})周波数( r j ,j+n1 )参照翻訳内でのこの n-gram の出現数を示します。この式は、BLEU メトリック内の n グラム一致の数を計算するために使用されます。

次に、精度pn p_nを計算します。p次の式を使用できます。

pn = ∑ i : ci , … , i + n − 1 ∈ c min ⁡ ( freq ⁡ ( ci , … , i + n − 1 ) , max ⁡ j = 1 m freq ⁡ ( rj , … , j + n − 1 ) ) ∑ i : ci , … , i + n − 1 ∈ c freq ⁡ ( ci , … , i + n − 1 ) p_n = \frac{\sum_{\boldsymbol{i}:\boldsymbol{c}{ \boldsymbol{i}, \ldots, \boldsymbol{i}+n-1}\in \boldsymbol{c}} \min(\operatorname{freq}(\boldsymbol{c}{\boldsymbol{i}, \ldots , \boldsymbol{i}+n-1}), \max_{\boldsymbol{j}=1}^{m} \operatorname{freq}(\boldsymbol{r}{\boldsymbol{j}, \ldots, \ボールドシンボル{j}+n-1}))}{\sum{\boldsymbol{i}:\boldsymbol{c}{\boldsymbol{i}, \ldots, \boldsymbol{i}+n-1}\in \ bulledsymbol{c}} \operatorname{freq}(\boldsymbol{c}{\boldsymbol{i}, \ldots, \boldsymbol{i}+n-1})}p=:c i+n1c周波数( c i ,+n1 )i : c i , , i + n 1cmin ( freq ( c i ,+n1 ) 最大j =1メートル周波数( r j ,j+n1 ) )

ここで、分子は予測シーケンスと参照翻訳の間の n グラム一致の総数を表し、分母は予測シーケンス内の n グラムの総数を表します。

つまり、pn p_npは 2 つの量の比です。1 つ目は予測シーケンスとラベル シーケンス内の一致するメタ構文の数、2 つ目は予測シーケンス内のメタ構文の数の比です。

達成

def bleu(pred_seq, label_seq, k):  #@save
    """计算BLEU"""
    pred_tokens, label_tokens = pred_seq.split(' '), label_seq.split(' ')
    len_pred, len_label = len(pred_tokens), len(label_tokens)
    score = math.exp(min(0, 1 - len_label / len_pred))
    for n in range(1, k + 1):
        num_matches, label_subs = 0, collections.defaultdict(int)
        for i in range(len_label - n + 1):
            label_subs[' '.join(label_tokens[i: i + n])] += 1
        for i in range(len_pred - n + 1):
            if label_subs[' '.join(pred_tokens[i: i + n])] > 0:
                num_matches += 1
                label_subs[' '.join(pred_tokens[i: i + n])] -= 1
        score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n))
    return score

評価

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, attention_weight_seq = predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{
      
      eng} => {
      
      translation}, bleu {
      
      bleu(translation, fra, k=2):.3f}')

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_51957239/article/details/129542045