NLP 中文分词-双向匹配算法(理论+Python实现)

一、理论描述

1.中文分词的概念:
是指把没有明显分界标志的字串切分为词串,包括标点符号、数字、数学符号、各种标记、人名、地名、机构名等未登录词的识别。汉语自动分词主要包括:(1)根据分词规范,建立机器词典;(2)根据分词算法和机器词典,把字串切分为词串;(3)机器学习方法和统计方法。

2.中文分词的重要性:
汉语中词是最小的独立运用单位,分词是句法分析的基础,是所有应用系统进行的第一步,是其他中文信息处理的基础,搜索引擎、机器翻译(MT)、语音合成、自动分类、自动摘要、自动校对等等都需要用到分词。只有进行了分词,才可以继续进行下面语法分析、语义分析等。

3.中文分词算法的分类:
根据有无分词词典分为有词典分词和无词典分词。根据使用的知识资源不同分为基于规则和基于统计的方法,以及两者结合的方法。
(1)基于规则的分词算法:事先人工建立好分词词典和分词规则库。原理为基于字符串匹配进行分词,要求有足够大的词表为依据。通过一定的算法来实现,如正向最大匹配法、 逆向最大匹配法、双向匹配法等。当分词词典所收容的词较少时分词的正确率低。
(2)基于统计的分词方法:把字与字间、词与词间的同现频率作为分词的依据, 可以没有建立好的词典;需要大规模的训练文本, 用来训练模型参数。优点是不受应用领域的限制,缺点是训练文本的选择将影响分词结果。

二、算法描述

本文实现双向匹配算法,具体算法描述如下:

1.正向最大匹配:
设MaxLen表示最大词长,D为分词词典。
Step1:从待切分语料中按正向取长度为MaxLen的字串str,令 Len=MaxLen;
Step2:把str与D中的词相匹配;
Step3:若匹配成功,则认为该字串为词,指向待切分语料的指针向后移Len个汉字,返回到Step1;
Step4:若不成功:如果Len>1,则将Len减1,从待切分语料中取长度为Len的字串str,返回到Step2。否则,得到长度为1的单字词,指向待切分语料的指针向前移1个汉字,返回Step1。
采用正向最大匹配得到切分结果1。

2.逆向最大匹配法:
设MaxLen表示最大词长,D为分词词典。
Step1:从待切分语料中按逆向取长度为MaxLen的字串str,令 Len=MaxLen;
Step2:把str与D中的词相匹配;
Step3:若匹配成功,则认为该字串为词,指向待切分语料的指针向前移Len个汉字,返回到Step1;
Step4:若不成功:如果Len>1,则将Len减1,从待切分语料中取长度为Len的字串str,返回到Step2。否则,得到长度为1的单字词,指向待切分语料的指针向前移1个汉字,返回Step1。
采用逆向最大匹配得到切分结果2。

3.双向匹配:
如果两个切分结果相同,则认为切分成功,否则认为有疑点。针对疑点,有两种处理策略:
(1)采用上下文信息,根据歧义规则库进行排歧;
(2)进行人工干预,选取一种切分为正确的切分。

三、详例描述

以“对外经济技术合作与交流不断扩大。”为例,详细描述算法如下:

(1)正向最大匹配:
初始化:MaxLen=3,pos=0,ResultStr=“”
Step1:Len=MaxLen=3,取str=“对外经”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“对外”;
str与D中的词匹配成功,ResultStr=“对外/”,pos=pos+Len=2
Step2:Len=MaxLen=3,取str=“经济技”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“经济”;
str与D中的词匹配成功,ResultStr=“对外/经济/”,pos=pos+Len=4
Step3:Len=MaxLen=3,取str=“技术合”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“技术”;
str与D中的词匹配成功,ResultStr=“对外/经济/技术/”,pos=pos+Len=6
Step4:Len=MaxLen=3,取str=“合作与”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“合作”;
str与D中的词匹配成功,
ResultStr=“对外/经济/技术/合作/”,pos=pos+Len=8;
Step5:Len=MaxLen=3,取str=“与交流”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“与交”;
str与D中的词匹配不成功,Len=Len-1=1,取str=“与”;
Len=1,单字直接作为一个词加入结果,
ResultStr=“对外/经济/技术/合作/与/”,pos=pos+Len=9;
Step6:Len=MaxLen=3,取str=“交流不”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“交流”;
str与D中的词匹配成功,
ResultStr=“对外/经济/技术/合作/与/交流/”,pos=pos+Len=11;
Step7:Len=MaxLen=3,取str=“不断扩”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“不断”;
str与D中的词匹配成功,
ResultStr=“对外/经济/技术/合作/与/交流/不断/”,pos=pos+Len=13;
Step8:Len=MaxLen=3,取str=“扩大。”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“扩大”;
str与D中的词匹配成功, ResultStr=“对外/经济/技术/合作/与/交流/不断/扩大/”,pos=pos+Len=15;
Step9:取str=“。”;
Len==1,单字直接作为一个词加入结果, ResultStr=“对外/经济/技术/合作/与/交流/不断/扩大/。/”

(2)负向最大匹配:
初始化:MaxLen=3,pos=15,ResultStr=“”
Step1:Len=MaxLen=3,取str=“扩大。”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“大。”;
str与D中的词匹配不成功,Len=Len-1=1,取str=“。”;
Len=1,单字直接作为一个词加入结果,
ResultStr=“/。”,pos=pos-Len=14
Step2:Len=MaxLen=3,取str=“断扩大”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“扩大”;
str与D中的词匹配成功,ResultStr=“/扩大/。”,pos=pos-Len=12
Step3:Len=MaxLen=3,取str=“流不断”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“不断”;
str与D中的词匹配成功,ResultStr=“/不断/扩大/。”,pos=pos-Len=10
Step4:Len=MaxLen=3,取str=“与交流”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“交流”;
str与D中的词匹配成功,
ResultStr=“/交流/不断/扩大/。”,pos=pos-Len=8;
Step5:Len=MaxLen=3,取str=“合作与”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“作与”;
str与D中的词匹配不成功,Len=Len-1=1,取str=“与”;
Len==1,单字直接作为一个词加入结果,
ResultStr=“/与/交流/不断/扩大/。”,pos=pos-Len=8;
Step6:Len=MaxLen=3,取str=“术合作”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“合作”;
str与D中的词匹配成功,
ResultStr=“/合作/与/交流/不断/扩大/。”,pos=pos-Len=6;
Step7:Len=MaxLen=3,取str=“济技术”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“技术”;
str与D中的词匹配成功,
ResultStr=“/技术/合作/与/交流/不断/扩大/。”,pos=pos-Len=4;
Step8:Len=MaxLen=3,取str=“外经济”;
str与D中的词匹配不成功,Len=Len-1=2,取str=“经济”;
str与D中的词匹配成功, ResultStr=“/经济/技术/合作/与/交流/不断/扩大/。”,pos=pos-Len=2;
Step9:取str=“对外”;
str与D中的词匹配成功,ResultStr=“/对外/经济/技术/合作/与/交流/不断/扩大/。”

(3)比较两次匹配的结果:两个切分结果相同,切分成功,最终的结果为:
“/对外/经济/技术/合作/与/交流/不断/扩大/。”

四、Python代码实现:

import time
import re

class Segment:
    # 数据成员
    sentence = ""
    MaxLen = 0
    pos = 0
    len = 0
    result_MM = ""  # 存放MM分词结果
    result_RMM = ""  # 存放RMM分词结果
    final_res = ""
    dict = []

    # 构造函数
    def __init__(self, sentence, MaxLen):
        self.sentence = sentence
        self.MaxLen = MaxLen
        self.pos = 0
        self.len = self.MaxLen
        self.result_MM = ""
        self.readDict()

    # 读字典
    def readDict(self):
        f = open("chineseDic.txt", "r", encoding="utf-8")
        lines = f.readlines()
        for line in lines:
            # print(line)
            words = line.split(",")
            self.dict.append(words[0])

    # 正向最大匹配
    def MM(self, nLen, nPos):
        length = len(self.sentence)
        if (nPos > length):
            return
        substr = self.sentence[nPos:nPos + nLen]
        if substr in self.dict:
            self.result_MM = self.result_MM + substr + "/ "
            nPos = nPos + nLen
            nLen = self.MaxLen
            self.MM(nLen, nPos)
        elif nLen > 1:
            nLen = nLen - 1
            self.MM(nLen, nPos)
        else:
            self.result_MM = self.result_MM + substr + "/ "
            nPos = nPos + 1
            nLen = self.MaxLen
            self.MM(nLen, nPos)

    # 逆向最大匹配
    def RMM(self, nLen, nPos):
        if (nPos < 0):
            return
        substr = self.sentence[nPos - nLen:nPos]
        if substr in self.dict:
            self.result_RMM = self.result_RMM + "/" + substr
            nPos = nPos - nLen
            nLen = self.MaxLen
            self.RMM(nLen, nPos)
        elif nLen > 1:
            nLen = nLen - 1
            self.RMM(nLen, nPos)
        else:
            self.result_RMM = self.result_RMM + substr + "/"
            nPos = nPos - 1
            nLen = self.MaxLen
            self.RMM(nLen, nPos)

    def getMMResult(self):
        return self.result_MM

    def getRMMResult(self):
        return self.result_RMM

    def getFinalResult(self):
        return self.final_res

    def printFinalResult(self):
        print("正向最大匹配结果:")
        seg_res_MM = self.result_MM.replace(" ", "")
        print(seg_res_MM)
        seg_list_MM = seg_res_MM.split('/')
        del seg_list_MM[-1]  # 由于按照'/'分割,所以最后会多出一个'',删去
        print(seg_list_MM)

        print("逆向最大匹配结果:")
        seg_res_RMM = self.result_RMM.replace(" ", "")
        print(seg_res_RMM)
        seg_list_RMM = list(reversed(seg_res_RMM.split('/')))
        del seg_list_RMM[0]
        del seg_list_RMM[-1]
        print(seg_list_RMM)

        len_MM = len(seg_list_MM)
        len_RMM = len(seg_list_RMM)
        flag = 1
        for i in range(0, min(len_MM, len_RMM)):
            if seg_list_MM[i] != seg_list_RMM[i]:
                print("两次分词结果不一致。")
                flag = 0
                break
        if (flag):
            print("两次分词结果一致。")
            print("最终的分词结果为:")
            self.final_res = self.result_MM
            print(self.final_res)


def to_region(segmentation):
    region = []
    start = 1
    for word in re.compile("\\s+").split(segmentation.strip()):  # 空格,回车,换行等空白符
        end = start + len(word) - 2
        region.append((start, end))
        start = end + 1
    return region


def PRF(target, pred):
    t_set, p_set = set(target), set(pred)
    target_num = len(t_set)
    pred_num = len(p_set)
    cap_num = len(t_set & p_set)
    p = cap_num / pred_num
    r = cap_num / target_num
    f = 2 * p * r / (p + r)
    print("P =", p)
    print("R =", r)
    print("F1 =", f)


if __name__ == '__main__':
    test_str = '在这一年中,中国的改革开放和现代化建设继续向前迈进。国民经济保持了“高增长、低通胀”的良好发展态势。农业生产再次获得好的收成,企业改革继续深化,人民生活进一步改善。对外经济技术合作与交流不断扩大。'
    seg = Segment(test_str, 3)

    time_start = time.time()
    seg.MM(3, 0)
    seg.RMM(3, len(test_str))
    time_end = time.time()

    seg.printFinalResult()
    print('分词时间:', time_end - time_start, 's')

    target_str = "在/  这/  一/  年/  中/  ,/  中国/  的/  改革/  开放/  和/  现代化/  建设/  继续/  向前/  迈进/  。/  国民经济/  保持/  了/  “/  高/  增长/  、/  低/  通胀/  ”/  的/  良好/  发展/  态势/  。/  农业/  生产/  再次/  获得/  好/  的/  收成/  ,/  企业/  改革/  继续/  深化/  ,/  人民/  生活/  进一步/  改善/  。/  对外/  经济/  技术/  合作/  与/  交流/  不断/  扩大/  。/"
    re_pred = to_region(seg.getFinalResult())
    re_target = to_region(target_str)

    # 每个单词按它在文本中的起止位置可记作区间[i, j]
    print("分词结果:", re_pred)
    print("标准答案:", re_target)

    PRF(re_target, re_pred)

五、结果展示:

(1)实验截图:
NLP 中文分词-双向匹配算法(理论+Python实现)_第1张图片NLP 中文分词-双向匹配算法(理论+Python实现)_第2张图片
NLP 中文分词-双向匹配算法(理论+Python实现)_第3张图片
(2)正向最大匹配结果:

在/这/一/年中/,/中国/的/改革/开放/和/现代化/建设/继续/向前/迈进/。/国民/经济/保持/了/“/高/增长/、/低/通胀/”/的/良好/发展/态势/。/农业/生产/再次/获得/好/的/收成/,/企业/改革/继续/深化/,/人民/生活/进一步/改善/。/对外/经济/技术/合作/与/交流/不断/扩大/。/

转化为列表为:[‘在’, ‘这’, ‘一’, ‘年中’, ‘,’, ‘中国’, ‘的’, ‘改革’, ‘开放’, ‘和’, ‘现代化’, ‘建设’, ‘继续’, ‘向前’, ‘迈进’, ‘。’, ‘国民’, ‘经济’, ‘保持’, ‘了’, ‘“’, ‘高’, ‘增长’, ‘、’, ‘低’, ‘通胀’, ‘”’, ‘的’, ‘良好’, ‘发展’, ‘态势’, ‘。’, ‘农业’, ‘生产’, ‘再次’, ‘获得’, ‘好’, ‘的’, ‘收成’, ‘,’, ‘企业’, ‘改革’, ‘继续’, ‘深化’, ‘,’, ‘人民’, ‘生活’, ‘进一步’, ‘改善’, ‘。’, ‘对外’, ‘经济’, ‘技术’, ‘合作’, ‘与’, ‘交流’, ‘不断’, ‘扩大’, ‘。’]

(3)逆向最大匹配结果:

/。/扩大/不断/交流/与/合作/技术/经济/对外/。/改善/进一步/生活/人民/,/深化/继续/改革/企业/,/收成/的/好/获得/再次/生产/农业/。/态势/发展/良好/的/”/通胀/低/、/增长/高/“/了/保持/经济/国民/。/迈进/向前/继续/建设/现代化/和/开放/改革/的/中国/,/年中/一/这/在/

转化为列表,并且调整顺序后:[‘在’, ‘这’, ‘一’, ‘年中’, ‘,’, ‘中国’, ‘的’, ‘改革’, ‘开放’, ‘和’, ‘现代化’, ‘建设’, ‘继续’, ‘向前’, ‘迈进’, ‘。’, ‘国民’, ‘经济’, ‘保持’, ‘了’, ‘“’, ‘高’, ‘增长’, ‘、’, ‘低’, ‘通胀’, ‘”’, ‘的’, ‘良好’, ‘发展’, ‘态势’, ‘。’, ‘农业’, ‘生产’, ‘再次’, ‘获得’, ‘好’, ‘的’, ‘收成’, ‘,’, ‘企业’, ‘改革’, ‘继续’, ‘深化’, ‘,’, ‘人民’, ‘生活’, ‘进一步’, ‘改善’, ‘。’, ‘对外’, ‘经济’, ‘技术’, ‘合作’, ‘与’, ‘交流’, ‘不断’, ‘扩大’, ‘。’]

(4)由以上结果可以看出,两次分词结果一致。最终的分词结果如下:

在/这/一/年中/,/中国/的/改革/开放/和/现代化/建设/继续/向前/迈进/。/国民/经济/保持/了/“/高/增长/、/低/通胀/”/的/良好/发展/态势/。/农业/生产/再次/获得/好/的/收成/,/企业/改革/继续/深化/,/人民/生活/进一步/改善/。/对外/经济/技术/合作/与/交流/不断/扩大/。/

(5)分词时间: 0.20544934272766113 s

(6)评测指标:
分词结果:
[(1, 1), (2, 2), (3, 3), (4, 5), (6, 6), (7, 8), (9, 9), (10, 11), (12, 13), (14, 14), (15, 17), (18, 19), (20, 21), (22, 23), (24, 25), (26, 26), (27, 28), (29, 30), (31, 32), (33, 33), (34, 34), (35, 35), (36, 37), (38, 38), (39, 39), (40, 41), (42, 42), (43, 43), (44, 45), (46, 47), (48, 49), (50, 50), (51, 52), (53, 54), (55, 56), (57, 58), (59, 59), (60, 60), (61, 62), (63, 63), (64, 65), (66, 67), (68, 69), (70, 71), (72, 72), (73, 74), (75, 76), (77, 79), (80, 81), (82, 82), (83, 84), (85, 86), (87, 88), (89, 90), (91, 91), (92, 93), (94, 95), (96, 97), (98, 98)]
标准答案:
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 8), (9, 9), (10, 11), (12, 13), (14, 14), (15, 17), (18, 19), (20, 21), (22, 23), (24, 25), (26, 26), (27, 30), (31, 32), (33, 33), (34, 34), (35, 35), (36, 37), (38, 38), (39, 39), (40, 41), (42, 42), (43, 43), (44, 45), (46, 47), (48, 49), (50, 50), (51, 52), (53, 54), (55, 56), (57, 58), (59, 59), (60, 60), (61, 62), (63, 63), (64, 65), (66, 67), (68, 69), (70, 71), (72, 72), (73, 74), (75, 76), (77, 79), (80, 81), (82, 82), (83, 84), (85, 86), (87, 88), (89, 90), (91, 91), (92, 93), (94, 95), (96, 97), (98, 98)]
**正确率:**P = 0.9491525423728814
**召回率:**R = 0.9491525423728814
**F-度量值:**F1 = 0.9491525423728814

五、总结反思:

(1)在编写逆向匹配的代码时,由于在分词时是从后向前的,所以最终得到的分词结果中词的顺序会颠倒,不便于最终两个分词结果的比较。我采取的方式是用python中的split(‘/’)将分词结果拆分成由词语构成的字符串数组,然后调整逆向匹配的数组元素的顺序,之后再一一比较,看两次的分词结果是否相同。
(2)在计算评测指标的值的时候,需要统计分词结果的词数、标准答案中的词数以及两者交集的次数,我采取的方式是对于分词结果和标准答案中每个词按它在文本中的起止位置记作区间[i, j],利用python中的元组进行表示,然后再利用集合set得到两者的交集。

你可能感兴趣的:(算法,python,自然语言处理)