| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 人工智能 -> 深入浅出对话系统——自己实现Transformer -> 正文阅读 |
|
[人工智能]深入浅出对话系统——自己实现Transformer |
引言我们在上篇文章中学习了🤗的Transformer库,为了更好地理解Transformer,本文通过PyTorch实现一个Transformer,并完成中英文机器翻译任务。 导包与初始化
初始化参数设置:
数据预处理主要做的事是加载数据、分词、构建词表和批次划分。对中文语句一般以字为单位进行切分,所以不需要对中文语句分词。 训练数据是长这样子的:
首先我们按批次对数据进行填充,使得同一批次内的数据长度对齐。
不同批次内的最大长度可以不一致。
下面来准备数据:
数据准备好了,下面我们正式开始了解Transformer模型。 Transformer模型概述Transformer相比LSTM而言最大的优势在于可以并行训练,极大地加快计算效率。通过位置编码理解语言的顺序,使用自注意力机制和全连接层前向计算,整个架构中没有循环结构。 借鉴了seq2seq,Transformer模型由编码器和解码器组成。
下面我们来详细了解其中的组件。 词嵌入层在编码器和解码器中都有词嵌入层,用于将输入、输出ont-hot向量映射为词嵌入向量。可以随机初始化进行学习,也可以加载预训练的词向量。
位置编码由于Transformer的输入采用并行方式,缺少单词位置信息。因此需要增加包含单词位置信息的位置编码层。 位置编码(positional encoding):位置编码向量与词向量维度相同, max_seq_len × embedding_dim \text{max\_seq\_len} \times \text{embedding\_dim} max_seq_len×embedding_dim。 Transformer原文中使用正、余弦函数的线性变换对单词位置编码: PE p o s , 2 i = sin ? ( p o s 1000 0 2 i / d model ) PE ( p o s , 2 i + 1 ) = cos ? ( p o s 1000 0 2 i / d model ) (1) \text{PE}_{pos, 2i} = \sin \left( \frac{pos}{10000^{2i / d_{\text{model}}}} \right) \\ \text{PE}_{(pos,2i + 1)} = \cos \left( \frac{pos}{10000^{2i / d_{\text{model}}}} \right)\tag{1} PEpos,2i?=sin(100002i/dmodel?pos?)PE(pos,2i+1)?=cos(100002i/dmodel?pos?)(1) 为了使用序列顺序信息,作者提出了利用不同频率的正弦和余弦函数表示位置编码。序列顺序信息重要性是不言而喻的。比如以下两个句子:
作者用词嵌入向量?位置编码得到输入向量,这里简单解释一下为什么作者选用正弦和余弦函数。 假设我们自己设置位置编码,一个简单的办法是增加索引到词嵌入向量。 假设 a a a表示词嵌入向量。这种方法有一个很大的问题,即句子越长,后面单词的序号就越大,而且索引值过大,可能会掩盖了嵌入向量的“光辉”。 你说序号太大了,那么我把每个序号都除以句子长度总不大了吧。听起来不错,但是这引入了另一个问题,就是由于句子的长度不同,导致同样的值可能代表不同的意思,这样让我们的模型很困惑。比如 0.8 0.8 0.8在句长为 5 5 5的句子中表示第 4 4 4个单词,但是在句长为 20 20 20的句子中表示第 16 16 16个单词。 💡 因为我们上面句子长度为8, 2 3 = 8 2^3=8 23=8,何不用二进制来表示顺序信息呢?如上图所示。从上往下看,比如4对应“100”,5对应“101”。 这里我们用3位表示就足够了,一般我们可以设置成 d m o d e l d_{model} dmodel?。 那这种方法就很好了吗?
我们的位置编码应该满足下面的要求:
作者提出的编码方式是一个简单且天才的技术,满足了上面所有的要求。首先,它不是一个标量,而是一个包含特定位置信息的 d d d维向量。其次,该编码并没有整合到模型中。相反,这个向量用于为每个单词设置关于它在句子中位置的信息。换言之,通过注入单词的顺序来增强模型的输入。 令
t
t
t为输入序列中某个位置,
p
t
→
\overset{\rightarrow}{p_t}
pt?→?是该位置的位置编码,
d
d
d是向量维度。
f
f
f是通过以下公式产生位置编码向量的函数: 我们也能想象位置编码
p
t
?
\vec{p_t}
pt??是一个包含各个频率的正弦和余弦向量,其中
d
d
d可以被
2
2
2整除。 可以看到,随着十进制数的增加,每个位的变化率是不一样的,越低位的变化越快,红色位 0 0 0和 1 1 1,每个数字都会变化一次; 而黄色位,每 8 8 8个数字才会变化一次。 但是二进制值的 0 , 1 0,1 0,1是离散的,浪费了它们之间无限的浮点数。所以我们使用它们的连续浮动版本-正弦函数。 此外,通过降低它们的频率,我们可以从红色位变成黄色位,这样就实现了这种低位到高位的变换。如下图所示: 下面补充一下波长和频率的计算: 对于正弦函数来说,波长(周期)的计算如上图。任意 sin ? ( B x ) \sin (Bx) sin(Bx)的波长是 2 π B \frac{2\pi}{B} B2π?,频率是 B 2 π \frac{B}{2\pi} 2πB?。 最后,通过设置位置编码的维度和词嵌入向量的维度一致,可以将位置编码加入到词向量。 原文中提到
对每个频率
ω
k
\omega_k
ωk?相应的正-余弦对,存在一个线性转换
M
∈
R
2
×
2
M \in \mathbb{R}^{2\times2}
M∈R2×2: 证明: 假设
M
M
M是一个
2
×
2
2 \times 2
2×2的矩阵,我们想要找到其中的元素
u
1
,
v
1
,
u
2
,
v
2
u_1,v_1,u_2,v_2
u1?,v1?,u2?,v2?满足:
[
u
1
v
1
u
2
v
2
]
?
[
sin
?
(
ω
k
?
t
)
cos
?
(
ω
k
?
t
)
]
=
[
sin
?
(
ω
k
?
t
)
cos
?
(
ω
k
?
?
)
+
cos
?
(
ω
k
?
t
)
sin
?
(
ω
k
?
?
)
cos
?
(
ω
k
?
t
)
cos
?
(
ω
k
?
?
)
?
sin
?
(
ω
k
?
t
)
sin
?
(
ω
k
?
?
)
]
\begin{bmatrix} u_1 & v_1 \\ u_2 & v_2 \end{bmatrix} \cdot \begin{bmatrix} \sin(\omega_k \cdot t) \\ \cos(\omega_k \cdot t) \end{bmatrix} = \begin{bmatrix} \sin(\omega_k \cdot t)\cos(\omega_k \cdot \phi) + \cos(\omega_k \cdot t)\sin(\omega_k \cdot \phi) \\ \cos(\omega_k \cdot t)\cos(\omega_k \cdot \phi) - \sin(\omega_k \cdot t)\sin(\omega_k \cdot \phi) \end{bmatrix}
[u1?u2??v1?v2??]?[sin(ωk??t)cos(ωk??t)?]=[sin(ωk??t)cos(ωk???)+cos(ωk??t)sin(ωk???)cos(ωk??t)cos(ωk???)?sin(ωk??t)sin(ωk???)?] 类似地,我们可以找到其他正-余弦对的 M M M,最终允许我们表示 p t + ? ? \vec{p_{t+\phi}} pt+???为一个 p t ? \vec{p_t} pt??对任意固定偏移量 ? \phi ?的线性函数。这个属性,使模型很容易学得相对位置信息。 这解释了为什么要选择交替的正弦和余弦函数,仅通过正弦或余弦函数达不到这一点。 我们实现位置编码如下:
编码器
在Transformer块中主要有多头注意力、残差连接、层归一化与前馈神经网络。我们分别来看下。 自注意力我们现在有了词向量句子和位置嵌入,假设我们有一些句子
X
X
X,其形状为 接着,为了学到多重含义的表达,对
X
e
m
b
e
d
d
i
n
g
X_{embedding}
Xembedding?做线性映射,分别乘上三个权重
W
Q
,
W
K
,
W
V
∈
R
e
m
b
e
d
_
d
i
m
×
e
m
b
e
d
_
d
i
m
W_Q,W_K,W_V \in \Bbb R^{embed\_dim \times embed\_dim}
WQ?,WK?,WV?∈Rembed_dim×embed_dim: 然后准备进行多头注意力,为什么是多头的呢? 上图最后我们把嵌入维度分割成了
h
h
h份,分割后
Q
,
K
,
V
Q,K,V
Q,K,V的维度为
注意力矩阵的第一行代表第一个词与这六个词的哪几个比较相关。 注意进行点积运算之后,
V
V
V的维度没有变化,还是 下面给一个简单的例子:
下面我们来实现多头注意,首先实现克隆帮助函数:
然后实现缩放注意力计算函数:
最后来实现多头注意力层:
层归一化层归一化针对每个输入的每个维度进行归一化操作。假设有 H H H个维度, x = ( x 1 , x 2 , ? ? , x H ) x=(x_1,x_2,\cdots,x_H) x=(x1?,x2?,?,xH?),层归一化首先计算这 H H H个维度的均值和方差,然后进行归一化得到 N ( x ) N(x) N(x),接着做一个缩放,类似批归一化。
μ
=
1
H
∑
i
=
1
H
x
i
,
σ
=
1
H
∑
i
=
1
H
(
x
i
?
μ
)
2
,
N
(
x
)
=
x
?
μ
σ
,
h
=
α
?
⊙
N
(
x
)
+
β
(5)
\mu = \frac{1}{H}\sum_{i=1}^H x_i,\quad \sigma = \sqrt{\frac{1}{H}\sum_{i=1}^H (x_i - \mu)^2}, \quad N(x) = \frac{x-\mu}{\sigma},\quad h = \alpha \,\odot N(x) + \beta \tag{5}
μ=H1?i=1∑H?xi?,σ=H1?i=1∑H?(xi??μ)2?,N(x)=σx?μ?,h=α⊙N(x)+β(5)
残差连接假设网络中某层输入 x x x后的输出为 F ( x ) F(x) F(x),不管激活函数是什么,经过深层网络都可能导致梯度消失的情况。增加残差连接,相当于某层输入 x x x后的输出为 F ( x ) + x F(x) + x F(x)+x。最坏的情况相当于没有经过 F ( x ) F(x) F(x)这一层,直接输入到高层,这样高层的表现至少能和低层一样好。 这里 SubLayer \text{SubLayer} SubLayer就是上面说的 F F F。训练时,梯度通过捷径直接反向传播至前层 x + SubLayer ( x ) (6) \mathbf{x} + \text{SubLayer}(\mathbf{x}) \tag{6} x+SubLayer(x)(6) 其中,
SubLayer
\text{SubLayer}
SubLayer表示
前馈网络前馈网络(Feed Forward)为两层线性映射及激活函数。
编码器整体架构Transformer编码器基本单元由两个子层组成:第一个子层实现多头 自注意力(self-attention机制(Multi-Head Attention);第二个子层实现全连接前馈网络。计算过程如下:
X
=
EmbeddingLookup
(
X
)
+
PositionalEncoding
(7)
X = \text{EmbeddingLookup}(X) + \text{PositionalEncoding} \tag{7}
X=EmbeddingLookup(X)+PositionalEncoding(7)
Q = Linear ( X ) = X W Q Q = \text{Linear}(X) = X W_{Q} Q=Linear(X)=XWQ? K = Linear ( X ) = X W K (8) K = \text{Linear}(X) = XW_{K} \tag{8} K=Linear(X)=XWK?(8) V = Linear ( X ) = X W V V = \text{Linear}(X) = XW_{V} V=Linear(X)=XWV? X attention = SelfAttention ( Q , K , V ) (9) X_{\text{attention}} = \text{SelfAttention}(Q, K, V) \tag{9} Xattention?=SelfAttention(Q,K,V)(9)
X attention = LayerNorm ( X attention ) (10) X_{\text{attention}} = \text{LayerNorm}(X_{\text{attention}}) \tag{10} Xattention?=LayerNorm(Xattention?)(10) X attention = X + X attention (11) X_{\text{attention}} = X + X_{\text{attention}} \tag{11} Xattention?=X+Xattention?(11)
X hidden = Linear ( Activate ( Linear ( X attention ) ) ) (12) X_{\text{hidden}} = \text{Linear}(\text{Activate}(\text{Linear}(X_{\text{attention}}))) \tag{12} Xhidden?=Linear(Activate(Linear(Xattention?)))(12)
X hidden = LayerNorm ( X hidden ) (13) X_{\text{hidden}} = \text{LayerNorm}(X_{\text{hidden}}) \tag{13} Xhidden?=LayerNorm(Xhidden?)(13)
X
hidden
=
X
attention
+
X
hidden
(14)
X_{\text{hidden}} = X_{\text{attention}} + X_{\text{hidden}} \tag{14}
Xhidden?=Xattention?+Xhidden?(14) Transformer编码器由 N = 6 N = 6 N=6个编码器基本单元组成。 然后基于上面的构建,我们来实现编码器层:
而编码器就是 N N N个编码器层的叠加:
编码器了解完了,下面我们来看解码器。 解码器
从上图可以看到,解码器有3个子层(SubLayer)。 解码器基本单元的输入、输出:
此外,解码器的输出(最后一个解码器基本单元的输出)需要经线性变换和softmax函数映射为下一时刻预测单词的概率分布。 解码器解码过程:给定编码器输出(编码器输入语句所有单词的词向量)和解码器前一时刻输出(单词),预测当前时刻单词的概率分布。 注意:训练过程中,编、解码器均可以并行计算(训练语料中已知前一时刻单词);推理过程中,编码器可以并行计算,解码器需要像RNN一样依次预测输出单词。 下面我们看一下解码器层的实现:
解码器的实现,主要是克隆了 N N N个解码器层,最后的输出经过层归一化:
解码器最上面的部分作为生成器(generator),由线性层+Softmax层组成:
基本上介绍的差不多了,除了还有一个细节——注意力掩码。 注意力掩码注意力掩码在编码器和解码器中作用不同。 编码器注意力掩码我们知道,自然语言处理中,文本语句批次数据内通常长度不一,因此需要为短语句进行填充,而填充的字符是不需要参与注意力计算的。 因此,编码器注意力掩码的目地:使批次中较短语句的填充部分不参与注意力计算。 模型训练通常按批次进行,同一批次中的语句长度可能不同,因此需要按语句最大长度对短语句进行0填充以补齐长度。语句填充部分属于无效信息,不应参与前向传播,考虑softmax函数特性, softmax ( z ) i = exp ? ( z i ) ∑ j = 1 K exp ? ( z j ) (15) \text{softmax}(\mathbf {z})_{i} = {\frac{\exp(z_{i})}{\sum _{j = 1}^{K} \exp(z_{j})}} \tag{15} softmax(z)i?=∑j=1K?exp(zj?)exp(zi?)?(15) 当 z i z_{i} zi?为填充时,可令 z i = ? ∞ z_{i} = - \infty zi?=?∞(一般取很大的负数)使其无效,即 z pad = ? ∞ ? exp ? ( z pad ) = 0 z_{\text{pad}} = - \infty \quad \Rightarrow \quad \exp(z_{\text{pad}}) = 0 zpad?=?∞?exp(zpad?)=0
接下来我们看下解码器注意力掩码。 解码器注意力掩码解码器注意力掩码相对于编码器略微复杂,不仅需要将填充部分屏蔽掉,还需要对当前及后续序列进行屏蔽(
当
当
当
注意力
比如第0行,只能看到1列,第1行只能看到2列。黄色的区域代表可以看到的列。 本小节最后,我们就可以实现批次类,
Transformer模型大部分有竞争力的神经网络序列转导模型都有一个编码器-解码器(Encoder-Decoder)结构。编码器映射一个用符号表示的输入序列 ( x 1 , ? ? , x n ) (x_1,\cdots,x_n) (x1?,?,xn?)到一个连续的序列表示 z = ( z 1 , ? ? , z n ) z=(z_1,\cdots, z_n) z=(z1?,?,zn?)。给定 z z z,解码器生成符号的一个输出序列 ( y 1 , ? ? , y m ) (y_1,\cdots,y_m) (y1?,?,ym?),一次生成一个元素。在每个时间步,模型是自回归(auto-regressive)的,在生成下个输出时消耗上一次生成的符号作为附加的输入。 实际上Transformer就是一编码器-解码器架构。
然后我们实现构建Transformer模型的函数:
模型训练标签平滑训练过程中,采用KL散度损失实现标签平滑( ? l s = 0.1 \epsilon_{ls} = 0.1 ?ls?=0.1)策略,提高模型鲁棒性、准确性和BLEU分数。 标签平滑:输出概率分布由one-hot方式转为真实标签的概率置为
标签平滑的例子:
计算损失
优化器Adam优化器, β 1 = 0.9 、 β 2 = 0.98 \beta_1=0.9、\beta_2=0.98 β1?=0.9、β2?=0.98 和 ? = 1 0 ? 9 \epsilon = 10^{?9} ?=10?9,并使用warmup策略调整学习率: l r = d model ? 0.5 min ? ( step_num ? 0.5 , step_num × warmup_steps ? 1.5 ) lr = d_{\text{model}}^{?0.5} \min(\text{step\_num}^{?0.5}, \text{step\_num} \times \text{warmup\_steps}^{?1.5}) lr=dmodel?0.5?min(step_num?0.5,step_num×warmup_steps?1.5) 使用固定步数 warmup_steps \text{warmup\_steps} warmup_steps先使学习率的线性增长(热身),而后随着 step_num \text{step\_num} step_num的增加以 step_num \text{step\_num} step_num的反平方根成比例逐渐减小学习率。
主要调节是在 r a t e rate rate 这个函数中,其中
以下对该优化器在不同模型大小( m o d e l _ s i z e model\_size model_size)和不同超参数( m a r m u p marmup marmup)值的情况下的学习率( l r a t e lrate lrate)曲线进行示例。
训练接下来,我们创建一个通用的训练和评分功能来跟踪损失。 我们传入一个上面定义的损失计算函数,它也处理参数更新。
然后就可以进行训练了
模型预测训练好了之后,我们用模型进行预测来看一下效果:
参考
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/31 7:09:31- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |