(7-3-2)神经机器翻译(NMT): 基于NMT的简易翻译系统

7.3.4  基于NMT的简易翻译系统

在本节的内容中,将展示构建一个基于NMT模型的机器翻译系统的过程,包括数据准备、模型架构、训练和评估。本项目使用注意力机制来提高翻译质量,并提供了可视化工具来帮助理解模型的翻译过程。这个项目可用作机器翻译任务的起点,可以根据需要进行扩展和改进。

具体来说,本项目的功能概括如下:

  1. 数据准备:从一个包含英语句子和对应葡萄牙语翻译的数据集中获取句子对,并进行基本的数据清洗和预处理。
  2. 模型架构:使用编码器-解码器(Encoder-Decoder)架构,其中编码器将输入句子编码为固定长度的向量,而解码器将该向量解码为目标语言句子。
  3. 词汇表:创建英语和葡萄牙语的词汇表,并为每个单词建立索引映射。
  4. 模型训练:通过多个训练轮次,使用批量数据来训练模型,优化模型权重以最小化翻译误差。在训练期间,使用教师强制(Teacher Forcing)来加速学习。
  5. 注意力机制:模型采用注意力机制,允许模型在翻译过程中关注输入句子的不同部分,以提高翻译质量。
  6. 模型评估:提供了一个评估函数,可用于输入句子并获得模型的翻译输出以及注意力权重分布。
  7. 随机样本预测:提供了一个函数,可以随机选择验证集中的样本,进行翻译预测并生成注意力热图,以帮助理解模型的翻译行为。

实例7-3:综合项目:翻译系统(源码路径:daima\7\NMT-translation.ipynb

(1)安装Chart Studio

本项目用到了库Chart Studio,这是Plotly提供的在线图表编辑和共享平台,允许用户轻松创建、自定义、分享和部署交互式图表和数据可视化。Chart Studio的目标是使数据可视化变得更加容易,并提供工具来探索、理解和传达数据。它与Plotly的Python、R和JavaScript图表库紧密集成,使用户能够轻松地将其创建的图表集成到数据科学和Web开发项目中。安装Chart Studio的命令如下所示:

pip install chart-studio

(2)准备数据集文件,本项目使用了一个包含英语句子及其葡萄牙语翻译的数据集文件por.txt,在文件中的每一行,文本文件包含一个英语句子及其法语翻译,用制表符分隔。编写如下代码递归遍历保存数据集的目录'input'以及其子目录中的所有文件,并将它们的完整路径打印到控制台。

import os
for dirname, _, filenames in os.walk('input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

执行后输出:

input/por.txt

(3)使用UTF-8编码格式打开文件por.txt,然后将文件内容按行分割,并输出文件的第5000行到第5010行的内容。

file_path = '../input/por.txt'
lines = open(file_path, encoding='UTF-8').read().strip().split('\n')
lines[5000:5010]

执行后会输出:

['Will it rain?\tSerá que chove?\tCC-BY 2.0 (France) Attribution: tatoeba.org #8918600 (CK) & #8930552 (JGEN)',
 'Wish me luck.\tDeseje-me sorte.\tCC-BY 2.0 (France) Attribution: tatoeba.org #2254917 (CK) & #872788 (alexmarcelo)',
 "Won't you go?\tVocê não vai?\tCC-BY 2.0 (France) Attribution: tatoeba.org #241051 (CK) & #6212788 (bill)",
 'Write in ink.\tEscreva à tinta.\tCC-BY 2.0 (France) Attribution: tatoeba.org #3258764 (CM) & #7351595 (alexmarcelo)',
 'Write in ink.\tEscreva a tinta.\tCC-BY 2.0 (France) Attribution: tatoeba.org #3258764 (CM) & #7351606 (alexmarcelo)',
 'Write to Tom.\tEscreva para o Tom.\tCC-BY 2.0 (France) Attribution: tatoeba.org #2240357 (CK) & #5985551 (Ricardo14)',
 'Years passed.\tPassaram os anos.\tCC-BY 2.0 (France) Attribution: tatoeba.org #282197 (CK) & #977841 (alexmarcelo)',
 'Years passed.\tAnos se passaram.\tCC-BY 2.0 (France) Attribution: tatoeba.org #282197 (CK) & #2324530 (Matheus)',
 'You amuse me.\tVocê me diverte.\tCC-BY 2.0 (France) Attribution: tatoeba.org #268209 (CM) & #1199960 (alexmarcelo)',
 'You are late.\tVocê está atrasado.\tCC-BY 2.0 (France) Attribution: tatoeba.org #277403 (CK) & #1275547 (alexmarcelo)']

(4)打印输出在前面代码中读取的文本文件的行数,也就是文件中的记录总数。

print("total number of records: ",len(lines))

在上述代码中,len(lines) 返回 lines 列表的长度,也就是文件中的行数。然后,通过 print() 函数将这个行数与文本消息 "total number of records: " 一起打印到屏幕上,以提供用户关于文件中记录数量的信息。执行后会输出:

total number of records:  168903

(5)使用Python标准库中的string模块来创建两个关于文本处理的工具,分别是exclude和remove_digits。这两个工具可以在文本处理中非常有用,例如,你可以使用它们来清洗文本,去除标点符号或数字,以便进行文本分析或其他自然语言处理任务。

exclude = set(string.punctuation)
remove_digits = str.maketrans('', '', string.digits)

(6)定义一个名为preprocess_eng_sentence的函数,用于预处理英语句子,以便在自然语言处理任务中使用,如机器翻译或文本生成。

def preprocess_port_sentence(sent):
    '''Function to preprocess Portuguese sentence'''
    sent = re.sub("'", '', sent) # remove the quotation marks if any
    sent = ''.join(ch for ch in sent if ch not in exclude)
    #sent = re.sub("[२३०८१५७९४६]", "", sent) # remove the digits
    sent = sent.strip()
    sent = re.sub(" +", " ", sent) # remove extra spaces
    sent = '<start> ' + sent + ' <end>' # add <start> and <end> tokens
    return sent

(7)定义一个名为preprocess_port_sentence的函数,用于预处理葡萄牙语句子。

def preprocess_port_sentence(sent):
    '''Function to preprocess Portuguese sentence'''
    sent = re.sub("'", '', sent) # remove the quotation marks if any
    sent = ''.join(ch for ch in sent if ch not in exclude)
    #sent = re.sub("[२३०८१५७९४६]", "", sent) # remove the digits
    sent = sent.strip()
    sent = re.sub(" +", " ", sent) # remove extra spaces
    sent = '<start> ' + sent + ' <end>' # add <start> and <end> tokens
    return sent

(8)创建列表sent_pairs,其中包含了经过预处理的英语句子和葡萄牙语句子的配对。

sent_pairs = []
for line in lines:
    sent_pair = []
    eng = line.rstrip().split('\t')[0]
    port = line.rstrip().split('\t')[1]
    eng = preprocess_eng_sentence(eng)
    sent_pair.append(eng)
    port = preprocess_port_sentence(port)
    sent_pair.append(port)
    sent_pairs.append(sent_pair)
sent_pairs[5000:5010]

执行后会输出:

[['<start> will it rain <end>', '<start> Será que chove <end>'],
 ['<start> wish me luck <end>', '<start> Desejeme sorte <end>'],
 ['<start> wont you go <end>', '<start> Você não vai <end>'],
 ['<start> write in ink <end>', '<start> Escreva à tinta <end>'],
 ['<start> write in ink <end>', '<start> Escreva a tinta <end>'],
 ['<start> write to tom <end>', '<start> Escreva para o Tom <end>'],
 ['<start> years passed <end>', '<start> Passaram os anos <end>'],
 ['<start> years passed <end>', '<start> Anos se passaram <end>'],
 ['<start> you amuse me <end>', '<start> Você me diverte <end>'],
 ['<start> you are late <end>', '<start> Você está atrasado <end>']]

(9)定义类LanguageIndex,用于创建一个单词到索引的映射和索引到单词的映射,以及构建语言的词汇表。这个类可以用于构建针对某种语言的索引映射,通常在自然语言处理任务中用于文本处理。

    def __init__(self, lang):
        self.lang = lang
        self.word2idx = {}
        self.idx2word = {}
        self.vocab = set()

        self.create_index()

    def create_index(self):
        for phrase in self.lang:
            self.vocab.update(phrase.split(' '))

        self.vocab = sorted(self.vocab)

        self.word2idx['<pad>'] = 0
        for index, word in enumerate(self.vocab):
            self.word2idx[word] = index + 1

        for word, index in self.word2idx.items():
            self.idx2word[index] = word

在上述代码中,构造函数__init__(self, lang)接受一个参数lang,表示要构建索引的语言。在构造函数中,会初始化word2idx和idx2word字典,以及vocab集合。然后调用create_index方法来创建索引。方法create_index(self)用于创建单词到索引的映射和索引到单词的映射,并构建词汇表,次方法的具体步骤如下:

  1. 遍历语言中的每个短语(通常是句子),并使用空格分割短语,将单词添加到vocab集合中,以构建词汇表。
  2. 对词汇表进行排序,以确保单词按照特定顺序排列。
  3. 添加一个特殊的'<pad>'标记到word2idx字典中,用于填充序列(通常用于序列长度不一致的情况,例如机器翻译中的句子)。
  4. 遍历词汇表中的每个单词,并将单词到索引和索引到单词的映射添加到word2idx和idx2word字典中,以构建完整的索引映射。

(10)定义函数max_length,它接受一个名为tensor的参数,其中tensor通常表示一个包含多个序列的数据结构(例如,一个列表的列表)。该函数的目的是找出tensor中最长序列的长度。函数max_length的主要逻辑是使用列表推导式来遍历tensor中的每个序列(通常是句子或文本序列),并计算每个序列的长度(通常是单词或字符的数量)。然后,使用max()函数找出这些长度中的最大值,即最长的序列的长度。

def max_length(tensor):
    return max(len(t) for t in tensor)

(11)定义了一个名为load_dataset的函数,其目的是加载并预处理已经清理好的输入和输出句子对,并将它们向量化成整数张量,同时构建相应的语言索引。

def load_dataset(pairs, num_examples):
    # pairs => 已经创建好的清理过的输入输出句子对

    # 使用上面定义的类来为语言建立索引
    inp_lang = LanguageIndex(en for en, ma in pairs)
    targ_lang = LanguageIndex(ma for en, ma in pairs)
    
    # 将输入语言和目标语言向量化
    
    # 英语句子
    input_tensor = [[inp_lang.word2idx[s] for s in en.split(' ')] for en, ma in pairs]
    
    # 马拉地语句子
    target_tensor = [[targ_lang.word2idx[s] for s in ma.split(' ')] for en, ma in pairs]
    
    # 计算输入和输出张量的最大长度
    # 这里,我们将它们设置为数据集中最长的句子的长度
    max_length_inp, max_length_tar = max_length(input_tensor), max_length(target_tensor)
    
    # 填充输入和输出张量到最大长度
    input_tensor = tf.keras.preprocessing.sequence.pad_sequences(input_tensor, 
                                                                 maxlen=max_length_inp,
                                                                 padding='post')
    
    target_tensor = tf.keras.preprocessing.sequence.pad_sequences(target_tensor, 
                                                                  maxlen=max_length_tar, 
                                                                  padding='post')
    
    return input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_tar

(12)调用了之前定义的函数load_dataset,用经过预处理的句子对(sent_pairs)作为输入,以及总句子数量(len(lines))作为参数。

input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_targ = load_dataset(sent_pairs, len(lines))

在上述代码中,函数load_dataset返回了如下所示的值:

  1. input_tensor:向量化后的输入张量,其中包含了英语句子的整数表示。
  2. target_tensor:向量化后的目标张量,其中包含了葡萄牙语句子的整数表示。
  3. inp_lang:英语语言的索引对象,用于将单词转换为整数索引。
  4. targ_lang:葡萄牙语语言的索引对象,用于将单词转换为整数索引。
  5. max_length_inp:输入张量的最大长度。
  6. max_length_targ:目标张量的最大长度。

这些值将用于后续的自然语言处理任务,例如机器翻译或文本生成,以确保数据被正确向量化和填充。

(13)将数据集划分为训练集和验证集,以便在训练模型时进行验证和性能评估。训练集用于训练模型,验证集用于评估模型的性能。在这里,80%的数据用于训练,20%的数据用于验证。

input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.1, random_state = 101)

len(input_tensor_train), len(target_tensor_train), len(input_tensor_val), len(target_tensor_val)

执行后会输出:

(152012, 152012, 16891, 16891)

(14)设置一些模型训练时的超参数和创建数据集,这些设置和数据集的创建通常用于训练神经网络模型,尤其是在自然语言处理任务中。训练数据集的数据将被分割成批次,以便在每个训练周期中对模型进行训练。

BUFFER_SIZE = len(input_tensor_train)
BATCH_SIZE = 64
N_BATCH = BUFFER_SIZE//BATCH_SIZE
embedding_dim = 256
units = 1024
vocab_inp_size = len(inp_lang.word2idx)
vocab_tar_size = len(targ_lang.word2idx)

dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)

对上述代码的具体说明如下:

  1. BUFFER_SIZE = len(input_tensor_train):BUFFER_SIZE表示数据集的缓冲区大小,它被设置为训练集的长度,用于数据集的随机化(洗牌)操作。
  2. BATCH_SIZE = 64:BATCH_SIZE表示每个训练批次中的样本数量,这里设置为64,即每次训练模型时会使用64个样本。
  3. N_BATCH = BUFFER_SIZE//BATCH_SIZE:N_BATCH表示每个训练周期中的批次数量,它是总样本数除以批次大小的结果。
  4. embedding_dim = 256:embedding_dim表示嵌入层的维度,通常用于将整数索引转换为密集的嵌入向量。
  5. units = 1024:units表示模型中RNN(循环神经网络)层的单元数量。
  6. vocab_inp_size = len(inp_lang.word2idx):vocab_inp_size表示输入语言的词汇表大小,即不同单词的数量。
  7. vocab_tar_size = len(targ_lang.word2idx):vocab_tar_size表示目标语言的词汇表大小,即不同单词的数量。
  8. tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)):使用from_tensor_slices方法创建一个数据集,将训练集中的输入和目标张量一一对应。
  9. .shuffle(BUFFER_SIZE):对数据集进行随机化(洗牌),使用BUFFER_SIZE作为缓冲区大小,以确保每个训练周期的数据都是随机的。
  10. .batch(BATCH_SIZE, drop_remainder=True):将数据集划分为批次,每个批次包含BATCH_SIZE个样本,drop_remainder=True表示如果剩余不足一个批次的样本将被丢弃,以确保每个批次都有相同数量的样本。

在本项目的模型中将使用的是GRU(Gated Recurrent Unit),而不是LSTM(Long Short-Term Memory)。GRU和LSTM都是循环神经网络(RNN)的变种,用于处理序列数据。GRU相对于LSTM更加简单,因为它合并了内部状态和输出状态,只有一个状态,而LSTM有两个状态(细胞状态和隐藏状态)。

注意:在实际应用中,选择使用GRU而不是LSTM的原因通常有以下几点:

  1. 实现更简单:GRU的内部结构相对较简单,只有一个状态,这使得它在实现和调试上更容易。
  2. 更快的训练:由于参数较少,GRU通常在训练时速度更快,可以更快地收敛。
  3. 更少的过拟合:GRU在某些情况下对数据噪声更具有鲁棒性,因此可能更不容易过拟合。
  4. 较小的模型:由于参数较少,GRU的模型大小相对较小,适合在计算资源有限的情况下使用。

然而,选择使用GRU或LSTM通常依赖于具体任务和数据集,因为它们的性能和适用性在不同情况下可能会有所不同。在某些情况下,LSTM可能表现更好,特别是在需要捕捉长期依赖关系的任务中。

(15)定义函数gru(units),用于创建一个GRU(Gated Recurrent Unit)层。GRU是一种循环神经网络(RNN)的变体,常用于处理序列数据。函数gru(units)接受一个参数units,表示GRU层中的单元数(或隐藏状态的维度)。

def gru(units):
    return tf.keras.layers.GRU(units, 
                                   return_sequences=True, 
                                   return_state=True, 
                                   recurrent_activation='sigmoid', 
                                   recurrent_initializer='glorot_uniform')

上述函数的目的是在神经网络模型中创建一个GRU层,用于处理序列数据,如文本序列或时间序列。GRU层具有学习能力,可以捕捉序列中的信息和依赖关系,常用于自然语言处理和其他序列建模任务。

下一步是定义编码器(encoder)和解码器(decoder)网络,其中编码器用于将输入的英语句子转换成隐藏状态和细胞状态,而解码器用于将这些状态转换成葡萄牙语句子。这是一个常见的序列到序列(seq2seq)模型结构,通常用于机器翻译等任务。

编码器的输入是英语句子,输出是GRU的隐藏状态和细胞状态。在神经网络中,通常使用RNN或GRU来实现编码器的功能,例如将输入序列编码为固定维度的隐藏状态。解码器则接受这些状态并生成目标语言的句子。编码器和解码器的具体实现通常需要考虑模型架构、层数、超参数等细节。

(16)定义编码器(Encoder)的类,用于将输入的英语句子编码成隐藏状态。编码器是一个神经网络模型,通常采用RNN(循环神经网络)或GRU(Gated Recurrent Unit)来实现。

class Encoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
        super(Encoder, self).__init__()
        self.batch_sz = batch_sz
        self.enc_units = enc_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.gru = gru(self.enc_units)
        
    def call(self, x, hidden):
        x = self.embedding(x)
        output, state = self.gru(x, initial_state = hidden)        
        return output, state
    
    def initialize_hidden_state(self):
        return tf.zeros((self.batch_sz, self.enc_units))

下一步是定义解码器(Decoder),解码器的任务是接收编码器的隐藏状态和细胞状态,以及输入的句子(通常是目标语言的句子),然后生成输出句子。这是一个典型的序列到序列(seq2seq)模型结构,通常用于机器翻译和其他文本生成任务。

解码器的主要工作是在每个时间步生成一个单词,同时维护自己的隐藏状态和细胞状态。通常使用RNN(循环神经网络)或GRU(Gated Recurrent Unit)来实现解码器。

(17)定义解码器类Decoder,用于生成目标语言的句子。具体实现代码如下所示。

class Decoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
        super(Decoder, self).__init__()
        self.batch_sz = batch_sz
        self.dec_units = dec_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.gru = gru(self.dec_units)
        self.fc = tf.keras.layers.Dense(vocab_size)
        
        # 用于注意力机制
        self.W1 = tf.keras.layers.Dense(self.dec_units)
        self.W2 = tf.keras.layers.Dense(self.dec_units)
        self.V = tf.keras.layers.Dense(1)
        
    def call(self, x, hidden, enc_output):

        hidden_with_time_axis = tf.expand_dims(hidden, 1)
        
        # 得分的形状 == (批次大小, 最大长度, 1)
        # 我们在最后一个轴上得到 1,因为我们将 tanh(FC(EO) + FC(H)) 应用于 self.V
        score = self.V(tf.nn.tanh(self.W1(enc_output) + self.W2(hidden_with_time_axis)))
        
        # 注意力权重的形状 == (批次大小, 最大长度, 1)
        attention_weights = tf.nn.softmax(score, axis=1)
        
        # 上下文向量的形状在求和后 == (批次大小, 隐藏大小)
        context_vector = attention_weights * enc_output
        context_vector = tf.reduce_sum(context_vector, axis=1)
        
        # 通过嵌入层后 x 的形状 == (批次大小, 1, 嵌入维度)
        x = self.embedding(x)
        
        # 在连接后 x 的形状 == (批次大小, 1, 嵌入维度 + 隐藏大小)
        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)
        
        # 将连接后的向量传递给 GRU
        output, state = self.gru(x)
        
        # 输出的形状 == (批次大小 * 1, 隐藏大小)
        output = tf.reshape(output, (-1, output.shape[2]))
        
        # 输出的形状 == (批次大小 * 1, 词汇表大小)
        x = self.fc(output)
        
        return x, state, attention_weights
        
    def initialize_hidden_state(self):
        return tf.zeros((self.batch_sz, self.dec_units))

上述解码器类的主要功能是在每个时间步生成目标语言的单词,同时维护隐藏状态、注意力权重和上下文向量。这是一个典型的序列到序列模型的解码器部分。

(18)分别创建编码器(Encoder)和解码器(Decoder)的实例,将它们初始化为相应的类,并传递了一些参数。

encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)
decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)

这两个实例将被用于构建机器翻译模型,其中编码器用于将英语句子编码成隐藏状态,而解码器用于生成目标语言的句子。接下来,你可以通过训练这个模型来实现机器翻译任务。

(19)定义优化器(optimizer)和损失函数(loss function),用于训练机器翻译模型。

optimizer = tf.optimizers.Adam()

def loss_function(real, pred):
    mask = 1 - np.equal(real, 0)
    loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=real, logits=pred) * mask
    return tf.reduce_mean(loss_)

上述损失函数loss_function(real, pred)通常用于训练序列到序列模型,其中模型生成序列数据(如机器翻译),并需要优化以最小化生成序列与目标序列之间的差异。Adam优化器将使用此损失函数来更新模型的权重以最小化损失,从而使模型更好地匹配目标数据。

(20)创建检查点(checkpoint),用于在训练过程中保存模型的权重和优化器的状态,以便稍后恢复模型的训练或使用。

checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(optimizer=optimizer,
                                 encoder=encoder,
                                 decoder=decoder)

在训练过程中,可以使用这个检查点对象来定期保存模型的状态,以便稍后恢复或部署模型。这对于长时间的模型训练非常有用,因为你可以随时保存模型状态,以防止训练中断或出现问题。

(21)执行模型的训练循环,训练机器翻译模型,具体实现代码如下所示。

EPOCHS = 10

for epoch in range(EPOCHS):
    start = time.time()
    
    hidden = encoder.initialize_hidden_state()
    total_loss = 0
    
    for (batch, (inp, targ)) in enumerate(dataset):
        loss = 0
        
        with tf.GradientTape() as tape:
            enc_output, enc_hidden = encoder(inp, hidden)
            
            dec_hidden = enc_hidden
            
            dec_input = tf.expand_dims([targ_lang.word2idx['<start>']] * BATCH_SIZE, 1)       
            
            # Teacher forcing - feeding the target as the next input
            for t in range(1, targ.shape[1]):
                # passing enc_output to the decoder
                predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)
                
                loss += loss_function(targ[:, t], predictions)
                
                # using teacher forcing
                dec_input = tf.expand_dims(targ[:, t], 1)
        
        batch_loss = (loss / int(targ.shape[1]))
        
        total_loss += batch_loss
        
        variables = encoder.variables + decoder.variables
        
        gradients = tape.gradient(loss, variables)
        
        optimizer.apply_gradients(zip(gradients, variables))
        
        if batch % 100 == 0:
            print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,
                                                         batch,
                                                         batch_loss.numpy()))
    # saving (checkpoint) the model every epoch
    checkpoint.save(file_prefix = checkpoint_prefix)
    
    print('Epoch {} Loss {:.4f}'.format(epoch + 1,
                                        total_loss / N_BATCH))
    print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

上述循环重复执行10轮,每一轮都使用数据集的批次进行前向传播、反向传播和参数更新,以训练模型。这是一个标准的训练循环,用于训练序列到序列模型。执行后会输出:

Epoch 1 Batch 0 Loss 1.9447
Epoch 1 Batch 100 Loss 1.2724
Epoch 1 Batch 200 Loss 1.1861
Epoch 1 Batch 300 Loss 1.0276
Epoch 1 Batch 400 Loss 0.9159
Epoch 1 Batch 500 Loss 0.8936
Epoch 1 Loss 0.7260
Time taken for 1 epoch 1474.7117013931274 sec
//省略部分输出结果
Epoch 10 Batch 2100 Loss 0.1063
Epoch 10 Batch 2200 Loss 0.1099
Epoch 10 Batch 2300 Loss 0.1381
Epoch 10 Loss 0.0840
Time taken for 1 epoch 1455.937628030777 sec

(22)从指定的检查点目录中恢复模型的状态,这个步骤对于在训练中断或需要保存/加载模型状态时非常有用,因为它允许你保持训练进度,而无需重新训练整个模型。

checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))

执行后会输出:

<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f6798ba34d0>

(23)定义用于评估(推理)机器翻译模型的函数 evaluate,它接受一些输入并返回翻译结果、输入句子和注意力权重。这个函数的主要作用是将输入序列通过编码器和解码器进行翻译,同时捕捉注意力权重以便后续可视化。函数返回翻译结果、输入句子和注意力权重,可以用于对模型的性能进行评估。

def evaluate(inputs, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ):
    
    attention_plot = np.zeros((max_length_targ, max_length_inp))
    sentence = ''
    for i in inputs[0]:
        if i == 0:
            break
        sentence = sentence + inp_lang.idx2word[i] + ' '
    sentence = sentence[:-1]
    
    inputs = tf.convert_to_tensor(inputs)
    
    result = ''

    hidden = [tf.zeros((1, units))]
    enc_out, enc_hidden = encoder(inputs, hidden)

    dec_hidden = enc_hidden
    dec_input = tf.expand_dims([targ_lang.word2idx['<start>']], 0)

    for t in range(max_length_targ):
        predictions, dec_hidden, attention_weights = decoder(dec_input, dec_hidden, enc_out)
        
        # storing the attention weights to plot later on
        attention_weights = tf.reshape(attention_weights, (-1, ))
        attention_plot[t] = attention_weights.numpy()

        predicted_id = tf.argmax(predictions[0]).numpy()

        result += targ_lang.idx2word[predicted_id] + ' '

        if targ_lang.idx2word[predicted_id] == '<end>':
            return result, sentence, attention_plot
        
        # the predicted ID is fed back into the model
        dec_input = tf.expand_dims([predicted_id], 0)

    return result, sentence, attention_plot

(24)定义函数 predict_random_val_sentence,用于从验证集中随机选择一个样本,进行模型的预测,并可视化注意力权重。

def predict_random_val_sentence():
    actual_sent = ''
    k = np.random.randint(len(input_tensor_val))
    random_input = input_tensor_val[k]
    random_output = target_tensor_val[k]
    random_input = np.expand_dims(random_input,0)
    result, sentence, attention_plot = evaluate(random_input, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
    print('Input: {}'.format(sentence[8:-6]))
    print('Predicted translation: {}'.format(result[:-6]))
    for i in random_output:
        if i == 0:
            break
        actual_sent = actual_sent + targ_lang.idx2word[i] + ' '
    actual_sent = actual_sent[8:-7]
    print('Actual translation: {}'.format(actual_sent))
    attention_plot = attention_plot[:len(result.split(' '))-2, 1:len(sentence.split(' '))-1]
    sentence, result = sentence.split(' '), result.split(' ')
    sentence = sentence[1:-1]
    result = result[:-2]

    # use plotly to generate the heat map
    trace = go.Heatmap(z = attention_plot, x = sentence, y = result, colorscale='greens')
    data=[trace]
    iplot(data)

上述函数的主要作用是随机选择一个验证集样本,对其进行翻译,并可视化注意力权重,以帮助理解模型在特定样本上的表现。这有助于评估模型的质量和了解模型的翻译行为。

(25)函数predict_random_val_sentence()用于随机选择一个验证集样本,进行模型的预测,并可视化注意力权重。它将打印输入句子、模型的翻译结果和实际目标句子,并显示一个注意力热图,以帮助理解模型在随机选择的样本上的表现。

predict_random_val_sentence()

执行函数predict_random_val_sentence()后会绘制一个注意力热图(heat map),这个热图显示了模型在翻译时对输入句子中每个单词的注意力权重分布,如图7-1所示。

图7-1  注意力热图

具体来说,热图的 x 轴表示输入句子的单词,y 轴表示模型生成的输出句子的单词。在热图中,每个单元格的颜色表示模型在生成输出时对相应输入单词的注意力程度,颜色越深表示注意力越集中。通过这个热图,可以看到模型在翻译过程中对输入的哪些部分进行了更多的关注,以帮助生成正确的输出。这种可视化有助于理解模型的翻译行为和注意力分布。

执行后还会输出翻译结果:

Input: tom spoke with me about you
Predicted translation: Tom falou comigo sobre você
Actual translation: Tom me falou de você

(26)再次执行函数predict_random_val_sentence(),它会选择一个随机的验证集样本,进行模型的预测,并生成注意力热图。

predict_random_val_sentence()

执行函数predict_random_val_sentence()后会绘制注意力热图,如图7-2所示。这个可视化热力图可以帮助我们了解模型的翻译效果以及模型在不同单词上的注意力分布。每次运行 predict_random_val_sentence() 都会选择不同的验证集样本,因此你可以多次运行以查看不同的结果。这有助于评估模型的性能和了解其翻译行为。

图7-2  注意力热图

执行后还会输出翻译结果:

Input: the pain was more than tom could stand

Predicted translation: A dor estava mais alto do que Tom podia ficar

Actual translation: A dor era mais do que Tom podia suportar

  • 32
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值