一、正向最大匹配
从左到右选择词典中最长的词条进行匹配,获得分词结果。
1、统计分词词典,确定词典中最长词条的字符的长度m作为窗口大小;
2、从左向右取待切分语句的m个字符作为匹配字段,与词典中的词语进行匹配,若匹配成功,则作为一个切分后的词语,否则,去掉待匹配字符的最后一个字符继续与词典匹配,重复上述步骤直到切分出所有词语。
dictA = ['南京','南京市', '南京市长', '市长' ,'长江大桥', '大桥']
maxDictA = max([len(word) for word in dictA])#取词典中词语的最大长度作为窗口长度
def cutA(sentence):
result = []
sentenceLen = len(sentence)
n = 0
while n < sentenceLen:
matched = 0
for i in range(maxDictA, 0, -1):#去掉待匹配字符的最后一个字符
piece = sentence[n:n+i]
if piece in dictA:
result.append(piece)
matched = 1
n = n + i
break
if not matched:
result.append(sentence[n])
n += 1
return result
print(cutA("南京市长江大桥"))
['南京市长', '江', '大桥']
二、逆向最大匹配
1、从右到左选择词典中最长的词条进行匹配,获得分词结果;
2、当匹配失败时,去掉待切分字符串的最前面的一个字符,继续与词典匹配。
dictA = ['南京','南京市', '南京市长', '市长' ,'长江大桥', '大桥']
maxDictA = max([len(word) for word in dictA])
def cutB(sentence):
result = []
sentenceLen = len(sentence)
while sentenceLen > 0:
word = ''
for i in range(maxDictA, 0, -1):
piece = sentence[sentenceLen-i:sentenceLen]
if piece in dictA:
word = piece
result.append(word)
sentenceLen -= i
break
if word is '':
sentenceLen -= 1
result.append(sentence[sentenceLen])
return result[::-1]
print(cutB("南京市长江大桥"))
['南京市', '长江大桥']
三、双向最大匹配
将正向最大匹配和逆向匹配得到的分词结果进行比较,按照最大匹配原则,最终结果为切分总词数最少。
def twowaycut(sentence):
if len(cutA(sentence)) > len(cutB(sentence)):
result = cutB(sentence)
elif len(cutA(sentence)) < len(cutB(sentence)):
result = cutA(sentence)
return result
print(twowaycut("南京市长江大桥"))
['南京市', '长江大桥']
四、基于枚举法的分词方法
以变量的方式提供了部分词语的unigram概率
1: 对于给定字符串:”我们学习人工智能,人工智能是未来“, 找出所有可能的分割方式。
2: 计算出每一个切分之后句子的概率。
3: 返回第二步中概率最大的结果。
1.获取中文词典
import xlrd
# 获取一个Book对象
workbook = xlrd.open_workbook("data/综合类中文词库.xlsx")
# 获取一个sheet对象的列表
booksheet = workbook.sheet_by_index(0)
rows = booksheet.get_rows()
voca=set([row[0].value for row in rows])#中文词典
print(voca,'\n',len(voca))
2.将Unigram概率转换为log形式
为了问题的简化,只列出了一小部分单词的概率。 在这里没有出现的的单词但是出现在词典里的,统一把概率设置成为0.00001。
import numpy as np
word_prob = {"北京":0.03,"的":0.08,"天":0.005,"气":0.005,"天气":0.06,"真":0.04,"好":0.05,"真好":0.04,"啊":0.01,"真好啊":0.02,
"今":0.01,"今天":0.07,"课程":0.06,"内容":0.06,"有":0.05,"很":0.03,"很有":0.04,"意思":0.06,"有意思":0.005,"课":0.01,
"程":0.005,"经常":0.08,"意见":0.08,"意":0.01,"见":0.005,"有意见":0.02,"分歧":0.04,"分":0.02, "歧":0.005}
# 计算-log(x)
for word in word_prob.keys():
word_prob[word]= round(-np.log(word_prob[word]),1)
print(word_prob)
3.递归计算所有可行的分词
# TODO:利用递归计算所有可行的分词之后的结果
def word_break(s, dic):
def sentences(cur):
result=[]
if cur <len(s):
for next in range(cur+1, len(s)+1):
if s[cur:next] in dic:
result = result+[s[cur:next]+(tail and ','+tail) for tail in sentences(next)
else:
return ['']
return result
list_new = []
for line in sentences(0):
line = line.split(",")
list_new.append(line)
return list_new
4.返回最大概率的分词结果
def word_segment_naive(input_str):
"""
1. 对于输入字符串做分词,并返回所有可行的分词之后的结果。
2. 针对于每一个返回结果,计算句子的概率
3. 返回概率最高的作为最后结果
input_str: 输入字符串 输入格式:“今天天气好”
best_segment: 最好的分词结果 输出格式:["今天","天气","好"]
"""
# TODO: 第一步: 计算所有可能的分词结果,要保证每个分完的词存在于词典里,这个结果有可能会非常多。
segments = word_break(input_str, voca) # 存储所有分词的结果。如果次字符串不可能被完全切分,则返回空列表(list)
# 格式为:segments = [["今天",“天气”,“好”],["今天",“天“,”气”,“好”],["今“,”天",“天气”,“好”],...]
# TODO: 第二步:循环所有的分词结果,并计算出概率最高的分词结果,并返回
best_segment =[]
best_score = np.inf
for seg in segments:
score=0
for word in seg:
if word in word_prob.keys():
score += word_prob[word]
else:
score += round(-np.log(0.00001),1)
if score < best_score:
best_score=score
best_segment = seg
return best_segment
5.测试
print (word_segment_naive("北京的天气真好啊"))
print (word_segment_naive("今天的课程内容很有意思"))
print (word_segment_naive("经常有意见分歧"))
['北京', '的', '天气', '真好', '啊']
['今天', '的', '课程', '内容', '很', '有意思']
['经常', '有', '意见', '分歧']
维特比(Viterbi)算法优化
1. 基于输入字符串,词典,以及给定的unigram概率来创建DAG(有向图)。
2. 编写维特比算法来寻找最优的PATH
3. 返回分词结果
def word_segment_viterbi(input_str):
# TODO: 第一步:根据词典,输入的句子,以及给定的unigram概率来创建带权重的有向图(Directed Graph),使用dp来求解最短路径。
graph ={}
N = len(input_str)
for i in range(N,0,-1):
k=i-1
in_list=[]
flag=input_str[k:i]
while k>=0:
if flag in voca:
in_list.append(k)
k-=1
flag = input_str[k:i]
graph[i]=in_list
# TODO: 第二步: 利用维特比算法来找出最好的PATH, 这个PATH是P(sentence)最大或者 -log P(sentence)最小的PATH。
mem=[0]* (N+1)
last_index=[0]*(N+1)
for i in range(1,N+1):
min_dis=np.inf
for j in graph[i]:
if input_str[j:i] in word_prob.keys():
#有向图的每一条边是一个单词的概率(只要存在于词典里的都可以作为一个合法的单词),这些概率在 word_prob,如果不在word_prob里的单词但在词典里,统一用概率值0.00001。
if min_dis > mem[j]+round(-np.log(word_prob[input_str[j:i]]),1):
min_dis=mem[j]+round(-np.log(word_prob[input_str[j:i]]),1)
last_index[i]=j
else:
if min_dis > mem[j]+round(-np.log(0.00001),1):
min_dis=mem[j]+round(-np.log(0.00001),1)
last_index[i]=j
mem[i]=min_dis
# TODO: 第三步: 根据最好的PATH, 返回最好的切分
best_segment=[]
j=N
while True:
best_segment.append(input_str[last_index[j]:j])
j=last_index[j]
if j==0 and last_index[j]==0:
break
best_segment.reverse()
return best_segment
['北京', '的', '天气', '真好', '啊']
['今天', '的', '课程', '内容', '很', '有意思']
['经常', '有', '意见', '分歧']