赞
踩
初始化阶段,
其中要注意的是 hid_dim要和Q、K、V词向量的长度相等
- import torch
- from torch import nn
-
-
- class MultiheadAttention(nn.Module):
- # n_heads:多头注意力的数量
- # hid_dim:每个词输出的向量维度
- def __init__(self, hid_dim, n_heads, dropout):
- super(MultiheadAttention, self).__init__()
- self.hid_dim = hid_dim
- self.n_heads = n_heads
-
- # 强制 hid_dim 必须整除 h
- assert hid_dim % n_heads == 0
- # 定义 W_q 矩阵
- self.w_q = nn.Linear(hid_dim, hid_dim)
- # 定义 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.do = nn.Dropout(dropout)
- # 缩放
- self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))
前向传播阶段:
其中要注意的是需要将Q、K、V拆分为多组(num_head组)注意力【通过增加一个维度实现】,每组注意力词向量的长度为原来的 1/num_head 倍
- def forward(self, query, key, value, mask=None):
- # K: [64,10,300], batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
- # V: [64,10,300], batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
- # Q: [64,12,300], batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
- bsz = query.shape[0]
- Q = self.w_q(query)
- K = self.w_k(key)
- V = self.w_v(value)
- # 这里把 K Q V 矩阵拆分为多组注意力,变成了一个 4 维的矩阵(增加了一个维度)
- # 最后一维就是是用 self.hid_dim // self.n_heads 来得到的,表示每组注意力的向量长度, 每个 head 的向量长度是:300/6=50
- # 64 表示 batch size,6 表示有 6组注意力,10 表示有 10 词,50 表示每组注意力的词的向量长度
- # Q: [64,12,300] 拆分多组注意力 -> [64,12,6,50] 转置得到 -> [64,6,12,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]
- # 转置是为了把注意力的数量 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
- if mask is not None:
- attention = attention.masked_fill(mask == 0, -1e10)
-
- # 第 2 步:计算上一步结果的 softmax,再经过 dropout,得到 attention。
- # 注意,这里是对最后一维做 softmax,也就是在输入序列的维度做 softmax
- # attention: [64,6,12,10]
- attention = self.do(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 放到前面,把 6 和 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
测试使用:
- # batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
- query = torch.rand(64, 12, 300)
- # batch_size 为 64,有 10 个词,每个词的 Key 向量是 300 维
- key = torch.rand(64, 10, 300)
- # batch_size 为 64,有 10 个词,每个词的 Value 向量是 300 维
- value = torch.rand(64, 10, 300)
- # 1、初始化过程(最终输出的维度,这个维度需要和词向量的维度一样(也就是要和Q、K、V的词向量维度一致,即hid_dim要和Q、K、V的词向量维度相等=300))
- attention = MultiheadAttention(hid_dim=300, n_heads=6, dropout=0.1)
- # 2、forward过程
- output = attention(query, key, value)
- ## output: torch.Size([64, 12, 300])
- print(output.shape)
在实际中,K、V 矩阵的序列长度是一样的,而 Q 矩阵的序列长度可以不一样。
这种情况发生在:在解码器部分的Encoder-Decoder Attention
层中,Q 矩阵是来自解码器下层,而 K、V 矩阵则是来自编码器的输出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。