中文分詞演進(查詞典,hmm標注,無監督統計)新詞發現

查詞典和字標注

目前中文分詞主要有兩種思路:查詞典和字標注。

  • 首先,查詞典的方法有:機械的最大匹配法、最少詞數法,以及基于有向無環圖的最大概率組合,還有基于語言模型的最大概率組合,等等。
    查詞典的方法簡單高效(得益于動態規劃的思想),尤其是結合了語言模型的最大概率法,能夠很好地解決歧義問題,但對于中文分詞一大難度——未登錄詞(中文分詞有兩大難度:歧義和未登錄詞),則無法解決;
  • 為此,人們也提出了基于字標注[BIOS, BME]的思路,所謂字標注,就是通過幾個標記(比如4標注的是:single,單字成詞;begin,多字詞的開頭;middle,三字以上詞語的中間部分;end,多字詞的結尾),把句子的正確分詞法表示出來。這是一個序列(輸入句子)到序列(標記序列)的過程,能夠較好地解決未登錄詞的問題,但速度較慢,而且對于已經有了完備詞典的場景下,字標注的分詞效果可能也不如查詞典方法。
    總之,各有優缺點(似乎是廢話~),實際使用可能會結合兩者,像結巴分詞,用的是有向無環圖的最大概率組合,而對于連續的單字,則使用字標注的HMM模型來識別。

1 中文分詞

1.1 查詞典優化之AC自動機

1.1.1 AC 自動機

本文首先要實現的是查詞典方法的分詞。1、給定一批詞,查找給定句子中是不是含有這個詞;2、如果有的話,怎么解決歧義性問題。
第一步,在計算機中稱為“多模式匹配”。
這步看上去簡單,但事實上要高效地實現并不容易。
一個完備的詞典,少說也有十幾萬詞語,如果一個個枚舉查找,那計算量是吃不消的,事實上我們在查字典的時候,會首先看首字母,然后只在首字母相同的那一塊找,然后又比較下一個字母,依此下去。
這需要兩個條件:
1、一個做好特殊排序的詞典;我們有所謂的前綴樹(trie)
2、有效的查找技巧,我們有一些經典的算法,比如AC自動機(Aho and Corasick)。

對于AC自動機,就是一個使用了trie數據結構的高效多模式匹配算法。
我們也不用親自實現它,因為Python已經有對應的庫了:pyahocorasick。
因此,我們只需要關心怎么使用它就行了。
官方的教程已經很詳細地介紹了pyahocorasick的基本使用方法了,這里也不贅述。(遺憾的是,雖然pyahocorasick已經同時支持python2和python3了,但是在python2中,它只支持bytes字符串不支持unicode字符串,而在python3中,則默認使用unicode編碼,這對我們寫程度會帶來一點困惑,當然,不是本質性的。)
pyahocorasick構建AC自動機有一個很人性化的地方,它能夠以“鍵-注釋”這樣成對的形式添加詞匯(請留意dic.add_word(i, (i, log(j/total)))這一句),這樣,我們可以在注釋這里,添加我們想要的信息,比如頻數、詞性等,然后在查找的時候會一并返回。有了上述AC自動機,我們就能很方便地構建一個全模式分詞,也就是把詞典中有的詞都掃描出來(其實這本來就是AC自動機的本職工作)。

構建一個基于AC自動機的分詞系統,首先需要有一個文本詞典,假設詞典有兩列,每一行是詞和對應的頻數,用空格分開。那么就可以構建一個AC自動機。

1.1.3 最大匹配法

最大匹配法是指從左到右逐漸匹配詞庫中的詞語,匹配到最長的詞語為止。
上面說的最大匹配法,準確來說是“正向最大匹配法”,類似地,還有“逆向最大匹配法”,顧名思義,是從右到左掃描句子進行最大匹配,效果一般比正向最大匹配要好些。如果用AC自動機來實現,唯一的辦法就是對詞典所有的詞都反序存儲,然后對輸入的句子也反序,然后進行正向最大匹配了。

def max_match_cut(sentence):sentence = sentence.decode('utf-8')words = ['']for i in sentence:i = i.encode('utf-8')if dic.match(words[-1] + i):words[-1] += ielse:words.append(i)return words

1.1.4 最大概率組合

基于最大概率組合的方法,是目前兼顧了速度和準確率的比較優秀的方法。它說的是:對于一個句子,如果切分為詞語w1,w2,…,wn是最優的切分方案,那么應該使得下述概率最大:P(w1,w2,…,wn)

直接估計這概率是不容易的,一般用一些近似方案,比如
P(w1,w2,…,wn)≈P(w1)P(w2|w1)P(w3|w2)…P(wn|wn?1)
這里P(wk|wk?1)就稱為語言模型,它已經初步地考慮了語義了。當然,普通分詞工具是很難估計P(wk|wk?1)的,一般采用更加簡單的近似方案。P(w1,w2,…,wn)≈P(w1)P(w2)P(w3)…P(wn)
放到圖論來看,這就是有向無環圖里邊的最大概率路徑了。

def max_proba_cut(sentence):paths = {0:([], 0)}end = 0for i,j in dic.iter(sentence):start,end = 1+i-len(j[0]), i+1if start not in paths:last = max([i for i in paths if i < start])paths[start] = (paths[last][0]+[sentence[last:start]], paths[last][1]-10)proba = paths[start][1]+j[1]if end not in paths or proba > paths[end][1]:paths[end] = (paths[start][0]+[j[0]], proba)if end < len(sentence):return paths[end][0] + [sentence[end:]]else:return paths[end][0]
def min_words_cut(sentence):paths = {0:([], 0)}end = 0for i,j in dic.iter(sentence):start,end = 1+i-len(j[0]), i+1if start not in paths:last = max([i for i in paths if i < start])paths[start] = (paths[last][0]+[sentence[last:start]], paths[last][1]+1)num = paths[start][1]+1if end not in paths or num < paths[end][1]:paths[end] = (paths[start][0]+[j[0]], num)if end < len(sentence):return paths[end][0] + [sentence[end:]]else:return paths[end][0]

1.2 字標注

1.2.1 HMM

對于字標注的分詞方法來說,輸入就是n個字,輸出就是n個標簽。我們用λ=λ1λ2…λn表示輸入的句子,o=o1o2…on表示輸出。

那什么是最優的輸出呢?從概率的角度來看,我們當然希望下面的條件概率最大
maxP(o|λ)=maxP(o1o2…on|λ1λ2…λn)
換句話說,o有很多可能性,而最優的o應該是最大概率的o。

要注意,P(o|λ)是關于2n個變量的條件概率,而且n還是不定的。這種情況下,我們幾乎不可能對P(o|λ)進行精確的建模。即便如此,我們可以稍微做些簡化,比如,如果我們假設每個字的輸出僅僅與當前字有關,那么我們就有P(o1o2…on|λ1λ2…λn)=P(o1|λ1)P(o2|λ2)…P(on|λn)
而估計P(ok|λk)就容易多了。這時候問題也得到很大簡化,因為要使得P(o|λ)最大,只需要每個P(ok|λk)最大就行。我們所做的假設,就稱為獨立性假設

以上簡化是一種方案,但是完全沒有考慮到上下文,而且會出現不合理的情況(比如按照我們的4標注,那么b后面只能接m或e,但是按照這種最大的方法,我們可能得到諸如bbb的輸出,這是不合理的)。于是我們就反過來,提出了一種隱含的模型(隱馬爾可夫模型),就如同數學中的函數與反函數一般,反過來思考。

由貝葉斯公式,我們得到P(o|λ)=P(o,λ)P(λ)=P(λ|o)P(o)P(λ)
由于λ是給定的輸入,那么P(λ)就是常數,可以忽略。那么最大化P(o|λ)就等價于最大化P(λ|o)P(o)
現在,我們可以對P(λ|o)作獨立性假設,得到P(λ|o)=P(λ1|o1)P(λ2|o2)…P(λn|on)
同時,對P(o)有P(o)=P(o1)P(o2|o1)P(o3|o1,o2)…P(on|o1,o2,…,on?1)
這時候可以作一個馬爾可夫假設:每個輸出僅僅于上一個輸出有關,那么:
P(o)=P(o1)P(o2|o1)P(o3|o2)…P(on|on?1)~P(o2|o1)P(o3|o2)…P(on|on?1)
這時候P(λ|o)P(o)~P(λ1|o1)P(o2|o1)P(λ2|o2)P(o3|o2)…P(on|on?1)P(λn|on)

我們稱P(λk|ok)為發射概率,P(ok|ok?1)為轉移概率。這時候,可以通過設置某些P(ok|ok?1)=0
,來排除諸如bb、bs這些不合理的組合。

可以看到,HMM對問題作了大量的簡化,簡化到不可能十分精確,因此,HMM模型一般都是用來解決“在查詞典方法的過程中不能解決的部分”(就好比結巴分詞所做的)。當然,你可以把馬爾可夫假設加強——比如假設每個狀態跟前面兩個狀態有關,那樣肯定會得到更精確的模型,但是模型的參數就更難估計了。

怎么訓練一個HMM分詞模型?主要就是P(λk|ok)和P(ok|ok?1)這兩部分概率的估計了。如果有一批標注語料,那么估計這兩個概率應該不難,但是如果沒有呢?有一個詞典也湊合。我們可以將一個帶有頻數的詞典轉化為一個HMM模型,

代碼的第一部分,就是用一個字典來表示P(λk|ok),P(λk|ok)的計算是通過詞典來獲取的,比如將詞典中所有的一字詞都計入s標簽下,把多字詞的首字都計入b標簽下,等等。計算過程中還使用了對數概率,防止溢出;
第二部分的轉移概率,直接根據直覺估算的;
第三部分就是通過viterbi算法動態規劃,求的最大概率路徑了。對于概率估算,簡單采用了加1平滑法,沒出現的單字都算1次。

from collections import Counter
from math import loghmm_model = {i:Counter() for i in 'sbme'}with open('dict.txt') as f:for line in f:lines = line.decode('utf-8').split(' ')if len(lines[0]) == 1:hmm_model['s'][lines[0]] += int(lines[1])else:hmm_model['b'][lines[0][0]] += int(lines[1])hmm_model['e'][lines[0][-1]] += int(lines[1])for m in lines[0][1:-1]:hmm_model['m'][m] += int(lines[1])log_total = {i:log(sum(hmm_model[i].values())) for i in 'sbme'}trans = {'ss':0.3,'sb':0.7,'bm':0.3,'be':0.7, 'mm':0.3,'me':0.7,'es':0.3,'eb':0.7}trans = {i:log(j) for i,j in trans.iteritems()}def viterbi(nodes):paths = nodes[0]for l in range(1, len(nodes)):paths_ = pathspaths = {}for i in nodes[l]:nows = {}for j in paths_:if j[-1]+i in trans:nows[j+i]= paths_[j]+nodes[l][i]+trans[j[-1]+i]k = nows.values().index(max(nows.values()))paths[nows.keys()[k]] = nows.values()[k]return paths.keys()[paths.values().index(max(paths.values()))]def hmm_cut(s):nodes = [{i:log(j[t]+1)-log_total[i] for i,j in hmm_model.iteritems()} for t in s]tags = viterbi(nodes)words = [s[0]]for i in range(1, len(s)):if tags[i] in ['b', 's']:words.append(s[i])else:words[-1] += s[i]return words

字標注法有效有兩個主要的原因,
第一個原因是它將分詞問題變成了一個序列標注問題,而且這個標注是對齊的,也就是輸入的字跟輸出的標簽是一一對應的,這在序列標注中是一個比較成熟的問題;
第二個原因是這個標注法實際上已經是一個總結語義規律的過程,以4tag標注為為例,我們知道,“李”字是常用的姓氏,一半作為多字詞(人名)的首字,即標記為b;而“想”由于“理想”之類的詞語,也有比較高的比例標記為e,這樣一來,要是“李想”兩字放在一起時,即便原來詞表沒有“李想”一詞,我們也能正確輸出be,也就是識別出“李想”為一個詞,也正是因為這個原因,即便是常被視為最不精確的HMM模型也能起到不錯的效果。

關于標注,還有一個值得討論的內容,就是標注的數目。常用的是4tag,事實上還有6tag和2tag,
而標記分詞結果最簡單的方法應該是2tag,即標記“切分/不切分”就夠了,但效果不好。為什么反而更多數目的tag效果更好呢?因為更多的tag實際上更全面概括了語義規律。比如,用4tag標注,我們能總結出哪些字單字成詞、哪些字經常用作開頭、哪些字用作末尾,但僅僅用2tag,就只能總結出哪些字經常用作開頭,從歸納的角度來看,是不夠全面的。但6tag跟4tag比較呢?我覺得不一定更好,6tag的意思是還要總結出哪些字作第二字、第三字,但這個總結角度是不是對的?我覺得,似乎并沒有哪些字固定用于第二字或者第三字的,這個規律的總結性比首字和末字的規律弱多了(不過從新詞發現的角度來看,6tag更容易發現長詞。)

1.2.2 字標注之雙向LSTM的seq2seq

關于深度學習與分詞,很早就有人嘗試過了,比如下列文章:
http://blog.csdn.net/itplus/article/details/13616045
https://github.com/xccds/chinese_wordseg_keras
http://www.leiphone.com/news/201608/IWvc75oJglAIsDvJ.html

這些文章中,不管是用簡單的神經網絡還是LSTM,它們的做法都跟傳統模型是一樣的,都是通過上下文來預測當前字的標簽,這里的上下文是固定窗口的,比如用前后5個字加上當前字來預測當前字的標簽。這種做法沒有什么不妥之處,但僅僅是把以往估計概率的方法,如HMM、ME、CRF等,換為了神經網絡而已,整個框架是沒變的,本質上還是n-gram模型。而有了LSTM,LSTM本身可以做序列到序列(seq2seq)的輸出,因此,為什么不直接輸出原始句子的序列呢?這樣不就真正利用了全文信息了嗎?這就是本文的嘗試。

LSTM可以根據輸入序列輸出一個序列,這個序列考慮了上下文的聯系,因此,可以給每個輸出序列接一個softmax分類器,來預測每個標簽的概率。基于這個序列到序列的思路,我們就可以直接預測句子的標簽。

1.3 基于語言模型的無監督分詞

從最大概率法出發,如果一個長度為l的字符串s1,s2,…,sl,最優分詞結果為w1,w2,…,wm,那么它應該是所有切分中,概率乘積p(w1)p(w2)…p(wm)最大
假如沒有詞表,自然也就不存在w1,w2,…,wm這些詞了。但是,我們可以用貝葉斯公式,將詞的概率轉化為字的組合概率:p(w)=p(c1)p(c2|c1)p(c3|c1c2)…p(ck|c1c2…ck?1)其中w是一個k字詞,c1,c2,…,ck分別是w的第1,2,…,k個字。可以發現,p(ck|c1c2…ck?1)就是我們前面提到過的字的語言模型。
如果s1,s2應該合并為一個詞,那么它的路徑概率是p(s1s2)p(s3)…p(sl)=p(s1)p(s2|s1)p(s3)…p(sl)
對于句子中的一個字sk來說,就有
p(b)=p(sk), 單字詞或者多字詞的首字
p?=p(sk|sk?1),多字詞的第二字
p(d)=p(sk|sk?2sk?1), 多字詞的第三字
p(e)=p(sk|sk?3sk?2sk?1),多字詞的其余部分
這就是將分詞問題變成了一種字標注問題,而每個標簽的概率由語言模型給出。而且,顯然b后面只能接b或者c,類似地,就得到非零的轉移概率只有:p(b|b),p(c|b),p(b|c),p(d|c),p(b|d),p(e|d),p(b|e),p(e|e)

這些轉移概率的值,決定了劃分出來的是長詞還是短詞。最后找最優路徑,依舊由viterbi算法完成。

到這里,問題就變成了語言模型的訓練了,這是無監督的。我們只需要花心思優化語言模型,而這方面不論是理論還是實戰都已經很成熟了,有不少現成的工具可以用。簡單地可以只用傳統的“統計+平滑”模型,如果要從語義來做,那么就可以用最新的神經語言模型。總而言之,分詞的效果,取決于語言模型的質量。

2 新詞發現

2.1 怎么做新詞發現

參考:互聯網時代的社會語言學:基于SNS的文本數據挖掘
怎樣的文本片段才算一個詞?
1.看這個文本片段出現的次數是否足夠多。
2.不過,光是出現頻數高還不夠,一個經常出現的文本片段有可能不是一個詞,而是多個詞構成的詞組。
內部凝固程度:令 p(x) 為文本片段 x 在整個語料中出現的概率,那么我們定義“電影院”的凝合程度就是 p(電影院) 與 p(電) · p(影院) 比值和 p(電影院) 與 p(電影) · p(院) 的比值中的較小值,“的電影”的凝合程度則是 p(的電影) 分別除以 p(的) · p(電影) 和 p(的電) · p(影) 所得的商的較小值。
3.我們還需要從整體來看它在外部的表現。文本片段的自由運用程度也是判斷它是否成詞的重要標準。如果一個文本片段能夠算作一個詞的話,它應該能夠靈活地出現在各種不同的環境中,具有非常豐富的左鄰字集合和右鄰字集合。
我們用信息熵來衡量一個文本片段的左鄰字集合和右鄰字集合有多隨機。考慮這么一句話“吃葡萄不吐葡萄皮不吃葡萄倒吐葡萄皮”,“葡萄”一詞出現了四次,其中左鄰字分別為 {吃, 吐, 吃, 吐} ,右鄰字分別為 {不, 皮, 倒, 皮} 。根據公式,“葡萄”一詞的左鄰字的信息熵為 – (1/2) · log(1/2) – (1/2) · log(1/2) ≈ 0.693 ,它的右鄰字的信息熵則為 – (1/2) · log(1/2) – (1/4) · log(1/4) – (1/4) · log(1/4) ≈ 1.04 。可見,在這個句子中,“葡萄”一詞的右鄰字更加豐富一些。
我們不妨就把一個文本片段的自由運用程度定義為它的左鄰字信息熵和右鄰字信息熵中的較小值。

主要利用了三個指標——頻數、凝固度(取對數之后就是我們所說的互信息熵)、自由度(邊界熵)——來判斷一個片段是否成詞

缺點:首先,為了得到n字詞,就需要找出1~n字的切片,然后分別做計算,這對于n比較大時,是件痛苦的時間;其次,最最痛苦的事情是邊界熵的計算,邊界熵要對每一個片段就行分組統計,然后再計算,這個工作量的很大的。本文提供了一種方案,可以使得新詞發現的計算量大大降低。

2.2 一個新的方法

新詞發現做的事情,就是根據語料判斷給定片段是不是真的成詞了,而所謂成詞,就是它相對獨立,不可切分。那為什么不反過來呢?為什么我們不去找一下哪些片段不能成詞呢?根據前面的說法,我們說片段的凝固度大于一定程度時,片段可能成詞(接下來要去考慮它的邊界熵)。那這不就是說,如果片段的凝固度低于一定程度時,這個片段就不可能成詞了嗎?那么我們就可以在原來的語料中把它斷開了。

我們可以做適當的簡化,如果a,b是語料中相鄰兩字,那么可以統計(a,b)成對出現的次數#(a,b),繼而估計它的頻率P(a,b),然后我們分別統計a,b出現的次數#a,#b,然后估計它們的頻率P(a),P(b),如果在這里插入圖片描述
那么就應該在原來的語料中把這兩個字斷開。這個操作本質上就是——我們根據這個指標,對原始語料進行初步的分詞!在完成初步分詞后,我們就可以統計詞頻了,然后根據詞頻來篩選。

對比matrix67文章中的三個指標,我們現在只用了兩個:頻數和凝固度,去掉了計算量最大的邊界熵,而且,在計算凝固度時,我們只需要計算二字片段的凝固度,省掉了更多字片段的凝固度計算,但是,由于我們是基于切分的方式做的,因此我們少了很多計算量,但理論上卻能夠得任意長度的詞語!

參考鏈接:【中文分詞系列】 2. 基于切分的新詞發現

import pymongodb = pymongo.MongoClient().baike.items
def texts():for a in db.find(no_cursor_timeout=True).limit(1000000):yield a['content']from collections import defaultdict #defaultdict是經過封裝的dict,它能夠讓我們設定默認值
from tqdm import tqdm #tqdm是一個非常易用的用來顯示進度的庫
from math import log
import reclass Find_Words:def __init__(self, min_count=10, min_pmi=0):self.min_count = min_countself.min_pmi = min_pmiself.chars, self.pairs = defaultdict(int), defaultdict(int) #如果鍵不存在,那么就用int函數#初始化一個值,int()的默認結果為0self.total = 0.def text_filter(self, texts): #預切斷句子,以免得到太多無意義(不是中文、英文、數字)的字符串for a in tqdm(texts):for t in re.split(u'[^\u4e00-\u9fa50-9a-zA-Z]+', a): #這個正則表達式匹配的是任意非中文、非英文、非數字,因此它的意思就是用任意非中文、非英文、非數字的字符斷開句子if t:yield tdef count(self, texts): #計數函數,計算單字出現頻數、相鄰兩字出現的頻數for text in self.text_filter(texts):self.chars[text[0]] += 1for i in range(len(text)-1):self.chars[text[i+1]] += 1self.pairs[text[i:i+2]] += 1self.total += 1self.chars = {i:j for i,j in self.chars.items() if j >= self.min_count} #最少頻數過濾self.pairs = {i:j for i,j in self.pairs.items() if j >= self.min_count} #最少頻數過濾self.strong_segments = set()for i,j in self.pairs.items(): #根據互信息找出比較“密切”的鄰字_ = log(self.total*j/(self.chars[i[0]]*self.chars[i[1]]))if _ >= self.min_pmi:self.strong_segments.add(i)def find_words(self, texts): #根據前述結果來找詞語self.words = defaultdict(int)for text in self.text_filter(texts):s = text[0]for i in range(len(text)-1):if text[i:i+2] in self.strong_segments: #如果比較“密切”則不斷開s += text[i+1]else:self.words[s] += 1 #否則斷開,前述片段作為一個詞來統計s = text[i+1]self.words[s] += 1 #最后一個“詞”self.words = {i:j for i,j in self.words.items() if j >= self.min_count} #最后再次根據頻數過濾fw = Find_Words(16, 1)
fw.count(texts())
fw.find_words(texts())import pandas as pd
words = pd.Series(fw.words).sort_values(ascending=False)

2.3 更好的新詞發現算法

以文本分類為例,估計最簡單高效的方案就是“樸素貝葉斯分類器”了,類似的,比較現代的是FastText,它可以看作是“樸素貝葉斯”的“神經網絡版”。
要注意,樸素貝葉斯基于一個樸素的假設:特征之間相互獨立。這個假設越成立,樸素貝葉斯的效果就越好。然而,對于文本來說,顯然上下文緊密聯系,這個假設還成立嗎?

注意到,當特征之間明顯不獨立的時候,可以考慮將特征組合之后,使得特征之間的相關性減弱,再用樸素貝葉斯。
比如,對于文本,如果以字為特征,則樸素假設顯然不成立,如“我喜歡數學”中的“喜”和“歡”、“數”和“學”都明顯相關,這時候我們可以考慮將特征進行組合,得到“我/喜歡/數學”,這樣三個片段之間的相關性就沒有那么強了,因此可以考慮用上述結果。
可以發現,這個過程很像分詞,或者反過來說,分詞的主要目的之一,就是將句子分為若干個相關性比較弱的部分,便于進一步處理。從這個角度來看,分的可能不一定是“詞”,也可能是短語、常用搭配等。
說白了,分詞就是為了削弱相關性,降低對詞序的依賴,這一點,哪怕在深度學習模型中,都是相當重要的。有些模型,不分詞但是用CNN,也就是把若干個字組合作為特征來看,這也是通過字的組合來減弱特征間的相關性的體現。

既然分詞是為了削弱相關性,那么我們分詞,就是在相關性弱的地方切斷了。

完整的算法步驟如下:

  • 第一步,統計:選取某個固定的n,統計2grams、3grams、…、ngrams,計算它們的內部凝固度,只保留高于某個閾值的片段,構成一個集合G;這一步,可以為2grams、3grams、…、ngrams設置不同的閾值,不一定要相同,因為字數越大,一般來說統計就越不充分,越有可能偏高,所以字數越大,閾值要越高;
  • 第二步,切分:用上述grams對語料進行切分(粗糙的分詞),并統計頻率。切分的規則是,只要一個片段出現在前一步得到的集合G中,這個片段就不切分,比如“各項目”,只要“各項”和“項目”都在G中,這時候就算“各項目”不在G中,那么“各項目”還是不切分,保留下來;
  • 第三步,回溯:經過第二步,“各項目”會被切出來(因為第二步保證寧放過,不切錯)。回溯就是檢查,如果它是一個小于等于n字的詞,那么檢測它在不在G中,不在就出局;如果它是一個大于n
    字的詞,那個檢測它每個n字片段是不是在G中,只要有一個片段不在,就出局。還是以“各項目”為例,回溯就是看看,“各項目”在不在3gram中,不在的話,就得出局。

每一步的補充說明:

  • 1、使用較高的凝固度,但綜合考慮多字,是為了更準,比如兩字的“共和”不會出現在高凝固度集合中,所以會切開(比如“我一共和三個人去玩”,“共和”就切開了),但三字“共和國”出現在高凝固度集合中,所以“中華人民共和國”的“共和”不會切開;
  • 2、第二步就是根據第一步篩選出來的集合,對句子進行切分(你可以理解為粗糙的分詞),然后把“粗糙的分詞結果”做統計,注意現在是統計分詞結果,跟第一步的凝固度集合篩選沒有交集,我們認為雖然這樣的分詞比較粗糙,但高頻的部分還是靠譜的,所以篩選出高頻部分;
  • 3、第三步,例如因為“各項”和“項目”都出現高凝固度的片段中,所以第二步我們也不會把“各項目”切開,但我們不希望“各項目”成詞,因為“各”跟“項目”的凝固度不高(“各”跟“項”的凝固度高,不代表“各”跟“項目”的凝固度高),所以通過回溯,把“各項目”移除(只需要看一下“各項目”在不在原來統計的高凝固度集合中即可,所以這步計算量是很小的)

參考:【中文分詞系列】 8. 更好的新詞發現算法

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213050.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213050.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213050.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

知識產權服務企業網站建設效果如何

知識產權服務也有較高的市場需求度&#xff0c;尤其如今互聯網深入到各個行業&#xff0c;無論個人還是企業都會以不同的方式經營&#xff0c;相應的為保障自身權益&#xff0c;注冊商標、專利等自然不可少&#xff0c;而對普通小白來說&#xff0c;想要完成這些流程也是有些難…

Python實現獲取b站視頻的彈幕內容

前言 本文是該專欄的第39篇,后面會持續分享python的各種干貨知識,值得關注。 在本專欄之前,有詳細介紹使用python增加b站視頻的播放量方法,感興趣的同學可往前翻閱《Python-增加b站視頻播放量》。而本文,筆者再來單獨的詳細介紹,通過python來獲取b站視頻的彈幕內容。如下…

CGAL的3D皮膚表面網格

1、介紹 Edelsbrunner 引入的皮膚表面和具有豐富而簡單的組合和幾何結構&#xff0c;使其適合在生物計算中模擬大分子。 對這些表面進行網格劃分通常是進一步處理其幾何形狀所必需的&#xff0c;例如在數值模擬和可視化中。 皮膚表面由一組加權點&#xff08;輸入球&#xff09…

力扣labuladong——一刷day70

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、力扣814. 二叉樹剪枝二、力扣1325. 刪除給定值的葉子節點 前言 這道題的難點在于要一直剪枝&#xff0c;直到沒有值為 0 的葉子節點為止&#xff0c;只有從…

RecursionError: maximum recursion depth exceeded in comparison

諸神緘默不語-個人CSDN博文目錄 這個bug的產生原因是運行rouge包時句子太長&#xff0c;所以遞歸次數過多了。完整的報錯信息懶得粘了&#xff0c;總之很長&#xff0c;解決方案就是手動在程序開始處就增大遞歸次數&#xff1a; import sys sys.setrecursionlimit(100000)具體…

html通過CDN引入Vue使用Vuex以及Computed、Watch監聽

html通過CDN引入Vue使用Vuex以及Computed、Watch監聽 近期遇到個需求&#xff0c;就是需要在.net MVC的項目中&#xff0c;對已有的項目的首頁進行優化&#xff0c;也就是寫原生html和js。但是咱是一個寫前端的&#xff0c;寫html還可以&#xff0c;.net的話&#xff0c;開發也…

K8S學習指南-minikube的安裝

簡介 Minikube 是一個用于在本地開發環境中運行 Kubernetes 集群的工具。它允許開發人員在單個節點上體驗 Kubernetes&#xff0c;無需配置復雜的生產環境。本指南將詳細介紹在 Windows、CentOS 和 Ubuntu 系統上安裝 Minikube 的步驟。 1. Windows 系統安裝 1.1 &#xff1…

期末速成數據庫極簡版【查詢】(3)

目錄 多表查詢 【8】多表連接——內連接 &#x1f642;等值連接 &#x1f642;自然連接 &#x1f642;非等值連接 【9】多表連接——外連接 【10】交叉連接不考 【11】聯合查詢 【12】擴展多表連接 【13】嵌套查詢 &#x1f642; 多表查詢 【8】多表連接——內連…

HIVE學習(hive基礎)

HIVE基礎介紹 一、HIVE簡介二、hive的數據類型1、基本數據類型2、復合數據類型 三、HIVE的DDL操作四、創建一個表1. 建表語句 五、修改表結構1.修改表名2. 列修改或增加3. 修改分區 五、常見函數六、一對一關聯left join左關聯right join 右關聯內連接全連接查詢只有A表的數據 …

計算機視覺-機器學習-人工智能頂會 會議地址

計算機視覺-機器學習-人工智能頂會 會議地址 最近應該要整理中文資料的參考文獻&#xff0c;很多會議文獻都需要補全會議地點&#xff08;新國標要求&#xff09;。四處百度感覺也挺麻煩的&#xff0c;而且沒有比較齊全的網站可以搜索。因此自己整理了一下計算機視覺-機器學習…

OSPF路由協議

隨著Internet技術在全球范圍的飛速發展&#xff0c;OSPF已成為目前應用最廣泛的路由協議之一。OSPF&#xff08;Open Shortest Path First&#xff09;路由協議是由IETF&#xff08;Internet Engineering Task Force&#xff09;IGP工作組提出的&#xff0c;是一種基于SPF算法的…

JS 云服務 Deno Depoly 宣布,推出定時運行功能 Deno Cron

如果需要定時執行 JS 腳本&#xff0c;以后多一個選項。 Web 構建日益復雜。編寫現代軟件包括利用云基礎設施、剖析模板代碼和管理復雜的配置&#xff0c;而開發人員只想專注于編寫業務邏輯。 Deno 旨在通過刪除配置和不必要的模板&#xff0c;從根本上簡化 Web 開發。我們將無…

網絡攻擊(三)--攻擊階段

5. 威脅建模階段 目標 了解威脅建模階段的工作內容 工作內容 威脅建模主要使用在情報搜集階段所獲取到的信息&#xff0c;來標識出目標系統上可能存在的安全漏洞與弱點。 在進行威脅建模時&#xff0c;確定最為高效的攻擊方法、所需要進一步獲取到的信息&#xff0c;以及從…

【前端】CSS浮動(學習筆記)

一、浮動 1、傳統網頁布局 網頁布局的本質&#xff1a;用 CSS 來擺放盒子&#xff0c;把盒子擺放到相應位置。 CSS 提供了三種傳統布局方式&#xff08;盒子如何進行排列順序&#xff09; 普通流&#xff08;標準流&#xff09;浮動定位 實際開發中&#xff0c;一個頁面基…

Go 反射技術判斷結構體字段數據為空

Api介紹 在Go語言中&#xff0c;反射API用于在運行時檢查類型信息、獲取和修改變量的值以及調用對象的方法。反射API包含了一組函數和類型&#xff0c;可以在程序運行時動態地操作對象。 以下是一些常用的反射API&#xff1a; reflect.TypeOf&#xff1a;返回一個值的類型信息。…

并查集基礎模板

題目我上面有人兒 代碼 #include <bits/stdtr1c.h> using namespace std; const int N 1005; int f[N]; int n; int siz[N]; // 初始化并查集 // void init() // { // for (int i 1; i < n; i) // { // f[i] i; // 初始化所有的節點都是自己的父節點 //…

Tomcat頭上有個叉叉

問題原因&#xff1a; 這是因為它就是個空的tomcat,并沒有導入項目運行 解決方案&#xff1a; war模式&#xff1a;發布模式&#xff0c;正式發布時用&#xff0c;將WEB工程以war包的形式上傳到服務器 war exploded模式&#xff1a;開發時用&#xff0c;將WEB工程的文件夾直接…

【網絡協議】LACP(Link Aggregation Control Protocol,鏈路聚合控制協議)

文章目錄 LACP名詞解釋LACP工作原理互發LACPDU報文確定主動端確定活動鏈路鏈路切換 LACP和PAgP有什么區別&#xff1f;LACP與LAG的關系LACP模式更優于手動模式LACP模式對數據傳輸更加穩定和可靠LACP模式對聚合鏈路組的故障檢測更加準確和有效 推薦閱讀 LACP名詞解釋 LACP&…

day11 前k個高頻元素

// 小頂堆 class mycomparison { public: bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) { return lhs.second > rhs.second; } }; vector<int> topKFrequent(vector<int>& nums, int k) { // 要統計元素出現…

智能外呼有什么好處?

智能外呼是一種自動化的電話營銷方式&#xff0c;利用AI智能外呼技術和大量數據分析&#xff0c;幫助企業實現與客戶之間的高效、精準、個性化的客戶溝通&#xff0c;還可以在客戶服務、市場營銷和銷售等方面帶來助力。那么&#xff0c;智能外呼有什么好處呢&#xff1f; 1. 提…