即將開啟 8 篇系列分享:AskSense 從構想到落地的全紀錄

 啟動契機:挖掘語意搜尋的真實需求

真正的啟動契機,其實源自於筆者在一次專案中協助其他團隊偵測詐騙訊息的需求。一般人每天收到大量充斥著看似合法卻暗藏陷阱的留言,單純的關鍵字比對往往抓不到那些巧妙包裝的騙局話術:譬如「立刻確認您的帳戶安全」或「您已獲得退款,請點擊此連結」──這些句子如果只比對「安全」、「退款」等字面,既無法判定其危險風險,也容易造成大量誤報。我意識到,唯有讓機器「理解」整段文字的語意、掌握上下文關聯,才能在訊息剛一流入系統便迅速標記疑似詐騙並警示用戶,真正達到「防詐」的目的。

於是,我將原本為 FAQ 搜尋打造的 AskSense 架構改造為一套詐騙偵測管線,先利用多語言 SBERT 將所有傳入訊息轉成語意向量,接著透過訓練好的分類模型或相似度閾值,將那些在「高風險向量空間」中的訊息一網打盡。為了避免遺漏新型詐騙手法,我又結合了「句子聚類」技術,定期對最新收集的可疑範本進行無監督分群分析,並手動標記、細化特徵。這個過程中,我不斷調整向量相似度閾值、優化聚類數量,並且為每一次錯報和漏報寫下詳細日誌,確保模型與流程能夠持續在真實場景下自我修正與進化。


系列大綱:八個關鍵章節

本系列將以八篇文章,循序拆解 AskSense 的內部密碼,從思路萌芽到雲端佈署,全方位記錄每一步技術抉擇與心路歷程:

篇章 標題 核心內容
1 問題與動機 專案一發想的真實痛點與用戶需求
2 初探向量化 Bag-of-Words、TF-IDF 試水與瓶頸
3 詞嵌入初體驗 Word2Vec 巨量模型的掙扎與輕量化 SBERT 的初步實驗
4 模型選擇抉擇 性能、準確度、記憶體與多語言支持的權衡——為何最終落在 MiniLM 家族
5 多語言挑戰 繁體中文 FAQ 支援、jieba 切詞、OpenCC 轉換與多語言模型的整合
6 優化與重構 從「向量冷啟動太慢」到「依賴衝突爆炸」,我如何引入緩存、虛擬環境與增量加載策略
7 從 CLI 到雲端:部署實踐 Streamlit Cloud、CI/CD Pipeline、環境變數保護與日誌監控的實戰
8 未來與擴展 FAISS 向量索引、使用者回饋閉環、混合檢索架構與更多戰略

技術棧與工具選擇:從 spaCy 到 SBERT

在專案最初,我選擇了 spaCy 的 en_core_web_sm 模型來做詞性標註和句子向量化,因為它輕量、易安裝,能快速地完成基本的 Tokenization、POS Tagging、Dependency Parsing 以及簡單的詞向量平均。然而在實際測試時,我發現把整句話的向量設定成各個詞向量的平均值,忽略了詞序、句法結構與語境差異。舉例來說,「狗咬人」與「人咬狗」經過平均後的向量幾乎相同,這就無法幫助搜尋引擎分辨兩句話的不同意圖。更糟的是,這些平均向量大多基於詞向量辭典(如 GloVe)的靜態權重,對於同一個詞在不同上下文中的細微變化無能為力。

帶著這些觀察,我嘗試了 Gensim 提供的 Word2Vec–Google News 300 預訓練模型,期望能利用它在大規模語料上學到的詞彙關係。然而,1.5 GB 的模型在我的開發機上就像一頭貪吃猛獸:一次讀入就迅速吞噬所有可用記憶體,讓我頻頻遭遇 OOM(Out of Memory)錯誤,CPU 使用率接近 100%,程式噗通噗通重啟。這場硬體與模型規模的拉鋸戰,最終讓我深刻體會到──選對模型不僅要考量準確度,更要兼顧資源消耗與執行效率。於是,我轉向了「輕量級」的 Sentence-BERT 系列:首先是英文版的 all-MiniLM-L6-v2,它僅有數十 MB,但能產出高品質的句子嵌入;後來又切換到 paraphrase-multilingual-MiniLM-L12-v2,一併支援繁體中文與英文,模型大小仍相對精簡,推論速度快,且在多語言場景下依然能保持良好的相似度區分。這一路的嘗試與取捨,讓我明白了在真實世界應用中,「輕巧可用」往往比「最先進最龐大」更加實際。


資料處理與多語言挑戰:繁體中文支援心法

在將 AskSense 擴展至繁體中文支援的過程中,我最先面臨的挑戰就是中文分詞與統一簡繁體問題。相比以空格分隔的英語,中文沒有天然的詞邊界,因此必須依靠工具如 jieba 來完成斷詞。然而預設詞庫中並未收錄許多專屬臺灣的用語或新興名詞,導致「臺灣」這類常用地名常被錯誤切分為「臺」與「灣」。為了解決這一點,我首先在 jieba 的自訂字典中加入了包含「臺灣」、「臺北市」、「高雄」、「詐騙」等專有名詞,使其能一次辨識為完整詞彙。此外,我特地設計了一個 pre-processing pipeline:先以 OpenCC 將簡體字轉為繁體,或將繁體字統一為更現代的用字,確保不同來源文本在斷詞前就已經標準化,避免同一詞彙因字形差異而被當成不同詞語。

完成分詞與字形統一後,下一步是將這些詞彙映射到語意向量上。使用多語言 SBERT 模型(如 paraphrase-multilingual-MiniLM-L12-v2)時,模型會把整句話轉換成稠密向量,但它對於中文的斷詞品質仍高度依賴前端分詞效果。為此,我在 pipeline 中加入了特別的過濾規則:移除過短(如「的」、「了」)或過長(如超過 5 個字符)的詞彙,並保留名詞、動詞與形容詞,因為這些詞性承載了主要語意;同時,我也測試了不同 stopword 表,排除常見虛詞干擾。這樣的精調確保輸入到 SBERT 的文字內容既精簡又富含關鍵資訊,大幅提升了最終的相似度計算準確度。

最後,我反覆比較了多種策略的效果:只用預設 jieba + SBERT、加上自訂字典、再加上 OpenCC 標準化、以及後續的 stopword 過濾和詞性挑選。每次改動後,我都用同一組測試對(例如「如何重設我的密碼?」 vs. 「我要怎麼修改登入密碼?」)來檢驗相似度分數,並觀察在「中文—中文」、「中文—英文」混合測試中的表現。經過無數次的微調,AskSense 最終在繁體中文問句上的語意匹配效果已達到與英語同等水準──不僅能準確找出同義或近義語句,也能區分相似語意卻具不同意圖的問題,真正實現了跨語言、跨表達的智慧搜尋。


優化性能的瓶頸與解方:記憶體、向量、快速相似度計算

在最初的版本中,每次啟動 AskSense 都要重新讀取完整的 FAQ 文本並通過 SBERT 模型將上萬條句子逐一轉換成向量,這個過程不僅計算量大,而且模型載入與向量運算在冷啟動時往往需要近 20 秒才能完成,嚴重影響使用體驗。為了解決這個瓶頸,我引入了「向量檔增量快取」的機制:在第一次執行時,將所有 FAQ 條目的嵌入向量計算好後,以 NumPy 的 .npy 格式序列化並存入磁碟;之後的啟動流程便可直接透過 numpy.load 一次性讀取整塊向量陣列,省去了模型重新計算的成本。此外,我利用 joblib 提供的多進程功能,在初次生成向量時並行處理多個條目,大幅縮短預計算時間,同時在載入階段也支援多線程解壓與反序列化,將冷啟動時間從 20 秒驟降至約 2 秒內。

在實際相似度檢索階段,面對可能擴充到百萬級的向量庫,線性遍歷計算餘弦相似度已經無法滿足即時回應的需求。因此我引入了 Facebook AI’s FAISS(Facebook AI Similarity Search)索引庫,利用其高效的倒排索引與近似最近鄰演算法建構向量索引。在建立索引時,向量同樣先經過 joblib 並行分片存取,然後透過 FAISS 的 IndexFlatIPIndexIVFFlat 等結構實現高維空間的快速檢索。這種結合向量快取與專業索引的雙重優化,既保證了模型啟動的迅速響應,也能在海量向量中毫秒級返回最相關的 FAQ 條目,真正讓 AskSense 在大規模生產環境中達成「既快且準」的搜尋體驗。


部署與安全:環境變數、CI/CD 與雲端佈署經驗

Streamlit Cloud 讓部署一鍵完成,但「一鍵」背後是 CI/CD pipeline 的磨合與環境變數保護:我使用 GitHub Actions 自動化測試、Lint、再推至 Cloud;並以 dotenv 檢測 .env 是否存在敏感字串,確保任何時候都不會把 API key 或憑證洩露到公開 Repo。這一路我為了避免「重覆部署失敗」,甚至寫了 Health Check 腳本,監控服務狀態並自動重啟卡住的執行緒。


心路歷程:深夜 Debug、失敗反覆與問題排解

在面對語意相似度落在 0.85 到 0.87 區間的小幅波動時,我曾陷入「參數微調還是架構重構」的無限循環。當時我嘗試在相似度計算前加入一層 TF-IDF 閾值過濾,想透過削弱高頻低資訊詞的影響來提升最終分數;卻發現整體命中率並沒有明顯上升,反而因為過度過濾導致少數重要詞被誤刪,匹配反而更糟。這段時間裡,我交叉驗證上千組參數組合,從不同語料拆分、stopword 清單調整到詞性篩選,都反覆測試至凌晨三點,最終領悟到:參數調整是一把雙面刃,若無明確指標與小步迭代,很容易陷入「調爽卻不知為何有效」的迷思,這才開始重視設計嚴謹的 AB 測試流程以及監控指標(如查全率、查準率的平衡),以科學化而非盲目嘗試的方式來逐步優化。

在另一次探索混合檢索策略時,我將 BoW 的關鍵字召回與 SBERT 的語意精排組合,期望藉由兩者優勢互補提高匹配品質。理論上,BoW 可快速濾出與關鍵詞高度相關的候選集,而 SBERT 再為其精準排序;實作後卻發現分數與純 SBERT 方案幾乎一致,甚至在部分查詢上略有倒退。這讓我深刻體會到:功能疊加並不等於效能提升,反而可能因流程複雜度增加而帶來更多噪聲。於是我開始梳理「何時該做減法」、刪除冗餘流程,以及「何時該疊加功能」、在確保基礎穩定後再加入輔助模塊,並以可量化的指標嚴格驗證每一步改動,最終將開發重心放回模型品質與工程效率的平衡上。


下一步展望:向 FAISS、使用者回饋機制與混合檢索邁進

在第八篇中,我會詳細說明如何將 FAISS 向量索引無縫整合到 AskSense 的搜尋管線裡,使得原本需要線性掃描上萬筆向量的相似度計算瞬間被壓縮到次毫秒級。具體而言,我先使用 FAISS 的 IndexFlatIP 進行內積索引,並在向量量表(vector quantization)階段採用了 IVF(Inverted File)架構,把所有句子向量分派到數百個子群集(centroids)中,這樣搜尋時只需在最相關的子群集中檢索,大幅降低計算量。為了避免冷啟動延遲,我把索引和映射表以二進制形式序列化並儲存在雲端,啟動時直接載入即可。這不僅讓搜尋速度比原來快上百倍,也讓 AskSense 在使用者查詢量突增時依然能保持穩定的回應時間。

同時,我結合了使用者實際點擊和回饋機制,打造了一套「動態再訓練」流程。每當使用者選擇某個答案並給出正負評價,系統便將這些反饋與原始句子成對存入資料庫,並定期觸發批次訓練:先用這些標記好的「正確匹配/錯誤匹配」示例微調 SBERT,再重新生成向量並更新 FAISS 索引。最後,我還實驗了以 TF-IDF 做召回——快速過濾掉與查詢字面無關的大多數候選集——再用 SBERT 做精排的混合檢索架構,通過 A/B 測試精心調整兩者權重,找到「召回率×精確率」的最佳平衡點。這樣一來,AskSense 不只是單純的問答工具,而是一個能持續從使用者行為中學習、並在每次部署後自我優化的智能助手。


八篇分享即將全面展開,每一篇都會以最真實的開發場景最血淚的除錯過程,搭配深度技術分析,還原我打造 AskSense 的每一步思考與抉擇。期待與大家一起交流討論,讓這趟 NLP 自學路更充實,也歡迎在留言區分享你曾經遇過的技術瓶頸或解法!

Comments

Popular posts from this blog

【新聞挖掘工坊:第 2 篇】Google News RSS 祕密通道:怎麼抓新聞連結?

【統計抽樣 × NLP 節能分析:第 3 篇】階層、系統、叢集:三大抽樣法一次搞懂

區域網路扁平架構與 Zero Trust 缺口:從 Streamlit 測試到 IoT 隔離的安全評估