摘要
概率最大分词是分词的其中一种算法,通过选出句子中所有的候选词,计算它们的累计概率,在不同的词语组合中选出累计概率最大的组合作为最终的分词结果。这里使用python进行实现。
算法描述
首先解释一下累计概率怎么计算:假如待分词的句子为“对外经济技术合作与交流不断扩大。”,候选词可能有“对”、“对外”、“外”、“经济”等。对于每个词的累计概率,等于它原来的概率乘上累计概率最大的左邻词的概率,即P’(wi) = P(wi) * P(wi-1),对于作为句子起始的单词则是他本身的概率。
例如:P’(对) = P(对),P’(对外) = P(对外)。P’(外) = P(外) * P(对),P’(经济) = P(经济) * max{P’(外) , P’(对外)},以此类推。
具体算法描述如下:
- 首先需要将句子按从左到右的顺序取出全部候选词w1,w2,…,wn;
- 计算每个候选词的概率值,并记录它的左邻词;
- 接着计算每个候选词的累计概率,累计概率最大的候选词为最佳左邻词;
- 如果wn为句子的尾词,且累计概率P’(wn)最大,则wn为句子终点词;
- 从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 = ‘对外经济技术合作与交流不断扩大。’
- word = ‘对’,查找词典中‘对’的概率为0.003388;
- dp[0] = 0.003388表示sentence[0, 0]当前的最大概率,root[0] = 0表示sentence[0]的词的起始下标为0;
- word = ‘对外’,查找词典中‘对’的概率为7.5e-05;
- dp[1] = 7.5e-05,表示sentence[0, 1]当前的最大概率;root[1] = 0,表示“对外”的起始下标为0,即“对外”此时为累计概率最大的词;
- ‘对外经’,‘对外经济’在词典中没有,故跳过;
- word = ‘外’,概率为0.00025,需要计算其累计概率P’(外) = P(外) * P’(对) = 0.00025 * dp[0],判断其是否大于dp[1],即是否大于P’(对外),若是,则替换dp[1],并将root[1]改为1。当此处为小于,故不会替换,跳过;
- 后面以此类推,再通过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")