中文词性标注详解
所謂的詞性標(biāo)注在NLP領(lǐng)域是一個應(yīng)用非常廣泛的技術(shù),總的來說,詞性標(biāo)注所解決的問題就是說,給定一句話 sss,我們將sss進行分詞操作,可以將 sss 分成 nnn 個詞,那么 sss 可以表示成:s=w1w2...wns = {w_1}{w_2}...{w_n}s=w1?w2?...wn?,我們將這 nnn 個詞每一個詞標(biāo)注一個詞性那么這句話詞性可以表示成 t=z1z2...znt = {z_1}{z_2}...{z_n}t=z1?z2?...zn?。在這個前提下,我們給定一句新的話術(shù) s′=w1′w2′...wn′s' = {w_1}'{w_2}'...{w_n}'s′=w1?′w2?′...wn?′,如何自動給這句話的每個詞打上標(biāo)簽?zāi)?#xff1f;也就是求出 t′=z1′z2′...zn′t' = {z_1}'{z_2}'...{z_n}'t′=z1?′z2?′...zn?′,這就是詞性標(biāo)注的作用。如圖所示就是詞性標(biāo)注的示意圖:
這個問題可以用概率的知識來解釋,我們可以把詞性標(biāo)注的問題建立一個概率的模型。在給定一個句子的前提下,求序列標(biāo)注的概率我們可以表示成 p(t∣s)p(t|s)p(t∣s),我們需要做的就是將這個概率最大化,在 p(t∣s)p(t|s)p(t∣s) 取得最大值的時候,所得到的標(biāo)注序列 ttt,就是我們要得到的結(jié)果,接下來我們就要最大化這個概率。
好了,我們來看 p(t∣s)p(t|s)p(t∣s) 這個概率表達(dá)式,運用貝葉斯定理,我們可以得到如下的等式: p(t∣s)=p(s∣t)p(t)p(t|s) = p(s|t)p(t)p(t∣s)=p(s∣t)p(t),那么我們可以將 p(t∣s)p(t|s)p(t∣s) 拆成兩項相乘的形式,前面一項是一個 translation model,后面一項是 language model。我們繼續(xù)將這個表達(dá)式進行展開,可以得到:
p(t∣s)=p(s∣t)p(t)=p(w1w2...wn∣z1z2...zn)p(z1z2...zn)p(t|s) = p(s|t)p(t) = p({w_1}{w_2}...{w_n}|{z_1}{z_2}...{z_n})p({z_1}{z_2}...{z_n})p(t∣s)=p(s∣t)p(t)=p(w1?w2?...wn?∣z1?z2?...zn?)p(z1?z2?...zn?)
接下來,我們可以將上述表達(dá)式進行展開操作,因為:
p(w1w2...wn∣z1z2...zn)p(z1z2...zn)=p(w1w2...wn?z1z2...zn)p({w_1}{w_2}...{w_n}|{z_1}{z_2}...{z_n})p({z_1}{z_2}...{z_n}) = p({w_1}{w_2}...{w_n} \cdot {z_1}{z_2}...{z_n})p(w1?w2?...wn?∣z1?z2?...zn?)p(z1?z2?...zn?)=p(w1?w2?...wn??z1?z2?...zn?)
然后我們可以繼續(xù)將表達(dá)式進行分解:
p(w1w2...wn?z1z2...zn)=p(w1∣w2...wnz1z2...zn)p(w2...wnz1z2...zn)p({w_1}{w_2}...{w_n} \cdot {z_1}{z_2}...{z_n}) = p({w_1}|{w_2}...{w_n}{z_1}{z_2}...{z_n})p({w_2}...{w_n}{z_1}{z_2}...{z_n})p(w1?w2?...wn??z1?z2?...zn?)=p(w1?∣w2?...wn?z1?z2?...zn?)p(w2?...wn?z1?z2?...zn?)
由馬爾科夫假設(shè)我們可以得到:
p(w1∣w2...wnz1z2...zn)=p(w1∣z1)p({w_1}|{w_2}...{w_n}{z_1}{z_2}...{z_n}) = p({w_1}|{z_1})p(w1?∣w2?...wn?z1?z2?...zn?)=p(w1?∣z1?)
那么可以得到:
p(w1w2...wn?z1z2...zn)=p(w1∣z1)p(w2...wnz1z2...zn)p({w_1}{w_2}...{w_n} \cdot {z_1}{z_2}...{z_n}) = p({w_1}|{z_1})p({w_2}...{w_n}{z_1}{z_2}...{z_n})p(w1?w2?...wn??z1?z2?...zn?)=p(w1?∣z1?)p(w2?...wn?z1?z2?...zn?)
同理以此類推,我們可以得到一個最終的表達(dá)式:
p(w1w2...wn?z1z2...zn)=∏i=1np(wi∣zi)?p(z1)p(z2∣z1)p(z3∣z2)...p(zn∣zn?1)p({w_1}{w_2}...{w_n} \cdot {z_1}{z_2}...{z_n}) = \prod\limits_{i = 1}^n {p({w_i}|{z_i})} \cdot p({z_1})p({z_2}|{z_1})p({z_3}|{z_2})...p({z_n}|{z_{n - 1}})p(w1?w2?...wn??z1?z2?...zn?)=i=1∏n?p(wi?∣zi?)?p(z1?)p(z2?∣z1?)p(z3?∣z2?)...p(zn?∣zn?1?)
在這個表達(dá)式中我們可以清楚地看到一些特性,∏i=1np(wi∣zi)\prod\limits_{i = 1}^n {p({w_i}|{z_i})}i=1∏n?p(wi?∣zi?) 就表示發(fā)射概率,后面的 p(z2∣z1)p(z3∣z2)...p(zn∣zn?1)p({z_2}|{z_1})p({z_3}|{z_2})...p({z_n}|{z_{n - 1}})p(z2?∣z1?)p(z3?∣z2?)...p(zn?∣zn?1?) 表示狀態(tài)轉(zhuǎn)移概率,p(z1)p({z_1})p(z1?) 表示初始的狀態(tài)概率。我們最后需要最大化這三項表達(dá)式的乘積。用表達(dá)式可以寫成:
z^=arg?max?p(z∣s)=arg?max?∏i=1np(wi∣zi)∏j=2np(zj∣zj?1)p(z1)\hat z = \arg \max p(z|s) = \arg \max \prod\limits_{i = 1}^n {p({w_i}|{z_i})} \prod\limits_{j = 2}^n {p({z_j}|{z_{j - 1}})} p({z_1})z^=argmaxp(z∣s)=argmaxi=1∏n?p(wi?∣zi?)j=2∏n?p(zj?∣zj?1?)p(z1?)
為了方便求解,我們將表達(dá)式進行求對數(shù)操作:
z^=arg?max?log?[∏i=1np(wi∣zi)∏j=2np(zj∣zj?1)p(z1)]\hat z = \arg \max\log[\prod\limits_{i = 1}^n {p({w_i}|{z_i})} \prod\limits_{j = 2}^n {p({z_j}|{z_{j - 1}})} p({z_1})]z^=argmaxlog[i=1∏n?p(wi?∣zi?)j=2∏n?p(zj?∣zj?1?)p(z1?)]
然后我們可以將乘積的形式變成求和的形式,也就是最終我們求解的目標(biāo)函數(shù):
z^=arg?max?z[∑i=1nlog?p(wi∣zi)+log?p(zi)+∑j=2nlog?p(zj∣zj?1)]\hat z = \mathop {\arg \max }\limits_z [\sum\limits_{i = 1}^n {\log p({w_i}|{z_i})} + \log p({z_i}) + \sum\limits_{j = 2}^n {\log p({z_j}|{z_{j - 1}})} ]z^=zargmax?[i=1∑n?logp(wi?∣zi?)+logp(zi?)+j=2∑n?logp(zj?∣zj?1?)]
在我之前的博客 隱馬爾科夫模型(HMM)算法的理解與超詳細(xì)推導(dǎo) 中有詳細(xì)介紹過HMM,這里我們用 AAA 表示發(fā)射概率,也就是 p(wi∣zi)p({w_i}|{z_i})p(wi?∣zi?),BBB 表示狀態(tài)轉(zhuǎn)移概率 p(zi∣zi?1)p({z_i}|{z_{i - 1}})p(zi?∣zi?1?),然后 π\(zhòng)piπ 表示初始狀態(tài) p(z1)p({z_1})p(z1?)。
好了,理清了整個中文詞性標(biāo)注的邏輯之后,我們首先要求 A、B、πA、B、\piA、B、π,然后根據(jù)維特比算法求得 zzz 序列的最大值,將我們z 的序列解碼出來,這就是整個詞性標(biāo)注的流程和邏輯。首先我們來看一下訓(xùn)練的數(shù)據(jù),我是在網(wǎng)上下載的人民日報詞性標(biāo)注的訓(xùn)練集,這個訓(xùn)練集網(wǎng)上很多地方可以下載到,然后都是從人民日報中的摘要進行詞性標(biāo)注出來的,內(nèi)容如圖所示:
里面已經(jīng)分好詞并且標(biāo)注好詞性了,每一句話就是一行,總共是兩萬多條。接下來我們來看發(fā)射概率矩陣 AAA:
再來看看狀態(tài)轉(zhuǎn)移概率矩陣 BBB:
好了,有了AAA 和 BBB,我們暫時把初始概率矩陣 π\(zhòng)piπ 先不管,我們來建立詞典和標(biāo)簽映射,代碼如下:
這里 MMM 表示詞典的大小,也就是詞的數(shù)量。NNN 表示詞性的數(shù)量,我們整個詞庫有55317個詞和45中詞性,如圖所示:
然后我們可以開始構(gòu)建 A、B、πA、B、\piA、B、π,代碼如下所示:
接下來,我們需要為 A、B、πA、B、\piA、B、π 填值了,首先我們需要統(tǒng)計發(fā)射矩陣出現(xiàn)的次數(shù)和轉(zhuǎn)移矩陣出現(xiàn)的次數(shù)以及初始狀態(tài)的次數(shù),
我們只需要解析 tag 的前后關(guān)系和 tag 與 word 的關(guān)系,代碼如下所示:
這樣的代碼很簡潔,也很容易理解,我們統(tǒng)計好詞頻之后,再將我們的 A、B、πA、B、\piA、B、π 進行歸一化,就是我們的發(fā)射概率矩陣,狀態(tài)轉(zhuǎn)移概率矩陣和初始矩陣了,代碼如下所示:
# normalize pi = pi / sum(pi) for i in range(N):A[i] /= sum(A[i])B[i] /= sum(B[i])然后我們可以看看 A、B、πA、B、\piA、B、π 的結(jié)果如下:
然后我們可以定義一個 log 函數(shù),當(dāng)我們概率為零的時候,需要加入一個平滑:
接下來我們就要開始用維特比算法進行解碼操作,所謂的維特比算法,就是運用我們動態(tài)規(guī)劃的算法思想。整體的流程如下所示:
在圖中橫坐標(biāo)是我們分詞之后的詞,縱坐標(biāo)是我們的45個不同的詞性,我們需要的就是找到一條路徑從第一個詞到最后一個詞,使得路徑所對應(yīng)的目標(biāo)函數(shù)最大,我們就取這條路徑所對應(yīng)的詞性序列作為最后這個話術(shù)的詞性標(biāo)注。在圖中,我們可以看到紅色、橘黃色和綠色的線都可以成為序列標(biāo)注的路徑。那么我們總共可以有 N=45nN = {45^n}N=45n 種路徑,如果我們要把 45n{45^n}45n 種路徑全部遍歷一遍,那將是災(zāi)難級別的時間復(fù)雜度,所以這里我們使用維特比算法,運用動態(tài)規(guī)劃的思想來解決這個問題。
我們每向前走一步,就記錄當(dāng)前的最佳的路徑,直到最后。走完最后一個詞之后,我們可以取出整個流程的最佳路徑,這就是維特比算法的大體流程,代碼如下所示:
最后我們就可以打印出,每句話的最佳序列標(biāo)注,我們來測試一個:
這個序列和我們訓(xùn)練數(shù)據(jù)中的進行對比:
結(jié)果上來看基本上沒有問題,但是要注意的是:這里用的是 jieba 分詞,很多分詞之后的結(jié)果和我們訓(xùn)練數(shù)據(jù)中的結(jié)果不一樣,所以很多情況下在詞典中找不到 jieba 分詞之后的詞的 id,所以我們再標(biāo)注訓(xùn)練數(shù)據(jù)的時候盡量應(yīng)該和我們分詞的結(jié)果一致,這樣就不會有問題了。
這就是從最底層實現(xiàn)了整個中文詞性標(biāo)注的流程,希望在詞性標(biāo)注方面對大家有所幫助,詳細(xì)代碼可以參考 GitHub ,謝謝。
總結(jié)
- 上一篇: linux下lamealsa进行音频流操
- 下一篇: 划重点 2022面试必刷461道大厂架构