雖然英文中有天然的單詞分隔符(空格),但是常有單詞與其他標點黏滯的情況,比如"Hey, how are you."中的"Hey"和"you"是需要與身後的標點分隔開的
為什麼需要分詞?
能不能不分詞?
中文分詞難在哪?
從古至今的分詞算法原理
從中到外的分詞工具
1 歧義問題
2 未登錄詞問題
3 規範問題
比如,我們可以直接從句子開頭的第一個字開始查字典,找出字典中以該字開頭的最長的單詞,然後就得到了第一個切分好的詞。比如這句「夕小瑤正在講NLP」,查字典發現「夕小瑤」是最長的詞了,於是得到
夕小瑤/正在講NLP
然後從下一個詞的開頭開始繼續匹配字典,發現「正在」就是最長的詞了,於是
夕小瑤/正在/講NLP
依此類推,最終得到
夕小瑤/正在/講/NLP
這種簡單的算法即為前向最大匹配法(FMM)
雖然做法很樸素,但是名字聽起來還有點高端╮(╯▽╰)╭不過,由於中文句子本身具有重要信息後置的特點,從後往前匹配的分詞正確率往往要高於從前往後,於是就有了反向進行的「後向最大匹配法(BMM)」。當然了,無論是FMM還是BMM,都一定存在不少切分錯誤,因此一種考慮更周到的方法是「雙向最大匹配」。雙向最大匹配算法是指對待切分句子分別使用FMM和RMM進行分詞,然後對切分結果不重合的歧義句進行進一步的處理。通常可對兩種方法得到的詞彙數目進行比較,根據數目的相同與否採取相應的措施,以此來降低歧義句的分詞錯誤率.2 基於統計
2.1 基於語言模型
基於詞典的方法雖然簡單,但是明顯能看出來太!不!智!能!了!稍微複雜一些的句子,例如「沒關係,除夕小瑤在家做飯。」,這時候如果使用後向最大匹配法,就會切分成「沒關係/,/除/夕小瑤/在家/做飯/。」,這明顯錯的很不可原諒。犯這種錯誤的根本原因在於,基於詞典的方法在切分時是沒有考慮詞語所在的上下文的,沒有從全局出發找最優解。其實上面這個句子無非就是在糾結兩種切分方式:
我們日常說話中很少會有人說出「沒關係/,/除/xxxx/做飯/。」這種句子,而第二個句子出現的頻率則會非常高,比如裡面的「小瑤」可以替換成「我」、「老王」等。顯然給定一個句子,各種切分組合是數量有限的,如果有一個東西可以評估出任何一個組合的存在合理性的分值,那麼不就找到了最佳的分詞組合嘛!
所以,這種方法的本質就是在各種切詞組合中找出那個最合理的組合,這個過程就可以看作在切分詞圖中找出一條概率最大的路徑:
而這個可以給詞序列存在合理性打分的東西就叫做「語言模型」(language model)。這種利用語言模型來評估各種切分組合的方法是不是就顯得智能多啦╮(╯▽╰)╭給定一個句子分詞後得到的單詞序列{w1,w2...wm},語言模型就能計算出這個句子(或者說詞序列)存在的可能性:顯然,當m取值稍微一大,乘法鏈的後面幾項會變得非常難計算(估計出這幾項的概率需要依賴極其龐大的語料才能保證估計誤差可接受)。計算困難怎麼辦?當然是用合理的假設來簡化計算,比如我們可以假設當前位置取什麼詞僅取決於相鄰的前面n個位置,即這種簡化的語言模型就稱為n-gram語言模型。這樣乘法鏈中的每個乘子都可以在已經完成人工標註的分詞語料中計算得到啦。當然了,在實際計算中可能還會引入一些平滑技巧,來彌補分詞語料規模有限導致的估計誤差,這裡就不展開講啦。
2.2 基於統計機器學習
NLP是一門跟機器學習強綁定的學科,分詞問題自然也不例外。中文分詞同樣可以建模成一個「序列標註」問題,即一個考慮上下文的字分類問題。因此可以先通過帶標籤的分詞語料來訓練一個序列標註模型,再用這個模型對無標籤的語料進行分詞。
樣本標籤
一般用{B:begin, M:middle, E:end, S:single}這4個類別來描述一個分詞樣本中每個字所屬的類別。它們代表的是該字在詞語中的位置。其中,B代表該字是詞語中的起始字,M代表是詞語中的中間字,E代表是詞語中的結束字,S則代表是單字成詞。人/b 們/e 常/s 說/s 生/b 活/e 是/s 一/s 部/s 教/b 科/m 書/e之後我們就可以直接套用統計機器學習模型來訓練出一個分詞器啦。統計序列標註模型的代表就是生成式模型的代表——隱馬爾可夫模型(HMM),和判別式模型的代表——(線性鏈)條件隨機場(CRF)。已經對這兩個模型很熟悉的小夥伴可以跳過。
隱馬爾可夫模型(HMM)
HMM模型的詳細介紹見
《如果你跟夕小瑤戀愛了... (上)》 / 《下》
舉個慄子!
那麼問題又來了,假如一個完美的HMM分詞模型給你了,那麼如何用這個模型對輸入的字序列進行序列標註呢?首先看下HMM模型中的兩個核心概念:觀測序列和狀態序列。觀測序列就是我可以直接看到的序列,也就是「小Q碩士畢業於中國科學院」這個字序列,而狀態序列則是不能通過肉眼直接觀察到的內在序列,也就是上面這句話所對應的標註結果「BEBEBMEBEBME」,而我們的HMM模型,就可以幫助我們完成從觀測序列->狀態序列的華麗變身!
用數學抽象表示如下:用 代表輸入的句子,n為句子長度,表示字, 代表輸出的標籤,那麼理想的輸出即為:
我們的理想輸出的是 ,通過貝葉斯公式能夠得到:
最後,求解 的常用方法是Veterbi算法。它是一種動態規劃方法,核心思想是:如果最終的最優路徑經過某個 ,那麼從初始節點到 點的路徑必然也是一個最優路徑。當然啦,這一切的前提是HMM中的模型參數都已經被訓練好了,而訓練這些模型參數可以通過萬能的極大似然估計來從有標籤的分詞語料中學習得到,這裡就不展開贅述啦,對細節不清楚的小夥伴可參考本小節開頭給出的兩篇文章。
條件隨機場 (CRF)
HMM隱馬模型有一個非常大的缺點,就是其存在輸出獨立性假設,導致其不能將上下文納入特徵設計,大大限制了特徵的可用範圍。CRF則沒有這個限制,它不對單獨的節點進行歸一化,而是對所有特徵進行全局歸一化,進而全局的最優值。因此,在分詞問題上,顯然作為判別式模型的CRF相比HMM更具優越性。
HMM模型圍繞的是一個關於序列X和Y的聯合概率分布𝑃(𝑋,𝑌),而條件隨機場則圍繞條件概率分布模型𝑃(𝑌|𝑋)展開。
從圖中我們可以看出模型之間的遞進關係,CRF是一個概率無向圖模型,它和HMM很類似,但是CRF沒有隱變量,並且是一個判別模型。使用CRF計算序列中的每個字位𝑥𝑖 對應的分詞標籤𝑦𝑖 時,都可以看做是一次考慮上下文依賴關係的分類。
3 基於神經網絡
3.1 基於(Bi-)LSTM
對LSTM模型還不熟悉的小夥伴見小夕以前寫的這篇的《step-by-step to LSTM》,本文對lstm的基本理論不再贅述啦。
如前面語言模型一節中所述,字的上下文信息對於排解切分歧義來說非常重要,能考慮的上下文越長,自然排解歧義的能力就越強。而前面的n-gram語言模型也只能做到考慮一定距離的上下文,那麼有沒有在理論上能考慮無限長上下文距離的分詞模型呢?當然啦,LSTM是有方向的,為了讓每個位置的字分類時既能考慮全部歷史信息(左邊的所有的字),又能考慮全部未來信息(右邊所有的字),我們可以使用雙向LSTM(Bi-LSTM)來充當序列標註的骨架模型,如圖LSTM完成對每個位置的上下文信息的編碼後,最終通過softmax分類層完成對每個位置的分類,從而跟HMM和CRF一樣完成了基於序列標註的中文分詞。
3.2 基於預訓練模型+知識蒸餾
最近的一年多的時間裡,BERT、ERNIE、XLNet等大型預訓練席捲了NLP的絕大部分領域,在分詞問題上也有顯著的優越性。
然而,眾所周知,預訓練模型太大了,過於消耗計算資源,如果要對海量的文本進行分詞,哪怕用上8卡的32G Tesla V100都會顯得力不從心,因此一種解決方案就是,將預訓練模型中的分詞知識通過知識蒸餾(Knowledge Distillation)來遷移到小模型(比如LSTM、GRU)上。近期Jieba分詞器中就上線了這麼一個用這種方法得到的先進分詞模型(其實是個通用的詞法分析模型),感興趣的小夥伴可以自行了解一下。預訓練模型和知識蒸餾的資料很多了,這裡就不贅述啦。
#encoding=utf-8#Jieba#pip install jiebaimport jieba
sentence = "不會講課的程式設計師不是一名好的算法工程師"tokens = jieba.cut(sentence)print("jieba: " + " ".join(tokens))
#output#Building prefix dict from the default dictionary ...#Loading model from cache /tmp/jieba.cache#Loading model cost 0.266 seconds.#Prefix dict has been built successfully.#jieba: 不會 講課 的 程式設計師 不是 一名 好 的 算法 工程
2 THULAC(THU Lexical Analyzer for Chinese)
由清華大學自然語言處理與社會人文計算實驗室研製推出的一套中文詞法分析工具包,具有中文分詞和詞性標註功能。該工具所採用的分詞模型為結構化感知機。更多算法細節請參考github項目和閱讀論文原文。
github項目地址:https://github.com/thunlp/THULAC
論文連結:https://www.mitpressjournals.org/doi/pdf/10.1162/coli.2009.35.4.35403
使用示例:
#THULAC#pip install thulacimport thulac
sentence = "不會講課的程式設計師不是一名好的算法工程師"thu1 = thulac.thulac(seg_only=True) text = thu1.cut(sentence, text=True) print("THULAC: " + text)
#output#Model loaded succeed#THULAC: 不 會 講課 的 程式設計師 不 是 一 名 好 的 算法 工程師
3 NLPIR-ICTCLAS漢語分詞系統
北京理工大學海量語言信息處理與雲計算工程研究中心大數據搜索與挖掘實驗室( Big Data Search and Mining Lab.BDSM@BIT)發布。是基於層次HMM的分詞庫,將分詞、POS、NER等都納入到了一個層次HMM的框架之下聯合訓練得到。主頁:http://ictclas.nlpir.org/github項目地址:https://github.com/tsroten/pynlpir#NLPIR-ICTCLAS#pip install pynlpirimport pynlpir
sentence = "不會講課的程式設計師不是一名好的算法工程師"pynlpir.open()tokens = [x[0] for x in pynlpir.segment(sentence)]print("NLPIR-TCTCLAS: " + " ".join(tokens))pynlpir.close()
#output#NLPIR-TCTCLAS: 不 會 講課 的 程式設計師 不 是 一 名 好 的 算法 工程
4 LTP
哈工大出品,同THULAC一樣,LTP也是基於結構化感知器(Structured Perceptron, SP),以最大熵準則學習的分詞模型。項目主頁:https://www.ltp-cloud.com/github項目地址:https://github.com/HIT-SCIR/ltp論文連結:http://jcip.cipsc.org.cn/CN/abstract/abstract1579.shtml使用示例:使用前需下載分詞模型(http://ltp.ai/download.html)
#LTP#pip install pyltpfrom pyltp import Segmentor
sentence = "不會講課的程式設計師不是一名好的算法工程師"segmentor = Segmentor()#segmentor.load("/path/to/your/cws/model")segmentor.load("ltp_data_v3.4.0/cws.model")tokens = segmentor.segment(sentence)print("LTP: " + " ".join(tokens))segmentor.release()
#output#LTP: 不 會 講課 的 程式設計師 不 是 一 名 好 的 算法 工程
5 HanLP
HanLP是隨《自然語言處理入門》配套開源的一系列NLP算法庫。除了經典的1.x版本在不斷迭代更新以外,今年還全新推出了2.0版本。1.x版本有有基於詞典的分詞工具和基於CRF的切詞模型。2.0版本開源了基於深度學習算法的分詞工具。github項目地址:https://github.com/hankcs/pyhanlp#HanLP#v1.x#pip install pyhanlpfrom pyhanlp import *
sentence = "不會講課的程式設計師不是一名好的算法工程師"print(HanLP.segment(sentence))github地址:https://github.com/hankcs/HanLP/tree/doc-zh
#HanLP#v2.0#pip install hanlpimport hanlp
sentence = "不會講課的程式設計師不是一名好的算法工程師"tokenizer = hanlp.load('PKU_NAME_MERGED_SIX_MONTHS_CONVSEG')tokens = tokenizer(sentence)print("hanlp 2.0: " + " ".join(tokens))#output#hanlp 2.0: 不 會 講課 的 程式設計師 不 是 一 名 好 的 算法 工程
6 Stanford CoreNLP
斯坦福推出的切詞工具,可以支持多種語言。算法核心是基於CRF模型。github項目地址:https://github.com/Lynten/stanford-corenlp論文連結:https://nlp.stanford.edu/pubs/sighan2005.pdf使用示例:需要先從stanford官網下載中文切詞模型(https://stanfordnlp.github.io/CoreNLP/)###stanford CoreNLP#pip install stanfordcorenlpfrom stanfordcorenlp import StanfordCoreNLP
sentence = "不會講課的程式設計師不是一名好的算法工程師"with StanfordCoreNLP(r'stanford-chinese-corenlp-2018-10-05-models', lang='zh') as nlp: print("stanford: " + " ".join(nlp.word_tokenize(sentence)))