第八周.01.用图传递理解Transformer

本文介绍了GNN(图神经网络)与Transformer在处理图结构任务中的结合,通过实例展示了如何在图中应用单头或多头Attention,并结合消息传递进行编码和解码。讲解了计算注意力分数、softmax归一化和最终输出的过程,并涉及了实际代码实现。


本文内容整理自深度之眼《GNN核心能力培养计划》
公式输入请参考: 在线Latex公式
接直播理论部分内容,这两节讲代码实现

理论回顾

https://docs.dgl.ai/tutorials/models/4_old_wines/7_transformer.html
下面是单头Attention的描述:
注意力计算先要算qkv和score。这里的i,j是两个单词,x是单词的特征:
q j = W q ⋅ x j k i = W k ⋅ x i v i = W v ⋅ x i s c o r e = q j T k i q_j=W_q\cdot x_j\\ k_i =W_k\cdot x_i\\ v_i = W_v\cdot x_i\\ score = q_j^Tk_i qj=Wqxjki=Wkxivi=Wvxiscore=qjTki
这里的score实际上计算的是query和key之间的相似度,相似度越高注意力就越大,当然还有别的方式来计算相似度,这里用的最简单的点乘。
然后将score进行softmax:
w j i = exp ⁡ { s c o r e j i } ∑ ( k , i ) ∈ E exp ⁡ { s c o r e k i } w_{ji}=\cfrac{\exp\{score_{ji}\}}{\sum_{(k,i)\in E}\exp\{score_{ki}\}} wji=(k,i)Eexp{scoreki}exp{scoreji}
然后计算单词i的加权求和权重:
w v i = ∑ ( k , i ) ∈ E w k i v k wv_i = \sum_{(k,i)\in E}w_{ki}v_k wvi=(k,i)Ewkivk
然后做最后的输出:
o = W o ⋅ w v o=W_o\cdot wv o=Wowv
推广到多头Attention:
o = W o ⋅ c o n c t ( [ w v ( 0 ) , w v ( 2 ) , ⋯   , w v ( h ) ] ) o=W_o\cdot conct([wv^{(0)},wv^{(2)},\cdots,wv^{(h)}]) o=Woconct([wv(0),wv(2),,wv(h)])
对应的代码:

class MultiHeadAttention(nn.Module):
    "Multi-Head Attention"
    def __init__(self, h, dim_model):
        "h: number of heads; dim_model: hidden dimension"
        super(MultiHeadAttention, self).__init__()
        self.d_k = dim_model // h
        self.h = h
        # W_q, W_k, W_v, W_o
        self.linears = clones(nn.Linear(dim_model, dim_model), 4)

    def get(self, x, fields='qkv'):
        "Return a dict of queries / keys / values."
        batch_size = x.shape[0]
        ret = {}
        if 'q' in fields:
            ret['q'] = self.linears[0](x).view(batch_size, self.h, self.d_k)
        if 'k' in fields:
            ret['k'] = self.linears[1](x).view(batch_size, self.h, self.d_k)
        if 'v' in fields:
            ret['v'] = self.linears[2](x).view(batch_size, self.h, self.d_k)
        return ret

    def get_o(self, x):
        "get output of the multi-head attention"
        batch_size = x.shape[0]
        return self.linears[3](x.view(batch_size, -1))

GNN+Transformer

在图结构中,Transformer的注意力要和消息传递相结合。如果用图来理解注意力机制是怎么弄呢?下面看例子

图结构

把句子中的每一个单词看做一个节点,那么原来的句子可以由三个子图构成。
第一个子图是Source language graph. 可以看到是一个完全图,每个节点 s i s_i si与其他节点 s j s_j sj都有边连接,这里还包含有 s i s_i si的自连接。
在这里插入图片描述
第二个图是Target language graph.可以看到这个图是上面图的一半,因为节点 t i t_i ti只连接 t j , i < j t_j,i<j tj,i<j,这里的意思是在输出的时候,当前节点的输入只和前面的单词节点有关,后面的单词节点还没生成,后面的单词输出与当前单词输出无关。

在这里插入图片描述
第三个图是:Cross-language graph.是一个二部图,就是每个输入节点 s i s_i si都和每个输出节点 t j t_j tj有一个对应关系
在这里插入图片描述
可以看到这里给出的三个子图实际上对应到Transformer的输入输出,Encoder和Decoder。三个子图合起来就是:
在这里插入图片描述

消息传递

有了图结构,就可以进行消息传递了。
假设节点 i i i对应的qkv都已经计算完成。那么对于每个节点 i i i的注意力消息传递都可以分为两个步骤:
1、计算节点 i i i和其他所有邻居节点之间的 s c o r e i j = q i ⋅ k j score_{ij}=q_i\cdot k_j scoreij=qikj

def message_func(edges):
    return {'score': ((edges.src['k'] * edges.dst['q'])
                      .sum(-1, keepdim=True)),
            'v': edges.src['v']}

如果搞不清从谁到谁,可以这样想,消息汇聚就是从邻居节点汇聚消息到当前节点,所以邻居节点是src,当前节点是dst。
2、消息汇聚:计算与 i i i相邻的所有节点 j j j v j v_j vj的加权和,权重就是上一步的score。

import torch as th
import torch.nn.functional as F

def reduce_func(nodes, d_k=64):
    v = nodes.mailbox['v']#邻居的embedding
    att = F.softmax(nodes.mailbox['score'] / th.sqrt(d_k), 1)
    return {'dx': (att * v).sum(1)}

原生Transformer实操部分下节讲。

<think>好的,我现在需要解释Transformer的编码器和解码器的特点以及它们的区别。首先,我应该回顾一下Transformer的基本结构。Transformer由编码器和解码器组成,每个部分都有不同的组件和作用。 编码器部分,我记得每个编码器层通常包含多头自注意力机制和前馈神经网络。而解码器层除了这两个组件外,还有一个额外的注意力层,用于处理编码器的输出。接下来,我需要详细比较两者的结构差异。 首先,编码器的作用是处理输入序列,提取特征。它使用自注意力机制来捕捉输入内部的依赖关系。而解码器的任务是根据编码器的输出和之前生成的输出,生成目标序列。因此,解码器需要防止看到未来的信息,所以会有掩码机制。 然后,我需要考虑它们的注意力机制的不同。编码器中的自注意力层允许每个位置关注输入序列的所有位置,而解码器的第一个自注意力层是掩码的,只能看到当前位置之前的信息。第二个注意力层则是用来关注编码器的输出,即编码器-解码器注意力。 另外,在训练时,解码器通常会使用教师强制,即使用真实目标序列作为输入,而编码器处理的是源序列。在推断时,解码器需要逐步生成输出,这可能导致自回归特性。 还要注意层数是否相同。一般来说,编码器和解码器的层数可以相同,比如原始论文中是6层,但具体应用可能会调整。每层的结构差异也需要明确,比如解码器有额外的注意力层。 可能还需要提到位置编码,两者都会使用,但解码器需要处理生成过程中的位置信息。此外,编码器的输出会传递给每个解码器层,而不仅仅是第一个层。 有没有其他关键点?比如残差连接和层归一化的位置是否一致?是的,两者都使用了残差连接和层归一化,但解码器在三个子层(掩码自注意力、编码器-解码器注意力、前馈网络)之后都有这些操作。 总结的话,结构差异、注意力机制的不同、训练与推断时的行为差异,以及输入输出的处理方式是主要区别。要确保这些点都被涵盖,并且用清晰的中文表达,避免术语错误。同时,可能需要用例子或公式来加强解释,比如展示注意力机制的公式,或者结构示意。但用户可能不需要太深入的数学细节,所以保持简洁。 最后,检查是否有遗漏,例如是否提到解码器的初始化可能依赖于编码器的输出,或者不同变体中的调整,比如一些模型可能只有编码器或解码器。不过原始Transformer是编码器-解码器结构,所以应围绕这个展开。确认回答的结构是否清晰,分点说明特点和区别,可能用表格对比更直观。</think>### **Transformer编码器与解码器特点及区别解析** --- #### **一、核心结构对比** | **组件** | **编码器(Encoder)** | **解码器(Decoder)** | |------------------|--------------------------------------------------|--------------------------------------------------| | **核心层数** | 通常N层(如原始论文N=6) | 相同层数(与编码器对应) | | **子层构成** | 1. 多头自注意力<br>2. 前馈网络(FFN) | 1. **掩码**多头自注意力<br>2. 编码器-解码器注意力<br>3. 前馈网络 | | **残差连接** | 每个子层后接残差连接+层归一化 | 每个子层后接残差连接+层归一化 | --- #### **二、核心功能差异** **1. 编码器(Encoder)** - **输入处理**:直接接收**源序列**(如待翻译的原文) - **注意力机制**: - **多头自注意力**:全量可见,无位置限制 - 公式:$$ \text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V $$ - **输出用途**:生成**上下文向量**,传递给所有解码器层 **2. 解码器(Decoder)** - **输入处理**: - **训练阶段**:接收**目标序列右移一位**(Teacher Forcing) - **推理阶段**:自回归生成(逐词输出) - **注意力机制**: - **掩码自注意力**:仅可见当前位置及之前内容(防止信息泄漏) $$ \text{MaskedAttention}(Q,K,V) = \text{softmax}\left(\frac{QK^T + M}{\sqrt{d_k}}\right)V $$ ($M$为掩码矩阵,下三角为0,上三角为$-\infty$) - **编码器-解码器注意力**:关注编码器输出的关键信息 - **输出用途**:生成最终预测结果(如翻译后的句子) --- #### **三、关键特性对比** | **特性** | **编码器** | **解码器** | |-------------------------|-----------------------------------------------|-----------------------------------------------| | **注意力可见范围** | 全序列可见 | 掩码自注意力仅见历史信息 | | **参数共享** | 不同层参数独立 | 不同层参数独立 | | **位置编码依赖** | 必需(捕获序列顺序) | 必需(生成时需感知位置) | | **训练目标** | 提取全局特征 | 生成目标序列(交叉熵损失监督) | --- #### **四、工作流程示例** 以机器翻译任务为例: ```text 编码器处理源序列:"Hello world" → 输出上下文向量 解码器逐步生成: Step1: 输入<start> → 预测"Bonjour" Step2: 输入<start> Bonjour → 预测"le" Step3: 输入<start> Bonjour le → 预测"monde" ``` --- #### **五、架构可视化** ![Transformer结构](https://miro.medium.com/v2/resize:fit:1400/1*BHzGVskWGS_3jEcYYi6miQ.png) *红色框:编码器(处理输入);蓝色框:解码器(生成输出)* --- #### **六、代码实现差异(PyTorch示例)** **编码器层**: ```python class EncoderLayer(nn.Module): def __init__(self, d_model, nhead, ff_dim): super().__init__() self.self_attn = MultiheadAttention(d_model, nhead) self.ffn = PositionwiseFFN(d_model, ff_dim) def forward(self, x): x = x + self.self_attn(x, x, x) # 自注意力 x = x + self.ffn(x) # 前馈网络 return x ``` **解码器层**: ```python class DecoderLayer(nn.Module): def __init__(self, d_model, nhead, ff_dim): super().__init__() self.masked_attn = MultiheadAttention(d_model, nhead) self.cross_attn = MultiheadAttention(d_model, nhead) self.ffn = PositionwiseFFN(d_model, ff_dim) def forward(self, x, enc_output): x = x + self.masked_attn(x, x, x, attn_mask=mask) # 掩码自注意力 x = x + self.cross_attn(x, enc_output, enc_output) # 交叉注意力 x = x + self.ffn(x) return x ``` --- #### **七、延伸应用场景** | **模型类型** | **编码器使用** | **解码器使用** | **典型案例** | |---------------------|-----------------------|-----------------------|-----------------------| | **纯编码器架构** | ✔️ | ❌ | BERT、ViT | | **纯解码器架构** | ❌ | ✔️ | GPT系列、LLaMA | | **编码器-解码器** | ✔️ | ✔️ | T5、BART、原始Transformer | --- #### **八、总结要点** 1. **结构差异**:解码器比编码器多一个注意力层(编码器-解码器交互) 2. **注意力限制**:解码器的自注意力需要掩码防止未来信息泄漏 3. **功能分工**:编码器专注于理解输入,解码器专注于生成输出 4. **应用扩展**:现代大模型常根据任务需求选择单一结构(如仅用解码器的GPT) 通过这种分工,Transformer实现了高效的序列到序列建模,成为NLP领域的基础架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值