Transformer的Pytorch实现【1】

使用Pytorch手把手搭建一个Transformer网络结构并完成一个小型翻译任务。

首先,对Transformer结构进行拆解,Transformer由编码器和解码器(Encoder-Decoder)组成,编码器由Multi-Head Attention + Feed-Forward Network组成的结构堆叠而成,解码器由Multi-Head Attention + Multi-Head Attention + Feed-Forward Network组成的结构堆叠而成。

class Encoder(nn.Module):def __init__(self, corpus) -> None:super().__init__()self.src_emb = nn.Embedding(len(corpus.src_vocab), d_embedding) # word embeddingself.pos_emb = nn.Embedding.from_pretrained(get_sin_enc_table(corpus.src_len + 1, d_embedding), freeze=True) # position embeddingself.layers = nn.ModuleList([EncoderLayer() for _ in range(encoder_n_layers)])def forward(self, enc_inputs):pos_indices = torch.arange(1, enc_inputs.size(1)+1).unsqueeze(0).to(enc_inputs)enc_outputs = self.src_emb(enc_inputs) + self.pos_emb(pos_indices)enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)enc_self_attn_weights = []for layer in self.layers:enc_outputs, enc_self_attn_weight = layer(enc_outputs, enc_self_attn_mask)enc_self_attn_weights.append(enc_self_attn_weight)return enc_outputs, enc_self_attn_weightsclass Decoder(nn.Module):def __init__(self, corpus) -> None:super().__init__()self.tgt_emb = nn.Embedding(len(corpus.tgt_vocab), d_embedding) # word embeddingself.pos_emb = nn.Embedding.from_pretrained(get_sin_enc_table(corpus.tgt_len + 1, d_embedding), freeze=True) # position embeddingself.layers = nn.ModuleList([DecoderLayer() for _ in range(decoder_n_layers)])def forward(self, dec_inputs, enc_inputs, enc_outputs):pos_indices = torch.arange(1, dec_inputs.size(1)+1).unsqueeze(0).to(dec_inputs)dec_outputs = self.tgt_emb(dec_inputs) + self.pos_emb(pos_indices)# 生成填充掩码dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)# 生成后续掩码dec_self_attn_subsequent_mask= get_attn_subsequent_mask(dec_inputs)# 整合掩码dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs) # 自注意力机制只有填充掩码,且是根据encoder和decoder的输入生成的dec_self_attn_weights = []dec_enc_attn_weights = []for layer in self.layers:dec_outputs, dec_self_attn_weight, dec_enc_attn_weight = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)dec_self_attn_weights.append(dec_self_attn_weight)dec_enc_attn_weights.append(dec_enc_attn_weight)return dec_outputs, dec_self_attn_weights, dec_enc_attn_weightsclass Transformer(nn.Module):def __init__(self, corpus) -> None:super().__init__()self.encoder = Encoder(corpus)self.decoder = Decoder(corpus)self.projection = nn.Linear(d_embedding, len(corpus.tgt_vocab), bias=False)def forward(self, enc_inputs, dec_inputs):enc_outputs, enc_self_attn_weights = self.encoder(enc_inputs)dec_outputs, dec_self_attn_weights, dec_enc_attn_weights = self.decoder(dec_inputs, enc_inputs, enc_outputs)dec_logits = self.projection(dec_outputs)return dec_logits, enc_self_attn_weights, dec_self_attn_weights, dec_enc_attn_weights

很直接的,我们可以看到,要实现Transformer需要实现两个基本结构:Multi-Head Attention + Feed-Forward Network。

Multi-Head Attention

要实现多头注意力机制,首先要实现注意力机制。

Attention的计算:

  1. 对输入进行线性变换,得到QKV矩阵
  2. QK点积、缩放、softmax
  3. 再对V进行加权求和

Multi-Head Attention就是包含多个Attention头:

  1. 多个头进行concat
  2. 连接全连接层,使得Multi-Head Attention得到的输出与输入相同

画板

我们来手把手走一下Multi-Head Attention的计算:

假设输入序列的长度为n,针对每个token的编码长度为d,则输入为 ( n , d ) (n, d) (n,d),权重矩阵: W Q : ( d , d q ) , W K : ( d , d q ) , W V : ( d , d v ) W_Q: (d, d_q), W_K: (d, d_q), W_V:(d, d_v) WQ:(d,dq),WK:(d,dq),WV:(d,dv)

  1. 得到的QKV分别为: Q : ( n , d q ) , K : ( n , d q ) , V : ( n , d v ) Q: (n, d_q), K: (n, d_q), V:(n, d_v) Q:(n,dq),K:(n,dq),V:(n,dv)
  2. Q与K的转置相乘: Q ⋅ K T : ( n , d q ) ⋅ ( d q , n ) = ( n , n ) Q \cdot K^T : (n, d_q) \cdot (d_q, n) = (n, n) QKT:(n,dq)(dq,n)=(n,n),每一个点的值代表第i个token和第j个token的相似度
  3. 缩放:不改变矩阵的尺寸,只改变矩阵中的值
  4. softmax:对矩阵中的值进行归一化
  5. 对V做加权求和: s o f t m a x ( Q ⋅ K T d k ) ⋅ V = ( n , n ) ⋅ ( n , d v ) = ( n , d v ) softmax(\frac {Q \cdot K^T} {\sqrt{d_k}})\cdot V = (n, n)\cdot(n, d_v) = (n, d_v) softmax(dk QKT)V=(n,n)(n,dv)=(n,dv)
  6. 针对一个 ( n , d ) (n, d) (n,d)的输入,单头得到的输出为 ( n , d v ) (n, d_v) (n,dv), 多头concat得到的输出就是 ( n h e a d s , n , d v ) (n_{heads}, n, d_v) (nheads,n,dv)
  7. transpose并进行fully-connection运算: ( n h e a d s , n , d ) − > ( n , n h e a d s ∗ d v ) − > ( n , d ) (n_{heads}, n, d) -> (n, n_{heads}*d_v) -> (n, d) (nheads,n,d)>(n,nheadsdv)>(n,d)

代码实现如下:

class MultiHeadAttention(nn.Module):def __init__(self) -> None:super().__init__()self.W_Q = nn.Linear(d_embedding, d_k * n_heads)self.W_K = nn.Linear(d_embedding, d_k * n_heads)self.W_V = nn.Linear(d_embedding, d_v * n_heads)self.linear = nn.Linear(n_heads * d_v, d_embedding)self.layer_norm = nn.LayerNorm(d_embedding)def forward(self, Q, K, V, attn_mask):'''Q: [batch, len_q, d_embedding]K: [batch, len_k, d_embedding]V: [batch, len_v, d_embedding]attn_mask: [batch, len_q, len_k]'''residual, batch_size = Q, Q.size(0)# step1: 对输入进行线性变换 + 重塑q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # [batch, n_heads, len_q, d_k]k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # [batch, n_heads, len_k, d_k]v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1, 2) # [batch, n_heads, len_v, d_v]# step2: 计算注意力分数, 点积 + 缩放scores = torch.matmul(q_s, k_s.transpose(-1, -2)) / np.sqrt(d_k) # [batch_size, n_heads, len_q, len_k]# step3: 使用注意力掩码, 将mask值为1处的权重替换为极小值attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1) # [batch_size, n_heads, len_q, len_k]scores.masked_fill_(attn_mask, -1e9)# step4: 对注意力分数进行归一化weights = nn.Softmax(dim=-1)(scores)# step5: 计算上下文向量,对V进行加权求和context = torch.matmul(weights, v_s) # [batch_size, n_heads, len_q, dim_v]# step6: fccontext = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) # [batch_size, len_q, n_heads * dim_v]output = self.linear(context) # [batch_size, len_q, d_embedding]# step7: layernormoutput = self.layer_norm(output + residual)return output, weights

Feed-Forward Network

在Encoder和Decoder的每个注意力层后面都会接一个Position-Wise Feed-Forward Network,起到进一步提取特征的作用。这个过程在输入序列上的每个位置都是独立完成的,不打乱,不整合,不循环,因此称为Position-Wise Feed-Forward。
计算公式为:
F ( x ) = m a x ( 0 , W 1 x + b 1 ) ∗ W 2 + b 2 F(x) = max(0, W_1x+b_1)*W_2+b_2 F(x)=max(0,W1x+b1)W2+b2

计算过程如图所示,使用conv1/fc先将输入序列映射到更高维度(d_ff是一个可调节的超参数,一般是4倍的d),然后再将映射后的序列降维到原始维度。
画板

使用conv1d的实现如下

nn.Conv1d(in_channels, out_channels, kernel_size, ...)
( b a t c h , n , d ) − > ( b a t c h , d , n ) − > ( b a t c h , d f f , n ) − > ( b a t c h , d , n ) − > ( b a t c h , n , d ) (batch, n, d)-> (batch, d, n) -> (batch, d_{ff}, n) -> (batch, d, n) -> (batch, n, d) batch,n,d>(batch,d,n)>(batch,dff,n)>(batch,d,n)>(batch,n,d)
第一个conv1d的参数为:
nn.Conv1d(d, d_ff, 1, ...)
第二个conv1d的参数为:
nn.Conv1d(d_ff, d, 1, ...)

class PoswiseFeedForwardNet(nn.Module):def __init__(self, d_ff=2048) -> None:super().__init__()# 定义一个一维卷积层,将输入映射到更高维度self.conv1 = nn.Conv1d(in_channels=d_embedding, out_channels=d_ff, kernel_size=1)# 定义一个一维卷积层,将输入映射回原始维度self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_embedding, kernel_size=1)self.layer_norm = nn.LayerNorm(d_embedding)def forward(self, inputs):'''inputs: [batch_size, len_q, embedding_dim]output: [batch_size, len_q, embedding_dim]'''residual = inputsoutput = self.conv1(inputs.transpose(1, 2))output = nn.ReLU()(output)output = self.conv2(output)output = self.layer_norm(output.transpose(1, 2) + residual)return output
使用fc的实现如下

nn.Linear(in_features, out_features, bias=True)
( b a t c h , n , d ) − > ( b a t c h , n , d f f ) − > ( b a t c h , n , d ) (batch, n, d)-> (batch, n, d_{ff}) -> (batch, n, d) batch,n,d>(batch,n,dff)>(batch,n,d)
第一个fc的参数为:
nn.Linear(d, d_ff, bias=True)
第一个fc的参数为:
nn.Linear(d_ff, d, bias=True)

class PoswiseFeedForwardNet_fc(nn.Module):def __init__(self, d_ff=2048) -> None:super().__init__()# 定义一个一维卷积层,将输入映射到更高维度self.fc1 = nn.Linear(d_embedding, d_ff, bias=True)self.fc2 = nn.Linear(d_ff, d_embedding, bias=True)# self.conv1 = nn.Conv1d(in_channels=d_embedding, out_channels=d_ff, kernel_size=1)# 定义一个一维卷积层,将输入映射回原始维度# self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_embedding, kernel_size=1)self.layer_norm = nn.LayerNorm(d_embedding)def forward(self, inputs):'''inputs: [batch_size, len_q, embedding_dim]output: [batch_size, len_q, embedding_dim]'''residual = inputsoutput = self.fc1(inputs)output = nn.ReLU()(output)output = self.fc2(output)output = self.layer_norm(output + residual)return output

内容参考链接:

GPT图解

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

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

相关文章

【MySQL】存储引擎

MySQL采用的是可插拔的存储引擎架构,也就是说在运行期间可以动态的加载或卸载存储引擎;查看当前服务器存储引擎的方法show engines,其中重点关注两个字段即可,其一是Support-表示当前服务器是否支持,其二是它的数值yes…

构建校园社团信息管理平台:Spring Boot技术的核心要点

6系统测试 6.1概念和意义 测试的定义:程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为: 目的:发现程序的错误; 任务:通过在计算机上执行程序,暴露程序中潜在的错误。 另一个…

RAG工具:FlashRAG用于高效 RAG 研究的 Python 工具包

随着大语言模型的火热,如何提高生成内容的准确性和可靠性,成为各行业关注的重点。检索增强生成(RAG)正是通过将强大的检索功能与语言模型结合,在生成文本时引入来自外部的实时信息。 今天,我们来了解一款为…

任天堂新款闹钟被玩家破解,竟能运行《毁灭战士》游戏!

任天堂于10月9日推出的Nintendo Sound Clock Alarmo闹钟在市场上引起了强烈反响。这款定价为99.99美元(约706元人民币)的闹钟,在日本则以12980日元(约619元人民币)的价格迅速被抢购一空。 近日,首批收到闹钟…

我笑了,居民日均劳动不满3.5小时

鸭鸭是一位现代都市青年,生活节奏规律,时间安排精细,非常符合国家统计局发布的时间利用调查报告中的数据。以下是鸭鸭一天的生活日常: 早上 7:00 - 鸭鸭准时起床,开始一天的生活。他通常会在床上稍微刷刷手机&#xf…

django快速基本配置(2)

知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 目录 配置开发目录 配置MySQL数据库 配置Redis数据库 配置工程日志 用户注册 跨域CORS 注意 配置开发目录 libs 存放第三方的库文件 utils 存放项目自己定义的公共函数或类等 apps 存…

前端技术月刊-2024.11

本月技术月刊聚焦于前端技术的最新发展和业务实践。业界资讯部分,React Native 0.76 版本发布,带来全新架构;Deno 2.0 和 Node.js 23 版本更新,推动 JavaScript 生态进步;Flutter 团队规模缩减,引发社区关注…

Golang的Web应用架构设计

# Golang的Web应用架构设计 介绍 是一种快速、高效、可靠的编程语言,它在Web应用开发中越来越受欢迎。Golang的Web应用架构设计通常包括前端、后端和数据库三个部分。在本篇文章中,我们将详细介绍Golang的Web应用架构设计及其组成部分。 前端 在Golang的…

element-plus按需引入报错AutoImport is not a function

官网文档:快速开始 | Element Plus webpack配置 // webpack.config.js const AutoImport require(unplugin-auto-import/webpack) const Components require(unplugin-vue-components/webpack) const { ElementPlusResolver } require(unplugin-vue-components…

【51单片机】串口通信原理 + 使用

学习使用的开发板:STC89C52RC/LE52RC 编程软件:Keil5 烧录软件:stc-isp 开发板实图: 文章目录 串口硬件电路UART串口相关寄存器 编码单片机通过串口发送数据电脑通过串口发送数据控制LED灯 串口 串口是一种应用十分广泛的通讯接…

操作系统——计算机系统概述——1.4操作系统结构

目录 操作系统的体系结构 大内核(宏内核/单内核): 微内核: 分层法 模块化 操作系统的体系结构 大内核(宏内核/单内核): 将操作系统的主要功能模块都作为系统内核,运行在核心态。…

ssh和ssl的区别在哪些方面?

在网络安全和数据保护领域,谈话中经常提到的两个词是SSH(安全外壳)和SSL(安全套接字层)。尽管这两者在在线通信安全中都具有重要意义,但它们的使用目的不同,并且处于网络堆栈的不同级别。本文将深入分析 SSH 和 SSL 主要区别在哪些方面。 概念…

第三十三篇:TCP协议如何避免/减少网络拥塞,TCP系列八

一、流量控制 一般来说,我们总是希望数据传输得更快一些,但是如果发送方把数据发送得太快,接收方可能来不及接收,造成数据的丢失,数据重发,造成网络资源的浪费甚至网络拥塞。所谓的流量控制(fl…

基于卷积神经网络的棉花病虫害识别与防治系统,resnet50,mobilenet模型【pytorch框架+python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示: 棉花病害识别与防治系统,卷积神经网络,resnet50,mobilenet【pytorch框架,python源码】_哔哩哔哩_bilibili (一)简介 基于卷积…

基于STM32的智能停车场管理系统设计

引言 本项目旨在基于STM32微控制器设计一个智能停车场管理系统。该系统集成了多种传感器和控制模块,以实现停车位实时检测、车辆识别、自动控制栏杆、车位信息显示和云端数据管理等功能。智能停车场管理系统可以有效提升停车场的运转效率,改善车主的停车…

《大数据与人工智能:提升数据质量与数量的利器》

《大数据与人工智能:提升数据质量与数量的利器》 一、大数据与人工智能的融合趋势二、大数据增加数据数量的方法(一)不同途径的数据增量(二)数据增强的多样方法 三、人工智能提升数据数量的手段(一&#xf…

C/C++常用编译工具链:GCC,Clang

目录 GNU Compiler Collection GCC的优势 编译产生的中间文件 Clang Clang的特点 什么是LLVM? Clang编译过程中产生的中间表示文件 关于Clang的调试 C 编译工具链中有几个主要的编译工具,包括: GNU Compiler Collection (GCC…

停车位类型分割系统:一条龙教学体系

停车位类型分割系统源码&数据集分享 [yolov8-seg-aux&yolov8-seg-C2f-DAttention等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global Al l…

DICOM标准:CR图像模块属性详解——计算放射线照相术(CR)及其在DICOM中的表示

目录 CR图像及其在DICOM中的表示 1 计算放射线照相术 1.1 CR序列组件 1.1 -- CR 序列模块属性 1.2 CR 图像模块 表1.2 -- CR 图像模块属性 结论 CR图像及其在DICOM中的表示 计算放射线照相术(Computed Radiography, CR)是一种利用计算机技术对传统…

springboot 基于web的动漫会员购系统,计算机毕业设计项目源码 024,计算机毕设程序(LW+开题报告、中期报告、任务书等全套方案)

摘 要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,动漫艺术当然也不例外。动漫会员购系统是以实际运用为开发背景,运用软件工程原理和开发方法,采用…