將文本轉換為向量(文本嵌入)是自然語言處理中的核心任務,有許多大模型可以完成這項工作。上一篇文章《構建AI智能體:十五、超越關鍵詞搜索:向量數據庫如何解鎖語義理解新紀元》我們是通過阿里雲的api調用的text-embedding-v4模型,同樣還有很多其他輕量級的模型可以很好的完成這個任務,我們今天找兩個結合前期講到的本地化部署來嘗試一下。
一、核心組件回顧
- ModelScope(魔搭):阿里雲推出的模型即共享平台,定位為中國版的“Hugging Face”,優勢是豐富的預訓練中文模型、國內下載速度快、阿里達摩院支持
- 向量:在AI和機器學習領域,向量是一組數字的有序列表,可以表示任何數據對象(如一段文字、一張圖片)在高維空間中的位置。
- 嵌入(Embedding):通過AI模型(如BERT、CNN、CLIP等)將非結構化數據轉換為向量的過程,稱為“嵌入”。這個轉換過程捕獲了數據的深層語義特徵。
- 向量數據庫:是一種專門用於存儲、索引和查詢高維向量的數據庫。它的核心功能是執行近似最近鄰(ANN)搜索,即快速找到與查詢向量最相似的向量集合。
二、從文本到向量檢索的完整流程
- 核心步驟分解:
- 文本預處理
- 本地嵌入模型加載與推理
- 向量生成與規範化
- 向量存儲與索引構建
- 查詢處理與相似性搜索
三、句子嵌入模型介紹
1. paraphrase-MiniLM-L6-v2模型
paraphrase-MiniLM-L6-v2 是 SentenceTransformers 庫中一個非常受歡迎且高效的句子嵌入模型。它的名字可以拆解來看:
- paraphrase: 意味着它在“釋義”(paraphrase)任務上進行了優化訓練。即,它非常擅長判斷兩個句子是否語義相同但表述不同。
- MiniLM: 指其基於 MiniLM 架構。這是一種通過知識蒸餾技術,將大型語言模型(如 BERT、RoBERTa)的知識壓縮到更小模型中的方法。它保留了老師模型的大部分性能,但體積更小、速度更快。
- L6: 代表這個模型有 6 層(Layer)。作為對比,原始的 bert-base 模型有 12 層。層數減少是模型變輕量的主要原因之一。
- v2: 代表版本號。
核心特點
- 輕量且高效:
- 參數量: 約 22.7 million(2270萬)個參數。
- 體積: 模型文件大約 90 MB。
- 速度: 由於其小巧的尺寸,它在 CPU 和 GPU 上都能進行非常快速的推理(編碼句子為向量),非常適合對延遲敏感的應用。
- 高質量的語義表示:
- 儘管模型很小,但得益於 MiniLM 蒸餾技術,它在語義相似度任務上的表現非常出色,甚至可以媲美一些更大的模型(如基於 BERT-base 的模型)。
- 它生成的句子向量能夠很好地捕獲句子的核心語義信息。
- 通用性強:雖然是為“釋義”任務優化的,但其生成的通用句子向量在多種下游任務中都有良好表現,例如:
- 語義搜索(Semantic Search)
- 信息檢索(Information Retrieval)
- 聚類(Clustering)
- 文本分類(Text Classification)的特徵輸入
- 重複數據刪除(Deduplication)
- 輸出維度:
- 它將任何長度的文本轉換為一個固定大小的 384 維的浮點數向量。
2. all-MiniLM-L6-v2 模型
all-MiniLM-L6-v2 是 SentenceTransformers 庫中的一個通用文本嵌入模型。它的名字是其核心特徵的縮寫:
- all: 意味着它是一個通用模型,旨在對各種類型的文本和任務都能產生高質量的向量表示,而不僅僅是為某個特定任務(如釋義)優化的。
- 其他屬性同上
核心特點:
- 輕量級與高性能的完美平衡
- 參數量: 約 22.7 million(2270萬)。
- 體積: 模型文件僅約 90 MB。非常易於下載、存儲和部署。
- 速度: 在 CPU 和 GPU 上都能進行極快的推理(編碼)。這對於需要實時或批量處理大量文本的應用(如搜索、推薦)至關重要。
- 性能: 儘管模型很小,但得益於高效的蒸餾技術,它在許多標準基準測試中的表現可以媲美甚至超越一些更大的模型(如原始的 BERT-base)。
- 通用性強,它被訓練用於捕捉句子的通用語義信息,因此在多種下游任務中都有良好表現,包括:
- 語義搜索: 找到與查詢語句語義最相關的文檔。
- 文本聚類: 將語義相似的句子或文檔自動分組。
- 信息檢索: 增強傳統關鍵詞搜索的能力。
- 文本分類與語義相似度計算: 作為機器學習模型的輸入特徵。
- 檢索增強生成: 為 LLM 從知識庫中檢索相關上下文。
- 輸出標準化
- 它將任何長度的文本轉換為一個固定大小的 384 維的浮點數向量。這個維度在存儲效率和處理速度之間取得了很好的平衡。
四、詳細示例
1. 加載本地大模型的起因
今天在加載嵌入模型時,提示了異常OSError: We couldn't connect to 'https://huggingface.co' to load this file,大模型無法下載,提示是要從huggingface中下載模型,由於網絡限制無法下載,於是考慮ModelScope中是否也可以找到此模型
運行代碼:
from sentence_transformers import SentenceTransformer
# 1. 準備示例文檔
documents = [
"這是一個測試文檔",
"我喜歡大模型做任務",
]
# 2. 加載嵌入模型(將文本轉換為向量)
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
# 3. 生成文檔向量
document_embeddings = model.encode(documents)
print(document_embeddings)
提示異常:
基於此,做其他方式的考慮,從ModelScope來加載模型!
2. 使用 sentence-transformers 庫調用模型示例
參考代碼:
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
# 1. 加載模型(首次運行時會自動從Hugging Face Hub下載)
# model = SentenceTransformer('all-MiniLM-L6-v2')
# 此處我們配置為從本地路徑加載
model = SentenceTransformer('D:/modelscope/hub/models/sentence-transformers/all-MiniLM-L6-v2')
# 2. 準備句子
sentences = [
"The weather is nice today!",
"It's so sunny outside.",
"Dogs are great companions.",
"Cats are independent pets."
]
# 3. 編碼句子,生成向量
embeddings = model.encode(sentences)
print(f"向量形狀: {embeddings.shape}") # 輸出: (4, 384)
# 4. 計算相似度(例如,計算第1句與所有句子的相似度)
similarities = cosine_similarity([embeddings[0]], embeddings)
print(f"相似度分數: {similarities}")
輸出結果:
向量形狀: (4, 384)
相似度分數: [[0.9999999 0.5719301 0.16580734 0.00947747]]
3. 模型先下載後加載,再執行向量化操作
先判斷本地是否存在模型是否存在,存在就直接加載,不存在則通過modelscope下載到本地,然後生成文檔向量,創建FAISS索引,進行查找,最後返回結果;
代碼示例:
import os
from sentence_transformers import SentenceTransformer
from modelscope import snapshot_download
from pathlib import Path
import faiss
# 1. 準備示例文檔
documents = [
"貓是一種常見的寵物,喜歡抓老鼠",
"我喜歡寫代碼",
"狗是人類最好的朋友,忠誠可愛",
"Python是一種流行的編程語言,簡單易學",
"人工智能正在改變世界,機器學習是其中重要部分",
"太陽系有八大行星,地球是其中之一"
]
def load_model(model_name='damo/nlp_corom_sentence-embedding_chinese-base', local_path=None):
"""
加載SentenceTransformer模型,優先使用ModelScope模型
參數:
model_name: ModelScope模型名稱或本地路徑
local_path: 可選的本地模型路徑
"""
try:
if local_path and os.path.exists(local_path):
# 從本地路徑加載模型
print(f"從本地路徑加載模型: {local_path}")
model = SentenceTransformer(local_path+"/"+model_name)
else:
# 從ModelScope加載模型(會自動下載如果不存在)
print(f"加載ModelScope模型: {model_name}")
print("如果本地沒有緩存,將自動從ModelScope下載...")
# 使用ModelScope下載模型到緩存目錄
model_dir = snapshot_download(model_name)
print(f"模型已下載到: {model_dir}")
# 從下載的目錄加載模型
model = SentenceTransformer(model_dir)
print("模型加載成功!")
return model
except Exception as e:
print(f"模型加載失敗: {e}")
return None
def download_model_to_path(model_name, save_path):
"""
將ModelScope模型下載到指定路徑
參數:
model_name: ModelScope模型名稱
save_path: 保存路徑
"""
try:
# 確保保存目錄存在
Path(save_path).mkdir(parents=True, exist_ok=True)
# 使用ModelScope下載模型到指定路徑
print(f"下載ModelScope模型 {model_name} 到 {save_path}")
model_dir = snapshot_download(model_name, cache_dir=save_path)
# 加載模型
model = SentenceTransformer(model_dir)
print("模型下載並保存成功!")
return model
except Exception as e:
print(f"模型下載失敗: {e}")
return None
# 示例使用
if __name__ == "__main__":
# 設置一些常用的中文模型選項
model_options = {
"base": "sentence-transformers/paraphrase-MiniLM-L6-v2"
}
# 2. 加載嵌入模型(將文本轉換為向量)
# 方式1: 直接加載基礎中文模型(自動下載或使用緩存)
# print("=== 方式1: 直接加載中文模型 ===")
# model1 = load_model(model_options["base"])
# if model1:
# # 測試模型
# embeddings = model1.encode(["這是一個測試句子"])
# print(f"生成的向量維度: {embeddings.shape}")
# 方式2: 下載到指定路徑
print("\n=== 方式2: 下載模型到指定路徑 ===")
model_path = "D:\modelscope\hub\models"
model2 = download_model_to_path(model_options["base"], model_path)
# 方式3: 從本地路徑加載
print("\n=== 方式3: 從本地路徑加載模型 ===")
model3 = load_model(model_name=model_options["base"],local_path=model_path)
# # 測試其他模型
# print("\n=== 測試小型中文模型 ===")
# small_model = load_model(model_options["base"])
if model3:
# 3. 生成文檔向量
document_embeddings = model3.encode(documents)
# 4. 創建FAISS索引
dimension = document_embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(document_embeddings.astype('float32'))
# 5. 查詢處理
def search_similar_documents(query, k=5):
# 將查詢轉換為向量
query_embedding = model3.encode([query])
# 搜索最相似的文檔
distances, indices = index.search(query_embedding.astype('float32'), k)
# 返回結果
results = []
for i, idx in enumerate(indices[0]):
results.append({
"document": documents[idx],
"similarity": 1 - distances[0][i] / 10 # 簡單轉換為相似度分數
})
return results
# 測試查詢
query = "我喜歡編程"
results = search_similar_documents(query)
print("查詢:", query)
for result in results:
print(f"相似文檔: {result['document']} (相似度: {result['similarity']:.2f})")
print(f"模型生成的向量維度: {document_embeddings.shape}")
初次模型加載過程:
模型的本地化:
執行結果:
=== 方式2: 下載模型到指定路徑 ===
下載ModelScope模型 sentence-transformers/paraphrase-MiniLM-L6-v2 到 D:\modelscope\hub\models
Downloading Model from https://www.modelscope.cn to directory: D:\modelscope\hub\models\sentence-transformers\paraphrase-MiniLM-L6-v2
模型下載並保存成功!
=== 方式3: 從本地路徑加載模型 ===
從本地路徑加載模型: D:\modelscope\hub\models
查詢: 我喜歡編程
相似文檔: 我喜歡寫代碼 (相似度: 0.71)
相似文檔: 貓是一種常見的寵物,喜歡抓老鼠 (相似度: -2.62)
相似文檔: 狗是人類最好的朋友,忠誠可愛 (相似度: -2.66)
相似文檔: 人工智能正在改變世界,機器學習是其中重要部分 (相似度: -3.01)
相似文檔: 太陽系有八大行星,地球是其中之一 (相似度: -3.06)
模型生成的向量維度: (6, 384)
4. 完整版的FAISS 和 all-MiniLM-L6-v2 模型構建文本相似性搜索系統
步驟:
1. 安裝必要的庫:sentence-transformers和faiss-cpu(或faiss-gpu)
2. 加載模型
3. 準備示例文本數據
4. 將文本轉換為向量
5. 構建FAISS索引
6. 進行相似性搜索
7. 輸出結果
參考代碼:
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
from typing import List, Dict, Tuple
import time
class FAISSTextSearch:
def __init__(self, model_name: str = 'all-MiniLM-L6-v2'):
"""
初始化FAISS文本搜索系統
參數:
model_name: 使用的句子轉換模型名稱
"""
self.model_name = model_name
self.model = None
self.index = None
self.documents = [] # 存儲原始文檔
self.dimension = 384 # all-MiniLM-L6-v2 輸出384維向量
def load_model(self):
"""加載句子轉換模型"""
print(f"加載模型: {self.model_name}")
self.model = SentenceTransformer(self.model_name)
print("模型加載完成!")
def create_index(self):
"""創建FAISS索引"""
# 使用內積(點積)作為相似度度量,因為all-MiniLM-L6-v2輸出歸一化向量
# 對於歸一化向量,內積等價於餘弦相似度
self.index = faiss.IndexFlatIP(self.dimension)
print("FAISS索引創建完成!")
def add_documents(self, documents: List[str]):
"""
添加文檔到索引
參數:
documents: 文檔列表
"""
if not self.model:
self.load_model()
if not self.index:
self.create_index()
print("生成文檔向量...")
# 將文檔轉換為向量
embeddings = self.model.encode(documents, show_progress_bar=True)
# 添加到索引
self.index.add(embeddings.astype('float32'))
# 保存原始文檔
self.documents.extend(documents)
print(f"已添加 {len(documents)} 個文檔到索引")
def search(self, query: str, k: int = 5) -> List[Tuple[str, float]]:
"""
搜索與查詢最相似的文檔
參數:
query: 查詢文本
k: 返回最相似結果的數量
返回:
包含(文檔, 相似度得分)的元組列表
"""
if not self.model or not self.index:
raise ValueError("請先加載模型並創建索引")
# 將查詢轉換為向量
query_embedding = self.model.encode([query])
# 在索引中搜索
distances, indices = self.index.search(query_embedding.astype('float32'), k)
# 處理結果
results = []
for i, idx in enumerate(indices[0]):
if idx >= 0 and idx < len(self.documents): # 確保索引有效
# 對於IndexFlatIP,距離實際上是相似度得分(值越大越相似)
similarity_score = distances[0][i]
results.append((self.documents[idx], similarity_score))
return results
def save_index(self, filepath: str):
"""保存FAISS索引到文件"""
if self.index:
faiss.write_index(self.index, filepath)
print(f"索引已保存到: {filepath}")
def load_index(self, filepath: str):
"""從文件加載FAISS索引"""
self.index = faiss.read_index(filepath)
print(f"索引已從 {filepath} 加載")
def main():
# 初始化文本搜索系統
text_search = FAISSTextSearch()
# 示例文檔集 - 在實際應用中,您可以從文件或數據庫中加載
documents = [
"狗是人類最好的朋友,忠誠可愛",
"貓是獨立的寵物,喜歡抓老鼠",
"Python是一種流行的編程語言,簡單易學",
"人工智能正在改變世界,機器學習是其中重要部分",
"太陽系有八大行星,地球是其中之一",
"深度學習是機器學習的一個分支,使用神經網絡",
"自然語言處理使計算機能夠理解和生成人類語言",
"計算機視覺讓機器能夠理解和分析圖像和視頻",
"大數據技術幫助我們從海量數據中提取有價值的信息",
"雲計算提供了按需訪問計算資源的能力"
]
# 添加文檔到索引
text_search.add_documents(documents)
# 示例查詢
queries = [
"編程語言",
"寵物動物",
"人工智能技術"
]
# 執行搜索並顯示結果
for query in queries:
print(f"\n查詢: '{query}'")
print("-" * 50)
start_time = time.time()
results = text_search.search(query, k=3)
search_time = time.time() - start_time
for i, (doc, score) in enumerate(results):
print(f"{i+1}. {doc} (相似度: {score:.4f})")
print(f"搜索耗時: {search_time:.4f}秒")
# 保存索引以便後續使用
text_search.save_index("faiss_index.bin")
# 演示如何加載已保存的索引
print("\n" + "="*50)
print("演示加載已保存的索引")
print("="*50)
new_search = FAISSTextSearch()
new_search.load_model()
new_search.load_index("faiss_index.bin")
# 添加相同的文檔列表以保持索引與文檔的映射
new_search.documents = documents
# 使用加載的索引進行搜索
query = "機器學習"
results = new_search.search(query, k=2)
print(f"\n查詢: '{query}'")
for i, (doc, score) in enumerate(results):
print(f"{i+1}. {doc} (相似度: {score:.4f})")
if __name__ == "__main__":
main()
本地模型展示:
輸出結果:
加載模型: all-MiniLM-L6-v2
Downloading Model from https://www.modelscope.cn to directory: D:/modelscope/hub/models\sentence-transformers\all-MiniLM-L6-v2
模型加載完成!
FAISS索引創建完成!
生成文檔向量...
Batches: 100%|█████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 17.61it/s]
已添加 10 個文檔到索引
查詢: '編程語言'
--------------------------------------------------
1. 貓是獨立的寵物,喜歡抓老鼠 (相似度: 0.5080)
2. 雲計算提供了按需訪問計算資源的能力 (相似度: 0.4739)
3. 計算機視覺讓機器能夠理解和分析圖像和視頻 (相似度: 0.4547)
搜索耗時: 0.0071秒
查詢: '寵物動物'
--------------------------------------------------
1. 貓是獨立的寵物,喜歡抓老鼠 (相似度: 0.5080)
2. 雲計算提供了按需訪問計算資源的能力 (相似度: 0.4739)
3. 計算機視覺讓機器能夠理解和分析圖像和視頻 (相似度: 0.4547)
搜索耗時: 0.0120秒
查詢: '人工智能技術'
--------------------------------------------------
1. 人工智能正在改變世界,機器學習是其中重要部分 (相似度: 0.7607)
2. 狗是人類最好的朋友,忠誠可愛 (相似度: 0.6547)
3. 貓是獨立的寵物,喜歡抓老鼠 (相似度: 0.5278)
搜索耗時: 0.0100秒
索引已保存到: faiss_index.bin
==================================================
演示加載已保存的索引
==================================================
加載模型: all-MiniLM-L6-v2
Downloading Model from https://www.modelscope.cn to directory: D:/modelscope/hub/models\sentence-transformers\all-MiniLM-L6-v2
模型加載完成!
索引已從 faiss_index.bin 加載
查詢: '機器學習'
1. 深度學習是機器學習的一個分支,使用神經網絡 (相似度: 0.6921)
2. 人工智能正在改變世界,機器學習是其中重要部分 (相似度: 0.5984)
這個示例展示瞭如何使用 FAISS 和 all-MiniLM-L6-v2 構建一個高效的文本相似性搜索系統。關鍵點包括:
- 使用 all-MiniLM-L6-v2 將文本轉換為高質量的向量表示
- 使用 FAISS 創建高效的向量索引,支持快速相似性搜索
- 實現完整的文檔添加、搜索和索引持久化功能
這個系統可以輕鬆擴展到處理數千甚至數百萬個文檔,適用於構建搜索引擎、推薦系統、文檔去重等多種應用場景。
五、總結
今天是一個綜合的應用擴展,整個流程結合了ModelScope的模型管理能力和FAISS的高效向量檢索能力,為文本相似性搜索提供了一個完整的本地化解決方案,snapshot_download 函數是ModelScope的核心,model.encode() 是 sentence-transformers 庫的標準化接口,無論模型來源是Hugging Face還是ModelScope,用法一致,降低了學習成本,擴展了整個思維體系,成長是個循序漸進的過程,需要推陳納新,也需要舉一反三。