BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构

本文主要是介绍BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


Encoder编码器-Decoder解码器框架 + Attention注意力机制

Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part1

Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part2

Pytorch:解码器端的Attention注意力机制、seq2seq模型架构实现英译法任务

BahdanauAttention注意力机制、LuongAttention注意力机制

BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构

图片的描述生成任务、使用迁移学习实现图片的描述生成过程、CNN编码器+RNN解码器(GRU)的模型架构、BahdanauAttention注意力机制、解码器端的Attention注意力机制

注意力机制、bmm运算

注意力机制 SENet、CBAM

机器翻译 MXNet(使用含注意力机制的编码器—解码器,即 Encoder编码器-Decoder解码器框架 + Attention注意力机制)

基于Seq2Seq的中文聊天机器人编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制)

基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)

注意:这一文章“基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)”该文章实现的Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。

基于seq2seq的西班牙语到英语的机器翻译任务

学习目标

  • 了解机器翻译任务及其相关数据集.
  • 掌握使用基于GRU的seq2seq模型架构实现翻译的过程.
  • 掌握Attention机制在解码器端的实现过程.

任务说明

  • 机器翻译任务是NLP领域最为经典且应用最广泛的任务之一,仅google的在线翻译系统每天请求量就已经过亿。当前最好的机器翻译系统正是使用深度学习技术解决各项难题,同时也将经典的seq2seq架构推广到各个领域,再加上近今年来风靡的Attention机制,使得机器翻译能力迅猛提升。下面我们将学习基于seq2seq模型架构和Attention 机制,完成西班牙语到英语的机器翻译案例。

数据集说明

  • 数据集名称: Anki (安基翻译数据集)
  • 原数据集下载地址: http://www.manythings.org/anki/spa-eng.zip
  • 数据集下载地址: http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip
  • 数据集预览:
Go. Ve.
Go. Vete.
Go. Vaya.
Go. Váyase.
Hi. Hola.
Run!    ¡Corre!
Run.    Corred.
Who?    ¿Quién?
Fire!   ¡Fuego!
Fire!   ¡Incendio!
Fire!   ¡Disparad!
  • 数据内容说明:
    • 数据总条目为: 118964,第一列为英语,第二列为对应翻译的西班牙语.

使用基于GRU的seq2seq模型架构实现翻译的过程

  • 第一步:下载和准备训练数据集
  • 第二步:对数据进行预处理并创建一个tf.data数据集
  • 第三步:构建模型并选择优化器和损失函数
  • 第四步:构建训练函数并进行训练
  • 第五步:构建评估函数并进行预测分析

第一步:下载和准备训练数据集

from __future__ import absolute_import, division, print_function, unicode_literalsimport tensorflow as tf
# 打印tensorflow版本
print("Tensorflow Version:", tf.__version__)# 导入之后绘制Attention效果图的工具包
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker# 导入sklearn中的相关工具以便进行训练集与验证集划分
from sklearn.model_selection import train_test_split# 导入一些必备的文本清洗工具包
import unicodedata
import reimport numpy as np
import os
import io
import time# 使用tf.keras工具中get_file方法下载文件
# 'spa-eng.zip'是下载的文件名
# origin表示文件下载地址, extract表示是否对文件进行解压缩
# 进行解压缩后, 获得压缩包的地址path_to_zip
path_to_zip = tf.keras.utils.get_file('spa-eng.zip', origin='http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip',extract=True)# 获得解压缩的标注文件地址 
path_to_file = os.path.dirname(path_to_zip)+"/spa-eng/spa.txt"
  • 输出效果:
Tensorflow Version: 2.1.0-rc2
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip
2646016/2638744 [==============================] - 0s 0us/step

第二步:对数据进行预处理并创建一个tf.data数据集

  • 对文本进行清洗并给每个句子添加一个 开始 和一个 结束 标记:
# 将 unicode 文件转换为 ascii
# 我们可以认为是去掉一些语言中的重音标记:如Ślusàrski--->Slusarski
def unicode_to_ascii(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn')def preprocess_sentence(w):"""句子处理函数, 输入为原始语料的一句话"""# 字母小写并去除两侧空白符,调用unicode_to_asciiw = unicode_to_ascii(w.lower().strip())# 在单词与跟在其后的标点符号之间插入一个空格# 例如: "he is a boy." => "he is a boy ."w = re.sub(r"([?.!,¿])", r" \1 ", w)w = re.sub(r'[" "]+', " ", w)# 除了 (a-z, A-Z, ".", "?", "!", ","),将所有字符替换为空格w = re.sub(r"[^a-zA-Z?.!,¿]+", " ", w)# 替换后再次去除两侧空白符w = w.rstrip().strip()# 给句子加上开始和结束标记# 以便模型知道何时开始和结束预测w = '<start> ' + w + ' <end>'return w
  • 调用:
en_sentence = u"May I borrow this book?"
sp_sentence = u"¿Puedo tomar prestado este libro?"
print(preprocess_sentence(en_sentence))
print(preprocess_sentence(sp_sentence).encode('utf-8'))
  • 输出效果:
<start> may i borrow this book ? <end>
b'<start> \xc2\xbf puedo tomar prestado este libro ? <end>'
  • 创建对应的翻译文本集
def create_dataset(path, num_examples):"""description: 创建对应的翻译文本集:共包含两个元组,第一个元组中都是英文,第二个元组中都是对应的西班牙文:param path: 数据集路径:param num_examples: 取数据集中的文本行数"""# 读取持久化文件中的全部行lines = io.open(path, encoding='UTF-8').read().strip().split('\n')# 取指定行数的数据并调用preprocess_sentence函数处理pairs = [[preprocess_sentence(w) for w in l.split('\t')]  for l in lines[:num_examples]]# 将两组文本整合在一个zip对象中return zip(*pairs)
  • 调用:
path = path_to_file
num_examples = 5
en, sp = create_dataset(path, num_examples)
print(en)
print(sp)
  • 输出效果:
# 第一个元组都是英文句子(因为我们取的是数据集前面的句子,一个单词就是一个句子)
('<start> go . <end>', '<start> go . <end>', '<start> go . <end>', '<start> go . <end>', '<start> hi . <end>')# 第二个元组都是对应西班牙文句子
('<start> ve . <end>', '<start> vete . <end>', '<start> vaya . <end>', '<start> vayase . <end>', '<start> hola . <end>')
  • 对文本进行数值映射并进行最大长度补齐
def max_length(tensor):"""获取tensor中的最大长度函数"""return max(len(t) for t in tensor)def tokenize(lang):"""对文本进行数值映射函数"""# 实例化一个Tokenizer数值映射器lang_tokenizer = tf.keras.preprocessing.text.Tokenizer(filters='')# 在输入文本上拟合映射器lang_tokenizer.fit_on_texts(lang)# 将映射器作用在当前文本上tensor = lang_tokenizer.texts_to_sequences(lang)# 使用pad_sequences对文本进行最大长度补齐tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor,padding='post')# 返回处理后的结果和映射器(因为在之后的预测中还会使用该映射器处理文本)return tensor, lang_tokenizerdef load_dataset(path, num_examples=None):"""对两种语言文本进行进行数值映射并进行最大长度补齐"""# 获得文本清洗之后的结果    targ_lang, inp_lang = create_dataset(path, num_examples)# 分别调用tokenize函数input_tensor, inp_lang_tokenizer = tokenize(inp_lang)target_tensor, targ_lang_tokenizer = tokenize(targ_lang)# 返回对应的结果return input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer
  • 调用:
path = path_to_file
num_examples = 5
input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer = load_dataset(path, num_examples) 
print("input_tensor:", input_tensor)
print("target_tensor:", target_tensor)
print(inp_lang_tokenizer)
print(targ_lang_tokenizer)
  • 输出效果:
input_tensor: [[1 4 2 3][1 5 2 3][1 6 2 3][1 7 2 3][1 8 2 3]]
target_tensor: [[1 4 2 3][1 4 2 3][1 4 2 3][1 4 2 3][1 5 2 3]]
<keras_preprocessing.text.Tokenizer object at 0x7f7c305f3b50>
<keras_preprocessing.text.Tokenizer object at 0x7f7cbe903090>
  • 限制训练集的大小以保证在可控时间内完成训练
    • 为了加快训练速度,将使用30,000个训练子集来训练模型。如果你的硬件资源足够充分,也可以选择使用更多数据来提高模型质量。如果在一个P100 GPU 上运行10万条数据大约花费10分钟左右。
# 尝试实验不同大小的数据集
# 这里使用30000个样本
num_examples = 30000input_tensor, target_tensor, inp_lang, targ_lang = load_dataset(path_to_file, num_examples)# 计算目标张量的最大长度 (max_length)
max_length_targ, max_length_inp = max_length(target_tensor), max_length(input_tensor)# 采用 8:2 的比例切分训练集和验证集
input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)# 训练集和验证集的样本数量
print(len(input_tensor_train), len(target_tensor_train), len(input_tensor_val), len(target_tensor_val))
  • 输出效果:
24000 24000 6000 6000
  • 查看一下数值映射后样本的对应情况:
def convert(lang, tensor):# 遍历张量中的每一个数值for t in tensor:# 数值映射从1开始,不包括0 if t!=0:# 使用传入的数值映射器的index_word方法寻找数值对应的单词print ("%d ----> %s" % (t, lang.index_word[t]))print ("输入文本的对应情况:")
convert(inp_lang, input_tensor_train[0])
print ()
print ("输出文本的对应情况:")
convert(targ_lang, target_tensor_train[0])
  • 输出效果:
Input Language; index to word mapping
1 ----> <start>
2605 ----> mande
19 ----> mi
280 ----> reloj
10 ----> a
2342 ----> arreglar
3 ----> .
2 ----> <end>Target Language; index to word mapping
1 ----> <start>
4 ----> i
99 ----> had
21 ----> my
177 ----> watch
1002 ----> fixed
3 ----> .
2 ----> <end>
  • 创建一个tf.data数据集对象
# 为了方便之后的训练,都需要将数据集转化成tf.data数据集对象,这已经成为了使用tf.keras进行模型训练前的标准步骤!# 设置超参数
BUFFER_SIZE = len(input_tensor_train)
BATCH_SIZE = 64
steps_per_epoch = len(input_tensor_train)//BATCH_SIZE
embedding_dim = 256
units = 1024
vocab_inp_size = len(inp_lang.word_index)+1
vocab_tar_size = len(targ_lang.word_index)+1# 转化成tf.data的dataset形式
dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
# 进行批次化,drop_remainder=True代表舍弃最后一个批次可能不满足batch_size大小的数据
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)# 取出一组样例数据查看一下,现将dataset封装成迭代器,再用next方法取出一个
example_input_batch, example_target_batch = next(iter(dataset))# 打印结果
print(example_input_batch.shape, example_target_batch.shape)
  • 输出效果:
TensorShape([64, 16]), TensorShape([64, 11])

第三步:构建模型并选择优化器和损失函数¶

  • 构建模型的编码器部分:
class Encoder(tf.keras.Model):def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):""":param vocab_size: 非重复的词汇总数:param embedding_dim: 词嵌入的维度:enc_units: 编码器中GRU层的隐含节点数:batch_sz: 数据批次大小(每次参数更新用到的数据量)"""super(Encoder, self).__init__()# 将变量传入类中self.batch_sz = batch_szself.enc_units = enc_units# 实例化embedding层self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)# 实例化gru层# return_sequences=True代表返回GRU序列模型的每个时间步的输出(每个输出做连接操作)# return_state=True代表除了返回输出外,还需要返回最后一个隐层状态# recurrent_initializer='glorot_uniform'即循环状态张量的初始化方式为均匀分布self.gru = tf.keras.layers.GRU(self.enc_units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')def call(self, x, hidden):# 对输入进行embedding操作x = self.embedding(x)# 通过gru层获得最后一个时间步的输出和隐含状态output, state = self.gru(x, initial_state = hidden)return output, statedef initialize_hidden_state(self):# gru层的隐含节点对应的参数张量以零张量初始化return tf.zeros((self.batch_sz, self.enc_units))
  • 调用:
# 实例化encoder
encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)# 样本输入
sample_hidden = encoder.initialize_hidden_state()
sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
print ('Encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))
print ('Encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))
  • 输出效果:
Encoder output shape: (batch size, sequence length, units) (64, 16, 1024)
Encoder Hidden state shape: (batch size, units) (64, 1024)
  • 构建注意力机制的类:
    • 注意力机制的计算规则遵循以下公式:

  • 构建注意力机制类的伪代码:
# 这里使用Bahdanau 注意力机制1, score = FC(tanh(FC(EO) + FC(H)))
2, attention weights = softmax(score, axis = 1).
# 解释: Softmax 默认被应用于最后一个轴,但是这里我们想将它应用于第一个轴,
# 因为分数 (score) 的形状是 (批大小,最大长度,隐层大小),最大长度 (max_length) 是输入的长度。
# 因为我们想为每个输入长度分配一个权重,所以softmax应该用在这个轴上。
3, context vector = sum(attention weights * EO, axis = 1)
# 解释: 选择第一个轴的原因同上.
4, embedding output = 解码器输入 X 通过一个嵌入层
5, merged vector = concat(embedding output, context vector)符号代表:
FC: 全连接层
EO: 编码器输出
H: 隐藏层状态
X: 解码器输入
  • 构建注意力机制类:
class BahdanauAttention(tf.keras.Model):def __init__(self, units):"""初始化三个必要的全连接层"""super(BahdanauAttention, self).__init__()self.W1 = tf.keras.layers.Dense(units)self.W2 = tf.keras.layers.Dense(units)self.V = tf.keras.layers.Dense(1)def call(self, features, hidden):"""description: 具体计算函数:param features: 编码器的输出:param hidden: 解码器的隐层输出状态return: 通过注意力机制处理后的结果和注意力权重attention_weights"""# 为hidden扩展一个维度(batch_size, hidden_size) --> (batch_size, 1, hidden_size)hidden_with_time_axis = tf.expand_dims(hidden, 1)# 根据公式计算注意力得分, 输出score的形状为: (batch_size, 64, hidden_size)score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))# 根据公式计算注意力权重, 输出attention_weights形状为: (batch_size, 64, 1)attention_weights = tf.nn.softmax(self.V(score), axis=1)# 最后根据公式获得注意力机制处理后的结果context_vector# context_vector的形状为: (batch_size, hidden_size)context_vector = attention_weights * featurescontext_vector = tf.reduce_sum(context_vector, axis=1)return context_vector, attention_weights
  • 调用:
attention_layer = BahdanauAttention(10)
attention_result, attention_weights = attention_layer(sample_hidden, sample_output)print("Attention result shape: (batch size, units) {}".format(attention_result.shape))
print("Attention weights shape: (batch_size, sequence_length, 1) {}".format(attention_weights.shape))
  • 输出效果:
Attention result shape: (batch size, units) (64, 1024)
Attention weights shape: (batch_size, sequence_length, 1) (64, 16, 1)
  • 构建RNN解码器:
    • 这里RNN是指GRU, 同时在解码器中使用注意力机制.
class Decoder(tf.keras.Model):def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):super(Decoder, self).__init__()# 所有的参数传递和实例化方法与编码器相同self.batch_sz = batch_szself.dec_units = dec_unitsself.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)self.gru = tf.keras.layers.GRU(self.dec_units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')# 实例化一个Dense层作为输出层self.fc = tf.keras.layers.Dense(vocab_size)# 在解码器阶段我们将使用注意力机制,这里实例化注意力的类self.attention = BahdanauAttention(self.dec_units)def call(self, x, hidden, enc_output):""":param x: 每个时间步上解码器的输入:param hidden: 每次解码器的隐层输出:param enc_output: 编码器的输出"""# 输入通过embedding层 x = self.embedding(x)# 使用注意力规则计算hidden与enc_output的'相互影响程度'context_vector, attention_weights = self.attention(enc_output, hidden)# 将这种'影响程度'与输入x拼接(这个操作也是注意力计算规则的一部分)x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)# 将新的x输入到gru层中得到输出output, state = self.gru(x)# 改变输出形状使其适应全连接层的输入形式output = tf.reshape(output, (-1, output.shape[2]))# 使用全连接层作为输出层# 输出的形状 == (批大小,vocab)x = self.fc(output)return x, state, attention_weights
  • 调用:
decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)sample_decoder_output, _, _ = decoder(tf.random.uniform((64, 1)),sample_output, sample_hidden)print ('Decoder output shape: (batch_size, vocab size) {}'.format(sample_decoder_output.shape))
  • 输出效果:
Decoder output shape: (batch_size, vocab size) (64, 4935)
  • 选取优化方法和损失函数:
# 选取Adam优化方法
optimizer = tf.keras.optimizers.Adam()# 损失基本计算方法为稀疏类别交叉熵损失
# from_logits=True代表是否将预测结果预期为非 0/1 的值进行保留
# 理论来讲二分类最终的结果应该只有0/1,函数将自动将其变为0/1,from_logits=True后,值不会被改变
# reduction='none',接下来我们将自定义损失函数,reduction必须设置为None,
# 我们可以将它看作是自定义损失函数的识别属性
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')# 因为每次生成的结果都是局部结果,要和真实结果进行比较需要对真实结果进行遮掩
# 等效于对损失计算结果进行掩码
def loss_function(real, pred):"""自定义损失函数,参数为预测结果pred和真实结果real"""# 使用tf.math.equal方法对real和0进行对比# 对结果再进行逻辑非操作生成掩码张量maskmask = tf.math.logical_not(tf.math.equal(real, 0))# 使用基本计算方法计算损失loss_ = loss_object(real, pred)# 将mask进行类型转换,使其能够进行后续操作mask = tf.cast(mask, dtype=loss_.dtype)# 将loss_与mask相乘即对loss_进行掩码loss_ *= mask# 计算loss_张量所有元素的均值return tf.reduce_mean(loss_)
  • 创建检测点保存对象:
# 定义检测点(每个阶段训练的模型)保存路径
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
# 使用tf.train.Checkpoint创建检测点保存对象
# 我们会在之后的训练中调用它来保存模型
checkpoint = tf.train.Checkpoint(optimizer=optimizer,encoder=encoder,decoder=decoder)

第四步: 构建训练函数并进行训练

  • 构建训练函数:
@tf.function # 该装饰器使该函数自动编译张量图, 使其可以直接执行 
def train_step(inp, targ, enc_hidden):# 设定初始损失为0loss = 0# 开启一个用于梯度记录的上下文管理器with tf.GradientTape() as tape:# 调用编码器部分得到编码器输出和编码器隐层输出enc_output, enc_hidden = encoder(inp, enc_hidden)# 将编码器隐层输出设定为解码器的初始隐层状态dec_hidden = enc_hidden# 以'起始符'作为解码器的第一个输入字符dec_input = tf.expand_dims([targ_lang.word_index['<start>']] * BATCH_SIZE, 1)# 开始循环解码过程for t in range(1, targ.shape[1]):# 使用解码器获得新的解码器隐层输出(状态),以及预测结果predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)# 使用损失函数计算本次训练过程的损失loss += loss_function(targ[:, t], predictions)# 使用teacher-forcing矫正可能的错误结果# 直接将正确结果作为下一次循环的输入dec_input = tf.expand_dims(targ[:, t], 1)# 计算每次batch的平均损失batch_loss = (loss / int(targ.shape[1]))# 获得整个模型训练的参数变量variables = encoder.trainable_variables + decoder.trainable_variables# 使用梯度记录管理器求解整个网络的梯度,参数是loss和全部参数变量gradients = tape.gradient(loss, variables)# 根据梯度更新参数optimizer.apply_gradients(zip(gradients, variables))# 返回每次batch的平均损失return batch_loss
  • 什么是teacher_forcing?

    • 它是一种用于序列生成任务的训练技巧, 在seq2seq架构中, 根据循环神经网络理论,解码器每次应该使用上一步的结果作为输入的一部分, 但是训练过程中,一旦上一步的结果是错误的,就会导致这种错误被累积,无法达到训练效果, 因此,我们需要一种机制改变上一步出错的情况,因为训练时我们是已知正确的输出应该是什么,因此可以强制将上一步结果设置成正确的输出, 这种方式就叫做teacher_forcing.
  • teacher_forcing的作用:

    • 能够在训练的时候矫正模型的预测,避免在序列生成的过程中误差进一步放大.
    • teacher_forcing能够极大的加快模型的收敛速度,令模型训练过程更快更平稳.
  • 进行训练并打印日志:
# 设置训练轮数
EPOCHS = 10
for epoch in range(EPOCHS):# 获得每轮训练的开始时间start = time.time()# 初始化编码器隐含状态 enc_hidden = encoder.initialize_hidden_state()# 初始化总损失为0total_loss = 0# 循环数据集中的每个批次进行训练for (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)):# 调用train_step函数获得批次平均损失batch_loss = train_step(inp, targ, enc_hidden)# 将批次平均损失相加获得轮数总损失total_loss += batch_loss# 每100个批次打印一次批次平均损失if batch % 100 == 0:print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,batch,batch_loss.numpy()))# 每两轮(epoch),保存一次模型if (epoch + 1) % 2 == 0:checkpoint.save(file_prefix = checkpoint_prefix)# 打印轮数平均损失print('Epoch {} Loss {:.4f}'.format(epoch + 1,total_loss / steps_per_epoch))# 打印模型训练耗时print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))
  • 输出效果:
Epoch 1 Batch 0 Loss 4.6268
Epoch 1 Batch 100 Loss 2.1385
Epoch 1 Batch 200 Loss 1.8945
Epoch 1 Batch 300 Loss 1.7187
Epoch 1 Loss 2.0145
Time taken for 1 epoch 34.930274963378906 secEpoch 2 Batch 0 Loss 1.5522
Epoch 2 Batch 100 Loss 1.3454
Epoch 2 Batch 200 Loss 1.3791
Epoch 2 Batch 300 Loss 1.3919
Epoch 2 Loss 1.3569
Time taken for 1 epoch 17.682456016540527 secEpoch 3 Batch 0 Loss 1.0861
Epoch 3 Batch 100 Loss 1.0815
Epoch 3 Batch 200 Loss 0.9158
Epoch 3 Batch 300 Loss 0.8474
Epoch 3 Loss 0.9312
Time taken for 1 epoch 17.20573592185974 secEpoch 4 Batch 0 Loss 0.6689
Epoch 4 Batch 100 Loss 0.5476
Epoch 4 Batch 200 Loss 0.5617
Epoch 4 Batch 300 Loss 0.5462
Epoch 4 Loss 0.6193
Time taken for 1 epoch 17.651796579360962 secEpoch 5 Batch 0 Loss 0.3442
Epoch 5 Batch 100 Loss 0.3203
Epoch 5 Batch 200 Loss 0.4267
Epoch 5 Batch 300 Loss 0.4384
Epoch 5 Loss 0.4171
Time taken for 1 epoch 17.20345664024353 secEpoch 6 Batch 0 Loss 0.2950
Epoch 6 Batch 100 Loss 0.3294
Epoch 6 Batch 200 Loss 0.3228
Epoch 6 Batch 300 Loss 0.2724
Epoch 6 Loss 0.2861
Time taken for 1 epoch 17.587135076522827 secEpoch 7 Batch 0 Loss 0.1584
Epoch 7 Batch 100 Loss 0.1876
Epoch 7 Batch 200 Loss 0.1844
Epoch 7 Batch 300 Loss 0.1919
Epoch 7 Loss 0.2025
Time taken for 1 epoch 17.285163402557373 secEpoch 8 Batch 0 Loss 0.1315
Epoch 8 Batch 100 Loss 0.1640
Epoch 8 Batch 200 Loss 0.1424
Epoch 8 Batch 300 Loss 0.1343
Epoch 8 Loss 0.1482
Time taken for 1 epoch 17.918259382247925 secEpoch 9 Batch 0 Loss 0.1118
Epoch 9 Batch 100 Loss 0.1030
Epoch 9 Batch 200 Loss 0.0644
Epoch 9 Batch 300 Loss 0.1106
Epoch 9 Loss 0.1172
Time taken for 1 epoch 17.48120665550232 secEpoch 10 Batch 0 Loss 0.0853
Epoch 10 Batch 100 Loss 0.0803
Epoch 10 Batch 200 Loss 0.0681
Epoch 10 Batch 300 Loss 0.0957
Epoch 10 Loss 0.0951
Time taken for 1 epoch 17.945307970046997 sec

第五步:构建评估函数并进行预测分析

  • 构建评估函数:
def evaluate(sentence):"""description: 评估函数:param sentence: 待翻译的句子"""# 初始化用于绘制注意力效果图的张量attention_plot = np.zeros((max_length_targ, max_length_inp))# 下面将对输入文本做与训练语料同样的操作# 对输入的句子进行文本预处理sentence = preprocess_sentence(sentence)# 使用输入数值映射器对文本进行数值化映射inputs = [inp_lang.word_index[i] for i in sentence.split(' ')]# 对文本进行最大长度补齐inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs],maxlen=max_length_inp,padding='post')# 转换成张量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.word_index['<start>']], 0)# 开始循环解码,过程和训练时十分类似for t in range(max_length_targ):predictions, dec_hidden, attention_weights = decoder(dec_input,dec_hidden,enc_out)# 存储每次产生的注意力权重以便后面制图attention_weights = tf.reshape(attention_weights, (-1, ))attention_plot[t] = attention_weights.numpy()# 从预测分布中获得概率最大预测idpredicted_id = tf.argmax(predictions[0]).numpy()# 使用目标数值映射器将其还原为对应的文本(单词/标识)result += targ_lang.index_word[predicted_id] + ' '# 如果解码还原后为'终止符',则返回结果if targ_lang.index_word[predicted_id] == '<end>':return result, sentence, attention_plot# 否则,预测id被输送回模型,作为下一次预测输入dec_input = tf.expand_dims([predicted_id], 0)# 返回预测结果,原始输入文本,以及整个过程的注意力张量组合(仍然是一个张量)return result, sentence, attention_plotdef plot_attention(attention, sentence, predicted_sentence):"""description: 注意力张量制图函数:param attention: 整个过程的注意力张量组合:param sentence: 原始输入文本:param predicted_sentence: 预测结果"""# 打开一个10x10的画布fig = plt.figure(figsize=(10,10))# 在画布上创建1x1的子画布ax = fig.add_subplot(1, 1, 1)# 绘制子画布上绘制矩形,根据给出输入数值不同,颜色也不相同# cmap='viridis'是一种矩形的色彩填充方案‘绿藻’ax.matshow(attention, cmap='viridis')# 定义字体规范字典,这里我们只要求字体大小即可fontdict = {'fontsize': 14}# 使用x轴设置方法使输入文本在x轴上显示# rotation=90代表有90度的倾斜角,便于观看ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)# 同样,使预测文本在y轴上显示ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)# 设置x和y轴的刻度,与绘制时add_subplot的参数对应即可ax.xaxis.set_major_locator(ticker.MultipleLocator(1))ax.yaxis.set_major_locator(ticker.MultipleLocator(1))# 绘图plt.show()
  • 调用:
def translate(sentence):"""预测并绘图"""# 调用评估函数获得结果result, sentence, attention_plot = evaluate(sentence)# 打印输入文本和预测文本print('Input: %s' % (sentence))print('Predicted translation: {}'.format(result))# 根据文本长度对注意力张量进行剪裁(剪裁掉的都是0部分)attention_plot = attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]# 进行注意力效果图绘制plot_attention(attention_plot, sentence.split(' '), result.split(' '))# 使用检测点对象恢复最近一次保存的模型
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))# 翻译西班牙语‘这里很冷。’
translate(u'hace mucho frio aqui.')# 翻译西班牙语‘这就是我的生活。’
translate(u'esta es mi vida.')# 翻译西班牙语‘他们还在家里吗?’
translate(u'¿todavia estan en casa?')# 翻译西班牙语‘尝试找出答案。’
translate(u'trata de averiguarlo.')
  • 输出效果:
Input: <start> hace mucho frio aqui . <end>
Predicted translation: it s very cold here . <end> 

Input: <start> esta es mi vida . <end>
Predicted translation: this is my life . <end> 

Input: <start> ¿ todavia estan en casa ? <end>
Predicted translation: are you still at home ? <end>

Input: <start> trata de averiguarlo . <end>
Predicted translation: try to figure it out . <end> 

  • 注意力效果图分析:
    • 图中x,y轴分别对应输入和输出文本,他们之间的影响使用明暗不同的矩形小方块表示,方块颜色越明亮(如黄色),则代表输入对输出影响的作用越大。例如,对于输出英文单词”cold”, 在x轴方向共有三个较明亮的小方块,它们对应的输入单词分别是”mucho”,”frio”,”aqui”,说明生成单词”cold”由以上三个单词来绝对,与此同时,”frio”所对应的小方块最明亮,说明它对生成”cold”所产生的贡献最大。因此,我们可以根据人类语言知识对比效果图来判定模型的”思路”是否和我们一致。

"""
使用基于GRU的seq2seq模型架构实现翻译的过程第一步:下载和准备训练数据集第二步:对数据进行预处理并创建一个tf.data数据集第三步:构建模型并选择优化器和损失函数第四步:构建训练函数并进行训练第五步:构建评估函数并进行预测分析
"""#---------------------------------第一步:下载和准备训练数据集-----------------------------------#
from __future__ import absolute_import, division, print_function, unicode_literalsimport tensorflow as tf
# 打印tensorflow版本
# print("Tensorflow Version:", tf.__version__)# 导入之后绘制Attention效果图的工具包
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker# 导入sklearn中的相关工具以便进行训练集与验证集划分
from sklearn.model_selection import train_test_split# 导入一些必备的文本清洗工具包
import unicodedata
import reimport numpy as np
import os
import io
import time# 使用tf.keras工具中get_file方法下载文件
# 'spa-eng.zip'是下载的文件名
# origin表示文件下载地址, extract表示是否对文件进行解压缩
# 进行解压缩后, 获得压缩包的地址path_to_zip
# path_to_zip = tf.keras.utils.get_file(
#     'spa-eng.zip', origin='http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip',
#     extract=True)# 获得解压缩的标注文件地址
# path_to_file = os.path.dirname(path_to_zip)+"/spa-eng/spa.txt"
path_to_file = "./spa.txt"#---------------------------------第二步:对数据进行预处理并创建一个tf.data数据集-----------------------------------##对文本进行清洗并给每个句子添加一个“开始标记” 和一个“结束标记”:
# 将 unicode 文件转换为 ascii
# 我们可以认为是去掉一些语言中的重音标记:如Ślusàrski--->Slusarski
def unicode_to_ascii(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn')def preprocess_sentence(w):"""句子处理函数, 输入为原始语料的一句话"""# 字母小写并去除两侧空白符,调用unicode_to_asciiw = unicode_to_ascii(w.lower().strip())# 在单词与跟在其后的标点符号之间插入一个空格# 例如: "he is a boy." => "he is a boy ."w = re.sub(r"([?.!,¿])", r" \1 ", w)w = re.sub(r'[" "]+', " ", w)# 除了 (a-z, A-Z, ".", "?", "!", ","),将所有字符替换为空格w = re.sub(r"[^a-zA-Z?.!,¿]+", " ", w)# 替换后再次去除两侧空白符w = w.rstrip().strip()# 给句子加上开始和结束标记# 以便模型知道何时开始和结束预测w = '<start> ' + w + ' <end>'return w#调用:
# en_sentence = u"May I borrow this book?"
# sp_sentence = u"¿Puedo tomar prestado este libro?"
# print(preprocess_sentence(en_sentence)) #<start> may i borrow this book ? <end>
# print(preprocess_sentence(sp_sentence).encode('utf-8')) #b'<start> \xc2\xbf puedo tomar prestado este libro ? <end>'#创建对应的翻译文本集
def create_dataset(path, num_examples):"""description: 创建对应的翻译文本集:共包含两个元组,第一个元组中都是英文,第二个元组中都是对应的西班牙文:param path: 数据集路径:param num_examples: 取数据集中的文本行数"""# 读取持久化文件中的全部行lines = io.open(path, encoding='UTF-8').read().strip().split('\n')# 取指定行数的数据并调用preprocess_sentence函数处理pairs = [[preprocess_sentence(w) for w in l.split('\t')] for l in lines[:num_examples]]#[['<start> go . <end>', '<start> ve . <end>'], ['<start> go . <end>', '<start> vete . <end>'], ......]# print(pairs)#['<start> go . <end>', '<start> ve . <end>'] ['<start> go . <end>', '<start> vete . <end>']  ......# print(*pairs)"""pairs:[['<start> 输入序列 <end>', '<start> 目标序列 <end>'],  ......]*pairs:['<start> 输入序列 <end>', '<start> 目标序列 <end>'] ...... zip(*pairs)作为返回值赋值给en, sp两个变量,即可把每个一维列表中的第一个元素封装为元祖赋值给en,把每个一维列表中的第二个元素封装为元祖赋值给sp"""# 将两组文本整合在一个zip对象中return zip(*pairs)#调用:
# path = path_to_file
# num_examples = 25
# en, sp = create_dataset(path, num_examples)
# print(en)  #('<start> go . <end>', '<start> go . <end>', '<start> go . <end>', '<start> go . <end>', ......)
# print(sp) #('<start> ve . <end>', '<start> vete . <end>', '<start> vaya . <end>', ......)"""
限制训练集的大小以保证在可控时间内完成训练为了加快训练速度,将使用30,000个训练子集来训练模型。如果你的硬件资源足够充分,也可以选择使用更多数据来提高模型质量。如果在一个P100 GPU 上运行10万条数据大约花费10分钟左右。
"""#对文本进行数值映射并进行最大长度补齐
def max_length(tensor):"""获取tensor中的最大长度函数"""return max(len(t) for t in tensor)def tokenize(lang):"""对文本进行数值映射函数"""# 实例化一个Tokenizer数值映射器lang_tokenizer = tf.keras.preprocessing.text.Tokenizer(filters='')# 在输入文本上拟合映射器lang_tokenizer.fit_on_texts(lang)# 将映射器作用在当前文本上tensor = lang_tokenizer.texts_to_sequences(lang)"""tf.keras.preprocessing.sequence.pad_sequences(sequences, maxlen=None, dtype='int32', padding='pre', truncating='pre',value=0.0)此函数将num_samples序列列表(整数列表)转换为2D Numpy形状的数组(num_samples, num_timesteps)。 num_timesteps是maxlen参数(如果提供),或者是最长序列的长度。小于num_timesteps的序列在末尾填充值。长度大于num_timesteps的序列将被截断,以使其适合所需的长度。填充或截断发生的位置分别由参数padding和截断确定。预填充是默认设置。maxlen最大长度:Int,所有序列的最大长度。padding填充:字符串,'pre'前置 或 'post'后置在每个序列之前或之后填充。value=0.0:浮点数或字符串,填充值。"""#maxlen=None:默认使用所有句子中最长句子的长度作为maxlen# 使用pad_sequences对文本进行最大长度补齐tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor, padding='post')# 返回处理后的结果和映射器(因为在之后的预测中还会使用该映射器处理文本)return tensor, lang_tokenizerdef load_dataset(path, num_examples=None):"""对两种语言文本进行进行数值映射并进行最大长度补齐"""# 获得文本清洗之后的结果targ_lang, inp_lang = create_dataset(path, num_examples)# 分别调用tokenize函数input_tensor, inp_lang_tokenizer = tokenize(inp_lang)target_tensor, targ_lang_tokenizer = tokenize(targ_lang)# 返回对应的结果return input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer# path = path_to_file
# num_examples = 5
# input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer = load_dataset(path, num_examples)
# print("input_tensor:", input_tensor)
# print("target_tensor:", target_tensor)
# print(inp_lang_tokenizer)
# print(targ_lang_tokenizer)
# input_tensor:
# [[1 4 2 3]
#  [1 5 2 3]
#  [1 6 2 3]
#  [1 7 2 3]
#  [1 8 2 3]]
# target_tensor:
# [[1 4 2 3]
#  [1 4 2 3]
#  [1 4 2 3]
#  [1 4 2 3]
#  [1 5 2 3]]
# <keras_preprocessing.text.Tokenizer object at 0x0000025DB62A6308>
# <keras_preprocessing.text.Tokenizer object at 0x0000025DB729C7C8># 尝试实验不同大小的数据集
# 这里使用30000个样本
num_examples = 30000
input_tensor, target_tensor, inp_lang, targ_lang = load_dataset(path_to_file, num_examples)
# 计算目标张量的最大长度 (max_length)
#max_length_targ:目标序列所有语句中的最大长度句子的长度(单词数)
#max_length_inp:输入序列所有语句中的最大长度句子的长度(单词数)
max_length_targ, max_length_inp = max_length(target_tensor), max_length(input_tensor)
print(max_length_targ, max_length_inp) #11 16
# 采用 8:2 的比例切分训练集和验证集
input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)
# 训练集和验证集的样本数量
print(len(input_tensor_train), len(target_tensor_train), len(input_tensor_val), len(target_tensor_val)) #24000 24000 6000 6000#查看一下数值映射后样本的对应情况:
def convert(lang, tensor):# 遍历张量中的每一个数值for t in tensor:# 数值映射从1开始,不包括0if t!=0:# 使用传入的数值映射器的index_word方法寻找数值对应的单词print ("%d ----> %s" % (t, lang.index_word[t]))# print ("输入文本的对应情况:")
# convert(inp_lang, input_tensor_train[0])
# print ()
# print ("输出文本的对应情况:")
# convert(targ_lang, target_tensor_train[0])
# 输入文本的对应情况:
# 1 ----> <start>
# 33 ----> los
# 1923 ----> fantasmas
# 1924 ----> existen
# 3 ----> .
# 2 ----> <end>
# 输出文本的对应情况:
# 1 ----> <start>
# 1508 ----> ghosts
# 1115 ----> exist
# 3 ----> .
# 2 ----> <end>#创建一个tf.data数据集对象
# 为了方便之后的训练,都需要将数据集转化成tf.data数据集对象,这已经成为了使用tf.keras进行模型训练前的标准步骤!
# 设置超参数
BUFFER_SIZE = len(input_tensor_train) #24000个用于训练阶段的样本
BATCH_SIZE = 64 #数据批次大小(每次参数更新用到的数据量)
steps_per_epoch = len(input_tensor_train)//BATCH_SIZE #steps_per_epoch:批量次数,即一个epoch中遍历BATCH_SIZE批量大小数据的次数
embedding_dim = 256 #词嵌入的维度
units = 1024 #编码器中GRU层的隐含节点数,即隐藏层中的隐藏神经元数量
# 非重复的词汇总数
vocab_inp_size = len(inp_lang.word_index)+1     #获取输入序列的不重复单词的总数作为输入序列的字典大小
vocab_tar_size = len(targ_lang.word_index)+1    #获取目标序列的不重复单词的总数作为目标序列的字典大小
print(vocab_inp_size) #9414 输入序列的不重复单词的总数作为输入序列的字典大小
print(vocab_tar_size) #4935 目标序列的不重复单词的总数作为目标序列的字典大小"""
dataset中输入序列tensor的形状为(64, 16),即(BATCH_SIZE, 输入序列最大长度句子的长度)
dataset中目标序列tensor的形状为(64, 11),即(BATCH_SIZE, 目标序列最大长度句子的长度)
"""
# 转化成tf.data的dataset形式
dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
# 进行批次化,drop_remainder=True代表舍弃最后一个批次可能不满足batch_size大小的数据
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)
# 取出一组样例数据查看一下,现将dataset封装成迭代器,再用next方法取出一个
example_input_batch, example_target_batch = next(iter(dataset))
# 打印结果
print(example_input_batch.shape, example_target_batch.shape) #(64, 16) (64, 11)#---------------------------------第三步:构建模型并选择优化器和损失函数-----------------------------------##构建模型的编码器部分:
class Encoder(tf.keras.Model):def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):""":param vocab_size: 非重复的词汇总数:param embedding_dim: 词嵌入的维度:enc_units: 编码器中GRU层的隐含节点数:batch_sz: 数据批次大小(每次参数更新用到的数据量)"""super(Encoder, self).__init__()# 将变量传入类中self.batch_sz = batch_szself.enc_units = enc_units# 实例化embedding层self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)"""return_sequences:布尔值。是返回输出序列中的最后一个输出还是完整序列。 默认值:False。True代表返回GRU序列模型的每个时间步的输出(每个输出做连接操作)return_state:布尔值。 除输出外,是否返回最后一个状态。 默认值:False。True代表除了返回输出外,还需要返回最后一个隐层状态。recurrent_initializer:recurrent_kernel权重矩阵的初始化程序,用于对递归状态进行线性转换。 默认值:正交。'glorot_uniform'即循环状态张量的初始化方式为均匀分布。"""# 实例化gru层# return_sequences=True代表返回GRU序列模型的每个时间步的输出(每个输出做连接操作)# return_state=True代表除了返回输出外,还需要返回最后一个隐层状态# recurrent_initializer='glorot_uniform'即循环状态张量的初始化方式为均匀分布self.gru = tf.keras.layers.GRU(self.enc_units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')def call(self, x, hidden):# 对输入进行embedding操作x = self.embedding(x)"""initial_state:要传递给单元格的第一个调用的初始状态张量的列表(可选,默认为None,这将导致创建零填充的初始状态张量)。"""# 通过gru层获得最后一个时间步的输出和隐含状态output, state = self.gru(x, initial_state = hidden)return output, statedef initialize_hidden_state(self):""" (BATCH_SIZE, 隐藏层中的隐藏神经元数量) """# gru层的隐含节点对应的参数张量以零张量初始化return tf.zeros((self.batch_sz, self.enc_units))#调用:
# 实例化encoder
encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)# 样本输入
# sample_hidden = encoder.initialize_hidden_state()
# sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
# #(64, 16, 1024) 即 (BATCH_SIZE, 输入序列最大长度句子的长度, 隐藏层中的隐藏神经元数量)
# print ('Encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))
# #(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)
# print ('Encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))"""
tensorflow提供了两种Attention Mechanisms(注意力机制)1.BahdanauAttention注意力机制:tfa.seq2seq.BahdanauAttention实现BahdanauAttention风格的(additive累积)注意力机制。2.LuongAttention注意力机制:tfa.seq2seq.LuongAttention实现LuongAttention风格的(multiplicative乘法)注意力机制构建BahdanauAttention注意力机制类的伪代码:# 这里实现 BahdanauAttention注意力机制1, score = FC(tanh(FC(EO) + FC(H)))2, attention weights = softmax(score, axis = 1).解释: Softmax 默认被应用于最后一个轴,但是这里我们想将它应用于第一个轴,因为分数 (score) 的形状是 (批大小,最大长度,隐层大小),最大长度 (max_length) 是输入的长度。因为我们想为每个输入长度分配一个权重,所以softmax应该用在这个轴上。3, context vector = sum(attention weights * EO, axis = 1)解释: 选择第一个轴的原因同上.4, embedding output = 解码器输入 X 通过一个嵌入层5, merged vector = concat(embedding output, context vector)符号代表:FC: 全连接层EO: 编码器输出H: 隐藏层状态X: 解码器输入
"""#构建注意力机制类:
class BahdanauAttention(tf.keras.Model):def __init__(self, units):"""初始化三个必要的全连接层"""super(BahdanauAttention, self).__init__()self.W1 = tf.keras.layers.Dense(units)self.W2 = tf.keras.layers.Dense(units)self.V = tf.keras.layers.Dense(1)"""传入值:features:编码器的输出,(64, 16, 1024) 即 (BATCH_SIZE, 输入序列最大长度句子的长度, 隐藏层中的隐藏神经元数量)hidden:解码器的隐层输出状态,(64, 1024) 即 (batch_size, hidden_size) (BATCH_SIZE, 隐藏层中的隐藏神经元数量)返回值:attention_result:(64, 1024) 即 (batch size, units) (BATCH_SIZE, 隐藏层中的隐藏神经元数量)attention_weights:(64, 16, 1) 即 (batch_size, sequence_length, 1) (BATCH_SIZE, 输入序列最大长度句子的长度, 1)"""def call(self, features, hidden):"""description: 具体计算函数:param features: 编码器的输出:param hidden: 解码器的隐层输出状态return: 通过注意力机制处理后的结果和注意力权重attention_weights""""""1.hidden_with_time_axis = tf.expand_dims(hidden, 1)解码器的隐层输出状态hidden,(64, 1024) 即 (batch_size, hidden_size) (BATCH_SIZE, 隐藏层中的隐藏神经元数量)。hidden扩展一个维度从(64, 1024)变成(64, 1,1024)。2.score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))计算注意力得分score。features:编码器的输出,(64, 16, 1024)。hidden_with_time_axis:解码器的隐层输出状态,(64, 1,1024)W1和W2:Dense(隐藏层中的隐藏神经元数量1024)tanh(W1(features) + W2(hidden_with_time_axis)):---> tanh(W1((64, 16, 1024)) + W2((64, 1,1024)))---> tanh((64, 16, 1024))---> (64, 16, 1024) 即 (BATCH_SIZE, 输入序列最大长度句子的长度, 隐藏层中的隐藏神经元数量)3.attention_weights = tf.nn.softmax(self.V(score), axis=1)计算注意力权重attention_weights。V:Dense(隐藏层中的隐藏神经元数量1)softmax(V(score), axis=1)---> softmax(V((64, 16, 1024)), axis=1)---> softmax((64, 16, 1), axis=1)---> (64, 16, 1) 即 (BATCH_SIZE, 输入序列最大长度句子的长度, 1)因为注意力得分score的形状是(BATCH_SIZE, 输入序列最大长度句子的长度, 隐藏层中的隐藏神经元数量),输入序列最大长度句子的长度(max_length)是输入的长度。因为我们想为每个输入长度分配一个权重,所以softmax应该用在第一个轴(max_length)上axis=1,而softmax默认被应用于最后一个轴axis=-1。4.context_vector = tf.reduce_sum(attention_weights * features, axis=1)获得注意力机制处理后的结果context_vector。reduce_sum(attention_weights * features, axis=1)---> reduce_sum((64, 16, 1) * (64, 16, 1024), axis=1)---> reduce_sum((64, 16, 1024), axis=1)---> (64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)"""# 为hidden扩展一个维度(batch_size, hidden_size) --> (batch_size, 1, hidden_size)hidden_with_time_axis = tf.expand_dims(hidden, 1)# 根据公式计算注意力得分, 输出score的形状为: (batch_size, 16, hidden_size)score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))# 根据公式计算注意力权重, 输出attention_weights形状为: (batch_size, 16, 1)attention_weights = tf.nn.softmax(self.V(score), axis=1)# 最后根据公式获得注意力机制处理后的结果context_vector# context_vector的形状为: (batch_size, hidden_size)context_vector = attention_weights * featurescontext_vector = tf.reduce_sum(context_vector, axis=1)return context_vector, attention_weights#调用:
# attention_layer = BahdanauAttention(1024)
# attention_result, attention_weights = attention_layer(sample_output, sample_hidden)
# print("Attention result shape: (batch size, units) {}".format(attention_result.shape)) #(64, 1024)
# print("Attention weights shape: (batch_size, sequence_length, 1) {}".format(attention_weights.shape)) #(64, 16, 1)"""
构建RNN解码器:这里RNN是指GRU, 同时在解码器中使用注意力机制.
"""
class Decoder(tf.keras.Model):def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):super(Decoder, self).__init__()# 所有的参数传递和实例化方法与编码器相同self.batch_sz = batch_szself.dec_units = dec_unitsself.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)self.gru = tf.keras.layers.GRU(self.dec_units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')# 实例化一个Dense层作为输出层self.fc = tf.keras.layers.Dense(vocab_size)# 在解码器阶段我们将使用注意力机制,这里实例化注意力的类self.attention = BahdanauAttention(self.dec_units)"""1.x = self.embedding(x)输入:(64, 1) 64行1列,批量大小句子数为64,1列为该行句子的第N列的单词输出:(64, 1, 256) (BATCH_SIZE, 输入序列最大长度句子的长度, 嵌入维度)2.context_vector, attention_weights = self.attention(enc_output, hidden)attention_weights注意力权重:(64, 16, 1) 即 (BATCH_SIZE, 输入序列最大长度句子的长度, 1)context_vector注意力机制处理后的结果:(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)3.x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)tf.expand_dims(context_vector, 1):(64, 1, 1024) 即 (BATCH_SIZE, 1, 隐藏层中的隐藏神经元数量)concat([(64, 1, 1024),(64, 1, 256)], axis=-1):1024+256=1280,最终输出 (64, 1, 1280)4.GRU1.tf.keras.layers.GRU(self.dec_units, return_sequences=True, return_state=True, recurrent_initializer='glorot_uniform')return_sequences:布尔值。是返回输出序列中的最后一个输出还是完整序列。 默认值:False。True代表返回GRU序列模型的每个时间步的输出(每个输出做连接操作)return_state:布尔值。 除输出外,是否返回最后一个状态。 默认值:False。True代表除了返回输出外,还需要返回最后一个隐层状态。recurrent_initializer:recurrent_kernel权重矩阵的初始化程序,用于对递归状态进行线性转换。 默认值:正交。'glorot_uniform'即循环状态张量的初始化方式为均匀分布。2.output, state = gru(x)      output:(64, 1, 1024) 即 (BATCH_SIZE, 1, 隐藏层中的隐藏神经元数量)(当前批次的样本个数, 当前样本的序列长度(单词个数), 隐藏层中神经元数量 * 1)state:(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)5.output = tf.reshape(output, (-1, output.shape[2]))(-1, output.shape[2]):表示把(64, 1, 1024)转换为(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)6.x = self.fc(output)x:(64, 4935) 即 (BATCH_SIZE, 目标序列的不重复单词的总数作为目标序列的字典大小)"""def call(self, x, hidden, enc_output):""":param x: 每个时间步上解码器的输入:param hidden: 每次解码器的隐层输出:param enc_output: 编码器的输出"""# print("x.shape",x.shape) #(64, 1)。64行1列,批量大小句子数为64,1列为该行句子的第N列的单词# 输入通过embedding层x = self.embedding(x)# print("x1.shape",x.shape) #(64, 1, 256)。(BATCH_SIZE, 输入序列最大长度句子的长度, 嵌入维度)# 使用注意力规则计算hidden与enc_output的'相互影响程度'context_vector, attention_weights = self.attention(enc_output, hidden)# print("tf.expand_dims(context_vector, 1).shape",tf.expand_dims(context_vector, 1).shape) #(64, 1, 1024)# 将这种'影响程度'与输入x拼接(这个操作也是注意力计算规则的一部分)x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)# print("x2.shape",x.shape) #(64, 1, 1280)# 将新的x输入到gru层中得到输出output, state = self.gru(x)# print("output1.shape",output.shape) #(64, 1, 1024) 即 (BATCH_SIZE, 1, 隐藏层中的隐藏神经元数量)# print("state.shape",state.shape) #(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)# 改变输出形状使其适应全连接层的输入形式output = tf.reshape(output, (-1, output.shape[2]))# print("output2.shape",output.shape) #(64, 1024) 即 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)# 使用全连接层作为输出层# 输出的形状 == (批大小,vocab)x = self.fc(output)# print("x3.shape",x.shape) #(64, 4935) 即 (BATCH_SIZE, 目标序列的不重复单词的总数作为目标序列的字典大小)return x, state, attention_weights#调用:
decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)
# sample_decoder_output, _, _ = decoder(tf.random.uniform((64, 1)), sample_output, sample_hidden)
# print ('Decoder output shape: (batch_size, vocab size) {}'.format(sample_decoder_output.shape)) #(64, 4935)"""
选取优化方法和损失函数
"""
# 选取Adam优化方法
optimizer = tf.keras.optimizers.Adam()# 损失基本计算方法为稀疏类别交叉熵损失
# from_logits=True代表是否将预测结果预期为非 0/1 的值进行保留
# 理论来讲二分类最终的结果应该只有0/1,函数将自动将其变为0/1,from_logits=True后,值不会被改变
# reduction='none',接下来我们将自定义损失函数,reduction必须设置为None,
# 我们可以将它看作是自定义损失函数的识别属性
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')"""
tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False, reduction=losses_utils.ReductionV2.AUTO,name='sparse_categorical_crossentropy'
)from_logits: y_pred是否预期为对数张量。 默认情况下,我们假设y_pred编码概率分布。 注意:使用from_logits = True可能在数值上更稳定。import tensorflow as tfhelp(tf.losses.categorical_crossentropy) 查看categorical_crossentropy函数的默认参数列表和使用方法介绍其中形参默认为from_logits=False,网络预测值y_pred 表示必须为经过了 Softmax函数的输出值。当 from_logits 设置为 True 时,网络预测值y_pred 表示必须为还没经过 Softmax 函数的变量 z。from_logits=True 标志位将softmax激活函数实现在损失函数中,便不需要手动添加softmax损失函数,提升数值计算稳定性。from_logits 指的就是是否有经过Logistic函数,常见的Logistic函数包括Sigmoid、Softmax函数。reduction: (可选的) tf.keras.losses.reduction的类型,适用于损失。 默认值为自动。 AUTO表示减少选项将由使用情况决定。 在几乎所有情况下,该默认值均为SUM_OVER_BATCH_SIZE。 与tf.distribute.Strategy一起使用时,在诸如tf.keras之类的内置训练循环之外,使用AUTO或SUM_OVER_BATCH_SIZE会引发错误。1.reduction='none':默认值为losses_utils.ReductionV2.AUTO,即默认使用SUM_OVER_BATCH_SIZE的方式计算批量样本个数的loss的sum总和返回一个总和值。设置'none'代表将自定义损失函数,即自定义损失函数的识别属性,代表每个样本的loss均保留返回。2.reduction='none'打印loss为 Tensor("sparse_categorical_crossentropy_4/weighted_loss/Mul:0", shape=(64,), dtype=float32)shape=(64,) 代表每个样本的loss均保留返回。3.reduction=默认值losses_utils.ReductionV2.AUTO打印loss为 Tensor("sparse_categorical_crossentropy_7/weighted_loss/value:0", shape=(), dtype=float32)shape=() 代表默认使用SUM_OVER_BATCH_SIZE的方式计算批量样本个数的loss的sum总和,那么返回的loss只有一个总和值。
"""
# 因为每次生成的结果都是局部结果,要和真实结果进行比较需要对真实结果进行遮掩
# 等效于对损失计算结果进行掩码
def loss_function(real, pred):"""自定义损失函数,参数为预测结果pred和真实结果real""""""1.创建mask掩码,用于对填充的字符进行掩盖,不计算进loss中,等效于对损失计算结果进行掩码1.bool_result = tf.math.equal(real, 0):返回 False/Truetf.math.logical_not([False,True]):返回 tf.Tensor([True False], shape=(2,), dtype=bool)2.pad_sequences填充值默认为0.0,因此只需要对填充的字符进行掩盖,只要输入是0 那么tf.math.equal(real, 0)就会返回True,再通过logical_not即能把True的变换为False,因此便能不计算进loss中,等效于对损失计算结果进行掩码。2.SparseCategoricalCrossentropy1.loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')    SparseCategoricalCrossentropy稀疏类别交叉熵损失,自动将其中一方不是one-hot表示的数据转换为one-hot表示。from_logits=True:softmax激活函数实现在损失函数中,便不需要手动添加softmax损失函数reduction='none':默认值为losses_utils.ReductionV2.AUTO,即默认使用SUM_OVER_BATCH_SIZE的方式计算批量样本个数的loss的sum总和返回一个总和值。设置'none'代表将自定义损失函数,即自定义损失函数的识别属性,代表每个样本的loss均保留返回。2.reduction='none'打印loss为 Tensor("sparse_categorical_crossentropy_4/weighted_loss/Mul:0", shape=(64,), dtype=float32)shape=(64,) 代表每个样本的loss均保留返回。3.reduction=默认值losses_utils.ReductionV2.AUTO打印loss为 Tensor("sparse_categorical_crossentropy_7/weighted_loss/value:0", shape=(), dtype=float32)shape=() 代表默认使用SUM_OVER_BATCH_SIZE的方式计算批量样本个数的loss的sum总和,那么返回的loss只有一个总和值。"""# 使用tf.math.equal方法对real和0进行对比# 对结果再进行逻辑非操作生成掩码张量maskbool_result = tf.math.equal(real, 0) #tf.math.logical_not([False,True]) 结果 tf.Tensor([ True False], shape=(2,), dtype=bool)mask = tf.math.logical_not(bool_result)# print("mask", mask) #Tensor("LogicalNot_4:0", shape=(64,), dtype=bool)# print("mask.shape",mask.shape) #(64,)# 使用基本计算方法计算损失loss_ = loss_object(real, pred)# print("loss_",loss_) #Tensor("sparse_categorical_crossentropy_4/weighted_loss/Mul:0", shape=(64,), dtype=float32)# 将mask进行类型转换,使其能够进行后续操作mask = tf.cast(mask, dtype=loss_.dtype)# 将loss_与mask相乘即对loss_进行掩码loss_ *= mask# 计算loss_张量所有元素的均值return tf.reduce_mean(loss_)#创建检测点保存对象:
# 定义检测点(每个阶段训练的模型)保存路径
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
# 使用tf.train.Checkpoint创建检测点保存对象
# 我们会在之后的训练中调用它来保存模型
checkpoint = tf.train.Checkpoint(optimizer=optimizer,encoder=encoder,decoder=decoder)#---------------------------------第四步: 构建训练函数并进行训练-----------------------------------#
#构建训练函数:
@tf.function # 该装饰器使该函数自动编译张量图, 使其可以直接执行
def train_step(inp, targ, enc_hidden):# 设定初始损失为0loss = 0# 开启一个用于梯度记录的上下文管理器with tf.GradientTape() as tape:# 调用编码器部分得到编码器输出和编码器隐层输出enc_output, enc_hidden = encoder(inp, enc_hidden)# print("enc_output.shape",enc_output.shape) #(64, 16, 1024) 。编码器输出 (BATCH_SIZE, 输入序列最大长度句子的长度, 隐藏层中的隐藏神经元数量)# print("enc_hidden.shape",enc_hidden.shape) #(64, 1024)。 编码器输出的隐藏状态 (BATCH_SIZE, 隐藏层中的隐藏神经元数量)# 将编码器隐层输出设定为解码器的初始隐层状态dec_hidden = enc_hidden# 以'起始符'作为解码器的第一个输入字符dec_input = tf.expand_dims([targ_lang.word_index['<start>']] * BATCH_SIZE, 1)# print("dec_input.shape",dec_input.shape) #(64, 1) 64行1列,批量大小句子数为64,1列为该行句子的第1列的单词'<start>'"""targ.shape:(64, 11) 。dataset中目标序列tensor的形状为 (BATCH_SIZE, 目标序列最大长度句子的长度)targ.shape[1]:11 即 目标序列最大长度句子的长度for t in range(1, targ.shape[1]):即遍历 目标序列最大长度句子的长度"""# 开始循环解码过程for t in range(1, targ.shape[1]):# 使用解码器获得新的解码器隐层输出(状态),以及预测结果predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)# print("predictions.shape", predictions.shape) #(64, 4935)# print("dec_hidden.shape", dec_hidden.shape) #(64, 1024)"""loss_function(targ[:, t], predictions):取所有行的第t列,即取目标序列最大长度句子的长度11中的每一列单词"""# 使用损失函数计算本次训练过程的损失loss += loss_function(targ[:, t], predictions)# 使用teacher-forcing矫正可能的错误结果# 直接将正确结果作为下一次循环的输入dec_input = tf.expand_dims(targ[:, t], 1)# print("dec_input.shape", dec_input.shape) #(64, 1)# 计算每次batch的平均损失batch_loss = (loss / int(targ.shape[1]))# 获得整个模型训练的参数变量variables = encoder.trainable_variables + decoder.trainable_variables# 使用梯度记录管理器求解整个网络的梯度,参数是loss和全部参数变量gradients = tape.gradient(loss, variables)# 根据梯度更新参数optimizer.apply_gradients(zip(gradients, variables))# 返回每次batch的平均损失return batch_loss"""
1.什么是teacher_forcing?它是一种用于序列生成任务的训练技巧, 在seq2seq架构中, 根据循环神经网络理论,解码器每次应该使用上一步的结果作为输入的一部分, 但是训练过程中,一旦上一步的结果是错误的,就会导致这种错误被累积,无法达到训练效果, 因此,我们需要一种机制改变上一步出错的情况,因为训练时我们是已知正确的输出应该是什么,因此可以强制将上一步结果设置成正确的输出, 这种方式就叫做teacher_forcing.
2.teacher_forcing的作用:能够在训练的时候矫正模型的预测,避免在序列生成的过程中误差进一步放大.teacher_forcing能够极大的加快模型的收敛速度,令模型训练过程更快更平稳.
"""""" 进行训练并打印日志 """
# 设置训练轮数
EPOCHS = 20
for epoch in range(EPOCHS):# 获得每轮训练的开始时间start = time.time()# 初始化编码器隐含状态enc_hidden = encoder.initialize_hidden_state()#编码器输入的隐藏状态:(BATCH_SIZE, 隐藏层中的隐藏神经元数量)print("enc_hidden",enc_hidden.shape) #(64, 1024)# 初始化总损失为0total_loss = 0"""steps_per_epoch:批量次数,即一个epoch中遍历BATCH_SIZE批量大小数据的次数for (遍历次数, (输入数据, 目标标签数据)) in enumerate(dataset.take(批量次数))"""print("steps_per_epoch",steps_per_epoch) #375# 循环数据集中的每个批次进行训练for (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)):# print("inp.shape",inp.shape) #(64, 16) 。dataset中输入序列tensor的形状为 (BATCH_SIZE, 输入序列最大长度句子的长度)# print("targ.shape",targ.shape) #(64, 11) 。dataset中目标序列tensor的形状为 (BATCH_SIZE, 目标序列最大长度句子的长度)# 调用train_step函数获得批次平均损失batch_loss = train_step(inp, targ, enc_hidden)# 将批次平均损失相加获得轮数总损失total_loss += batch_loss# 每100个批次打印一次批次平均损失if batch % 100 == 0:print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1, batch, batch_loss.numpy()))# 每两轮(epoch),保存一次模型if (epoch + 1) % 2 == 0:checkpoint.save(file_prefix = checkpoint_prefix)# 打印轮数平均损失print('Epoch {} Loss {:.4f}'.format(epoch + 1, total_loss / steps_per_epoch))# 打印模型训练耗时print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))#---------------------------------第五步:构建评估函数并进行预测分析-----------------------------------#
#构建评估函数:
def evaluate(sentence):"""description: 评估函数:param sentence: 待翻译的句子"""# 初始化用于绘制注意力效果图的张量attention_plot = np.zeros((max_length_targ, max_length_inp))# 下面将对输入文本做与训练语料同样的操作# 对输入的句子进行文本预处理sentence = preprocess_sentence(sentence)# 使用输入数值映射器对文本进行数值化映射inputs = [inp_lang.word_index[i] for i in sentence.split(' ')]# 对文本进行最大长度补齐inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=max_length_inp, padding='post')# 转换成张量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.word_index['<start>']], 0)# 开始循环解码,过程和训练时十分类似for t in range(max_length_targ):predictions, dec_hidden, attention_weights = decoder(dec_input, dec_hidden, enc_out)# 存储每次产生的注意力权重以便后面制图attention_weights = tf.reshape(attention_weights, (-1, ))attention_plot[t] = attention_weights.numpy()# 从预测分布中获得概率最大预测idpredicted_id = tf.argmax(predictions[0]).numpy()# 使用目标数值映射器将其还原为对应的文本(单词/标识)result += targ_lang.index_word[predicted_id] + ' '# 如果解码还原后为'终止符',则返回结果if targ_lang.index_word[predicted_id] == '<end>':return result, sentence, attention_plot# 否则,预测id被输送回模型,作为下一次预测输入dec_input = tf.expand_dims([predicted_id], 0)# 返回预测结果,原始输入文本,以及整个过程的注意力张量组合(仍然是一个张量)return result, sentence, attention_plotdef plot_attention(attention, sentence, predicted_sentence):"""description: 注意力张量制图函数:param attention: 整个过程的注意力张量组合:param sentence: 原始输入文本:param predicted_sentence: 预测结果"""# 打开一个10x10的画布fig = plt.figure(figsize=(10,10))# 在画布上创建1x1的子画布ax = fig.add_subplot(1, 1, 1)# 绘制子画布上绘制矩形,根据给出输入数值不同,颜色也不相同# cmap='viridis'是一种矩形的色彩填充方案‘绿藻’ax.matshow(attention, cmap='viridis')# 定义字体规范字典,这里我们只要求字体大小即可fontdict = {'fontsize': 14}# 使用x轴设置方法使输入文本在x轴上显示# rotation=90代表有90度的倾斜角,便于观看ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)# 同样,使预测文本在y轴上显示ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)# 设置x和y轴的刻度,与绘制时add_subplot的参数对应即可ax.xaxis.set_major_locator(ticker.MultipleLocator(1))ax.yaxis.set_major_locator(ticker.MultipleLocator(1))# 绘图plt.show()#调用:
def translate(sentence):"""预测并绘图"""# 调用评估函数获得结果result, sentence, attention_plot = evaluate(sentence)# 打印输入文本和预测文本print('Input: %s' % (sentence))print('Predicted translation: {}'.format(result))# 根据文本长度对注意力张量进行剪裁(剪裁掉的都是0部分)attention_plot = attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]# 进行注意力效果图绘制plot_attention(attention_plot, sentence.split(' '), result.split(' '))# 使用检测点对象恢复最近一次保存的模型
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))# 翻译西班牙语‘这里很冷。’
translate(u'hace mucho frio aqui.')
# 翻译西班牙语‘这就是我的生活。’
translate(u'esta es mi vida.')
# 翻译西班牙语‘他们还在家里吗?’
translate(u'¿todavia estan en casa?')
# 翻译西班牙语‘尝试找出答案。’
translate(u'trata de averiguarlo.')

这篇关于BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/297141

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言