Pytorch 从零实现 Transformer

前言

之前虽然了解过 Transformer 架构,但是没有自己实现过。

最近阅读 transformers 库中 Llama 模型结构,于是想试着亲手实现一个简单的 Transformer。

在实现过程中加深了理解,同时发现之前阅读 Llama 中一些错误的地方,因此做一个记录。

笔者小白,如果实现过程中存在错误,请不吝指出。

Embedding

Embedding 可以将高维的离散文本数据映射到低维的连续向量空间。这不仅减小了输入数据的维度,也有助于减少数据的稀疏性,提高模型的性能和效率。

同时,词嵌入可以捕捉单词之间的语义关系,相似的单词在嵌入空间中会更接近。

使用 Pytorch 可以很方便定义出 Embedding 模型:

class Embedder(nn.Module):def __init__(self, vocab_size: int, d_model: int) -> None:super().__init__()self.embed = nn.Embedding(vocab_size, d_model)def forward(self, x: torch.Tensor) -> torch.Tensor:return self.embed(x)

Positional Encoding

Transformer 中没有类似 RNN 的循环机制,需要通过位置编码记录单词的位置和顺序。

其计算位置编码的公式如下:

PE(pos,2i)=sin(pos100002i/dmodel)PE_{(pos,2i)}=sin(\frac{pos}{10000^{2i/d_{model}}})PE(pos,2i)​=sin(100002i/dmodel​pos​)

PE(pos,2i+1)=cos(pos100002i/dmodel)PE_{(pos,2i+1)}=cos(\frac{pos}{10000^{2i/d_{model}}})PE(pos,2i+1)​=cos(100002i/dmodel​pos​)

其中 pospospos 是位置,而 iii 是维度。

Pytorch 实现位置编码器代码如下:

class PositionalEncoder(nn.Module):def __init__(self, d_model: int = 512, max_seq_len: int = 2048, base: int = 10000) -> None:super().__init__()self.d_model = d_modelinv_freq_half = 1.0 / (base ** (torch.arange(0, d_model, 2, dtype=torch.float) / d_model))inv_freq = torch.arange(0, d_model, dtype=inv_freq_half.dtype)inv_freq[..., 0::2] = inv_freq_halfinv_freq[..., 1::2] = inv_freq_halfpos = torch.arange(max_seq_len, dtype=inv_freq.dtype)pe = torch.einsum("i, j -> ij", pos, inv_freq)pe[..., 0::2] = pe[..., 0::2].sin()pe[..., 1::2] = pe[..., 1::2].cos()self.register_buffer("pe", pe)def forward(self, x: torch.Tensor) -> torch.Tensor:# 使 embedding 相对大一些x = x * math.sqrt(self.d_model)seq_len = x.shape[1]pe = self.pe[:seq_len].to(dtype=x.dtype)return x + pe

在 PyTorch 中,nn.Module 类中的 register_buffer() 方法用于将一个张量(或缓冲区)注册为模型的一部分。

注册的缓冲区不会参与模型的梯度计算,但会在模型的保存和加载时保持状态。

register_buffer() 的主要作用是在模型中保留一些不需要梯度更新的状态。

在前向传播中加入位置编码前扩大 embedding 的值,是为了保证原始语言信息不会因为加入位置信息而丢失。

Mask

Mask 在 Transformer 中有很重要的作用:

  • 在 Encoder 和 Decoder 中,Mask 会遮住用于 Padding 的位置。
  • 在 Decoder 中,Mask 会遮住预测剩余位置,防止 Dcoder 提前得到信息。

Multi-Headed Attention

多头注意力是 Transformer 中的核心模块,它们网络结构如下:

在多头注意力中,会将 embedding 分割为 hhh 个头,每个头的维度为 dmodel/hd_{model} / hdmodel​/h。

In this work we employ h=8h = 8h=8 parallel attention layers, or heads. For each of these we use dkd_kdk​ = dvd_vdv​ = dmodel/hd_{model}/hdmodel​/h = 64.

多头注意力公式如下:

MultiHead(Q,K,V)=Concat(head1,…,headn)WOMultiHead(Q,K,V)=Concat(head_1,…,head_n)W^OMultiHead(Q,K,V)=Concat(head1​,…,headn​)WO

headi=Attention(QWiQ,KWiK,VWiV)head_i=Attention(QW_iQ,KW_iK,VW_i^V)headi​=Attention(QWiQ​,KWiK​,VWiV​)

多头注意力代码如下:

class MultiHeadAttention(nn.Module):def __init__(self, d_model: int, heads: int = 8, dropout: int = 0.1) -> None:super().__init__()self.d_model = d_modelself.heads = headsself.d_k = self.d_model // self.headsif self.heads * self.d_k != self.d_model:raise ValueError(f"d_model must be divisible by heads (got `d_model`: {self.d_model}"f" and `heads`: {self.heads}).")self.q_proj = nn.Linear(d_model, d_model)self.k_proj = nn.Linear(d_model, d_model)self.v_proj = nn.Linear(d_model, d_model)self.dropout = nn.Dropout(dropout)self.o_proj = nn.Linear(d_model, d_model)def forward(self,q: torch.Tensor,k: torch.Tensor,v: torch.Tensor,mask: Optional[torch.Tensor] = None,):bsz = q.shape[0]# translate [bsz, seq_len, d_model] to [bsz, seq_len, heads, d_k]q = self.q_proj(q).view(bsz, -1, self.heads, self.d_k)k = self.k_proj(k).view(bsz, -1, self.heads, self.d_k)v = self.v_proj(v).view(bsz, -1, self.heads, self.d_k)# translate [bsz, seq_len, heads, d_k] to [bsz, heads, seq_len, d_k]q = q.transpose(1, 2)k = k.transpose(1, 2)v = v.transpose(1, 2)# calculate attentionscores = attention(q, k, v, self.d_k, mask, self.dropout)# cat multi-headsconcat = scores.transpose(1, 2).contiguous().view(bsz, -1, self.d_model)output = self.o_proj(concat)return output

注意力计算公式为:

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

其中计算注意力的代码如下:

def attention(q: torch.Tensor,k: torch.Tensor,v: torch.Tensor,d_k: int,mask: Optional[torch.Tensor] = None,dropout: Optional[nn.Dropout] = None,
) -> torch.Tensor:# calculate the scores# q: [bsz, heads, seq_len, d_k]# k: [bsz, heads, d_k, seq_len]scores = torch.matmul(q, k.transpose(-1, -2)) / torch.sqrt(d_k)if mask is not None:# tanslate [bsz, seq_len, seq_len] to [bsz, 1, seq_len, seq_len]mask = mask.unsqueeze(1)scores = scores.masked_fill(mask == 0, -1e9)scores = F.softmax(scores, dim=-1)if dropout is not None:scores = dropout(scores)output = torch.matmul(scores, v)return output

The Feed-Forward Network

Feed-Forward 由两个线性变换和一个激活函数构成。

This consists of two linear transformations with a ReLU activation in between.

其公式如下:

FFN=max(0,xW1+b1)W2+b2FFN=max(0,xW_1+b_1)W_2+b_2FFN=max(0,xW1​+b1​)W2​+b2​

该网络中输入输出维度为 512,中间线性层维度为 2048。

The dimensionality of input and output is dmodel=512d_{model} = 512dmodel​=512, and the inner-layer has dimensionality dff=2048d_{ff} = 2048dff​=2048.

实现代码如下:

class FeedForward(nn.Module):def __init__(self, d_model: int = 512, d_ff: int = 2048, dropout: float = 0.1) -> None:super().__init__()self.linear_1 = nn.Linear(d_model, d_ff)self.dropout = nn.Dropout(dropout)self.linear_2 = nn.Linear(d_ff, d_model)def forward(self, x: torch.Tensor) -> torch.Tensor:x = self.dropout(F.relu(self.linear_1(x)))x = self.linear_2(x)return x

Norm

正则化可以防止数据在不同网络中流动时范围差距过大,保证模型稳定性。

实现代码如下:

class Norm(nn.Module):def __init__(self, d_model: int, eps: float = 1e-6) -> None:super().__init__()self.dim = d_modelself.alpha = nn.Parameter(torch.ones(self.dim))self.bias = nn.Parameter(torch.zeros(self.dim))self.eps = epsdef forward(self, x: torch.Tensor) -> torch.Tensor:norm = (self.alpha* (x - x.mean(dim=-1, keepdim=True))/ (x.std(dim=-1, keepdim=True) + self.eps)+ self.bias)return norm

Assemble

Transformer 由多个 EncoderLayer 和 DecoderLayer 组合在一起,首先实现 EncoderLayer。

[注意] 在每个子层输出和下一个子层输入以及正则化前,有一层 dropout。

We apply dropout [33] to the output of each sub-layer, before it is added to the sub-layer input and normalized. In addition, we apply dropout to the sums of the embeddings and the positional encodings in both the encoder and decoder stacks. For the base model, we use a rate of Pdrop = 0.1

EncoderLayer 实现代码如下:

class EncoderLayer(nn.Module):def __init__(self, d_model: int = 512, heads: int = 8, d_ff: int = 2048, dropout: float = 0.1) -> None:super().__init__()self.attn = MultiHeadAttention(d_model, heads, dropout)self.dropout_1 = nn.Dropout(dropout)self.norm_1 = Norm(d_model)self.ffn = FeedForward(d_model, d_ff, dropout)self.dropout_2 = nn.Dropout(dropout)self.norm_2 = Norm(d_model)def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.Tensor:x = x + self.dropout_1(self.attn(x, x, x, mask))x = self.norm_1(x)x = x + self.dropout_2(self.ffn(x))x = self.norm_2(x)return x

DecoderLayer 实现代码如下:

class DecoderLayer(nn.Module):def __init__(self, d_model: int = 512, heads: int = 8, d_ff: int = 2048, dropout: float = 0.1) -> None:super().__init__()self.attn_1 = MultiHeadAttention(d_model, heads, dropout)self.dropout_1 = nn.Dropout(dropout)self.norm_1 = Norm(d_model)self.attn_2 = MultiHeadAttention(d_model, heads, dropout)self.dropout_2 = nn.Dropout(dropout)self.norm_2 = Norm(d_model)self.ffn = FeedForward(d_model, d_ff, dropout)self.dropout_3 = nn.Dropout(dropout)self.norm_3 = Norm(d_model)def forward(self,x: torch.Tensor,enc_output: torch.Tensor,src_mask: torch.Tensor,tgt_mask: torch.Tensor,) -> torch.Tensor:x = x + self.dropout_1(self.attn_1(x, x, x, tgt_mask))x = self.norm_1(x)x = x + self.dropout_2(self.attn_2(x, enc_output, enc_output, src_mask))x = self.norm_2(x)x = x + self.dropout_3(self.ffn(x))x = self.norm_3(x)return x

Encoder 和 Decoder 分别由 N 个 EncoderLayer 和 DecoderLayer 组成。

代码实现如下:

class Encoder(nn.Module):def __init__(self,vocab_size: int,N: int = 6,d_model: int = 512,max_seq_len: int = 2048,heads: int = 8,d_ff: int = 2048,dropout: float = 0.1,) -> None:super().__init__()self.N = Nself.embed = Embedder(vocab_size, d_model)self.pe = PositionalEncoder(d_model, max_seq_len)self.layers = nn.ModuleList([EncoderLayer(d_model, heads, d_ff, dropout) for _ in range(N)])def forward(self, src: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:x = self.embed(src)x = self.pe(x)for layer in self.layers:x = layer(x, mask)return xclass Decoder(nn.Module):def __init__(self,vocab_size: int,N: int = 6,d_model: int = 512,max_seq_len: int = 2048,heads: int = 8,d_ff: int = 2048,dropout: float = 0.1,) -> None:super().__init__()self.N = Nself.embed = Embedder(vocab_size, d_model)self.pe = PositionalEncoder(d_model, max_seq_len)self.layers = nn.ModuleList([DecoderLayer(d_model, heads, d_ff, dropout) for _ in range(N)])def forward(self,tgt: torch.Tensor,enc_output: torch.Tensor,src_mask: torch.Tensor,tgt_mask: torch.Tensor,) -> torch.Tensor:x = self.embed(tgt)x = self.pe(x)for layer in self.layers:x = layer(x, enc_output, src_mask, tgt_mask)return x

最后组装成 Transformer!

class Transformer(nn.Module):def __init__(self,src_vocab: int,tgt_vocab: int,N: int = 6,d_model: int = 512,max_seq_len: int = 2048,heads: int = 8,d_ff: int = 2048,dropout: float = 0.1,) -> None:super().__init__()self.encoder = Encoder(src_vocab, N, d_model, max_seq_len, heads, d_ff, dropout)self.decoder = Decoder(tgt_vocab, N, d_model, max_seq_len, heads, d_ff, dropout)self.out = nn.Linear(d_model, tgt_vocab)def forward(self,src: torch.Tensor,tgt: torch.Tensor,src_mask: torch.Tensor,tgt_mask: torch.Tensor,) -> torch.Tensor:enc_output = self.encoder(src, src_mask)dec_output = self.decoder(tgt, enc_output, src_mask, tgt_mask)output = F.softmax(self.out(dec_output), dim=-1)return output

Test

测试一下代码能不能运行,按照如下配置测试:

from transformer_scratch import Transformer
import torchbsz = 4
max_seq_len = 1024
src_vocab = 128
tgt_vocab = 64
N = 3
d_ff = 512model = Transformer(src_vocab, tgt_vocab, N=N, max_seq_len=max_seq_len, d_ff=d_ff)src = torch.randint(low=0, high=src_vocab, size=(bsz, max_seq_len))
tgt = torch.randint(low=0, high=tgt_vocab, size=(bsz, max_seq_len))
src_mask = torch.ones(size=(bsz, max_seq_len, max_seq_len))
tgt_mask = torch.ones(size=(bsz, max_seq_len, max_seq_len))res = model(src, tgt, src_mask, tgt_mask)
print(f"Output data shape is: {res.shape}")

输出:Output data shape is: torch.Size([4, 1024, 64])

Reference

在编写过程中参考下面的博客,感谢大佬分享自己的经验。

那么,我们该如何学习大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、大模型全套的学习路线

学习大型人工智能模型,如GPT-3、BERT或任何其他先进的神经网络模型,需要系统的方法和持续的努力。既然要系统的学习大模型,那么学习路线是必不可少的,下面的这份路线能帮助你快速梳理知识,形成自己的体系。

L1级别:AI大模型时代的华丽登场

L2级别:AI大模型API应用开发工程

L3级别:大模型应用架构进阶实践

L4级别:大模型微调与私有化部署

一般掌握到第四个级别,市场上大多数岗位都是可以胜任,但要还不是天花板,天花板级别要求更加严格,对于算法和实战是非常苛刻的。建议普通人掌握到L4级别即可。

以上的AI大模型学习路线,不知道为什么发出来就有点糊,高清版可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

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

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

相关文章

【实战项目二】Python爬取豆瓣影评

目录 一、环境准备 二、编写代码 一、环境准备 pip install beautifulsoup4 pip intall lxml pip install requests我们需要爬取这些影评 二、编写代码 我们发现每个影评所在的div的class都相同,我们可以从这入手 from bs4 import BeautifulSoup import request…

Qwen2大模型微调入门实战(完整代码)

Qwen2是通义千问团队的开源大语言模型,由阿里云通义实验室研发。以Qwen2作为基座大模型,通过指令微调的方式实现高准确率的文本分类,是学习大语言模型微调的入门任务。 指令微调是一种通过在由(指令,输出)对…

倩女幽魂手游攻略:云手机自动搬砖辅助教程!

《倩女幽魂》手游自问世以来一直备受玩家喜爱,其精美画面和丰富的游戏内容让人沉迷其中。而如今,借助VMOS云手机,玩家可以更轻松地进行搬砖,提升游戏体验。 一、准备工作 下载VMOS云手机: 在PC端或移动端下载并安装VM…

流程的控制

条件选择语句 我们一般将条件选择语句分为三类: 单条件双条件多条件 本篇文章将分开诉说着三类。 单条件 单条件的语法很简单: if (条件) {// 代码}条件这里我们需要注意下,可以向里写入两种: 布尔值布尔表达式 当然&…

Docker高级篇之Docker网络

文章目录 1. Docker Network简介2. Docker 网络模式3. Docker 网络模式之bridge4. Docker 网络模式之host5. Docker 网络模式之none6. Docker 网络模式之container7. Docker 网络模式之自定义网络模式 1. Docker Network简介 从Docker的架构和运作流程来看,Docker是…

计算机组成原理之指令寻址

一、顺序寻址 1、定长指令字结构 2、变长指令字结构 二、跳跃寻址 三、数据寻址 1、直接寻址 2、间接寻址 3、寄存器寻址 寄存器间接寻址 4、隐含寻址 5、立即寻址 6、偏移寻址 1、基址寻址 2、变址寻址 3、相对寻址

力扣199. 二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4]示例 2: 输入: [1,null,3] 输出: [1,3]示例 3: 输入: [] 输出: [] /*** Def…

语法分析!!!

一、实验题目 根据给定文法编写调试预测分析程序&#xff0c;对任意输入串用预测分析法进行语法分析。 二、实验目的 加深对预测分析法的理解。 三、实验内容 四、实验代码 #include <iostream> #include <stdio.h> #include <string> #include <…

隐式链接DLL

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 【例9.5】创建的基于MFC对话框的应用程序MFCImLink2&#xff0c;隐式链接例9.2创建的MFCLibrary2.dll&#xff0c;使用其中的导出函数求正方形的面积。 (1) 使用MFC应用程…

【零基础一看就会】Python爬虫从入门到应用(下)

目录 一、urllib的学习 1.1 urllib介绍 1.2 urllib的基本方法介绍 urllib.Request &#xff08;1&#xff09;构造简单请求 &#xff08;2&#xff09;传入headers参数 &#xff08;3&#xff09;传入data参数 实现发送post请求&#xff08;示例&#xff09; response.…

野火FPGA跟练(四)——串口RS232、亚稳态

目录 简介接口与引脚通信协议亚稳态RS232接收模块模块框图时序波形RTL 代码易错点Testbench 代码仿真 RS232发送模块模块框图时序波形RTL 代码Testbench 代码仿真 简介 UART&#xff1a;Universal Asynchronous Receiver/Transmitter&#xff0c;异步串行通信接口。发送数据时…

微服务开发与实战Day04

一、网关路由 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发、身份校验。 在SpringCloud中网关的实现包括两种&#xff1a; 1. 快速入门 Spring Cloud Gateway 步骤&#xff1a; ①新建hm-gateway模块 ②引入依赖pom.xml(hm-gateway) <?xml version…

解锁俄罗斯市场:如何选择优质的俄罗斯云服务器

在当前云计算市场上&#xff0c;很多大型的云厂商并没有俄罗斯服务器的云节点&#xff0c;这给许多企业在拓展海外业务时带来了一定的困扰。然而&#xff0c;俄罗斯作为一个经济发展迅速的国家&#xff0c;其市场潜力不可忽视。因此&#xff0c;选择一台优质的俄罗斯云服务器成…

【MySQL】(基础篇三) —— 创建数据库和表

管理数据库和表 管理数据库 创建数据库 在MySQL中&#xff0c;创建数据库的SQL命令相对简单&#xff0c;基本语法如下&#xff1a; CREATE DATABASE 数据库名;如果你想避免在尝试创建已经存在的数据库时出现错误&#xff0c;可以添加 IF NOT EXISTS 子句&#xff0c;这样如…

数据结构(C):二叉树前中后序和层序详解及代码实现及深度刨析

目录 &#x1f31e;0.前言 &#x1f688;1.二叉树链式结构的代码是实现 &#x1f688;2.二叉树的遍历及代码实现和深度刨析代码 &#x1f69d;2.1前序遍历 ✈️2.1.1前序遍历的理解 ✈️2.1.2前序代码的实现 ✈️2.1.3前序代码的深度解剖 &#x1f69d;2.2中序遍历 ✈…

计算机网络:数据链路层 - 扩展的以太网

计算机网络&#xff1a;数据链路层 - 扩展的以太网 集线器交换机自学习算法单点故障 集线器 这是以前常见的总线型以太网&#xff0c;他最初使用粗铜轴电缆作为传输媒体&#xff0c;后来演进到使用价格相对便宜的细铜轴电缆。 后来&#xff0c;以太网发展出来了一种使用大规模…

AI菜鸟向前飞 — LangChain系列之十七 - 剖析AgentExecutor

AgentExecutor 顾名思义&#xff0c;Agent执行器&#xff0c;本篇先简单看看LangChain是如何实现的。 先回顾 AI菜鸟向前飞 — LangChain系列之十四 - Agent系列&#xff1a;从现象看机制&#xff08;上篇&#xff09; AI菜鸟向前飞 — LangChain系列之十五 - Agent系列&#…

Springboot使用webupload大文件分片上传(包含前后端源码)

Springboot使用webupload大文件分片上传&#xff08;包含源码&#xff09; 1. 实现效果1.1 分片上传效果图1.2 分片上传技术介绍 2. 分片上传前端实现2.1 什么是WebUploader&#xff1f;功能特点接口说明事件APIHook 机制 2.2 前端代码实现2.2.1&#xff08;不推荐&#xff09;…

计算机组成原理之计算机系统层次结构

目录 计算机系统层次结构 复习提示 1.计算机系统的组成 2.计算机硬件 2.1冯诺依曼机基本思想 2.1.1冯诺依曼计算机的特点 2.2计算机的功能部件 2.2.1MAR 和 MDR 位数的概念和计算 3.计算机软件 3.1系统软件和应用软件 3.2三个级别的语言 3.2.1三种机器语言的特点 3…

★pwn 24.04环境搭建保姆级教程★

★pwn 24.04环境搭建保姆级教程★ &#x1f338;前言&#x1f33a;Ubuntu 24.04虚拟机&#x1f337;VM&#x1f337;Ubuntu 24.04镜像 &#x1f33a;工具&#x1f337;可能出现的git clone错误&#x1f337;复制粘贴问题&#x1f337;攻击&#x1f337;编题 &#x1f33a;美化&…