• learn编码器


    目录

    1、编码器的作用

    2、编码器的结构图

    3、代码实现如下


    1、编码器的作用

    编码器用于对输入进行指定的特征提取的过程,也称为编码,由 N 个编码器层堆叠而成

    2、编码器的结构图

    3、代码实现如下

    1. import numpy as np
    2. from torch.autograd import Variable
    3. import copy
    4. from torch import softmax
    5. import math
    6. import torch.nn as nn
    7. import torch
    8. # 构建Embedding类来实现文本嵌入层
    9. class Embeddings(nn.Module):
    10. def __init__(self,vocab,d_model):
    11. """
    12. :param vocab: 词表的大小
    13. :param d_model: 词嵌入的维度
    14. """
    15. super(Embeddings,self).__init__()
    16. self.lut = nn.Embedding(vocab,d_model)
    17. self.d_model = d_model
    18. def forward(self,x):
    19. """
    20. :param x: 因为Embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
    21. :return:
    22. """
    23. return self.lut(x) * math.sqrt(self.d_model)
    24. class PositionalEncoding(nn.Module):
    25. def __init__(self,d_model,dropout,max_len=5000):
    26. """
    27. :param d_model: 词嵌入的维度
    28. :param dropout: 随机失活,置0比率
    29. :param max_len: 每个句子的最大长度,也就是每个句子中单词的最大个数
    30. """
    31. super(PositionalEncoding,self).__init__()
    32. self.dropout = nn.Dropout(p=dropout)
    33. pe = torch.zeros(max_len,d_model) # 初始化一个位置编码器矩阵,它是一个0矩阵,矩阵的大小是max_len * d_model
    34. position = torch.arange(0,max_len).unsqueeze(1) # 初始一个绝对位置矩阵 max_len * 1
    35. div_term = torch.exp(torch.arange(0,d_model,2)*-(math.log(1000.0)/d_model)) # 定义一个变换矩阵,跳跃式的初始化
    36. # 将前面定义的变换矩阵进行奇数、偶数的分别赋值
    37. pe[:,0::2] = torch.sin(position*div_term)
    38. pe[:,1::2] = torch.cos(position*div_term)
    39. pe = pe.unsqueeze(0) # 将二维矩阵扩展为三维和embedding的输出(一个三维向量)相加
    40. self.register_buffer('pe',pe) # 把pe位置编码矩阵注册成模型的buffer,对模型是有帮助的,但是却不是模型结构中的超参数或者参数,不需要随着优化步骤进行更新的增益对象。注册之后我们就可以在模型保存后重加载时,将这个位置编码与模型参数一同加载进来
    41. def forward(self, x):
    42. """
    43. :param x: 表示文本序列的词嵌入表示
    44. :return: 最后使用self.dropout(x)对对象进行“丢弃”操作,并返回结果
    45. """
    46. x = x + Variable(self.pe[:, :x.size(1)],requires_grad = False) # 不需要梯度求导,而且使用切片操作,因为我们默认的max_len为5000,但是很难一个句子有5000个词汇,所以要根据传递过来的实际单词的个数对创建的位置编码矩阵进行切片操作
    47. return self.dropout(x)
    48. def subsequent_mask(size):
    49. """
    50. :param size: 生成向后遮掩的掩码张量,参数 size 是掩码张量的最后两个维度大小,它的最后两个维度形成一个方阵
    51. :return:
    52. """
    53. attn_shape = (1,size,size) # 定义掩码张量的形状
    54. subsequent_mask = np.triu(np.ones(attn_shape),k = 1).astype('uint8') # 定义一个上三角矩阵,元素为1,再使用其中的数据类型变为无符号8位整形
    55. return torch.from_numpy(1 - subsequent_mask) # 先将numpy 类型转化为 tensor,再做三角的翻转,将位置为 0 的地方变为 1,将位置为 1 的方变为 0
    56. def attention(query, key, value, mask=None, dropout=None):
    57. """
    58. :param query: 三个张量输入
    59. :param key: 三个张量输入
    60. :param value: 三个张量输入
    61. :param mask: 掩码张量
    62. :param dropout: 传入的 dropout 实例化对象
    63. :return:
    64. """
    65. d_model = query.size(-1) # 得到词嵌入的维度,取 query 的最后一维大小
    66. scores = torch.matmul(query,key.transpose(-2,-1)) / math.sqrt(d_model) # 按照注意力公式,将 query 和 key 的转置相乘,这里是将 key 的最后两个维度进行转置,再除以缩放系数,得到注意力得分张量 scores
    67. # query(2,8,4,64) key.transpose(-2,-1) (2,8,64,4) 进行矩阵乘法为 (2,8,4,4)
    68. if mask is not None:
    69. scores = torch.masked_fill(scores,mask == 0,-1e9) # 使用 tensor 的 mask_fill 方法,将掩码张量和 scores 张量中每一个位置进行一一比较,如果掩码张量处为 0 ,则使用 -1e9 替换
    70. # scores = scores.masked_fill(mask == 0,-1e9)
    71. p_attn = softmax(scores, dim = -1) # 对 scores 的最后一维进行 softmax 操作,使用 F.softmax 方法,第一个参数是 softmax 对象,第二个参数是最后一个维度,得到注意力矩阵
    72. print('scores.shape ',scores.shape)
    73. if dropout is not None:
    74. p_attn = dropout(p_attn)
    75. return torch.matmul(p_attn,value),p_attn # 返回注意力表示
    76. class MultiHeadAttention(nn.Module):
    77. def __init__(self, head, embedding_dim , dropout=0.1):
    78. """
    79. :param head: 代表几个头的参数
    80. :param embedding_dim: 词向量维度
    81. :param dropout: 置零比率
    82. """
    83. super(MultiHeadAttention, self).__init__()
    84. assert embedding_dim % head == 0 # 确认一下多头的数量可以整除词嵌入的维度 embedding_dim
    85. self.d_k = embedding_dim // head # 每个头获得词向量的维度
    86. self.head = head
    87. self.linears = nn.ModuleList([copy.deepcopy(nn.Linear(embedding_dim, embedding_dim)) for _ in range(4)]) # 深层拷贝4个线性层,每一个层都是独立的,保证内存地址是独立的,分别是 Q、K、V以及最终的输出线性层
    88. self.attn = None # 初始化注意力张量
    89. self.dropout = nn.Dropout(p=dropout)
    90. def forward(self, query, key, value, mask=None):
    91. """
    92. :param query: 查询query [batch size, sentence length, d_model]
    93. :param key: 待查询key [batch size, sentence length, d_model]
    94. :param value: 待查询value [batch size, sentence length, d_model]
    95. :param mask: 计算相似度得分时的掩码(设置哪些输入不计算到score中)[batch size, 1, sentence length]
    96. :return:
    97. """
    98. if mask is not None:
    99. mask = mask.unsqueeze(1) # 将掩码张量进行维度扩充,代表多头中的第 n 个头
    100. batch_size = query.size(0)
    101. query, key, value = [l(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2) for l, x in zip(self.linears, (query, key, value))] # 将1、2维度进行调换,目的是让句子长度维度和词向量维度靠近,这样注意力机制才能找到词义与句子之间的关系
    102. # 将每个头传递到注意力层
    103. x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)
    104. # 得到每个头的计算结果是 4 维的张量,需要形状的转换
    105. # 前面已经将1,2两个维度进行转置了,所以这里要重新转置回来
    106. # 前面已经经历了transpose,所以要使用contiguous()方法,不然无法使用 view 方法
    107. x = x.transpose(1, 2).contiguous() \
    108. .view(batch_size, -1, self.head * self.d_k)
    109. return self.linears[-1](x) # 在最后一个线性层中进行处理,得到最终的多头注意力结构输出
    110. class LayerNormalization(nn.Module):
    111. def __init__(self, features, eps=1e-6):
    112. """
    113. :param features: 词嵌入的维度
    114. :param eps: 出现在规范化公式的分母中,防止分母为0
    115. """
    116. super(LayerNormalization, self).__init__()
    117. # a 系数的默认值为1,模型的参数
    118. self.a = nn.Parameter(torch.ones(features))
    119. # b 系统的初始值为0,模型的参数
    120. self.b = nn.Parameter(torch.zeros(features))
    121. # 把 eps 传递到类中
    122. self.eps = eps
    123. def forward(self, x):
    124. # 在最后一个维度上求 均值,并且输出维度保持不变
    125. mean = x.mean(-1, keepdim=True)
    126. std = x.std(-1, keepdim=True)
    127. return self.a * (x - mean) / (std + self.eps) + self.b
    128. class PositionFeedForward(nn.Module):
    129. def __init__(self, d_model, d_ff, dropout=0.1):
    130. """
    131. :param d_model: 输入维度,词嵌入的维度
    132. :param d_ff: 第一个的输出连接第二个的输入
    133. :param dropout: 置零比率
    134. """
    135. super(PositionFeedForward, self).__init__()
    136. self.linear1 = nn.Linear(d_model, d_ff)
    137. self.linear2 = nn.Linear(d_ff, d_model)
    138. self.dropout = nn.Dropout(p = dropout)
    139. def forward(self, x):
    140. return self.linear2(self.dropout(torch.relu(self.linear1(x))))
    141. class SublayerConnection(nn.Module):
    142. def __init__(self, size, dropout=0.1):
    143. super(SublayerConnection,self).__init__()
    144. # 实例化了一个 LN 对象
    145. self.norm = LayerNormalization(size)
    146. self.dropout = nn.Dropout(p = dropout)
    147. def forward(self,x,sublayer):
    148. """
    149. :param x: 接受上一个层或者子层的输入作为第一个参数
    150. :param sublayer: 该子层连接中的子层函数胡作为第二个参数
    151. :return:
    152. """
    153. "首先对输出进行规范化,然后将结果交给子层处理,之后对子层进行 dropout处理" \
    154. "随机失活一些神经元,来防止过拟合,最后还有一个add操作" \
    155. "因此存在跳跃连接,所以是将输入x与dropout后的子层输出结果相加作为最终的子层连接输出"
    156. return x + self.dropout(sublayer(self.norm(x)))
    157. class EncoderLayer(nn.Module):
    158. def __init__(self, size, self_attn, feed_forward, dropout):
    159. """
    160. :param size: 词嵌入的维度
    161. :param self_attn: 传入多头自注意力子层实例化对象,并且是自注意力机制
    162. :param feed_forward: 前馈全连接层实例化对象
    163. :param dropout: 置零比率
    164. """
    165. super(EncoderLayer,self).__init__()
    166. self.self_attn = self_attn
    167. self.feed_forward = feed_forward
    168. self.sublayer = nn.ModuleList([copy.deepcopy(SublayerConnection(size,dropout)) for _ in range(2)])
    169. self.size = size
    170. def forward(self, x, mask):
    171. """
    172. :param x: 上一层的输出
    173. :param mask: 掩码张量
    174. :return:
    175. """
    176. "首先通过第一个子层连接结构,其中包含多头自注意力子层"
    177. "然后通过第二个子层连接结构,其中包含前馈全连接子层,最后返回结果"
    178. x = self.sublayer[0](x,lambda x:self.self_attn(x,x,x,mask))
    179. return self.sublayer[1](x,self.feed_forward)
    1. import torch.nn as nn
    2. import copy
    3. class Encoder(nn.Module):
    4. def __init__(self, layer, N):
    5. """
    6. :param layer: 编码器层
    7. :param N: 编码器的个数
    8. """
    9. super(Encoder,self).__init__()
    10. self.layers = nn.ModuleList([copy.deepcopy(layer) for _ in range(N)])
    11. "初始化一个规范化层,用于最后的输出"
    12. self.norm = LayerNormalization(layer.size)
    13. def forward(self, x, mask):
    14. for layer in self.layers:
    15. x = layer(x,mask)
    16. return self.norm(x)
    17. # 实例化参数
    18. size = d_model = 512
    19. head = 8
    20. mask = torch.zeros(2,4,4)
    21. d_ff = 62
    22. dropout = 0.2
    23. max_len = 60 # 句子最大长度
    24. #-----------------------------------------词嵌入层
    25. # 输入 x 是 Embedding层输出的张量,形状为 2 * 4 * 512
    26. x = Variable(torch.LongTensor([[100,2,42,508],[491,998,1,221]]))
    27. emb = Embeddings(1000,512) # 嵌入层
    28. embr = emb(x)
    29. #-----------------------------------------位置编码
    30. pe = PositionalEncoding(d_model, dropout,max_len)
    31. pe_result = pe(embr)
    32. x = pe_result
    33. #-----------------------------------------多头注意力
    34. self_attn = MultiHeadAttention(head,d_model)
    35. #-----------------------------------------FFN
    36. ff = PositionFeedForward(d_model, d_ff, dropout)
    37. #-----------------------------------------编码器层
    38. layer = EncoderLayer(size, copy.deepcopy(self_attn), copy.deepcopy(ff),dropout)
    39. #-----------------------------------------编码器
    40. N = 8
    41. en = Encoder(layer, N)
    42. en_result = en(x,mask)
    43. print(en_result)
    44. print(en_result.shape)

    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    scores.shape  torch.Size([2, 8, 4, 4])
    tensor([[[ 0.8972,  1.1464, -2.2886,  ...,  0.4909,  0.5196,  0.0756],
             [-0.1428,  1.3504,  0.2368,  ..., -1.0005,  0.1516, -2.1761],
             [-0.0204,  0.1874, -0.7120,  ...,  1.0886,  0.5129,  1.1401],
             [-0.1354, -0.0835, -1.4364,  ...,  1.4079,  0.3481, -0.0844]],

            [[-1.5945, -0.1464,  0.3596,  ..., -1.7038,  1.0241, -0.0716],
             [-0.4506, -0.0832, -0.3198,  ...,  0.0378, -0.5991, -0.7635],
             [ 1.4974,  1.8504,  2.1817,  ..., -0.7692, -0.1072, -1.0367],
             [-0.2180, -0.8411,  0.1120,  ...,  1.3320, -0.3436, -0.3399]]],
           grad_fn=)
    torch.Size([2, 4, 512])

  • 相关阅读:
    来了~worthington组织培养术语第二弹!
    洛谷 P2183 [国家集训队]礼物)(扩展卢卡斯定理)
    RocketMQ事务消息机制
    GitHub星标超70K,阿里大佬的架构总结“分布式全解”笔记霸榜
    k8s-kubernetes常用命令,服务部署,可视化控制台安装及token的生成
    前端逆向之下载canvas引用的图片
    CTPN论文翻译与思考
    Linux中YUM仓库的配置
    “数字赋能、智创未来”第三届中国(宁波)软件峰会暨程序员节即将开启
    java基础面试
  • 原文地址:https://blog.csdn.net/qq_51691366/article/details/133785645