译者 | 朱先忠
审校 | 重楼
近年来,随着诸如ChatGPT、Bard等生成式人工智能工具的发布,大型语言模型(LLM)在机器学习社区引起了全球热议。这些解决方案背后的核心思想之一是计算非结构化数据(如文本和图像)的数字表示,并找出这些表示之间的相似之处。
然而,将所有这些概念应用到生产环境中存在其自身的一系列机器学习工程挑战:
- 如何快速生成这些表示?
- 如何将它们存储在适当的数据库中?
- 如何快速计算生产环境的相似性?在这篇文章中,我将介绍两种开源解决方案,目的是解决下面这些问题:
- 句子变换器(https://www.sbert.net/;参考引文1):一种基于文本信息的嵌入生成技术;
- Qdrant(https://qdrant.tech/):一种能够存储嵌入并提供简单的查询接口的向量数据库。这两个工具都将应用于开发本文中的新闻门户推荐系统(参考引文2)。NPR(News Portal Recommendation),即新闻门户推荐数据集(在Kaggle网络公开免费使用:https://www.kaggle.com/datasets/joelpl/news-portal-recommendations-npr-by-globo),旨在支持学术界开发推荐算法。在本文的最后,您将学会:
- 使用句子转换器生成新闻嵌入
- 使用Qdrant数据库存储嵌入
- 查询嵌入以推荐新闻文章需要说明的是,本文的所有代码您都可以在Github网站上获得。
1.使用句子转换器生成嵌入
首先,我们需要找到一种将输入数据转换为向量的方法,我们称之为嵌入(如果你想深入了解嵌入概念,我推荐您阅读一下Boykis的文章《什么是嵌入?》,参考引文3:https://vickiboykis.com/what_are_embeddings/about.html)。
因此,首先让我们来看看我们可以使用NPR数据集处理什么样的数据:
import pandas as pd
df = pd.read_parquet("articles.parquet")
df.tail()
NPR数据集提供的样本数据(图片由作者本人生成)
NPR数据集提供了一些有趣的文本数据,如文章的标题和正文内容等。我们可以在嵌入生成过程中使用它们,如下图所示:
嵌入生成过程(作者本人提供的图片)
这样一来,一旦我们从输入数据中定义了文本特征,我们就需要建立一个嵌入模型来生成我们的数字表示。幸运的是,存在像HuggingFace这样的网站,你可以在那里寻找适合特定语言或任务的预训练模型。在我们的例子中,我们可以使用neuralmind/bert-base-portuguese-cased模型,该模型是用巴西葡萄牙语训练的,用于以下任务:
- 命名实体识别
- 句子文本相似性
- 文本蕴含识别下面的实现代码展示了我们是如何翻译嵌入生成过程的:
from sentence_transformers import SentenceTransformer
model_name = "neuralmind/bert-base-portuguese-cased"
encoder = SentenceTransformer(model_name_or_path=model_name)
title = """
Paraguaios vão às urnas neste domingo (30) para escolher novo presidente
"""
sentence = title
sentence_embedding = encoder.encode(sentence)
print (sentence_embedding)
# output: np.array([-0.2875876, 0.0356041, 0.31462672, 0.06252239, ...])
根据这里的代码逻辑,给定一个样本输入数据,我们就可以将标题和标签内容连接到单个文本中,并将其传递给编码器以生成文本嵌入。
我们可以对NPR数据集中的所有其他文章应用于上面相同的过程:
def generate_item_sentence(item: pd.Series, text_columns=["title"]) -> str:
return ' '.join([item[column] for column in text_columns])
df["sentence"] = df.apply(generate_item_sentence, axis=1)
df["sentence_embedding"] = df["sentence"].apply(encoder.encode)
请注意:上面这个过程可能需要耗费更长的时间,具体情况取决于您的机器的处理能力。
一旦我们有了所有新闻文章的嵌入;接下来,我们就可以定义一个存储它们的策略了。
2.存储嵌入
由于生成嵌入可能是一个昂贵的过程;因此,我们可以使用向量数据库来存储这些嵌入并基于不同的策略执行有关查询。
目前,已经存在几个向量数据库软件可以实现这项任务,但我将在本文中选择使用Qdrant,这是一个开源解决方案,它提供了可用于Python、Go和Typescript等多种流行编程语言的API支持。为了更好地比较这些向量数据库,请查看引文4来了解更多有关详情。
Qdrant设置准备
为了处理所有的Qdrant操作,我们需要创建一个指向向量数据库的客户端对象。Qdrant允许您创建一个免费的层服务来测试与数据库的远程连接,但为了简单起见,我选择在本地创建并保持数据库:
from qdrant_client import QdrantClient
client = QdrantClient(path="./qdrant_data")
一旦建立了这种连接,我们就可以在数据库中创建一个集合,用于存储新闻文章嵌入:
from qdrant_client import models
from qdrant_client.http.models import Distance, VectorParams
client.create_collection(
collection_name = "news-articles",
vectors_config = models.VectorParams(
size = encoder.get_sentence_embedding_dimension(),
distance = models.Distance.COSINE,
),
)
print (client.get_collections())
# output: CollectionsResponse(collectinotallow=[CollectionDescription(name='news-articles')])
请注意,代码中的向量配置参数是用于创建集合的。这些参数告诉Qdrant向量的一些属性,比如它们的大小和比较向量时要使用的距离指标(我会使用余弦相似性,不过你也可以使用例如内积或欧几里得距离等其他的计算策略)。
生成向量点
在最终存储到数据库之前,我们需要创建合适的上传对象。在Qdrant数据库中,向量可以使用PointStruct类存储,您可以使用该类定义以下属性:
- id:向量的id(在NPR的情况下,是newsId)
- vector:表示向量的一维数组(由嵌入模型生成)
- payload:一个包含任何其他相关元数据的字典,这些元数据稍后可以用于查询集合中的向量(在NPR的情况下,是文章的标题、正文和标签)。
from qdrant_client.http.models import PointStruct
metadata_columns = df.drop(["newsId", "sentence", "sentence_embedding"], axis=1).columns
def create_vector_point(item:pd.Series) -> PointStruct:
"""Turn vectors into PointStruct"""
return PointStruct(
id = item["newsId"],
vector = item["sentence_embedding"].tolist(),
payload = {
field: item[field]
for field in metadata_columns
if (str(item[field]) not in ['None', 'nan'])
}
)
points = df.apply(create_vector_point, axis=1).tolist()
上传向量
最后,在将所有信息都转换成点结构后,我们就可以将它们分块上传到数据库中:
CHUNK_SIZE = 500
n_chunks = np.ceil(len(points)/CHUNK_SIZE)
for i, points_chunk in enumerate(np.array_split(points, n_chunks)):
client.upsert(
collection_name="news-articles",
wait=True,
points=points_chunk.tolist()
)
3.查询向量
现在,既然我们已经用向量存储满集合,接下来,我们就可以开始查询数据库了。我们可以通过多种方式输入信息来查询数据库,但我认为这两种非常有用的输入可以使用:
- 输入文本
- 输入向量ID
3.1 使用输入向量查询向量
假设我们已经成功构建了用于搜索引擎的上述向量数据库,我们希望用户的输入是一个输入文本,并且我们必须返回最相关的内容。
由于向量数据库中的所有操作都是使用向量来实现的,所以,我们首先需要将用户的输入文本转换为向量,这样我们就可以根据该输入找到类似的内容。回想一下,我们曾经使用句子转换器将文本数据编码到嵌入中;因此,我们也可以使用相同的编码器为用户的输入文本生成数字表示。
由于NPR包含了新闻文章,那么假设用户键入“Donald Trump”(唐纳德·特朗普)来了解美国大选信息:
query_text = "Donald Trump"
query_vector = encoder.encode(query_text).tolist()
print (query_vector)
# output: [-0.048, -0.120, 0.695, ...]
一旦计算出输入查询向量,我们就可以搜索集合中最接近的向量,并定义我们希望从这些向量中得到什么样的输出,比如它们的newsId、标题和主题:
from qdrant_client.models import Filter
from qdrant_client.http import models
client.search(
collection_name="news-articles",
query_vector=query_vector,
with_payload=["newsId", "title", "topics"],
query_filter=None
)
注意:默认情况下,Qdrant使用近似最近邻居算法来快速扫描嵌入,但是您也可以进行完全扫描,并带来准确的最近邻数据——请记住,这是一个更昂贵的操作。
运行上面的操作后,以下是生成的输出标题(为了更好地理解,翻译成了英语):
- 输入句子:Donald Trump(唐纳德·特朗普)
- 输出1:Paraguayans go to the polls this Sunday (30) to choose a new president(巴拉圭人将于本周日(30日)前往投票站选举新总统)
- 输出2:Voters say Biden and Trump should not run in 2024, Reuters/Ipsos poll shows(路透社/益普索民意调查显示,选民表示拜登和特朗普不应在2024年参选)
- 输出3:Writer accuses Trump of sexually abusing her in the 1990s(作家指责特朗普在20世纪90年代对她进行性虐待)
- 输出4:Mike Pence, former vice president of Donald Trump, gives testimony in court that could complicate the former president(唐纳德·特朗普的前副总统迈克·彭斯在法庭上作证,这可能会给前总统带来不少麻烦)似乎除了带来与特朗普本人有关的新闻外,嵌入模型还成功地描述了与总统选举有关的话题。请注意,在第一个输出中,除了总统选举之外,没有直接引用输入术语“唐纳德·特朗普”。
此外,我还省略了query_filter参数。如果您想指定输出必须满足某些给定条件,这是一个非常有用的工具。例如,在新闻门户网站中,通常只过滤最近的文章(比如从过去7天起),这是很重要的。因此,您可以查询满足最小发布时间戳的新闻文章。
注意:在新闻推荐场景下,存在诸如公平性和多样性等多个需要考虑的方面。当然,这是一个开放的讨论主题;但是,如果您对这一领域感兴趣的话,不妨参阅NORMalize研讨会上的文章。
3.2 使用输入向量ID查询向量
最后,我们可以要求向量数据库“推荐”更接近某些所需向量ID但远离不需要的向量ID的内容。期望的ID和不期望的ID分别被称为正样本和负样本,它们被认为是推荐的种子样本。
例如,假设我们有以下正样本ID:
seed_id = '8bc22460-532c-449b-ad71-28dd86790ca2'
# title (translated): 'Learn why Joe Biden launched his bid for re-election this Tuesday'
那么,我们就可以要求提供与此样本类似的内容:
client.recommend(
collection_name="news-articles",
positive=[seed_id],
negative=None,
with_payload=["newsId", "title", "topics"]
)
运行上面的操作后,以下是已翻译的输出标题:
- 输入项:Learn why Joe Biden launched his bid for re-election this Tuesday(了解乔·拜登本周二发起连任竞选的原因)
- 输出1:Biden announces he will run for re-election(拜登宣布将竞选连任)
- 产出2:USA: the 4 reasons that led Biden to run for re-election(美国:导致拜登竞选连任的4个原因)
- 产出3:Voters say Biden and Trump should not run in 2024, Reuters/Ipsos poll shows(路透社/益普索民意调查显示,选民表示拜登和特朗普不应在2024年参选)
- 输出4:Biden’s advisor’s gaffe that raised doubts about a possible second government after the election(拜登顾问的失态引发了人们对大选后可能成立第二届政府的怀疑)
结论
本文向您展示了如何将LLM和向量数据库结合起来构建一个新闻推荐系统。特别提到了,使用句子转换器来实现从NPR数据集中的文本新闻文章中生成数字表示(嵌入)的方法。一旦计算出这些嵌入,就可以用这些嵌入来填充例如Qdrant这样的向量数据库,Qdrant的使用将非常有助于通过多种策略来实现向量查询。
最后,您可以基于本文提供的基础示例进行大量进一步的改进,例如:
- 测试其他嵌入模型
- 测试其他距离指标
- 测试其他向量数据库
- 使用Go等基于编译型编程语言以获得更好的性能
- 创建API支持的推荐系统
换言之,您可以提出许多想法来改进基于LLM推荐技术的机器学习工程。所以,如果您想分享您对这些改进的想法,请毫不犹豫地给我发信息吧。
关于我本人
我是巴西媒体科技公司Globo的资深数据科学家。在公司的推荐团队工作,我身边有一个了不起、才华横溢的团队,他们付出了大量努力,通过G1、GE、Globoplay等数字产品向数百万用户提供个性化内容。如果没有他们不可或缺的帮助,这篇文章是不可能与各位读者见面的。
参考文献
[1]N. reimers and I. Gurevych, Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks (2019), Association for Computational Linguistics。
[2]J. Pinho, J. Silva and L. Figueiredo, NPR: a News Portal Recommendations dataset (2023), ACM Conference on Recommender Systems。
[3]V. Boykis, What are embeddings?,个人博客。
[4]M. Ali, The Top 5 Vector Databases (2023),DataCamp博客。
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。
原文标题:Large Language Models and Vector Databases for News Recommendations,作者:João Felipe Guedes