赞
踩
本文参考网上资料,并结合个人理解所撰写。如有错误,欢迎指出,十分感谢!持续更新中…
NLP(Natural Language Processing,自然语言处理)是人工智能(AI)和计算机科学的一个分支,专注于使计算机能够理解和生成人类语言。
常见的NLP任务有:文本分类(例如垃圾邮件检测)、情感分析(例如微博等社交媒体分析公众对某话题的情感倾向)、机器翻译(谷歌翻译、百度翻译等)、文本摘要、文本生成、问答对话系统(银行自动客服系统)、语音识别(苹果的Siri)等等
NLP自然语言处理领域常见的模型有循环神经网络模型RNN、Transformer模型,其中循环神经网络模型中有长短期神经网络LSTM以及门控循环单元GRU两种典型变体。下面对这四种模型结构和基本原理进行介绍。
循环神经网络(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。
长短期神经网络是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。
门控循环单元是在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模型是由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(dk
QKT)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学习链接:link
BERT(Bidirectional Encoder Representations from Transformers)是一种基于Transformer Encoder的预训练语言处理模型,由Google AI在2018年提出。
[
C
L
S
]
输入句子的起始符号、
[
S
E
P
]
分隔符号(分隔句子、放置句尾部、分隔问题与回答)
[CLS] 输入句子的起始符号、[SEP]分隔符号(分隔句子、放置句尾部、分隔问题与回答)
[CLS]输入句子的起始符号、[SEP]分隔符号(分隔句子、放置句尾部、分隔问题与回答)
下面对BERT模型进行介绍:
三个子模块的嵌入层
得到三个子嵌入矩阵后相加即可得到融合了词义、句类、位置信息的词向量作为后续输入。
训练中两个阶段:
以翻译任务为例,待翻译句子为“我爱你”。
训练过程分为两大步骤,前向传播和反向传播。
前向传播
在NLP领域中Embedding即词嵌入技术,是一种将文本中的单词转换为固定长度的向量(词向量)技术。现有的模型考虑任务需求、计算资源、模型性能等因素词向量维度设置为100-1000不等。
1、机器学习需要数字化表达,因此文本数字化表达为向量才能被模型学习和训练;
2、维度灾难,早期NLP模型所用的独热编码(One-hot )的方式会导致特征向量极度稀疏;
假设一个词汇表有10000个词,那么按照位置进行独热编码,每个词就是一个10000维度的向量,那么整个词汇表就是10000x10000的矩阵,更何况现在词汇表一般都是几十万个词。稀疏矩阵会导致内存的浪费、计算效率的降低。
3、词汇鸿沟,独热编码不能表达词汇之间的联系;
独热编码下所有的单词之间的距离都相同,这显然是不合理的,因为实际中某些词汇的关系更密切。
Embedding的方法有很多,其中基于内容的word2vec方法是比较经典的方法。
word2vec方法包括两种架构:CBOW连续词袋模型(Continuous Bag of Words)和Skip-gram跳字模型。
两种模型的目标都是经过训练得到词嵌入矩阵E。利用词汇的独热编码向量O与E相乘,即可得到稠密的低维度的词向量。
-CBOW
预设好窗口大小,利用窗口内的上下文来预测目标词。
-Skip-gram
跳字模型为CBOW的逆过程。
-M3E(Moka Massive Mixed Embedding)
-BGE M3
由于大模型知识时效性受限以、大模型专业能力可能有限以及重新训练大模型成本高昂等方面的局限性,目前对于专业垂直领域有两种应用范式可以参考。对于垂直领域的大语言模型应用可以分为两种范式,一种是预训练模型+Finetune,另外一种是预训练+RAG。
Pretrain预训练
大模型一般先会用海量的语料进行无监督预料预训练。这种预训练是以自监督的方式进行的,方式有以上文为input下文为target进行训练、随机掩码训练或者NSP下一个句子预测训练等方式。这种自监督的方式极大地扩展了文本的可利用性。
Finetune微调
在下游垂直领域,利用标注好的少量特定任务预料制作成指令数据,对预训练大模型进行微调。微调后的模型在特定任务或领域具有更好的表现。
Finetune方式需要对大模型进行微调训练,仍然有不少的训练成本,可以用LoRA等高效的微调方式。
RAG检索增强生成
RAG即Retrieval-Augmented Generation是一种将知识检索和语言生成结合在一起的技术。简单来说就是基于用户的问题输入可以在知识数据库中检索到对应的参考文本,再将输入和检索结果一起放入大模型的指令Prompt中,从而大模型可以更好的回答回用户问题。
RAG这种方式不用训练所以成本低因此更轻量级,并且可以实时更新知识库,可解释性强;但是输入会占用输入上下文大小,所以占用检索的时间开销;系统复杂需要额外搜索逻辑和外部数据库。
这两种范式并不是互斥的,可以同时应用。
参考学习链接: RAG B站学习链接
RAG即Retrieval-Augmented Generation是一种将知识检索和语言生成结合在一起的技术。在详细介绍RAG之前我们先了解一下大模型存在的问题。
大模型存在的问题
为了解决上述的问题检索增强生成技术RAG应运而生。RAG结合了结合了大模型的生成能力和检索的精确性。
RAG流程示意图:
RAG输出到大模型Prompt模版:
RAG 难点
文本检索与语义检索
文本检索和语义检索可以结合使用。例如先用文本检索筛选出候选文档,再用语义检索来进一步提高检索准确性。
多头自注意力机制是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(dk
QKT)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之间的一种做法。
在自然语言处理(NLP)和其他序列数据处理任务中,padding是一种技术,用于将不同长度的序列转换为具有相同长度的序列。这通常是为了确保所有序列都可以被均匀地输入到模型中,从而允许模型以相同的方式处理每个序列。
在NLP中,序列通常由单词、字符或其他元素组成。不同的句子或文本片段可能包含不同数量的单词或字符,这意味着它们的序列长度也不同。为了处理这些不同长度的序列,我们需要对较短的序列进行扩展,使其长度与最长的序列相同。这个过程就是padding。
在padding过程中,通常会在序列的末尾添加特定的标记(padding token),如 ,这个标记在模型中不会被计算或考虑。然后,所有序列都被扩展到相同的长度。例如,如果一个句子包含3个单词,而另一个句子包含5个单词,我们可以将第一个句子扩展到5个单词,每个单词后面都添加一个 标记。
padding的重要性在于,它允许模型对所有输入序列进行统一处理,而不需要为每个序列调整模型结构。这使得模型能够更高效地处理不同长度的序列数据,并且有助于提高模型的泛化能力。
Dropout是一种防止模型过拟合的方法,能增加模型的泛化能力。
具体的:
1、每次前向传播都随机的按照一定比例使一些神经单元失效;
2、输入和输出单元保持不变,它们不进行dropout;
3、向后传播时不对失效单元涉及的参数进行更新;
4、新的前向传播,重新随机选择失效的单元;
Pytorch中dropout参数范围是[0, 1)
名称 | 研发机构 | 介绍 |
---|---|---|
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 | 百度 |
语义检索是通过词和句子嵌入等技术将文本表示为语义丰富的向量。通过相似度计算和结果排序找到最相关的文档。语义检索通过深度学习和自然语言处理技术,使得系统能更准确地理解用户查询,提高检索的准确性和效果。
文本读取
知识库中的文本数据可能有各种格式的数据比如pdf、word、excel、ppt、csv、txt等,读取这些文件中的文本内容是语义检索的第一步。
读取文件中数据参考学习链接: link
文本切分
将文本切分为多个Chunk,这样在后续进行检索时会更加精确。
文本的长度会影响文本编码的结果。短文本和长文本在编码成向量时可能会表达不同的语义信息。即使两者包含相同的单词或有相似的语义,由于上下文的不同,得到的向量也会有所不同。因此在用短文本来检索长文本时或者用长文本检索短文本时,可能导致一定的误差。针对这种问题,有些检索系统采用了截断或填充的方式处理。
文本编码(文本嵌入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来进行模型加载,代码量可能会比较大。
Transformer中数据尺寸为Batch_size * Max_len * Embeddingsize,其中Batch_size是批处理的大小、Max_len是指模型输入最大的长度、Embeddingsize是嵌入尺寸。
layer normalization是分别对每个句子(Max_len * Embeddingsize)进行归一化。
batch normalization是分别对每一个位置词的一个批次内(Batch_size * Embeddingsize)进行归一化。
召回率(Recall)是信息检索中常用的一个性能指标,它衡量的是检索系统能够返回相关文档的能力。具体来说,召回率是检索到的相关文档数量与所有相关文档总数的比例。
提升方法:
学习链接:link
为什么需要Position Encoding:
为什么用正、余弦进行位置编码:
下游数据和上游数据逐个元素相加,再进行层归一化。
比较交叉熵和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可能已经是个不错的概率了;
交叉熵只关注真实标签对应的维度,能够很好的处理分类任务中的稀疏性;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。