譯者 | 朱先忠
審校 | 重樓
近年來,隨著諸如ChatGPT、Bard等生成式人工智能工具的發(fā)布,大型語言模型(LLM)在機器學習社區(qū)引起了全球熱議。這些解決方案背后的核心思想之一是計算非結構化數(shù)據(jù)(如文本和圖像)的數(shù)字表示,并找出這些表示之間的相似之處。
然而,將所有這些概念應用到生產環(huán)境中存在其自身的一系列機器學習工程挑戰(zhàn):
- 如何快速生成這些表示?
- 如何將它們存儲在適當?shù)臄?shù)據(jù)庫中?
- 如何快速計算生產環(huán)境的相似性?在這篇文章中,我將介紹兩種開源解決方案,目的是解決下面這些問題:
- 句子變換器(https://www.sbert.net/;參考引文1):一種基于文本信息的嵌入生成技術;
- Qdrant(https://qdrant.tech/):一種能夠存儲嵌入并提供簡單的查詢接口的向量數(shù)據(jù)庫。這兩個工具都將應用于開發(fā)本文中的新聞門戶推薦系統(tǒng)(參考引文2)。NPR(News Portal Recommendation),即新聞門戶推薦數(shù)據(jù)集(在Kaggle網絡公開免費使用:https://www.kaggle.com/datasets/joelpl/news-portal-recommendations-npr-by-globo),旨在支持學術界開發(fā)推薦算法。在本文的最后,您將學會:
- 使用句子轉換器生成新聞嵌入
- 使用Qdrant數(shù)據(jù)庫存儲嵌入
- 查詢嵌入以推薦新聞文章需要說明的是,本文的所有代碼您都可以在Github網站上獲得。
1.使用句子轉換器生成嵌入
首先,我們需要找到一種將輸入數(shù)據(jù)轉換為向量的方法,我們稱之為嵌入(如果你想深入了解嵌入概念,我推薦您閱讀一下Boykis的文章《什么是嵌入?》,參考引文3:https://vickiboykis.com/what_are_embeddings/about.html)。
因此,首先讓我們來看看我們可以使用NPR數(shù)據(jù)集處理什么樣的數(shù)據(jù):
import pandas as pd
df = pd.read_parquet("articles.parquet")
df.tail()
NPR數(shù)據(jù)集提供的樣本數(shù)據(jù)(圖片由作者本人生成)
NPR數(shù)據(jù)集提供了一些有趣的文本數(shù)據(jù),如文章的標題和正文內容等。我們可以在嵌入生成過程中使用它們,如下圖所示:
嵌入生成過程(作者本人提供的圖片)
這樣一來,一旦我們從輸入數(shù)據(jù)中定義了文本特征,我們就需要建立一個嵌入模型來生成我們的數(shù)字表示。幸運的是,存在像HuggingFace這樣的網站,你可以在那里尋找適合特定語言或任務的預訓練模型。在我們的例子中,我們可以使用neuralmind/bert-base-portuguese-cased模型,該模型是用巴西葡萄牙語訓練的,用于以下任務:
- 命名實體識別
- 句子文本相似性
- 文本蘊含識別下面的實現(xiàn)代碼展示了我們是如何翻譯嵌入生成過程的:
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, ...])
根據(jù)這里的代碼邏輯,給定一個樣本輸入數(shù)據(jù),我們就可以將標題和標簽內容連接到單個文本中,并將其傳遞給編碼器以生成文本嵌入。
我們可以對NPR數(shù)據(jù)集中的所有其他文章應用于上面相同的過程:
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.存儲嵌入
由于生成嵌入可能是一個昂貴的過程;因此,我們可以使用向量數(shù)據(jù)庫來存儲這些嵌入并基于不同的策略執(zhí)行有關查詢。
目前,已經存在幾個向量數(shù)據(jù)庫軟件可以實現(xiàn)這項任務,但我將在本文中選擇使用Qdrant,這是一個開源解決方案,它提供了可用于Python、Go和Typescript等多種流行編程語言的API支持。為了更好地比較這些向量數(shù)據(jù)庫,請查看引文4來了解更多有關詳情。
Qdrant設置準備
為了處理所有的Qdrant操作,我們需要創(chuàng)建一個指向向量數(shù)據(jù)庫的客戶端對象。Qdrant允許您創(chuàng)建一個免費的層服務來測試與數(shù)據(jù)庫的遠程連接,但為了簡單起見,我選擇在本地創(chuàng)建并保持數(shù)據(jù)庫:
from qdrant_client import QdrantClient
client = QdrantClient(path="./qdrant_data")
一旦建立了這種連接,我們就可以在數(shù)據(jù)庫中創(chuàng)建一個集合,用于存儲新聞文章嵌入:
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')])
請注意,代碼中的向量配置參數(shù)是用于創(chuàng)建集合的。這些參數(shù)告訴Qdrant向量的一些屬性,比如它們的大小和比較向量時要使用的距離指標(我會使用余弦相似性,不過你也可以使用例如內積或歐幾里得距離等其他的計算策略)。
生成向量點
在最終存儲到數(shù)據(jù)庫之前,我們需要創(chuàng)建合適的上傳對象。在Qdrant數(shù)據(jù)庫中,向量可以使用PointStruct類存儲,您可以使用該類定義以下屬性:
- id:向量的id(在NPR的情況下,是newsId)
- vector:表示向量的一維數(shù)組(由嵌入模型生成)
- payload:一個包含任何其他相關元數(shù)據(jù)的字典,這些元數(shù)據(jù)稍后可以用于查詢集合中的向量(在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()
上傳向量
最后,在將所有信息都轉換成點結構后,我們就可以將它們分塊上傳到數(shù)據(jù)庫中:
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.查詢向量
現(xiàn)在,既然我們已經用向量存儲滿集合,接下來,我們就可以開始查詢數(shù)據(jù)庫了。我們可以通過多種方式輸入信息來查詢數(shù)據(jù)庫,但我認為這兩種非常有用的輸入可以使用:
- 輸入文本
- 輸入向量ID
3.1 使用輸入向量查詢向量
假設我們已經成功構建了用于搜索引擎的上述向量數(shù)據(jù)庫,我們希望用戶的輸入是一個輸入文本,并且我們必須返回最相關的內容。
由于向量數(shù)據(jù)庫中的所有操作都是使用向量來實現(xiàn)的,所以,我們首先需要將用戶的輸入文本轉換為向量,這樣我們就可以根據(jù)該輸入找到類似的內容?;叵胍幌?,我們曾經使用句子轉換器將文本數(shù)據(jù)編碼到嵌入中;因此,我們也可以使用相同的編碼器為用戶的輸入文本生成數(shù)字表示。
由于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使用近似最近鄰居算法來快速掃描嵌入,但是您也可以進行完全掃描,并帶來準確的最近鄰數(shù)據(jù)——請記住,這是一個更昂貴的操作。
運行上面的操作后,以下是生成的輸出標題(為了更好地理解,翻譯成了英語):
- 輸入句子:Donald Trump(唐納德·特朗普)
- 輸出1:Paraguayans go to the polls this Sunday (30) to choose a new president(巴拉圭人將于本周日(30日)前往投票站選舉新總統(tǒng))
- 輸出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(唐納德·特朗普的前副總統(tǒng)邁克·彭斯在法庭上作證,這可能會給前總統(tǒng)帶來不少麻煩)似乎除了帶來與特朗普本人有關的新聞外,嵌入模型還成功地描述了與總統(tǒng)選舉有關的話題。請注意,在第一個輸出中,除了總統(tǒng)選舉之外,沒有直接引用輸入術語“唐納德·特朗普”。
此外,我還省略了query_filter參數(shù)。如果您想指定輸出必須滿足某些給定條件,這是一個非常有用的工具。例如,在新聞門戶網站中,通常只過濾最近的文章(比如從過去7天起),這是很重要的。因此,您可以查詢滿足最小發(fā)布時間戳的新聞文章。
注意:在新聞推薦場景下,存在諸如公平性和多樣性等多個需要考慮的方面。當然,這是一個開放的討論主題;但是,如果您對這一領域感興趣的話,不妨參閱NORMalize研討會上的文章。
3.2 使用輸入向量ID查詢向量
最后,我們可以要求向量數(shù)據(jù)庫“推薦”更接近某些所需向量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(了解喬·拜登本周二發(fā)起連任競選的原因)
- 輸出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(拜登顧問的失態(tài)引發(fā)了人們對大選后可能成立第二屆政府的懷疑)
結論
本文向您展示了如何將LLM和向量數(shù)據(jù)庫結合起來構建一個新聞推薦系統(tǒng)。特別提到了,使用句子轉換器來實現(xiàn)從NPR數(shù)據(jù)集中的文本新聞文章中生成數(shù)字表示(嵌入)的方法。一旦計算出這些嵌入,就可以用這些嵌入來填充例如Qdrant這樣的向量數(shù)據(jù)庫,Qdrant的使用將非常有助于通過多種策略來實現(xiàn)向量查詢。
最后,您可以基于本文提供的基礎示例進行大量進一步的改進,例如:
- 測試其他嵌入模型
- 測試其他距離指標
- 測試其他向量數(shù)據(jù)庫
- 使用Go等基于編譯型編程語言以獲得更好的性能
- 創(chuàng)建API支持的推薦系統(tǒng)
換言之,您可以提出許多想法來改進基于LLM推薦技術的機器學習工程。所以,如果您想分享您對這些改進的想法,請毫不猶豫地給我發(fā)信息吧。
關于我本人
我是巴西媒體科技公司Globo的資深數(shù)據(jù)科學家。在公司的推薦團隊工作,我身邊有一個了不起、才華橫溢的團隊,他們付出了大量努力,通過G1、GE、Globoplay等數(shù)字產品向數(shù)百萬用戶提供個性化內容。如果沒有他們不可或缺的幫助,這篇文章是不可能與各位讀者見面的。
參考文獻
[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社區(qū)編輯,51CTO專家博客、講師,濰坊一所高校計算機教師,自由編程界老兵一枚。
原文標題:Large Language Models and Vector Databases for News Recommendations,作者:Jo?o Felipe Guedes