雖然TF-IDF是一個很好的起點,但在實際應用中,我更傾向於使用更先進的向量表示方法,如詞嵌入(Word Embeddings)或句子嵌入(Sentence Embeddings)。這些方法能更好地捕捉語意關係,提供更精確的相似度比對。
詞嵌入與句子嵌入
詞嵌入如Word2Vec、GloVe或FastText能將詞對映到連續向量空間,捕捉詞之間的語意關係。句子嵌入則進一步將整個句子或段落對映到向量空間,如Universal Sentence Encoder或SBERT(Sentence-BERT)。
使用SBERT進行句子嵌入的簡單實作:
from sentence_transformers import SentenceTransformer
# 載入預訓練模型
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
# 生成句子嵌入
embeddings = model.encode(documents)
# 計算餘弦相似度
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(embeddings)
這段程式碼使用Sentence-BERT(SBERT)模型生成句子嵌入。SBERT是根據BERT的變體,專為句子相似度任務最佳化。首先載入預訓練的’paraphrase-MiniLM-L6-v2’模型,這是一個輕量級但效能良好的模型。然後使用model.encode()函式將檔案列表轉換為向量表示,每個檔案被對映到一個固定維度的稠密向量。最後計算這些向量間的餘弦相似度。與TF-IDF相比,SBERT能更好地捕捉語意關係,特別是在處理同義詞、多義詞和語境相關性時。這種方法在實際應用中通常能提供更準確的相似度比對結果。
上下文感知嵌入
最新的預訓練語言模型如BERT、RoBERTa或GPT系列能生成上下文感知的嵌入,即同一個詞在不同上下文中會有不同的向量表示。這對於捕捉微妙的語意差異非常有價值。
例如,“bank"一詞在"river bank"和"bank account"中有完全不同的含義,上下文感知嵌入能有效區分這些情況。
人工智慧代理記憶系統的實際挑戰與解決方案
在實作人工智慧代理記憶系統時,我遇到了幾個常見挑戰,分享一些解決方案:
檢索相關性與多樣性平衡
純粹根據相似度的檢索可能導致結果過於相似,缺乏多樣性。我採用了一種改進的檢索策略,在選擇最相似項的同時,也考慮結果集的多樣性:
def diverse_retrieval(query_vector, memory_vectors, top_k=5, diversity_factor=0.3):
"""檢索相關與多樣的記憶"""
similarities = cosine_similarity([query_vector], memory_vectors)[0]
selected_indices = []
# 選擇最相似的項
best_index = np.argmax(similarities)
selected_indices.append(best_index)
# 迭代選擇其餘項
while len(selected_indices) < top_k:
# 計算候選項與已選項的最大相似度
max_similarities = []
for i in range(len(similarities)):
if i in selected_indices:
max_similarities.append(float('inf')) # 已選項不再考慮
continue
# 與已選項的最大相似度
sim_to_selected = max([cosine_similarity([memory_vectors[i]], [memory_vectors[j]])[0][0]
for j in selected_indices])
max_similarities.append(sim_to_selected)
# 結合相關性與多樣性
scores = similarities - diversity_factor * np.array(max_similarities)
next_index = np.argmax(scores)
selected_indices.append(next_index)
return selected_indices
這個函式實作了一種平衡相關性與多樣性的檢索策略。首先計算查詢向量與所有記憶向量的相似度,然後選擇最相似的項作為第一個結果。接下來,迭代選擇剩餘結果時,不僅考慮與查詢的相似度,還考慮與已選項的差異度。對於每個候選項,計算它與所有已選項的最大相似度,表示其與已選集合的重疊程度。然後使用diversity_factor引數調整多樣性的重要性,從相似度分數中減去加權後的重疊度,得到綜合分數。每次迭代選擇綜合分數最高的項,確保結果既相關又多樣。這種方法在實踐中能提供更全面的記憶檢索結果。
記憶檢索效率最佳化
隨著記憶量增長,暴力計算所有向量相似度變得低效。在大規模應用中,我使用了近似最近鄰搜尋演算法如FAISS或Annoy來加速檢索:
import faiss
def build_faiss_index(vectors, dimension):
"""建立FAISS索參照於高效相似度搜尋"""
index = faiss.IndexFlatL2(dimension) # L2距離索引
vectors = np.array(vectors).astype('float32')
index.add(vectors)
return index
def search_memories(query_vector, index, k=5):
"""使用FAISS索引搜尋最相似的記憶"""
query_vector = np.array([query_vector]).astype('float32')
distances, indices = index.search(query_vector, k)
return indices[0]
這段程式碼展示瞭如何使用FAISS(Facebook AI Similarity Search)函式庫來加速向量相似度搜尋。FAISS是一個專為高效大規模向量搜尋而設計的函式庫。build_faiss_index函式建立一個根據L2距離的FAISS索引,將浮點數型向量增加到索引中。search_memories函式接受一個查詢向量,使用索引快速搜尋k個最相似的記憶。FAISS支援多種索引型別,包括精確搜尋和近似搜尋,後者可以在稍微犧牲準確性的情況下大幅提升搜尋速度。在記憶系統中使用FAISS可以顯著提高檢索效率,特別是當記憶量達到數十萬或更多時。這種最佳化對於實時互動的人工智慧代理至關重要。
記憶一致性維護
人工智慧代理可能會從對話中取得相互矛盾的訊息。我實作了一個記憶一致性檢查機制,識別並解決潛在的矛盾:
def check_memory_consistency(new_memory, existing_memories, threshold=0.7):
"""檢查新記憶與現有記憶的一致性"""
contradictions = []
# 將新記憶向量化
new_memory_vector = vectorize_memory(new_memory)
# 尋找相似但可能矛盾的記憶
for memory in existing_memories:
memory_vector = vectorize_memory(memory)
similarity = cosine_similarity([new_memory_vector], [memory_vector])[0][0]
# 如果相似度高,進一步檢查是否存在矛盾
if similarity > threshold:
if contains_contradiction(new_memory, memory):
contradictions.append(memory)
if contradictions:
resolved_memory = resolve_contradictions(new_memory, contradictions)
return resolved_memory
else:
return new_memory
這個函式實作了記憶一致性檢查機制。當加入新記憶時,首先將其向量化,然後尋找現有記憶中與之相似度高於閾值的項。對於這些相似項,使用contains_contradiction函式(未展示)進一步檢查是否存在實質性矛盾,如偏好的變化或事實陳述的不一致。如果發現矛盾,則呼叫resolve_contradictions函式(未展示)解決這些矛盾。解決方法可能包括保留最新訊息、標記不確定性或生成綜合表示。vectorize_memory函式可以使用前面介紹的任何向量化方法。這種機制有助於維護人工智慧代理記憶的一致性,避免因矛盾訊息導致的混亂回應。
結合向量搜尋與大模型語言
向量相似度搜尋與大模型語言的結合為人工智慧代理提供了強大的能力。在我的實踐中,我發現以下結合方式特別有效:
動態提示工程
根據檢索到的記憶動態構建提示,引導大模型語言生成更相關、更個人化的回應:
def create_dynamic_prompt(user_query, retrieved_memories, system_prompt):
"""根據檢索到的記憶建立動態提示"""
memories_text = "\n".join([f"- {memory}" for memory in retrieved_memories])
prompt = f"""{system_prompt}
使用者的過往往相關記憶:
{memories_text}
請根據以上記憶和使用者的當前查詢提供回應。確保回應與使用者的歷史互動保持一致。
使用者查詢: {user_query}
"""
return prompt
這個函式建立了一個動態提示,將系統提示、檢索到的記憶和使用者當前查詢整合在一起。系統提示通常包含人工智慧代理的角色定義和行為。檢索到的記憶以列表形式呈現,提供相關的歷史上下文。最後加入使用者的當前查詢,指導大模型語言生成回應。這種動態提示工程方法使模型能夠參考相關的歷史訊息,同時專注於當前查詢,生成連貫與個人化的回應。在實踐中,可以根據具體應用場景和模型特性調整提示的結構和內容,以獲得最佳效果。
記憶重要性評估
不是所有檢索到的記憶都同等重要,我使用大模型語言來評估記憶的相關性和重要性:
def evaluate_memory_importance(query, retrieved_memories, llm_client):
"""使用LLM評估記憶的重要性"""
prompt = f"""請評估以下記憶對回答使用者查詢的重要性,為每項分配1-10的分數。
使用者查詢: {query}
記憶:
"""
for i, memory in enumerate(retrieved_memories):
prompt += f"{i+1}. {memory}\n"
prompt += "\n請為每項記憶評分(1-10),並簡要說明原因。格式為 '專案編號: 分數 - 原因'"
response = llm_client.generate(prompt)
# 解析回應提取分數
importance_scores = parse_importance_scores(response)
# 根據重要性排序記憶
sorted_memories = [mem for _, mem in sorted(
zip(importance_scores, retrieved_memories),
key=lambda pair: pair[0],
reverse=True
)]
return sorted_memories
這個函式利用大模型語言(LLM)評估檢索到的記憶對當前查詢的重要性。首先建構一個提示,要求模型為每條記憶分配1-10的重要性分數。llm_client.generate()函式呼叫LLM API取得評估結果。然後使用parse_importance_scores函式(未展示)從回應中提取分數,該函式可以使用正規表示式或其他文書處理方法實作。最後,根據重要性分數對記憶進行排序,確保最重要的記憶優先考慮。這種方法結合了向量搜尋的效率和LLM的理解能力,提供了更人工智慧的記憶篩選機制。在實際應用中,這可以顯著提高回應的相關性和品質。
在人工智慧代理開發過程中,向量相似度搜尋技術為構建有效的記憶與知識系統提供了堅實基礎。從基本的TF-IDF和餘弦相似度,到高階的句子嵌入和近似搜尋演算法,再到與大模型語言的創新結合,這些技術共同推動了人工智慧代理能力的不斷提升。
透過精心設計的記憶系統,人工智慧代理能夠保持對話的連貫性,展現個人化的互動體驗,並隨著時間的推移不斷學習和適應。隨著技術的發展,我們可以期待更加人工智慧、自然的人機互動體驗。
在實際應用中,記得平衡技術複雜性與系統效能,選擇適合特定場景的方法。最重要的是,始終將使用者經驗放在首位,讓技術真正服務於人類需求。
向量相似度與檔案檢索:從理論到實踐
在現代資訊檢索系統中,向量相似度計算是許多應用的核心技術。無論是搜尋引擎、推薦系統或是人工智慧助理,都需要有效判斷文字間的語義相似性。在研究這些技術時,我發現從基礎的向量表示到高階的嵌入模型,每一步都對構建有效的檢索系統至關重要。
檔案向量化與相似度計算
當我們談論檔案相似度時,首先需要解決的問題是如何將文字轉換為電腦可以計算的數值形式。這個過程稱為向量化(Vectorization)。
向量化方法的選擇直接影響了系統判斷檔案間語義相似度的能力。一個簡單的例子是計算「The sky is blue and beautiful.」與其他檔案的相似度。透過向量表示後,我們可以使用餘弦相似度(Cosine Similarity)來量化這種關係。
在實際工作中,我常發現向量化方法的選擇對最終的語義相似度判斷有決定性影響。接下來,讓我們探討如何儲存這些向量並進行相似度搜尋。
向量資料函式庫與相似度搜尋實作
向量化檔案後,下一步是將這些向量儲存在向量資料函式庫中,以便後續的相似度搜尋。在這裡,我將展示如何使用Python實作一個簡單的向量資料函式庫。
根據TF-IDF的向量資料函式庫實作
以下是一個根據TF-IDF(詞頻-逆檔案頻率)的向量資料函式庫實作:
# 使用TF-IDF向量化檔案並建立簡易向量資料函式庫
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# 假設我們有一系列檔案
documents = [
"The sky is blue and beautiful.",
"Love this blue and beautiful sky!",
"The quick brown fox jumps over the lazy dog.",
# 更多檔案...
]
# 使用TF-IDF向量化檔案
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
vector_database = X.toarray()
# 定義相似度搜尋函式
def cosine_similarity_search(query, database, vectorizer, top_n=5):
query_vec = vectorizer.transform([query]).toarray()
similarities = cosine_similarity(query_vec, database)[0]
top_indices = np.argsort(-similarities)[:top_n] # 取得前n個最相似的索引
return [(idx, similarities[idx]) for idx in top_indices]
# 主要搜尋迴圈
while True:
query = input("Enter a search query (or 'exit' to stop): ")
if query.lower() == 'exit':
break
top_n = int(input("How many top matches do you want to see? "))
search_results = cosine_similarity_search(query, vector_database, vectorizer, top_n)
print("Top Matched Documents:")
for idx, score in search_results:
print(f"- {documents[idx]} (Score: {score:.4f})")
print("\n")
這段程式碼展示了一個基礎的向量搜尋實作。首先,使用TF-IDF向量化器將文字轉換為數值向量。TF-IDF是一種常用的文字向量化方法,它考慮了詞在檔案中的頻率以及在整個檔案集中的稀有程度。
cosine_similarity_search
函式接受查詢文字,將其轉換為向量,然後計算與資料函式庫中所有向量的餘弦相似度。最後,它回傳相似度最高的前N個檔案及其分數。
當執行這段程式碼並輸入「blue」作為查詢時,系統會回傳包含「blue」或語義相關詞彙的檔案,如下所示:
Enter a search query (or 'exit' to stop): blue
How many top matches do you want to see? 3
Top Matched Documents:
- The sky is blue and beautiful. (Score: 0.4080)
- Love this blue and beautiful sky! (Score: 0.3439)
- The brown fox is quick and the blue dog is lazy! (Score: 0.2560)
這種根據TF-IDF的搜尋雖然簡單,但存在明顯侷限性:它主要依賴詞彙頻率,無法真正理解詞彙間的語義關係。例如,它可能無法識別「azure」與「blue」的語義相似性,因為它們是不同的詞彙。
深入理解檔案嵌入技術
TF-IDF雖然是一種嘗試捕捉檔案語義的方法,但它僅根據詞頻統計,無法理解詞彙之間的關係。現代方法使用檔案嵌入(Document Embedding)技術,這種向量化方法能更好地保留檔案的語義含義。
嵌入網路的工作原理
嵌入網路是透過在大型資料集上訓練神經網路來構建的,它能將詞彙、句子或檔案對映到高維向量空間,根據上下文和資料中的關係捕捉語義和語法關係。在實際應用中,我們通常使用預訓練模型來生成嵌入向量,這些模型可以從Hugging Face或OpenAI等來源取得。
使用OpenAI進行檔案嵌入
接下來,我將展示如何使用OpenAI的嵌入模型來處理檔案,並透過降維技術將結果視覺化:
import os
from openai import OpenAI
import numpy as np
from sklearn.decomposition import PCA
from dotenv import load_dotenv
# 載入環境變數
load_dotenv()
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError("No API key found. Please check your .env file.")
client = OpenAI(api_key=api_key)
def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return client.embeddings.create(input=[text],
model=model).data[0].embedding
# 假設檔案集合
documents = [
"The sky is blue and beautiful.",
"Love this blue and beautiful sky!",
"The quick brown fox jumps over the lazy dog.",
# 更多檔案...
]
# 為每個檔案生成嵌入向量
embeddings = [get_embedding(doc) for doc in documents]
embeddings_array = np.array(embeddings)
# 使用PCA降維以便視覺化
pca = PCA(n_components=3)
reduced_embeddings = pca.fit_transform(embeddings_array)
# 此時可以使用matplotlib等工具將reduced_embeddings視覺化
這段程式碼使用OpenAI的嵌入模型將檔案轉換為高維向量。當使用OpenAI的text-embedding-ada-002
模型時,每個檔案會被轉換為1536維的向量。
由於我們無法直接視覺化這麼高維度的資料,程式使用主成分析(PCA)技術將向量降至3維,這樣就可以在三維空間中繪製出來。在降維後的空間中,語義相似的檔案會被聚集在一起。
這種根據嵌入的方法比TF-IDF更能捕捉檔案的語義訊息,因為它不僅考慮詞頻,還理解詞彙之間的關係。例如,它能夠識別「sky」與「heaven」或「blue」與「azure」之間的語義關聯。
在實際專案中,我發現OpenAI的嵌入模型在一般語義相似性任務中表現最佳,這使得它成為大多數記憶和檢索應用的標準選擇。
使用ChromaDB實作實用的檔案查詢系統
瞭解了檔案嵌入的基本原理後,讓我們整合所有元件,構建一個使用本地向量資料函式庫ChromaDB的完整範例。ChromaDB是一個優秀的本地向量儲存解決方案,適用於開發或小型專案。
根據ChromaDB的檔案查詢系統
import chromadb
from openai import OpenAI
import os
from dotenv import load_dotenv
# 載入API金鑰
load_dotenv()
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError("No API key found. Please check your .env file.")
client = OpenAI(api_key=api_key)
def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return client.embeddings.create(input=[text],
model=model).data[0].embedding
# 範例檔案
documents = [
"The sky is blue and beautiful.",
"Love this blue and beautiful sky!",
"The quick brown fox jumps over the lazy dog.",
"The dog is lazy but the brown fox is quick!",
"The brown fox is quick and the blue dog is lazy!",
# 更多檔案...
]
# 生成嵌入向量並分配ID
embeddings = [get_embedding(doc) for doc in documents]
ids = [f"id{i}" for i in range(len(documents))]
# 建立ChromaDB客戶端和集合
chroma_client = chromadb.Client()
collection = chroma_client.create_collection(name="documents")
# 將檔案嵌入增加到集合中
collection.add(
embeddings=embeddings,
documents=documents,
ids=ids
)
# 定義查詢函式
def query_chromadb(query, top_n=2):
query_embedding = get_embedding(query)
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_n
)
return [(id, score, text) for id, score, text in
zip(results['ids'][0],
results['distances'][0],
results['documents'][0])]
# 主要查詢迴圈
while True:
query = input("Enter a search query (or 'exit' to stop): ")
if query.lower() == 'exit':
break
top_n = int(input("How many top matches do you want to see? "))
search_results = query_chromadb(query, top_n)
print("Top Matched Documents:")
for id, score, text in search_results:
print(f"ID:{id} TEXT: {text} SCORE: {round(score, 2)}")
print("\n")
這段程式碼展示瞭如何使用ChromaDB實作一個完整的向量搜尋系統。首先,使用OpenAI的API為每個檔案生成嵌入向量。然後,建立一個ChromaDB集合並將檔案、嵌入向量和ID增加到其中。
query_chromadb
函式接受查詢文字,生成其嵌入向量,然後在ChromaDB中搜尋最相似的檔案。值得注意的是,ChromaDB回傳的是距離分數而非相似度分數。餘弦距離的計算公式為:
餘弦距離(A,B) = 1 - 餘弦相似度(A,B)
這意味著餘弦距離的範圍是0(最相似)到2(語義上完全相反)。
當執行此程式碼並輸入「dogs are lazy」作為查詢時,系統會回傳語義上最相關的檔案:
Enter a search query (or 'exit' to stop): dogs are lazy
How many top matches do you want to see? 3
Top Matched Documents:
ID:id7 TEXT: The dog is lazy but the brown fox is quick! SCORE: 0.24
ID:id5 TEXT: The brown fox is quick and the blue dog is lazy! SCORE: 0.28
ID:id2 TEXT: The quick brown fox jumps over the lazy dog. SCORE: 0.29
這個結果清楚地展示了根據嵌入的搜尋如何捕捉查詢的語義含義,而不僅是關鍵字比對。即使查詢使用了「dogs」(複數形式),系統仍能找到包含「dog」(單數形式)的相關檔案。