分词 | 概率最大中文分词python实现

摘要

概率最大分词是分词的其中一种算法,通过选出句子中所有的候选词,计算它们的累计概率,在不同的词语组合中选出累计概率最大的组合作为最终的分词结果。这里使用python进行实现。

算法描述

首先解释一下累计概率怎么计算:假如待分词的句子为“对外经济技术合作与交流不断扩大。”,候选词可能有“对”、“对外”、“外”、“经济”等。对于每个词的累计概率,等于它原来的概率乘上累计概率最大的左邻词的概率,即P’(wi) = P(wi) * P(wi-1),对于作为句子起始的单词则是他本身的概率。

例如:P’(对) = P(对),P’(对外) = P(对外)。P’(外) = P(外) * P(对),P’(经济) = P(经济) * max{P’(外) , P’(对外)},以此类推。

具体算法描述如下:

  1. 首先需要将句子按从左到右的顺序取出全部候选词w1,w2,…,wn;
  2. 计算每个候选词的概率值,并记录它的左邻词;
  3. 接着计算每个候选词的累计概率,累计概率最大的候选词为最佳左邻词;
  4. 如果wn为句子的尾词,且累计概率P’(wn)最大,则wn为句子终点词;
  5. 从wn开始,按从左到右的顺序,依次将每个词的最佳左邻词输出,即为句子的分词结果。

当句子很长的时候,句子的切分方式就很多,因此计算概率的次数和所消耗的内存空间就会很大,因此使用动态规划的方式来编写此算法。

使用列表dp[i]表示sentence[0, i](在python中为sentence[0: i+1],这里为了方便说明)的所有切分的最大概率,root[]记录使得dp[i]最大的词的起始坐标。使用word_tail表示尾词或当前词的下标,root[word_tail]则表示该词的起始下标。

详例描述

设置我们搜寻候选词的最大长度max_len = 4,初始化dp[],root[]长度为句子长度,每项均为0。
sentence = ‘对外经济技术合作与交流不断扩大。’

  1. word = ‘对’,查找词典中‘对’的概率为0.003388;
  2. dp[0] = 0.003388表示sentence[0, 0]当前的最大概率,root[0] = 0表示sentence[0]的词的起始下标为0;
  3. word = ‘对外’,查找词典中‘对’的概率为7.5e-05;
  4. dp[1] = 7.5e-05,表示sentence[0, 1]当前的最大概率;root[1] = 0,表示“对外”的起始下标为0,即“对外”此时为累计概率最大的词;
  5. ‘对外经’,‘对外经济’在词典中没有,故跳过;
  6. word = ‘外’,概率为0.00025,需要计算其累计概率P’(外) = P(外) * P’(对) = 0.00025 * dp[0],判断其是否大于dp[1],即是否大于P’(对外),若是,则替换dp[1],并将root[1]改为1。当此处为小于,故不会替换,跳过;
  7. 后面以此类推,再通过root[]记录的下标从尾词开始向前找概率最大的左邻词。

当句子过长时,概率相乘到最后会很小可能会导致溢出问题,故将概率取自然对数P(W)* = -lnP(W) = Σ[-lnP(wi)],于是求最大值问题就变成了求最小值问题。只需要在获取词典的时候取自然对数,并在计算累计概率时改为相加即可。

实现代码

首先是加载字典

# 获得词-词频字典
def get_dict(path):
    pro_dict = {}
    with open(path, 'r') as r:
        lines = r.readlines()
        for line in lines:
            sub = line.split(',')
            # 对概率取自然对数和负号
            # 使最大值问题转化为最小值问题
            pro_dict[sub[0]] = -math.log(float(sub[2].strip('\n')[:-1]) / 100)
    return pro_dict


max_len = 4 

分词算法

def max_pro(sentence):
    pro_dict = get_dict('WordFrequency.txt')
    dp = [999] * len(sentence)  # dp[i]表示sentence[0: i+1]的最大概率
    root = [0] * len(sentence)  # root[i]表示sentence[i]的词的起始下标
    for i in range(len(sentence)):
        for j in range(i, i+max_len):
            if j < len(sentence):
                word = sentence[i: j+1]
                # print(word)
                if word in pro_dict.keys():
                    temp_pro = pro_dict[word]
                    print(word, temp_pro)
                    if i > 0:
                        temp_pro += dp[i-1]
                    if temp_pro < dp[j]:
                        dp[j] = temp_pro
                        root[j] = i
            else:
                break
    # 输出结果
    result = []
    word_tail = len(sentence) - 1
    while word_tail >= 0:
        result.append(sentence[root[word_tail]: word_tail + 1])
        word_tail = root[word_tail] - 1
    result.reverse()
    return dp, root, result

主函数,测试了分词的具体时间,包括了加载字典所需要的时间

if __name__ == '__main__':
    start = time.time()
    dp, root, result = max_pro('对外经济技术合作与交流不断扩大。')
    # print(dp)
    # print(root)
    print(result)
    end = time.time()
    print("running time: " + str(end - start) + "s")
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值