將文本轉換為向量(文本嵌入)是自然語言處理中的核心任務,有許多大模型可以完成這項工作。上一篇文章《構建AI智能體:十五、超越關鍵詞搜索:向量數據庫如何解鎖語義理解新紀元》我們是通過阿里雲的api調用的text-embedding-v4模型,同樣還有很多其他輕量級的模型可以很好的完成這個任務,我們今天找兩個結合前期講到的本地化部署來嘗試一下。

一、核心組件回顧

  • ModelScope(魔搭):阿里雲推出的模型即共享平台,定位為中國版的“Hugging Face”,優勢是豐富的預訓練中文模型、國內下載速度快、阿里達摩院支持
  • 向量:在AI和機器學習領域,向量是一組數字的有序列表,可以表示任何數據對象(如一段文字、一張圖片)在高維空間中的位置。
  • 嵌入(Embedding):通過AI模型(如BERT、CNN、CLIP等)將非結構化數據轉換為向量的過程,稱為“嵌入”。這個轉換過程捕獲了數據的深層語義特徵。
  • 向量數據庫:是一種專門用於存儲、索引和查詢高維向量的數據庫。它的核心功能是執行近似最近鄰(ANN)搜索,即快速找到與查詢向量最相似的向量集合。

二、從文本到向量檢索的完整流程

  • 核心步驟分解:
  1. 文本預處理
  2. 本地嵌入模型加載與推理
  3. 向量生成與規範化
  4. 向量存儲與索引構建
  5. 查詢處理與相似性搜索

三、句子嵌入模型介紹

1. paraphrase-MiniLM-L6-v2模型

paraphrase-MiniLM-L6-v2 是 SentenceTransformers 庫中一個非常受歡迎且高效的句子嵌入模型。它的名字可以拆解來看:

  • paraphrase: 意味着它在“釋義”(paraphrase)任務上進行了優化訓練。即,它非常擅長判斷兩個句子是否語義相同但表述不同。
  • MiniLM: 指其基於 MiniLM 架構。這是一種通過知識蒸餾技術,將大型語言模型(如 BERT、RoBERTa)的知識壓縮到更小模型中的方法。它保留了老師模型的大部分性能,但體積更小、速度更快。
  • L6: 代表這個模型有 6 層(Layer)。作為對比,原始的 bert-base 模型有 12 層。層數減少是模型變輕量的主要原因之一。
  • v2: 代表版本號。

核心特點

  1. 輕量且高效
  • 參數量: 約 22.7 million(2270萬)個參數。
  • 體積: 模型文件大約 90 MB
  • 速度: 由於其小巧的尺寸,它在 CPU 和 GPU 上都能進行非常快速的推理(編碼句子為向量),非常適合對延遲敏感的應用。
  1. 高質量的語義表示
  • 儘管模型很小,但得益於 MiniLM 蒸餾技術,它在語義相似度任務上的表現非常出色,甚至可以媲美一些更大的模型(如基於 BERT-base 的模型)。
  • 它生成的句子向量能夠很好地捕獲句子的核心語義信息。
  1. 通用性強:雖然是為“釋義”任務優化的,但其生成的通用句子向量在多種下游任務中都有良好表現,例如:
  • 語義搜索(Semantic Search)
  • 信息檢索(Information Retrieval)
  • 聚類(Clustering)
  • 文本分類(Text Classification)的特徵輸入
  • 重複數據刪除(Deduplication)
  1. 輸出維度
  • 它將任何長度的文本轉換為一個固定大小的 384 維的浮點數向量。

2. all-MiniLM-L6-v2 模型

all-MiniLM-L6-v2 是 SentenceTransformers 庫中的一個通用文本嵌入模型。它的名字是其核心特徵的縮寫:

  • all: 意味着它是一個通用模型,旨在對各種類型的文本和任務都能產生高質量的向量表示,而不僅僅是為某個特定任務(如釋義)優化的。
  • 其他屬性同上

核心特點:

  1. 輕量級與高性能的完美平衡
  • 參數量: 約 22.7 million(2270萬)。
  • 體積: 模型文件僅約 90 MB。非常易於下載、存儲和部署。
  • 速度: 在 CPU 和 GPU 上都能進行極快的推理(編碼)。這對於需要實時或批量處理大量文本的應用(如搜索、推薦)至關重要。
  • 性能: 儘管模型很小,但得益於高效的蒸餾技術,它在許多標準基準測試中的表現可以媲美甚至超越一些更大的模型(如原始的 BERT-base)。
  1. 通用性強,它被訓練用於捕捉句子的通用語義信息,因此在多種下游任務中都有良好表現,包括:
  • 語義搜索: 找到與查詢語句語義最相關的文檔。
  • 文本聚類: 將語義相似的句子或文檔自動分組。
  • 信息檢索: 增強傳統關鍵詞搜索的能力。
  • 文本分類與語義相似度計算: 作為機器學習模型的輸入特徵。
  • 檢索增強生成: 為 LLM 從知識庫中檢索相關上下文。
  1. 輸出標準化
  • 它將任何長度的文本轉換為一個固定大小的 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)


提示異常:

構建AI智能體:十六、構建本地化AI應用:基於ModelScope與向量數據庫的文本向量化_搜索

基於此,做其他方式的考慮,從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索引,進行查找,最後返回結果;

構建AI智能體:十六、構建本地化AI應用:基於ModelScope與向量數據庫的文本向量化_相似度_02

代碼示例:

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}")


初次模型加載過程:

構建AI智能體:十六、構建本地化AI應用:基於ModelScope與向量數據庫的文本向量化_搜索_03

模型的本地化:

構建AI智能體:十六、構建本地化AI應用:基於ModelScope與向量數據庫的文本向量化_相似度_04

執行結果:

=== 方式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()


本地模型展示:

構建AI智能體:十六、構建本地化AI應用:基於ModelScope與向量數據庫的文本向量化_相似度_05


輸出結果:

加載模型: 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 構建一個高效的文本相似性搜索系統。關鍵點包括:

  1. 使用 all-MiniLM-L6-v2 將文本轉換為高質量的向量表示
  2. 使用 FAISS 創建高效的向量索引,支持快速相似性搜索
  3. 實現完整的文檔添加、搜索和索引持久化功能

這個系統可以輕鬆擴展到處理數千甚至數百萬個文檔,適用於構建搜索引擎、推薦系統、文檔去重等多種應用場景。

五、總結

        今天是一個綜合的應用擴展,整個流程結合了ModelScope的模型管理能力和FAISS的高效向量檢索能力,為文本相似性搜索提供了一個完整的本地化解決方案,snapshot_download 函數是ModelScope的核心,model.encode() 是 sentence-transformers 庫的標準化接口,無論模型來源是Hugging Face還是ModelScope,用法一致,降低了學習成本,擴展了整個思維體系,成長是個循序漸進的過程,需要推陳納新,也需要舉一反三。