写在前面:
LLM 擅长于一般的语言理解与推理,而不是某个具体的知识点。如何为ChatGPT/LLM大语言模型添加额外知识?
1、通过fine-tuning来和新知识及私有数据进行对话,OpenAI 模型微调的过程,并不复杂。你只需要把数据提供给 OpenAI 就好了,对应的整个微调的过程是在云端的“黑盒子”里进行的。需要提供的数据格式是一个文本文件,每一行都是一个 Prompt,以及对应这个 Prompt 的 Completion 接口会生成的内容。
有了准备好的数据,我们只要再通过 subprocess 调用 OpenAI 的命令行工具,来提交微调的指令就可以了。
微调模型还有一个能力,不断收集新的数据,不断在前一个微调模型的基础之上继续微调我们的模型。
2、通过word embeddings + pinecone数据库来搭建自己私有知识库。 chatgpt预训练完成后,会生成一个embeddings向量字典,比如我们可以将我们的私有知识库各个章节通过openai的相关api获取到对应的embeddings,然后将这些embeddings保存到向量数据库(比如 Facebook 开源的 Faiss库、Pinecone 和 Weaviate),当用户要对某个领域后者问题进行语义查询时,则将用户的输入同样通过openai的相关api来获取相应的embeddings向量,然后再和向量数据库pinecone中的我们的私有知识库类型做语义相似度查询,然后返回给用户。PS: 内容向量化
比如判断某一段文本 是积极还是消极,向chatgpt 查询目标文本的向量,然后计算其与“积极” “消极” 两个词 embedding 向量的“距离”,谁更近,说明这段文本更偏向于积极或消极。
过几天openAI的模型版本升级了,这些保存的embedding会失效吗?特定模型也有带日期的快照版本,选取那些快照版本就好了。
向量是基于大模型生成的,因此对两段文本向量相似度计算必须基于同一个模型,不同的模型算出来的向量之间是没有任何关系的,甚至连维数都不一样。不过你可以把基于A 模型来算向量相似度进行检索把文本找出来,然后把找到的文本喂给B模型来回答问题。
3、通过langchain这个chatgpt编程框架来给chatgpt赋能。 langchain可以将不同的工具模块和chatgpt给链接(chain)起来。
4、chatgpt 插件,比如有一个提供酒旅租车信息的插件:
本文针对2 作讲解:
一、RAG定义:
检索增强生成技术(Retrieval-Augmented Generation,RAG是一种使用私有或者专有数据源的信息来辅助文本生成的技术,它将检索模型(设计用于搜索大型数据集或知识库)和生成模型(例如大语言模型LLM)结合在一起。通过从更多数据源添加背景信息,以及通过训练来补充 LLM 的原始知识库,检索增强生成能够提高搜索体验的相关性。这能够改善大型语言模型的输出,但又无需重新训练模型。额外信息源的范围很广,从训练 LLM 时并未用到的互联网上的新信息,到专有商业背景信息,或者属于企业的机密内部文档,都会包含在内。
RAG 对于诸如回答问题和内容生成等任务,具有极大价值,因为它能支持生成式AI系统使用外部信息源生成更准确且更符合语境的回答。它会实施搜索检索方法(语义搜索或者混合检索)来回应用户的意图并提供更相关的结果。
二、RAG运作的原理
1、embedding
Embedding 的过程,就是把数据集合映射到向量空间,进而把数据进行向量化的过程。Embedding 的目标,就是找到一组合适的向量,来刻画现有的数据集合。
计算的基础是数,而自然语言是文字,因此很容易想到要做的第一步是让文字数字化,为行文方便,我们将这个过程叫做编码。要设计编码的方法,自然需要思考的问题是:哪些性质是编码规则必须要满足的?
1)每一个词具有唯一量化值,不同词需要具有不同的量化值
2)词义相近词需要有”相近”的量化值;词义不相近的词量化值需要尽量“远离”。OpenAI 的 Jack Rae 在 Standford 的分享 中提到了一个很深刻的理解语言模型的视角:语言模型就是一个压缩器。所有的压缩,大抵都能被概括在以下框架内:提取共性,保留个性,过滤噪声。
3)词义的多维性。对于每一个词,我们可以表达为一组数,而非一个数;这样一来,就可以在不同的维度上定义远近,词与词之间复杂的关系便能在这一高维的空间中得到表达。
图像可以有embedding,句子和段落也可以有 embedding —— 本质都是通过一组数来表达意义。段落的 embedding 可以作为基于语义搜索的高效索引,AI 绘画技术的背后,有着这两种 embedding 的互动 —— 未来如果有一个大一统的多模态模型,embedding 必然是其中的基石和桥梁 。
几种典型的embedding:
1、one hot
比如用one-hot编码来表示4个梁山好汉。
Embedding层把我们的稀疏矩阵,通过一些线性变换(比如用全连接层进行转换,也称为查表操作),变成了一个密集矩阵,这个密集矩阵用了N(例子中N=4)个特征来表征所有的好汉。在这个密集矩阵中,表象上代表着密集矩阵跟单个好汉的一一对应关系,实际上还蕴含了大量的好汉与好汉之间的内在关系(如:我们得出的李逵跟刘唐的关系)。它们之间的关系,用嵌入层学习来的参数进行表征。这个从稀疏矩阵到密集矩阵的过程,叫做embedding,很多人也把它叫做查表,因为它们之间也是一个一一映射的关系。这种映射关系在反向传播的过程中一直在更新。因此能在多次epoch后,使得这个关系变成相对成熟,即:正确的表达整个语义以及各个语句之间的关系。这个成熟的关系,就是embedding层的所有权重参数。Embedding最大的劣势是无法解释每个维度的含义,这也是复杂机器学习模型的通病。
Embedding除了把独立向量联系起来之外,还有两个作用:降维,升维。
embedding层 降维的原理就是矩阵乘法。比如一个 1 x 4 的矩阵,乘以一个 4 x 3 的矩阵,得倒一个 1 x 3 的矩阵。4 x 3 的矩阵缩小了 1 / 4。假如我们有一个100W X 10W的矩阵,用它乘上一个10W X 20的矩阵,我们可以把它降到100W X 20,瞬间量级降了。
升维可以理解为:前面有一幅图画,你离远了看不清楚,离近了看就可以看清楚细节。当对低维的数据进行升维时,可能把一些其他特征给放大了,或者把笼统的特征给分开了。同时这个embedding是一直在学习在优化的,就使得整个拉近拉远的过程慢慢形成一个良好的观察点。
如何生成? 矩阵分解; 无监督建模; 有监督建模
2、tensorflow的实现
一般在tensorflow中都会使用一个shape=[id_index_size, embedding_size]的Variable 矩阵做embedding参数,然后根据id特征的index去Variable矩阵中查表得到相应的embedding表示。这里需要注意的是:id_index_size的大小一般都不会等于对应id table的元素个数,因为有很多id元素不在原始的id table表中,比如新上架的一些商品等。此时需要将id_index_size设置的大一些,以留一些位置给那些不在id table表的元素使用。
使用tf.Variable 作为 embedding参数:
使用get_embedding_variable接口:
使用categorical_column_with_embedding接口:
https://mp.weixin.qq.com/s/v0K_9Y6aWAyHj7N1bIGvBw
input_embedding = embedding * input_ids 从效果上 可以把 input_ids 视为索引的作用,返回第4、0、2 行数据,但 embedding_lookup 函数 也可以看做是一个 矩阵乘法(底层两种都支持,是一个策略参数),也因此 embedding层可以通过 optimizer 进行更新。
原生的tf optimizer 根据 梯度/grad 的类型 来决定更新weight/ variable 的方法,当传来的梯度是普通tensor时,调用_apply_dense方法去更新参数;当传来的梯度是IndexedSlices类型时,则去调用optimizer._apply_sparse_duplicate_indices函数。 Embedding 参数的梯度中包含每个 tensor 中发生变化的数据切片 IndexedSlices。IndexedSlices类型是一种可以存储稀疏矩阵的数据结构,只需要存储对应的行号和相应的值即可。可以认为是一种类似 SparseTensor 的思想,用元素数据和元素位置表示一个较大 tensor 。将 tensor 按第一维度切片,从而将一个较大的形状为 [LARGE0, D1, … , DN] 的 tensor 表示为多个较小的形状为 [D1, … , DN] 的 tensor。
总结一下涉及到哪些问题: 稀疏参数的表示(开始由Variable 表示 ,各种框架提供EmbeddingVariable 表示)、存储(ps,底层是分布式hashmap)、通信(只通信部分,数据存在gpu + gpu 直接通信)、优化(稀疏参数的优化器与稠密参数的优化器不兼容) 和 稀疏参数的梯度的表示、通信(由IndexedSlices 表示)、优化。
代码阅读:
定义基类便于扩展:
调用OpenAI 的embedding 方法:
cosine_similarity方法,这个方法是用来计算两个向量之间的余弦相似度的。其次在初始化类的时候设置了,模型的路径和是否是API模型。比如使用OpenAI的Embedding API的话就需要设置self.is_api=Ture。
2、切分
对文档进行切分,将切分后的片段转化为embedding向量,构建向量索引。这里有个技巧,片段与片段之间最好要有一些重叠的内容,这样才能保证检索的时候能够检索到相关的文档片段。还有就是切分文档的时候最好以句子为单位,也就是按 \n 进行粗切分,这样可以基本保证句子内容是完整的。我们可以设置一个最大的 Token 长度max_token_len,然后根据这个最大的 Token 长度来切分文档。这样切分出来的文档片段就是一个一个的差不多相同长度的文档片段了:
3、向量存储
在目前市场中,向量数据库被认为是最适合大模型外挂式存储的工具,两者是“最佳拍档”。如果说向量是中间过程,那么数据库才是归宿,有了存储、调用数据的地方才能用起来。在大型语言模型方面,向量数据库能够存储和检索海量的文本向量,通过计算向量之间的相似度,实现更加智能的文本匹配和语义搜索。对于生成型AI,向量数据库可以高效地存储生成的向量结果,快速地进行生成结果的查询和匹配,提供更加精准和多样化的生成体验。在语义搜索领域,向量数据库能够将语义信息转化为向量表示,支持更加准确和语义感知的搜索和推荐。
向量数据库的工作原理是基于向量空间理论,将数据存储在三维向量空间中,并通过向量加法、向量减法、向量乘法等操作进行数据存储和查询。每个向量代表一个实体的属性集合,可以是文本、图像、音频或其他形式的数据。向量数据库以向量维度为基准进行数据存储和检索,采用高效的向量空间索引和相似性计算算法,从而提供快速的数据查询和分析能力。
具体来说,向量数据库采用列式存储方式,将数据按照列进行存储,每个列都代表一个向量。向量数据库支持向量加法和向量减法操作,可以将两个向量相加或减去得到新的向量。向量数据库还支持向量乘法操作,可以将一个向量乘以另一个向量得到新的向量。
下次在此补充向量数据库的原理。
一个最小的RAG架构应该包含以下内容:
persist:数据库持久化,本地保存
load_vector:从本地加载数据库
get_vector:获得文档的向量表示
query:根据问题检索相关的文档片段
query 方法具体是怎么实现的呢?
首先先把用户提出的问题向量化,然后去数据库中检索相关的文档片段,最后返回检索到的文档片段。可以看到咱们在向量检索的时候仅使用 Numpy 进行加速,代码非常容易理解和修改。
这里并没有使用成熟的数据库(以上提到的一些成熟的向量数据库如FAISS),这样可以更好的理解RAG的原理。
4、结合生成式AI
在生成阶段,RAG使用生成器基于检索到的信息来生成响应。生成器通常是一个预训练的语言模型,例如GPT模型。生成器接受两个输入:查询和检索到的信息。
查询用于指导生成的主题或意图,而检索到的信息则用于提供上下文和相关性。
生成器根据这些输入生成响应,并通过迭代生成过程逐步完善结果。
在生成过程中,RAG还可以通过引入额外的约束或指导信息来增强可控性。例如,可以使用特定的关键词或条件来约束生成结果的内容或风格。这种方式使得用户能够更好地控制生成的结果,满足特定的需求。
先实现一个基类:
BaseModel 包含了两个方法,chat和load_model,如果使用API模型,比如OpenAI的话,那就不需要load_model方法,如果你要本地化运行的话,那还是会选择使用开源模型,那就需要load_model方法:
这里咱们以 InternLM2-chat-7B 模型为例:
可以用一个字典来保存所有的prompt:
5、一个demo
我们也可以从本地加载已经处理好的数据库:
三、代码可以优化的方向
1、embedding
tensorflow原生的embedding layer 存在以下问题:
1)静态 Embedding OOV 问题。在构建 Embedding Layer 的时候,TensorFlow 需要首先构建一个静态 shape[Vocab_size, Embedding size ] 的 Variable,然后利用 Lookup 的算子将特征值的 Embedding 向量查询出。在增量或者流式训练中,会出现 OOV 的问题。
2)静态 Embedding hash 特征冲突。为了规避上述的 OOV 问题,通常做法是将特征值 hash 到一定的范围,但是又会引入 hash 冲突的问题,导致不同的特征值共用同一个 Embedding,会造成信息丢失,对模型训练是有损的。
3)静态 Embedding 内存浪费。为了缓解 hash 冲突,通常会设置比真实的特征值个数 N 大一到两倍的 hash 范围,而这又会强行地增加模型的体积。
低频特征冗余。在引入稀疏特征时,出现频次较低以及许久未出现的特征 ID 对于模型而言是冗余的。此外,交叉特征占据了大量的存储,可以在不影响训练效果的前提下过滤掉这些特征 ID。因此,迫切需求特征淘汰以及准入机制。
2、向量化存储结构优化
3、检索加快
4、结合生成式AI的部署优化
参考链接:
https://qiankunli.github.io/2022/03/02/embedding.html