現(xiàn):Atlas開源工具實戰(zhàn)解析)
1. 從一個跑不動的分詞任務說起我最初接觸Atlas是因為在做一個大規(guī)模行業(yè)語料清洗和新詞發(fā)現(xiàn)的需求。傳統(tǒng)做法是先把語料跑一遍分詞再用詞頻統(tǒng)計篩出候選但問題很快暴露出來通用分詞器對垂直領域的專業(yè)術語、新造詞、組合詞幾乎無能為力而人工維護詞典的成本又高得離譜——今天收錄的詞明天可能就過氣了而且各個業(yè)務線的語料風格差異巨大一套詞表根本沒法通吃。這個場景下我需要的不是一個“詞表”而是一套能從語料里自動把新的、穩(wěn)定的、有語義邊界的語言單元挖出來的方案。當時調研了一圈業(yè)界常用做法無非是N-gram頻次統(tǒng)計加規(guī)則過濾或者用互信息和左右熵這類統(tǒng)計量做候選篩選。Atlas就是后一種思路的工程化實現(xiàn)它不依賴預置詞典不要求標注語料只需要一堆原始文本就能通過統(tǒng)計手段自動發(fā)現(xiàn)新詞。這套設計非常契合我的需求——尤其適合垂直領域術語挖掘、輿情關鍵詞提取、搜索詞補全、還有文本數(shù)據(jù)的探索性分析這類場景。需要說明的是Atlas并不是一個Web服務也不是一個帶界面的工具它是一套開源的C命令行程序核心能力是“從大規(guī)模語料中提取新詞”主要面向有基本工程能力的開發(fā)者、算法工程師和數(shù)據(jù)從業(yè)者。如果你手上正好有一批文本想知道里面有哪些反復出現(xiàn)的規(guī)律性片段而且不想手動翻數(shù)據(jù)那這篇文章應該能幫你省下不少時間。2. Atlas的核心思路為什么是“互信息左右熵”這套組合2.1 兩個統(tǒng)計量的直覺理解Atlas的底層原理并不復雜它同時使用了兩個統(tǒng)計量來衡量一個候選片段是否是“詞”互信息PMI和左右熵Entropy?;バ畔⒑饬康氖瞧蝺炔孔峙c字之間的綁定強度。打個比方如果“人”和“工”經常一起出現(xiàn)而它們各自單獨出現(xiàn)的頻率也都很高那它們湊在一起到底是巧合還是真形成了一個固定組合互信息就是回答這個問題的它比較“一起出現(xiàn)的概率”和“各自單獨出現(xiàn)的期望概率”。如果一起出現(xiàn)的概率遠大于隨機拼湊的期望互信息值就高說明這兩個字確實有很強的內部黏合性。左右熵衡量的是片段外部的自由程度。一個真正的詞應該能出現(xiàn)在各種不同的上下文里。比如“人工智能”后面可以接“發(fā)展”“技術”“領域”“研究”前面也可以有不同的修飾詞這說明它的左右邊界很自由是一個穩(wěn)定的獨立單元。反之如果某個片段只固定出現(xiàn)在同一個句子里比如只在“根據(jù)氣象部門預測”中出現(xiàn)那它的左右熵就會很低大概率只是一個固定搭配的碎片不是真正的新詞。這兩個指標一個管“內部黏合”一個管“外部自由”組合起來就能比較好地判斷一個N-gram片段到底是不是一個獨立的語言單元。這也是Atlas區(qū)別于單純詞頻統(tǒng)計的關鍵它不只看一個片段出現(xiàn)多少次更看它出現(xiàn)的方式是否符合“詞”的特征。2.2 這套組合為什么適合大規(guī)模語料我個人的體會是互信息和左右熵這套組合特別適合“數(shù)據(jù)量大、沒有標注、領域未知”的語料場景。原因有三點。第一它是無監(jiān)督的。不需要任何人工標注不需要詞典甚至不需要你知道語料里大概有哪些領域直接扔文本進去就行。這意味著它可以快速遷移到新的業(yè)務線不用每個項目都重新標注一遍數(shù)據(jù)。第二它的計算模式是高度可并行的。Atlas底層用C實現(xiàn)數(shù)據(jù)處理流程設計成分塊處理在多核機器上能直接用滿CPU資源。我實測在32核的服務器上處理幾GB的語料也就幾十分鐘跑完這在大規(guī)模文本處理場景下是完全能接受的。第三它的輸出是可控的。通過調節(jié)頻次下限、互信息閾值、熵閾值等參數(shù)可以控制新詞挖掘的“保守程度”——想要高精度就調高閾值想要高召回就調低閾值。這種靈活性在實際工程里非常重要因為不同業(yè)務對新詞的精度和召回要求完全不一樣。3. 環(huán)境準備與編譯從源碼到可執(zhí)行文件3.1 依賴只有兩項Atlas的依賴非常輕官方文檔里寫明只需要g支持C11和CMake2.8以上版本。我在Ubuntu 16.04和CentOS 7上都編譯過表現(xiàn)都很穩(wěn)定。如果你用的是macOS或者Windows的WSL環(huán)境理論上也沒問題但官方主要在Linux上做的測試所以強烈建議在Linux環(huán)境下使用能少踩很多坑。3.2 編譯步驟實錄整個編譯過程就三步簡單到沒什么可說的git clone https://github.com/Tencent/atlas.git cd atlas ./build.shbuild.sh腳本會自動創(chuàng)建build目錄、調用cmake生成Makefile、然后執(zhí)行make編譯。編譯完成后可執(zhí)行文件在build/atlas。我第一次編譯的時候遇到一個小問題服務器上g版本比較老只有4.8結果編譯到一半報了一堆C11語法相關的錯誤。后來把g升級到5.4以上就順利過了。所以如果你也遇到編譯失敗優(yōu)先檢查編譯器版本不要急著改代碼。3.3 確認安裝成功編譯完成后先跑一下幫助命令確認安裝正常./build/atlas --help如果能看到train、segment等子命令的幫助信息說明編譯成功。Atlas有兩個核心子命令train用于訓練新詞模型也就是新詞挖掘segment用于基于訓練好的詞典做分詞。這個分詞能力算是附贈的——你可以先用train從語料里挖出新詞再把新詞補充進詞典然后用segment做后續(xù)的文本切分。4. train命令參數(shù)詳解每個參數(shù)背后的邏輯4.1 參數(shù)清單與推薦值Atlas的train命令參數(shù)不算多但每個都很關鍵。我按自己的使用習慣整理了一張表標了推薦值和使用場景方便你直接參考。參數(shù)含義推薦值說明train_file輸入語料文件路徑必填每行一句/一段UTF-8編碼model_dir輸出模型目錄必填訓練結果輸出到該目錄min_freq候選詞最小頻次5~10低于該頻次的N-gram直接丟棄控制候選集規(guī)模max_word_len候選詞最大長度5~6超過該長度的片段不考察min_pmi互信息最低閾值20~30控制內部黏合強度要求min_entropy左右熵最低閾值1.0~2.0控制上下文自由度要求max_freq候選詞最大頻次1000000過濾掉“的”“了”等超高頻泛化詞train_mode訓練模式11表示新詞發(fā)現(xiàn)2表示模型增量訓練thread_num并行線程數(shù)CPU核數(shù)多核加速建議拉滿dic_path基礎詞典路徑可選傳入基礎詞典用于過濾已有詞4.2 這些參數(shù)到底怎么影響結果很多人第一次跑Atlas喜歡把所有參數(shù)都用默認值然后看結果覺得“這挖的什么玩意兒”。其實參數(shù)不調好結果確實會很糙。我逐一講下我踩過的坑。min_freq這個參數(shù)決定了候選詞的“準入門檻”。如果設得太低比如1~2候選集會爆炸性膨脹大量隨機拼湊的二字組合會混進來導致后續(xù)排序和過濾的負擔很重。設得太高又會漏掉一些低頻但很有價值的術語——尤其是長尾領域、小樣本場景下的專業(yè)詞。我一般先設5跑一遍看看結果再根據(jù)輸出質量和數(shù)量做調整。min_pmi是個相對值不同語料下合適的閾值差異很大。新聞類語料詞匯豐富、搭配多樣PMI普遍偏低閾值太高什么都挖不出來垂直領域語料比如醫(yī)學、法律術語重復度高PMI普遍偏高閾值可以適當提高來過濾噪聲。我建議第一次跑的時候先設一個較低的閾值比如10看輸出詞表里噪聲的比例再逐步上調。這個“先粗后細”的調參思路比一上來就賭一個高閾值要高效得多。min_entropy閾值我平時設在1.0~1.5之間。左右熵低于1.0的片段基本就是固定搭配比如“根據(jù)”“通過”“為了”這類。這些詞雖然也算“詞”但通常不是你想挖的新詞。如果你特別想過濾掉這類虛詞可以把min_entropy提到2.0左右但要注意有些專有名詞的上下文也比較固定比如人名、地名熵值天然偏低閾值太高會把它們一起過濾掉。max_word_len默認值是5但我建議在中文語料上設到6或者7。四字成語、五字術語、六字機構名都很常見只設到5會漏掉不少有意義的組合。當然長度設得越長候選集規(guī)模越大計算時間也會增加需要平衡一下。4.3 一個完整的train命令示例假設我們有一份新聞語料文件news_corpus.txt每行一條新聞UTF-8編碼。我的常用命令是這樣的./build/atlas train \ --train_filenews_corpus.txt \ --model_dir./news_model \ --min_freq5 \ --max_word_len6 \ --min_pmi15 \ --min_entropy1.2 \ --thread_num16 \ --train_mode1跑完后模型目錄里會生成詞表文件和模型文件。詞表文件里每一行是一個候選詞及其統(tǒng)計信息包含詞本身、頻次、左右熵、互信息等字段。5. 數(shù)據(jù)預處理這一步做不好后面全是白費5.1 語料清洗是真正的第一步Atlas本身不做語料清洗你喂給它什么它就分析什么。如果語料里滿是HTML標簽、URL、特殊符號、亂碼字符這些噪聲會直接影響N-gram的統(tǒng)計結果——想象一下“nbsp”這種字符串如果頻繁出現(xiàn)在語料里它也會被當成一個候選詞挖出來。所以數(shù)據(jù)預處理的質量直接決定了新詞挖掘的下限。我的清洗流程一般是先用正則去掉HTML標簽和URL再統(tǒng)一標點符號為全角或半角然后過濾掉包含異常字符的行最后做一次簡單的去重。需要注意的是不要過度清洗——比如把所有的數(shù)字都刪掉可能會導致一些包含數(shù)字的術語比如“3D打印”“5G通信”完全無法被發(fā)現(xiàn)。5.2 是否要做分詞預處理Atlas官方推薦的是“不預先分詞”直接使用原始文本作為train_file輸入。它的設計邏輯是新詞挖掘就是為了發(fā)現(xiàn)分詞器不認識的新詞如果先用分詞器切一遍新詞已經被切碎了互信息和左右熵的計算就失去了意義。所以直接用原始文本不要做任何分詞處理。但有一個例外如果語料本身是拼接出來的比如把標題和正文拼在一起你需要確保拼接處有明確的邊界標記比如空格或換行。否則標題的最后一個字和正文的第一個字跨邊界組成一個虛假的“詞”會干擾統(tǒng)計結果。5.3 語料規(guī)模的底線Atlas對語料規(guī)模有一個比較尷尬的底線要求——太少了跑不出效果。如果你的語料只有幾百KB挖掘出來的“新詞”大概率都是噪聲統(tǒng)計顯著性完全不夠。我個人的經驗是至少準備50MB以上的干凈文本大約幾萬篇新聞結果才會有參考價值。如果語料本身很小那更加建議降低min_freq和min_pmi用“低門檻人工審核”的方式來補救。6. 實操過程從原始語料到最終詞表的完整流程6.1 流程概覽我給自己定了一套固定的操作流程每次跑Atlas都照這個來基本不會出大問題。整理成步驟來看語料采集與清洗確認文本編碼為UTF-8去除明顯噪聲按行分割。初步訓練用比較寬松的參數(shù)跑一遍train拿到基礎詞表。結果粗篩用詞頻和簡單規(guī)則快速過濾明顯噪聲比如純數(shù)字、純符號、單字詞。參數(shù)迭代根據(jù)粗篩結果調整閾值重新訓練。人工抽檢隨機抽取200~500個新詞人工判斷準確率。詞表固化把通過審核的詞補充進業(yè)務詞典用于后續(xù)分詞或檢索。6.2 關鍵步驟的實際操作訓練完成后我習慣先用命令行快速看一眼詞表規(guī)模和頭部詞wc -l news_model/word_dict.txt head -50 news_model/word_dict.txt詞表文件每一行的格式大致是“詞\t頻次\t左熵\t右熵\t互信息”。頭部詞通常是最頻繁出現(xiàn)的一批詞你可以快速判斷這些詞是不是符合預期——如果全是“我們”“可以”“什么”這類通用詞說明min_freq設太低或者需要調整min_entropy如果已經出現(xiàn)了不少領域術語說明參數(shù)方向基本正確。接下來我會用一個簡單的Python腳本做自動過濾把包含數(shù)字字母以外的雜字符、或者明顯是HTML實體的詞刪掉再按頻次排序導出成可讀性更高的詞表import codecs with codecs.open(news_model/word_dict.txt, r, utf-8) as fin, \ codecs.open(filtered_words.txt, w, utf-8) as fout: for line in fin: parts line.strip().split(\t) if len(parts) 5: continue word, freq, left_entropy, right_entropy, pmi parts[0], int(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 過濾純符號、純數(shù)字 if not any(\u4e00 ch \u9fff for ch in word): continue if len(word) 2: continue # 按業(yè)務需求進一步過濾 fout.write(f{word}\t{freq}\t{pmi}\t{left_entropy}\t{right_entropy}\n)這個腳本很簡單但它幫我擋掉了大量明顯沒用的候選讓后續(xù)人工抽檢的負擔小了很多。6.3 效果評估的標準很多人在“新詞挖掘”這件事上最困惑的是我怎么知道挖掘結果好不好我的評估標準很簡單——抽500個詞人工標注是否為“有意義的新詞”然后算準確率。這里的“有意義”根據(jù)業(yè)務定義不同而有差異做搜索詞補全可能更看重詞語的完整性和搜索意圖做輿情分析可能更看重領域術語的覆蓋率做分詞詞典擴充則要求詞本身有明確的語義邊界。如果準確率低于50%說明參數(shù)太寬松需要調高min_pmi或min_entropy如果在80%以上說明參數(shù)基本合適可以進一步微調如果準確率很高但召回明顯不足遺漏了大量明顯的新詞則需要考慮降低min_freq或適當擴大max_word_len。7. 常見問題與排查技巧實錄7.1 編譯報錯Atlas最常見的編譯錯誤就是C11標準不支持。如果你用的是老舊的g 4.8編譯時會報出一堆“xx is not a member of std”之類的錯誤。解決方案是升級g或者安裝高版本的build-essential工具鏈。另外如果系統(tǒng)里同時存在多個gcc版本可能需要手動指定編譯器版本./build.sh \ CC/usr/bin/gcc-5 \ CXX/usr/bin/g-57.2 訓練結果幾乎全是噪聲如果你發(fā)現(xiàn)挖出來的“新詞”全是“的了我們在有”這類詞大概率是min_freq設置得太低。頻次小于5的N-gram組合統(tǒng)計上根本不具備顯著性尤其在小語料上更是如此。先把min_freq調到10以上再把min_pmi調到20以上結果通常會有質的改善。7.3 一個領域詞都挖不出來和上面相反如果語料里明顯有很多專業(yè)術語但一個都沒挖出來那多半是閾值設太高了。先檢查min_pmi垂直領域語料中術語的PMI一般很高但有些簡寫和組合詞PMI并不高可以先把min_pmi降到5~10看輸出結果再做判斷。另外檢查一下max_word_len是不是太短很多術語是4個字以上的。7.4 性能瓶頸Atlas默認是單線程的嗎不是但它也不會自動跑滿所有核。你需要顯式設置thread_num參數(shù)。我習慣設為CPU核數(shù)跑起來之后CPU利用率基本可以拉滿。如果你有32核thread_num32幾GB的語料大概幾十分鐘跑完。這里有個小技巧如果語料實在太大、內存吃緊可以先按行隨機采樣一部分語料做參數(shù)探索確定好閾值后再全量跑能節(jié)省不少時間。7.5 常見問題速查表現(xiàn)象可能原因解決方案編譯失敗C11報錯g版本過低升級到g 5.4以上全是通用虛詞min_freq太低或min_entropy太低提高min_freq到10min_entropy到1.5什么都挖不出來閾值過高或語料太小降低min_pmi/min_entropy增加語料量訓練太慢單線程跑顯式設置thread_num為CPU核數(shù)結果里有交叉重復片段沒有用基礎詞典過濾傳入dic_path把已知詞過濾掉含大量英文/數(shù)字串語料清洗不足預處理階段做文本規(guī)范化8. 模型的增量訓練與分詞語料產出8.1 增量訓練的使用場景Atlas還支持增量訓練模式train_mode2這個功能一開始我沒太在意后來在業(yè)務迭代時發(fā)現(xiàn)它非常有用。場景是這樣的第一次用一季度的語料訓練出一個新詞模型到了季度末積累了新的語料如果從零開始重新訓練每次都要全量跑一遍耗時耗資源。用增量訓練模式可以基于已有的模型只對新增語料做學習把新出現(xiàn)的高頻詞補充進模型里。增量訓練的命令和train類似關鍵是train_mode設為2并指定已有的model_dir作為初始模型./build/atlas train \ --train_filenew_corpus.txt \ --model_dir./existing_model \ --train_mode2 \ --min_freq5 \ --min_pmi15 \ --min_entropy1.2 \ --thread_num16增量訓練模式下模型會保留原有詞表同時把新增語料中挖掘出的新詞合并進來。這個機制在實際業(yè)務中非常實用尤其是語料按周、按月持續(xù)產出的場景不需要每次都全量重跑。8.2 用segment子命令做分詞擴展訓練好的詞表除了用于新詞盤點還可以直接用于分詞。Atlas的segment子命令加載訓練好的模型對新的文本進行分詞——它的分詞結果會優(yōu)先使用訓練階段學到的詞因此對垂直領域語料的效果往往比通用分詞器更好。./build/atlas segment \ --model_dir./news_model \ --inputtest.txt \ --outputseg_result.txt這個功能很適合做前置處理先用少量語料訓練一個領域詞表然后把這個模型當作一個定制分詞器對全量語料進行切分。比起通用分詞器自定義詞典的方案Atlas的方案更省心——因為它不需要你手動維護一個龐大的領域詞典模型自己會從語料中不斷學習。9. 我的經驗總結與調參實戰(zhàn)建議這套工具我用了很長一段時間踩過的坑不少但沉淀下來的方法論也越來越清晰。最核心的一條經驗是先粗后細兩輪訓練定參數(shù)。第一輪用寬松參數(shù)min_freq3min_pmi5min_entropy0.8跑一遍目標是看全貌——知道語料里大概有哪些潛在的詞候選集大一點沒關系畢竟只是摸底。第二輪根據(jù)第一輪的輸出質量收緊參數(shù)比如min_freq8min_pmi20min_entropy1.5目標是跑出真正高質量的新詞表。這個方法比一上來就猜最優(yōu)參數(shù)要高效得多因為不同語料的統(tǒng)計特征差異太大你根本沒法憑空判斷哪個閾值是“最優(yōu)”的。另外一個經驗是不要過度依賴單一指標。互信息和左右熵各自只能反映“詞”的一個側面組合使用才能互相彌補。如果你發(fā)現(xiàn)某些很明顯的新詞沒被挖出來可能就是因為它的左熵或右熵偏低比如經常跟在同一個詞后面這時候可以適當放寬min_entropy然后用頻次排序來做二次篩選。反過來如果噪聲多優(yōu)先提PMI而不是提熵值——提熵值容易誤傷真實新詞提PMI則比較精準。最后關于中文編碼務必確保輸入文件是UTF-8編碼否則會出現(xiàn)亂碼甚至程序崩潰。如果你手上的語料是GBK編碼的先用iconv轉一下iconv -f GBK -t UTF-8 source.txt target.txt這一步雖然簡單但真的會卡住很多人——我自己就在這上面浪費過不少時間。如果你準備在自己的業(yè)務里試一下Atlas我的建議是先從一份50MB左右的干凈語料開始把上面這些參數(shù)跑一遍感受一下每個參數(shù)對結果的影響。數(shù)據(jù)量不大迭代成本低你能比較快地建立起對這套工具的直覺。等摸清楚它的脾氣之后再上大語料、做增量訓練、接入生產流程都會順很多。