Transformer 结构分析
self-attetion
1. 输入
X.shape == (batch\_size, seq\_len, embedding\_dim)
\]
2. 计算Q,K,V
K = Linear(X) = XW_{K} \\
V = Linear(X) = XW_{V} \\
\\
W == (embedding\_dim, embedding\_dim) \\
Q, K, V == (batch\_size, seq\_len, embedding\_dim)
\]
3. 处理多头
将最后一维(embedding_dim) 拆成h份,需要保证embedding_dim能够被h整除。每个tensor的最后两个维度表示一个头,QKV各自都有h个头,接下来需要把这些头分别进行计算

4. 计算
按顺序取出上图中的一组QKV,计算:
\]
\]
\]
\]
(1)计算得到各个字之间的关系(相似度).这里的d的维度是
(batch_size, h, seq_len, embedding_dim) * (batch_size, h, embedding_dim, seq_len)==>(batch_size, h, seq_len, seq_len)。QKV分别有batch_size * h个矩阵,可以认为是在一个(batch_size, h)的棋盘中,每个位置放置了一个大小为(seq_len, embedding_dim)的矩阵。这里的前两个维度不变只是把棋盘中对应位置的矩阵拿出来做矩阵乘法,并把结果再放回到棋盘中。(2)用mask矩阵遮盖掉超出句子长度的部分。将句子中用来pading的字符全部替换成 inf, 这样 计算softmax的时候它们的值会为0,就不会参与到接下来与V的计算当中
(3) \(d_k\) 是为了改变已经偏离的方差。我的理解是,由于矩阵转置后相乘会有很多内积运算,而内积运算将\(d_k\)个数相加时会改变数据的分布。而这个分布的趋势是 \(mean=0, variance=d_k\)。为了使方差回归到1,把所有结果都除上一个\(\sqrt{d_k}\),这样求平方时会抵消已有的方差\(d_k\)
# 均值为0,方差为1
a = np.random.randn(2,3000)
b = np.random.randn(3000,2)
c = a.dot(b) print(np.var(a))
print(np.mean(c))
print(np.var(c)) # 1.0262973662546435
# 25.625943965792157
# 1347.432397285718
To illustrate why the dot products get large, assume that the components of q and k are independent random variables with > mean 0 and variance 1. Then their dot product, \(q \cdot k=\sum_{i=1}^{d_{k}} q_{i} k_{i}\), has mean 0 and variance dk.
(4)计算各个词义所占的比例 \(d \cdot v\),按照权重融合了各个字的语义。最后将多个头的结果拼接成一个完成的embedding作为self-attendion的输出。
(batch_size, h, seq_len, seq_len)*batch_size, h, seq_len, embedding/h
部分代码如下:
# (batch, seq_len, h, embed/head) -> (batch, h, seq_len, embed/head)
q = self.qry(y).view(y.size(0), y.size(1), self.head, -1).transpose(1, 2)
k = self.key(x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
v = self.val(x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
d = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(k.size(-1)) # 相似度 (batch , h, seq, seq)
d = d.masked_fill(m, -float('inf')) # 把所有为true的地方替换成inf,这里是遮盖掉句子内部的pad
a = F.softmax(d, dim=-1) # (batch , h, seq, seq)
# (batch , h, seq_len, seq_len) * (batch, h, seq_len, embedding/h)
# => (batch, h, seq_len, embedding/h)
# => (batch, seq_len, h, embedding/h)
c = torch.matmul(a, v).transpose(1, 2)
# (batch, seq_len, embedding)
c = c.contiguous().view(c.size(0), c.size(1), -1)
结构图

Encoder的完整过程:
1). 字向量与位置编码:
\]
\]
2). 自注意力机制:
\]
\]
\]
\]
3). 残差连接与\(Layer \ Normalization\)
\]
\]
4). 两层线性映射并用激活函数激活, 比如说\(ReLU\):
\]
5). 重复3).:
\]
\]
\]
Decoder 的完整过程
1). 输入数据
- 输入y的embedding:
X \in ({batch\_size * seq\_len * embed\_dim} )
\]
- encoder层的输出
h \in (batch\_size * seq\_len * embed\_dim)
\]
- mx: x的mask;遮盖住pad的部分,替换为inf,这样计算softmax就会变成0,不会影响后面的计算
def get_pad(self, x):
"""
根据句子的实际长度获取句子的句子的mask。用于计算attention的mask,它不是对角矩阵
维度是 (batch, head, seq_len, seq_len)
:param x:
:return: mask (batch, head, seq_len, seq_len)
"""
seq_len = x.size(1)
pad = (x == 0)
for _ in range(2):
pad = torch.unsqueeze(pad, dim=1)
return pad.repeat(1, self.head, seq_len, 1)
- my: y的mask;用于mask-self-attention,先经过和x的一样的mask过程,再用对角矩阵进行mask,这样在进行训练的时候,只能看到当前字和当前字之前的字。这里的mask是一个对角矩阵,它的形状类似下面这样:
torch.triu(torch.ones(seq_len, seq_len).byte(), diagonal=1) # [0, 1, 1, 1, 1, 1, 1, 1, 1, 1],
# [0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
# [0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
# [0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
# [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
# [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
# [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
# [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def get_att(head: int, seq_len: int):
"""
计算mask self attention的mask,对角矩阵
:param head: int
:param seq_len: int
:return:
"""
# 上三角矩阵, 不保留对角线
att = torch.triu(torch.ones(seq_len, seq_len).byte(), diagonal=1)
for _ in range(2):
# torch.squeeze() 删掉维度为1的维度:(1,3)==> (3)
# torch.unsqueeze() 扩充维度,在指定位置加上维数为1的维度:(3)==> (1,3)
att = torch.unsqueeze(att, dim=0)
# 像瓦片一样平铺
return att.repeat(1, head, 1, 1)
2). 多层 decoder Layer结构
- mask-self-attention + 残差 + LayerNorm; y经过mask之后含义已经改变,每一行表示当前词和之前的语义,表示的是某一时刻的可以获得的语义。比如0时刻只能获得第一个单词的语义,而第二个时刻可以获得前两个单词的语义。
mask_self_attention得到的结果,每一行就是一个时刻包含的语义关系,表示我当前已经翻译出的单词的语义。
y = LayerNorm(y + r)
\]
- self-attention + 残差 + LayerNorm,这里每一层decoder layer的数据都来自encoder的输出x,x经过变换生成K,V,用当前的y计算得到Q。然后计算Q和K的相似度再应用到V上就是结果; 这里的 \(Q_y, K_x, V_x\)就类似于seq2seq中的attention,把每个时刻的y和所有的x进行内积运算,找到每个x的权重再从所有的x中抽取需要的信息。一个\(Q_y\)已经包含了decoder中的所有时刻。最后得到的结果表示的是,每个时刻应该从encoder中抽取哪些信息。\(y_0\)的shape是
(batch_size, h, seq_len, embedding/h).
y = LayerNorm(y + r)
\]
- 激活层:
y = LayerNorm(y_0 + y)
\]
class DecodeLayer(nn.Module):
def __init__(self, embed_len, head):
super(DecodeLayer, self).__init__()
self.head = head
self.qrys = nn.ModuleList([nn.Linear(embed_len, embed_len / head) for _ in range(2)])
self.keys = nn.ModuleList([nn.Linear(embed_len, embed_len / head) for _ in range(2)])
self.vals = nn.ModuleList([nn.Linear(embed_len, embed_len / head) for _ in range(2)])
self.lal = nn.Sequential(nn.Linear(embed_len, embed_len),
nn.ReLU(),
nn.Linear(embed_len, embed_len))
self.lns = nn.ModuleList([nn.LayerNorm(embed_len) for _ in range(3)])
def mul_att(self, x, y, m, i):
# q (batch, seq_len, head, embed/head) -> (batch, head, seq_len, embed/head)
q = self.qrys[i](y).view(y.size(0), y.size(1), self.head, -1).transpose(1, 2)
k = self.keys[i](x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
v = self.vals[i](x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
# (batch, head, seq_len, embed/head)
d = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(k.size(-1))
d = d.masked_fill(m, -float('inf'))
a = F.softmax(d, dim=-1)
# (batch , h, seq_len, seq_len) * (batch, h, seq_len, embedding/h)
# => (batch, h, seq_len, embedding/h)
# => (batch, seq_len, h, embedding/h)
c = torch.matmul(a, v).transpose(1, 2)
c = c.contiguous().view(c.size(0), c.size(1), -1)
return c
def forward(self, y, x, my, mx):
"""
:param y: 带上positional encoder的embedding。 (batch, seq_len, embedding)
:param x: encoder的输出 (batch, seq_len, embedding)
:param my: y 的mask (batch, head, seq_len, seq_len)
:param mx: x 的mask (batch, head, seq_len, seq_len)
:return:
"""
r = y # 暂时保存用于计算残差网络
y = self.mul_att(y, y, my, 0)
y = self.lns[0](y + r)
r = y
y = self.mul_att(x, y, mx, 1)
y = self.lns[1](y + r)
r = y
y = self.lal(y)
return self.lns[2](y + r)
3)输出:
logits = softmax(y)
\]
待补充
Transformer 结构分析的更多相关文章
- Spatial Transformer Networks(空间变换神经网络)
Reference:Spatial Transformer Networks [Google.DeepMind]Reference:[Theano源码,基于Lasagne] 闲扯:大数据不如小数据 这 ...
- ABBYY PDF Transformer+怎么标志注释
ABBYY PDF Transformer+是一款可创建.编辑.添加注释及将PDF文件转换为其他可编辑格式的通用工具,可用来在PDF页面的任何位置添加注释(关于如何通过ABBYY PDF Transf ...
- OAF_文件系列6_实现OAF导出XML文件javax.xml.parsers/transformer(案例)
20150803 Created By BaoXinjian
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- Facebook的体系结构分析---外文转载
Facebook的体系结构分析---外文转载 From various readings and conversations I had, my understanding of Facebook's ...
- Android项目目录结构分析
Android项目目录结构分析 1.HelloWorld项目的目录结构1.1.src文件夹1.2.gen文件夹1.3.Android 2.1文件夹1.4.assets 1.5.res文件夹1.6.An ...
- 【转载】nedmalloc结构分析
原文:nedmalloc结构分析 nedmalloc是一个跨平台的高性能多线程内存分配库,很多库都使用它,例如:OGRE.现在我们来看看nedmalloc的实现 (以WIN32部分为例) 位操作 ...
- 如何用Transformer+从PDF文档编辑数据
ABBYY PDF Transformer+是一款可创建.编辑.添加注释及将PDF文件转换为其他可编辑格式的通用工具,可使用该软件从PDF文档编辑机密信息,然后再发布它们,文本和图像均可编辑,本文将为 ...
- ABBYY PDF Transformer+ Pro支持全世界189种语言
ABBYY PDF Transformer+ Pro版支持189种语言,包括我们人类的自然语言.人造语言以及正式语言.受支持的语言可能会因产品的版本不同而各异.本文具体列举了所有ABBYY PDF T ...
随机推荐
- web监听器解析
监听器是web三大组件之一,事件监听机制如下: 事件:某个事件,如果初始化上下文 事件源:事件发生的地方 监听器:一个对象,拥有需要执行的逻辑 注册监听:将事件.事件源.监听器绑定在一起.当事件源发生 ...
- 历时2月,动态线程池 DynamicTp 发布里程碑版本 V1.0.8
关于 DynamicTp DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为动态调参.通知报警.运行监控.三方包线程池管理等几大类. 经过多个版本迭代,目前最新版 ...
- Aoac唤醒的软件方案
Aoac唤醒 这篇文章包含了Win10下的Aoac唤醒和Win11下的Aoac唤醒的不同地方,在结尾处: 从Win10时代开始,通过软件来唤醒机器是一个很常见的事情了,但是很少有文档去将如何通过AOA ...
- 【MySQL】从入门到掌握2-下载安装
上期:[MySQL]从入门到掌握1-一些背景知识 第一章:下载 官网下载地址: https://dev.mysql.com/downloads/mysql/ https://dev.mysql.com ...
- 第二十四篇:对于dom的理解
好家伙, HTML CSS JS structure style function 结构体 样式 功能 <> ...
- wsl创建证书让chrome浏览器识别
我在wsl里面测试http2协议的时候,在wsl用openssl创建了证书, 然后我把证书放在windows证书管理后,用chrome打开发现提示这个: 虽然可以点击下面的强制 访问 但是chrome ...
- KingbaseES blob 类型数据导入导出
KingbaseES兼容了oracle的blob数据类型.通常是用来保存二进制形式的大数据,也可以用来保存其他类型的数据. 下面来验证一下各种数据存储在数据库中形式. 建表 create table ...
- KingbaseES 数据库本地化配置 LC_CTYPE 和 LC_COLLATE
区域支持指的是应用遵守文化偏好的问题,包括字母表.排序.数字格式等.PostgreSQL使用服务器操作系统提供的标准 ISO C 和POSIX的区域机制.更多的信息请参考你的系统的文档. 概述 区域支 ...
- 如何使用U盘重装Windows7系统?
一.重装步骤 第一步 将U盘制作为启动盘. 备注:推荐使用比较纯净的制作工具,如开源工具Rufus制作USB启动盘. 第二步 进入BIOS界面,选择U盘启动. 备注:不同的电脑进入BIOS界面的方式不 ...
- Servlet小结
1.sevlet的生命周期 用户在发送第一次请求的时候Servlet对象被实例化(AServlet的构造方法被执行了.并且执行的是无参数构造方法.) AServlet对象被创建出来之后,Tomcat服 ...