自然语言处理3 -- 词性标注
系列文章,請多關(guān)注
Tensorflow源碼解析1 – 內(nèi)核架構(gòu)和源碼結(jié)構(gòu)
NLP預(yù)訓(xùn)練模型1 – 綜述
Transformer家族1 – Transformer詳解和源碼分析
自然語言處理1 – 分詞
自然語言處理2 – jieba分詞用法及原理
自然語言處理3 – 詞性標注
自然語言處理4 – 句法分析
自然語言處理5 – 詞向量
自然語言處理6 – 情感分析
1 概述
詞性標注在自然語言處理中也屬于基礎(chǔ)性的模塊,為句法分析、信息抽取等工作打下基礎(chǔ)。和分詞一樣,中文詞性標注也存在著很多難點,比如一詞多詞性,未登錄詞處理等諸多問題。通過基于字符串匹配的字典查詢算法和基于統(tǒng)計的詞性標注算法,可以很好的解決這些問題。一般需要先將語句進行分詞,然后再進行詞性標注。
2 詞性標注難點
詞性作為詞語基本的語法屬性,是詞語和語句的關(guān)鍵性特征。詞性種類也很多,ICTCLAS 漢語詞性標注集歸納的詞性種類及其表示見 https://www.cnblogs.com/chenbjin/p/4341930.html。詞性標注中的難點主要有
3 詞性標注算法
和分詞一樣,詞性標注算法也分為兩大類,基于字符串匹配的字典查找算法和基于統(tǒng)計的算法。jieba分詞就綜合了兩種算法,對于分詞后識別出來的詞語,直接從字典中查找其詞性。而對于未登錄詞,則采用HMM隱馬爾科夫模型和viterbi算法來識別。
3.1 基于字符串匹配的字典查找算法
先對語句進行分詞,然后從字典中查找每個詞語的詞性,對其進行標注即可。jieba詞性標注中,對于識別出來的詞語,就是采用了這種方法。這種方法比較簡單,通俗易懂,但是不能解決一詞多詞性的問題,因此存在一定的誤差。
下圖即為jieba分詞中的詞典的一部分詞語。每一行對應(yīng)一個詞語,分為三部分,分別為詞語名 詞數(shù) 詞性。因此分詞完成后只需要在字典中查找該詞語的詞性即可對其完成標注。
3.2 基于統(tǒng)計的詞性標注算法
和分詞一樣,我們也可以通過HMM隱馬爾科夫模型來進行詞性標注。觀測序列即為分詞后的語句,隱藏序列即為經(jīng)過標注后的詞性標注序列。起始概率 發(fā)射概率和轉(zhuǎn)移概率和分詞中的含義大同小異,可以通過大規(guī)模語料統(tǒng)計得到。觀測序列到隱藏序列的計算可以通過viterbi算法,利用統(tǒng)計得到的起始概率 發(fā)射概率和轉(zhuǎn)移概率來得到。得到隱藏序列后,就完成了詞性標注過程。
4 jieba詞性標注原理
jieba在分詞的同時,可以進行詞性標注。利用jieba.posseg模塊來進行詞性標注,會給出分詞后每個詞的詞性。詞性標示兼容ICTCLAS 漢語詞性標注集,可查閱網(wǎng)站https://www.cnblogs.com/chenbjin/p/4341930.html
import jieba.posseg as pseg words = pseg.cut("我愛北京天安門") for word, flag in words: ... print('%s %s' % (word, flag)) ... 我 r # 代詞 愛 v # 動詞 北京 ns # 名詞 天安門 ns # 名詞下面來對pseg.cut()進行詳細的分析,其主要流程為
4.1 準備工作
準備工作中做的事情和jieba分詞基本一致,check字典是否初始化好,如果沒有則先初始化字典。將語句轉(zhuǎn)為UTF-8或者GBK。根據(jù)正則匹配,將輸入文本分隔成一個個語句。代碼如下。
def __cut_internal(self, sentence, HMM=True):# 如果沒有字典沒有初始化,則先加載字典。否則直接使用字典緩存即可。self.makesure_userdict_loaded()# 將語句轉(zhuǎn)為UTF-8或者GBKsentence = strdecode(sentence)# 根據(jù)正則匹配,將輸入文本分隔成一個個語句。分隔符包括空格 逗號 句號等。blocks = re_han_internal.split(sentence)# 根據(jù)是否采用了HMM模型來進行不同方法的選擇if HMM:cut_blk = self.__cut_DAGelse:cut_blk = self.__cut_DAG_NO_HMM# 遍歷正則匹配分隔好的語句,對每個語句進行單獨的分詞和詞性標注for blk in blocks:if re_han_internal.match(blk):# 分詞和詞性標注for word in cut_blk(blk):yield wordelse:tmp = re_skip_internal.split(blk)for x in tmp:if re_skip_internal.match(x):yield pair(x, 'x')else:for xx in x:if re_num.match(xx):yield pair(xx, 'm')elif re_eng.match(x):yield pair(xx, 'eng')else:yield pair(xx, 'x')4.2 遍歷語句,進行分詞和詞性標注
步驟和jieba分詞基本一致,主體步驟如下,詳細的每個步驟見 自然語言處理2jieba分詞用法及原理
其中word_tag_tab在初始化加載詞典階段構(gòu)建得到,它使用詞語為key,對應(yīng)詞性為value。代碼如下
def load_word_tag(self, f):self.word_tag_tab = {}f_name = resolve_filename(f)# 遍歷字典的每一行。每一行對應(yīng)一個詞語。包含詞語 詞數(shù) 詞性三部分for lineno, line in enumerate(f, 1):try:# 去除首尾空格符line = line.strip().decode("utf-8")if not line:continue# 利用空格將一行分隔為詞語 詞數(shù) 詞性三部分word, _, tag = line.split(" ")# 使用詞語為key,詞性為value,構(gòu)造Dictself.word_tag_tab[word] = tagexcept Exception:raise ValueError('invalid POS dictionary entry in %s at Line %s: %s' % (f_name, lineno, line))f.close()4.3 未登錄詞,HMM隱馬爾科夫模型處理
和分詞一樣,詞性標注中,也使用HMM隱馬爾科夫模型來處理未登錄詞。通過大規(guī)模語料統(tǒng)計,得到起始概率 發(fā)射概率和轉(zhuǎn)移概率。分別對應(yīng)prob_start.py prob_emit.py和prob_trans.py三個文件,他們給出了詞語在BEMS四種情況下,每種詞性對應(yīng)的概率。然后使用viterbi算法,利用得到的三個概率,將觀測序列(分詞后的語句)轉(zhuǎn)化得到隱藏序列(詞性標注序列)。這樣就完成了未登錄詞的詞性標注。代碼如下。
# 通過HMM隱馬爾科夫模型獲取詞性標注序列,解決未登錄的問題def __cut(self, sentence):# 通過viterbi算法,利用三個概率,由語句觀測序列,得到詞性標注隱藏序列# prob為# pos_list對應(yīng)每個漢字,包含分詞標注BEMS和詞語詞性兩部分。prob, pos_list = viterbi(sentence, char_state_tab_P, start_P, trans_P, emit_P)begin, nexti = 0, 0# 遍歷語句的每個漢字,如果是E或者S時,也就是詞語結(jié)束或者單字詞語,則分隔得到詞語和詞性pairfor i, char in enumerate(sentence):pos = pos_list[i][0]if pos == 'B':# B表示詞語的開始begin = ielif pos == 'E':# E表示詞語的結(jié)束,此時輸出詞語和他的詞性yield pair(sentence[begin:i + 1], pos_list[i][1])nexti = i + 1elif pos == 'S':# S表示單字詞語,此時也輸出詞語和他的詞性yield pair(char, pos_list[i][1])nexti = i + 1# 一般不會走到這兒,以防萬一。對剩余的所有漢字一起輸出一個詞語和詞性。if nexti < len(sentence):yield pair(sentence[nexti:], pos_list[nexti][1])觀測序列到隱藏序列的計算,則通過viterbi算法實現(xiàn)。代碼如下
# 通過viterbi算法,由觀測序列,也就是語句,來得到隱藏序列,也就是BEMS標注序列和詞性標注序列 # obs為語句,states為"BEMS"四種狀態(tài), # start_p為起始概率, trans_p為轉(zhuǎn)移概率, emit_p為發(fā)射概率,三者通過語料訓(xùn)練得到 def viterbi(obs, states, start_p, trans_p, emit_p):V = [{}] # 每個漢字的每個BEMS狀態(tài)的最大概率。mem_path = [{}] # 分詞路徑# 初始化每個state,states為"BEMS"all_states = trans_p.keys()for y in states.get(obs[0], all_states): # initV[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT)mem_path[0][y] = ''# 逐字進行處理for t in xrange(1, len(obs)):V.append({})mem_path.append({})#prev_states = get_top_states(V[t-1])prev_states = [x for x in mem_path[t - 1].keys() if len(trans_p[x]) > 0]prev_states_expect_next = set((y for x in prev_states for y in trans_p[x].keys()))obs_states = set(states.get(obs[t], all_states)) & prev_states_expect_nextif not obs_states:obs_states = prev_states_expect_next if prev_states_expect_next else all_states# 遍歷每個狀態(tài)for y in obs_states:# 計算前一個狀態(tài)到本狀態(tài)的最大概率和它的前一個狀態(tài)prob, state = max((V[t - 1][y0] + trans_p[y0].get(y, MIN_INF) +emit_p[y].get(obs[t], MIN_FLOAT), y0) for y0 in prev_states)# 將該漢字下的某狀態(tài)(BEMS)的最大概率記下來V[t][y] = prob# 記錄狀態(tài)轉(zhuǎn)換路徑mem_path[t][y] = statelast = [(V[-1][y], y) for y in mem_path[-1].keys()]# if len(last)==0:# print obsprob, state = max(last)route = [None] * len(obs)i = len(obs) - 1while i >= 0:route[i] = statestate = mem_path[i][state]i -= 1return (prob, route)5 總結(jié)
jieba可以在分詞的同時,完成詞性標注,因此標注速度可以得到保證。通過查詢字典的方式獲取識別詞的詞性,通過HMM隱馬爾科夫模型來獲取未登錄詞的詞性,從而完成整個語句的詞性標注。但可以看到查詢字典的方式不能解決一詞多詞性的問題,也就是詞性歧義問題。故精度上還是有所欠缺的。
系列文章,請多關(guān)注
Tensorflow源碼解析1 – 內(nèi)核架構(gòu)和源碼結(jié)構(gòu)
NLP預(yù)訓(xùn)練模型1 – 綜述
Transformer家族1 – Transformer詳解和源碼分析
自然語言處理1 – 分詞
自然語言處理2 – jieba分詞用法及原理
自然語言處理3 – 詞性標注
自然語言處理4 – 句法分析
自然語言處理5 – 詞向量
自然語言處理6 – 情感分析
總結(jié)
以上是生活随笔為你收集整理的自然语言处理3 -- 词性标注的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lambda-Stream应用
- 下一篇: 9.17整型常量