门户网站建设策划,网站设置在设备之间共享怎么开启,android 移动网站开发详解,国外哪些网站可以兼职做任务#x1f368; 本文为#x1f517;365天深度学习训练营 中的学习记录博客#x1f356; 原作者#xff1a;K同学啊 | 接辅导、项目定制 目录 多头注意力机制前馈传播位置编码编码层解码层Transformer模型构建使用示例 本文为TR3学习打卡#xff0c;为了保证记录顺序我这里写… 本文为365天深度学习训练营 中的学习记录博客 原作者K同学啊 | 接辅导、项目定制 目录 多头注意力机制前馈传播位置编码编码层解码层Transformer模型构建使用示例 本文为TR3学习打卡为了保证记录顺序我这里写为N9 总结 之前有学习过文本预处理的环节对文本处理的主要方式有以下三种
1词袋模型one-hot编码
2TF-IDF
3Word2Vec(词向量(Word Embedding) 以及Word2vec(Word Embedding 的方法之一))
详细介绍及中英文分词详见pytorch文本分类一文本预处理
N1期介绍了one-hot编码
N2期主要介绍Embedding,及EmbeddingBag 使用示例对词索引向量转化为词嵌入向量
N3期主要介绍应用三种模型的英文分类
N4期将主要介绍中文基本分类熟悉流程、拓展textCNN分类通用模型、拓展Bert分类模型进阶
N5期主要介绍Word2Vec和nn.Embedding(), nn.EmbeddingBag()相比都是嵌入技术用于将离散的词语或符号映射到连续的向量空间。
nn.Embedding 和 nn.EmbeddingBag 是深度学习框架如 PyTorch中的层直接用于神经网络模型中而 Word2Vec 是一种独立的词嵌入算法。
使用来说如果需要在神经网络中处理变长序列的嵌入可以选择 nn.EmbeddingBag如果需要预训练词嵌入用于不同任务可以选择 Word2Vec。
N6期主要介绍使用Word2Vec实现文本分类
与N4文本分类的异同点总结
共同点 数据加载都使用了PyTorch的DataLoader来批量加载数据。 模型训练训练过程大同小异都是前向传播、计算损失、反向传播和梯度更新。不同点 分词处理 BERT模型使用专门的BERT分词器。 传统嵌入方法通常使用jieba等工具进行分词。 Word2Vec模型假设数据已经分词。 词向量表示 BERT模型使用BERT生成的上下文相关的词向量。 传统嵌入方法使用静态预训练词向量。 Word2Vec模型训练一个Word2Vec模型生成词向量。 模型结构 BERT模型使用预训练的BERT模型作为编码器。 传统嵌入方法一般使用嵌入层卷积/循环神经网络。 Word2Vec模型使用Word2Vec词向量和一个简单的线性分类器。值得学习的点 词向量的使用了解如何使用Word2Vec生成词向量并将其用于下游任务。 数据预处理不同方法的数据预处理方式尤其是分词和词向量化的处理。 模型训练标准的模型训练和评估流程尤其是损失计算、反向传播和梯度更新等步骤。 超参数选择注意学习率、批量大小和训练轮数等超参数的选择。 通过这些比较和分析可以更好地理解不同文本分类方法的优缺点以及适用场景。
N7期需要理解RNN 及 seq2seq代码并在此基础上成功运行代码理解代码流程
N8期在上一期的基础上加入了注意力机制
N9期transformer的代码复现注释里加入了一些理解但是对transformer的网络结构的理解还需加强
在之前的任务中我们学习了Seq2Seq知晓了Attention为RNN带来的优点。那么有没有一种神经网络结构直接基于attention构造并且不再依赖RNN、LSTM或者CNN网络结构了呢答案便是Transformer。Seq2Seq和Transformer都是用于处理序列数据的深度学习模型但它们是两种不同的架构。 1Seq2Seq
定义 Seq2Seq是一种用于序列到序列任务的模型架构最初用于机器翻译。这意味着它可以处理输入序列并生成相应的输出序列。结构 Seq2Seq模型通常由两个主要部分组成编码器和解码器。编码器负责将输入序列编码为固定大小的向量而解码器则使用此向量生成输出序列。问题 传统的Seq2Seq模型在处理长序列时可能会遇到梯度消失/爆炸等问题而Transformer模型的提出正是为了解决这些问题。
2Transformer
定义 Transformer是一种更现代的深度学习模型专为处理序列数据而设计最初用于自然语言处理任务。它不依赖于RNN或CNN等传统结构而是引入了注意力机制。结构 Transformer模型主要由编码器和解码器组成它们由自注意力层和全连接前馈网络组成。它使用注意力机制来捕捉输入序列中不同位置之间的依赖关系同时通过多头注意力来提高模型的表达能力。优势 Transformer的设计使其能够更好地处理长距离依赖关系同时具有更好的并行性。 在某种程度上可以将Transformer看作是Seq2Seq的一种演变Transformer可以执行Seq2Seq任务并且相对于传统的Seq2Seq模型具有更好的性能和可扩展性。
多头注意力机制
# 多头注意力机制
import math
import torch
import torch.nn as nndevice torch.device(cuda if torch.cuda.is_available() else cpu)class MultiHeadAttention(nn.Module):# n_heads:多头注意力的数量# hid_dim:每个词输出的向量维度def __init__(self,hid_dim,n_heads):super(MultiHeadAttention,self).__init__()self.hid_dim hid_dimself.n_heads n_heads# 强制hid_dim 必须整除 hassert hid_dim % n_heads 0# 定义W_q 矩阵self.w_q nn.Linear(hid_dim,hid_dim) # nn.Linear(in_features, out_features) 是 PyTorch 中的线性变换层# 定义W_k 矩阵self.w_k nn.Linear(hid_dim,hid_dim)# 定义W_v 矩阵self.w_v nn.Linear(hid_dim,hid_dim)self.fc nn.Linear(hid_dim,hid_dim)# 缩放self.scale torch.sqrt(torch.FloatTensor([hid_dim // n_heads])) # 缩放因子目的是防止注意力得分过大从而使 softmax 函数过于饱和。hid_dim // n_heads 代表每个注意力头的维度取平方根来缩放。def forward(self,query,key,value,maskNone):# 注意 QKV的在句子长度这一个维度的数值可以一样可以不一样。# K: [64,10,300], 假设batch_size 为 64有 10 个词每个词的 Query 向量是 300 维# V: [64,10,300], 假设batch_size 为 64有 10 个词每个词的 Query 向量是 300 维# Q: [64,12,300], 假设batch_size 为 64有 12 个词每个词的 Query 向量是 300 维bsz query.shape[0] # 获取批次大小# 对输入的 query, key, value 进行线性变换。Q self.w_q(query)K self.w_k(key)V self.w_v(value)# 这里把 K Q V 矩阵拆分为多组注意力# 最后一维就是是用 self.hid_dim // self.n_heads 来得到的表示每组注意力的向量长度, 每个 head 的向量长度是300/650# 64 表示 batch size6 表示有 6组注意力10 表示有 10 词50 表示每组注意力的词的向量长度# K: [64,10,300] 拆分多组注意力 - [64,10,6,50] 转置得到 - [64,6,10,50]# V: [64,10,300] 拆分多组注意力 - [64,10,6,50] 转置得到 - [64,6,10,50]# Q: [64,12,300] 拆分多组注意力 - [64,12,6,50] 转置得到 - [64,6,12,50]# 转置是为了把注意力的数量 6 放到前面把 10 和 50 放到后面方便下面计算Q Q.view(bsz,-1,self.n_heads,self.hid_dim//self.n_heads).permute(0,2,1,3)K K.view(bsz,-1,self.n_heads,self.hid_dim//self.n_heads).permute(0,2,1,3)V V.view(bsz,-1,self.n_heads,self.hid_dim//self.n_heads).permute(0,2,1,3)# 第 1 步Q 乘以 K的转置除以scale# [64,6,12,50] * [64,6,50,10] [64,6,12,10]# attention[64,6,12,10]attention torch.matmul(Q,K.permute(0,1,3,2))/self.scale# 如果 mask 不为空那么就把 mask 为 0 的位置的 attention 分数设置为 -1e10这里用“0”来指示哪些位置的词向量不能被attention到比如padding位置当然也可以用“1”或者其他数字来指示主要设计下面2行代码的改动。if mask is not None:attention attention.masked_fill(mask 0 , -1e10)# 第 2 步计算上一步结果的 softmax再经过 dropout得到 attention。# 注意这里是对最后一维做 softmax也就是在输入序列的维度做 softmax# attention: [64,6,12,10]attention torch.softmax(attention,dim-1)# 第三步attention结果与V相乘得到多头注意力的结果# [64,6,12,10] * [64,6,10,50] [64,6,12,50]# x: [64,6,12,50]x torch.matmul(attention,V)# 因为 query 有 12 个词所以把 12 放到前面把 50 和 6 放到后面方便下面拼接多组的结果# x: [64,6,12,50] 转置- [64,12,6,50]x x.permute(0,2,1,3).contiguous()# 这里的矩阵转换就是把多组注意力的结果拼接起来# 最终结果就是 [64,12,300]# x: [64,12,6,50] - [64,12,300]x x.view(bsz,-1,self.n_heads * (self.hid_dim // self.n_heads))x self.fc(x)return x前馈传播
# 前馈传播
class Feedforward(nn.Module):这个 Feedforward 类实现了一个两层的前馈神经网络是 Transformer 中的子层之一。每个 Transformer 层包括自注意力机制Multi-Head Attention和前馈神经网络。前馈层的作用是进一步处理每个位置词向量的信息将特征从高维投影回输入的原始维度同时保持全局信息。def __init__(self,d_model,d_ff,dropout0.1):super(Feedforward,self).__init__()# 两层线性映射和激活函数ReLUself.linear1 nn.Linear(d_model,d_ff)self.dropout nn.Dropout(dropout)self.linear2 nn.Linear(d_ff,d_model)def forward(self,x):x torch.nn.functional.relu(self.linear1(x))x self.dropout(x)x self.linear2(x)return x位置编码
# 位置编码
class PositionalEncoding(nn.Module):这是 Transformer 中实现位置编码Positional Encoding部分的实现。位置编码用于将输入序列中的位置信息引入到模型中因为 Transformer 没有传统 RNN 中的时间顺序机制所以需要用位置编码来帮助模型理解输入序列中各个位置之间的相对关系。def __init__(self,d_model,dropout,max_len5000):super(PositionalEncoding,self).__init__()self.dropout nn.Dropout(p dropout)# 初始化Shape为(max_len, d_model)的PE (positional encoding)pe torch.zeros(max_len,d_model).to(device) # pe: 一个形状为 [max_len, d_model] 的张量用于存储所有位置的编码初始化为全零张量。这个张量会在后续步骤中被填充上具体的位置信息。position torch.arange(0,max_len).unsqueeze(1) # 生成位置索引 [0, 1, 2, ...], 形状为 [max_len, 1]# 这里就是sin和cos括号中的内容通过e和ln进行了变换div_term torch.exp(torch.arange(0,d_model,2) * -(math.log(10000.0) / d_model)) # 用于在位置编码中进行缩放的除法项pe[:, 0::2] torch.sin(position * div_term) # 计算PE(pos, 2i) 计算偶数位置的 sin 编码pe[:, 1::2] torch.cos(position * div_term) # 计算PE(pos, 2i1) 计算奇数位置的 cos 编码pe pe.unsqueeze(0) # 为了将其与输入张量相加增加 batch 维度形状变为 [1, max_len, d_model]# 如果一个参数不参与梯度下降但又希望保存model的时候将其保存下来# 这个时候就可以用register_bufferself.register_buffer(pe, pe)def forward(self, x):x 为embedding后的inputs例如(1,7, 128)batch size为1,7个单词单词维度为128# 将位置编码与输入相加x x self.pe[:, :x.size(1)].requires_grad_(False) # 取出与输入序列长度相同的部分位置编码。如果输入序列的长度小于 max_len只取前 x.size(1) 个位置的编码;之后禁止对位置编码进行梯度计算因为位置编码是固定的不需要更新。return self.dropout(x)编码层
# 编码层
class EncoderLayer(nn.Module):def __init__(self, d_model, n_heads, d_ff, dropout0.1):super(EncoderLayer, self).__init__()# 编码器层包含自注意力机制和前馈神经网络self.self_attn MultiHeadAttention(d_model, n_heads)self.feedforward Feedforward(d_model, d_ff, dropout)self.norm1 nn.LayerNorm(d_model)self.norm2 nn.LayerNorm(d_model)self.dropout nn.Dropout(dropout)def forward(self, x, mask):# 自注意力机制attn_output self.self_attn(x, x, x, mask)x x self.dropout(attn_output)x self.norm1(x)# 前馈神经网络ff_output self.feedforward(x)x x self.dropout(ff_output)x self.norm2(x)return x解码层
# 解码层
class DecoderLayer(nn.Module):def __init__(self, d_model, n_heads, d_ff, dropout0.1):super(DecoderLayer, self).__init__()# 解码器层包含自注意力机制、编码器-解码器注意力机制和前馈神经网络self.self_attn MultiHeadAttention(d_model, n_heads)self.enc_attn MultiHeadAttention(d_model, n_heads)self.feedforward Feedforward(d_model, d_ff, dropout)self.norm1 nn.LayerNorm(d_model)self.norm2 nn.LayerNorm(d_model)self.norm3 nn.LayerNorm(d_model)self.dropout nn.Dropout(dropout)def forward(self, x, enc_output, self_mask, context_mask):# 自注意力机制attn_output self.self_attn(x, x, x, self_mask)x x self.dropout(attn_output)x self.norm1(x)# 编码器-解码器注意力机制attn_output self.enc_attn(x, enc_output, enc_output, context_mask)x x self.dropout(attn_output)x self.norm2(x)# 前馈神经网络ff_output self.feedforward(x)x x self.dropout(ff_output)x self.norm3(x)return xTransformer模型构建
# Transformer模型构建
class Transformer(nn.Module):def __init__(self, vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout0.1):super(Transformer, self).__init__()# Transformer 模型包含词嵌入、位置编码、编码器和解码器self.embedding nn.Embedding(vocab_size, d_model)self.positional_encoding PositionalEncoding(d_model, dropout)self.encoder_layers nn.ModuleList([EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_encoder_layers)])self.decoder_layers nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_decoder_layers)])self.fc_out nn.Linear(d_model, vocab_size)self.dropout nn.Dropout(dropout)def forward(self, src, trg, src_mask, trg_mask):# 词嵌入和位置编码src self.embedding(src)src self.positional_encoding(src)trg self.embedding(trg)trg self.positional_encoding(trg)# 编码器for layer in self.encoder_layers:src layer(src, src_mask)# 解码器for layer in self.decoder_layers:trg layer(trg, src, trg_mask, src_mask)# 输出层output self.fc_out(trg)return output使用示例
# 使用示例
vocab_size 10000 # 假设词汇表大小为10000
d_model 512
n_heads 8
n_encoder_layers 6
n_decoder_layers 6
d_ff 2048
dropout 0.1transformer_model Transformer(vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout)# 定义输入这里的输入是假设的需要根据实际情况修改
src torch.randint(0, vocab_size, (32, 10)) # 源语言句子
trg torch.randint(0, vocab_size, (32, 20)) # 目标语言句子
src_mask (src ! 0).unsqueeze(1).unsqueeze(2) # 掩码用于屏蔽填充的位置
trg_mask (trg ! 0).unsqueeze(1).unsqueeze(2) # 掩码用于屏蔽填充的位置# 模型前向传播
output transformer_model(src, trg, src_mask, trg_mask)
print(output.shape)torch.Size([32, 20, 10000])