--- title: 繁中維基語意搜尋 zh-TW Wiki Semantic Search emoji: 🔍 colorFrom: indigo colorTo: pink sdk: gradio sdk_version: 6.20.0 app_file: app.py pinned: false license: mit short_description: bge-m3 + FAISS 語意搜尋 vs BM25(50k 繁中維基段落) models: - BAAI/bge-m3 datasets: - steven0226/zhtw-wiki-semsearch-index --- # 🔍 繁中維基語意搜尋引擎(bge-m3 × FAISS × Gradio) 對繁體中文維基百科隨機抽樣的 **50,000 個段落**建立語意索引,並排比較 **語意搜尋(bge-m3 + FAISS)** 與 **關鍵字搜尋(BM25 + jieba)**, 讓「同義改寫也搜得到」的差異一眼可見。 **🚀 線上 Demo:[Hugging Face Space](https://huggingface.co/spaces/steven0226/zhtw-wiki-semantic-search)** (免費 ZeroGPU Space,app 為純 CPU 程式碼;冷啟動需幾分鐘) ## 為什麼做這個? 關鍵字搜尋只認得字面上的詞:搜「天空為什麼藍藍的」,BM25 只能比對 「天空」「藍」這些字,找不到用「瑞利散射」「大氣層」描述同一件事的段落。 語意搜尋把整句話變成 1024 維向量,「意思相近」的段落在向量空間裡自然靠近—— 即使一個字都沒重疊。Demo 裡的橘色標記就是「語意找得到、關鍵字找不到」的結果。 ## 架構 ``` ┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │ 本機 RTX 4090 │ │ HF Dataset repo │ │ HF Space(ZeroGPU) │ │ │ │ │ │ │ │ prepare_data.py │ │ index.faiss (205MB) │ │ app.py(Gradio) │ │ build_index.py ├─────▶│ metadata.parquet ├─────▶│ ・bge-m3 編碼查詢 │ │ (fp16 批次編碼) │ 上傳 │ (title/text/url) │ 啟動時 │ ・FAISS top-10 │ │ │ │ │ 下載 │ ・BM25 對照組 │ └─────────────────┘ └──────────────────────┘ └─────────────────────┘ ``` 重的 embedding 計算(50k 段 × bge-m3)在本機 GPU 做,Space 只負責: 把查詢句編碼成向量(CPU、約 1–3 秒)→ FAISS 內積搜尋(<1ms)→ 顯示結果。 ## bge-m3 是什麼? [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3) 是北京智源(BAAI)的多語言 embedding 模型,基於 XLM-RoBERTa-large(~568M 參數): - **多語言**:100+ 語言共用同一個向量空間,中文查詢可以配對英文段落 - **Multi-Functionality**:同時支援 dense、sparse(lexical)、multi-vector (ColBERT)三種檢索——本專案用 dense 向量(1024 維) - **免 instruction prefix**:不像 bge-*-zh-v1.5 需要「為這個句子生成表示…」前綴 - 向量 L2-normalize 後用內積(= cosine 相似度)配 `faiss.IndexFlatIP` 做精確搜尋 (50k 規模不需要 IVF/HNSW 近似索引) ## 語意 vs 關鍵字:實際例子 | 口語查詢 | 語意搜尋找到 | BM25 的困境 | |---|---|---| | 天空為什麼藍藍的 | 瑞利散射、大氣光學條目 | 「藍藍的」比對不到「散射」 | | 為什麼感冒好了比較不容易再中一次 | 免疫記憶、抗體條目 | 全句沒有「免疫」兩字 | | 月亮為什麼有時候圓有時候只剩彎彎的一條 | 月相條目 | 「彎彎的一條」不在任何條目裡 | (實際結果依 50k 隨機抽樣內容而定,歡迎在 Demo 裡自己試) ## 重現步驟 ```powershell # 0. 環境(Windows + NVIDIA GPU;Linux 把路徑分隔換掉即可) uv venv .venv --python 3.12 --seed .venv\Scripts\Activate.ps1 uv pip install -r requirements-local.txt --index-strategy unsafe-best-match # 1. 資料:下載繁中維基(8.2GB)→ 清理 → 抽 50,000 段(seed=42,可重現) python scripts/prepare_data.py # 2. 索引:GPU fp16 批次編碼 + FAISS IndexFlatIP python scripts/build_index.py # 3. 上傳到 HF dataset repo(HF_TOKEN 放專案根目錄 .env) python scripts/upload_index.py # 4. 本機端到端測試(先本機檔案、再走真實下載路徑) $env:DATA_DIR="data"; python app.py Remove-Item Env:DATA_DIR; python app.py # 5. 部署 Space python scripts/deploy_space.py ``` ## 設計筆記 - **HF 免費帳號政策(2026)**:新的 Gradio Space 只能建在 ZeroGPU 硬體 (cpu-basic 需 PRO 訂閱)。本 app 全程純 CPU 程式碼、不呼叫 GPU, 不消耗訪客的 ZeroGPU 配額;torch 必須 pin ZeroGPU 支援清單內的版本(2.8–2.11) - **模型只載一次**:app.py 在 module top-level eager 載入(Space 的 Building/Starting 畫面天然就是 loading 提示),並先做一次暖身編碼 - **BM25 基線**:rank_bm25 + jieba 斷詞,啟動時對同一批 50k 段落現場建索引, 與語意側用完全相同的語料對照才公平 - **CJK 路徑陷阱**:faiss 的 `write_index`/`read_index` 用窄字元 fopen, 在含中文的 Windows 路徑會失敗——全程改用 `serialize_index`/`deserialize_index` + Python byte I/O ## 效能數據 | 項目 | 數值 | |---|---| | 50k 段落 GPU 編碼(4090, fp16, batch 128) | 52.2 秒(約 958 句/秒),見 [build_stats.json](https://huggingface.co/datasets/steven0226/zhtw-wiki-semsearch-index/blob/main/build_stats.json)(2026-07-11 單次量測) | | FAISS IndexFlatIP 檔案大小 | ~205 MB(50,000 × 1024 × fp32) | | Space 查詢延遲(ZeroGPU host CPU) | 編碼 ~1–3s + 檢索 <1ms | | Space 冷啟動 | ~5 分鐘(裝套件 + 下載模型/索引 + 建 BM25) | ## 授權 - 程式碼:MIT - 維基百科段落文字:[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.zh-hant) © Wikipedia 貢獻者(每筆結果都附原始條目連結;索引資料的完整署名見 [dataset repo](https://huggingface.co/datasets/steven0226/zhtw-wiki-semsearch-index)) ## Source repository 程式碼、訓練與評估流程、測試與完整證據都在 GitHub:。GitHub `kuotunyu` 與 Hugging Face `steven0226` 為同一人。 Source code, the training/evaluation pipeline, tests and the full evidence trail live at . GitHub `kuotunyu` and Hugging Face `steven0226` are the same author.