基于枚举法的停词
逻辑:
例子:我们经常有意见分歧
词典:【“我们”,“经常”,“有”,“有意见”,“意见”,“分歧”】
枚举满足词典的所有可能情况:
- 我们、 经常、有、意见、分歧
- 我们、经常、有意见、分歧
实现所需的数据需求:
- 中文词库,充当词典的作用
- 以变量的方式提供了部分unigram概率 word_prob
给定词典=[我们 学习 人工 智能 人工智能 未来 是], 另外我们给定unigram概率:p(我们)=0.25, p(学习)=0.15, p(人工)=0.05, p(智能)=0.1, p(人工智能)=0.2, p(未来)=0.1, p(是)=0.15
实现逻辑
Step 1: 对于给定字符串:”我们学习人工智能,人工智能是未来“, 找出所有可能的分割方式
- [我们,学习,人工智能,人工智能,是,未来]
- [我们,学习,人工,智能,人工智能,是,未来]
- [我们,学习,人工,智能,人工,智能,是,未来]
- 我们,学习,人工智能,人工,智能,是,未来] …
Step 2: 我们也可以计算出每一个切分之后句子的概率
- p(我们,学习,人工智能,人工智能,是,未来)= -log p(我们)-log p(学习)-log p(人工智能)-log p(人工智能)-log p(是)-log p(未来)
- p(我们,学习,人工,智能,人工智能,是,未来)=-log p(我们)-log p(学习)-log p(人工)-log p(智能)-log p(人工智能)-log p(是)-log p(未来)
- p(我们,学习,人工,智能,人工,智能,是,未来)=-log p(我们)-log p(学习)-log p(人工)-log p(智能)-log p(人工)-log p(智能)-log p(是)-log p(未来)等等
Step 3: 返回第二步中概率最大的结果,也就是-log和最小的
关于为什么用log?
如果此项在语料库中出现次数比较少,有可能出现underflow的情况,解决办法就是加上log,log是严格递增的函数,原本的关系也是仍然成立的,在比较的情况下是等同的。
代码实现
import math
import xlrd
# TODO: 第一步: 从dic.txt/dic.xlsx中读取所有中文词。
print("Reading dic...")
# 获取对象
workbook = xlrd.open_workbook("中文词库.xlsx")
dic_words = [] # 保存词典库中读取的单词
# 获取第一个sheet的对象列表
booksheet = workbook.sheet_by_index(0)
rows = booksheet.get_rows()
for row in rows:
dic_words.append(row[0].value)
print("len:" + str(len(dic_words)))
# 以下是每一个单词出现的概率。为了问题的简化,我们只列出了一小部分单词的概率。 在这里没有出现的的单词但是出现在词典里的,统一把概率设置成为0.00001
# 比如 p("学院")=p("概率")=...0.00001
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}
print(sum(word_prob.values()))
# TODO:计算-log(x)
for word in word_prob.keys():
word_prob[word] = round(-math.log(word_prob[word]), 1)
print(word_prob)
# 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
def word_segment_naive(input_str):
"""
1. 对于输入字符串做分词,并返回所有可行的分词之后的结果。
2. 针对于每一个返回结果,计算句子的概率
3. 返回概率最高的最作为最后结果
input_str: 输入字符串 输入格式:“今天天气好”
best_segment: 最好的分词结果 输出格式:["今天","天气","好"]
"""
# TODO: 第一步: 计算所有可能的分词结果,要保证每个分完的词存在于词典里,这个结果有可能会非常多。
segments = word_break(input_str, dic_words) # 存储所有分词的结果。如果次字符串不可能被完全切分,则返回空列表(list)
# 格式为:segments = [["今天",“天气”,“好”],["今天",“天“,”气”,“好”],["今“,”天",“天气”,“好”],...]
# TODO: 第二步:循环所有的分词结果,并计算出概率最高的分词结果,并返回
best_segment = []
best_score = math.inf # best_score初始值等于正无穷大
for seg in segments:
score = 0
for w in seg:
if w in word_prob.keys():
score += word_prob[w]
else:
score += round(-math.log(0.00001), 1)
if score < best_score:
best_score = score
best_segment = seg
return best_segment
# TODO:测试
print(word_segment_naive("北京的天气真好啊"))
print(word_segment_naive("今天的课程内容很有意思"))
print(word_segment_naive("经常有意见分歧"))