知识在计算机内的表示是人工智能的核心问题。从数据库、互联网到大模型时代,知识的储存方式也发生了变化。在数据库中,知识以结构化的数据形式储存在数据库中,需要机器语言(如SQL)才能调用这些信息。互联网时代,人们调用搜索引擎获取互联网上的非结构化的知识。而对于大语言模型而言,知识以参数的形式储存在模型中,通过自然语言的 Prompt 问答的方式就可以直接调用这些知识。
语言是离散的符合,自然语言的表示学习,就是将人类的语言表示成更易于计算机理解的方式,尤其在深度学习兴起后,如何在网络的输入层更好的进行自然语言表示,成了值得关注的问题。在机器学习中,embedding 是指将高维度的数据(例如文字、图片、音频)映射到低维度空间的过程。Embedding 可以将文本数据映射成一个数值向量形式,而且语义相近的词,在向量空间上具有相似的位置,从而方便计算机进行处理和分析。比如用 Cosine 距离计算相似度;句子中多个词的 Embedding 相加得到句向量。
Embedding 起源于 Word Embedding,经过多年的发展,已取得长足进步。从横向发展来看,由原来单纯的 Word Embedding,发展成现在的Item Embedding、Entity Embedding、Graph Embedding、Position Embedding、Segment Embedding等;从纵向发展来看,由原来静态的Word Embedding发展成动态的预训练模型,如ELMo、BERT、GPT、GPT-2、GPT-3、ALBERT、XLNet等,这些预训练模型可以通过微调服务下游任务。Embedding 不再固定不变,从而使这些预训练模型可以学习到新的语义环境下的语义,高效完成下游的各种任务,如分类、问答、摘要生成、阅读理解等,其中有很多任务的完成效率已超过人工完成的平均水平。
embedding 技术的发展可以追溯到 20 世纪五六十年代的语言学研究,其中最著名的是 Harris 在 1954 年提出的分布式语义理论(distributional semantic theory)。这个理论认为,单词的语义可以通过它们在上下文中的分布来表示,也就是说,单词的含义可以从其周围的词语中推断出来。
从2010年以来,随着深度学习技术的发展,embedding 技术得到了广泛的应用和研究。在这个时期,出现了一些重要的嵌入算法,例如Word2Vec、GloVe和FastText等。这些算法可以通过训练神经网络或使用矩阵分解等技术来学习单词的嵌入表示,使得判断语义相似度成为可能,开启了自然语言预训练的序章。这些算法被广泛用于各种自然语言处理任务中,例如文本分类、机器翻译、情感分析等。但上下文无关的词向量模型无法很好地解决一词多义的问题。
近年来,随着深度学习和自然语言处理技术的快速发展,embedding 技术得到了进一步的改进和发展。例如,BERT、ELMo 和 GPT 等大型语言模型可以生成上下文相关的 embedding 表示,可以更好地捕捉单词的语义和上下文信息。EMLo 模型考虑了上下文的词向量表示方法,以双向 LSTM 作为特征提取器,开启了第二代预训练语言模型的时代。后来基于自注意力机制的 Transformer 作为更强大的特征提取器,被应用于 GPT、BERT 等模型,不断刷新自然语言处理领域的 SOTA (当前最优结果)。
降维: 在许多实际问题中,原始数据的维度往往非常高。例如,在自然语言处理中,如果使用One-hot编码来表示词汇,其维度等于词汇表的大小,可能达到数十万甚至更高。通过Embedding,我们可以将这些高维数据映射到一个低维空间,大大减少了模型的复杂度。
捕捉语义信息: Embedding不仅仅是降维,更重要的是,它能够捕捉到数据的语义信息。例如,在词嵌入中,语义上相近的词在向量空间中也会相近。这意味着Embedding可以保留并利用原始数据的一些重要信息。
适应性: 与一些传统的特征提取方法相比,Embedding是通过数据驱动的方式学习的。这意味着它能够自动适应数据的特性,而无需人工设计特征。
泛化能力: 在实际问题中,我们经常需要处理一些在训练数据中没有出现过的数据。由于Embedding能够捕捉到数据的一些内在规律,因此对于这些未见过的数据,Embedding仍然能够给出合理的表示。
可解释性: 尽管Embedding是高维的,但我们可以通过一些可视化工具(如t-SNE)来观察和理解Embedding的结构。这对于理解模型的行为,以及发现数据的一些潜在规律是非常有用的。
生成Embedding的方法有很多。这里列举几个比较经典的方法和库:
Word2Vec:是一种基于神经网络的模型,用于将单词映射到向量空间中。Word2Vec包括两种架构:CBOW (Continuous Bag-of-Words) 和 Skip-gram。CBOW 通过上下文预测中心单词,而 Skip-gram 通过中心单词预测上下文单词。这些预测任务训练出来的神经网络权重可以用作单词的嵌入。
GloVe:全称为 Global Vectors for Word Representation,是一种基于共现矩阵的模型。该模型使用统计方法来计算单词之间的关联性,然后通过奇异值分解(SVD)来生成嵌入。GloVe 的特点是在计算上比 Word2Vec 更快,并且可以扩展到更大的数据集。
FastText:是由 Facebook AI Research 开发的一种模型,它在 Word2Vec 的基础上添加了一个字符级别的 n-gram 特征。这使得 FastText 可以将未知单词的嵌入表示为已知字符级别 n-gram 特征的平均值。FastText 在处理不规则单词和罕见单词时表现出色。
大模型的 Embeddings:如OpenAI官方发布的 第二代模型:text-embedding-ada-002。它最长的输入是8191个tokens,输出的维度是1536。
embedding 的保存可以考虑使用向量数据库。例如,
Pinecone的产品,最近刚以10亿美元的估值融资了1亿美金。Shopify, Brex, Hubspot都是它产品的用户。
Milvus是一个开源的向量数据库。
Word Embedding:词嵌入通常被用来生成词的向量表示,这个过程通常是静态的,即一旦训练完成,每个词的向量表示就确定了。词嵌入的主要目标是捕获单词或短语的语义和语法信息,并将这些信息以向量形式表示出来。词嵌入的一个重要特性是,语义上相近的词在嵌入空间中的距离也比较近。然而,词嵌入并不能理解上下文信息,即相同的词在不同的上下文中可能有不同的含义,但词嵌入无法区分这些含义。
Language Model:语言模型则是预测词序列的概率模型,这个过程通常是动态的,会根据输入的上下文进行变化。语言模型的主要目标是理解和生成文本。这包括对上下文的理解,词的预测,句子的生成等等。语言模型会用到词嵌入,但同时也会对上下文进行建模,这样可以处理词在不同上下文中的不同含义。在某种程度上,你可以将词嵌入看作是语言模型的一部分或者输入,语言模型使用词嵌入捕捉的信息,来进行更深层次的语义理解和文本生成。当然,现在有一些更先进的模型,比如 BERT,GPT 等,它们生成的是上下文相关的词嵌入,即词的嵌入会根据上下文变化,这样一定程度上弥补了传统词嵌入模型的不足。
尽管目前 GPT-4 或者 ChatGPT 的能力已经很强大,但是目前它依然有很大的缺陷:
知识不足或信息丢失
训练数据是基于2021年9月之前的数据,缺少最新的数据
无法访问私有的文档
基于历史会话中获取信息
对输入的内容长度有限制,
这个限制通常在几千到数万个tokens之间,具体取决于模型架构和可用的硬件资源。这意味着对于更长的文本,例如整本书或长文章,可能无法一次将所有文本输入到语言模型中。
解决方案:
基于 Embedding 搜索 + 问答的模式,把知识通过 Embedding 后存储到向量数据库,作为长期记忆。提问时首先在向量数据库中搜索相关知识,然后仅把返回的结果提供给大模型作为输入。进一步也可以针对问题和检索得到的 embedding 做一些提示工程,来优化 ChatGPT 的回答。
准备数据:美食评论数据集
该数据集包含截至2012年10月用户在亚马逊上留下的共计568,454条美食评论。取了该数据集的一个子集,其中包括最近1,000条评论。这些评论都是用英语撰写的,并且倾向于积极或消极。每个评论都有一个产品ID、用户ID、评分、标题(摘要)和正文。我们将把评论摘要和正文合并成一个单一的组合文本。对这个组合文本进行编码,并输出一个单一的向量嵌入。
# 导入 pandas 包。Pandas 是一个用于数据处理和分析的 Python 库,方便进行数据的读取、处理、分析等操作。import pandas as pd# 导入 tiktoken 库。Tiktoken 是 OpenAI 开发的一个库,用于从模型生成的文本中计算 token 数量。import tiktoken# 这个函数可以获取 GPT-3 模型生成的嵌入向量。from openai.embeddings_utils import get_embeddingimport openaiimport os# 从OPENAI_API_KEY环境变量中读取你的API密钥openai.api_key = os.getenv("OPENAI_API_KEY")
input_datapath = "data/fine_food_reviews_1k.csv"# 从指定路径读取csv文件df = pd.read_csv(input_datapath, index_col=0)df = df[["Time", "ProductId", "UserId", "Score", "Summary", "Text"]]# 删除包含缺失值的df = df.dropna()# 将 "Summary" 和 "Text" 字段组合成新的字段 "combined"df["combined"] = ( "title: " + df.Summary.str.strip() + "; content: " + df.Text.str.strip())
# 模型类型建议使用官方推荐的第二代嵌入模型:text-embedding-ada-002embedding_model = "text-embedding-ada-002"# text-embedding-ada-002 模型对应的分词器(TOKENIZER)embedding_encoding = "cl100k_base"
# 设置要筛选的评论数量为1000top_n = 1000# text-embedding-ada-002 模型支持的输入最大 Token 数是8191,向量维度 1536# 在我们的 DEMO 中过滤 Token 超过 8000 的文本max_tokens = 8000# 对DataFrame进行排序,基于"Time"列,然后选取最后的2000条评论。df = df.sort_values("Time").tail(top_n * 2) # 从'embedding_encoding'获取编码encoding = tiktoken.get_encoding(embedding_encoding)# 计算每条评论的token数量,存储在新的'n_tokens'列中。df["n_tokens"] = df.combined.apply(lambda x: len(encoding.encode(x)))# 如果评论的token数量超过最大允许的token数量,我们将删除该评论。然后选取最后的1000条评论。df = df[df.n_tokens <= max_tokens].tail(top_n)# 打印出剩余评论的数量。print(len(df))
# 实际生成会耗时几分钟df["embedding"] = df.combined.apply(lambda x: get_embedding(x, engine=embedding_model))output_datapath = "data/fine_food_reviews_with_embeddings_1k.csv"df.to_csv(output_datapath)## 加载并查看结果df_embedded = pd.read_csv(embedding_datapath, index_col=0)df_embedded["embedding"]import ast# 将字符串转换为向量df_embedded["embedding_vec"] = df_embedded["embedding"].apply(ast.literal_eval)print(len(df_embedded["embedding_vec"][0])) # 向量长度
# 导入 NumPy 包,NumPy 是 Python 的一个开源数值计算扩展。这种工具可用来存储和处理大型矩阵,import numpy as np# matplotlib 是一个 Python 的 2D 绘图库,pyplot 是其子库,提供了一种类似 MATLAB 的绘图框架。import matplotlib.pyplot as plt# TSNE (t-Distributed Stochastic Neighbor Embedding) 是一种用于数据可视化的降维方法,尤其擅长处理高维数据的可视化。# 它可以将高维度的数据映射到 2D 或 3D 的空间中,以便我们可以直观地观察和理解数据的结构。from sklearn.manifold import TSNE# 将嵌入向量列表转换为二维 numpy 数组matrix = np.vstack(df_embedded['embedding_vec'].values)# 创建一个 t-SNE 模型,t-SNE 是一种非线性降维方法,常用于高维数据的可视化。# n_components 表示降维后的维度(在这里是2D)# perplexity 可以被理解为近邻的数量# random_state 是随机数生成器的种子# init 设置初始化方式# learning_rate 是学习率。tsne = TSNE(n_components=2, perplexity=15, random_state=42, init='random', learning_rate=200)# 使用 t-SNE 对数据进行降维,得到每个数据点在新的2D空间中的坐标vis_dims = tsne.fit_transform(matrix)# 定义了五种不同的颜色,用于在可视化中表示不同的等级colors = ["red", "darkorange", "gold", "turquoise", "darkgreen"]# 从降维后的坐标中分别获取所有数据点的横坐标和纵坐标x = [x for x,y in vis_dims]y = [y for x,y in vis_dims]# 根据数据点的评分(减1是因为评分是从1开始的,而颜色索引是从0开始的)获取对应的颜色索引color_indices = df_embedded.Score.values - 1# 确保你的数据点和颜色索引的数量匹配assert len(vis_dims) == len(df_embedded.Score.values)colormap = matplotlib.colors.ListedColormap(colors)# 使用 matplotlib 创建散点图,其中颜色由颜色映射对象和颜色索引共同决定,alpha 是点的透明度plt.scatter(x, y, c=color_indices, cmap=colormap, alpha=0.3)# 为图形添加标题plt.title("Amazon ratings visualized in language using t-SNE")
# cosine_similarity 函数计算两个嵌入向量之间的余弦相似度。from openai.embeddings_utils import get_embedding, cosine_similarity# 定义一个名为 search_reviews 的函数,# Pandas DataFrame 产品描述,数量,以及一个 pprint 标志(默认值为 True)。def search_reviews(df, product_description, n=3, pprint=True): product_embedding = get_embedding( product_description, engine="text-embedding-ada-002" ) print(product_embedding[0:100]) df["similarity"] = df.embedding_vec.apply(lambda x: cosine_similarity(x, product_embedding)) results = ( df.sort_values("similarity", ascending=False) .head(n) .combined.str.replace("title: ", "") .str.replace("; content:", ": ") ) if pprint: for r in results: print(r[:200]) print() return results# 使用 'delicious beans' 作为产品描述和 3 作为数量,# 调用 search_reviews 函数来查找与给定产品描述最相似的前3条评论。# 其结果被存储在 res 变量中。res = search_reviews(df_embedded, 'delicious beans', n=3)
以 LLM 为基础的知识问答系统构建方法核心在于:
将用户问题和本地知识进行 Embedding,通过向量相似度(Vector Similarity)实现召回
通过 LLM 对用户问题进行意图识别;并对原始答案加工整合。
具体来说,该系统主要实现以下过程:
1. 准备搜索数据(每个文档仅需准备一次) 收集:我们将下载数百篇有关2022年奥运会的维基百科文章; 分块:文档被分成简短的、独立的片段用于Embedding; Embedding:每个部分都使用OpenAI API来实现Embedding; 保存:Embedding被存储起来(对于大型数据集,使用矢量数据库)。 2. 搜索(针对每次查询) 给定用户问题,使用OpenAI API将查询转换成Embedding; 使用Embedding,根据查询的相关性对文本部分进行排名。 3. 提问(针对每次查询) 将问题和最相关的部分插入至发送到GPT的消息中; 返回GPT给出的答案。
成本,由于GPT比Embedding搜索更昂贵,因此具有大量查询的系统成本将主要由步骤3决定。
对于每个查询使用约1,000个Token的gpt-3.5-turbo,每个查询的成本约为0.002美元,或1美元约500个查询(截至2023年4月); 对于gpt-4,再次假设每个查询约1,000个Token,每个查询的成本约为0.03美元,或1美元约30个查询(截至2023年4月)。
当然,确切的成本将取决于系统的具体实现和使用模式。
# importsimport ast # 用于将Embedding的数组转换为Stringimport openai # 访问OpenAI APIimport pandas as pd # 存储文本和Embedding数据import tiktoken # 计算Tokenfrom scipy import spatial # 计算Vector的相似度以便用于搜索# modelsEMBEDDING_MODEL = "text-embedding-ada-002"GPT_MODEL = "gpt-3.5-turbo"
由于gpt-3.5-turbo和gpt-4的训练数据大多于2021年9月结束,因此模型无法回答有关近期事件的问题,例如2022年冬季奥运会。
例如,让我们尝试提问“哪些运动员赢得了2022年冰壶金牌?”
# an example question about the 2022 Olympicsquery = 'Which athletes won the gold medal in curling at the 2022 Winter Olympics?'response = openai.ChatCompletion.create( messages=[ {'role': 'system', 'content': 'You answer questions about the 2022 Winter Olympics.'}, {'role': 'user', 'content': query}, ], model=GPT_MODEL, temperature=0,)print(response['choices'][0]['message']['content'])
结果: I'm sorry, but as an AI language model, I don't have information about the future events. The 2022 Winter Olympics will be held in Beijing, China, from February 4 to 20, 2022. The curling events will take place during the games, and the winners of the gold medal in curling will be determined at that time.
在这种情况下,模型不知道2022年发生的事情,无法回答该问题。你可以通过将某个主题的内容插入到输入消息中来提供有关该主题的知识。
为了帮助模型了解2022年冬季奥运会的冰壶知识,我们可以将相关维基百科文章的上半部分复制并粘贴到我们的消息中:
# text copied and pasted from: https://en.wikipedia.org/wiki/Curling_at_the_2022_Winter_Olympics# I didn't bother to format or clean the text, but GPT will still understand it# the entire article is too long for gpt-3.5-turbo, so I only included the top few sectionswikipedia_article_on_curling = """(内容略......)"""query = f"""Use the below article on the 2022 Winter Olympics to answer the subsequent question. If the answer cannot be found, write "I don't know."Article:\"\"\"{wikipedia_article_on_curling}\"\"\"Question: Which athletes won the gold medal in curling at the 2022 Winter Olympics?"""response = openai.ChatCompletion.create( messages=[ {'role': 'system', 'content': 'You answer questions about the 2022 Winter Olympics.'}, {'role': 'user', 'content': query}, ], model=GPT_MODEL, temperature=0,)print(response['choices'][0]['message']['content'])
结果:由于输入消息中包含维基百科文章,因此GPT能够正确回答。 There were three events in curling at the 2022 Winter Olympics, so there were three sets of athletes who won gold medals...
当然,这个例子部分依赖于人类智慧。我们知道问题是关于冰壶的,所以我们插入了一篇关于冰壶的维基百科文章。
1. 准备搜索数据
使用已预嵌入的数据集,其中包含数百篇有关2022年冬季奥运会的维基百科文章。
embeddings_path = "https://cdn.openai.com/API/examples/data/winter_olympics_2022.csv" df = pd.read_csv(embeddings_path) # convert embeddings from CSV str type back to list type df['embedding'] = df['embedding'].apply(ast.literal_eval) #the dataframe has two columns: "text" and "embedding"
2. 搜索
# 定义一个搜索函数 def strings_ranked_by_relatedness( query: str, # 用户查询 df: pd.DataFrame, # 带有文本和Embedding列的数据 relatedness_fn=lambda x, y: 1 - spatial.distance.cosine(x, y), top_n: int = 100 ) -> tuple[list[str], list[float]]: """根据查询和文本的Embedding距离对文本进行排序,返回两个列表:前N个文本,按相关性排名, 它们对应的相关性分数。""" # 使用OpenAI API嵌入用户查询; query_embedding_response = openai.Embedding.create( model=EMBEDDING_MODEL, input=query, ) query_embedding = query_embedding_response["data"][0]["embedding"] strings_and_relatednesses = [ (row["text"], relatedness_fn(query_embedding, row["embedding"])) for i, row in df.iterrows() ] strings_and_relatednesses.sort(key=lambda x: x[1], reverse=True) strings, relatednesses = zip(*strings_and_relatednesses) return strings[:top_n], relatednesses[:top_n] # examples strings, relatednesses = strings_ranked_by_relatedness("curling gold medal", df, top_n=5) for string, relatedness in zip(strings, relatednesses): print(f"{relatedness=:.3f}") display(string)
打印结果:
relatedness=0.879 'Curling at the 2022 Winter Olympics\n\n==Medal summary==\n\n...' relatedness=0.872 "Curling at the 2022 Winter Olympics\n\n==Results summary==\n\n===Women's tournament===..." relatedness=0.869 'Curling at the 2022 Winter Olympics\n\n==Results summary==\n\n===Mixed doubles ...' relatedness=0.868 "Curling at the 2022 Winter Olympics\n\n==Medal summary==\n\n===Medalists===..." relatedness=0.867 "Curling at the 2022 Winter Olympics\n\n==Results summary==\n\n===Men's tournament..."
3. 提问
通过上面的搜索功能,我们现在可以自动检索相关知识并将其插入到 GPT 的消息中。
def num_tokens(text: str, model: str = GPT_MODEL) -> int: """文本对应的 token 数量""" encoding = tiktoken.encoding_for_model(model) return len(encoding.encode(text)) def query_message( query: str, # 用户问题 df: pd.DataFrame, # 知识库 model: str, token_budget: int ) -> str: """组装查询Prompt,Return a message for GPT, with relevant source texts pulled from a dataframe.""" strings, relatednesses = strings_ranked_by_relatedness(query, df) introduction = 'Use the below articles on the 2022 Winter Olympics to answer the subsequent question. If the answer cannot be found in the articles, write "I could not find an answer."' question = f"\n\nQuestion: {query}" message = introduction for string in strings: next_article = f'\n\nWikipedia article section:\n"""\n{string}\n"""' if num_tokens(message + next_article + question, model=model) > token_budget: break else: message += next_article return message + question def ask( query: str, df: pd.DataFrame = df, model: str = GPT_MODEL, token_budget: int = 4096 - 500, print_message: bool = False, ) -> str: """向大模型提问,Answers a query using GPT and a dataframe of relevant texts and embeddings.""" message = query_message(query, df, model=model, token_budget=token_budget) if print_message: print(message) messages = [ {"role": "system", "content": "You answer questions about the 2022 Winter Olympics."}, {"role": "user", "content": message}, ] response = openai.ChatCompletion.create( model=model, messages=messages, temperature=0 ) response_message = response["choices"][0]["message"]["content"] return response_message # 最后,让我们向系统提问有关金牌冰壶的原始问题: ask('Which athletes won the gold medal in curling at the 2022 Winter Olympics?')
尽管 gpt-3.5-turbo 对 2022 年冬季奥运会一无所知,但我们的搜索系统能够检索参考文本供模型阅读,从而使其能够正确列出男子和女子锦标赛的金牌获得者。
通过该方式可以解决目前GPT或者其它大模型没有最新数据或者输入限制的问题。在 AutoGPT 中,作者也有一个思路,就是每次都把最近的问题以及最相关的目标一起发送给 GPT,获得答案,以此来获得更长记忆的能力。但是从实现效果来说,这种利用 Embeddings 先获取最相关文本再提问的方式可能是比较优雅的。而且也可以控制输入和输出的 tokens。
词嵌入技术的未来发展可能会沿着以下几个方向:
模型的细粒度和多模态性:例如,字符级(Char-level)的嵌入、语义级的嵌入,以及结合图像、声音等多模态信息的嵌入。
更好的理解和利用上下文信息:例如,动态的、可变长度的上下文,以及更复杂的上下文结构。
模型的可解释性和可控制性:这包括模型的内部结构和嵌入空间的理解,以及对模型生成结果的更精细控制。
更大规模的模型和数据:例如,GPT-4、GPT-5等更大规模的预训练模型,以及利用全球范围的互联网文本数据。