深度学习中注意力机制介绍及seq2seq案例

news2024/12/26 23:12:42

一. 注意力机制介绍

普通机器翻译

图中表示的是一个中文到英文的翻译:欢迎 来 北京 → welcome to BeiJing。编码器首先处理中文输入"欢迎 来 北京",通过GRU模型获得每个时间步的输出张量,最后将它们拼接(按位相加)成一个中间语义张量c, 或者C=hn;接着解码器将使用这个中间语义张量c以及每一个时间步的隐层张量, 逐个生成对应的翻译语言

  • 问题1:如果翻译的句子很长很复杂,比如直接一篇文章输进去,模型的计算量很大,并且模型的准确率下降严重。

  • 问题2:在翻译时,可能在不同的语境下,同一个词具有不同的含义,但是网络对这些词向量并没有区分度,没有考虑词与词之间的相关性,导致翻译效果比较差。

注意力机制

让机器注意到每个词向量之间的相关性,有侧重地进行翻译,模拟人类理解的过程。

注意力机制实现原理

原理

通俗来讲就是对于模型的每一个输入项,可能是图片中的不同部分,或者是语句中的某个单词分配一个权重,这个权重的大小就代表了我们希望模型对该部分一个关注程度。这样一来,通过权重大小来模拟人在处理信息的注意力的侧重,有效的提高了模型的性能,并且一定程度上降低了计算量。

分类

深度学习中的注意力机制通常可分为三类: 软注意(全局注意)、硬注意(局部注意)和自注意(内注意)

软注意力机制

软注意机制(Soft/Global Attention: 对每个输入项的分配的权重为0-1之间,也就是某些部分关注的多一点,某些部分关注的少一点,因为对大部分信息都有考虑,但考虑程度不一样,所以相对来说计算量比较大

硬注意力机制

硬注意机制(Hard/Local Attention,[了解即可]): 对每个输入项分配的权重非0即1,和软注意不同,硬注意机制只考虑那部分需要关注,哪部分不关注,也就是直接舍弃掉一些不相关项。优势在于可以减少一定的时间和计算成本,但有可能丢失掉一些本应该注意的信息

自注意力机制

自注意力机制( Self/Intra Attention): 对每个输入项分配的权重取决于输入项之间的相互作用,即通过输入项内部的"表决"来决定应该关注哪些输入项。和前两种相比,在处理很长的输入时,具有并行计算的优势

soft_attention

注意力的作用

比如机器翻译任务,输入source为:Tom chase Jerry,输出target为:“汤姆”,“追逐”,“杰瑞”。在翻译“Jerry”这个中文单词的时候,普通Encoder-Decoder框架中,source里的每个单词对翻译目标单词“杰瑞”贡献是相同的,很明显这里不太合理,显然“Jerry”对于翻译成“杰瑞”更重要。

如果引入Attention模型,在生成“杰瑞”的时候,应该体现出英文单词对于翻译当前中文单词不同的影响程度,比如给出类似下面一个概率分布值:(Tom,0.3)(Chase,0.2) (Jerry,0.5).每个英文单词的概率代表了翻译当前单词“杰瑞”时,注意力分配模型分配给不同英文单词的注意力大小。

基于上述例子所示, 对于target中任意一个单词都应该有对应的source中的单词的注意力分配概率.而且,由于注意力模型的加入,原来在生成target单词时候的中间语义C就不再是固定的,而是会根据注意力概率变化的C

中间语义C

CTom=g(0.6∗f2(Tom),0.2∗f2(Chase),0.2∗f2(Jerry))

CChase=g(0.2∗f2(Tom),0.7∗f2(Chase),0.1∗f2(Jerry))

CJerry=g(0.3∗f2(Tom),0.2∗f2(Chase),0.5∗f2(Jerry))

生成目标单词

y1=f1(C1)

y2=f1(C2,y1)

y3=f1(C3,y1,y2)

注意力概率分布计算

上图中h_i表示Source中单词j对应的隐层节点状态h_j,H_i表示Target中单词i的隐层节点状态,注意力计算的是Target中单词i对Source中每个单词对齐可能性, Q*K^T,即F(h_j,H_i-1),而函数F可以用不同的方法,然后函数F的输出经过softmax进行归一化就得到了注意力分配概率分布。

上面就是经典的Soft Attention模型的基本思想,区别只是函数F会有所不同。

本质思想

其实Attention机制可以看作,Target中每个单词是对Source每个单词的加权求和,而权重是Source中每个单词对Target中每个单词的重要程度。因此,Attention的本质思想会表示成下图:

将Source中的构成元素看作是一系列的数据对,给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,即权重系数;然后对Value进行加权求和,并得到最终的Attention数值。将本质思想表示成公式如下:

深度学习中的注意力机制中提到:Source 中的 Key 和 Value 合二为一,指向的是同一个东西,也即输入句子中每个单词对应的语义编码,所以可能不容易看出这种能够体现本质思想的结构。因此,Attention计算转换为下面3个阶段。

输入由三部分构成:Query、Key和Value。其中,(Key, Value)是具有相互关联的KV对,Query是输入的“问题”,Attention可以将Query转化为与Query最相关的向量表示。

第一步:Query和Key进行相似度计算,得到Attention Score;

第二步:对Attention Score进行Softmax归一化,得到权值矩阵;

第三步:权重矩阵与Value进行加权求和计算。

hard_attention

根据注意力分布选择输入向量中的一个作为输出。这里有两种选择方式:

  1. 选择注意力分布中,分数最大的那一项对应的输入向量作为Attention机制的输出。

  2. 根据注意力分布进行随机采样,采样结果作为Attention机制的输出。

硬性注意力通过以上两种方式选择Attention的输出,这会使得最终的损失函数与注意力分布之间的函数关系不可导,导致无法使用反向传播算法训练模型,硬性注意力通常需要使用强化学习来进行训练。因此,一般深度学习算法会使用软性注意力的方式进行计算,

self_attention

Self Attention,指的是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力机制。当然,具体的计算过程仍然是一样的,只是计算对象发生了变化而已。(q=k=v)

将输入信息embedding后乘上不同的权重矩阵得到QKV, 即QKV不相等, 且Q也是来自于source

二. 注意力机制介绍2

seq2seq

注意力机制规则

它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时(来源相同并不是值相等), 称作自注意力计算规则;当Q、K、V不相等时称为一般注意力计算规则

版本1

  • 查询张量Q: 解码器每一步输出或者是当前输入的x

  • 键张量K: 编码部分每个时间步的结果组合而成

  • 值张量V:编码部分每个时间步的结果组合而成

版本2

  • 查询张量Q: 解码器每一步的输出(s1通过线性层后面的结果, 再次转为词向量)或者是当前输入的x

  • 键张量K: 解码器上一步的隐藏层输出

  • 值张量V:编码部分每个时间步输出结果组合而成

版本解码对比

  1. 采用自回归机制,比如:输入“go”来预测“welcome”,输入“welcome”来预测"to",输入“to”来预测“Beijing”。在输入“welcome”来预测"to"解码中,可使用注意力机制

  2. 查询张量Q:一般可以是“welcome”词嵌入层以后的结果,查询张量Q为生成谁就是谁的查询张量(比如这里为了生成“to”,则查询张量就是“to”的查询张量,请仔细体会这一点)

  3. 键向量K:一般可以是上一个时间步的隐藏层输出

  4. 值向量V:一般可以是编码部分每个时间步的结果组合而成

  5. 查询张量Q来生成“to”,去检索“to”单词和“欢迎”、“来”、“北京”三个单词的权重分布,注意力结果表示(用权重分布 乘以内容V)

注意力机制计算规则

版本1

将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.

Attention(Q,K,V)=Softmax(Q⋅K^T/√dk)⋅V

★版本2

将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.

Attention(Q,K,V)=Softmax(Linear([Q,K]))⋅V

bmm计算

import torch
​
​
# 1. torch.bmm(), 不支持广播
# 如果参数1形状是(b × n × m),
# 参数2形状是(b × m × p),
# 则输出为(b × n × p)
input = torch.randn(10, 3, 4)
mat2 = torch.randn(10, 4, 5)
res = torch.bmm(input, mat2)
print(res.size())   # torch.Size([10, 3, 5])
​
​
# 2. torch.mm(), 不支持广播
res = torch.mm(input[0], mat2[0])
print(res.size())   # torch.Size([3, 5])
​
​
# 3. torch.matmul(), 支持广播, input(10, 3, 4) mat2[0].shape=(4, 5)或者=(1, 4, 5)
mat3 = torch.randn(1, 4, 5)
res1 = torch.matmul(input, mat2[0])
res2 = torch.matmul(input, mat3)
print(res1.size())  # torch.Size([10, 3, 5])
print(res2.size())  # torch.Size([10, 3, 5])
​
​
# 4. * or torch.mul()哈达玛积, 支持广播, input(10, 3, 4) mat2[0].shape=(4, 5)或者=(1, 4, 5)
a = torch.rand(3, 4)
b = torch.rand(3, 4)
print(a * b)
print(torch.mul(a, b))
print(torch.mul(a, b).shape)

注意力机制定义

注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.

  1. rnn等循环神经网络,随着时间步的增长,前面单词的特征会遗忘,造成对句子特征提取不充分

  2. rnn等循环神经网络是一个时间步一个时间步的提取序列特征,效率低下

  3. 研究者开始思考,能不能对32个单词(序列)同时提取事物特征,而且还是并行的,所以引入注意力机制!

注意力机制作用

  • 解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量(中间语义C), 无法存储过多信息的情况.

  • 编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).

注意力机制实现步骤

步骤

  • 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.

  • 第二步: 根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.

  • 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.

代码演示

# 任务描述:
# 有QKV:v是内容比如32个单词,每个单词64个特征,k是32个单词的索引,q是查询张量
# 我们的任务:输入查询张量q,通过注意力机制来计算如下信息:
# 1、查询张量q的注意力权重分布:查询张量q和其他32个单词相关性(相识度)
# 2、查询张量q的结果表示:有一个普通的q升级成一个更强大q;用q和v做bmm运算
# 3 注意:查询张量q查询的目标是谁,就是谁的查询张量。
#   eg:比如查询张量q是来查询单词"我",则q就是我的查询张量
​
import torch
import torch.nn as nn
import torch.nn.functional as F
​
​
# MyAtt类实现思路分析
# 1 init函数 (self, query_size, key_size, value_size1, value_size2, output_size)
# 准备2个线性层 注意力权重分布self.attn 注意力结果表示按照指定维度进行输出层 self.attn_combine
# 2 forward(self, Q, K, V):
# 求查询张量q的注意力权重分布, attn_weights[1,32]
# 求查询张量q的注意力结果表示 bmm运算, attn_applied[1,1,64]
# q 与 attn_applied 融合,再按照指定维度输出 output[1,1,32]
# 返回注意力结果表示output:[1,1,32], 注意力权重分布attn_weights:[1,32]
​
class MyAtt(nn.Module):
    #                   32          32          32              64      32
    def __init__(self, query_size, key_size, value_size1, value_size2, output_size):
        super(MyAtt, self).__init__()
        self.query_size = query_size
        self.key_size = key_size
        self.value_size1 = value_size1
        self.value_size2 = value_size2
        self.output_size = output_size
​
        # 线性层1 注意力权重分布
        self.attn = nn.Linear(self.query_size + self.key_size, self.value_size1)
​
        # 线性层2 注意力结果表示按照指定维度输出层 self.attn_combine
        self.attn_combine = nn.Linear(self.query_size + self.value_size2, output_size)
​
    def forward(self, Q, K, V):
        # 1 求查询张量q的注意力权重分布, attn_weights[1,32]
        # [1,1,32],[1,1,32]--> [1,32],[1,32]->[1,64]
        # [1,64] --> [1,32]
        # tmp1 = torch.cat( (Q[0], K[0]), dim=1)
        # tmp2 = self.attn(tmp1)
        # tmp3 = F.softmax(tmp2, dim=1)
        attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]), dim=-1)), dim=-1)
​
        # 2 求查询张量q的结果表示 bmm运算, attn_applied[1,1,64]
        # [1,1,32] * [1,32,64] ---> [1,1,64]
        attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
​
        # 3 q 与 attn_applied 融合,再按照指定维度输出 output[1,1,64]
        # 3-1 q与结果表示拼接 [1,32],[1,64] ---> [1,96]
        output = torch.cat((Q[0], attn_applied[0]), dim=-1)
        # 3-2 shape [1,96] ---> [1,32]
        output = self.attn_combine(output).unsqueeze(0)
​
        # 4 返回注意力结果表示output:[1,1,32], 注意力权重分布attn_weights:[1,32]
        return output, attn_weights
​
​
if __name__ == '__main__':
    query_size = 32
    key_size = 32
    value_size1 = 32  # 32个单词
    value_size2 = 64  # 64个特征
    output_size = 32
​
    Q = torch.randn(1, 1, 32)
    K = torch.randn(1, 1, 32)
    V = torch.randn(1, 32, 64)
    # V = torch.randn(1, value_size1, value_size2)
​
    # 1 实例化注意力类 对象
    myattobj = MyAtt(query_size, key_size, value_size1, value_size2, output_size)
​
    # 2 把QKV数据扔给注意机制,求查询张量q的注意力结果表示、注意力权重分布
    output, attn_weights = myattobj(Q, K, V)
    print('查询张量q的注意力结果表示output--->', output.shape, output)
    print('查询张量q的注意力权重分布attn_weights--->', attn_weights.shape, attn_weights)
​

三. 英译法案例

数据预处理

SOS表示开始

EOS表示结束

UNK表示未登录词, 即训练数据中没有的词, 用于解决oov(out of vocab)问题

导包及数据清洗

# 用于正则表达式
import re
# 用于构建网络结构和函数的torch工具包
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
# torch中预定义的优化方法工具包
import torch.optim as optim
import time
# 用于随机生成数据
import random
import matplotlib.pyplot as plt
​
# 定义设备选择
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
​
# 定义开始标志
SOS_TOKEN = 0
# 定义结束标志
EOS_TOKEN = 1
# 最大句子长度
MAX_LENGTH = 10
# data路径
data_path = "./data/eng-fra-v2.txt"
​
​
# 定义数据清洗函数
def normalizeString(s):
    S = s.lower().strip()
    S = re.sub(r"([.!?])", r" \1", S)   # 符号也是token, 不能和单词混在一起
    S = re.sub(r"[^a-zA-Z.!?]+", r" ", S)
    return S

构建文本词典

# 2. 读取文档数据
def my_getdata():
    # 1. 读取数据
    with open(data_path, 'r', encoding='utf-8') as fr:
        content = fr.read()
    lines = content.strip().split('\n')
​
    # 2. 构建语言对pair
    my_pair = [[normalizeString(s) for s in l.split('\t')] for l in lines]
​
    # 3. 构建词典 word2idx, 用于编码
    # 3.1 初始化eng词典 fra词典
    # UNK 表示未登录词(预测时不在词表内的词) 用来解决oov问题
    english_word2idx = {'SOS': 0, 'EOS': 1, 'UNK': 2}
    french_word2idx = {'SOS': 0, 'EOS': 1, 'UNK': 2}
​
    # 3.2 遍历my_pair
    for pair in my_pair:
        for word in pair[0].split(' '):
            if word not in english_word2idx:
                english_word2idx[word] = len(english_word2idx)
​
        for word in pair[1].split(' '):
            if word not in french_word2idx:
                french_word2idx[word] = len(french_word2idx)
​
    # 4. 构建idx2word, 用于解码
    english_index2word = {v: k for k, v in english_word2idx.items()}
    french_index2word = {v: k for k, v in french_word2idx.items()}
​
    # 5. 返回数据
    return english_word2idx, english_index2word, len(english_word2idx), french_word2idx, french_index2word, len(
        french_word2idx), my_pair
​
​
english_word2idx, english_index2word, english_word2idx_n, french_word2idx, french_index2word, french_word2idx_n, my_pair = my_getdata()
​

构建数据源对象

# 3. 构建数据集
class MyDataset(Dataset):
    def __init__(self, my_pair):
        self.my_pair = my_pair
        self.n_samples = len(my_pair)
​
    def __len__(self):
        return self.n_samples
​
    def __getitem__(self, index):   # __getitem__方法会调用全局变量
        # 修正索引
        index = min(max(0, index), self.n_samples - 1)
​
        # 在样本数据中获取第idx条数据数据的x和y
        x = self.my_pair[index][0]
        y = self.my_pair[index][1]
​
        # 将词转为词表中索引, 并转化为张量
        # (虽然english_word2ind并没有显示传入, 但是在主函数中调用my_getdata方法并返回了词典, 自定义的数据集类内可以访问全局变量)
        x = [english_word2idx.get(word, english_word2idx_n['UNK']) for word in x.split(" ")]
        # x可以不加EOS_TOKEN标志
        # x.append(EOS_TOKEN)
        tensor_x = torch.tensor(x, dtype=torch.long, device=device)
        y = [french_word2idx.get(word, english_word2idx_n['UNK']) for word in y.split(" ")]
        y.append(EOS_TOKEN)
        tensor_y = torch.tensor(y, dtype=torch.long, device=device)
​
        return tensor_x, tensor_y

构建数据加载器

# 4. 构建数据加载器
def get_dataloader():
    my_dataset = MyDataset(my_pair)
    my_dataloader = DataLoader(dataset=my_dataset, batch_size=1, shuffle=True)
    return my_dataloader

GRU编码器和解码器

无注意力编码器

# 5. 构建编码器GRU
class EncoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size):
        super(EncoderGRU, self).__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
​
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)
​
    def forward(self, input):
        out = self.embed(input)
        out, hn = self.gru(out)
        return out, hn

自注意力编码器

# _5. 构建编码器GRU, 自注意力版本
class SelfAttenEncoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size):
        super(SelfAttenEncoderGRU, self).__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
​
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)
​
        self.l1 = nn.Linear(self.hidden_size, self.hidden_size)
        self.l2 = nn.Linear(self.hidden_size, self.hidden_size)
        self.l3 = nn.Linear(self.hidden_size, self.hidden_size)
​
    def forward(self, input):
        out = self.embed(input)
​
        q = self.l1(out)
        k = self.l2(out)
        v = self.l3(out)
​
        out1 = torch.matmul(q, k.transpose(1, 2))
        out2 = out1 / self.hidden_size**0.5
        out3 = F.softmax(out2, dim=-1)
        out = torch.matmul(out3, v)
​
        out, hn = self.gru(out)
        return out, hn
    
# 6. 测试编码器GRU
def ceshi_encoder():
    mydataloader = get_dataloader()
    my_encoder = SelfAttenEncoderGRU(english_word2idx_n, 256).to(device)
    x, y = next(iter(mydataloader))
    out, hn = my_encoder(x)
    print(x.shape)
    print(y.shape)
    print(out.shape)
    print(hn.shape)
​

无注意力解码器

# 7. 构建解码器GRU, 无注意力机制
class DecoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size):
        super(DecoderGRU, self).__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size

        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)

        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)

        self.out = nn.Linear(self.hidden_size, self.vocab_size)

        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hn):   # input.shape(1, 1), 只有一个token
        output = self.embed(input)  # output.shape(1, 1, 256)
        output = F.relu(output)
        output, hn = self.gru(output, hn)
        output = self.out(output[0])    # 得到的output.shape(1, 4345)法语词表大小
        output = self.softmax(output)
        return output, hn
    
    
# 8. 测试逐token解码, 没有注意力机制
def ceshi_decoder():
    mydataloader = get_dataloader()
    my_encoder = SelfAttenEncoderGRU(english_word2idx_n, 256).to(device)
    my_decoder = DecoderGRU(french_word2idx_n, 256).to(device)
    x, y = next(iter(mydataloader))
    print(x.shape)
    print(y.shape)
    out, hn = my_encoder(x)

    # 逐token解码
    for i in range(y.shape[1]):     # y.shape=(1, sql_len)
        temp = y[0][i].view(1, -1)
        out, hn = my_decoder(temp, hn)
        print('out.shape: ', out.shape)

有注意力解码器

# 9. 构建解码器GRU, 有注意力机制
class AttnDecoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size, dropout_p=0.1, max_length=MAX_LENGTH):
        super(AttnDecoderGRU, self).__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
        self.dropout_p = dropout_p
        self.max_length = max_length

        # 词嵌入层
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)

        # 随机失活层
        self.dropout = nn.Dropout(self.dropout_p)

        # 注意力层, 两个线性层
        self.attn = nn.Linear(self.hidden_size * 2, self.max_length)  # 计算注意力权重
        self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)  # 计算注意力表示, 作为GRU输入

        # GRU层
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)

        # 输出层
        self.out = nn.Linear(self.hidden_size, self.vocab_size)

        # softmax层
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hn, encoder_outputs):
        # input.shape(1, 1)
        # hn.shape(1, 1, 256)
        # encoder_outputs.shape=(1, 10, 256)
        input_x = self.embed(input)
        input_x = self.dropout(input_x)

        # 拼接词嵌入后的x和hn, 计算注意力权重, attn_weight.shape=(1, 1, 10)
        attn_weight = F.softmax(self.attn(torch.cat((input_x[0], hn[0]), dim=-1)), dim=-1)

        # 计算注意力表示, attn_applied.shape=(1, 1, 256), 计算 hn和encoder_outputs的点积, 注意力权重乘以V
        attn_applied = torch.bmm(attn_weight.unsqueeze(0), encoder_outputs.unsqueeze(0))  # encoder_outputs(1, 10, 256)

        # 融合注意力表示和词嵌入后的x, combine_output.shape=(1, 1, 256)
        combine_output = self.attn_combine(torch.cat((input_x, attn_applied), dim=-1))

        output = F.relu(combine_output)

        output, hn = self.gru(output, hn)

        output = self.out(output[0])

        output = self.softmax(output)
        return output, hn, attn_weight

模型训练

单批次训练

# 10. 模型训练
# 超参数设置
teacher_forcing_ratio = 0.5


# 训练函数
def train_pre_model(x, y, my_encoder, my_attn_decoder, encoder_optimizer, decoder_optimizer, cross_entropy_loss):
    # 计算encoder
    encoder_output, encoder_hn = my_encoder(x)

    # encoder_output_c(中间语义张量C, 或者是V)长度
    encoder_output_c = torch.zeros(MAX_LENGTH, my_encoder.hidden_size, device=device)
    # 逐token复制encoder_output, x.shape(batch_size, seq_len, hidden_size)
    for i in range(x.shape[1]):
        encoder_output_c[i] = encoder_output[0][i]
    # 解码器第一个时间步的h0
    decoder_hn = encoder_hn
    # 解码器第一个时间步的输入y(1, 1)
    input_y = torch.tensor([[SOS_TOKEN]], dtype=torch.long, device=device)

    # 初始化当前样本的损失
    my_loss = 0
    # 初始化teacher_forcing
    #
    use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False

    # 开始训练
    if use_teacher_forcing:  # 小于指定的teacher_forcing
        for i in range(y.shape[1]):
            output_y, decoder_hn, _ = my_attn_decoder(input_y, decoder_hn, encoder_output_c)
            target_y = y[0][i].view(1)
            my_loss += cross_entropy_loss(output_y, target_y)
            # 用真实值作为下次输入
            input_y = y[0][i].view(1, -1)
    else:
        for i in range(y.shape[1]):
            output_y, decoder_hn, _ = my_attn_decoder(input_y, decoder_hn, encoder_output_c)
            target_y = y[0][i].view(1)
            my_loss += cross_entropy_loss(output_y, target_y)

            topv, topi = output_y.topk(1)

            # 设置结束条件
            if topi.squeeze().item() == EOS_TOKEN:
                break

            input_y = topi.detach()  # 不使用梯度

    # 反向传播
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()
    my_loss.backward()
    encoder_optimizer.step()
    decoder_optimizer.step()
    return my_loss.item() / y.shape[1]

所有数据训练-绘图

# 训练主函数
def train_seq2seq(epochs=2, lr=1e-4):
    # 1. 获取数据迭代器
    my_dataloader = get_dataloader()

    # 2. 实例化编码器解码器
    my_encoder = SelfAttenEncoderGRU(english_word2idx_n, 256).to(device)
    my_decoder = AttnDecoderGRU(french_word2idx_n, 256).to(device)

    # 3. 优化器, 损失函数
    encoder_optimizer = optim.Adam(my_encoder.parameters(), lr=lr)
    decoder_optimizer = optim.Adam(my_decoder.parameters(), lr=lr)
    cross_entropy_loss = nn.NLLLoss()
    total_iter = 0
    print_interval_total, plot_interval_total = 0.0, 0.0
    # 4. 绘图的loss列表
    plot_loss_list = []
    # 5. 开始训练, 外循环
    for epoch in range(1, 1 + epochs):
        start_time = time.time()
        # 5.1 内循环
        for i, (x, y) in enumerate(my_dataloader, start=1):
            x, y = x.to(device), y.to(device)
            my_loss = train_pre_model(x, y, my_encoder, my_decoder, encoder_optimizer, decoder_optimizer,
                                      cross_entropy_loss)
            plot_interval_total += my_loss
            print_interval_total += my_loss
            total_iter += 1
            if i % 100 == 0:
                plot_loss_list.append(plot_interval_total / total_iter)
                # plot_interval_total = 0
            if i % 1000 == 0:
                print_loss_avg = print_interval_total / total_iter
                use_time = time.time() - start_time
                print(f'epoch: {epoch}, iter: {i}, loss: {print_loss_avg:.5f}, time: {use_time:.3f}s')
                # print_interval_total = 0
        print('*' * 50)
        print('epoch: %d, total_loss: %.5f, time: %.3f' % (
            epoch, plot_interval_total / len(my_dataloader), time.time() - start_time))
        print('*' * 50)
        torch.save(my_encoder.state_dict(), 'model/encoder_model%d.pth' % epoch)
        torch.save(my_decoder.state_dict(), 'model/decoder_model%d.pth' % epoch)
    # 绘图
    plt.figure()
    plt.plot(plot_loss_list)
    plt.savefig('picture/loss.png')
    plt.show()

    return plot_loss_list

模型预测

# 11. 预测函数
def predict_seq2seq(x):
    tensor_x = [english_word2idx.get(word, english_word2idx['UNK']) for word in x.split(" ")]
    tensor_x = [tensor_x]
    tensor_x = torch.tensor(tensor_x, dtype=torch.long, device=device)
    with torch.no_grad():
        my_encoder = SelfAttenEncoderGRU(english_word2idx_n, 256).to(device)
        my_decoder = AttnDecoderGRU(french_word2idx_n, 256).to(device)
        my_encoder.load_state_dict(torch.load('model/encoder_model3.pth', weights_only=True))
        my_decoder.load_state_dict(torch.load('model/decoder_model3.pth', weights_only=True))

        encoder_output, encoder_hn = my_encoder(tensor_x)
        # 获取中间语义张量C
        # encoder_output_c(中间语义张量C, 或者是V)长度
        encoder_output_c = torch.zeros(MAX_LENGTH, my_encoder.hidden_size, device=device)
        # 逐token复制encoder_output, tensor_x.shape(batch_size, seq_len, hidden_size)
        for i in range(tensor_x.shape[1]):
            encoder_output_c[i] = encoder_output[0][i]

        # 解码器第一个时间步的h0
        decoder_hn = encoder_hn
        # 解码器第一个时间步的输入y(1, 1)
        input_y = torch.tensor([[SOS_TOKEN]], dtype=torch.long, device=device)

        # 存储预测结果
        french_words = []
        # 循环输出解码结果
        while True:
            output_y, decoder_hn, _ = my_decoder(input_y, decoder_hn, encoder_output_c)
            topv, topi = output_y.topk(1)
            if topi.squeeze().item() == EOS_TOKEN:
                break
            input_y = topi.detach()
            french_word = french_index2word[topi.item()]
            french_words.append(french_word)
        words = ' '.join(french_words)
        print('英语: ', x)
        print('法语:', words)
        
if __name__ == '__main__':
    train_seq2seq(epochs=5)

    """
    ['i m impressed with your french .', 'je suis impressionne par votre francais .'],
    ['i m more than a friend .', 'je suis plus qu une amie .'],
    ['she is beautiful like her mother .', 'elle est belle comme sa mere .']
    """

    predict_seq2seq('i m impressed with your french .')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业—使用Flink处理Kafka中的数据_ChangeRecord2

使用 Flink 消费 Kafka 中 ChangeRecord 主题的数据&#xff0c;每隔 1 分钟输出最近 3 分钟的预警次数最多的 设备&#xff0c;将结果存入Redis 中&#xff0c; key 值为 “warning_last3min_everymin_out” &#xff0c; value 值为 “ 窗口结束时间&#xff0c;设备id” &am…

Android 消息队列之MQTT的使用:物联网通讯,HTTP太重了,使用MQTT;断网重连、注册、订阅、发送数据和接受数据,实现双向通讯。

目录&#xff1a; 问题MQTT是什么以及为什么使用如何使用&#xff1a;第一阶段、基础功能如何使用&#xff1a;第二阶段、增加断网重连如何使用&#xff1a;第三阶段、封装 一、问题 在开发的时候&#xff0c;我们一般都使用Http和后台进行通讯&#xff0c;比如我们是开发物联…

项目-02-数学学院后台项目开发过程中的问题总结

目录 一、后台&#xff08;pc端&#xff0c;vue2&#xff09;1. dialog对话框被黑色蒙层盖住2. 将前端表格导出为word文档3. 在线查看、下载 .docx、.doc、.pdf文档 一、后台&#xff08;pc端&#xff0c;vue2&#xff09; 1. dialog对话框被黑色蒙层盖住 问题&#xff1a; d…

大数据实验E5HBase:安装配置,shell 命令和Java API使用

实验目的 熟悉HBase操作常用的shell 命令和Java API使用&#xff1b; 实验要求 掌握HBase的基本操作命令和函数接口的使用&#xff1b; 实验平台 操作系统&#xff1a;Linux&#xff08;建议Ubuntu16.04或者CentOS 7 以上&#xff09;&#xff1b;Hadoop版本&#xff1a;3…

使用Tomcat搭建简易文件服务器

创建服务器 1. 复制一个tomcat服务器&#xff0c;并命名为file-service(好区分即可) 2.在webapp里面新建一个文件夹 uploadfiles ,用于存储上传的文件 3. 修改conf/service.xml,配置文件服务器的端口与上传文件夹的访问 在Host标签之间加入一个Context标签 docBase"uploa…

【算法】位运算合集

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 零&#xff1a;位运算基础公式 零&#xff1a;五道基础题 1&#xff1a;位1的个数 2&#xff1a;比…

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势?

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势&#xff1f; 重要性&#xff1a;⭐⭐⭐ &#x1f4af; NLP Github 项目&#xff1a; NLP 项目实践&#xff1a;fasterai/nlp-project-practice 介绍&#xff1a;该仓库围绕着 NLP 任务模型的设计、训练…

《Vue零基础教程》(5)计算属性和侦听器好讲解

1 计算属性 1) 什么是计算属性 计算属性就是基于现有属性计算后的属性 2) 计算属性的作用 计算属性用于对原始数据的再次加工 3) 案例 需求 实现如下效果 使用表达式实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF…

Narya.ai正在寻找iOS工程师!#Mixlab内推

如果你对AI技术和iOS开发充满热情&#xff0c;这里有一个绝佳的机会加入一家专注于AI应用创新的初创公司。Narya.ai正在招聘iOS工程师&#xff0c;帮助他们开发下一代效率工具&#xff0c;旨在提升用户的日常生活效率与幸福感。 关于Narya.ai&#xff1a; 专注于AI应用层创新&a…

【开源免费】基于SpringBoot+Vue.JS课程答疑系统(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 070 &#xff0c;文末自助获取源码 \color{red}{T070&#xff0c;文末自助获取源码} T070&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

FPGA实战篇(触摸按键控制LED灯)

1.触摸按键简介 触摸按键主要可分为四大类&#xff1a;电阻式、电容式、红外感应式以及表面声波式。根据其属性的不同&#xff0c;每种触摸按键都有其合适的使用领域。 电阻式触摸按键由多块导电薄膜按照按键的位置印制而成&#xff0c;但由于耐用性较差且维护复杂&#xff0c…

VSCode如何关闭Vite项目本地自启动

某些情况下VSCode打开Vite项目不需要自动启动&#xff0c;那么如何关闭该功能 文件>首选项>设置 搜索vite 将Vite:Auto Start 勾选取消即可

重生之我在异世界学编程之C语言:深入指针篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文&#xff08;1&#xff09;内置数…

TypeScript (一)运行环境配置,数据类型,可选类型,联合类型,type与interface,交叉类型,断言as,字面量类型,类型缩小

文章目录 一、认识TS1.1 JS 存在的问题1.2 TS的出现1.3 TS运行环境运行ts的三种方式 1.4 变量声明1.5 类型推断 二、数据类型2.1 JS数据类型(1) 数组Array(2) 对象Object(3) 其他类型 2.2 TS特有数据类型(1) any类型(2) unknown类型(3) void类型(4) never (了解)(5) tuple类型 …

【Leetcode 每日一题】3274. 检查棋盘方格颜色是否相同

问题背景 给你两个字符串 c o o r d i n a t e 1 coordinate1 coordinate1 和 c o o r d i n a t e 2 coordinate2 coordinate2&#xff0c;代表 8 8 8 \times 8 88 国际象棋棋盘上的两个方格的坐标。 以下是棋盘的参考图。 如果这两个方格颜色相同&#xff0c;返回 t …

【Dubbo03】消息队列与微服务之dubbo-admin 二进制与编译安装

实战案例&#xff1a;二进制安装 dubbo-admin 新版用Golang重构&#xff0c;提供了二进制包&#xff0c;可以直接部署 #下载二进制包 [rootubuntu2204 ~]#wget https://github.com/apache/dubbo-admin/releases/download/0.5.0/apache-dubbo-admin-0.5.0-bin-release.tar.gz …

Kylin Server V10 下 Kafka 集群部署

一、ZooKeeper 集群部署 1、主机规划 主机名 IP 地址 myid 10.8.3.35 1 10.8.3.36 2 10.8.3.37 3 2、拓扑结构 3、部署 (1) 下载Zookeeper [root@localhost ~]# cd /usr/local [root@localhost local]# wget https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-…

redis的应用----缓存

redis的应用----缓存 一、缓存的概念二、使用redis作为缓存2.1使用redis作为缓存的原因2.2缓存机制的访问步骤 三、缓存的更新策略3.1定期更新3.2实时更新3.3淘汰策略 四、缓存常见的问题4.1缓存预热(Cache preheating)4.2缓存穿透(Cache penetration)4.3缓存雪崩(Cache avalan…

用于LiDAR测量的1.58um单芯片MOPA(一)

--翻译自M. Faugeron、M. Krakowski1等人2014年的文章 1.简介 如今&#xff0c;人们对高功率半导体器件的兴趣日益浓厚&#xff0c;这些器件主要用于遥测、激光雷达系统或自由空间通信等应用。与固态激光器相比&#xff0c;半导体器件更紧凑且功耗更低&#xff0c;这在低功率供…

SpringBoot两天

SpringBoot讲义 什么是SpringBoot&#xff1f; Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xf…