基于规则中文分词

一、中文分词技术

词是中文语言理解中最小的能独立语言的语言单位。

中文分词上指将汉字序列按照一定规则逐个切分为词序列的过程。由于中文中词与词之间没有明确的分隔标志,因此中文分词需要依靠一定技术和方法找寻类似英文中空格作用的分隔符。

1.基于规则分词

基于规则分词是中文分词最先使用的方法,常见的方法有正向最大匹配法、逆向最大匹配法等。

基于规则的分词方法是一种比较机械的分词方法,其基本思想上讲待分词语句中的字符串和词典逐个匹配,找到匹配的字符串则切分,不匹配则减去边缘的某些字符,从头再次匹配,直至匹配完毕或者没有匹配到词典中的字符串而结束。

1.1.正向最大匹配法

假设有一个待分词的中文文本和一个分词词典,词典中最长字符串的长度为l。从左至右切分待分词文本的前l个字符,然后在词典中查找是否有一样的字符串。

若匹配失败则删去该字符串的最后一个字符,仅保留下前l-1个字符,继续匹配这个字符,以此类推。

若匹配成功,那么被切分下来的第二个文本成为新的待分词文本,重复以上操作直至匹配完毕。

若一个字符串全部匹配失败,那么逐次删去第一个字符,并重复上述操作。

1
2
3
4
5
6
7
#dic.utf8
北京市民
民办高中
天安门广场
北京市
高中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_dict():

    dictionary = []

    dic_path = './data/dic.utf8'

    for line in open(dic_path, 'r', encoding='utf-8-sig'):

        line = line.strip()

        if not line:

            continue

        dictionary.append(line)

    return list(set(dictionary))
1
2
3
4
5
6
7
8
9
10
11
def calculate_max_length(dictionary):

    max_length = 0

    word_length = []

    for word in dictionary:

        word_length.append(len(word))

    return max(word_length)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def MM(text):  # Maximum Matching

    # 读取词典

    dictionary = get_dict()

    # 获取词典最大长度

    max_length = calculate_max_length(dictionary)

    # 切分文本

    global MM_cut_list

    MM_cut_list = []

    text_length = len(text)

    while text_length > 0:

        divide = text[0:max_length]    #从前向后截取长度为max_length的字符串

        while divide not in dictionary:      #当前截取的字符串不在分词字典中,则进循环

            if len(divide)==1:             #当前截取的字符串长度为1时,说明分词字典无匹配项

                break                      #直接保留当前的一个字

            divide=divide[0:len(divide)-1] #当前截取的字符串长度减一

        MM_cut_list.append(divide)         #记录下当前截取的字符串

        text = text[len(divide):]  #截取未分词的句子

        text_length = len(text)

    #print(MM_cut_list)

MM('北京市民办高中')

1.2.逆向最大匹配法

逆向最大匹配法跟正向最大匹配法原理相反。从右至左匹配待分词文本后的L个字符串,在词典中查找是否有一样的字符串。

若匹配失败,仅留下待分词文本的后L-1个词,继续匹配这个字符串,以此类推。

若匹配成功,则被切分下来的第一个文本序列成为新的待分词文本,重复以上操作直至匹配完毕。

若一个词序列完全匹配失败,则逐次删去最后一个字符,并重复上述操作。

1
2
3
for i in range(10,0,-1):

    print(i)
1
2
3
for i in range(10,1):

    print(i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 def RMM(text): # Reverse Maximum Matching

    # 读取词典

    dictionary = get_dict()

    # 获取词典最大长度

    max_length = calculate_max_length(dictionary)

    # 切分文本

    global RMM_cut_list

    RMM_cut_list = []

    text_length = len(text)

    while text_length > 0:

        match = False

        # range(start, stop, step):根据start与stop指定范围内以及step设定的步长

        for i in range(max_length, 0, -1): # i等于max_langth到1

            if text_length - i < 0:  # 考虑到当词典的最长词的长度大于要匹配的文本的长度时,需要对典的最长词的长度进行裁剪,

                continue   # 直到词典的最长词的长度=匹配的文本的长度,此时截取位置从0开始

            new_word = text[text_length - i:text_length] # 截取文本的最后max_length位

            if new_word in dictionary: # 匹配成功

                RMM_cut_list.append(new_word)

                text_length -= i # 被切下来的第一个文本序列成为新的待分词文本

                match=True

                break

        if not match: # 说明没有匹配成功

            text_length -= 1  #切分的长度L要减1,仅留下待分词的后L-1个词

    RMM_cut_list.reverse()

    #print(RMM_cut_list)

RMM('北京市民办高中')

1.3.双向最大匹配法

双向最大匹配法的基本思想上讲正向最大匹配法和逆向最大匹配法的结果进行对比,选取两种方法中分词数量较少的结果作为切分结果。

如果分词数量一致,再判断分词结果是否完全相同,如果分词结果相同,可以返回任何一个;如果分词结果不同,返回单字数比较少的那个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

text = '北京市民办高中'

MM(text)

RMM(text)

#词典元素存储变量

cut_words_list = []

#转换分词结果的长度

MM_cut_list_size = len(MM_cut_list)

RMM_cut_list_size = len(RMM_cut_list)



# 比较正向最大匹配和逆向最大匹配结果。

#如果分词数量结果不同,那么取分词数量较少的那个。

if MM_cut_list_size != RMM_cut_list_size:

    if MM_cut_list_size > RMM_cut_list_size:

        cut_words_list = RMM_cut_list

    else:

        cut_words_list = MM_cut_list

else:

    # 如果分词数量结果相同:

    # 1.分词结果相同,可以返回任何一个。

    # 2.分词结果不同,返回单字数比较少的那个,

    # 如果单字数个数也相同,则任意返回一个。

    fmm_single = 0

    bmm_single = 0

    isSame = True

    for i in range(len(RMM_cut_list)):

        if isSame and MM_cut_list[i] not in RMM_cut_list:

            isSame = False  # 此时分词结果不同

        if len(MM_cut_list[i]) == 1:

            fmm_single += 1  # 正向最大匹配结果中单字数加1

        if len(RMM_cut_list[i]) == 1:

            bmm_single += 1 # 逆向最大匹配结果中单字数加1

    if isSame: # 如果词结果相同,返回任何一个

        cut_words_list = MM_cut_list_size

    if fmm_single > bmm_single:

        cut_words_list = RMM_cut_list

    else:

        cut_words_list = MM_cut_list

print(cut_words_list)

二、尾巴