目录
- nlp&llm问题整理
- 1、nlp常见任务?
- 2、介绍一下nlp常用模型
- 3、描述一下transformer的推理与训练过程
- 4、介绍一下embedding技术
- 5、垂直领域大模型如何应用?
- 6、什么是rag?解决了什么问题?
- 6、文本检索的算法?
- 7、multi-head self-attention原理和代码实现
- 8、什么是padding?
- 9、什么是dropout?
- 10、目前知名的大模型有哪些?
- 11、语义检索流程?
- 12、layer normalization和batch normalization的区别
- 13、如何提高rag召回率?
- 14、transformer为什么需要位置编码?为什么用正、余弦进行位置编码?
- 如何选择嵌入模型?
- llm如何处理长文本输入?
- transformer中q、k、v是什么?
- 残差连接是什么?有什么作用?
- 损失函数对比
- 如何评估rag系统的效果
- peft 中微调的区别
- 模型压缩与加速方法
- rlhf
- 大模型梯度攻击
- 对比一下kg + llm 与 rag的优劣
- 向量之间计算相似度的方法,它们有什么区别?
nlp&llm问题整理
本文参考网上资料,并结合个人理解所撰写。如有错误,欢迎指出,十分感谢!持续更新中…
1、nlp常见任务?
nlp(natural language processing,自然语言处理)是人工智能(ai)和计算机科学的一个分支,专注于使计算机能够理解和生成人类语言。
常见的nlp任务有:文本分类(例如垃圾邮件检测)、情感分析(例如微博等社交媒体分析公众对某话题的情感倾向)、机器翻译(谷歌翻译、百度翻译等)、文本摘要、文本生成、问答对话系统(银行自动客服系统)、语音识别(苹果的siri)等等
2、介绍一下nlp常用模型
nlp自然语言处理领域常见的模型有循环神经网络模型rnn、transformer模型,其中循环神经网络模型中有长短期神经网络lstm以及门控循环单元gru两种典型变体。下面对这四种模型结构和基本原理进行介绍。
循环神经网络(rnn)
循环神经网络(recurrent neural networks, rnns)是一种用于处理序列数据的神经网络,其特点是能够在序列的每个时间步上保持一个隐藏状态,并将其传递给下一个时间步。rnn的模型结构可以有多种变体,但它们的核心思想是相同的。
上图是一个简单的rnn结构:
x
t
为输入向量
h
t
为隐藏状态向量
a
为模型参数
x_t 为输入向量 \\ h_t为隐藏状态向量 \\ a为模型参数
xt为输入向量ht为隐藏状态向量a为模型参数
循环神经网络的流程可以描述为:
1、时序数据中第一批数据x1输入rnn,初始化h0(全0初始化)、模型参数a可以初始化为一个较小的值;
2、x1和h0两个向量进行拼接,然后和参数a矩阵相乘,并输入双曲正切激活函数tanh,即可得到下一个时间步的隐层状态h1;
3、x2和h1两个向量进行拼接(回到第2步)… 如此循环;
循环神经网络和前馈神经网络(feedforward neural networks)不同,其中前馈神经网络的信息流动是单向的,从输入层传播到隐层再到输出层,而rnn中隐层状态会进行循环传递,并且参数a在每个时间步都是共享的。
pytorch中对rnn单元的定义如上图所示,必须要定义的超参为输入向量x的尺寸input_size和隐层h的尺寸hidden_size;rnn单元的输出其实就是下个循环所需要的隐层信息h。
rnn的长期依赖问题:简单来说就是由于rnn这种特殊的循环结构,每一步的状态都包含了上一个状态的输出,因此根据链式法则在反向传播求导的时候,越早的项连续相乘的部分就会越长。在处理长序列数据的时候,若连续相乘的项很小就有梯度消失的问题,而相反相乘项比较大的时候就会出现梯度爆炸的问题。
为了解决长期依赖的问题,学者们提出了长短期神经网络lstm和门控循环单元gru。
长短期神经网络(lstm)
长短期神经网络是rnn的一种,增加了多个门控结构,以及新增了一个信息流-------细胞状态。
如上图所示:
x
t
为输入向量
h
t
为隐藏状态向量
c
t
为细胞状态向量
黄色小框从左到右依次为:
f
为遗忘门、
i
输入门(中间俩小框)、
o
输出门
x_t 为输入向量 \\ h_t为隐藏状态向量 \\ c_t为细胞状态向量\\黄色小框从左到右依次为:f为遗忘门、i输入门(中间俩小框)、o输出门
xt为输入向量ht为隐藏状态向量ct为细胞状态向量黄色小框从左到右依次为:f为遗忘门、i输入门(中间俩小框)、o输出门
这些门控结构数学表达就是上图右侧的一些计算公式,是对输入向量、隐藏向量以及细胞状态向量的矩阵运算和非线性激活函数的运用。
定性的讲:
- 遗忘门是决定了细胞状态向量中信息的去留;
- 输入门也叫更新门,是对细胞状态向量的更新;
- 输出门则是由输入、隐藏以及细胞状态向量共同决定模型的输出。
lstm通过这些特殊的门控结构和细胞状态引入,有效地控制和存储信息,有效地改善了长期依赖问题。但也因此产生了大量的参数,训练开销增大。
pytorch中同样定义一个lstm单元只需定义好输入向量x的尺寸input_size和隐层h的尺寸hidden_size,细胞状态向量尺寸和隐藏状态向量一致。lstm单元的输出则与简单rnn不同,除了下个循环所需要的隐藏信息h之外,还有细胞状态c。
门控循环单元(gru)
门控循环单元是在lstm(长短期记忆网络)的基础上提出的,旨在简化lstm的结构并提高其计算效率。gru的设计目标是减少lstm的参数数量,从而降低计算复杂度,并提高模型的训练速度。
gru模型结构如上图所示:
x
t
为输入向量
h
t
为隐藏状态向量
r
t
为重置门
z
t
为更新门
h
~
t
为候选隐藏状态向量
x_t 为输入向量 \\ h_t为隐藏状态向量 \\ r_t为重置门\\z_t为更新门\\\widetilde{h}_t为候选隐藏状态向量
xt为输入向量ht为隐藏状态向量rt为重置门zt为更新门h
t为候选隐藏状态向量
与lstm相比取消了细胞状态向量,门控结构也减少了,训练效率增加的同时对于长序列数据也能学习到长期依赖关系。
pytorch中公式中的n就是候选隐藏状态。可以看出gru模型的输入输出和rnn的一致。gru的计算效率介于rnn和lstm之间。
transformer模型
transformer模型是由vaswani等人于2017年在论文《attention is all you need》中提出的,它是一种基于自注意力机制的深度神经网络模型,特别适用于处理序列数据。transformer模型的诞生背景是为了克服传统序列处理模型的局限性,提高计算效率,并更好地捕捉长距离依赖关系。自注意力机制和并行计算的特点使得transformer模型在nlp领域取得了显著的成功。
下面对模型逐层进行解释:
inputs: 词在词典中的索引组成的向量;例如一个3个词组成的句子索引向量可以为 [213, 123, 1];
embedding层: 将每个词的索引转换为512维(transformer默认的词向量维度)的词向量,词向量是能够表达词与词之间关系的稠密向量;例如对于一个3个词组成的句子,它的词向量矩阵尺寸为3 * 512 ;
positional encoding: 区分单、双数,利用正、余弦函数进行位置编码,表达词绝对和相对的位置信息;例子: 以一个简短的句子“我爱你”为例,对其进行位置编码
p
o
s
为词在句子序列中的位置
i
为维度索引,表示第
i
对单、双数维度
d
m
o
d
e
l
为模型嵌入向量维度为
512
pos为词在句子序列中的位置\\ i为维度索引,表示第i对单、双数维度 \\ d_{model}为模型嵌入向量维度为512
pos为词在句子序列中的位置i为维度索引,表示第i对单、双数维度dmodel为模型嵌入向量维度为512
位置编码层输出的矩阵尺寸和词向量矩阵一致;
矩阵相加(+): 在输入多头注意力层之前,每个词的词向量与其对应的位置编码向量进行逐元素相加。这个操作将词的语义信息和位置信息结合起来,为transformer模型提供了处理序列数据所需的顺序信息;
multi-head attention: 这一块是transformer模型的重点!多头注意力机制是一种扩展自注意力机制的方法,它将自注意力机制分解为多个“头”,每个“头”都在不同的表示空间中学习信息,从而能够捕捉到更丰富的特征和关系。具体的:
-
赋值: 经过位置编码的矩阵x分别赋值给v、k、q,因此目前这三个矩阵一模一样且都等于x;
-
线性层linear: 矩阵v、k、q分别输入独立的线性层,进行线性变换,即各自乘上一个参数矩阵。参数矩阵是并行独立训练出来的,所以变换后v、k、q现在不相同了,但他们维度还是一致的;
-
拆分: v、k、q根据头数h进行拆分,多头进行并行工作。在实际代码实现中就是:比如一个3*512的v矩阵,拆分成8头,即变成了8 * 3 * 64的矩阵。这样达到的效果是后续的矩阵运算本质上是每头单独进行运算,而不是一整块进行运算;
-
scaled dot-product attention: 经过拆分后的v、k、q输入“缩放点乘积注意力”模块,这个模块是多头注意力机制中的核心模块,基于k、q矩阵通过一番操作得到输入句子中每个词之间的注意力权重矩阵,然后对值矩阵(v)进行加权求和,从而使得每个词向量都融合上下文的信息,达到了语义增强的目的。该模块数学本质就是下面这个公式:
a t t e n t i o n ( q , k , v ) = s o f t m a x ( q k t d k ) v d k 为 q 与 k 的维度 attention(q,k,v) = softmax(\frac{qk^t}{\sqrt{d_k}})v\\d_k为q与k的维度 attention(q,k,v)=softmax(dkqkt)vdk为q与k的维度
- qk^t是两个矩阵之间的点积操作,得到n * n维度的点积矩阵,n为输入句子中的词数量。该矩阵意义是输入句子中每个词之间的相关性;
- */sqrt(d_k)缩小点积矩阵中元素的大小,避免后续softmax时梯度消失。类似于归一化、标准化的作用;
- 掩码操作mask(opt.): 包含padding mask和sequence mask两种。掩码就是让矩阵某些元素变为负无穷的数,使得其在后续softmax中的概率为0。其中padding mask旨在消除输入序列中padding的影响;而sequence mask只存在于解码器中,目的是在预测下一个词时,覆盖住后面的词汇注意力信息,达到只用前面序列来预测下一词的目的。
- softmax是一种基于自然指数函数的概率计算方法,可将向量转换成概率分布。这一步输出的概率分布矩阵就是其实就是注意力权重,反应了句子每个词之间之间的联系大小;
- 最后概率分布矩阵再和v矩阵相乘,得到注意力模块下的输出; -
拼接concat: 将多头输出的多个小矩阵拼接成一个大矩阵,这个大矩阵的尺寸和拆分之前的矩阵尺寸一致。代码实际操作时就是:例如8 * 3 * 64的8头矩阵转换为3*512的矩阵;
-
线性层linear: 再经过一个线性层,这层目的是将拼接后的矩阵信息更好的融合。输出和输入尺寸一致;
h
为头数,默认
8
v
值矩阵、
k
键矩阵、
q
查询矩阵
h为头数,默认8\\v值矩阵、k键矩阵、q查询矩阵
h为头数,默认8v值矩阵、k键矩阵、q查询矩阵
相加&标准化add&norm: 将multi-head attention模块输入矩阵和输出矩阵相加,然后将其标准化;
前馈神经网络feed forward: 将矩阵输入一个两层的全连接网络,激活函数为relu。输出维度和输入保持一致;
至此transformer中解码器和编码器的组成和基本原理描述完毕,可以对照transformer整体框架进行一下信息流梳理:
-输入inputs经过了embedding、位置编码,然后输入n个注意力机制和前馈神经网络组成的单元,得到编码器输出;
-输出outputs经过了embedding、位置编码;然后输入第一个注意力机制模块;然后再输入第二注意力机制模块。输入第二个注意力机制模块时,v、k由编码器输出赋值,q由解码器第一个注意力机制模块输出赋值;然后再经过前馈神经网络;同样经过n个这样的过程,得到解码器输出;
-解码器的输出矩阵输入一个线性层,将维度扩大到词汇表。例如线性层输入是3 * 512(3表示序列长度,512是嵌入维度),并且假设词汇表维度为10000,那么这个线性层输出尺寸就是3 * 10000;
-接着输入一个softmax计算概率分布,那么基于这个分布就可以得到输出序列中每个位置上最大概率出现的词汇。
bert模型
bert学习链接:link
bert(bidirectional encoder representations from transformers)是一种基于transformer encoder的预训练语言处理模型,由google ai在2018年提出。
[
c
l
s
]
输入句子的起始符号、
[
s
e
p
]
分隔符号(分隔句子、放置句尾部、分隔问题与回答)
[cls] 输入句子的起始符号、[sep]分隔符号(分隔句子、放置句尾部、分隔问题与回答)
[cls]输入句子的起始符号、[sep]分隔符号(分隔句子、放置句尾部、分隔问题与回答)
下面对bert模型进行介绍:
三个子模块的嵌入层
- token嵌入:这个就是传统的nlp中的词嵌入方法,把分词后的每个词项进行embedding,在基础的bert模型中词向量为768维;
- segment嵌入:在处理输入为句子对(nsp)问题时,将不是相邻句子分别赋值0或者1;对其它任务,不需要进行seqment embedding,都赋值为0即可;
- position嵌入:与transformer中基于函数的positionencoding方式不同,bert的位置嵌入是基于参数的方式,与词嵌入相似;
得到三个子嵌入矩阵后相加即可得到融合了词义、句类、位置信息的词向量作为后续输入。
训练中两个阶段:
- pre-training预训练
bert论文中提到了预训练策略包括masked lm与nsp。
masked lm: inputs随机遮盖或替换15%的词项,然后通过模型预测被遮盖或替换的部分。计算损失函数cross entropy的时候只用被遮盖或替换的部分。
nsp(next sentence predict): inputs的两个句子。50%是相邻的一对,50%是随机取的两个句子。输入模型模型来预测两者之间的相邻关系c。计算损失函数cross entropy。
- fine-tuning微调
在预训练模型的参数基础上,针对下游任务进行进一步的训练。以问答系统为例:- 制作数据集:将问题和答案(参考资料)转换为bert模型可以接受的输入格式,首部添加[cls]、问题和答案用[sep]分割;
- 利用训练集中进行前向传播和反向传播微调参数;
- 利用验证集进行评估;
3、描述一下transformer的推理与训练过程
推理过程
以翻译任务为例,待翻译句子为“我爱你”。
- inputs即为“我爱你”这个句子的索引向量,经过encoder各个模块,输出的矩阵赋值给decoder的k、q;
step = 0 - 由于目前是第一次推理,因此outputs为起始符号"[start]"的索引向量,经过decoder的前半部分,生成一个矩阵作为后续的v;
- 然后将k、q、v输入decoder的后半部分,输出预测的第一个词“i”;
step = 1 - 现在是第二次推理,此时outputs为“[start] i”即之前已经预测出来的句子序列的索引向量,经过decoder的前半部分,生成一个矩阵作为后续的v,encoder生成的矩阵作为k和q;
- 然后将k、q、v输入decoder的后半部分,输出预测的第二个词“love”;
step = 2 - 现在是第三次推理,此时outputs为“[start] i love”,经过decoder的前半部分,生成一个矩阵作为后续的v,encoder生成的矩阵作为k和q;
- 然后将k、q、v输入decoder的后半部分,输出预测的第三个词“you”;
step = 3 - 现在是第四次推理,此时outputs为“[start] i love you”,经过decoder的前半部分,生成一个矩阵作为后续的v,encoder生成的矩阵作为k和q;
- 然后将k、q、v输入decoder的后半部分,输出预测的第四个词“[end]”即终止符,停止后续推理;
训练过程
训练过程分为两大步骤,前向传播和反向传播。
前向传播
- 和推理过程信息传递一致,每个时间步预测一个词;
- decoder的outputs输入为targets完整的目标句子,同样以翻译任务“我爱你”为例,decoder的outputs就是targets-----“i love you”的索引向量;
- decoder通过mask掩码来实现不提前透露预测信息,来保持和信息传递与推理一致;
反向传播 - output probabilities与target计算损失,损失函数是常用于分类问题的交叉熵损失(cross-entropy loss);
- 反向传播更新参数;
4、介绍一下embedding技术
在nlp领域中embedding即词嵌入技术,是一种将文本中的单词转换为固定长度的向量(词向量)技术。现有的模型考虑任务需求、计算资源、模型性能等因素词向量维度设置为100-1000不等。
为什么需要embedding技术?
1、机器学习需要数字化表达,因此文本数字化表达为向量才能被模型学习和训练;
2、维度灾难,早期nlp模型所用的独热编码(one-hot )的方式会导致特征向量极度稀疏;
假设一个词汇表有10000个词,那么按照位置进行独热编码,每个词就是一个10000维度的向量,那么整个词汇表就是10000x10000的矩阵,更何况现在词汇表一般都是几十万个词。稀疏矩阵会导致内存的浪费、计算效率的降低。
3、词汇鸿沟,独热编码不能表达词汇之间的联系;
独热编码下所有的单词之间的距离都相同,这显然是不合理的,因为实际中某些词汇的关系更密切。
embedding的方法
embedding的方法有很多,其中基于内容的word2vec方法是比较经典的方法。
word2vec方法包括两种架构:cbow连续词袋模型(continuous bag of words)和skip-gram跳字模型。
两种模型的目标都是经过训练得到词嵌入矩阵e。利用词汇的独热编码向量o与e相乘,即可得到稠密的低维度的词向量。
-cbow
预设好窗口大小,利用窗口内的上下文来预测目标词。
- 模型输入是上下文词汇的one-hot编码;
- 经过embedding层,即词汇编码各自和嵌入矩阵相乘,得到词向量;
- 然后计算上下文词向量的平均值;
- 再输入线性层,得到输出向量;
- 输入softmax层得到概率分布,最大概率位置对应的词即为预测结果;
- 经过大量的样本学习,即可得到所需的embedding嵌入矩阵。
-skip-gram
跳字模型为cbow的逆过程。
- 模型输入是目标词的独热编码向量;
- 经过in嵌入层,得到隐层;
- 再经过out嵌入层得到输出向量;
- 输入softmax层得到字典中每个词汇是目标词上下文的概率;
- 经过大量的数据训练,即可得到我们需要的嵌入矩阵in_embedding。
-m3e(moka massive mixed embedding)
-bge m3
5、垂直领域大模型如何应用?
由于大模型知识时效性受限以、大模型专业能力可能有限以及重新训练大模型成本高昂等方面的局限性,目前对于专业垂直领域有两种应用范式可以参考。对于垂直领域的大语言模型应用可以分为两种范式,一种是预训练模型+finetune,另外一种是预训练+rag。
-
pretrain预训练
大模型一般先会用海量的语料进行无监督预料预训练。这种预训练是以自监督的方式进行的,方式有以上文为input下文为target进行训练、随机掩码训练或者nsp下一个句子预测训练等方式。这种自监督的方式极大地扩展了文本的可利用性。 -
finetune微调
在下游垂直领域,利用标注好的少量特定任务预料制作成指令数据,对预训练大模型进行微调。微调后的模型在特定任务或领域具有更好的表现。
finetune方式需要对大模型进行微调训练,仍然有不少的训练成本,可以用lora等高效的微调方式。 -
rag检索增强生成
rag即retrieval-augmented generation是一种将知识检索和语言生成结合在一起的技术。简单来说就是基于用户的问题输入可以在知识数据库中检索到对应的参考文本,再将输入和检索结果一起放入大模型的指令prompt中,从而大模型可以更好的回答回用户问题。
rag这种方式不用训练所以成本低因此更轻量级,并且可以实时更新知识库,可解释性强;但是输入会占用输入上下文大小,所以占用检索的时间开销;系统复杂需要额外搜索逻辑和外部数据库。
这两种范式并不是互斥的,可以同时应用。
6、什么是rag?解决了什么问题?
参考学习链接: rag b站学习链接
rag即retrieval-augmented generation是一种将知识检索和语言生成结合在一起的技术。在详细介绍rag之前我们先了解一下大模型存在的问题。
大模型存在的问题
- 模型幻觉:大模型输出内容不完整、不准确甚至有误导性的内容;
- 时效性问题:大模型的重新训练需要较长的时间成本,因此大模型不可能学习到实时信息;
- 安全性问题:一些公司数据需要保密,因此不可能让大模型来学习他们的保密数据,以免敏感数据泄漏;
为了解决上述的问题检索增强生成技术rag应运而生。rag结合了结合了大模型的生成能力和检索的精确性。
rag流程示意图:
rag输出到大模型prompt模版:
rag 难点
- 问题理解难:用户提问往往是短文本、可能用词不规范,而知识库中是长文本。如何将它们建立有效的关联;
- 知识检索难:知识库的信息来源多样性,pdf、ppt、word等各种各样的格式;
文本检索与语义检索
- 文本检索是通过关键词或短语匹配文本数据的过程,使用tfidf、bm25等方法。特点是处理速度快,适用于大规模文本数据。
- 语义检索是通过nlp技术来进行检索,如词嵌入、预训练语言模型。能够处理一词多义、近义词等语义上的复杂情况。
文本检索和语义检索可以结合使用。例如先用文本检索筛选出候选文档,再用语义检索来进一步提高检索准确性。
6、文本检索的算法?
- tf-idf算法
tfidf主要思路是通过计算词项在文档中的频率(tf)和逆文档频率(idf)来衡量一个词对于一个文档集合的重要性。如果一个词项在某文档中出现的频率高,并且它在其他文档中很少出现,那么意味着这个词项具有很好的类别区分能力。
t f ( t , d ) = t 在 d 中出现次数 d 中词项总数 tf(t, d) = \frac{t在d中出现次数}{d中词项总数} tf(t,d)=d中词项总数t在d中出现次数 i d f ( t ) = l o g ( 文档总数 包含 t 的文档数 + 1 ) idf(t) =log( \frac{文档总数}{包含t的文档数+1}) idf(t)=log(包含t的文档数+1文档总数) t 表示某词项、 d 表示某文档 t表示某词项、d表示某文档 t表示某词项、d表示某文档
其中tf表征了某词项在某个文档中出现的相对频率;idf表征了一个词项在整个文档集合中的稀有程度,值越高越稀有。
t f i d f ( t , d , d ) = t f ( t , d ) ∗ i d f ( t ) tfidf(t, d, d) = tf(t, d) * idf(t) tfidf(t,d,d)=tf(t,d)∗idf(t) d 表示文档集合 d表示文档集合 d表示文档集合
算例:假设d由10个文档d = {d_0, …, d_9}组成,词项t=“深度学习” 只在d_2、d_3中分别出现了10次、20次,并且d_2、d_3分别有100个、500个词项,那么:
tfidf(“深度学习“, d_2, d) = 10/100 * log(10/3) = 0.120
tfidf(“深度学习“, d_3, d) = 20/500 * log(10/3) = 0.048
7、multi-head self-attention原理和代码实现
多头自注意力机制是transformer模型的核心模块。
multi-head attention: 多头注意力机制是一种扩展自注意力机制的方法,它将自注意力机制分解为多个“头”,每个“头”都在不同的表示空间中学习信息,从而能够捕捉到更丰富的特征和关系。具体的:
-
赋值: 经过位置编码的矩阵x分别赋值给v、k、q,因此目前这三个矩阵一模一样且都等于x;
-
线性层linear: 矩阵v、k、q分别输入独立的线性层,进行线性变换,即各自乘上一个参数矩阵。参数矩阵是并行独立训练出来的,所以变换后v、k、q现在不相同了,但他们维度还是一致的;
-
拆分: v、k、q根据头数h进行拆分,多头进行并行工作。在实际代码实现中就是:比如一个3*512的v矩阵,拆分成8头,即变成了8 * 3 * 64的矩阵。这样达到的效果是后续的矩阵运算本质上是每头单独进行运算,而不是一整块进行运算;
-
scaled dot-product attention: 经过拆分后的v、k、q输入“缩放点乘积注意力”模块,这个模块是多头注意力机制中的核心模块,基于k、q矩阵通过一番操作得到输入句子中每个词之间的注意力权重矩阵,然后对值矩阵(v)进行加权求和,从而使得每个词向量都融合上下文的信息,达到了语义增强的目的。该模块数学本质就是下面这个公式:
a t t e n t i o n ( q , k , v ) = s o f t m a x ( q k t d k ) v d k 为 q 与 k 的维度 attention(q,k,v) = softmax(\frac{qk^t}{\sqrt{d_k}})v\\d_k为q与k的维度 attention(q,k,v)=softmax(dkqkt)vdk为q与k的维度
- qk^t是两个矩阵之间的点积操作,得到n * n维度的点积矩阵,n为输入句子中的词数量。该矩阵意义是输入句子中每个词之间的相关性;
- */sqrt(d_k)缩小点积矩阵中元素的大小,避免后续softmax时梯度消失。类似于归一化、标准化的作用;
- 掩码操作mask(opt.): 包含padding mask和sequence mask两种。掩码就是让矩阵某些元素变为负无穷的数,使得其在后续softmax中的概率为0。其中padding mask旨在消除输入序列中padding的影响;而sequence mask只存在于解码器中,目的是在预测下一个词时,覆盖住后面的词汇注意力信息,达到只用前面序列来预测下一词的目的。
- softmax是一种基于自然指数函数的概率计算方法,可将向量转换成概率分布。这一步输出的概率分布矩阵就是其实就是注意力权重,反应了句子每个词之间之间的联系大小;
- 最后概率分布矩阵再和v矩阵相乘,得到注意力模块下的输出; -
拼接concat: 将多头输出的多个小矩阵拼接成一个大矩阵,这个大矩阵的尺寸和拆分之前的矩阵尺寸一致。代码实际操作时就是:例如8 * 3 * 64的8头矩阵转换为3*512的矩阵;
-
线性层linear: 再经过一个线性层,这层目的是将拼接后的矩阵信息更好的融合。输出和输入尺寸一致;
h
为头数,默认
8
v
值矩阵、
k
键矩阵、
q
查询矩阵
h为头数,默认8\\v值矩阵、k键矩阵、q查询矩阵
h为头数,默认8v值矩阵、k键矩阵、q查询矩阵
代码实现
import sys
import torch
import torch.nn as nn
import math
class multiheadselfattention(nn.module):
def __init__(self, model_size, num_heads=8):
"""
:param model_size: 模型维度==词向量维度
:param num_heads: 头数
"""
super().__init__()
if model_size % num_heads != 0:
print("model_size必须能被num_heads整除")
sys.exit(-1)
self.model_size = model_size
self.num_heads = num_heads
self.linear_q = nn.linear(model_size, model_size, bias=false)
self.linear_k = nn.linear(model_size, model_size, bias=false)
self.linear_v = nn.linear(model_size, model_size, bias=false)
self.combine = nn.linear(model_size, model_size)
def forward(self, x):
batch, seq_len, input_size = x.shape
nh = self.num_heads
# 单头的k、q、v尺寸
new_size = self.model_size // nh
q, k, v = self.linear_q(x), self.linear_k(x), self.linear_v(x) # x输入三个独立的线性层
# 对输出的q、k、v进行重构,本来是batch * seq_len * model_size --->> 变成了batch * nh * seq_len * new_size
q = q.reshape(batch, seq_len, nh, new_size).transpose(1, 2)
k = k.reshape(batch, seq_len, nh, new_size).transpose(1, 2)
v = v.reshape(batch, seq_len, nh, new_size).transpose(1, 2)
score = torch.matmul(q, k.transpose(2, 3)) / math.sqrt(new_size) # 理解为词之间的相关性,shape: batch * nh * seq_len * seq_len
mask = torch.tril(torch.ones(seq_len, seq_len))
score = score.masked_fill(mask == 0, float("-inf")) # mask中0的地方对应score中赋值为负无穷
score = torch.softmax(score, dim=-1) # 对最后一维度进行softmax, shape: batch * nh * seq_len * seq_len
attention = torch.matmul(score, v) # 注意力系数和value信息相乘 shape: batch * nh * seq_len * new_size
attention = attention.transpose(1, 2).reshape(batch, seq_len, self.model_size) # 回到输入的尺寸shape: batch * seq_len * model_size
output = self.combine(attention) # 再输入一个线性层
return output
multi-head attention的相关问题:
-
为什么用多头?
1、有利于并行加速(但是相比于不分多头参数量没有变);
2、model_size维度分成多头,在不同空间中学习,效果更好(但是头数也不是越多越好); -
为什么scaling?
避免q*k^t值过大,输入softmax后导致“饱和”,梯度消失难以训练; -
muti-query attention(mqa)的新型注意力机制
19年google提出,目的是减少参数量,加速推理,同时也减少了缓存。原理是k、v在线性变换时直接降维成单头的维度大小(model_size / heads_num),q则和mha中一样处理;计算attentionscore的时候每头共享v和k,其它操作不变。但是这种方式会降低一些模型效果。 -
grouped-query attention(gqa)的新型注意力机制
其实就是介于mha与mqa之间的一种做法。
8、什么是padding?
在自然语言处理(nlp)和其他序列数据处理任务中,padding是一种技术,用于将不同长度的序列转换为具有相同长度的序列。这通常是为了确保所有序列都可以被均匀地输入到模型中,从而允许模型以相同的方式处理每个序列。
在nlp中,序列通常由单词、字符或其他元素组成。不同的句子或文本片段可能包含不同数量的单词或字符,这意味着它们的序列长度也不同。为了处理这些不同长度的序列,我们需要对较短的序列进行扩展,使其长度与最长的序列相同。这个过程就是padding。
在padding过程中,通常会在序列的末尾添加特定的标记(padding token),如 ,这个标记在模型中不会被计算或考虑。然后,所有序列都被扩展到相同的长度。例如,如果一个句子包含3个单词,而另一个句子包含5个单词,我们可以将第一个句子扩展到5个单词,每个单词后面都添加一个 标记。
padding的重要性在于,它允许模型对所有输入序列进行统一处理,而不需要为每个序列调整模型结构。这使得模型能够更高效地处理不同长度的序列数据,并且有助于提高模型的泛化能力。
9、什么是dropout?
dropout是一种防止模型过拟合的方法,能增加模型的泛化能力。
具体的:
1、每次前向传播都随机的按照一定比例使一些神经单元失效;
2、输入和输出单元保持不变,它们不进行dropout;
3、向后传播时不对失效单元涉及的参数进行更新;
4、新的前向传播,重新随机选择失效的单元;
pytorch中dropout参数范围是[0, 1)
10、目前知名的大模型有哪些?
名称 | 研发机构 | 介绍 |
---|---|---|
bert | 谷歌 | 一种基于仅编码器式transformer-encoder架构的预训练语言模型,由google ai于2018年提出。 |
gemini | 谷歌deepmind | 多模态;基于transformer架构 |
llama | meta | 2024年发布开源的llama 3,参数最高达到70b,基于transformer-decoder架构 |
gpt | openai | 基于transformer-decoder架构;23年发布了多模态大模型gpt-4 |
通义千问 qwen | 阿里云 | 基于transformer架构 |
glm | 智谱ai | |
文心大模型ernie | 百度 |
11、语义检索流程?
语义检索是通过词和句子嵌入等技术将文本表示为语义丰富的向量。通过相似度计算和结果排序找到最相关的文档。语义检索通过深度学习和自然语言处理技术,使得系统能更准确地理解用户查询,提高检索的准确性和效果。
-
文本读取
知识库中的文本数据可能有各种格式的数据比如pdf、word、excel、ppt、csv、txt等,读取这些文件中的文本内容是语义检索的第一步。
读取文件中数据参考学习链接: link -
文本切分
将文本切分为多个chunk,这样在后续进行检索时会更加精确。
文本的长度会影响文本编码的结果。短文本和长文本在编码成向量时可能会表达不同的语义信息。即使两者包含相同的单词或有相似的语义,由于上下文的不同,得到的向量也会有所不同。因此在用短文本来检索长文本时或者用长文本检索短文本时,可能导致一定的误差。针对这种问题,有些检索系统采用了截断或填充的方式处理。- 递归式分割
- html、markdown、代码分割;
- token分割,存在不同的token计量方法。结合chunksize+overlap得到不同切分;
- 字符分割,基于用户定义的字符进行分割;
- 语义分块器;
-
文本编码(文本嵌入embedding)
文本编码对于语义检索的精度至关重要,目前大多数语义检索系统采用预训练模型进行文本编码,例如bert(bidirectional encoder representations from transformers)、gpt(generative pre-trained transformer)等。
我们不仅需要对知识库中的文本进行embedding,得到向量数据库,还要对用户的提问进行embedding得到问题向量。下面是一个embedding代码实例:
from sentence_transformers import sentencetransformer
from read_data import data_read
import json
"""
利用基于bert的embedding模型m3e-small进行语义检索
任务是在pdf资料中找到问题最相关的页码
"""
pdf_content, questions = data_read() # 加载数据,加载了pdf数据和针对pdf的问题
model = sentencetransformer('m3e-small', device='mps') # 加载模型,m3e本质上是个bert模型
questions_sentences = [x['question'] for x in questions] # 301个问题list
pdf_content_sentences = [x['content'] for x in pdf_content] # 35页pdf资料,一页就是一个元素
question_embeddings = model.encode(questions_sentences, normalize_embeddings=true, device='mps') # 嵌入向量 301 * 512
pdf_embeddings = model.encode(pdf_content_sentences, normalize_embeddings=true, device='mps') # 35 * 512
#通过计算每个问题和pdf每一页的相似性,来找到每个问题最相关的页码
for query_idx, feat in enumerate(question_embeddings):
score = feat @ pdf_embeddings.t # 计算相似度, feat 512的向量 score 35 维的向量
max_score_page_idx = score.argsort()[-1] + 1 # argsort() 从小到大返回索引值
questions[query_idx]['reference'] = 'page_' + str(max_score_page_idx)
sentence-transformer是一个在文本检索领域做模型加载、训练、预测的常用模型库。也可以用pytorch或者huggingface的transformer来进行模型加载,代码量可能会比较大。
- 相似度计算
我们拿到向量数据库和问题输入向量后,就可以用向量计算来得到相似度score。取最大的score对应的文本就是检索到的最相关的文本。
12、layer normalization和batch normalization的区别
transformer中数据尺寸为batch_size * max_len * embeddingsize,其中batch_size是批处理的大小、max_len是指模型输入最大的长度、embeddingsize是嵌入尺寸。
layer normalization是分别对每个句子(max_len * embeddingsize)进行归一化。
batch normalization是分别对每一个位置词的一个批次内(batch_size * embeddingsize)进行归一化。
13、如何提高rag召回率?
召回率(recall)是信息检索中常用的一个性能指标,它衡量的是检索系统能够返回相关文档的能力。具体来说,召回率是检索到的相关文档数量与所有相关文档总数的比例。
提升方法:
- 文本切分
对文本进行精细化切分,可以提升检索准确度。从而提升召回率。 - 多路召回
多路召回即采用多种文本检索、语义检索的模型或方法,再将它们的排序结果进行融合。例如:- 用户输入问题q;
- 基于tf-idf指标的文本检索,得到q对应的最相关的top k的文本索引a;
- 基于bert相关模型语义检索得到,得到q对应的最相关的top k的文本索引b;
- 按照排名先后对a、b中的索引进行打分,排名越前越高;
- 将a、b中相同索引的得分进行相加,再进行降序排列;
- 得到新的最高分数的文本索引为q的最终检索内容;
- 基于预训练模型的重排序
可以基于预训练模型(如cross-encoder交叉编码器模型)对检索出来的k个结果与输入问题q进行相关性打分,从而对k个结果重新进行排序。 - 其他:增加文档集大小、优化检索算法等等
14、transformer为什么需要位置编码?为什么用正、余弦进行位置编码?
学习链接:link
为什么需要position encoding:
- 以前的rnn都是一个token一个token的循环输入,本身就是带有位置信息的输入方式。而transformer是一整个文本输入,模型需要加入位置编码信息来帮助模型更好的理解顺序;或者说是词项之间的顺序对于语言理解来说太重要了,一样的词不一样的顺序意思可能刚好相反,因此需要位置拜尼马来加强模型对位置的理解;
为什么用正、余弦进行位置编码:
- cos、sin 有界[-1, 1];
- pos位置编码和pos+k位置编码线性相关>>>>>意味着两个位置pos1、pos2隔了k距离,那它们的位置编码之间有固定的线性关系(不与pos有关只和k有关);
- 定性的看形式和二进制编码一致;
如何选择嵌入模型?
- 在hugging face网站里看mteb排行榜(massive text embedding benchmark leaderboard),理论上排名越高效果越好;
- m3e(moka massive mixed embedding)是一个比较常用的文本嵌入模型;
- 看具体的语言能力的支持、同质、异质文本嵌入能力、代码检索能力等等;
llm如何处理长文本输入?
transformer中q、k、v是什么?
残差连接是什么?有什么作用?
下游数据和上游数据逐个元素相加,再进行层归一化。
- 防止梯度消失;
- 帮助深层网络的训练;
损失函数对比
- 均方差mse(mean square error)
通过计算预测值和真实值之差的平方的平均值来衡量误差。适用于回归任务,如预测流量、价格等。
- 交叉熵损失(cross-entropy loss)
适用于分类任务。
p 为真值、 q 为预测值 p为真值、q为预测值 p为真值、q为预测值
比较交叉熵和mse。因为在损失函数计算时每个样例是独立的,我们不妨从单个样例的角度来看mse和交叉熵的区别:
-
如果真实标签是(1, 0, 0),模型1的预测标签是(0.8, 0.2, 0),模型2的是(0.8, 0.1, 0.1),那么mse-based,就是模型2更好;交叉熵-based认为一样.从最终预测的类别上看,模型1和模型2的真实输出其实是一样的.
-
再换个角度,mse对残差大的样例惩罚更大些.比如真实标签分别是(1, 0, 0).模型1的预测标签是(0.8, 0.2, 0),模型2的是(0.9, 0.1, 0).即使输出的标签都是类别0, 但mse-based算出来模型1的误差是模型2的4倍,而交叉熵-based算出来模型1的误差是模型2的2倍左右.为了弥补模型1在这个样例上的损失,mse-based需要3个完美预测的样例才能达到和模型2一样的损失,而交叉熵-based只需要一个.实际上,模型输出正确的类别,0.8可能已经是个不错的概率了;
-
交叉熵只关注真实标签对应的维度,能够很好的处理分类任务中的稀疏性;
如何评估rag系统的效果
- 无监督方法
无监督是指利用llm大模型等工具对rag系统进行打分,不介入人工标注。
1、利用大模型对用户问题、索引文档、系统回答三者之间进行语义相关性打分。这种方法人工成本低,可以实现自动化评估,但是评估精度受语义模型效果限制;
2、让llm根据知识库文档生成query、ground-truth,然后利用它们对模型进行评估; - 有监督方法
1、人工提取query、answer、reference(页码);rag系统输出回答和页码索引,基于真值可以计算rag的回答精度。具体的文本可以进行相似度计算打分,页码可以计算准确率;
peft 中微调的区别
- lora:基于增量参数矩阵为稀疏矩阵的假设,将增量矩阵w转换为a、b两个低秩矩阵相乘,反向传播时只对a、b进行调整。是一种重参数化的高效微调方式;其中超参数rank在垂直领域的微调中可以设置大一些,通用场景训练中可以设置小一些;
- prefix-tuning:输入tokens前加入连续的与任务相关的prefix。训练过程中只对特定任务的prefix涉及的linear参数进行修改。这是一种增加式的微调方式;
模型压缩与加速方法
- 剪枝:去除权重小的参数;
- 低秩分解:用小矩阵代替大矩阵;
- 知识蒸馏:用小模型替代大模型;
- 量化(大模型领域最适合的方式):浮点数转化成占用内存更小的整型;反量化则是整型恢复到浮点数;这种方式会造成一定的精度损失;
- ptdq:直接对训练好的模型权重参数进行量化。简单、运算量小;
- ptsq:对参数和激活函数后的值进行量化;
- qat:在训练过程中进行量化;
rlhf
大模型梯度攻击
对比一下kg + llm 与 rag的优劣
向量之间计算相似度的方法,它们有什么区别?
- 欧几里得距离,也叫l2范数
是很常见的计算距离的方式,适用于数据类型为连续的情况。距离越大,表示相似度越小。取值范围为[0, + ∞ +\infty +∞)。 - inner product 内积,也叫点积
反应了两个向量之间大小和方向上的相似度,常用于非标准化向量之间的相似度计算。如果应用于( − ∞ -\infty −∞, + ∞ +\infty +∞)。 - 余弦相似度cosine similarity
余弦相似度表征了两个向量之间夹角大小,取值范围为[-1, 1]。0代表正交,1代表方向一致,-1代表反向。 - jaccard similarity
适用于元素都是0或1的向量之间相似度计算,取值范围为[0, 1]。值越大表示相似度越大。 - hamming distance
适用于元素都是0或1的向量之间的计算。hamming distance衡量了两个向量之间元素不相同的个数之和。例如[1 0 0 0 1]和[1 1 1 0 1]之间的hamming distance为2。
发表评论