【持续更新中】transformer详解和embedding大模型
但由于Transformer不包含任何循环结构,Transformer模型对输入序列中的每个元素进行处理时是并行的,各个单词在Transformer中都同时经过Decoder-Encoder的变换,这就导致了Transformer无法捕获单词的位置信息。在编码器-解码器注意力中,查询来自前一个解码器层的输出,而键和值来自整个编码器的输出。有些区域很敏感,有些则不敏感。推荐时,会有一个助词词汇,剔除
Embedding的相关背景
这里记录一下自己学习embedding大模型的记录,涉及到transformer和bert这些。
embedding模型主要用于表示数据,而生成模型(如GPT)用于生成内容。但像BERT这样的模型虽然主要用于表示,也可以用于生成任务的中间步骤。
embedding模型可以捕捉语义信息,但可能需要大量计算资源,且在某些领域需要微调。
-
优势:
-
捕捉深层语义,超越关键词匹配的局限性。
-
支持多语言、多模态的统一表示。
-
可通过微调适配垂直领域(感觉主要是对一些专业性较强的如医疗、法律、经济等比较有帮助)。
-
-
挑战:
-
计算资源消耗大,落地需优化推理效率。
-
对领域特定术语或小众语言可能表现不足。
-
解释性较差,需结合可视化工具辅助分析。
-
一切都可以编码,比如说图片是三原色,所以可以用一些三维矩阵的组合来表示任何一张图,图形越清晰,矩阵越多。音频等也同理。
把文本拆分成一个个token,这样计算机处理起来更方便。英文里,“subword” 可拆成 “sub” 和 “word”;中文里,“海南麒麟瓜” 能拆成 “海”“南”“麒”“麟”“瓜” 这些 Token。不同模型处理文本得到的 Token 数量不同。这个和分词还有每个方法分token的机制不同导致分出来的数量不同很好理解,比如有的英文按照空格和标点分隔,有的中文会按照维护的中文词典分隔。中文分词主要解决无空格语言的词汇边界问题,依赖词典或统计模型。Tokenization更通用,可处理多语言混合文本,例如同时分割中英文字符和表情符号这种。
embedding可以将离散型的数据(如单词、商品等)映射到连续型向量空间。这种映射不仅使得可以对离散型数据进行数值计算,而且能够保留数据之间的相似性信息。
如下图所示,可以对比one-hot编码之类的简单编码方式(维度导致的计算资源不同、无法体现信息关联性等等):

Embedding model(嵌入模型) 是一种将词语或句子转换成数字向量的技术。它实际上是将高维、离散的输入数据(例如文本、图像、声音等)转换成低维、连续的向量表示的过程。
以 LLM 为基础的知识问答系统构建方法核心在于:
1、将用户问题和本地知识进行 Embedding,通过向量相似度(Vector Similarity)实现召回
2、通过 LLM 对用户问题进行意图识别;并对原始答案加工整合

Embedding的主要应用场景
下面可以自行了解,仅举了一些例子,讲解的时候可以具体一些:
语义搜索与问答 - 客服机器人
机器翻译 – 文本翻译
文本分类与情感分析 - 新闻分类、电商评论检索
推荐系统 - Netflix、Yelp
防范交易风险 - 盗刷判断
下面这个其实一般是用神经网络解决的,但是也可以看出它的特征提取的思想:每一层提取的特征不相同,假设发生引入干扰啥的,对于一些识别重要特征的层需要减少。

应用场景举例
阿里云的embedding模型服务示例
从这里图示可以看的出来,嵌入模型的主要场景在阿里平台上分为两种:实现语义搜索、实现语义推荐,示例1是<通过文本向量模型获取产品描述对应的向量,然后基于向量,计算评论与产品描述之间的余弦相似度,最终返回与产品描述最相关的评论>,示例2是<通过计算字符串的embedding向量,并基于余弦相似度的距离,找出与给定字符串最相似的k个字符串后输出这些字符串及其相似度距离>,检索模型->推荐系统,训练好了会有一个缓存。设置和加载嵌入缓存,以提高推荐系统等任务中嵌入向量的处理效率

Example1-实现语义搜索:通过文本向量模型获取产品描述对应的向量,然后基于向量,计算评论与产品描述之间的余弦相似度,最终返回与产品描述最相关的评论。
Example2-实现语义推荐:通过阿里灵积的API生成文本嵌入表示,设置和加载embedding缓存(本质上就是用空间换时间),定义获取embedding的函数,从数据集中提取所有文章的描述,然后分别基于特定主题的文章来获取5篇最相似[1]的文章推荐。通过计算字符串的embedding向量,并基于余弦相似度[1]的距离,找出与给定字符串最相似的k个字符串。

示例:假设每千次调用成本为0.1元,缓存命中率90%可为10万次查询节省900元。
*缓存淘汰策略可以参考LRU和TTL。
Embedding 一个详细应用场景举例

Embedding相关知识点
Transformer
首先,可以先了解一下Transformer工作原理的四部曲:Embedding(向量化)、Attention(注意力机制)、MLPs(多层感知机,在下面写的反映出来就是前向传播和反向传播)和Unembedding(模型输出,这里先主要说embedding部分的)。这里记得提示一定要注意数据预处理(清洗、格式处理等)。
从宏观角度来看,Transformer的编码器是由多个相同的层叠加而成的,每个层都有两个子层。第一个子层是多头自注意力(multi-head self-attention)汇聚;第二个子层是基于位置的前馈网络(positionwise feed-forward network)。具体来说,在计算编码器的自注意力时,查询、键和值都来自前一个编码器层的输出,并且每个子层都采用了残差连接(residual connection)。在Transformer中,对于序列中任何位置的任何输入x∈Rd,都要求满足sublayer(x)∈Rd,以便残差连接满足x+sublayer(x)∈Rd。在残差连接的加法计算之后,紧接着应用层规范化(layer normalization)。因此,输入序列对应的每个位置,Transformer编码器都将输出一个d维表示向量。Transformer可支持并行计算的一点就是矩阵乘法。
位置编码
为什么要引入位置编码呢?
Transformer 放弃了循环结构,而采用了自注意力机制,这使得 Transformer 可以并行计算,从而大大提高了训练速度。同时,自注意力机制也使得 Transformer 可以捕获任意距离的依赖关系,从而解决了长期依赖问题。但由于 Transformer 不包含任何循环结构,Transformer模型对输入序列中的每个元素进行处理时是并行的,各个单词在 Transformer 中都同时经过 Decoder-Encoder 的变换,这就导致了 Transformer 无法捕获单词的位置信息。因此,如果没有位置信息,Transformer模型无法区分序列中元素的先后顺序。这样会导致无法识别前后文语境使得相同单词计算得到一样的权重。
为了解决这个问题,需要在输入的单词向量中加入某种信息,以区分每个单词的位置。这一信息被称为位置编码,并且在Transformer中使用的是绝对编码。举个比较直观的例子:
“Life is like riding a bicycle, it requires balance.”(生活就像骑自行车,它需要平衡。)
“Life requires balance, like riding a bicycle.”(生活需要平衡,就像骑自行车一样。)
如果没有位置编码,那么由于它们是相同的字符,所以对应的编码是一样的,所以在计算机处理的时候是一样的。还有诸如“苹果”和“苹果”在不同语境下也是有差异的。一个好的位置编码应该满足的一些条件(未必完全):
1、它应当为每个时间步(单词在句子中的位置)输出唯一的编码
2、在不同长度的句子中,任何两个时间步之间的距离都应保持一致
3、这个方法应当能够推广到任意长的句子,即位置编码的数值应当是有界的,而且无需依赖训练数据
4、编码有一定的值域范围,避免比 word embedding 大很多,使得模型倾向于 position(所以一些时候会给原来的值放大一些防止这种情况)
5、需要体现一定的相对次序关系,并且在一定范围内的编码差异不应该依赖于文本
由此引入位置编码——为序列添加位置信息,弥补自注意力机制的位置不敏感性。(有种说法可以当作频率调制,根据嵌入向量中的每个位置而变化)
下面这三个不恰当的位置编码例子特别好,可以和上面的要求进行对比,位置编码直接看可能不能太明白为什么这样设计,看这种反例很能帮助理解位置编码。



首先,它不只是一个数字,而是一个d维的向量,包含了句子里某个特定位置的信息;其次,这个编码没有集成到模型本身,而是把这个带有句子位置信息的向量配备进字词当中。换句话说,我们通过注入字词的顺序来加强了模型的输入。

pos是词的输入序列中的位置(同一位置的词不变),𝑑𝑚𝑜𝑑𝑒𝑙d_model是维度(如在transformer中为512固定不变),i表述编码中的位置(数量/2,向上取整),偶数位置用正弦,奇数位置用余弦。
举例说明:
["珍珠奶茶", "芝士莓莓", "芒果冰沙", "柠檬绿茶"]
(普通位置编码)
比如此处的pos值为0~3,d_model 为4,i的值为0~1
---------------------------------------------------------------------------------------------------------------------
["我们", "家", "的", "猫", "和", "狗", "是", "好", "朋友"]
(transformer中的位置编码)
比如此处的pos值为0~8,d_model 为512,i的值为0~4
激活函数
这里只介绍两个常见用于transformer场景的激活函数。
ReLU

ReLU 激活函数图像如上图所示,函数表达式如下:

-
引入非线性:神经网络需要非线性激活函数来学习复杂的模式。ReLU 函数为线性函数引入了非线性特性,使得神经网络能够拟合非线性的数据分布。如果没有激活函数,神经网络将只能表示线性关系,无法处理复杂的现实问题。
-
计算效率高:ReLU 函数的计算非常简单,只需要进行一次比较和取最大值的操作。这使得它在计算上非常高效,特别是在大规模神经网络和深度学习中,可以显著提高训练和推理的速度。
-
缓解梯度消失问题:在深度神经网络中,梯度消失是一个常见的问题。当使用传统的激活函数如 Sigmoid 或 Tanh 时,在反向传播过程中,梯度可能会变得非常小,导致网络难以训练。ReLU 函数在正区间的梯度为常数 1,不会出现梯度消失的问题,这有助于深度神经网络的训练。
-
稀疏性:ReLU 函数具有稀疏激活的特性。在训练过程中,很多神经元的输出会被设置为 0,这使得网络具有一定的稀疏性。稀疏性可以减少模型的参数数量,降低过拟合的风险,并提高模型的泛化能力。
ReLU 函数和softmax都是深度学习中较为流行的一种激活函数,它具有如下优点:
1、当输入为正时,不存在梯度饱和问题。
2、由于ReLU 函数中只存在线性关系,因此它的计算速度比较快。
当然,它也有缺点:
-
Dead ReLU 问题。当输入为负时,ReLU 完全失效,在正向传播过程中,这不是问题。有些区域很敏感,有些则不敏感。但是在反向传播过程中,如果输入负数,则梯度将完全为零,sigmoid 函数和 tanh 函数也具有相同的问题;
-
ReLU 函数的输出为 0 或正数,这意味着 ReLU 函数不是以 0 为中心的函数。
GELU(比较复杂)


GELU可以当作为RELU的一种平滑策略,曲线相对Relu比较平。
Softmax
Softmax 是用于多类分类问题的激活函数,在多类分类问题中,超过两个类标签则需要类成员关系。对于长度为 K 的任意实向量,Softmax 可以将其压缩为长度为 K,值在(0,1)范围内,并且向量中元素的总和为 1 的实向量,最终作用说人话就是把值分布转换为概率分布。

下面这个图比较直观:

Softmax 与正常的 max 函数不同:max 函数仅输出最大值,但 Softmax 确保较小的值具有较小的概率,并且不会直接丢弃。Softmax 函数的分母结合了原始输出值的所有因子,这意味着 Softmax 函数获得的各种概率彼此相关。
由于softmax是指数倍计算,所以值不够大的会被归一化成0。
Max分类只输出最大的类别索引,没有提供关于模型预测信心的信息,也不考虑类别间的概率分布
Softmax 激活函数的主要缺点是:
-
在零点不可微;
-
负输入的梯度为零,这意味着对于该区域的激活,权重不会在反向传播期间更新,因此会产生永不激活的死亡神经元。
引入温度后的softmax计算函数:

在一些场景中比如阿里云的在线大模型服务中经常看到选取温度,温度确实本质上是对softmax产生影响从而影响结果的输出的。当Temperature值较高时,softmax函数的输出会更加平滑,体现在各个类别的概率分布会更加均匀,模型更倾向于给出多样化的输出。相反,当Temperature值较低时,softmax函数的输出会更加尖锐,体现在某个类别的概率会显著高于其他类别,模型更倾向于给出确定的输出。
而温度过高和过低表现都不太好,拿写个故事来说,如果温度过低,那么故事会非常老套,温度过高,就容易说着说着就开始胡言乱语(通常错误的回答被称作幻觉),因为本质上这种输出都是通过计算概率最高的那个来获取的,温度高了把合乎逻辑的输出都给盖住了,比如说openai中的温度范围取值是0~2。
下面这个图就表示的非常形象:随着温度的升高,输出结果的几率逐渐接近。

自注意力
首先来介绍注意力机制。

突出的非自主性提示 依赖于任务的意志提示
注意力可以从上面的图看到,模拟的是人类或者生物对于一些东西的观察,左边这个图由于咖啡杯是红色的,其它都是黑白的,所以人眼自然而然的会关注到这个红色的咖啡杯,因为它体现出了更大的突出特征性。这个在计算机的眼中也是很正常的,在图像处理中,颜色的方差可以用来描述图像中颜色的多样性。红色咖啡杯在图像中引入了更多的颜色变化,因此方差更大,使得图像更加生动和吸引人,计算出来的值也都更大。注意力层的作用就是让模型在处理文本时将注意力只放在某些词语上,而且其实Transformer 模型本来是为了翻译任务而设计的。(很好理解,因为相似的可能同时出现,基于训练预料能发现与它关联的-直接关联、隐含关联)但是随着时间的发展,可以结合其它的算法。
下面是Encoder-Decoder分心模型

句子对分别由各自的单词序列构成,得到X和Y:

非线性变换后得到中间语义表示C:
![]()
对于解码器Decoder来说,其任务是根据句子X的中间语义表示C和之前已经生成的历史信息 y1, y2, … … yi-1 来生成 i 时刻要生成的单词 yi ,每个 yi 都依次产生,那么看起来就是整个系统根据输入句子X生成了目标句子 Y 。

下面是带了注意力的模型:

自注意力这个地方是自己的Q、K、V进行计算,输出得到最终结果。
简单介绍一下q、k、v的大致概念,query 、 key & value 的概念其实来源于推荐系统。基本原理是:给定一个 query,计算query 与 key 的相关性,然后根据query 与 key 的相关性去找到最合适的 value。举个例子:在电影推荐中。query 是某个人对电影的喜好信息(比如兴趣点、年龄、性别等)、key 是电影的类型(喜剧、年代等)、value 就是待推荐的电影。在这个例子中,query, key 和 value 的每个属性虽然在不同的空间,其实他们是有一定的潜在关系的,也就是说通过某种变换,可以使得三者的属性在一个相近的空间中。
512维的向量能够编码足够复杂的语义和位置信息。更高的维度可以捕捉更多特征,但会显著增加计算量;更低的维度则可能限制模型能力。(其实就是中间层而非输入输出层多和少的问题,感兴趣可以了解一下)。在transformer经典的论文中的位置编码也是512维,方便向量相加。
注意力Attention机制的最核心的公式为:

Transformer论文中将这个Attention公式描述为:Scaled Dot-Product Attention。其中,Q为Query、K为Key、V为Value。Q、K、V是从哪儿来的呢?Q、K、V其实都是从同样的输入矩阵X线性变换来的:

其中Wq、Wk和Wv是三个可训练的参数矩阵,它们是随机初始化的相同维度的矩阵(其实大多数时候设置成一样的,因为这个后面会调整)。输入矩阵X分别与它们三个相乘,生成计算所需要的Q、K、V,相当于经历了一次线性变换。Attention不直接使用X,而是使用经过矩阵乘法生成的这三个矩阵,因为使用三个可训练的参数矩阵,这样增加更多的参数,可增强模型的拟合能力,提高模型的效果。除以dk是防止数值太大,训练结果发散。一般最后K和V的值都是接近的。
多了一个参数层,参数帮忙拟合任意函数。如果不做变换,那么一个词永远和自己相关度最大。这不是正常想要的结果,比如这个词是代词,那正常思维肯定想知道它具体指代什么而不关心这个代词本身是什么。
为什么要乘以矩阵(2)?——参考奇异值分解的过程

通过将输入向量与矩阵相乘,至少有以下优势:
1、增加了每个输入 token 关注输入序列中其他 token 的可能性,而不是关注单个 token 本身
2、可能更好的(潜在)输入向量表示
3、将输入向量转换为所需维度的空间,例如,从维度 5 转换为 2,或从 n 转换为 m 等(这在实践中很有用)
当 m<d 时,投影过程类似PCA,通过SVD的奇异值截断保留主要信息;当 m>d 时,则扩展维度以增强表达能力
在推荐系统中,低频词往往携带独特信息。通过投影矩阵的SVD分解:
低频词可能分布在权重矩阵的高奇异值方向(因其信息密度高),在投影后被放大。
高频词(如“the”、“is”)可能分布在低奇异值方向,在投影后被抑制,避免主导注意力计算。
极低频词(可能为脏数据)的投影方向可能随机且不稳定,但由于其对应的奇异值极小,在计算注意力时会被自然过滤。
注意力的例子:更高的评分相关性代表着更高的注意力,从而对结果的影响就更大。比如说green分很高,apple的分很低,但是由于eating和apple的相关性更高,所以计算出来的结果apple的权重就会大一些。并且附加一个点,在推荐系统里按照大多数情况来说,出现频率越少的词汇往往越为重要,极少的除外,因为可能被处理为是脏数据而被剔除。


1、两个序列必须具有相同的维度
2、两个序列可以是不同的模式形态(如:文本、声音、图像)
3、一个序列作为输入的Q,定义了输出的序列长度,另一个序列提供输入的K&V
Cross-Attention 机制涉及到两个不同的序列,其中一个序列的元素作为查询(Query),而另一个序列的元素作为键(Key)和值(Value)。这种机制使得模型能够在处理一个序列时参考另一个序列的信息,从而在两个序列之间建立关联。在Transformer模型的解码器(Decoder)部分中,Cross-Attention被用来让解码器的每个位置都能关注到编码器的输出,这样解码器就可以利用编码器处理后的输入序列信息来生成输出序列。
下面是一个具体计算相似度和sum的例子:

但是在实际的应用场景,为了提高计算速度,我们采用的是矩阵的方式,直接计算出Query, Key, Value的矩阵,然后把embedding的值与三个矩阵直接相乘,把得到的新矩阵 Q 与 K 相乘,乘以一个常数,做softmax操作,通过 query 和 key 的相似性程度来确定 value 的权重分布。当dk很大时,QK⊤的乘法结果方差变大,进行Scale可以使方差变小,训练时梯度更新更稳定。最后乘上 V 矩阵,得到加权求和的结果。

下面这个图表达的会更加直观一点:

Normalization有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为0方差为1的数据。我们在把数据送入激活函数之前进行normalization(归一化),因为我们不希望输入数据落在激活函数的饱和区。
掩码,防止看到未来的结果

通过计算预测概率生成的内容后可以使用蒸馏技术将相关信息教给其他模型。
优点:
局部感受野,通过堆叠扩大范围 -> 全局感受野,直接建模任意位置关系。
损失函数
残差链接
Bert中的池化策略
多层感知机
前馈网络(FFN)
用于增强非线性。
结构:每个FFN包含两个线性变换层和一个激活函数(通常为ReLU)
作用:对自注意力输出的每个位置独立进行非线性变换,增强模型表达能力
特性:同一层内不同位置的神经元共享参数,但处理过程相互独立
反向传播
反向传播其实是一个在之前神经网络阶段也很常用的,损失是用来衡量一个模型训练水平的很重要的指标。在反向传播过程中,模型通过损失函数(如交叉熵损失)计算预测误差,并逐层反向传递梯度。局部梯度指每个操作(如矩阵乘法、激活函数、注意力计算等)对参数的偏导数,通过链式法则累积:

1、梯度下降法:

2、Adam优化器
是一种自适应学习率优化算法,算法原理如下:

m_t:梯度的指数加权移动平均值
v_t:梯度的平方值的指数加权移动平均值
β_1、β_2:超参数,控制指数加权移动平均值的衰减率
ε:一个很小的常数,防止分母为0
混合专家(MoE)
下面介绍和Pre-Norm结构还有梯度裁剪一样都可以提升训练效率和稳定性的MOE,在FFN中引入多个专家网络,通过门控机制动态路由输入,减少冗余计算并提升模型容量。在之前的传统大模型中,Transformer中的FFN参数占比较高,通常超过60+%,而且随着模型参数量的增加,这块占比可能会高达百分之九十以上;并且FNN的处理具有严重的稀疏性,虽说参数量很大,可通常每次计算只有少部分参数被激活或使用,因此可以通过减少FFN的推理激活参数量来优化大模型的推理速度。它其实就是将Transformer中一个大的FFN网络替换成多个小的专家网络(也是FFN结构),每次推理只激活其中的1~2个专家网络,大大减少推理的计算参数量,从而提升整个MoE模型的推理效率。
在推理时,MoE模型还有一个优势,就是可以将不同专家放在不同的节点或GPU上,通过专家并行来进一步加速整体模型的推理效率。
专家机制我感觉类似神经网络里面的每层特征,还是拿前面的数字识别举例,比如说有的层提取边缘特征,有的层提取轮廓(什么圆圈、直线、曲线这种)等等,让不同的专家有不同的能力。比如现在有8个专家,假如输入一篇数学的文章,体现出来主要使用是1和3的话(假定这两个专家擅长处理数字和逻辑推理),输入一篇历史的文章,体现出来主要使用是4和6。背后每个token都有更倾向的专家集。

当一个 token 的表示传入 MoE 层时,首先会经过一个专门的 Gate 网络,该网络负责计算 token 与各个路由专家之间的匹配得分。具体流程如下:
-
计算匹配得分
Gate 网络通过线性变换计算每个 token 与所有路由专家的兼容性得分。得分反映了 token 与各专家“契合”的程度。 -
选择 Top-K 专家
基于得分,Gate 网络为每个 token 选择 Top-K 个最合适的路由专家。在 DeepSeek‐V3 中,每个 token 通常选择 8 个路由专家(在一些实现中还可能对跨节点路由做限制,如最多路由到 4 个不同节点),从而只激活极少数专家进行计算。 -
专家处理与加权聚合
被选中的专家各自对 token 进行独立处理(一般采用一个轻量级的前馈网络,类似于 Transformer 中的 FFN 模块,可以对比前文的Transformer的前馈网络部分),产生各自的输出。最终,这些专家的输出会根据 Gate 网络给出的权重进行加权聚合,再与共享专家的输出进行融合,形成当前 MoE 层的最终输出表示。
每个专家本质上是小型的FFN(前馈神经网络),其输入和输出维度与原FFN层一致。假设原FFN的中间隐藏层为16384维,在MoE中每个专家可能设计为4096维的多个并行FFN,最终输出通过加权合并后仍保持与原FFN相同的维度。MoE模型和路由网络的梯度更新都是基于整个批次的损失计算,而非单个token。即使不同token激活了不同专家,梯度会在整个批次处理完成后统一更新,这种批量处理机制避免了频繁的梯度更新。
使用举例:
Mixtral 8 * 7B是由法国Mistral AI团队开源的一款高质量稀疏专家混合模型。对于每个token,Gating网络选择8组专家网络中的2个进行处理,并且将两个专家的输出加权求和给到下一层网络,在增加模型参数总量的同时,优化了模型推理的成本。在大多数基准测试中,Mixtral 8x7B模型与Llama2 70B(hidden_dim=28672)和GPT-3.5表现相当,因此具有很高的使用性价比。
下面这个可以帮助理解,在这附上:
-
引入偏置(Bias)
每个路由专家都会有一个可学习的偏置项。当某个专家长期处于低使用状态时,其偏置会自适应地上调,从而增加其被选中的概率;反之,对于被频繁激活的专家,其偏置会下降,从而降低激活概率。这种方法可以在不引入额外损失项的情况下,自动实现专家之间的负载均衡。 -
仅影响路由选择
值得注意的是,这个偏置项只用于专家选择过程,即在计算得分时加上偏置,而不会在专家输出加权求和时使用,从而确保了最终输出不受不必要的干扰。
目前一些主流MOE架构:
1、全替换 2、仅替换部分 3、专家设计(共享、非共享-选择性激活) 4、混合异构专家模型(MoE层中的每个专家的大小不再相同,赋予每个专家不同的表达能力)
残差模块与归一化
残差模块作用:
解决梯度消失:深度网络中,反向传播时梯度可能逐渐变小,导致浅层参数无法更新。
保留原始信息:允许网络跳过某些复杂变换,直接传递输入信息,确保至少不差于原始输入。
Output=F(X)+X
层归一化作用:
稳定训练:解决每层输入分布变化大导致训练慢的问题。
加速收敛:通过归一化使每层输入分布稳定,类似“音量调节”。
具体步骤:
1.计算均值与方差:对每个样本的所有特征求均值 μ 和方差 σ2"σ" ^2。
2.归一化:减去均值,除以标准差(加极小值 ϵ 防除零)。
3.缩放与平移:通过可学习参数 γ(缩放)和 β(平移)恢复数据表达能力。
[3.0,5.0,2.0] -> [−0.21,1.07,−0.86] μ=3.333 σ2=2.444
这里注意下连续梯度值>1时,梯度在反向传播过程中发生链式相乘导致的梯度爆炸问题,可以通过梯度裁剪的方法比如限制梯度向量的范数来控制更新幅度。
这里的
缩放系数 = max_norm / 原梯度范数
新梯度 = 原梯度 * 缩放系数
前面的层归一化和残差连接已经尽可能保证了,一边训练时强制限制个梯度范数为1.0就行。
embedding模型蒸馏
模型蒸馏其实通俗来说就是一个老师教一个学生,我感觉比较有价值的是可以节约训练时间,而且可以用更小的参数尽量使得模型性能更好(公式不好复制下面直接截胶片中的图):

projection = nn.Linear(d_student, d_teacher)
# 优化器(仅优化学生模型和投影层)
optimizer = torch.optim.Adam(
list(student_model.parameters()) +
list(projection.parameters()),
lr=1e-5)
for batch in dataloader:
input_ids, labels = batch
with torch.no_grad():
teacher_outputs = teacher_model(input_ids)
teacher_logits = teacher_outputs.logits
teacher_embed = teacher_outputs.last_hidden_state[:, 0, :]
student_outputs = student_model(input_ids)
student_logits = student_outputs.logits
student_embed = student_outputs.last_hidden_state[:, 0, :]
student_outputs = student_model(input_ids)
student_logits = student_outputs.logits
student_embed = student_outputs.last_hidden_state[:, 0, :]
# 1. Embedding对齐损失(L_embed)
projected_embed = projection(student_embed) #投影到教师维度
loss_embed = F.mse_loss(projected_embed, teacher_embed)
# 2. Logits对齐损失(L_logits,使用温度系数)
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
log_soft_student = F.log_softmax(student_logits / temperature, dim=-1)
loss_logits = F.kl_div(log_soft_student, soft_teacher,
reduction='batchmean') * (temperature ** 2)
loss_hard = F.cross_entropy(student_logits, labels)
loss_total = alpha * (loss_embed + loss_logits) + (1 - alpha) * loss_hard
#反向传播
optimizer.zero_grad()
loss_total.backward()
optimizer.step()
检索增强技术(Retrieval Augmented Generation,RAG)
背景
为什么搜索比fine-tuning更好?
一、目前微调模型存在的问题
举例子:输入分布漂移或者事实改变,更少的产生幻觉
尽管目前 GPT-4 或者 ChatGPT 的能力已经很强大,但是目前它依然有很大的缺陷:
1. 知识不足或信息丢失
2. 训练数据是基于2021年9月之前的数据,缺少最新的数据
3. 无法访问私有的文档
4. 基于历史会话中获取信息
5. 对输入的内容长度有限制,
这个限制通常在几千到数万个tokens之间,具体取决于模型架构和可用的硬件资源。这意味着对于更长的文本,例如整本书或长文章,可能无法一次将所有文本输入到语言模型中和内存中。
解决方案:
基于 Embedding 搜索 + 问答的模式,把知识通过 Embedding 后存储到向量数据库,作为长期记忆。提问时首先在向量数据库中搜索相关知识,然后仅把返回的结果提供给大模型作为输入。进一步也可以针对问题和检索得到的 embedding 做一些提示工程,来优化 ChatGPT 的回答。
二、为什么搜索比微调更好
GPT可以通过两种方式学习知识:
1、通过模型权重(即在训练集上微调模型)
2、通过模型输入(即将知识插入到输入消息中)
虽然通过数据训练是GPT学习所有其他知识的方式,看起来微调会比搜索好,但是OpenAI通常不建议将其作为教授模型知识的方式。微调更适合于教授专业任务或风格,对于事实回忆来说则不太可靠。
注意,这里说的是事实回忆,也就是说,如果你有自己的数据,只想从数据中获得知识或者获得相关的内容,那么搜索比微调可能更好。举个例子来说,如果你有大批的历史数据,你只想找到数据中你需要的部分,包括某些概念在哪里出现过,为什么使用了它等等。这些都算是事实回忆。
但是,如果你希望模型可以基于某种模式生成某些内容。例如,你希望模型可以基于XX风格生成某些工作日志甚至是宣传内容,那么微调可能效果更好。
类比来说,模型权重就像长期记忆。当对模型进行微调时,就像为一周后的考试而学习。当考试到来时,模型可能会忘记细节,或者错误地记住它从未读过的事实。
相比之下,消息输入就像短期记忆。当您将知识插入消息时,就像带着开放的笔记参加考试。有了笔记,模型更有可能得出正确的答案。
相对于微调,文本搜索的一个缺点是每个模型一次只能读取有限数量的文本,以OpenAI为例,其一次输入的内容限制如下:
模型名称
文本最长输入
gpt-3.5-turbo(4096个tokens【大约5页】)
gpt-4(8192个tokens【大约10页】)
gpt-4-32k(32768个tokens【约40页】)
延续这个比喻,可以将模型想象成一位学生,尽管可能有许多参考书可供借鉴,但一次只能查看几页笔记。
因此,为了构建一个能够利用大量文本回答问题的系统,OpenAI建议使用“搜索-提问”方法。
三、文本检索
搜索文本的方法很多,包括:
• 基于词汇的搜索(Lexical-based)
• 基于图形的搜索(Graph-based)
• 基于嵌入的搜索(Embedding-based)
OpenAI推荐的词嵌入方法。嵌入很容易实现,并且在问题中表现尤为出色,因为问题通常在词汇上不会与它们的答案重叠。
可以考虑将仅使用嵌入的搜索视为作为整个系统的起点。更好的搜索系统可能会结合多种搜索方法,以及特性,如受欢迎程度、最近性、用户历史记录、与先前搜索结果的冗余、点击率数据等。
通过将问题首先转换为假设的答案然后再进行嵌入的技术,如HyDE,也可以提高问答检索的性能。同样,GPT还可以通过自动将问题转换为一组关键词或搜索术语来潜在地改善搜索结果。
四、如何建立基于embeddings的搜索系统来解决问题
总的来说,这样的系统包含3个步骤:准备需要检索的知识、检索、提问即可:
• 准备搜索数据(仅一次)
(1) 收集:即获取你要用的数据,例如OpenAI的案例是下载几百篇有关2022年奥运会的维基百科文章。
(2) 切块:将文档分成短小的、大多是自包含的部分以进行嵌入。
(3) 嵌入:使用OpenAI API对每个部分数据获得embeddings结果。
(4) 存储:存储embedding是(对于大型数据集,可以使用向量数据库)。
• 搜索(每次查询一次)
(1) 给定用户问题,从OpenAI API生成查询的embeddings。
(2) 使用embeddings,按照与查询相关性对文本部分进行排序。
• 提问(每次查询一次)
(1) 将问题和最相关的部分插入到发送给GPT的消息中。
(2) 返回GPT的答案。
简单总结一下就是将原始数据用嵌入的方式存储,然后针对问题获取embeddings结果,再利用这个结果检索最相似的内容,把这个提问和最相似的结果发给GPT,获得GPT的回答即可。也就是说,这是另一种解决GPT无法回答最新数据和长文本内容的方法。这个方法比原始的方法来说多了几个步骤,但是成本并不一定更多,因为GPT的接口比embedding的接口更贵。这种场景常见的使用方式为结合问题来对相关的文档库进行搜索&回答。
搜索引擎的设计中,最核心的两项技术是「召回(Match)」和「排序(Rank)」
推荐系统 - 训练集和测试集的选取、基本原理(依据你的喜好/依据你想看的内容)
csv文件里面是很多数据
以 LLM 为基础的知识问答系统构建方法核心在于:
• 将用户问题和本地知识进行 Embedding,通过向量相似度(Vector Similarity)实现召回
• 通过 LLM 对用户问题进行意图识别;并对原始答案加工整合。
基于变换器的双向编码器表示技术(BERT)
介绍完了前面的Transformer,下面就开始介绍BERT。BERT的结构其实可以算作是截取了Transformer的一部分(Encoder)。这块其实不是特别了解,就先大概写一下。
BERT 代表双向编码器表示来自Transformer(BERT),用于高效地将高度非结构化的文本数据表示为向量。BERT是一个经过训练的 Transformer 编码器堆栈。
BERT模型的训练和权重调整过程可以分为两个主要阶段:预训练(Pre-training)和微调(Fine-tuning)。
1、预训练
预训练的方法主要分为两种
1. Masked Language Model(MLM)
任务描述:
- 在输入序列中随机选择15%的单词进行遮蔽,并用特殊符号MASK替换其中的80%,10%用随机单词替换,剩余10%保持不变。
- 模型的目标是预测这些被遮蔽的单词。
mask比率:15%,任务是将这些全部预测,将其中的80%替换成[mask],10%替换成一个随机的token,剩下的10%保留原来的token。这样做可以提高模型的鲁棒性。这个比例也可以自己控制。随机的原因是防止过拟合,为什么呢,比如说机械性的学习,看到my dog 就会输出is hairy,因为第二个句子是有明显错误的,因为
80%的时间是采用[mask],my dog is hairy → my dog is [MASK]
10%的时间是随机取一个词来代替mask的词,my dog is hairy -> my dog is apple
10%的时间保持不变,my dog is hairy -> my dog is hairy
桌上的水杯里有咖啡(正确)
咖啡的水杯里有桌上(错误)
具体步骤:
- 输入处理:对于输入文本中的每个句子,随机选择15%的token进行遮蔽。然后,80%的被遮蔽的token被替换成一个特殊的MASK标记,10%的token被替换成其他随机的token,而剩下的10%则保持原样。
- 模型预测:模型通过编码器层生成每个位置的上下文表示,然后通过一个分类层预测被遮蔽的token。
- 损失计算:使用交叉熵损失函数计算模型预测和实际被遮蔽的token之间的差异。
- 反向传播:通过反向传播算法更新模型参数,以减小损失。
举个简单的例子列一下交叉熵怎么计算的,在bert的MLM中有这样一个待预测的句子:
"阅读是通向[MASK]世界的一扇窗。"
["[CLS]", "阅读", "是", "通向", "[MASK]", "世界", "的", "一", "扇", "窗", "[SEP]"]
["知识": 0.7, "未知": 0.1, "新": 0.15, "神秘": 0.05]
["知识": 1, "未知": 0, "新": 0, "神秘": 0]
下面交叉熵的计算公式后可得出:

真实分布P:P = [1,0,0,0]
预测分布Q:Q = [0.7,0.1,0.15,0.05]
交叉熵损失H(p,q) = −[1⋅log0.7 + 0⋅log0.1 + 0⋅log0.15 + 0⋅log0.05] = −log0.7 ≈ 0.357
BERT模型使用一个预定义的词汇表。比如,BERT-base模型的词汇表大小是30000个单词和子词。这意味着在计算交叉熵损失时,模型会基于这个巨大的词汇表进行计算。每个标记的位置都会有一个概率分布,分布在这30000个词汇上的概率之和为1,这个步骤会涉及到大规模的矩阵乘法和Softmax计算。
2. Next Sentence Prediction(NSP)
任务描述:
- 输入一对句子,模型需要预测第二个句子是否是第一个句子的下一句。
- 50%的输入对是真实的句子对,另外50%是随机抽取的不相关的句子对。
具体步骤:
- 输入处理:对于输入的一对句子,有50%的概率是真实的句子对(IsNext),另外50%是随机抽取的不相关的句子(NotNext)。然后,这一对句子被输入BERT模型。
- 模型预测:模型通过编码器层生成CLS标记的表示,然后通过一个分类层预测这一对句子是否在语义上是相邻的(IsNext)还是不相关的(NotNext)。
- 损失计算:使用二分类交叉熵损失函数计算模型预测和实际标签之间的差异。
当使用transformer训练生成类任务的模型时,一批句子送入模型时,需要进行掩码(MASK)的操作,从而避免信息被提前泄露,导致模型无法学习。但,自然语言类任务中还存在表征学习、情感分类、语义相似度等其它任务,在训练针对这一类任务的模型时,掩码是不必要的,甚至不利于模型使用更多语义信息,这便是BERT的第一大贡献。
在BERT出现之前,许多训练任务都是有监督的任务,比如:二分类(语义是否相似)、问答。然而,有标签的语料太少了。这时,大家就开始动脑筋了,文本天然有时序性,也就是,我说了一句,你大概能猜出下一句是什么,但猜句子有点困难,那是不是可以使用猜单词的方式呢?但这个方式已经被OpenAI用过了,被称为NTP(Next Token Prediction)的训练方式。有没有其它方式呢?BERT论文的作者就翻到了一篇论文,这篇论文提出一个猜想,使用完形填空填空的方式也可以预训练模型,BERT的作者就做了实验,发现真的可以,这便成了BERT的第二大贡献。
BERT的创新点如下:
1、如果不预测下一个token,双向的transformer(不加mask)是好的。
传统的语言模型如GPT,是基于左到右的方式进行预测,即每个词的预测仅依赖于前面的词。这种方法意味着模型不能同时利用前后文的信息来理解当前词的语义。而BERT引入的创新就是它不再只关注前文预测下一个词。在自然语言处理(NLP)任务中,仅基于上下文的部分来理解当前词可能导致信息不足。BERT使用的是双向的Transformer,即同时考虑前后文的信息,这样模型能够更全面地理解每个词的语义。这里的“双向”指的是模型在处理每个词时,既考虑它前面的词,也考虑它后面的词。为了实现这一点,BERT采用了掩码语言模型(MLM),即随机掩盖句子中的一些词,并让模型预测这些被掩盖的词。
而这里提到的“(不加mask)是好的”是强调,如果不去强制模型只能看前文(即不加单向的mask),让模型能够自由地同时看前后文,这样的双向Transformer可以更好地理解语言的上下文关系。
2、完形填空可以拿来与训练NLP类的模型,且性能卓越。
3、用完形填空预训练出的模型,可以处理极多的下游任务(微调后)。
因此训练语料的质量非常重要,因为每个权重是一样的,如果有太多无效数据的话会对结果有很多影响
Linear Classifier从头学,Bert是微调

输入一个句子,输出一个class(分类)

是否是相邻的句子(True、false还是未知)


红色决定起始、蓝色决定结尾,如果蓝色在前面就是无解(bert只能回答连续的答案)
红色和蓝色是学出来的,给很多问题和文章还有答案,让它看到新的就能知道落在第几个词汇

bert有24层,每一层像一个nlp的pipline,每次词汇24个vector,nlp某一层的值越大,越需要其中的某一层

104种语言训练出来,所以天然会翻译,从来没有人教中文文章怎么分类,只有英文,它也会自动学会
更多推荐


所有评论(0)