NLP实战——基于枚举实现中文分词(python)

基于枚举法的停词

逻辑:
例子:我们经常有意见分歧
词典:【“我们”,“经常”,“有”,“有意见”,“意见”,“分歧”】

枚举满足词典的所有可能情况:

  1. 我们、 经常、有、意见、分歧
  2. 我们、经常、有意见、分歧

实现所需的数据需求:

  • 中文词库,充当词典的作用
  • 以变量的方式提供了部分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("经常有意见分歧"))

参考博客

学人工智能的猜猜
Divine0

你可能感兴趣的:(机器学习,人工智能,NLP,分词工具,机器学习)