学习目标
- 目标
- 掌握seq2seq模型特点
- 掌握集束搜索方式
- 掌握BLEU评估方法
- 掌握Attention机制
- 应用
- 应用Keras实现seq2seq对日期格式的翻译
目录
1.seq2seq
seq2seq模型是在2014年,是由Google Brain团队和Yoshua Bengio 两个团队各自独立的提出来。
1.1.定义
seq2seq是一个Encoder--Decoder 结构 的网络,它的输入是一个序列,输出也是一个序列, Encoder 中将一个可变长度的信号序列变为固定长度的向量表达 ,Decoder 将这个固定长度的向量变成可变长度的目标的信号序列。

注:Cell可以用 RNN ,GRU,LSTM 等结构。
- 相当于将RNN模型当中的s0输入变成一个encoder
1.2.条件语言模型理解
- 1、编解码器作用
- 编码器的作用是把一个不定长的输入序列x1,...,xt,输出到一个编码状态C
- 解码器输出yt的条件概率将基于之前的输出序列y1,yt−1和编码状态C
argmaxP(y1,...,yT′∣x1,...,xT),给定输入的序列,使得输出序列的概率值最大。
- 2、根据最大似然估计,最大化输出序列的概率

由于这个公式需要求出:...这个概率连乘会非常非常小不利于计算存储,所以需要对公式取对数计算:

所以这样就变成了..概率相加。
这样也可以看成输出结果通过softmax就变成了概率最大,而损失最小的问题,输出序列损失最小化。
1.3.应用场景
- 神经机器翻译(NMT)

- 聊天机器人
接下来我们来看注意力机制,那么普通的seq2seq会面临什么样的问题?
2.注意力机制
2.1.长句子问题

对于更长的句子,seq2seq就显得力不从心了,无法做到准确的翻译,一下是通常BLEU的分数随着句子的长度变化,可以看到句子非常长的时候,分数就很低。
BLEU(Bilingual Evaluation Understudy)是一种用于评估机器翻译质量的自动评价指标,它通过比较机器翻译结果与人工参考翻译之间的相似度来打分。BLEU 分数范围在 0 到 1 之间(或 0% 到 100%),分数越高表示翻译质量越好。
BLEU 的核心思想
- N-gram 匹配 :计算机器翻译(候选文本)和参考翻译之间的 n-gram(连续词序列) 重叠程度。
- 例如,1-gram(单个词)、2-gram(双词组合)等。
- 精度(Precision):统计候选翻译中有多少 n-gram 出现在参考翻译中,并做标准化(避免长句惩罚)。
- 短句惩罚(Brevity Penalty, BP):防止短翻译因匹配少量 n-gram 而获得高分。

本质原因:在Encoder-Decoder结构中,Encoder把所有的输入序列都编码成一个统一的语义特征C再解码,**因此, C中必须包含原始序列中的所有信息,它的长度就成了限制模型性能的瓶颈。**当要翻译的句子较长时,一个C可能存不下那么多信息,就会造成翻译精度的下降。
2.2.定义
- 建立Encoder的隐层状态输出到Decoder对应输出y所需要的上下文信息
- 目的:增加编码器信息输入到解码器中相同时刻的联系,其它时刻信息减弱

2.3.公式
注意上述的几个细节,颜色的连接深浅不一样,假设Encoder的时刻记为t,而Decoder的时刻记为t′。
-
1、
- αt′t为参数,在网络中训练得到
- 理解:蓝色的解码器中的cell举例子
-
-
2、αt′t的N个权重系数由来?
- 权重系数通过softmax计算:
- et′t是由t时刻的编码器隐层状态输出和解码器t′−1时刻的隐层状态输出计算出来的
- s为解码器隐层状态输出,h为编码器隐层状态输出
- v,Ws,Wh都是网络学习的参数
- 权重系数通过softmax计算:

3.机器翻译案例
使用简单的"日期转换"任务代替翻译任务,为了不然训练时间变得太长。
网络将输入以各种可能格式(例如"1958年8月29日","03/30/1968","1987年6月24日","July 3, 2025")编写的日期,并将其翻译成标准化的机器可读日期(例如"1958 -08-29","1968-03-30","1987-06-24")。使用seq2seq网络学习以通用机器可读格式YYYY-MM-DD输出日期。
3.1.环境配置
bash
pip install faker
pip install tqdm
pip install babel
pip install keras==2.2.4
- faker:生成数据包
- tqdm:python扩展包
- babel:代码装换器
- keras:更加方便简洁的深度学习库
- 为了快速编写代码
3.2.代码分析
-
Seq2seq():
- 序列模型类
- load_data(self,m):加载数据类,选择加载多少条数据
- init_seq2seq(self):初始化模型,需要自定义自己的模型
- self.get_encoder(self):定义编码器
- self.get_decoder(self):定义解码器
- self.get_attention(self):定义注意力机制
- self.get_output_layer(self):定义解码器输出层
- model(self):定义模型整体输入输出逻辑
- train(self, X_onehot, Y_onehot):训练模型
- test(self):测试模型
- 训练
python
if __name__ == '__main__':
s2s = Seq2seq()
X_onehot, Y_onehot = s2s.load_data(10000)
s2s.init_seq2seq()
s2s.train(X_onehot, Y_onehot)
#s2s.test()
整个数据集特征值的形状: (10000, 30, 37)
整个数据集目标值的形状: (10000, 10, 11)
查看第一条数据集格式:特征值:9 may 1998, 目标值: 1998-05-09
12 0 24 13 34 0 4 12 12 11 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36\] \[ 2 10 10 9 0 1 6 0 1 10
one_hot编码: [[0. 0. 0. ... 0. 0. 0.]
1. 0. 0. ... 0. 0. 0.
0. 0. 0. ... 0. 0. 0.
...
0. 0. 0. ... 0. 0. 1.
0. 0. 0. ... 0. 0. 1.
0. 0. 0. ... 0. 0. 1.\]\] \[\[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.\]
Epoch 1/1
100/10000 [..............................] - ETA: 10:52 - loss: 23.9884 - dense_1_loss: 2.3992 - dense_1_acc: 0.3200 - dense_1_acc_1: 0.0000e+00 - dense_1_acc_2: 0.0100 - dense_1_acc_3: 0.1300 - dense_1_acc_4: 0.0000e+00 - dense_1_acc_5: 0.0400 - dense_1_acc_6: 0.0900 - dense_1_acc_7: 0.0000e+00 - dense_1_acc_8: 0.3500 - dense_1_acc_9: 0.1100
200/10000 [..............................] - ETA: 5:27 - loss: 23.9289 - dense_1_loss: 2.3991 - dense_1_acc: 0.2550 - dense_1_acc_1: 0.0000e+00 - dense_1_acc_2: 0.0050 - dense_1_acc_3: 0.1150 - dense_1_acc_4: 0.0950 - dense_1_acc_5: 0.0250 - dense_1_acc_6: 0.1150 - dense_1_acc_7: 0.0800 - dense_1_acc_8: 0.3400 - dense_1_acc_9: 0.1050
- 测试

3.3.加载数据
- 模型参数:
python
def __init__(self, Tx=30, Ty=10, n_x=32, n_y=64):
# 定义网络的相关参数
self.model_param = {
"Tx": Tx, # 定义encoder序列最大长度
"Ty": Ty, # decoder序列最大长度
"n_x": n_x, # encoder的隐层输出值大小
"n_y": n_y # decoder的隐层输出值大小和cell输出值大小
}
对于加载数据来说,我们不需要去进行编写逻辑了,看一下大概的逻辑.
- 加载数据
- 加载的代码逻辑在nmt_utils当中
- from nmt_utils import *
- 加载的代码逻辑在nmt_utils当中
nmt_utils.py:
python
import numpy as np
from faker import Faker # 用于生成虚假数据
import random
from tqdm import tqdm # 用于显示进度条
from babel.dates import format_date # 用于格式化日期
from tensorflow.keras.utils import to_categorical # 用于one-hot编码
import tensorflow.keras.backend as K # Keras后端引擎
import matplotlib.pyplot as plt # 绘图库
# 设置随机种子以保证结果可复现
from faker import Faker
import random
# 设置全局随机种子(推荐方式)
Faker.seed(12345) # ✅ 使用类方法设置 Faker 的种子
random.seed(12345) # 设置 Python random 模块的种子
fake = Faker() # 创建实例
# 定义日期格式列表
FORMATS = ['short',
'medium',
'long',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'd MMM YYY',
'd MMMM YYY',
'dd MMM YYY',
'd MMM, YYY',
'd MMMM, YYY',
'dd, MMM YYY',
'd MM YY',
'd MMMM YYY',
'MMMM d YYY',
'MMMM d, YYY',
'dd.MM.YY']
# 设置语言环境(可修改为其他语言)
LOCALES = ['en_US']#这是us英文,可修改为其他语言
def load_date():
"""
生成虚假日期数据
返回: 元组包含(人类可读日期字符串, 机器可读日期字符串, 日期对象)
"""
dt = fake.date_object() # 生成随机日期对象,
# 输出: datetime.date(2023, 5, 15)
try:
# 格式化人类可读日期
human_readable = format_date(dt, format=random.choice(FORMATS), locale='en_US')# 格式化日期,locale为'en_US',可修改为其他语言,输出: 'May 15, 2023'
human_readable = human_readable.lower() # 转为小写,输出: 'may 15, 2023'
human_readable = human_readable.replace(',', '') # 移除逗号,输出:'may 15 2023'
machine_readable = dt.isoformat() # 机器可读格式,输出: '2023-05-15'
except AttributeError as e:
return None, None, None
return human_readable, machine_readable, dt
def load_dataset(m):
"""
加载包含m个样本的数据集并构建词汇表
:m: 要生成的样本数量
返回:
dataset -- 包含(人类可读日期字符串, 机器可读日期字符串)对的列表
human -- 人类可读词汇表字典
machine -- 机器可读词汇表字典
"""
human_vocab = set() # 人类可读日期字符集
machine_vocab = set() # 机器可读日期字符集
dataset = [] # 数据集
Tx = 30 # 输入序列长度
# 生成m个样本
for i in tqdm(range(m)):
h, m, _ = load_date()
if h is not None:
dataset.append((h, m)) # 添加(人类可读, 机器可读)对
human_vocab.update(tuple(h)) # 更新人类可读字符集
machine_vocab.update(tuple(m)) # 更新机器可读字符集
# 构建人类可读词汇表字典(添加未知词和填充词标记)
human = dict(zip(sorted(human_vocab) + ['<unk>', '<pad>'],
list(range(len(human_vocab) + 2))))
# 构建机器可读词汇表字典和反向字典
inv_machine = dict(enumerate(sorted(machine_vocab)))
machine = {v: k for k, v in inv_machine.items()}
return dataset, human, machine
def preprocess_data(dataset, human_vocab, machine_vocab, Tx, Ty):
"""
预处理数据:将字符串转换为整数序列并进行one-hot编码
"""
X, Y = zip(*dataset) # 解压数据集
# 将字符串转换为整数序列
X = np.array([string_to_int(i, Tx, human_vocab) for i in X])
Y = [string_to_int(t, Ty, machine_vocab) for t in Y]
# 进行one-hot编码
Xoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(human_vocab)), X)))
Yoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(machine_vocab)), Y)))
return X, np.array(Y), Xoh, Yoh
def string_to_int(string, length, vocab):
"""
将字符串转换为整数序列表示
参数:
string -- 输入字符串,如 'Wed 10 Jul 2007'
length -- 时间步长,决定输出是填充还是截断
vocab -- 词汇表字典
返回:
rep -- 整数列表(或'<unk>'),表示字符串字符在词汇表中的位置
"""
# 标准化处理:转为小写并移除逗号
string = string.lower()
string = string.replace(',', '')
# 如果字符串过长则截断
if len(string) > length:
string = string[:length]
# 将每个字符映射到词汇表中的索引,未知字符用'<unk>'表示
rep = list(map(lambda x: vocab.get(x, '<unk>'), string))
# 如果字符串过短则填充
if len(string) < length:
rep += [vocab['<pad>']] * (length - len(string))
return rep
def softmax(x, axis=1):
"""
Softmax激活函数
参数:
x: 张量
axis: 应用softmax归一化的轴
返回:
softmax变换后的张量
异常:
当输入是一维张量时抛出ValueError
"""
ndim = K.ndim(x)
if ndim == 2:
return K.softmax(x)
elif ndim > 2:
e = K.exp(x - K.max(x, axis=axis, keepdims=True))
s = K.sum(e, axis=axis, keepdims=True)
return e / s
else:
raise ValueError('Cannot apply softmax to a tensor that is 1D')
函数名 | 核心功能 | 典型应用场景 |
---|---|---|
load_date() | 1. 生成随机日期对象 2. 格式化为人类可读字符串(如"may 15 2023") 3. 生成机器可读ISO格式(如"2023-05-15") | 生成单条日期训练样本 |
load_dataset(m) | m个日期样本 2. 构建人类可读字符词汇表 3. 构建机器可读字符词汇表 4. 自动添加<unk> 和<pad> 特殊标记 |
初始化训练数据集和词汇表 |
preprocess_data() | 1. 将字符串转为整数序列 2. 统一序列长度(填充/截断) 3. 生成one-hot编码 4. 输出可直接用于Keras模型的张量 | 数据预处理管道 |
string_to_int() | 1. 字符到词汇表索引的映射 2. 处理未知字符(返回<unk> ) 3. 序列长度标准化(填充<pad> ) |
文本到数值的转换 |
softmax() | 1. 实现多维度softmax 2. 数值稳定性优化(减最大值) 3. 支持Keras后端运算 | 神经网络激活函数 |
主函数加载数据:
我们先看整个训练的逻辑,并从中来实现整个模型的定义,计算逻辑


python
def load_data(self, m):
"""
指定获取m条数据
:param m: 数据的总样本数
:return:
dataset:[('9 may 1998', '1998-05-09'), ('10.09.70', '1970-09-10')]
x_vocab:翻译前的格式对应数字{' ': 0, '.': 1, '/': 2, '0': 3, '1': 4, '2': 5, '3': 6, '4': 7,....}
y_vocab:翻译后的格式对应数字{'-': 0, '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6, '6': 7, '7': 8, '8': 9, '9': 10}
"""
# 获取3个值:数据集,特征词的字典映射,目标词字典映射
dataset, x_vocab, y_vocab = load_dataset(m)
# 获取处理好的数据:特征x以及目标y的one_hot编码
X, Y, X_onehot, Y_onehot = preprocess_data(dataset, x_vocab, y_vocab, self.model_param["Tx"], self.model_param["Ty"])
print("整个数据集特征值的形状:", X_onehot.shape)
print("整个数据集目标值的形状:", Y_onehot.shape)
# 打印数据集
print("查看第一条数据集格式:特征值:%s, 目标值: %s" % (dataset[0][0], dataset[0][1]))
print(X[0], Y[0])
print("one_hot编码:", X_onehot[0], Y_onehot[0])
# 添加特征词个不重复个数以及目标词的不重复个数
self.model_param["x_vocab"] = x_vocab
self.model_param["y_vocab"] = y_vocab
self.model_param["x_vocab_size"] = len(x_vocab)
self.model_param["y_vocab_size"] = len(y_vocab)
return X_onehot, Y_onehot
3.4.定义网络
- (1)定义好网络的输入输出格式
- (2)定义好优化器(选择Adam,参数lr=0.005, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.001)
- from keras.optimizers import Adam
- model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
- (3)模型训练,
- model.fit(inputs, outputs, epochs=1,batch_size=100)
python
def train(self, X_onehot, Y_onehot):
"""
训练
:param X_onehot: 特征值的one_hot编码
:param Y_onehot: 目标值的one_hot编码
:return:
"""
# 利用网络结构定义好模型输入输出
model = self.model()
opt = Adam(lr=0.005, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.001)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
s0 = np.zeros((10000, self.model_param["n_y"]))
c0 = np.zeros((10000, self.model_param["n_y"]))
outputs = list(Y_onehot.swapaxes(0, 1))
# 输入x,以及decoder中LSTM的两个初始化值
model.fit([X_onehot, s0, c0], outputs, epochs=1, batch_size=100)
return None
- (1)定义网络好的输入到输出流程
- 步骤1、定义模型的输入
- 步骤2:使用encoder的双向LSTM结构得输出a
- 步骤3:循环decoder的Ty次序列输入,获取decoder最后输出
- 1: 定义decoder第t'时刻的注意力结构并输出context
- context = self.computer_one_attention(a, s)(需要实现Attention结构的计算过程)
- 2: 对"context" 和初始两个状态s0,c0输入到deocder当中,返回两个输出
- s, _, c = self.decoder(context, initial_state=[s, c])
- 3: 应用 Dense layere获取deocder的t'时刻的输出 out = self.output_layer(s)
- 1: 定义decoder第t'时刻的注意力结构并输出context
- 步骤 4: 创建model实例,定义输入输出
- from keras.models import Model

定义整个网络模型:
python
def model(self):
"""
定义整个网络模型
:return: keras 当中的model类型
"""
# 1、定义encoder的输入X (30, 37)
X = Input(shape=(self.model_param["Tx"], self.model_param["x_vocab_size"]), name="X")
# 定义一个初始输入的s0, 64大小
s0 = Input(shape=(self.model_param["n_y"],), name="s0")
c0 = Input(shape=(self.model_param["n_y"],), name="c0")
s = s0
c = c0
# 定义一个装有输出的列表
outputs = []
# 2、输入到encoder当中,得到a
a = self.encoder(X)
# 3、计算输出结果,循环deocder当中t'个时刻,计算每个LSTM的输出结果
for t in range(self.model_param["Ty"]):
# (1)循环计算每一个时刻的context
context = self.computer_one_attention(a, s)
# (2)输入s0,c0,context到某个时刻decoder得到下次的输出s, c
# 因为是LSTM结构,所以有两个隐层状态,其中s可以用作输出
s, _, c = self.decoder(context, initial_state=[s, c])
# (3)s输出到最后一层softmax得到预测结果
out = self.output_layer(s)
outputs.append(out)
# 输入输出定义好了
model = Model(inputs=(X, s0, c0), outputs=outputs)
return model
3.5.定义模型
模型初始化模型结构定义
- 在训练中有一些模型结构,所以现需要定义这些结构统一初始化,这些模型结构作为整个Seq2Seq类的属性,初始化逻辑。
python
def init_seq2seq(self):
"""
初始化网络结构
:return:
"""
# 添加encoder属性
self.get_encoder()
# 添加decoder属性
self.get_decoder()
# 添加attention属性
self.get_attention()
# 添加get_output_layer属性
self.get_output_layer()
return None
- 定义编解码器、Attention机制、输出层
Keras是一个高级神经网络API,用Python编写,能够在TensorFlow之上运行。它的开发重点是实现快速实验。能够以最小的延迟从想法到结果是进行良好研究的关键。
如果您需要深度学习库,请使用Keras:允许简单快速的原型设计(通过用户友好性,模块化和可扩展性)
- 编码器
- 编码器:使用双向LSTM(隐层传递有双向值传递)
- from keras.layers import LSTM, Bidirectional
- LSTM(units, return_sequences=False,name="")
- units: 正整数, units状态输出的维度
- return_sequences:布尔类型 是否返回输出序列
- return:LSTM layer
- Bidirectional(layer, merge_mode='concat')
- 对RNN、LSTM进行双向装饰
- layer:RNN layer或者LSTM layer
- merge_mode:将RNN/LSTM的前向和后向输出值进行合并
- {'sum', 'mul', 'concat', 'ave', None}

python
def get_encoder(self):
"""
定义编码器结构
:return:
"""
# 指定隐层值输出的大小
self.encoder = Bidirectional(LSTM(self.model_param["n_x"], return_sequences=True, name='bidirectional_1'), merge_mode='concat')
return None
- 解码器
- return_state : 布尔,是否返回输出以及状态值
- if return_state: 第一个值是output. 后面的值是状态值shape 为 (batch_size, units).
- return_state : 布尔,是否返回输出以及状态值

python
def get_decoder(self):
"""
定义解码器结构
:return:
"""
# 定义decoder结构,指定隐层值的形状大小,return_state=True
self.decoder = LSTM(self.model_param["n_y"], return_state=True)
return None
- 输出层
- from keras.layer import Dense
- 指定一个普通的全连接层,并且可以指定激活函数
- Dense(units, activation=None)
- 神经元个数(输出大小)
- activation=None:激活函数

python
def get_output_layer(self):
"""
定义输出层
:return: output_layer
"""
# 对decoder输出进行softmax,输出向量大小为y_vocab大小
self.output_layer = Dense(self.model_param["y_vocab_size"], activation=softmax)
return None
computer_one_attention函数实现:attention层结构
- 1、定义结构
- 2、实现输入输出结果

- from keras.layers import RepeatVector, Concatenate, Dot, Activation

python
def get_attention(self):
"""
定义Attention的结构
:return: attention结构
"""
# 定义RepeatVector复制成多个维度
repeator = RepeatVector(self.model_param["Tx"])
# 进行矩阵拼接
concatenator = Concatenate(axis=-1)
# 进行全连接层10个神经元
densor1 = Dense(10, activation="tanh", name='Dense1')
# 接着relu函数
densor2 = Dense(1, activation="relu", name='Dense2')
# softmax
activator = Activation(softmax,
name='attention_weights')
# context计算
dotor = Dot(axes=1)
# 将结构存储在attention当中
self.attention = {
"repeator": repeator,
"concatenator": concatenator,
"densor1": densor1,
"densor2": densor2,
"activator": activator,
"dotor": dotor
}
return None
- Attention输入输出逻辑
- 使用Attention结构去实现输入到输出的逻辑
python
def computer_one_attention(self, a, s_prev):
"""
利用定义好的attention结构计算中的alpha系数与a对应输出
:param a:隐层状态值 (m, Tx, 2*n_a)
:param s_prev: LSTM的初始隐层状态值, 形状(sample, n_s)
:return: context
"""
# 使用repeator扩大数据s_prev的维度为(sample, Tx, n_y),这样可以与a进行合并
s_prev = self.attention["repeator"](s_prev)
# 将a和s_prev 按照最后一个维度进行合并计算
concat = self.attention["concatenator"]([a, s_prev])
# 使用densor1全连接层网络计算出e
e = self.attention["densor1"](concat)
# 使用densor2增加relu激活函数计算
energies = self.attention["densor2"](e)
# 使用"activator"的softmax函数计算权重"alphas"
# 这样一个attention的系数计算完成
alphas = self.attention["activator"](energies)
# 使用dotor,矩阵乘法,将 "alphas" and "a" 去计算context/c
context = self.attention["dotor"]([alphas, a])
return context
3.6.测试模型
训练:
python
def train(self, X_onehot, Y_onehot):
"""
训练
:param X_onehot: 特征值的one_hot编码
:param Y_onehot: 目标值的one_hot编码
:return:
"""
# 利用网络结构定义好模型输入输出
model = self.model()
opt = Adam(lr=0.005, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.001)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
s0 = np.zeros((10000, self.model_param["n_y"]))
c0 = np.zeros((10000, self.model_param["n_y"]))
outputs = list(Y_onehot.swapaxes(0, 1))
# 输入x,以及decoder中LSTM的两个初始化值
model.fit([X_onehot, s0, c0], outputs, epochs=1, batch_size=100)
return None
测试逻辑
- model.load_weights(path):加载模型
python
def test(self):
"""
测试
:return:
"""
model = self.model()
model.load_weights("./models/model.h5")
example = '1 March 2001'
source = string_to_int(example, self.model_param["Tx"], self.model_param["x_vocab"])
source = np.expand_dims(np.array(list(map(lambda x:
to_categorical(x, num_classes=self.model_param["x_vocab_size"]),
source))), axis=0)
s0 = np.zeros((10000, self.model_param["n_y"]))
c0 = np.zeros((10000, self.model_param["n_y"]))
prediction = model.predict([source, s0, c0])
prediction = np.argmax(prediction, axis=-1)
output = [dict(zip(self.model_param["y_vocab"].values(), self.model_param["y_vocab"].keys()))[int(i)] for i in prediction]
print("source:", example)
print("output:", ''.join(output))
return None


完整代码:
python
# 替换原来的导入方式
# 使用 tensorflow.keras 替代 keras 或 keras.src
from tensorflow.keras.layers import Input, Dense, RepeatVector, Concatenate, Dot, Activation, LSTM, Bidirectional
from tensorflow.keras.optimizers import Adam, SGD, RMSprop
from tensorflow.keras.models import Model
from tensorflow.keras.utils import to_categorical
from nmt_utils import *
import numpy as np
class Seq2seq(object):
"""
序列模型去进行日期的翻译
"""
def __init__(self, Tx=30, Ty=10, n_x=32, n_y=64):
self.model_param = {
"Tx": Tx, # 定义encoder序列最大长度
"Ty": Ty, # decoder序列最大长度
"n_x": n_x, # encoder的隐层输出值大小
"n_y": n_y # decoder的隐层输出值大小和cell输出值大小
}
def load_data(self, m):
"""
指定获取m条数据
:param m: 数据的总样本数
:return:
dataset:[('9 may 1998', '1998-05-09'), ('10.09.70', '1970-09-10')]
x_vocab:翻译前的格式对应数字{' ': 0, '.': 1, '/': 2, '0': 3, '1': 4, '2': 5, '3': 6, '4': 7,....}
y_vocab:翻译后的格式对应数字{'-': 0, '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6, '6': 7, '7': 8, '8': 9, '9': 10}
"""
# 获取3个值:数据集,特征词的字典映射,目标词字典映射
dataset, x_vocab, y_vocab = load_dataset(m)
# 获取处理好的数据:特征x以及目标y的one_hot编码
X, Y, X_onehot, Y_onehot = preprocess_data(dataset, x_vocab, y_vocab, self.model_param["Tx"], self.model_param["Ty"])
print("整个数据集特征值的形状:", X_onehot.shape)
print("整个数据集目标值的形状:", Y_onehot.shape)
# 打印数据集
print("查看第一条数据集格式:特征值:%s, 目标值: %s" % (dataset[0][0], dataset[0][1]))
print(X[0], Y[0])
print("one_hot编码:", X_onehot[0], Y_onehot[0])
# 添加特征词个不重复个数以及目标词的不重复个数
self.model_param["x_vocab"] = x_vocab
self.model_param["y_vocab"] = y_vocab
self.model_param["x_vocab_size"] = len(x_vocab)
self.model_param["y_vocab_size"] = len(y_vocab)
return X_onehot, Y_onehot
def get_encoder(self):
"""
获取encoder属性
:return: None
"""
self.encoder = Bidirectional(LSTM(self.model_param["n_x"], return_sequences=True, name="bidirectional_1"), merge_mode='concat')
return None
def get_decoder(self):
"""
获取deocder属性
:return: None
"""
self.decoder = LSTM(self.model_param["n_y"], return_state=True)
return None
def get_output_layer(self):
"""
获取输出层
:return: None
"""
self.output_layer = Dense(self.model_param["y_vocab_size"], activation=softmax)
return None
def get_attention(self):
"""
实现attention的结构属性
:return: None
"""
# 1、定义Repeat函数
repeator = RepeatVector(self.model_param["Tx"])
# 2、定义Concat函数
concatenator = Concatenate(axis=-1)
# 3、定义Dense
densor1 = Dense(10, activation="tanh", name="Dense1")
densor2 = Dense(1, activation="relu", name='Dense2')
# 4、Activatation
activator = Activation(softmax,
name='attention_weights')
# 5、Dot相当于npt.dot
dotor = Dot(axes=1)
# 将结构存储在attention当中
self.attention = {
"repeator": repeator,
"concatenator": concatenator,
"densor1": densor1,
"densor2": densor2,
"activator": activator,
"dotor": dotor
}
return None
def init_seq2seq(self):
"""
初始化网络结构
:return:
"""
# 添加encoder属性
self.get_encoder()
# 添加decoder属性
self.get_decoder()
# 添加attention属性
self.get_attention()
# 添加get_output_layer属性
self.get_output_layer()
return None
def computer_one_attention(self, a, s_prev):
"""
逻辑函数,计算context
:param a: encoder的所有输出,t'时刻,a=t=1,2,3,4,......Tx
:param s_prev: decoder的输出,t'-1
:return: context
"""
# - 1、扩展s_prev的维度到encoder的所有时刻,编程Tx份
s_prev = self.attention["repeator"](s_prev)
# - 2、进行s_prev和a进行拼接
concat = self.attention["concatenator"]([a, s_prev])
# - 3、进行全连接计算得到e, 经过激活函数relu计算出e'
e = self.attention["densor1"](concat)
en = self.attention["densor2"](e)
# - 4、e'进过softmax计算,得到系数,每个attention 有Tx个alphas参数
alphas = self.attention["activator"](en)
# - 5、系数与a进行计算得到context
context = self.attention["dotor"]([alphas, a])
return context
def model(self):
"""
定义整个网络模型
:return: keras 当中的model类型
"""
# 1、定义encoder的输入X (30, 37)
X = Input(shape=(self.model_param["Tx"], self.model_param["x_vocab_size"]), name="X")
# 定义一个初始输入的s0, 64大小
s0 = Input(shape=(self.model_param["n_y"],), name="s0")
c0 = Input(shape=(self.model_param["n_y"],), name="c0")
s = s0
c = c0
# 定义一个装有输出的列表
outputs = []
# 2、输入到encoder当中,得到a
a = self.encoder(X)
# 3、计算输出结果,循环deocder当中t'个时刻,计算每个LSTM的输出结果
for t in range(self.model_param["Ty"]):
# (1)循环计算每一个时刻的context
context = self.computer_one_attention(a, s)
# (2)输入s0,c0,context到某个时刻decoder得到下次的输出s, c
# 因为是LSTM结构,所以有两个隐层状态,其中s可以用作输出
s, _, c = self.decoder(context, initial_state=[s, c])
# (3)s输出到最后一层softmax得到预测结果
out = self.output_layer(s)
outputs.append(out)
# 输入输出定义好了
model = Model(inputs=(X, s0, c0), outputs=outputs)
return model
def train(self, X_onehot, Y_onehot):
"""
训练
:param X_onehot: 特征值的one_hot编码
:param Y_onehot: 目标值的one_hot编码
:return:
"""
# 利用网络结构定义好模型输入输出
model = self.model()
opt = Adam(lr=0.005, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.001)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
s0 = np.zeros((10000, self.model_param["n_y"]))
c0 = np.zeros((10000, self.model_param["n_y"]))
outputs = list(Y_onehot.swapaxes(0, 1))
# 输入x,以及decoder中LSTM的两个初始化值
model.fit([X_onehot, s0, c0], outputs, epochs=1, batch_size=100)
return None
def test(self):
model = self.model()
model.load_weights("./models/model.h5")
example = '1 March 2001'
source = string_to_int(example, self.model_param["Tx"], self.model_param["x_vocab"])
source = np.expand_dims(np.array(list(map(lambda x:
to_categorical(x, num_classes=self.model_param["x_vocab_size"]),
source))), axis=0)
# 修改这里:s0和c0的形状应与source一致(1个样本)
s0 = np.zeros((1, self.model_param["n_y"])) # 原来是(10000, n_y)
c0 = np.zeros((1, self.model_param["n_y"])) # 原来是(10000, n_y)
prediction = model.predict([source, s0, c0])
prediction = np.argmax(prediction, axis=-1)
output = [dict(zip(self.model_param["y_vocab"].values(),
self.model_param["y_vocab"].keys()))[int(i)] for i in prediction]
print("source:", example)
print("output:", ''.join(output))
if __name__ == '__main__':
s2s = Seq2seq()
X_onehot, Y_onehot = s2s.load_data(10000)
s2s.init_seq2seq()
# s2s.train(X_onehot, Y_onehot)
s2s.test()
nmt_utils.py:
python
import numpy as np
from faker import Faker # 用于生成虚假数据
import random
from tqdm import tqdm # 用于显示进度条
from babel.dates import format_date # 用于格式化日期
from tensorflow.keras.utils import to_categorical # 用于one-hot编码
import tensorflow.keras.backend as K # Keras后端引擎
import matplotlib.pyplot as plt # 绘图库
# 设置随机种子以保证结果可复现
from faker import Faker
import random
# 设置全局随机种子(推荐方式)
Faker.seed(12345) # ✅ 使用类方法设置 Faker 的种子
random.seed(12345) # 设置 Python random 模块的种子
fake = Faker() # 创建实例
# 定义日期格式列表
FORMATS = ['short',
'medium',
'long',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'full',
'd MMM YYY',
'd MMMM YYY',
'dd MMM YYY',
'd MMM, YYY',
'd MMMM, YYY',
'dd, MMM YYY',
'd MM YY',
'd MMMM YYY',
'MMMM d YYY',
'MMMM d, YYY',
'dd.MM.YY']
# 设置语言环境(可修改为其他语言)
LOCALES = ['en_US']#这是us英文,可修改为其他语言
def load_date():
"""
生成虚假日期数据
返回: 元组包含(人类可读日期字符串, 机器可读日期字符串, 日期对象)
"""
dt = fake.date_object() # 生成随机日期对象,
# 输出: datetime.date(2023, 5, 15)
try:
# 格式化人类可读日期
human_readable = format_date(dt, format=random.choice(FORMATS), locale='en_US')# 格式化日期,locale为'en_US',可修改为其他语言,输出: 'May 15, 2023'
human_readable = human_readable.lower() # 转为小写,输出: 'may 15, 2023'
human_readable = human_readable.replace(',', '') # 移除逗号,输出:'may 15 2023'
machine_readable = dt.isoformat() # 机器可读格式,输出: '2023-05-15'
except AttributeError as e:
return None, None, None
return human_readable, machine_readable, dt
def load_dataset(m):
"""
加载包含m个样本的数据集并构建词汇表
:m: 要生成的样本数量
返回:
dataset -- 包含(人类可读日期字符串, 机器可读日期字符串)对的列表
human -- 人类可读词汇表字典
machine -- 机器可读词汇表字典
"""
human_vocab = set() # 人类可读日期字符集
machine_vocab = set() # 机器可读日期字符集
dataset = [] # 数据集
Tx = 30 # 输入序列长度
# 生成m个样本
for i in tqdm(range(m)):
h, m, _ = load_date()
if h is not None:
dataset.append((h, m)) # 添加(人类可读, 机器可读)对
human_vocab.update(tuple(h)) # 更新人类可读字符集
machine_vocab.update(tuple(m)) # 更新机器可读字符集
# 构建人类可读词汇表字典(添加未知词和填充词标记)
human = dict(zip(sorted(human_vocab) + ['<unk>', '<pad>'],
list(range(len(human_vocab) + 2))))
# 构建机器可读词汇表字典和反向字典
inv_machine = dict(enumerate(sorted(machine_vocab)))
machine = {v: k for k, v in inv_machine.items()}
return dataset, human, machine
def preprocess_data(dataset, human_vocab, machine_vocab, Tx, Ty):
"""
预处理数据:将字符串转换为整数序列并进行one-hot编码
"""
X, Y = zip(*dataset) # 解压数据集
# 将字符串转换为整数序列
X = np.array([string_to_int(i, Tx, human_vocab) for i in X])
Y = [string_to_int(t, Ty, machine_vocab) for t in Y]
# 进行one-hot编码
Xoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(human_vocab)), X)))
Yoh = np.array(list(map(lambda x: to_categorical(x, num_classes=len(machine_vocab)), Y)))
return X, np.array(Y), Xoh, Yoh
def string_to_int(string, length, vocab):
"""
将字符串转换为整数序列表示
参数:
string -- 输入字符串,如 'Wed 10 Jul 2007'
length -- 时间步长,决定输出是填充还是截断
vocab -- 词汇表字典
返回:
rep -- 整数列表(或'<unk>'),表示字符串字符在词汇表中的位置
"""
# 标准化处理:转为小写并移除逗号
string = string.lower()
string = string.replace(',', '')
# 如果字符串过长则截断
if len(string) > length:
string = string[:length]
# 将每个字符映射到词汇表中的索引,未知字符用'<unk>'表示
rep = list(map(lambda x: vocab.get(x, '<unk>'), string))
# 如果字符串过短则填充
if len(string) < length:
rep += [vocab['<pad>']] * (length - len(string))
return rep
def softmax(x, axis=1):
"""
Softmax激活函数
参数:
x: 张量
axis: 应用softmax归一化的轴
返回:
softmax变换后的张量
异常:
当输入是一维张量时抛出ValueError
"""
ndim = K.ndim(x)
if ndim == 2:
return K.softmax(x)
elif ndim > 2:
e = K.exp(x - K.max(x, axis=axis, keepdims=True))
s = K.sum(e, axis=axis, keepdims=True)
return e / s
else:
raise ValueError('Cannot apply softmax to a tensor that is 1D')