• Self-Attention和Multi-Head Attention的详细代码内容(没有原理)


    先看self-attention

    (原理可以参考这个:层层剖析,让你彻底搞懂Self-Attention、MultiHead-Attention和Masked-Attention的机制和原理_iioSnail的博客-CSDN博客_attention mask

    1. class SelfAttention(nn.Module):
    2. def __init__(self, input_vector_dim: int, dim_k=None, dim_v=None):
    3. """
    4. 初始化SelfAttention,包含如下关键参数:
    5. input_vector_dim: 输入向量的维度,对应上述公式中的d,例如你将单词编码为了10维的向量,则该值为10
    6. dim_k: 矩阵W^k和W^q的维度
    7. dim_v: 输出向量的维度,即b的维度,例如如果想让Attention后的输出向量b的维度为15,则定义为15,若不填,默认取取input_vector_dim
    8. """
    9. super(SelfAttention, self).__init__()
    10. self.input_vector_dim = input_vector_dim
    11. # 如果 dim_k 和 dim_v 为 None,则取输入向量的维度
    12. if dim_k is None:
    13. dim_k = input_vector_dim
    14. if dim_v is None:
    15. dim_v = input_vector_dim
    16. """
    17. 实际写代码时,常用线性层来表示需要训练的矩阵,方便反向传播和参数更新
    18. """
    19. self.W_q = nn.Linear(input_vector_dim, dim_k, bias=False)
    20. self.W_k = nn.Linear(input_vector_dim, dim_k, bias=False)
    21. self.W_v = nn.Linear(input_vector_dim, dim_v, bias=False)
    22. # 这个是根号下d_k
    23. self._norm_fact = 1 / np.sqrt(dim_k)
    24. def forward(self, x):
    25. """
    26. 进行前向传播:
    27. x: 输入向量,size为(batch_size, input_num, input_vector_dim)
    28. """
    29. # 通过W_q, W_k, W_v矩阵计算出,Q,K,V
    30. # Q,K,V矩阵的size为 (batch_size, input_num, output_vector_dim)
    31. Q = self.W_q(x)
    32. K = self.W_k(x)
    33. V = self.W_v(x)
    34. # permute用于变换矩阵的size中对应元素的位置,
    35. # 即,将K的size由(batch_size, input_num, output_vector_dim),变为(batch_size, output_vector_dim,input_num)
    36. # 0,1,2 代表各个元素的下标,即变换前,batch_size所在的位置是0,input_num所在的位置是1
    37. K_T = K.permute(0, 2, 1)
    38. # bmm是batch matrix-matrix product,即对一批矩阵进行矩阵相乘
    39. # bmm详情参见:https://pytorch.org/docs/stable/generated/torch.bmm.html
    40. atten = nn.Softmax(dim=-1)(torch.bmm(Q, K_T)) * self._norm_fact
    41. # 最后再乘以 V
    42. output = torch.bmm(atten, V)
    43. return output

    再看Multi-Head Attention

    MultiHead Attention在带入公式前做了一件事情,就是,它按照“词向量维度”这个方向,将Q,K,V拆成了多个头,如图所示:

    注意力计算机制与self-attention类似,但该方法会在词向量维度方向将Q、K、V分割成多个“head”,分别计算后再拼在一起。由于性能降低,需要再采用一个额外的Wo矩阵,对Attention再进行一次线性变换。

    1. def attention(query, key, value):
    2. """
    3. 计算Attention的结果。
    4. 这里其实传入的是Q,K,V,而Q,K,V的计算是放在模型中的,请参考后续的MultiHeadedAttention类。
    5. 这里的Q,K,V有两种Shape,如果是Self-Attention,Shape为(batch, 词数, d_model),
    6. 例如(1, 7, 128),即batch_size为1,一句7个单词,每个单词128维
    7. 但如果是Multi-Head Attention,则Shape为(batch, head数, 词数,d_model/head数),
    8. 例如(1, 8, 7, 16),即Batch_size为1,8个head,一句7个单词,128/8=16。
    9. 这样其实也能看出来,所谓的MultiHead其实就是将128拆开了。
    10. 在Transformer中,由于使用的是MultiHead Attention,所以Q,K,V的Shape只会是第二种。
    11. """
    12. # 获取d_model的值。之所以这样可以获取,是因为query和输入的shape相同,
    13. # 若为Self-Attention,则最后一维都是词向量的维度,也就是d_model的值。
    14. # 若为MultiHead Attention,则最后一维是 d_model / h,h为head数
    15. d_k = query.size(-1)
    16. # 执行QK^T / √d_k
    17. scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
    18. # 执行公式中的Softmax
    19. # 这里的p_attn是一个方阵
    20. # 若是Self Attention,则shape为(batch, 词数, 次数),例如(1, 7, 7)
    21. # 若是MultiHead Attention,则shape为(batch, head数, 词数,词数)
    22. p_attn = scores.softmax(dim=-1)
    23. # 最后再乘以 V。
    24. # 对于Self Attention来说,结果Shape为(batch, 词数, d_model),这也就是最终的结果了。
    25. # 但对于MultiHead Attention来说,结果Shape为(batch, head数, 词数,d_model/head数)
    26. # 而这不是最终结果,后续还要将head合并,变为(batch, 词数, d_model)。不过这是MultiHeadAttention
    27. # 该做的事情。
    28. return torch.matmul(p_attn, value)
    29. class MultiHeadedAttention(nn.Module):
    30. def __init__(self, h, d_model):
    31. """
    32. h: head的数量
    33. """
    34. super(MultiHeadedAttention, self).__init__()
    35. assert d_model % h == 0
    36. # We assume d_v always equals d_k
    37. self.d_k = d_model // h
    38. self.h = h
    39. # 定义W^q, W^k, W^v和W^o矩阵。
    40. # 如果你不知道为什么用nn.Linear定义矩阵,可以参考该文章:
    41. # https://blog.csdn.net/zhaohongfei_358/article/details/122797190
    42. self.linears = [
    43. nn.Linear(d_model, d_model),
    44. nn.Linear(d_model, d_model),
    45. nn.Linear(d_model, d_model),
    46. nn.Linear(d_model, d_model),
    47. ]
    48. def forward(self, x):
    49. # 获取Batch Size
    50. nbatches = x.size(0)
    51. """
    52. 1. 求出Q, K, V,这里是求MultiHead的Q,K,V,所以Shape为(batch, head数, 词数,d_model/head数)
    53. 1.1 首先,通过定义的W^q,W^k,W^v求出SelfAttention的Q,K,V,此时Q,K,V的Shape为(batch, 词数, d_model)
    54. 对应代码为 `linear(x)`
    55. 1.2 分成多头,即将Shape由(batch, 词数, d_model)变为(batch, 词数, head数,d_model/head数)。
    56. 对应代码为 `view(nbatches, -1, self.h, self.d_k)`
    57. 1.3 最终交换“词数”和“head数”这两个维度,将head数放在前面,最终shape变为(batch, head数, 词数,d_model/head数)。
    58. 对应代码为 `transpose(1, 2)`
    59. """
    60. query, key, value = [
    61. linear(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
    62. for linear, x in zip(self.linears, (x, x, x))
    63. ]
    64. """
    65. 2. 求出Q,K,V后,通过attention函数计算出Attention结果,
    66. 这里x的shape为(batch, head数, 词数,d_model/head数)
    67. self.attn的shape为(batch, head数, 词数,词数)
    68. """
    69. x = attention(
    70. query, key, value
    71. )
    72. """
    73. 3. 将多个head再合并起来,即将x的shape由(batch, head数, 词数,d_model/head数)
    74. 再变为 (batch, 词数,d_model)
    75. 3.1 首先,交换“head数”和“词数”,这两个维度,结果为(batch, 词数, head数, d_model/head数)
    76. 对应代码为:`x.transpose(1, 2).contiguous()`
    77. 3.2 然后将“head数”和“d_model/head数”这两个维度合并,结果为(batch, 词数,d_model)
    78. """
    79. x = (
    80. x.transpose(1, 2)
    81. .contiguous()
    82. .view(nbatches, -1, self.h * self.d_k)
    83. )
    84. # 最终通过W^o矩阵再执行一次线性变换,得到最终结果。
    85. return self.linears[-1](x)

  • 相关阅读:
    JVM学习总结笔记2
    【JAVA高级】——Druid连接池和Apache的DBUtils使用
    21-关键帧动画
    基于STM32F103ZET6库函数独立看门狗(IWDG)实验
    美团T3架构师推荐633页JavaEE核心框架实战
    UE4 Ultradynamicsky进行地面交互
    七月集训(第21天) —— 堆(优先队列)
    Selenium定位之find_element方法
    照片处理软件 DxO FilmPack 7 mac中文版软件介绍
    vue-3d-model更改模型的背景颜色
  • 原文地址:https://blog.csdn.net/m0_55097528/article/details/127759943