推薦系統技術已發展多年,而深度學習的興起為這個領域帶來了革命性的變化。在使用神經網路構建推薦系統時,我們可以利用多種架構來捕捉使用者行為的複雜模式和專案特性。

內容處理與特徵學習

卷積神經網路(CNN)和迴圈神經網路(RNN)在處理專案內容方面表現出色。這些網路能夠自動從原始資料中學習特徵,為根據內容的個人化推薦提供強大支援。當我們需要分析圖片、文字或音訊等非結構化資料時,這些模型特別有價值。

例如,在電影推薦系統中,CNN可以從電影海報或預告片截圖中提取視覺特徵,而RNN則可以處理劇情摘要或評論文字,從而捕捉到傳統方法難以識別的內容模式。

時序模型與使用者行為

使用者互動通常具有時間序列特性,例如瀏覽歷史或點選流。在這種情境下,RNN或其變體如長短期記憶網路(LSTM)特別適合:

# LSTM模型用於序列推薦的簡化範例
import tensorflow as tf

class SequentialRecommender(tf.keras.Model):
    def __init__(self, num_items, embedding_dim, lstm_units):
        super().__init__()
        self.item_embedding = tf.keras.layers.Embedding(
            num_items + 1, embedding_dim, mask_zero=True)
        self.lstm = tf.keras.layers.LSTM(lstm_units)
        self.dense = tf.keras.layers.Dense(num_items, activation='softmax')
        
    def call(self, inputs):
        x = self.item_embedding(inputs)
        x = self.lstm(x)
        return self.dense(x)

這段程式碼實作了一個根據LSTM的序列推薦器。首先,透過item_embedding層將專案ID轉換為密集向量表示,這一步類別似於NLP中的詞嵌入。接著,LSTM層處理這些嵌入序列,捕捉使用者行為中的時間依賴關係。最後,透過全連線層和softmax啟用函式輸出每個專案的推薦機率。

此架構的優點在於能夠記住使用者的長期興趣並捕捉短期偏好變化,從而產生更加動態的推薦。我在實際應用中發現,增加註意力機制通常能進一步提升模型效能,特別是對於長序列行為。

自編碼器與表示學習

自編碼器(Autoencoder)和變分自編碼器(VAE)是降維和表示學習的強大工具,在推薦系統中有廣泛應用。

自編碼器的基本原理

自編碼器是一種用於無監督學習和降維的神經網路架構,由編碼器和解碼器兩部分組成:

  • 編碼器將輸入資料對映到低維潛在空間
  • 解碼器嘗試從編碼表示重建原始輸入資料

這種結構使得模型能夠學習資料的壓縮表示,同時保留關鍵訊息。

變分自編碼器的進階特性

VAE擴充套件了傳統自編碼器,引入了機率元素。VAE不僅學習將輸入資料編碼到潛在空間,還使用機率方法建模這個潛在空間的分佈。這允許從學習到的潛在空間生成新的資料樣本。

# 用於推薦系統的簡化VAE實作
class RecommenderVAE(tf.keras.Model):
    def __init__(self, original_dim, latent_dim):
        super().__init__()
        # 編碼器
        self.encoder = tf.keras.Sequential([
            tf.keras.layers.Dense(256, activation='relu'),
            tf.keras.layers.Dense(latent_dim * 2)  # 均值和對數方差
        ])
        
        # 解碼器
        self.decoder = tf.keras.Sequential([
            tf.keras.layers.Dense(256, activation='relu'),
            tf.keras.layers.Dense(original_dim, activation='sigmoid')
        ])
    
    def encode(self, x):
        mean_logvar = self.encoder(x)
        mean, logvar = tf.split(mean_logvar, num_or_size_splits=2, axis=1)
        return mean, logvar
        
    def reparameterize(self, mean, logvar):
        eps = tf.random.normal(shape=mean.shape)
        return eps * tf.exp(logvar * 0.5) + mean
    
    def decode(self, z):
        return self.decoder(z)
        
    def call(self, x):
        mean, logvar = self.encode(x)
        z = self.reparameterize(mean, logvar)
        reconstruction = self.decode(z)
        return reconstruction, mean, logvar

這段程式碼實作了一個變分自編碼器用於推薦系統。編碼器將使用者-專案互動矩陣壓縮為低維潛在表示,但與普通自編碼器不同,VAE產生的是潛在空間的機率分佈(由均值和對數方差表示)。

reparameterize函式實作了"重引數化技巧",允許在保持可微性的同時從分佈中取樣。解碼器嘗試從潛在表示重建原始互動矩陣。這種方法不僅能學習使用者和專案的隱含特徵,還能模擬資料的不確定性,提高推薦的多樣性。

在實踐中,VAE的這種機率性質使其特別適合處理稀疏的使用者-專案互動資料,能夠生成更多樣化的推薦結果。

神經網路推薦的進階技術

神經網路獨特的架構允許多種先進技術應用於推薦系統:

側面訊息整合

神經網路可以輕鬆結合使用者和專案的附加屬性,如人口統計資料、地理位置或社交連線。這種能力大提升了推薦的準確性和相關性。

例如,在音樂推薦系統中,我們可以結合使用者的年齡、地理位置、聆聽時間等因素,與音樂的節奏、情緒、流派等特性,建立更全面的推薦模型。

深度強化學習

在某些情境下,深度強化學習能夠從使用者反饋中學習,最佳化推薦策略以最大化長期回報。這種方法特別適合需要平衡探索與利用的推薦場景。

透過將推薦問題建模為馬可夫決策過程,強化學習能夠學習一個策略,在不同情境下選擇最佳的推薦專案。這種方法的優勢在於能夠考慮長期使用者滿意度,而不僅是即時點選率。

神經網路推薦系統的挑戰

雖然神經網路為推薦系統提供了強大的能力,但也帶來了一系列挑戰:

模型複雜性與訓練需求

深度神經網路,尤其是多層架構,可能變得極其複雜。隨著我們增加更多隱藏層和神經元,模型學習複雜模式的能力增加,但同時訓練難度也大幅提升。

這些模型通常需要專門的硬體如GPU,訓練成本可能相當高昂。在實際應用中,我發現平衡模型複雜性和資源消耗是一個持續的挑戰。

過擬合風險

神經網路的一個共同問題是過擬合,即模型在訓練資料上表現卓越,但無法泛化到未見資料。這在推薦系統中尤為關鍵,因為我們的目標是為使用者推薦他們尚未互動過的專案。

選擇適當的架構、處理大型資料集和調整超引數是有效使用神經網路的關鍵。儘管近年來取得了相關進展,但這些技術仍然存在一些缺陷,主要是它們的任務特定性。

例如,專為評分預測設計的推薦系統無法處理需要推薦最可能符合使用者口味的前k個專案的任務。實際上,如果我們將這一限制擴充套件到其他「前LLM」AI解決方案,我們可能會看到一些相似之處:這正是LLM和更廣泛的大型基礎模型正在革命性改變的任務特定情境,它們高度通用化與可適應各種任務。

LLM如何改變推薦系統

大模型語言(LLM)正在徹底改變推薦系統領域。根據Wenqi Fan等人的論文《大模型語言時代的推薦系統》,LLM可以透過三種主要方式定製為推薦系統:

預訓練方法

為推薦系統預訓練LLM是使其取得廣泛世界知識和使用者偏好的重要步驟,使模型能夠以零樣本或少樣本方式適應不同推薦任務。

一個推薦系統LLM的例子是P5,由Shijie Gang等人在論文《將推薦視為語言處理:統一預訓練、個人化提示和預測正規化》中介紹。P5是使用大模型語言構建推薦系統的統一文字到文字正規化,包含三個步驟:

  1. 預訓練:根據T5架構的基礎語言模型在大規模網路語料函式庫上預訓練,並在推薦任務上微調
  2. 個人化提示:根據使用者行為資料和上下文特徵為每個使用者生成個人化提示
  3. 預測:將個人化提示輸入預訓練的語言模型以生成推薦

P5根據LLM可以編碼廣泛世界知識和使用者偏好的理念,並可適應不同推薦任務。

微調策略

從頭開始訓練LLM是一項計算密集型活動。一種替代與幹擾較小的方法是微調。論文作者回顧了兩種主要的微調策略:

  1. 全模型微調:根據特定推薦資料集更改整個模型的權重
  2. 引數高效微調:只更改一小部分權重或開發可訓練的介面卡以適配特定任務

提示工程

第三種也是「最輕量級」的定製LLM為推薦系統的方法是提示工程。根據作者的觀點,主要有三種提示技術:

  1. 常規提示:透過設計文字範本或提供少量輸入-輸出範例,將下游任務統一為語言生成任務
  2. 上下文學習:使LLM能夠根據上下文訊息學習新任務,無需微調
  3. 思維鏈提示:透過在提示中提供多個演示來描述思維鏈作為範例,增強LLM的推理能力

無論型別如何,提示工程是測試通用LLM是否可以應對推薦系統任務的最快方法。

實作LLM驅動的推薦系統

在理解了推薦系統理論和LLM如何增強它們後,讓我們開始構建自己的推薦應用程式:一個名為MovieHarbor的電影推薦系統。目標是使應用程式盡可能通用,意味著我們希望它能夠透過對話介面處理各種推薦任務。

我們將模擬所謂的「冷啟動」場景,即使用者首次與推薦系統互動,系統尚未擁有使用者的偏好歷史。這是推薦系統中的一個經典挑戰,而LLM的強大上下文理解能力使其特別適合解決此類別問題。

在設計這個系統時,我認為關鍵是平衡模型的通用性和個人化能力。傳統推薦系統在有大量歷史資料時表現優秀,但在冷啟動階段往往表現不佳。而LLM憑藉其龐大的世界知識和語言理解能力,可以從使用者的自然語言描述中快速建立偏好模型。

下一部分我們將探討如何利用LangChain作為AI協調工具,實作這個LLM驅動的電影推薦系統,並解決冷啟動問題。

神經網路和LL

開發智慧電影推薦系統:結合LLM與向量搜尋的實戰

在人工智慧快速發展的今日,推薦系統已成為我們日常數位生活中不可或缺的一部分。無論是影音平台、電商網站,還是社交媒體,都依賴精準的內容推薦來提升使用者經驗。隨著大模型語言(LLM)的崛起,我們現在有機會建構更智慧、更人性化的推薦機制。

在這篇文章中,我將帶領大家從零開始,開發一個名為「MovieHarbor」的電影推薦系統,透過整合大模型語言與向量資料函式庫,實作根據內容的智慧推薦功能。

資料選擇與準備

為了實作這個系統,我選擇使用Kaggle上的電影推薦資料集。這個資料集的價值在於它不僅包含電影評分和標題等基本資訊,還提供了每部電影的文字描述,這對於生成文字嵌入(embeddings)至關重要。

資料預處理:轉換原始資料為可用形式

在將大模型語言應用於資料集前,我們需要進行一系列的預處理工作。雖然原始資料集包含許多欄位,但我們主要關注以下幾個:

  • 型別(Genres): 適用於該電影的型別列表
  • 標題(Title): 電影標題
  • 概述(Overview): 劇情描述文字
  • 評分均值(Vote_average): 1到10分的電影評分
  • 評分次數(Vote_count): 該電影的投票數量

讓我們逐步進行資料預處理:

1. 格式化電影型別欄位

首先,我們需要將型別欄位從原始的字典格式轉換為更易於處理的numpy陣列:

import pandas as pd
import ast

# 將字串表示的字典轉換為實際字典
md['genres'] = md['genres'].apply(ast.literal_eval)

# 轉換'genres'欄位
md['genres'] = md['genres'].apply(lambda x: [genre['name'] for genre in x])

這段程式碼處理電影型別資料,原始資料中的型別資訊是以字典格式的字串儲存的,需要轉換成實際的Python物件。ast.literal_eval()函式安全地將字串轉為Python資料結構,然後透過lambda函式提取每個型別字典中的’name’值,最終將型別資訊轉換為一個簡單的字串列表,例如從複雜的JSON格式轉為[‘Action’, ‘Thriller’, ‘Drama’]這樣的形式,便於後續處理。

2. 合併評分資訊

接著,我們將評分均值和評分次數合併為一個加權評分欄位,並限制資料為評分次數前95%的電影,以避免因評分次數過少而產生偏差:

# 計算加權評分(使用IMDb公式)
def calculate_weighted_rate(vote_average, vote_count, min_vote_count=10):
    return (vote_count / (vote_count + min_vote_count)) * vote_average + \
           (min_vote_count / (vote_count + min_vote_count)) * 5.0

# 設定最低評分次數以防止結果偏差
vote_counts = md[md['vote_count'].notnull()]['vote_count'].astype('int')
min_vote_count = vote_counts.quantile(0.95)

# 建立新欄位'weighted_rate'
md['weighted_rate'] = md.apply(
    lambda row: calculate_weighted_rate(row['vote_average'], 
                                       row['vote_count'], 
                                       min_vote_count), 
    axis=1
)

這段程式碼實作了一種類別似IMDb使用的加權評分計算方法。這種方法解決了一個常見問題:少量評分可能導致不公平的高分或低分。函式中的公式結合了電影的實際評分和評分次數,如果評分次數很少,評分會向5.0(中間值)靠攏;如果評分次數很多,則更接近實際評分均值。程式首先確定了評分次數的第95百分位數作為閾值,然後為每部電影計算加權評分。這樣處理後,評分更能反映電影的真實品質,而不會被少數極端評分左右。

3. 建立綜合資訊欄位

我們建立一個名為combined_info的新欄位,將提供給LLM作為上下文的所有元素合併在一起,包括電影標題、概述、型別和評分:

md_final['combined_info'] = md_final.apply(
    lambda row: f"Title: {row['title']}. Overview: {row['overview']} " + 
                f"Genres: {', '.join(row['genres'])}. Rating: {row['weighted_rate']}", 
    axis=1
).astype(str)

這段程式碼的核心目的是建立一個融合所有關鍵電影資訊的文字欄位。對於機器學習模型,特別是文字嵌入模型,提供結構化與資訊豐富的輸入非常重要。這裡使用f-string格式化將電影的標題、劇情概述、型別和加權評分整合為一個單一字串,並用明確的標籤(如"Title:"、“Overview:“等)分隔各部分資訊。這種格式不僅保留了所有重要資訊,還能讓模型更容易理解不同部分的含義和關係,為後續的嵌入處理提供更好的語義基礎。

4. 標記化處理

為了獲得更好的嵌入結果,我們需要對合併後的文字進行標記化處理:

import pandas as pd
import tiktoken
import os
import openai
from openai.embeddings_utils import get_embedding

openai.api_key = os.environ["OPENAI_API_KEY"]

embedding_encoding = "cl100k_base"  # text-embedding-ada-002使用的編碼
embedding_model = "text-embedding-ada-002"
max_tokens = 8000  # text-embedding-ada-002的最大值為8191

encoding = tiktoken.get_encoding(embedding_encoding)

# 排除過長無法嵌入的評論
md_final["n_tokens"] = md_final.combined_info.apply(lambda x: len(encoding.encode(x)))
md_final = md_final[md_final.n_tokens <= max_tokens]

這段程式碼處理文字標記化(tokenization),這是將文字轉換為模型可理解的數值表示的關鍵步驟。程式使用OpenAI的tiktoken函式庫和cl100k_base編碼器,這是為text-embedding-ada-002模型設計的標記器。該標記器能將文字分割成最多8,191個標記(token),但程式設定上限為8,000作為安全邊界。

每個標記大約代表4個字元或0.75個英文單詞。程式計算每條電影資訊包含多少標記,並過濾掉超過限制的記錄。這一步驟確保所有資料都能被embedding模型處理,避免因超出token限制而導致的錯誤。

關於cl100k_base標記器,它根據位元組對編碼(BPE)演算法,擁有100,000個標記的詞彙表,主要包含常見詞和詞片段,也包括標點符號、格式和控制的特殊標記。它能處理多種語言和領域的文字,為每個輸入編碼最多8,191個標記。

5. 生成文字嵌入

使用OpenAI的text-embedding-ada-002模型為電影概述生成嵌入向量:

md_final["embedding"] = md_final.overview.apply(lambda x: get_embedding(x, engine=embedding_model))

這行程式碼是整個推薦系統的核心之一。它呼叫OpenAI的API,使用text-embedding-ada-002模型為每部電影的概述文字生成嵌入向量。嵌入向量是高維空間中的點,能捕捉文字的語義訊息。相似內容的文字在向量空間中距離較近,這正是我們構建推薦系統的基礎。

當使用者描述他們想看什麼型別的電影時,我們可以將使用者的描述也轉換為嵌入向量,然後在向量空間中找到最接近的電影。這種方法能有效捕捉語義相似性,而不僅是關鍵字比對,使推薦更加人工智慧和準確。

在最終處理中,我們將欄位名稱進行調整,並將資料儲存為pickle格式:

md_final.rename(columns={'embedding': 'vector'}, inplace=True)
md_final.rename(columns={'combined_info': 'text'}, inplace=True)
md_final.to_pickle('movies.pkl')

6. 儲存到向量資料函式庫

資料準備完成後,我們需要將其儲存到向量資料函式庫中,以便高效地進行相似性搜尋。這裡我們使用LanceDB,一個專為向量搜尋而設計的開放原始碼資料函式庫:

import lancedb

uri = "data/sample-lancedb"
db = lancedb.connect(uri)
table = db.create_table("movies", md)

這段程式碼將處理好的電影資料存入LanceDB向量資料函式庫中。LanceDB是一個為向量搜尋最佳化的開放原始碼資料函式庫,提供持久化儲存和高效查詢功能。在推薦系統中,快速檢索相似向量是關鍵,傳統資料函式庫在這方面效能較差。

程式首先指定資料函式庫的儲存位置,然後建立連線,並建立一個名為"movies"的表格來儲存所有電影資料。LanceDB會自動識別資料框中的"vector"欄位作為向量資料,並建立適當的索引以加速相似性搜尋。這樣,當使用者提出查詢時,系統可以在毫秒級別內找到語義上最相似的電影。

構建冷啟動情境下的問答式推薦聊天機器人

完成資料準備後,我們可以開始構建推薦系統了。首先,讓我們處理冷啟動情境,即首次與使用者互動,沒有任何使用者歷史資料的情況。

冷啟動問題是推薦系統常見的難題:瞭解使用者的資訊越少,越難提供符合其偏好的推薦。在這一部分,我們將使用LangChain和OpenAI的大模型語言來模擬冷啟動情境。

實作的高層架構如下:

  1. 使用者提出查詢
  2. 系統將查詢轉換為嵌入向量
  3. 在向量資料函式庫中搜尋最相似的電影
  4. 回傳推薦結果

現在,讓我們開始構建這個鏈:

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import LanceDB
import os

os.environ["OPENAI_API_KEY"]
embeddings = OpenAIEmbeddings()
docsearch = LanceDB(connection=table, embedding=embeddings)

query = "I'm looking for an animated action movie. What could you suggest to me?"
docs = docsearch.similarity_search(query)
docs

這段程式碼實作了根據向量相似度的電影推薦功能。首先匯入必要的LangChain元件,設定OpenAI API金鑰,並初始化OpenAI的嵌入模型。然後建立一個LanceDB向量儲存介面,連線到我們之前建立的電影資料表。

核心部分是similarity_search方法,它處理以下步驟:

  1. 將使用者的查詢(“我在找一部動畫動作電影”)轉換為嵌入向量
  2. 在向量空間中計算這個查詢向量與所有電影向量的相似度(預設使用餘弦相似度)
  3. 回傳最相似的電影檔案列表

執行後,系統回傳了包含電影標題、概述、型別和評分的檔案列表,以及相似度分數。分數越低,表示距離越小,相似度越高。這種方法能夠理解查詢的語義,而不僅是關鍵字比對,因此即使用者的描述與資料函式庫中的文字不完全一致,也能找到相關的電影。

開發人工智慧電影推薦系統:結合向量搜尋與大模型語言

在當前的人工智慧浪潮中,推薦系統已經從簡單的根據規則或協同過濾演進至更加人工智慧化的形式。透過結合向量搜尋與大模型語言(LLM)的能力,我們能夠建立既理解內容又能以對話方式與使用者互動的推薦系統。本文將探討如何實作這樣一個人工智慧電影推薦系統。

從相似檔案到對話式回應

在向量搜尋的基礎上,我們可以進一步利用OpenAI的GPT模型將檢索結果轉化為自然的對話回應。這種方法不僅能夠提供相關推薦,還能以人性化的方式解釋推薦理由。

以下是如何結合LangChain的RetrievalQA與OpenAI的GPT模型:

qa = RetrievalQA.from_chain_type(
    llm=OpenAI(), 
    chain_type="stuff",
    retriever=docsearch.as_retriever(), 
    return_source_documents=True
)

query = "I'm looking for an animated action movie. What could you suggest to me?"
result = qa({"query": query})
print(result['result'])

這段程式碼建立了一個RetrievalQA鏈,將向量搜尋與語言模型結合起來。chain_type="stuff"表示將檢索到的所有檔案合併為一個上下文傳遞給語言模型。return_source_documents=True引數讓我們能夠取得支援推薦的原始檔案。

執行這段程式碼後,我們得到的回應是:

'I would suggest Transformers. It is an animated action movie with genres of Adventure, Science Fiction, and Action, and a rating of 6.'

這個回應非常自然,就像人類電影工作者給出的建議一樣。

探索推薦背後的來源檔案

讓我們看模型是根據哪些檔案做出的推薦:

result['source_documents'][0]

輸出結果顯示:

Document(page_content='Title: Hitman: Agent 47. Overview: An assassin teams up with a woman to help her find her father and uncover the mysteries of her ancestry. Genres: Action, Crime, Thriller. Rating: 5.365800865800866', metadata={'genres': array(['Action', 'Crime', 'Thriller'], dtype=object), 'title': 'Hitman: Agent 47', 'overview': 'An assassin teams up with a woman to help her find her father and uncover the mysteries of her ancestry.', 'weighted_rate': 5.365800865800866, 'n_tokens': 52, 'vector': array([-0.00566491, -0.01658553, -0.02255735, ..., -0.01242317, -0.01303058, -0.00709073], dtype=float32), '_distance': 0.42414575815200806})

有趣的是,模型推薦的電影(Transformers)與第一個回傳的檔案(Hitman: Agent 47)不一致。這說明語言模型不僅依賴相似度最高的結果,而是綜合考慮了多個因素,包括使用者的具體需求(動畫電影)和評分等因素。這正是LLM強大之處——它能夠權衡多種因素,做出更符合使用者期望的推薦。

豐富推薦系統的考量因素

語言模型只使用了部分可用訊息(主要是文字概述)來做出推薦。如何讓我們的MovieHarbor系統更充分地利用所有可用資料?我發現有兩種主要方法:

1. 過濾方式

這種方法在查詢前增加過濾條件,例如只考慮特定型別的電影。以下是如何過濾只包含喜劇型別的電影:

df_filtered = md[md['genres'].apply(lambda x: 'Comedy' in x)]
qa = RetrievalQA.from_chain_type(
    llm=OpenAI(), 
    chain_type="stuff",
    retriever=docsearch.as_retriever(search_kwargs={'data': df_filtered}),
    return_source_documents=True
)

query = "I'm looking for a movie with animals and an adventurous plot."
result = qa({"query": query})

在這段程式碼中,我們首先建立了一個只包含喜劇電影的過濾資料集df_filtered,然後將其作為檢索器的搜尋引數。這樣系統在推薦時只會考慮喜劇型別的電影,即使用者沒有明確要求喜劇片。

我們也可以在中繼資料級別上進行過濾,例如只推薦評分高於7分的電影:

qa = RetrievalQA.from_chain_type(
    llm=OpenAI(), 
    chain_type="stuff",
    retriever=docsearch.as_retriever(search_kwargs={'filter': {'weighted_rate__gt': 7}}),
    return_source_documents=True
)

這段程式碼使用了filter引數來指定評分條件。weighted_rate__gt: 7表示只回傳加權評分大於7的電影。這種方法特別適合當你有明確的品質閾值要求時。

2. 代理方式

這是一種更創新的方法,將檢索器轉換為代理可以利用的工具。這使用者能夠以自然語言表達偏好,由代理決定何時以及如何使用檢索工具。

from langchain.agents.agent_toolkits import create_retriever_tool
from langchain.agents.agent_toolkits import create_conversational_retrieval_agent
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature=0)
retriever = docsearch.as_retriever(return_source_documents=True)

tool = create_retriever_tool(
    retriever,
    "movies",
    "Searches and returns recommendations about movies."
)
tools = [tool]
agent_executor = create_conversational_retrieval_agent(llm, tools, verbose=True)
result = agent_executor({"input": "suggest me some action movies"})

這段程式碼使用了LangChain的代理框架。首先建立了一個檢索工具tool,然後使用create_conversational_retrieval_agent函式建立一個能夠使用該工具的代理。verbose=True引數讓我們能夠看到代理的思考過程。

當執行這段程式碼時,代理會首先分析使用者請求,確定需要使用電影檢索工具,然後呼叫該工具取得相關電影,最後生成一個自然的回應。

執行結果如下:

> Entering new AgentExecutor chain...
Invoking: `movies` with `{'genre': 'action'}`
[Document(page_content='The action continues from [REC], [...] 
Here are some action movies that you might enjoy:
1. [REC]² - The action continues from [REC], with a medical officer and a SWAT team sent into a sealed-off apartment to control the situation. It is a thriller/horror movie.
2. The Boondock Saints - Twin brothers Conner and Murphy take swift retribution into their own hands to rid Boston of criminals. It is an action/thriller/crime movie.
3. The Gamers - Four clueless players are sent on a quest to rescue a princess and must navigate dangerous forests, ancient ruins, and more. It is an action/comedy/thriller/foreign movie.
4. Atlas Shrugged Part III: Who is John Galt? - In a collapsing economy, one man has the answer while others try to control or save him. It is a drama/science fiction/mystery movie.
Please note that these recommendations are based on the genre "action" and may vary in terms of availability and personal preferences.
> Finished chain.

最佳化推薦系統的提示工程

為了讓我們的推薦系統更加貼合目標,進行一些提示工程是必要的。在覆寫現有提示範本前,最好先了解LangChain預設的範本是什麼:

print(qa.combine_documents_chain.llm_chain.prompt.template)

輸出結果為:

Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question} Helpful Answer:

現在,我們可以設計一個更符合電影推薦系統需求的提示範本。例如,我們希望系統每次回應都提供三個電影建議,包括簡短的劇情描述和推薦理由:

from langchain.prompts import PromptTemplate

template = """You are a movie recommender system that help users to find movies that match their preferences.
Use the following pieces of context to answer the question at the end. For each question, suggest three movies, with a short description of the plot and the reason why the user migth like it.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question} Your response:"""

PROMPT = PromptTemplate(
    template=template, input_variables=["context", "question"])

這個提示範本明確指定了系統的角色(電影推薦系統)和回應格式(三部電影,每部包含劇情描述和推薦理由)。input_variables指定了範本中的變數,它們將在執行時被實際值替換。

接下來,我們將這個新的提示範本應用到我們的RetrievalQA鏈中:

chain_type_kwargs = {"prompt": PROMPT}
qa = RetrievalQA.from_chain_type(
    llm=OpenAI(),
    chain_type="stuff",
    retriever=docsearch.as_retriever(),
    return_source_documents=True,
    chain_type_kwargs=chain_type_kwargs
)

query = "I'm looking for a funny action movie, any suggestion?"
result = qa({'query': query})
print(result['result'])

透過chain_type_kwargs引數,我們將自定義的提示範本傳遞給RetrievalQA鏈。這樣,當系統生成回應時,就會遵循我們指定的格式和指導原則。

執行這段程式碼,我們得到如下結果:

1. A Good Day to Die Hard: An action-packed comedy directed by John Moore, this movie follows Iconoclastic, take-no-prisoners cop John McClane as he travels to Moscow to help his wayward son Jack. With the Russian underworld in pursuit, and battling a countdown to war, the two McClanes discover that their opposing methods make them unstoppable heroes. You might enjoy this film for its blend of intense action sequences and humorous father-son dynamics.

這個回應完美地遵循了我們設定的格式,提供了電影標題、劇情描述和推薦理由。

推薦系統的進階最佳化

在實際應用中,我發現推薦系統還可以進一步最佳化:

  1. 多模態訊息整合:除了文字描述,還可以考慮整合電影海報、預告片段等視覺訊息,提供更全面的推薦體驗。

  2. 使用者偏好學習:隨著使用者與系統互動增多,可以逐步建立使用者偏好模型,實作個人化推薦。

  3. 解釋性增強:進一步最佳化提示範本,讓系統不僅推薦電影,還能解釋推薦背後的邏輯,增加透明度。

  4. 多輪對話最佳化:改進代理系統,支援多輪對話,讓使用者能夠在對話中逐步精煉自己的需求。

  5. 評價反饋機制:加入使用者對推薦結果的評價反饋環節,持續最佳化推薦品質。

實作考量與最佳實踐

在實作類別似的推薦系統時,有幾個關鍵點需要注意:

  1. 向量品質:嵌入向量的品質直接影響推薦相關性,選擇適合的嵌入模型至關重要。

  2. 提示工程:精心設計的提示可以大幅提升LLM的輸出品質,值得投入時間最佳化。

  3. 計算效率:對於大型資料集,需要考慮向量搜尋的效率問題,可能需要使用ANN(近似最近鄰)演算法或專用向量資料函式庫。

  4. 資料更新機制:考慮如何處理新增電影的嵌入生成和索引更新。

  5. 錯誤處理:增加適當的錯誤處理機制,確保即使在LLM暫時不可用的情況下,系統也能提供基本的推薦功能。

在開發過程中,我發現逐步迭代是最佳策略——從基本功能開始,然後根據使用者反饋和系統表現不斷最佳化。這種方法使我們能夠在保持系統可靠性的同時,逐步提升其人工智慧性和使用者經驗。