LLM系列:4.transformer算法:1.Transformer原理(中)

③.带掩码的自注意力层(Masked Self-Attention}
<1>.公式

因为采用 Teacher Forcing 导致模型一次性看见了所有答案,必须利用掩码(Mask)防止信息作弊。掩码操作会在求出的注意力得分矩阵(QK^T)上,将当前词之后(即未来时间步)的位置填充为负无穷,经 Softmax 后权重变为 0。 这确保了模型在预测位置 ttt 的单词时,只能依靠 ttt 之前的信息。

掩码自注意力机制是通过修改基本的注意力机制公式来实现的,基本的注意力公式如下:

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V) = softmax(\frac{QK^{T}}{\sqrt{d_k}})VAttention(Q,K,V)=softmax(dkQKT)V

在这个公式的基础上引入掩码功能,则涉及到下面三个改变:

Attention(Q,K,V)=softmax(QKTdk+M)VAttention(Q,K,V) = softmax(\frac{QK^{T}}{\sqrt{d_k}} + M)VAttention(Q,K,V)=softmax(dkQKT+M)V

MMM是掩码矩阵

  1. 在计算 QKTQK^TQKT 的点积后,但在应用softmax函数之前,掩码自注意力机制通过使用一个掩码矩阵来修改这个点积结果。这个掩码矩阵有特定的结构:对于不应该被当前位置注意的所有位置(即未来的位置),掩码会赋予一个非常大的负值(如负无穷)。

  2. 应用softmax函数:当softmax函数应用于经过掩码处理的点积矩阵时,那些被掩码覆盖的位置(即未来的位置)的权重实际上会接近于零。这是因为 e 的非常大的负数次幂几乎为零。

  3. 结果的动态调整:这样处理后,每个位置的输出在计算时只会考虑到它前面的位置或当前位置的信息,确保了生成的每一步都不会“看到”未来的数据。

我们可以来看具体的矩阵——

  • 没有掩码时的QKTQK^TQKT点积(此时的Q、K都是从输出矩阵中生成的)

QKT=[q1⋅k1Tq1⋅k2T⋯q1⋅knTq2⋅k1Tq2⋅k2T⋯q2⋅knT⋮⋮⋱⋮qn⋅k1Tqn⋅k2T⋯qn⋅knT] QK^T = \begin{bmatrix} q_1 \cdot k_1^T & q_1 \cdot k_2^T & \cdots & q_1 \cdot k_n^T \\ q_2 \cdot k_1^T & q_2 \cdot k_2^T & \cdots & q_2 \cdot k_n^T \\ \vdots & \vdots & \ddots & \vdots \\ q_n \cdot k_1^T & q_n \cdot k_2^T & \cdots & q_n \cdot k_n^T \end{bmatrix} QKT=q1k1Tq2k1Tqnk1Tq1k2Tq2k2Tqnk2Tq1knTq2knTqnknT

  • 没有掩码时softmax函数结果

softmax(QKT)=[eq1⋅k1T∑j=1neq1⋅kjTeq1⋅k2T∑j=1neq1⋅kjT⋯eq1⋅knT∑j=1neq1⋅kjTeq2⋅k1T∑j=1neq2⋅kjTeq2⋅k2T∑j=1neq2⋅kjT⋯eq2⋅knT∑j=1neq2⋅kjT⋮⋮⋱⋮eqn⋅k1T∑j=1neqn⋅kjTeqn⋅k2T∑j=1neqn⋅kjT⋯eqn⋅knT∑j=1neqn⋅kjT] softmax(QK^T) = \begin{bmatrix} \frac{e^{q_1 \cdot k_1^T}}{\sum_{j=1}^n e^{q_1 \cdot k_j^T}} & \frac{e^{q_1 \cdot k_2^T}}{\sum_{j=1}^n e^{q_1 \cdot k_j^T}} & \cdots & \frac{e^{q_1 \cdot k_n^T}}{\sum_{j=1}^n e^{q_1 \cdot k_j^T}} \\ \frac{e^{q_2 \cdot k_1^T}}{\sum_{j=1}^n e^{q_2 \cdot k_j^T}} & \frac{e^{q_2 \cdot k_2^T}}{\sum_{j=1}^n e^{q_2 \cdot k_j^T}} & \cdots & \frac{e^{q_2 \cdot k_n^T}}{\sum_{j=1}^n e^{q_2 \cdot k_j^T}} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{e^{q_n \cdot k_1^T}}{\sum_{j=1}^n e^{q_n \cdot k_j^T}} & \frac{e^{q_n \cdot k_2^T}}{\sum_{j=1}^n e^{q_n \cdot k_j^T}} & \cdots & \frac{e^{q_n \cdot k_n^T}}{\sum_{j=1}^n e^{q_n \cdot k_j^T}} \end{bmatrix} softmax(QKT)=j=1neq1kjTeq1k1Tj=1neq2kjTeq2k1Tj=1neqnkjTeqnk1Tj=1neq1kjTeq1k2Tj=1neq2kjTeq2k2Tj=1neqnkjTeqnk2Tj=1neq1kjTeq1knTj=1neq2kjTeq2knTj=1neqnkjTeqnknT

  • 有掩码时,我们使用的掩码矩阵

M=[0−∞−∞⋯−∞00−∞⋯−∞000⋯−∞⋮⋮⋮⋱⋮000⋯0] M = \begin{bmatrix} 0 & -\infty & -\infty & \cdots & -\infty \\ 0 & 0 & -\infty & \cdots & -\infty \\ 0 & 0 & 0 & \cdots & -\infty \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \cdots & 0 \end{bmatrix} M=0000000000

这是一个上半部分全部是无穷大、下半部分全部是0的矩阵。在进行掩码时,我们用掩码矩阵与原始QKTQK^TQKT点积进行加和,然后再将加和结果放入softmax函数。

  • 有掩码时,掩码矩阵对原始QKTQK^TQKT矩阵的影响

QKT+M=[q1⋅k1T+0q1⋅k2T−∞⋯q1⋅knT−∞q2⋅k1T+0q2⋅k2T+0⋯q2⋅knT−∞⋮⋮⋱⋮qn⋅k1T+0qn⋅k2T+0⋯qn⋅knT+0] QK^T + M = \begin{bmatrix} q_1 \cdot k_1^T + 0 & q_1 \cdot k_2^T - \infty & \cdots & q_1 \cdot k_n^T - \infty \\ q_2 \cdot k_1^T + 0 & q_2 \cdot k_2^T + 0 & \cdots & q_2 \cdot k_n^T - \infty \\ \vdots & \vdots & \ddots & \vdots \\ q_n \cdot k_1^T + 0 & q_n \cdot k_2^T + 0 & \cdots & q_n \cdot k_n^T + 0 \end{bmatrix} QKT+M=q1k1T+0q2k1T+0qnk1T+0q1k2Tq2k2T+0qnk2T+0q1knTq2knTqnknT+0

=[q1⋅k1T−∞−∞⋯−∞q2⋅k1Tq2⋅k2T−∞⋯−∞⋮⋮⋱⋮−∞qn⋅k1Tqn⋅k2T⋯qn⋅kn−1Tqn⋅knT] = \begin{bmatrix} q_1 \cdot k_1^T & -\infty & -\infty & \cdots & -\infty \\ q_2 \cdot k_1^T & q_2 \cdot k_2^T & -\infty & \cdots & -\infty \\ \vdots & \vdots & \ddots & \vdots & -\infty \\ q_n \cdot k_1^T & q_n \cdot k_2^T & \cdots & q_n \cdot k_{n-1}^T & q_n \cdot k_n^T \end{bmatrix} =q1k1Tq2k1Tqnk1Tq2k2Tqnk2Tqnkn1TqnknT

经过掩码处理过的QKTQK^TQKT矩阵的右上角全部呈现为负无穷,左下角呈现为具体的值,在这种情况下应用softmax函数后,会得到:

  • 有掩码时,softmax函数应用后的影响

softmax(QKT+M)=[eq1⋅k1Teq1⋅k1T000eq2⋅k1Teq2⋅k1T+eq2⋅k2Teq2⋅k2Teq2⋅k1T+eq2⋅k2T00eq3⋅k1Teq3⋅k1T+eq3⋅k2T+eq3⋅k3Teq3⋅k2Teq3⋅k1T+eq3⋅k2T+eq3⋅k3Teq3⋅k3Teq3⋅k1T+eq3⋅k2T+eq3⋅k3T0eq4⋅k1T∑j=14eq4⋅kjTeq4⋅k2T∑j=14eq4⋅kjTeq4⋅k3T∑j=14eq4⋅kjTeq4⋅k4T∑j=14eq4⋅kjT] \text{softmax}(QK^T + M) = \begin{bmatrix} \frac{e^{q_1 \cdot k_1^T}}{e^{q_1 \cdot k_1^T}} & 0 & 0 & 0 \\ \frac{e^{q_2 \cdot k_1^T}}{e^{q_2 \cdot k_1^T} + e^{q_2 \cdot k_2^T}} & \frac{e^{q_2 \cdot k_2^T}}{e^{q_2 \cdot k_1^T} + e^{q_2 \cdot k_2^T}} & 0 & 0 \\ \frac{e^{q_3 \cdot k_1^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & \frac{e^{q_3 \cdot k_2^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & \frac{e^{q_3 \cdot k_3^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & 0 \\ \frac{e^{q_4 \cdot k_1^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_2^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_3^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_4^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} \end{bmatrix} softmax(QKT+M)=eq1k1Teq1k1Teq2k1T+eq2k2Teq2k1Teq3k1T+eq3k2T+eq3k3Teq3k1Tj=14eq4kjTeq4k1T0eq2k1T+eq2k2Teq2k2Teq3k1T+eq3k2T+eq3k3Teq3k2Tj=14eq4kjTeq4k2T00eq3k1T+eq3k2T+eq3k3Teq3k3Tj=14eq4kjTeq4k3T000j=14eq4kjTeq4k4T

从softmax函数的具体公式来看,当输入值zzz高度接近负无穷时,以e为底的对数函数的取值会无穷地趋近于0,因此才会得到一个上半个三角全为0的矩阵。通过这种方式,可以让原始矩阵中的一部分信息被“掩盖”(变为0),这个操作就是掩码的本质。

σ(z)i=ezi∑j=1Kezj\sigma(z)_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} σ(z)i=j=1Kezjezi

<2>.掩码后的注意力机制的输出结果
  • Decoder中,多头注意力机制输出的softmax结果(这部分信息来自于真实标签y,teacher forcing要求的)

softmax(QKT+M)=[eq1⋅k1Teq1⋅k1T000eq2⋅k1Teq2⋅k1T+eq2⋅k2Teq2⋅k2Teq2⋅k1T+eq2⋅k2T00eq3⋅k1Teq3⋅k1T+eq3⋅k2T+eq3⋅k3Teq3⋅k2Teq3⋅k1T+eq3⋅k2T+eq3⋅k3Teq3⋅k3Teq3⋅k1T+eq3⋅k2T+eq3⋅k3T0eq4⋅k1T∑j=14eq4⋅kjTeq4⋅k2T∑j=14eq4⋅kjTeq4⋅k3T∑j=14eq4⋅kjTeq4⋅k4T∑j=14eq4⋅kjT] \text{softmax}(QK^T + M) = \begin{bmatrix} \frac{e^{q_1 \cdot k_1^T}}{e^{q_1 \cdot k_1^T}} & 0 & 0 & 0 \\ \frac{e^{q_2 \cdot k_1^T}}{e^{q_2 \cdot k_1^T} + e^{q_2 \cdot k_2^T}} & \frac{e^{q_2 \cdot k_2^T}}{e^{q_2 \cdot k_1^T} + e^{q_2 \cdot k_2^T}} & 0 & 0 \\ \frac{e^{q_3 \cdot k_1^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & \frac{e^{q_3 \cdot k_2^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & \frac{e^{q_3 \cdot k_3^T}}{e^{q_3 \cdot k_1^T} + e^{q_3 \cdot k_2^T} + e^{q_3 \cdot k_3^T}} & 0 \\ \frac{e^{q_4 \cdot k_1^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_2^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_3^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} & \frac{e^{q_4 \cdot k_4^T}}{\sum_{j=1}^{4} e^{q_4 \cdot k_j^T}} \end{bmatrix} softmax(QKT+M)=eq1k1Teq1k1Teq2k1T+eq2k2Teq2k1Teq3k1T+eq3k2T+eq3k3Teq3k1Tj=14eq4kjTeq4k1T0eq2k1T+eq2k2Teq2k2Teq3k1T+eq3k2T+eq3k3Teq3k2Tj=14eq4kjTeq4k2T00eq3k1T+eq3k2T+eq3k3Teq3k3Tj=14eq4kjTeq4k3T000j=14eq4kjTeq4k4T

当这个矩阵乘以v后,依然不会改变携带的信息,因此我们可以使用这个脚标来标注整个多头注意力机制输出的结果,使用数字简化则有

Decoder softmax=[1⋅11⋅21⋅31⋅42⋅12⋅22⋅32⋅43⋅13⋅23⋅33⋅44⋅14⋅24⋅34⋅4] \text{Decoder softmax} = \begin{bmatrix} \boldsymbol{\color{green}{1}}\cdot\boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{1}} \cdot\boldsymbol{\color{red}{2}} & \boldsymbol{\color{green}{1}} \cdot\boldsymbol{\color{red}{3}} & \boldsymbol{\color{green}{1}} \cdot\boldsymbol{\color{red}{4}} \\ \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{green}{2}} & \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{red}{3}} & \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{red}{4}} \\ \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{2}} & \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{3}} & \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{red}{4}} \\ \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{2}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{3}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{4}} \end{bmatrix} Decoder softmax=11213141122232421323334314243444

经过掩码之后,实际上是

Decoder masked softmax=[1⋅10002⋅12⋅2003⋅13⋅23⋅304⋅14⋅24⋅34⋅4] \text{Decoder masked softmax} = \begin{bmatrix} \boldsymbol{\color{green}{1}}\cdot\boldsymbol{\color{green}{1}} & 0 & 0 & 0\\ \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{2}} \cdot \boldsymbol{\color{green}{2}} & 0 & 0\\ \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{2}} & \boldsymbol{\color{green}{3}} \cdot \boldsymbol{\color{green}{3}} & 0\\ \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{1}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{2}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{3}} & \boldsymbol{\color{green}{4}} \cdot \boldsymbol{\color{green}{4}} \end{bmatrix} Decoder masked softmax=11213141022324200334300044

转换成注意力得分,则有

Decoder-Masked-Attention=[a11000a21a2200a31a32a330a41a42a43a44] \text{Decoder-Masked-Attention} = \begin{bmatrix} a_{11} & 0 & 0 & 0 \\ a_{21} & a_{22} & 0 & 0 \\ a_{31} & a_{32} & a_{33} & 0 \\ a_{41} & a_{42} & a_{43} & a_{44} \end{bmatrix} Decoder-Masked-Attention=a11a21a31a410a22a32a4200a33a43000a44
假设 VVV 矩阵如下,由于矩阵V是从原始标签y生成的embedding矩阵,因此矩阵V的序列方向是从上到下。
V=[v1v1…v1v2v2…v2v3v3…v3v4v4…v4] V = \begin{bmatrix} v_{1} & v_{1} & \ldots & v_{1} \\ v_{2} & v_{2} & \ldots & v_{2} \\ v_{3} & v_{3} & \ldots & v_{3} \\ v_{4} & v_{4} & \ldots & v_{4} \end{bmatrix} V=v1v2v3v4v1v2v3v4v1v2v3v4
特别注意!在这里为了避免脚标产生混淆,没有写特征维度脚标。此时我们所有的脚标都只代表了时间点,特征维度脚标被省略了!事实上真正的V矩阵应该是
V=[v11v12…v1dv21v22…v2dv31v32…v3dv41v42…v4d] V = \begin{bmatrix} v_{1}^1 & v_{1}^2 & \ldots & v_{1}^d \\ v_{2}^1 & v_{2}^2 & \ldots & v_{2}^d \\ v_{3}^1 & v_{3}^2 & \ldots & v_{3}^d \\ v_{4}^1 & v_{4}^2 & \ldots & v_{4}^d \end{bmatrix} V=v11v21v31v41v12v22v32v42v1dv2dv3dv4d

在我们此时的讨论流程中,特征维度脚标只有标识作用,与整体过程理解无关,因此在这里出于教学目的将其省略。但事实上它应该是存在的。

Decoder-Masked-Attention\text{Decoder-Masked-Attention}Decoder-Masked-Attention 矩阵与 VVV 矩阵相乘,得到结果矩阵 CCC,就是带掩码的多头注意力机制的结果

C=Decoder-Masked-Attention×V C = \text{Decoder-Masked-Attention} \times V C=Decoder-Masked-Attention×V

C=[a11000a21a2200a31a32a330a41a42a43a44][v1v1…v1v2v2…v2v3v3…v3v4v4…v4] C = \begin{bmatrix} a_{11} & 0 & 0 & 0 \\ a_{21} & a_{22} & 0 & 0 \\ a_{31} & a_{32} & a_{33} & 0 \\ a_{41} & a_{42} & a_{43} & a_{44} \end{bmatrix} \begin{bmatrix} v_{1} & v_{1} & \ldots & v_{1} \\ v_{2} & v_{2} & \ldots & v_{2} \\ v_{3} & v_{3} & \ldots & v_{3} \\ v_{4} & v_{4} & \ldots & v_{4} \end{bmatrix} C=a11a21a31a410a22a32a4200a33a43000a44v1v2v3v4v1v2v3v4v1v2v3v4

结果矩阵 CCC 的元素 cijc_{ij}cij 的计算如下:

ci=∑kaik⋅vk c_{i} = \sum_{k} a_{ik} \cdot v_{k} ci=kaikvk

具体计算为(每一列对应不同特征维度):

C=[a11v1a11v1…a11v1a21v1+a22v2a21v1+a22v2…a21v1+a22v2a31v1+a32v2+a33v3a31v1+a32v2+a33v3…a31v1+a32v2+a33v3a41v1+a42v2+a43v3+a44v4a41v1+a42v2+a43v3+a44v4…a41v1+a42v2+a43v2+a44v4] C = \begin{bmatrix} a_{11}v_{1} & a_{11}v_{1} & \ldots & a_{11}v_{1} \\ a_{21}v_{1} + a_{22}v_{2} & a_{21}v_{1} + a_{22}v_{2} & \ldots & a_{21}v_{1} + a_{22}v_{2} \\ a_{31}v_{1} + a_{32}v_{2} + a_{33}v_{3} & a_{31}v_{1} + a_{32}v_{2} + a_{33}v_{3} & \ldots & a_{31}v_{1} + a_{32}v_{2} + a_{33}v_{3} \\ a_{41}v_{1} + a_{42}v_{2} + a_{43}v_{3} + a_{44}v_{4} & a_{41}v_{1} + a_{42}v_{2} + a_{43}v_{3} + a_{44}v_{4} & \ldots & a_{41}v_{1} + a_{42}v_{2} + a_{43}v_{2} + a_{44}v_{4} \end{bmatrix} C=a11v1a21v1+a22v2a31v1+a32v2+a33v3a41v1+a42v2+a43v3+a44v4a11v1a21v1+a22v2a31v1+a32v2+a33v3a41v1+a42v2+a43v3+a44v4a11v1a21v1+a22v2a31v1+a32v2+a33v3a41v1+a42v2+a43v2+a44v4
矩阵 CCC 就是一个按相关度加权融合了历史上下文信息的新特征矩阵。它本质上依然是序列中每个位置的特征表示(依然可以看作一种 Embedding),只不过经过这一步,它从“只认识自己”的静态词向量,升级成了“纵观全局历史”的动态特征向量。

<3>.普通掩码 和 前瞻掩码

在NLP的世界中,掩码最被人熟知的作用就是掩盖未来的信息、避免序列中未来的信息被泄露到算法中,然而掩码(Masking)是一种多功能的机制,其本质是为了“掩盖信息”,但并不局限于掩盖未来的信息。

  • 在注意力机制中、掩盖未来信息、不允许Q向未来的K发问的掩码被叫做“前瞻掩码”(look-ahead Masking),这里的“前瞻”正是代表了“未来”(对时间序列来说是未来的时间点、对文字序列来说是右侧的信息)。
  • 掩码在Transformer中还有另一个巨大的作用,就是掩盖噪音信息,避免噪音影响注意力机制的计算。掩盖噪音的掩码是最普通的掩码之一,在NLP中它主要负责掩盖填充句子时产生的padding。

image-20260626012839927

Transformer的输入数据结构为(batch_size, seq_len, input_dimensions),不同句子的seq_len必须保持一致,然而在现实中我们不太可能让每个句子的长度都一致,因此句子过长的部分我们就会截断句子、句子太短的部分我们就会使用填充。这些填充大部分都是0填充,这些0填充与其他token正常编码的结果计算之后,就会在注意力分数中留下许多的噪音值,因此在将这些信息输出之前,我们就会需要在QK.TQK.TQK.T矩阵上进行“填充掩码”,来帮助注意力机制减少噪音带来的影响。

注意:前瞻掩码通常只是解码器专属的,但是填充掩码是解码器和编码器都可以使用的。在编码器的多头注意力机制中,那个“可选的掩码(下面粉色的那个)”就是填充掩码机制。

image-20260512165822054

pytorch ==> 允许我们自创掩码矩阵M,输入到pytorch的各个层里进行掩码(QK.T + M)

  • 填充掩码的实现函数原理:

    import torch
    
    # 创造一个示例数据
    batch_size = 4
    seq_len = 10
    embedding_dim = 8
    seq = torch.randint(0, 5, (batch_size, seq_len, embedding_dim))  # 随机生成一些数据
    
    #填充部分
    pad_token = 0
    seq[0, 7:, :] = pad_token  # 设置填充值,第一个batch只有7句
    seq[1, 9:, :] = pad_token  # 设置填充值,第二个batch只有9句
    seq[3, 5:, :] = pad_token  # 设置填充值,第四个batch只有5句
    
    print(seq == pad_token)
    # tensor([[[False, False,  True,  True,  True, False, False, False],
    #          [ True, False, False, False, False, False, False, False],
    #          [False, False, False,  True, False, False,  True, False],
    #          [False, False, False, False,  True, False, False, False],
    #          [False, False, False, False, False, False,  True, False],
    #          [ True, False, False, False, False, False, False,  True],
    #          [False, False, False, False, False, False,  True, False],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True]],
    # 
    #         [[False, False, False, False, False, False, False, False],
    #          [False,  True, False, False,  True, False, False, False],
    #          [False, False, False, False, False, False, False, False],
    #          [False, False, False, False,  True, False, False, False],
    #          [False, False, False, False, False, False,  True,  True],
    #          [False,  True, False, False, False, False, False,  True],
    #          [False, False, False, False, False, False, False,  True],
    #          [False, False, False, False, False,  True,  True, False],
    #          [False,  True, False, False, False,  True,  True, False],
    #          [ True,  True,  True,  True,  True,  True,  True,  True]],
    # 
    #         [[False, False, False, False, False, False, False, False],
    #          [False, False, False, False,  True,  True, False,  True],
    #          [False, False, False, False, False, False,  True, False],
    #          [False, False, False, False, False, False, False, False],
    #          [False, False, False,  True, False,  True, False,  True],
    #          [False, False, False, False, False, False, False, False],
    #          [ True,  True, False, False, False, False, False, False],
    #          [False, False, False, False, False,  True, False, False],
    #          [False, False, False, False, False, False,  True, False],
    #          [False, False,  True, False, False, False, False, False]],
    # 
    #         [[False, False, False, False, False,  True, False, False],
    #          [False, False, False, False, False, False, False, False],
    #          [ True,  True, False, False,  True,  True, False, False],
    #          [False, False, False, False, False, False, False, False],
    #          [False, False, False,  True, False, False, False, False],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True],
    #          [ True,  True,  True,  True,  True,  True,  True,  True]]])
    print((seq == pad_token).all(dim=-1)) #只有全部是0的行,才是真正的掩码,在最后一个维度上查看是否整行都为0
    # tensor([[False, False, False, False, False, False, False,  True,  True,  True],
    #         [False, False, False, False, False, False, False, False, False,  True],
    #         [False, False, False, False, False, False, False, False, False, False],
    #         [False, False, False, False, False,  True,  True,  True,  True,  True]])
    print((seq == pad_token).all(dim=-1).float()) #使用float,这就是标注出来需要掩码的行
    # tensor([[0., 0., 0., 0., 0., 0., 0., 1., 1., 1.],
    #         [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
    #         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #         [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.]])
    
    #接下来,要将掩码的行转变为掩码矩阵
    #QK.T矩阵的维度是(batch_size,num_heads,seq_len,seq_len) 注:num_heads是多头注意力头数
    #掩码矩阵为了要能够与QK.T矩阵相加,也必须是这个结构
    #unsqueeze用于在特定位置增加维度,expand则用于复制&拓展维度
    print((seq == pad_token).all(dim=-1).unsqueeze(1).unsqueeze(3).expand(-1, -1, -1, seq.size(1)).shape)
    # torch.Size([4, 1, 10, 10])
    print(
        (seq == pad_token).all(dim=-1).unsqueeze(1).unsqueeze(3).expand(-1, -1, -1, seq.size(1)).float()
    )
    # tensor([[[[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]],
    # 
    # 
    #         [[[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]],
    # 
    # 
    #         [[[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]],
    # 
    # 
    #         [[[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
    #           [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]]])
    
<4>.普通掩码 和 前瞻掩码 实现函数
  1. 普通掩码

    需升维的填充掩码函数:

    def create_padding_mask(seq, pad_token=0):
        # seq: (batch_size, seq_len, embedding_dim)
        # 检查填充值位置
        padding_mask = (seq == pad_token).all(dim=-1)  # (batch_size, seq_len)
        # 增加维度以匹配注意力权重矩阵的形状
        # (batch_size, num_heads, seq_len, seq_len)
        padding_mask = padding_mask.unsqueeze(1).unsqueeze(3).expand(-1, -1, -1, seq.size(1))
        # 将填充值部分设置为负无穷大,有效数据部分设置为0
        padding_mask = padding_mask.float() * -1e9  # (batch_size, num_heads, seq_len, seq_len)
        return padding_mask
    

    注:如果是配合PyTorch中已经设置好的Transformer类来使用,则二维的掩码矩阵((seq == pad_token).all(dim=-1).float())就足够了,Transformer类会自动执行将掩码矩阵升维的过程;如果是利用更底层的机制创建的Transformer,则会需要我们手动执行上述流程来匹配掩码的结构。在实际使用时,要根据实际情况选择是否主动对掩码矩阵进行升维。

    无需升维的填充掩码函数:

    def create_padding_mask(seq, pad_token=0):
        # seq: (batch_size, seq_len, embedding_dim)
        # 检查填充值位置
        padding_mask = (seq == pad_token).all(dim=-1)  # (batch_size, seq_len)
        padding_mask = padding_mask.float() * -1e9
        return padding_mask
    
  2. 前瞻掩码

    def create_look_ahead_mask(seq_len, start_seq = 1):
        mask = torch.triu(torch.ones((seq_len, seq_len)),diagonal=start_seq)  # triu左下方的三角矩阵,diagonal控制对角线位置
        mask = mask.float() * -1e9  # 将未来的位置设置为负无穷大
        return mask # (seq_len, seq_len)
    

    前瞻掩码矩阵的结构为(seq_len, seq_len),而填充掩码矩阵的结构为(batch_size,num_heads,seq_len,seq_len)。前者可以通过广播的方式与QK.TQK.TQK.T矩阵相加,后者则必须写明4个维度的信息,这是因为前瞻掩码对所有的序列都是一样的掩码方式,但填充掩码却是在每个batch内都是不一致的,因为每个batch内的句子可能会不一致。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值