NLP基礎-準確分詞(使用工具分詞)

關於NLP相關包安裝配置,可以參考:
NLP工具包安裝配置
關於分詞的原理可以參考:
自然語言處理NLP-隱馬爾科夫)

在這裏插入圖片描述

1. 加載字典來保證詞可以分準

對一些專業的名詞來說,使用原有的詞庫可能無法很好的將詞分開,比如在對醫療文本進行分類時,諸如:聯合奧沙利鉑、氟尿嘧啶單藥等專用的藥品名詞。

jieba中自定義詞典的加載

將開始沒分準確的詞放入字典中,就可以對其正確分詞

jieba中的詞典,通過json.loads進行加載:

jieba.load_userdict("dict.txt")

例:

import jieba
seg_list = jieba.cut("輔助治療中的氟尿嘧啶單藥或聯合奧沙利鉑,並未直接納入TNM分期系統", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  

在這裏插入圖片描述
加載字典後:

import jieba
jieba.load_userdict("dict.txt")
seg_list = jieba.cut("輔助治療中的氟尿嘧啶單藥或聯合奧沙利鉑,並未直接納入TNM分期系統", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精確模式

在這裏插入圖片描述

hanlp中自定義詞典的加載

hanlp的自定義字典的目錄:E:\NLP\hanlp\data\dictionary\custom
由hanlp.properties文件可以看到,其中自定義詞典的加載爲:resume_nouns.txt文件
在這裏插入圖片描述
在改動之前,先把CustomDictionary.txt.bin的文件刪除,否則加載的都是txt.bin文件
在這裏插入圖片描述
我們可以看到hanlp的詞典和jieba中詞典的區別是hanlp的詞典中包括詞、詞性、詞頻組成,jieba的詞典中只是詞。

2. 通過正則匹配的方法來將詞進行分開

適用於數字等百分號的匹配,無法準確的一一加入字典準確分詞的形式,則可以通過正則匹配進行準確分詞:

根本原理就是使用正則表達式匹配替換後,再把正則匹配上替換的詞替換爲原來的詞。

cut_data.py:

import jieba
import re
from tokenizer import cut_hanlp

def merge_two_list(a, b):
    c=[]
    len_a, len_b = len(a), len(b)
    minlen = min(len_a, len_b)
    for i in range(minlen):
        c.append(a[i])
        c.append(b[i])

    if len_a > len_b:
        for i in range(minlen, len_a):
            c.append(a[i])
    else:
        for i in range(minlen, len_b):
            c.append(b[i])
    return c
    
if __name__=="__main__":
    # 第一步首先打開需要分詞的文件
    fp=open("text.txt","r",encoding="utf8")
    # 第二步創建需要保存分詞後結果的文件
    fout=open("result_cut.txt","w",encoding="utf8")
    # 第三步使用正則進行清洗,將一些詞進行處理
    # 匹配出非漢字的情況,並且限制在5個字符
    regex1=u'(?:[^\u4e00-\u9fa5()*&……%¥$,,。.@! !]){1,5}期'
    # 這個正則用來匹配百分號的小數{1-3}表示3位,[0-9]表示10個數字
    # 小數點.後打上問號,表示1個或者0個
    regex2=r'(?:[0-9]{1,3}[.]?[0-9]{1,3})%'
    p1=re.compile(regex1)
    p2=re.compile(regex2)
    # 讀取每一行
    for line in fp.readlines():
        result1=p1.findall(line)
        # 將正則匹配的詞進行替換
        if result1:
            regex_re1=result1
            line=p1.sub("FLAG1",line)
        result2=p2.findall(line)
        if result2:
            line=p2.sub("FLAG2",line)
        # 開始切完詞後是迭代器類型,還需要join進行顯示
        words=jieba.cut(line)
        words1=cut_hanlp(line)
        result=" ".join(words1)
        # 再把正則匹配上替換的詞替換爲原來的詞
        if "FLAG1" in result:
            # 先通過split方法拆分爲列表
            result=result.split("FLAG1")
            # 再通過merge方法將兩個列表合併
            result=merge_two_list(result,result1)
            result ="".join(result)
        if "FLAG2" in result:
            result=result.split("FLAG2")
            result=merge_two_list(result,result2)
            result="".join(result)
        fout.write(result)
    fout.close()

tokenizer.py:

import os,gc,re,sys

from jpype import *

startJVM(getDefaultJVMPath(),r"-Djava.class.path=E:\NLP\hanlp\hanlp-1.5.0.jar;E:\NLP\hanlp",
         "-Xms1g",
         "-Xmx1g")

Tokenizer = JClass('com.hankcs.hanlp.tokenizer.StandardTokenizer')

def to_string(sentence,return_generator=False):
    if return_generator:
        return (word_pos_item.toString().split('/') for word_pos_item in Tokenizer.segment(sentence))
    else:
        return " ".join([word_pos_item.toString().split('/')[0] for word_pos_item in Tokenizer.segment(sentence)]   )
    
def seg_sentences(sentence,with_filter=True,return_generator=False):  
    segs=to_string(sentence,return_generator=return_generator)
    if with_filter:
        g = [word_pos_pair[0] for word_pos_pair in segs if len(word_pos_pair)==2 and word_pos_pair[0]!=' ' and word_pos_pair[1] not in drop_pos_set]
    else:
        g = [word_pos_pair[0] for word_pos_pair in segs if len(word_pos_pair)==2 and word_pos_pair[0]!=' ']
    return iter(g) if return_generator else g

def cut_hanlp(raw_sentence,return_list=True):
    if len(raw_sentence.strip())>0:
        return to_string(raw_sentence) if return_list else iter(to_string(raw_sentence))

3. 動態調整字典詞頻

有時會出現字典已經加載,但是詞不一定能分得開的情況,這時就需要通過改變詞頻率的方法進行動態調整。

jieba中調整詞頻

jieba.suggest_freq('臺中' , tune=True)
import jieba
jieba.load_userdict("dict.txt")
jieba.suggest_freq('臺中' , tune=True)
if __name__ = "__main__":
	string = "臺中正確應該不會被切開"
	words = jieba.cut(string,HMM=False)
	result = " ".join(words)
	print(result)

但在實際應用中,不可能一句話一句話進行加載,那這樣就可以採用先open打開這個字典文件,再對字典文件中的詞進行一一遍歷,如下:

fp = open("dict.txt", 'r' , encoding = 'utf8')
for line in fp:
	lline = ine.strip()
	jieba.suggest_freq(line, true = True)

爲了讓運行更高效些,將for循環改爲列表生成式:

[jieba.suggest_freq(line.strip(), tune=True) for line in open("dict.txt",'r',encoding='utf8')]

hanlp中調整詞頻

對於hanlp切詞而言,因爲hanlp的詞典是同時記錄單詞、詞性、詞頻數的,如果出現了相同詞頻數的單詞,該如何進行選擇切分?如:
在這裏插入圖片描述
這裏我們默認最長匹配原則,即優先對len長的進行切分,而Hanlp切詞時會按照字典加載的順序進行切詞,單詞如果優先加載,就會被切除。故我們需要先對詞典中的詞按照長度進行排序。
sort_dict_by_len.py:

import os
# 第一步先打開文件
dict_file=open(r"E:\NLP\hanlp\data\dictionary\custom"+os.sep+"resume_nouns.txt",'r',encoding='utf8')
d={}
# 第二步把單詞取出,對每個詞和每個長度建立一個字典
[d.update({line:len(line.split(" ")[0])}) for line in dict_file]
f=sorted(d.items(), key=lambda x:x[1], reverse=True)
dict_file=open(r"E:\NLP\hanlp\data\dictionary\custom"+os.sep+"resume_nouns1.txt",'w',encoding='utf8')
[dict_file.write(item[0]) for item in f]
dict_file.close()

再進行hanlp切詞後結果如下:

from tokenizer import cut_hanlp
if __name__=="__main__":
    string="臺中正確應該不會被切開。"
    words=cut_hanlp(string)
    print(words)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章