【深度学习:进阶篇】--4.3.seq2seq与Attention机制

学习目标

  • 目标
    • 掌握seq2seq模型特点
    • 掌握集束搜索方式
    • 掌握BLEU评估方法
    • 掌握Attention机制
  • 应用
    • 应用Keras实现seq2seq对日期格式的翻译

目录

学习目标

1.seq2seq

1.1.定义

1.2.条件语言模型理解

1.3.应用场景

2.注意力机制

2.1.长句子问题

2.2.定义

2.3.公式

3.机器翻译案例

3.1.环境配置

3.2.代码分析

3.3.加载数据

3.4.定义网络

3.5.定义模型

3.6.测试模型


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 的核心思想​

  1. ​N-gram 匹配​ :计算机器翻译(候选文本)和参考翻译之间的 ​n-gram(连续词序列)​ 重叠程度。
    • 例如,1-gram(单个词)、2-gram(双词组合)等。
  2. ​精度(Precision)​:统计候选翻译中有多少 n-gram 出现在参考翻译中,并做标准化(避免长句惩罚)。
  3. ​短句惩罚(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​​都是网络学习的参数

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.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)
    • 步骤 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).
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

完整代码:

RNN.py

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')
相关推荐
聚铭网络2 分钟前
案例精选 | 某省级税务局AI大数据日志审计中台应用实践
大数据·人工智能·web安全
涛神-DevExpress资深开发者43 分钟前
DevExpress V25.1 版本更新,开启控件AI新时代
人工智能·devexpress·v25.1·ai智能控件
Jamie201901061 小时前
健康孪生智能体使用起来复杂吗?医者AI技术核心与用户体验
人工智能
GLAB-Mary1 小时前
AI会取代网络工程师吗?理解AI在网络安全中的角色
网络·人工智能·web安全
道可云1 小时前
道可云人工智能每日资讯|浦东启动人工智能创新应用竞赛
人工智能·百度·ar·xr·deepseek
kyle~1 小时前
目标检测在国防和政府的应用实例
人工智能·目标检测·计算机视觉
兮℡檬,1 小时前
torchvision中的数据使用
人工智能
Qdgr_2 小时前
价值实证:数字化转型标杆案例深度解析
大数据·数据库·人工智能
c++服务器开发2 小时前
一文详解Character AI:实用指南+ ChatGPT、Gemini对比分析
人工智能·chatgpt
hanniuniu132 小时前
AI时代API挑战加剧,API安全厂商F5护航企业数字未来
人工智能·安全