【NLP自然语言处理】11.基础-Transformer编码器部分(子层,编码器层,编码器,解码器层,解码器,输出部分)
一.子层连接结构1.子层连接结构如图所示输入到每个子层以及规范化层的过程中还使用了残差链接跳跃连接因此把这一部分结构整体叫做子层连接代表子层及其链接结构在每个编码器层中都有两个子层这两个子层加上周围的链接结构就形成了两个子层连接结构.子层连接结构图:组件中如果是Multi-Head Attention,那么子层就是多头注意力机制层, 如果是Feed Forward,那么子层就是前馈全连接层2.子层连接结构的代码分析dm03_encoder_sublayer.py子层连接结构 子层(前馈全连接层 或者 注意力机制层) norm层 残差连接SublayerConnection实现思路分析:1 init函数 (self, size, dropout0.1):定义self.norm层 self.dropout层, 其中LayerNorm(size)2 forward(self, x,sublayer) 返回以后的结果 案例: 演示Transformer框架中的 编码器的 两个子层代码实现. 编码器子层1: 多头注意力层(Multi-Head Attention) 残差连接(Add) 层规范化(Norm) 编码器子层2: 前向传播层(Feed Forward) 残差连接(Add) 层规范化(Norm) # 导包 from dm02_encoder_element import * from dm01_input import * # 1. 初始化子层连接结构, 核心是: 残差连接 层规范化, 让模型训练更稳定, 避免梯度消失和梯度爆炸 class SublayerConnection(nn.Module): # 1. 初始化函数 # 参1: 输入的维度(词向量的维度) # 参2: 随机失活的概率 def __init__(self, d_model, dropout0.1): # 1. 初始化父类 super().__init__() # 2. 定义规范化层(LayerNorm) self.norm LayerNorm(d_model) # 3. 定义随机失活层(Dropout) self.dropout nn.Dropout(dropout) # 2. 前向传播函数 # 参1: x - 输入张量, 形状一般是: [batch_size, seq_len, d_model] - [2, 4, 512] # 参2: sublayer - 子层对象, 例如: 多头注意力层(Multi-Head Attention)的对象, 前馈全连接层(Feed Forward)的对象 def forward(self, x, sublayer): # 核心逻辑: 两种常见的实现方式 # 方式1(大多场景用这个): 先子层处理, 再残差连接, 层规范化 # 随机失活 层规划范 子层处理 残差连接 my_result self.dropout(self.norm(sublayer(x))) x # 方式2: 先层规范化, 再子层处理, 残差连接 # my_result self.dropout(sublayer(self.norm(x))) x # 返回结果 return my_result # 2. 测试子层连接结构. def use_sublayer(): # 1. 准备输入数据(词向量 位置编码) x use_position() # 2. 创建子层连接对象 sublayer_conn SublayerConnection(512) # 3. 定义子层对象 - 传入该对象, 充当: 子层处理, 可以是: 多头注意力层(Multi-Head Attention), 前馈全连接层(Feed Forward) # 思路1: 采用函数嵌套(闭包写法)实现, 定义子层函数 - 一个可调用的对象, 接收x, 返回处理结果. # def sublayer(x): # # 3.1 创建 多头注意力层(Multi-Head Attention)对象 # multi_attn MultiHeadAttention(embed_dim512, head8) # # 3.2 计算 注意力, 并返回. # # 因为是自注意力机制, 所以: QKVx # return multi_attn(x, x, x) # # 4. 通过子层连接结构 处理输入. # result sublayer_conn(x, sublayer(x)) # 思路2: 用匿名函数实现. # 多头注意力 # result sublayer_conn(x, lambda x: MultiHeadAttention(embed_dim512, head8)(x, x, x)) # 前馈全连接层 result sublayer_conn(x, lambda x: FeedForward(512, 2048)(x)) # 5. 打印子层处理结果. print(fresult.shape: {result.shape}) # [2, 4, 512] return result # 3. 主函数, 程序入口 if __name__ __main__: use_sublayer()子层连接结构小结什么是子层连接结构:如图所示输入到每个子层以及规范化层的过程中还使用了残差链接跳跃连接因此我们把这一部分结构整体叫做子层连接代表子层及其链接结构, 在每个编码器层中都有两个子层这两个子层加上周围的链接结构就形成了两个子层连接结构.学习并实现了子层连接结构的类: SublayerConnection类的初始化函数输入参数是size, dropout, 分别代表词嵌入大小和置零比率.它的实例化对象输入参数是x, sublayer, 分别代表上一层输出以及子层的函数表示.它的输出就是通过子层连接结构处理的输出.二.编码器层1.编码器层的作用作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.编码器层的构成图:2.编码器层的代码分析编码器层类 EncoderLayer 实现思路分析init函数 (self, size, self_attn, feed_forward, dropout):实例化多头注意力层对象self_attn前馈全连接层对象feed_forwardsize词嵌入维度512clones两个子层连接结构self.sublayer clones(SublayerConnection(size,dropout),2)forward函数 (self, x, mask)数据经过子层连接结构1self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))数据经过子层连接结构2self.sublayer[1](x, self.feed_forward) 案例: 自定义代码, 模拟: 编码器层 流程: 词向量 位置编码 - 多头注意力 - 残差连接 层规范化 - 前馈神经网络 - 残差连接 层规范化 import torch # 导包 from dm01_input import * # 输入部分(词嵌入层 位置编码) from dm02_encoder_element import * # 编码器的组件(多头注意力层, 前馈神经网络, 规范化层, 掩码...) from dm03_encoder_sublayer import * # 编码器的子层... # 需求: 所谓的 编码器层就是把刚才的两个子层(多头注意力层, 前馈全连接层) 合二为一 串起来. # 1. 初始化编码器层. class EncoderLayer(nn.Module): # 1. 初始化函数 # 参1: 词嵌入维度, 参2: 多头注意力对象, 参3: 前馈神经网络对象, 参4: 随机失活概率 def __init__(self, d_model, self_attn, feed_forward, dropout0.1): # 1.1 初始化父类成员 super().__init__() # 1.2 保存子层实例 self.d_model d_model self.self_attn self_attn self.feed_forward feed_forward # 1.3 克隆两个 子层连接结构 self.sublayer clones(SublayerConnection(d_model, dropout), 2) # 2. 前向传播函数. 完成: 自注意力层 前馈神经网络层的特征处理动作, 每层都带 残差连接 和 规范化 def forward(self, x, mask): # 1. 第1层 子层连接: 自注意力层 x self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask)) # 2. 第2层 子层连接: 前馈神经网络层 x self.sublayer[1](x, lambda x: self.feed_forward(x)) # 3. 返回结果. return x # 2. 测试编码器层的流程. def use_encoder_layer(): # 1. 准备数据(词嵌入 位置编码) x use_position() # 2. 实例化子层组件. multi_head MultiHeadAttention(embed_dim512, head8) ff FeedForward(d_model512, d_ff2048) # 3. 创建编码器层. encoder_layer EncoderLayer(d_model512, self_attnmulti_head, feed_forwardff) # 4. 构建掩码张量. 形状: [batch_size, seq_len, seq_len] mask torch.zeros([8, 4, 4]) # 5. 执行编码器层的 - 前馈传播 output encoder_layer(x, mask) # 6. 验证输出的维度 print(f编码器层输出形状: {output.shape}) # 7. 可选: 打印部分输出内容, 观察特征该变化. 即: 第1句话, 前2个词, 前5个维度(词向量), 回想: [2, 4, 512] print(f编码器层输出内容: \n{output[:1, :2, :5]}) if __name__ __main__: use_encoder_layer()编码器层输出形状: torch.Size([2, 4, 512]) 编码器层输出内容: tensor([[[ -6.4999, 29.0509, -36.3342, 25.4680, -16.4996], [-67.3371, -8.9799, -22.2725, 2.2234, 1.2530]]], grad_fnSliceBackward0)编码器层小结学习了编码器层的作用:作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.学习并实现了编码器层的类: EncoderLayer类的初始化函数共有4个, 特别是size其实就是词嵌入维度的大小. 第二个self_attn之后将传入多头自注意力子层实例化对象, 并且是自注意力机制. 第三个是feed_froward,之后将传入前馈全连接层实例化对象. 最后一个是置0比率dropout.实例化对象的输入参数有2个x代表来自上一层的输出,mask代表掩码张量.它的输出代表经过整个编码层的特征表示三.编码器1. 编码器的作用编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.编码器的结构图:2.编码器的代码分析编码器类 Encoder 实现思路分析init函数 (self,layer, N)实例化多个编码器层对象self.layers,通过方法clones(layer, N)实例化规范化层self.norm LayerNorm(layer.size)forward函数 (self, x, mask)数据经过N个层x layer(x, mask)返回规范化后的数据return self.norm(x) 案例: 演示 编码器的代码实现 总结: 1. 编码器默认有6个编码器层 2. 每个编码器层默认有2个子层 多头注意力层 前馈全连接层 3. 还有 残差连接 层规范化 import torch # 导包 from dm04_encoder_layer import * # 1. 定义编码器类 class Encoder(nn.Module): # 1. 初始化函数 # 参1: 单个编码器层对象, 参2: 编码器层数量 def __init__(self, layer, N): # 1.1 初始化父类成员 super().__init__() # 1.2 克隆N个 编码器层对象. self.layers clones(layer, N) # 1.3 定义最终的规范化层 self.norm LayerNorm(layer.d_model) # 512 # 2. 前向传播函数 # 参1: x输入张量, 维度: [batch_size, seq_len, d_model] - [2, 4, 512] # 参2: mask掩码张量, 维度: [batch_size, seq_len, seq_len] - [8, 4, 4], 8个头 def forward(self, x, mask): # 2.1 依次通过N个编码器层 for layer in self.layers: # x通过每一个 编码器层的处理 x layer(x, mask) # 2.2 最终规范化. 提升模型的稳定性 return self.norm(x) # 2. 测试编码器对象. def use_encoder(): # 1. 获取数据(词向量 位置编码) x use_position() # 2. 构建单个编码器层(作为基础单元) # 2.1 多头注意力层的对象 multi_head MultiHeadAttention(embed_dim512, head8) # 2.2 前馈全连接层的对象 ff FeedForward(d_model512, d_ff2048) # 2.3 把 多头注意力层和 前馈全连接层 组合起来 - 编码器层对象 encoder_layer EncoderLayer(d_model512, self_attnmulti_head, feed_forwardff) # 3. 实例化编码器(堆叠 3 个编码器层), 论文默认是: 6个 encoder Encoder(encoder_layer, 3) # 4. 构建掩码张量. mask torch.zeros([8, 4, 4]) # 5. 执行编码过程. encoder_output encoder(x, mask) # 6. 打印结果 encoder_output: tensor([[[-2.0857, -2.4963, -0.9597, ..., 0.1442, 0.4573, 1.9326], [-0.5972, -0.4195, 0.3985, ..., -0.6094, -0.9564, -1.2931], [-1.3990, -0.8341, -0.3576, ..., -0.7218, -0.2229, 0.8463], [-1.5959, -2.5380, 0.1631, ..., 1.0620, 0.4049, -0.4202]], [[ 0.5490, 1.8417, 0.2169, ..., -0.5048, 1.3395, 0.9050], [ 1.0340, -0.1681, -0.5003, ..., 0.5836, -0.0576, -0.6897], [ 0.3073, 0.8616, 0.8630, ..., -0.1669, 0.4068, -0.6433], [-1.4824, -2.3847, -0.0920, ..., 0.7676, 0.2995, -0.4313]]], grad_fnAddBackward0), shape: torch.Size([2, 4, 512]) print(fencoder_output: {encoder_output}, shape: {encoder_output.shape}) # [2, 4, 512] # 8. 返回编码器层处理后的结果, 作为解码器的输入, 继续往后执行 return encoder_output if __name__ __main__: use_encoder()encoder_output: tensor([[[-2.0857, -2.4963, -0.9597, ..., 0.1442, 0.4573, 1.9326], [-0.5972, -0.4195, 0.3985, ..., -0.6094, -0.9564, -1.2931], [-1.3990, -0.8341, -0.3576, ..., -0.7218, -0.2229, 0.8463], [-1.5959, -2.5380, 0.1631, ..., 1.0620, 0.4049, -0.4202]], [[ 0.5490, 1.8417, 0.2169, ..., -0.5048, 1.3395, 0.9050], [ 1.0340, -0.1681, -0.5003, ..., 0.5836, -0.0576, -0.6897], [ 0.3073, 0.8616, 0.8630, ..., -0.1669, 0.4068, -0.6433], [-1.4824, -2.3847, -0.0920, ..., 0.7676, 0.2995, -0.4313]]], grad_fnAddBackward0), shape: torch.Size([2, 4, 512])编码器小结学习了编码器的作用:编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.学习并实现了编码器的类: Encoder类的初始化函数参数有两个分别是layer和N代表编码器层和编码器层的个数.forward函数的输入参数也有两个, 和编码器层的forward相同,x代表上一层的输出,mask代码掩码张量.编码器类的输出就是Transformer中编码器的特征提取表示, 它将成为解码器的输入的一部分.四.解码器层目标了解解码器中各个组成部分的作用.掌握解码器中各个组成部分的实现过程1.解码器介绍解码器部分:由N个解码器层堆叠而成每个解码器层由三个子层连接结构组成第一个子层连接结构包括一个掩码多头自注意力子层和规范化层以及一个残差连接第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接说明:解码器层中的各个部分如多头注意力机制规范化层前馈全连接网络子层连接结构都与编码器中的实现相同.因此这里可以直接拿来构建解码器层2.解码器层的作用作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作即解码过程.3.解码器层的代码实现解码器层类 DecoderLayer 实现思路分析init函数 (self,size, self_attn, src_attn, feed_forward, dropout)词嵌入维度尺寸大小size,自注意力机制层对象self_attn,一般注意力机制层对象src_attn,前馈全连接层对象feed_forwardclones3子层连接结构self.sublayer clones(SublayerConnection(size,dropout),3)forward函数 (self,x, memory, source_mask, target_mask)数据经过子层连接结构1self.sublayer[0](x, lambda x:self.self_attn(x, x, x, target_mask))数据经过子层连接结构2self.sublayer[1](x,lambda x:self.src_attn(x, m, m, source_mask))数据经过子层连接结构3self.sublayer[2](x,self.feed_forward) 案例: 演示解码器层的代码实现. import copy import torch # 导包 from dm05_encoder import * # 编码器 # 1. 定义解码器层 - Masked Multi-Head Attention, Feed Forward, Multi-Head Attention... class DecoderLayer(nn.Module): # 1. 初始化函数. # 参1 d_model: 词向量维度 # 参2 self_attn: 自注意力机制, 处理 解码器输入序列内部关系(解码器的输入) # 参3 src_attn: 源序列(编码器-解码器)注意力机制, 处理 关联编码器的输出 和 解码器的输入序列之间的关系 # 参4 feed_forward: 前馈全连接层, 强化特征(解码器的输出) # 参5 dropout: 随机失活概率 def __init__(self, d_model, self_attn, src_attn, feed_forward, dropout0.1): # 1.1 初始化父类成员. super().__init__() # 1.2 定义属性. self.d_model d_model self.self_attn self_attn self.src_attn src_attn self.feed_forward feed_forward # 1.3 定义3个子层的连接结构. self.layers clones(SublayerConnection(d_model, dropout), 3) # 2. 解码器层的 前向传播. # 参1 x: 解码器的输入序列(词嵌入 位置编码) # 参2 encoder_output: 编码器的输出序列(词嵌入 源序列位置编码) # 参3 source_mask: 源序列的填充掩码, 用于 编码器-解码器 注意力. # 参4 target_mask: 目标序列的填充掩码, 用于 自注意力. def forward(self, x, encoder_output, source_mask, target_mask): x self.layers[0](x, lambda x: self.self_attn(x, x, x, target_mask)) # 2. 经过第2个子层 - 多头注意力机制. Q ! K, K V # 查询来自 解码器的输入序列, K,V来自编码器的输出序列. x self.layers[1](x, lambda x: self.src_attn(x, encoder_output, encoder_output, source_mask)) # 3. 经过第3个子层 - 前馈全连接层. # x self.layers[2](x, lambda x: self.feed_forward(x)) x self.layers[2](x, self.feed_forward) # 4.返回结果 return x # 2. 测试解码器层. def use_decoder_layer(): # 1. 定义解码器的输入 - Q y torch.LongTensor([ [1, 2, 3, 4], [5, 6, 7, 8] ]) # 2. 将值传入到Embedding层(词嵌入层) # 2.1 创建词嵌入层对象. my_embed Embeddings(1000, 512) # 2.2 将上述的y(输入) - 词向量 embed_y my_embed(y) # print(词嵌入层:, embed_y.shape) # 嵌入层: torch.Size([2, 4, 512]) # 3. 将embed_y(词嵌入处理结果) - 位置编码层 my_position PositionEncoding(512, 0.1) position_y my_position(embed_y) # print(位置编码层:, position_y.shape) # 4. 实例化 多头注意力机制 multi_attn MultiHeadAttention(embed_dim512, head8) # 解码器层有多头注意力机制 self_attn copy.deepcopy(multi_attn) # 深层拷贝 - 确保参数不会共享 # 编码器层有多头注意力机制, 源序列(编码器-解码器)注意力机制, 处理 关联编码器的输出 和 解码器的输入序列之间的关系 src_attn copy.deepcopy(multi_attn) # 5. 实例化 前馈全连接层 ff FeedForward(512, 2048) # 6. 获取编码器的输出结果. encoder_output use_encoder() # 7. 定义mask: source_mask 和 target_mask真实作用不一样(要解决你真正的业务来判断), 这里就随便举例了. source_mask torch.zeros(8, 4, 4) target_mask torch.zeros(8, 4, 4) # 8. 实例化 解码器层 my_decoder_layer DecoderLayer(512, self_attn, src_attn, ff) # 9. 将 数据 传给 解码器层, 获取其输出结果. result my_decoder_layer(position_y, encoder_output, source_mask, target_mask) # 10. 打印结果 print(解码器层:, result.shape) if __name__ __main__: use_decoder_layer()解码器层小结学习了解码器层的作用:作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作即解码过程.学习并实现了解码器层的类: DecoderLayer类的初始化函数的参数有5个, 分别是size代表词嵌入的维度大小, 同时也代表解码器层的尺寸第二个是self_attn多头自注意力对象也就是说这个注意力机制需要QKV第三个是src_attn多头注意力对象这里Q!KV 第四个是前馈全连接层对象最后就是droupout置0比率.forward函数的参数有4个分别是来自上一层的输入x来自编码器层的语义存储变量mermory 以及源数据掩码张量和目标数据掩码张量.最终输出了由编码器输入和目标数据一同作用的特征提取结果五.解码器1. 解码器的作用根据编码器的结果以及上一次预测的结果, 对下一次可能出现的值进行特征表示2.解码器的代码分析 案例: 演示 解码器 代码实现. # 导包 from dm06_decoder_layer import * # 1. 定义解码器: 把多个解码器层(层数: 6)进行堆叠, 最后加一层规范化, 让输出更稳定. class Decoder(nn.Module): # 1. 初始化函数 # 参1: layer - 单个解码器层(DecoderLayer类)的对象, 要被复制N次 # 参2: 解码层的堆叠数量 def __init__(self, layer, N): # 1.1 初始化父类成员. super().__init__() # 1.2 克隆N个解码器层. self.layers clones(layer, N) # 1.3 定义最终的规划范层. self.norm LayerNorm(layer.d_model) # 2. 定义前向传播函数. # 参1 x: 解码器的输入序列(词嵌入 位置编码) # 参2 encoder_output: 编码器的输出序列(词嵌入 源序列位置编码) # 参3 source_mask: 源序列的填充掩码, 用于 编码器-解码器 注意力. # 参4 target_mask: 目标序列的填充掩码, 用于 自注意力. def forward(self, x, encoder_output, source_mask, target_mask): # 1. 数据x依次经过 多个 解码器层的处理即可 for layer in self.layers: # layer: 就表示某一个具体的解码器层 x layer(x, encoder_output, source_mask, target_mask) # 2. 全局规范化, 把所有层的输出再统一标准化. return self.norm(x) # 2. 测试解码器, 从输入到输出走一遍流程, 验证每层的维度是否正常 def use_decoder(): # 1. 定义解码器的输入 - Q y torch.LongTensor([ [1, 2, 3, 4], [5, 6, 7, 8], ]) # 2. 将值传入到Embedding层(词嵌入层) # 2.1 创建词嵌入层对象. my_embed Embeddings(1000, 512) # 2.2 将上述的y(输入) - 词向量 embed_y my_embed(y) print(词嵌入层:, embed_y.shape) # 3. 将embed_y(词嵌入处理结果) - 位置编码层 my_position PositionEncoding(512, 0.1) position_y my_position(embed_y) print(位置编码层:, position_y.shape) # 4. 实例化 多头注意力机制. multi_attn MultiHeadAttention(512, 8) self_attn copy.deepcopy(multi_attn) # 深层拷贝 - 确保参数不会共享. src_attn copy.deepcopy(multi_attn) # 5. 实例化 前馈全连接层. ff FeedForward(512, 2048) # 6. 获取编码器的输出结果. encoder_output use_encoder() # 7. 定义mask: source_mask 和 target_mask真实作用不一样(要解决你真正的业务来判断), 这里我就随便举例了. source_mask torch.zeros(8, 4, 4) target_mask torch.zeros(8, 4, 4) # 8. 实例化 解码器层. my_decoder_layer DecoderLayer(512, self_attn, src_attn, ff) # 9. 堆6层解码器层 - 组成解码器. my_decoder Decoder(my_decoder_layer, 6) # 10. 跑一遍解码流程: 输入 - 6层解码器层 - 输出 result my_decoder(position_y, encoder_output, source_mask, target_mask) # 11. 打印结果. print(f最终解码器的输出结果(维度): {result.shape}) print(f最终解码器的输出结果: {result}) # 12. 返回结果 return result if __name__ __main__: use_decoder()词嵌入层: torch.Size([2, 4, 512]) 位置编码层: torch.Size([2, 4, 512]) 最终解码器的输出结果(维度): torch.Size([2, 4, 512]) 最终解码器的输出结果: tensor([[[ 0.4377, 0.3056, -0.3046, ..., -0.4923, -1.2813, -0.2581], [-2.5350, -0.2563, 0.5506, ..., -0.0928, 1.3440, -0.3467], [ 0.1723, -0.0616, -0.4943, ..., -0.6493, -0.7001, -1.5030], [ 1.2671, -1.1107, 0.6320, ..., 2.0098, -1.5513, -0.7924]], [[ 1.2439, -0.1372, 0.9656, ..., -0.7734, -0.6460, 0.1942], [ 1.1462, -2.0309, 0.4610, ..., 0.0062, 0.1847, -1.5321], [ 0.6701, -0.2183, 2.0715, ..., -0.4287, -1.2599, 1.3352], [ 1.2626, -0.9773, -0.4502, ..., -1.5590, -1.1468, -1.9215]]], grad_fnAddBackward0)解码器总结学习了解码器的作用:根据编码器的结果以及上一次预测的结果, 对下一次可能出现的值进行特征表示.学习并实现了解码器的类: Decoder类的初始化函数的参数有两个第一个就是解码器层layer第二个是解码器层的个数N.forward函数中的参数有4个x代表目标数据的嵌入表示memory是编码器层的输出src_mask, tgt_mask代表源数据和目标数据的掩码张量.输出解码过程的最终特征表示六.输出层目标了解线性层和softmax的作用掌握线性层和softmax的实现过程1.输出部分介绍输出部分包含:线性层softmax层2.线性层的作用通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.3.softmax层的作用使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为14.线性层和softmax层的代码分析 案例: 演示Transformer的 输出部分. # 导包 from dm07_decoder import * # todo 1. 定义输出部分, 把解码器的特征转成最终预测结果(比如: 翻译后的词) class Generator(nn.Module): # 1. 初始化函数. # 参1 d_model: 词向量维度, 例如: 512 # 参2 vocab_size: 词典大小, 例如: 1000 def __init__(self, d_model, vocab_size): # 1.1 初始化父类成员 super().__init__() # 1.2 定义线性层, 封装: 输入: 词向量维度, 输出: 词典大小 # 例如: 输入[2, 4, 512] - 输出[2, 4, 1000] 假设词典大小为1000 self.linear nn.Linear(d_model, vocab_size) # 2. 前向传播函数. def forward(self, x): # 2.1 线性层, 输出: [2, 4, 1000] x self.linear(x) # 2.2 log_softmax(): 将分数转成 对数概率分布. # -1表示对最后1维(词汇表维度)做计算, 确保 概率和为 1 return F.log_softmax(x, dim-1) # 2. 测试输出结果, 从解码器到最终预测的完成流程 def use_generator(): # 功能: 获取解码器的特征 - 通过生成器转成目标词汇表的概率分布 - 验证输出维度 概率分布格式 # 1. 获取解码器的输出结果 result use_decoder() # 2. 初始化 输出生成器, 将512维的解码器特征, 转成1000维的概率分布. generator Generator(512, 1000) # 3. 通过生成器, 获取概率分布 output generator(result) # 4. 验证输出 # 形状为: [batch_size, seq_len, vocab_size], 每个位置的数值是对数概率, 可以通过 exp()转成概率. print(foutput 模型最终输出结果: {output.shape}, {output}) # [2, 4, 1000] # 5. 验证概率和为1, 取 第1个样本, 第1个词的概率总和 - 无限接近1 # 5.1 提取第1个样本, 第1个词的 对数概率, 形状: [1000] log_probs output[1, 3] # 5.2 把对数概率 - 转成普通的概率. probs torch.exp(log_probs) # print(f第1个样本, 第1个词的概率: {probs}) # 5.3 验证概率和为1 print(f第1个词的概率总和为: {torch.sum(probs)}) if __name__ __main__: use_generator()词嵌入层: torch.Size([2, 4, 512]) 位置编码层: torch.Size([2, 4, 512]) 最终解码器的输出结果(维度): torch.Size([2, 4, 512]) 最终解码器的输出结果: tensor([[[ 0.6687, -0.0925, 0.0686, ..., 2.0267, 0.1717, -0.3848], [-0.3604, -0.3588, 1.3145, ..., 1.5206, 0.1748, 0.1989], [ 0.0963, 1.9654, 1.6360, ..., -0.1644, 0.2319, -0.1677], [ 0.9622, 2.0344, -0.2315, ..., -0.1613, 0.3393, 0.1598]], [[ 0.8796, 1.1931, 0.8431, ..., -0.6909, 1.4011, 0.0823], [ 0.2605, 2.2712, 1.3752, ..., -0.2793, 0.9122, 0.1591], [ 0.4806, 0.5197, -1.2658, ..., 0.1482, 1.0588, 1.8717], [-0.0938, 0.8664, -0.4494, ..., -0.7347, -0.2988, 0.3140]]], grad_fnAddBackward0) output 模型最终输出结果: torch.Size([2, 4, 1000]), tensor([[[-7.0168, -7.3308, -6.7317, ..., -6.9036, -6.2364, -7.4845], [-6.5014, -6.7251, -7.3096, ..., -7.4995, -7.0598, -7.3684], [-7.2256, -6.3947, -7.7666, ..., -6.7527, -6.9248, -7.0969], [-7.0797, -6.1436, -7.1163, ..., -6.5037, -7.0738, -7.2776]], [[-7.6525, -7.6472, -6.3397, ..., -7.6205, -7.4463, -5.6784], [-6.6492, -7.8835, -7.5305, ..., -7.1127, -7.4865, -6.6285], [-7.5391, -6.6252, -7.7764, ..., -7.2196, -7.3970, -6.4900], [-6.5289, -6.4780, -7.1219, ..., -6.7806, -6.8647, -7.5506]]], grad_fnLogSoftmaxBackward0) 第1个词的概率总和为: 0.9999996423721313线性层小结学习了输出部分包含:线性层softmax层线性层的作用:通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.softmax层的作用:使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.学习并实现了线性层和softmax层的类: Generator初始化函数的输入参数有两个,d_model代表词嵌入维度,vocab_size代表词表大小.forward函数接受上一层的输出.最终获得经过线性层和softmax层处理的结果【上一篇】【NLP自然语言处理】10.基础-Transformer编码器组件部分(位置编码,注意力机制, 多头注意力机制,前馈全连接层,规范化层)【下一篇】【NLP自然语言处理】12.基础-Transformer模型的构建