当前位置: 首页 > article >正文

手撕 Transformer (2):嵌入层和位置编码的实现上篇文章讲过,Transformer 可分为四个部分:输入、输出、编码器、解

嵌入层的作用为了将文本中词汇的数字表示转换为向量表示语义向量这样后续神经网络就可以对其进行计算了。1.1 代码实现import torchimport torch.nn as nnimport mathfrom torch.autograd import Variableclass Embeddings(nn.Module):def __init__(self, d_model, vocab):# d_model: 词嵌入的维度# vocab: 词表的大小super(Embeddings, self).__init__()self.lut nn.Embedding(vocab, d_model)self.d_model d_modeldef forward(self, x):# 前向传播# x 是输入进模型的文本通过映射后的数字张量return self.lut(x) * math.sqrt(self.d_model)if __name__ __main__:d_model 512vocab 1000x Variable(torch.LongTensor([[123, 233, 510, 998], [985, 211, 110, 996]]))emb Embeddings(d_model, vocab)emb_result emb(x)print(embedding result: , emb_result)print(emb_result.shape) # torch.Size([2, 4, 512])在Embeddings类中self.lut nn.Embedding(vocab, d_model)会创建一个随机初始化的嵌入矩阵。PyTorch 的nn.Embedding模块默认使用均匀分布随机初始化权重。在模型训练过程中当计算损失函数并执行反向传播时嵌入层的权重会接收到梯度然后通过优化器如 Adam进行更新。这样模型会逐渐学习到更有意义的词向量表示这些表示会捕捉到词语之间的语义和语法关系。运行结果embedding result: tensor([[[-49.8672, -21.6785, 18.1069, ..., 0.2031, -28.3568, 5.5724],[-55.8387, -26.6077, 37.4205, ..., 7.8280, -5.1322, 8.1475],[ 21.9637, 9.6126, 53.4801, ..., 16.6295, 37.5978, 13.2768],[-19.0594, -13.2244, 16.7811, ..., 16.9383, -46.1544, -3.1326]],[[ 9.8451, 22.9543, 3.1216, ..., 18.1514, 24.2709, 31.3333],[ 30.5660, -9.3572, -5.8656, ..., 4.3933, 9.5235, 9.1021],[ 14.2475, 28.2354, 49.7318, ..., 9.2369, -23.4376, -7.1588],[ 15.4746, 40.1049, -19.8356, ..., -25.1046, 13.6735, -18.5525]]],grad_fnMulBackward0)torch.Size([2, 4, 512])为什么需要学习嵌入向量随机初始化的嵌入向量只是初始值不包含任何语义信息。通过训练模型会根据具体任务如机器翻译、文本分类等的目标调整嵌入向量使得相似含义的词在向量空间中距离更近不同含义的词距离更远。为什么计算完Embedding之后要乘以 self.lut(x) * math.sqrt(self.d_model)放大信号词嵌入通常是随机初始化的其方差较小通过乘以 来放大嵌入向量的幅度确保嵌入向量的尺度与位置编码通常使用正弦/余弦函数生成相当。注意此处不是注意力机制中的缩放点积那个是除以 。我们可以单独把Embedding的作用拿出来看一下embedding nn.Embedding(10, 3)input1 torch.LongTensor([[1,2,4,5],[4,3,2,9]])print(整数张量表示词ID, input1)print(input1转成向量表示,embedding(input1))从下面的运行结果可以明显看出Embedding的数字表示转向量表示的作用。整数张量表示词ID tensor([[1, 2, 4, 5],[4, 3, 2, 9]])input1转成向量表示 tensor([[[-0.6656, 1.6754, -0.5841],[ 1.1583, 0.0122, 0.0297],[-1.5521, 1.9699, 0.0168],[ 0.9703, -0.0608, -0.6835]],[[-1.5521, 1.9699, 0.0168],[ 1.1763, 0.1059, -0.6196],[ 1.1583, 0.0122, 0.0297],[-0.7003, 0.6548, 0.0784]]], grad_fnEmbeddingBackward0)如果词ID中有数字0计算出的嵌入向量会是 0 吗从下面的例子中可以看到并不是。因为嵌入向量是随机初始化的并且在训练过程中不断更新。示例embedding nn.Embedding(10, 3)input2 torch.LongTensor([[0,2,0,5]])print(整数张量表示词ID, input2)print(input2转成向量表示,embedding(input2))从下面的运行结果可以看出虽然词ID中有 0但是嵌入向量中并没有 0 。整数张量表示词ID tensor([[0, 2, 0, 5]])input2转成向量表示 tensor([[[-2.0432, 0.4369, -0.4257],[-0.1574, 0.1013, -0.1821],[-2.0432, 0.4369, -0.4257],[ 0.0601, 0.9223, 0.3128]]], grad_fnEmbeddingBackward0)在实际应用中有的时候是需要嵌入向量中有 0 的让这些参数在训练的过程中不更新。当数据批量输入进模型时序列的长度可能不一致这时候就需要对短序列的特定维度进行补 0 使其与最长序列相等。初始化时对应的嵌入向量会初始化为 0 。训练时填充位置的嵌入向量不会被更新梯度为 0避免填充位置对模型训练产生干扰。推理时填充位置的嵌入向量保持为 0不影响模型对有效序列的处理。例如在机器翻译任务中输入句子[I love you, He eats]假设最长的序列是I love you长度为3短序列为He eats长度为2。填充后[[1, 2, 3], [4, 5, 0]]。具体代码只需要添加一个参数即可示例embedding nn.Embedding(10, 3, padding_idx0)input3 torch.LongTensor([[0,2,0,5]])input4 torch.LongTensor([[1, 2, 3], [4, 5, 0]])print(整数张量表示词ID, input3)print(input3转成向量表示,embedding(input3))print(input4转成向量表示,embedding(input4))运行结果整数张量表示词ID tensor([[0, 2, 0, 5]])input3转成向量表示 tensor([[[ 0.0000, 0.0000, 0.0000],[-0.7443, 0.0692, 0.0825],[ 0.0000, 0.0000, 0.0000],[-0.1140, -0.5122, -0.4336]]], grad_fnEmbeddingBackward0)input4转成向量表示 tensor([[[-1.0667, -0.9710, -0.4726],[-0.7443, 0.0692, 0.0825],[-0.8729, 0.7102, -1.5695]],[[ 0.7366, 1.0636, 0.5947],[-0.1140, -0.5122, -0.4336],[ 0.0000, 0.0000, 0.0000]]], grad_fnEmbeddingBackward0)2 位置编码2.1 为什么需要位置编码RNN 和 LSTM 是一个词一个词按顺序进模型自然知道先后CNN 有卷积核能看到局部顺序Transformer 不含循环结构、也不含卷积操作的自注意力是并行的同时看所有词没有顺序概念。如果没有位置编码那么“我爱你”和“你爱我”的词嵌入完全一样注意力计算结果完全一样。所以需要有位置编码。位置编码的作用补上顺序信息。在嵌入向量进入编码器和解码器之前我们需要把位置信息加入嵌入向量。位置编码与嵌入向量具有相同的维度 model二者可以直接相加。我们在本文只讨论 Transformer 原论文中使用的位置编码——正余弦位置编码这是一种相对位置编码。这种位置编码是固定、不可训练的。但不是所有的位置编码都是不可训练的如 BERT 和 GPT-1/2 用的位置编码是可学习的位置嵌入我们在此处不展开。Transformer 使用不同频率的正余弦函数构造位置编码其公式如下(,2)sin⁡(100002/model)(,21)cos⁡(100002/model)其中是词在句子中的位置比如第1个词第2个词model是词向量的维度在原论文中是 512是维度的索引。因为公式是把偶数维度 (2) 给 sin奇数维度 (21) 给 cos所以 的取值范围是 0,1,2,...,model2−1。第一次看到这两个公式的时候很多人都是一头雾水。比如为什么是 10000 为什么要计算 model2其实这不是严格“数学推导出来”的而是根据设计目标推导出的一个合理形式。这里把公式记住就行本文不做展开详情见这篇文章浅谈正余弦位置编码的数学原理2.2 代码实现class PositionalEncoding(nn.Module):Implement the PE function.def __init__(self, d_model, dropout, max_len5000):d_model: 词嵌入的维度dropout: 丢弃神经元的概率max_len: 每个句子的最大长度super(PositionalEncoding, self).__init__()self.dropout nn.Dropout(pdropout)# 初始化位置编码矩阵pe torch.zeros(max_len, d_model)# torch.arange(0, max_len)创建一维张量此时张量的形状为 torch.Size([max_len])# unsqueeze(dim) 表示在指定维度位置插入一个新维度# unsqueeze(0)在第 0 维插入形状变化 torch.Size([max_len]) → torch.Size([1, max_len])# unsqueeze(1)在第 1 维插入形状变化 torch.Size([max_len]) → torch.Size([max_len, 1])# 这样设计是为了后续与 div_term 进行广播运算时能正确计算出位置编码矩阵position torch.arange(0, max_len).unsqueeze(1)# 生成不同频率的缩放因子用于后续的正弦和余弦计算div_term torch.exp(# torch.arange(0, d_model, 2)生成从0到d_model-1步长为2的序列如[0, 2, 4, ..., d_model-2]# math.log(10000.0)自然对数作为频率的基数torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))# 对位置编码张量的偶数维度从0开始步长为2应用正弦函数pe[:, 0::2] torch.sin(position * div_term)# 对位置编码张量的奇数维度从1开始步长为2应用余弦函数pe[:, 1::2] torch.cos(position * div_term)# 在第 0 维插入一个新维度作为后续的 batch_size 维度# [max_len, d_model] - [1, max_len, d_model]pe pe.unsqueeze(0)# 将 pe 注册为模型的缓冲区使其成为模型的一部分自动保存和加载不参与梯度计算self.register_buffer(pe, pe)def forward(self, x): # x 的形状 [batch_size, seq_len, d_model]# self.pe[:, : x.size(1)]预计算位置编码张量 [1, max_len, d_model] 变为 [1, seq_len, d_model]# .requires_grad_(False)明确指定位置编码不参与梯度计算# x ...通过 pytorch 的广播机制位置编码会自动扩展为 [batch_size, seq_len, d_model]x x self.pe[:, : x.size(1)].requires_grad_(False)# dropout 随机将部分神经元的输出置为0防止过拟合return self.dropout(x)2.3 代码和公式之间的关联步骤 1构造位置索引矩阵positionposition torch.arange(0, max_len).unsqueeze(1)torch.arange(0, max_len)生成一个一维张量[0, 1, 2, ..., max_len-1]形状为[max_len]。.unsqueeze(1)在第 1 维列方向插入一个维度得到形状[max_len, 1]的列向量。为什么需要列向量因为后面要与频率缩放因子div_term行向量进行广播乘法生成一个[max_len, d_model/2]的矩阵其中每个元素是pos * factor_i。步骤 2计算频率缩放因子div_termdiv_term torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))torch.arange(0, d_model, 2)生成[0, 2, 4, ..., d_model-2]这些值正是公式中的 2。math.log(10000.0)是自然对数 ln⁡(10000)。将[2i]乘以-(ln(10000)/d_model)得到-(2i * ln(10000))/d_model。再取指数exp得到exp(-(2i * ln(10000))/d_model)。根据指数和对数性质exp⁡(−2ln⁡(10000)model)10000−2/model这正是公式中分母部分的倒数。也就是说div_term实际上是一个向量其第 个元素为div_term[]10000−2/model步骤 3计算position * div_termposition形状为torch.size([max_len, 1])div_term形状为torch.size([d_model/2])。通过广播机制两者相乘得到一个形状为[max_len, d_model/2]的矩阵矩阵的每个元素为position[]×div_term[]⋅10000−2/model这正是正弦/余弦函数的自变量。步骤 4填充偶数和奇数维度pe[:, 0::2]选取所有行、从第 0 列开始每隔一列即偶数索引列赋值为sin(position * div_term)。这样就实现了(,2)sin⁡(⋅10000−2/model)pe[:, 1::2]选取所有行、从第 1 列开始每隔一列即奇数索引列赋值为cos(position * div_term)。这样就实现了(,21)cos⁡(⋅10000−2/model)为什么代码中使用指数和对数变换直接计算10000 ** (-2i/d_model)也可以但存在两个问题幂运算在深度学习中可能不如指数对数稳定且高效。使用exp和log可以避免显式的除法更适合在 GPU 上并行计算。通过恒等变换10000−2/modelexp⁡(−2modelln⁡(10000))我们可以用一次exp和一次乘法完成所有频率的计算简洁高效。总结

相关文章:

手撕 Transformer (2):嵌入层和位置编码的实现上篇文章讲过,Transformer 可分为四个部分:输入、输出、编码器、解

嵌入层的作用:为了将文本中词汇的数字表示转换为向量表示(语义向量),这样后续神经网络就可以对其进行计算了。 1.1 代码实现 import torchimport torch.nn as nnimport mathfrom torch.autograd import Variableclass Embeddings…...

【数字孪生实战案例】如何给电子地图标记点实现三维点位同款的视角切换效果?~山海鲸可视化

在可视化项目中,常规电子地图标记点仅支持基础点位标注,无法联动视角切换;本文讲解如何为地图标记点复刻三维标记的视角跳转能力,实现点击点位即可一键切换预设场景视角。 1.在左侧组件库添加“GIS电子地图(基础&#…...

阿姆智创15.6寸工控一体机厂家,源头智造ODM定制方案,赋能SMT产线及设备场景

阿姆智创15.6寸工业触控工控一体机,以强悍硬件性能、丰富工业接口、稳定系统适配与一站式解决方案,深度服务SMT产线、运动控制、机器视觉等工业场景,为设备厂商与制造企业提供高可靠、可定制、易集成的智能控制终端,助力工业自动化…...

Redis专题(一)

1. 主从部署主从复制主要⽤于实现数据的冗余备份和读分担,并不是真正的高可用。一个主节点,一个或者多个从节点。同步数据的方向:单向 ,只能主节点到从节点。作用:数据冗余:除了数据持久化之外的一种数据冗…...

ToClaw全方位介绍:你的第一只“龙虾”AI助手,一分钟轻松领养!

ToClaw全方位介绍:你的第一只“龙虾”AI助手,一分钟轻松领养! 一、先来聊聊这只“龙虾”的故事 2026年开年,如果问中文互联网最火爆的技术热词是什么,那一定非「OpenClaw」莫属。这个被大家亲切称为“龙虾”的开源项目…...

创建基础数据表后数据无法保存怎么排查_权限设置与回滚处理

...

Docker 安装 Redis 完整实操教程(新手专用,数据不丢失)

本教程全程使用官方源,无第三方镜像,步骤简单易懂,重点解决「重启数据丢失」「权限异常」问题,新手可直接复制命令操作,无需额外配置。一、前置准备(必做)确保你的电脑已安装 Docker&#xff08…...

养鸡场规划:如何计算所需农场数量

在养鸡业中,如何高效地管理和规划农场的使用是一个关键问题。最近,我遇到了一位养鸡场主的需求,他需要根据每天的鸡出栏数据来计算所需农场的数量。今天,我们就来探讨如何通过编程解决这个问题。 问题背景 假设你有一个包含以下数…...

宝塔面板PHP8.0如何快速安装Redis缓存扩展_在PHP设置的安装扩展模块中一键配置

宝塔面板PHP 8.0下无法一键安装Redis扩展,因官方源无适配预编译包且构建脚本不兼容ZTS/NTS、phpize路径及头文件要求;须用pecl手动编译redis-5.3.7并正确配置php.ini。宝塔面板 PHP 8.0 下无法通过「安装扩展」一键启用 Redis,是因为官方源里…...

CUDA12.4环境适配:OpenClaw调用Qwen3-14B镜像的驱动配置详解

CUDA12.4环境适配:OpenClaw调用Qwen3-14B镜像的驱动配置详解 1. 为什么需要关注CUDA环境适配 上周我在本地部署Qwen3-14B镜像时,遇到了一个典型问题:模型加载到一半突然崩溃,控制台只留下一行模糊的CUDA错误提示。经过两天排查才…...

红烧肉制作技术详解

红烧肉制作技术详解 红烧肉是一道传统的中式美食,以其色泽红亮、口感酥烂、味道浓郁而闻名。本文将详细介绍红烧肉的制作步骤及技巧,帮助你在家也能做出美味的红烧肉。 材料准备 五花肉 500克生姜 适量大葱 适量八角 2颗桂皮 1小块冰糖 适量料酒 适量老抽…...

OpenClaw压力测试:Qwen3-32B在RTX4090D上的连续任务稳定性

OpenClaw压力测试:Qwen3-32B在RTX4090D上的连续任务稳定性 1. 测试背景与目标 上周在本地部署了OpenClaw对接Qwen3-32B模型后,我遇到了一个现实问题:当连续执行复杂任务链时,系统会在运行2-3小时后突然崩溃。作为需要724小时运行…...

OpenClaw技能市场探秘:Qwen3-32B-Chat镜像赋能10大自动化场景

OpenClaw技能市场探秘:Qwen3-32B-Chat镜像赋能10大自动化场景 1. 为什么需要技能市场? 第一次接触OpenClaw时,我误以为它只是个"高级版按键精灵"。直到在ClawHub技能市场看到wechat-publisher这个模块——它能直接将Markdown文章…...

Anaconda 虚拟环境创建后,切换Python 版本

Anaconda 虚拟环境创建后,Python 版本可以更换!完全不用删除重建环境,一行命令就能直接修改 / 切换 Python 版本,非常方便。一、切换 Python 版本的命令先激活你的虚拟环境,再执行升级 / 降级命令:1. 先激活…...

Anthropic源码又泄露了,让你把这个瓜吃明白?(Claude Code被动开源)

Anthropic源码又,又,又,又泄露了...到底发生了什么事?简单说,Claude Code在发布npm包时,一不小心把一个调试50多M的.map文件给打包进去了。多了个文件而已,听上去,是不是没什么&…...

OpenClaw本地化优势:Qwen3-14b_int4_awq模型数据安全实践

OpenClaw本地化优势:Qwen3-14b_int4_awq模型数据安全实践 1. 为什么选择本地化部署 去年我在处理一批客户调研数据时,遇到了一个棘手问题——调研报告包含大量敏感信息,但团队需要AI辅助分析。当时尝试了几个云端方案,要么因为数…...

OpenClaw场景合集:Qwen3-4B在10个日常任务中的高效应用

OpenClaw场景合集:Qwen3-4B在10个日常任务中的高效应用 1. 为什么选择OpenClawQwen3-4B组合 去年冬天,当我第一次尝试用OpenClaw自动化处理堆积如山的邮件时,这个组合就成了我的效率利器。OpenClaw作为本地化智能体框架,配合Qwe…...

海南自由贸易港借助“.CN”域名塑造线上专属品牌形象

自海南自由贸易港全岛封关运作以来,市场主体加速集聚,数字化转型需求持续释放,“.CN”域名逐步融入自贸港园区与入驻企业的线上品牌构建场景,成为其彰显数字化身份的重要标识。作为政策落地与产业集聚的核心平台,海南自…...

市场知名的光伏项目品牌找哪家

这两年不少做企业的、建农村自建房的业主都盯上了光伏项目——发了电自己用,余电还能卖,长期收益稳定,不少人靠着光伏每年多赚几万甚至几十万。但我接触过至少几十个踩坑的业主:要么找了小品牌装完就跑路,发电量比承诺…...

安装Ubuntu后安装ros一键操作

# ROS安装初始配置完整指南(新手必看) ## 前言 ROS(Robot Operating System)是机器人软件开发的主流框架,但对于新手来说,安装配置过程往往充满挑战。本文将详细介绍如何使用小鱼的一键安装脚本快速完成ROS…...

Grok API 实战指南:从申请到集成的开发者全攻略

1. Grok API 是什么?能做什么? 如果你是一名开发者,最近可能被 Grok API 刷屏了。简单来说,Grok API 是 xAI 公司提供的一套接口服务,允许开发者将强大的 Grok 大模型集成到自己的应用中。想象一下,你开发的…...

4月,新一轮发票抽奖,请收好这份开具发票指南!!

4月,重庆发票抽奖新的一轮发票抽奖已经了(目前第三轮)。你所在的城市不知道是不是也是第三期发票抽奖了。发票抽奖首先需要发票。发票除了线下直接找商家开具外,我们也可以在线上直接开具。这份发票开具指南,归纳总结我…...

51单片机实战:基于XPT2046的多传感器AD转换与LCD显示

1. 项目背景与核心器件选型 第一次接触51单片机AD转换时,我被各种专业术语搞得一头雾水。直到用XPT2046芯片完成了电位器、光敏电阻、热敏电阻的三路信号采集,才真正理解模拟信号数字化的奥妙。这个成本不到5元的触摸屏控制芯片,其实是个隐藏…...

别再纠结了!用Python的Pymoo库5分钟搞定多目标优化,找到你的Pareto最优解

用Python的Pymoo库5分钟实现多目标优化:从理论到实战的完整指南 当你在设计一款新产品时,既要控制成本又要保证性能;当你在调整机器学习模型时,既要提高准确率又要降低计算资源消耗——这些看似矛盾的需求,正是多目标优…...

从COX分析到预后模型:如何用R筛选关键基因并画出发表级森林图?

从COX分析到预后模型:如何用R筛选关键基因并画出发表级森林图? 在生物信息学研究中,COX比例风险模型是分析基因与患者生存关系的重要工具。但许多研究者在完成初步分析后常陷入困惑:面对数十个候选基因,如何筛选真正有…...

大数据可视化

1. 传播分析评估维度:包含认知(知晓、记忆)、行动(点击、搜索)、情感(喜好、美誉)三个层面传统评估:主要关注广告点击率和观看次数等表面指标深度评估:需要分析广告观看后…...

盈鹏飞T527评估板AHD摄像头实战:从硬件连接到QT界面调试全流程

盈鹏飞T527评估板AHD摄像头全流程开发指南:从硬件对接到QT界面优化 在嵌入式视觉系统开发中,AHD摄像头因其长距离传输优势成为安防、工业检测等场景的首选。盈鹏飞T527评估板搭载全志T527处理器,通过TP2815转换板实现四路AHD摄像头接入&#…...

Oracle VM VirtualBox快速上手指南——Win10环境下的下载与安装详解

1. 为什么选择VirtualBox搭建虚拟环境? 作为一个在虚拟化领域摸爬滚打多年的老手,我测试过市面上几乎所有主流虚拟机软件。对于Windows 10用户来说,Oracle VM VirtualBox绝对是入门虚拟化的首选利器。它最大的优势就是完全免费开源&#xff0…...

NonBlockingDelay:嵌入式非阻塞延时库原理与实践

1. 项目概述NonBlockingDelay 是一个专为嵌入式系统设计的轻量级、零依赖、单头文件(.hpp)非阻塞延时库。其核心目标是彻底替代delay()这类会挂起 CPU、阻塞所有任务执行的同步延时函数,使开发者能够在维持主循环(loop()&#xff…...

网站主域名和子域名的seo优化有何不同

网站主域名和子域名的SEO优化有何不同 在现代网络环境中,网站的SEO优化已经成为了提升网站流量、吸引潜在客户的关键环节。无论是网站主域名还是子域名,其在SEO优化中都有着不同的重要性和作用。本文将详细探讨网站主域名和子域名在SEO优化中的不同&…...