首先,需要三个.txt文件:
需要提取高频词的搜狐新闻文本news.txt(需要5积分,不能白嫖,气四~)
训练HMM的trainCorpus.txt(需要5积分,不能白嫖,气四~)
停顿词stopword.txt(白嫖,复制到txt即可,开森~)
一、HMM中文分词
使用Python实现HMM分词的过程主要包括训练HMM、定义viterbi函数、分词三个步骤
1、训练HMM
训练HMM过程定义了train函数,用于在给定语料下,统计并计算各个位置状态的初始概率、转移概率和发射概率。
train函数定义了三个用于存放初始概率、转移概率和发射概率的字典,并将结果存至JSON文件当中。
训练HMM的过程包含4个步骤:
(1)加载需要的库,输入待分词文本。
(2)读取语料。语料中的每句话中的每个词都以空格隔开,读取每一行中的词语并标注其位置状态信息,共有B、E、M、S四种位置状态。
(3)计算概率参数。统计每个出现在词头的位置状态的次数,得到初始状态概率;统计每种位置状态转移至另一种状态的次数,得到转移概率;统计每个位置状态下对应的字及其出现次数,计算时采用加法平滑,得到发射概率。
(4)存储概率参数。将初始概率、转移概率和发射概率写入JSON文件中,dumps函数用于将字典转化为字符串格式,enumerate函数用于将对象组合为一个索引序列。
import os
# 若要二次运行,则需删除已生成的json文件,否则会继续对原文件写入内容并出现解析错误
import json
import datetime
text = '学校是学习的好地方!'
def train():
# 初始化参数
trans_prob = {} # 转移概率
emit_prob = {} # 发射概率
init_prob = {} # 状态出现次数
Count_dict={}
state_list = ['B', 'M', 'E', 'S']
for state in state_list:
trans = {}
for s in state_list:
trans[s] = 0
trans_prob[state] = trans
emit_prob[state] = {}
init_prob[state] = 0
Count_dict[state] = 0
count = -1
# 读取并处理单词、计算概率矩阵
path = 'E:/data/trainCorpus.txt'
for line in open(path, 'r'):
count += 1
line = line.strip()
if not line:
continue
# 读取每一行的单词
word_list = []
for i in line:
if i != ' ':
word_list.append(i)
# 标注每个单词的位置标签
word_label = []
for word in line.split():
label = []
if len(word) == 1:
label.append('S')
else:
label += ['B'] + ['M'] * (len(word) - 2) + ['E']
word_label.extend(label)
# 统计各个位置状态下的出现次数,用于计算概率
for index, value in enumerate(word_label):
Count_dict[value] += 1
if index == 0:
init_prob[value] += 1
else:
trans_prob[word_label[index - 1]][value] += 1
emit_prob[word_label[index]][word_list[index]] = (
emit_prob[word_label[index]].get(
word_list[index], 0) + 1.0)
# 初始概率
for key, value in init_prob.items():
init_prob[key] = value * 1 / count
# 转移概率
for key, value in trans_prob.items():
for k, v in value.items():
value[k] = v / Count_dict[key]
trans_prob[key] = value
# 发射概率,采用加1平滑
for key, value in emit_prob.items():
for k, v in value.items():
value[k] = (v + 1) / Count_dict[key]
emit_prob[key] = value
# 将3个概率矩阵保存至json文件
model = 'E:/data/hmm_model.json'
f = open(model, 'a+')
f.write(json.dumps(trans_prob) + '\n' + json.dumps(emit_prob) +
'\n' + json.dumps(init_prob))
f.close()
2、定义viterbi函数
viterbi函数用于实现维特比算法。将待分词文本输入其中,可以得到最大概率时每个字的位置状态序列。
viterbi函数包含5个参数,分别是待分词文本、4个状态位置、初始概率、转移概率和发射概率。
viterbi函数需要实现以下3个步骤:
(1)对待分词文本的第一个字,计算4个位置状态下的初始概率。在当前语料下,寻找每个字在上述发射概率字典中对应的概率值,计算其发射概率。
(2)求解在4个位置状态下,待分词文本中每个字的最大概率的位置状态,求得最大概率位置状态序列。
(3)根据待分词文本末尾字的位置状态,从状态序列中选取其中概率最大的,函数将返回最大的概率值和对应的位置状态序列。
def viterbi(text, state_list, init_prob, trans_prob, emit_prob):
V = [{}]
path = {}
# 初始概率
for state in state_list:
V[0][state] = init_prob[state] * emit_prob[state].get(text[0], 0)
path[state] = [state]
# 当前语料中所有的字
key_list = []
for key, value in emit_prob.items():
for k, v in value.items():
key_list.append(k)
# 计算待分词文本的状态概率值,得到最大概率序列
for t in range(1, len(text)):
V.append({})
newpath = {}
for state in state_list:
if text[t] in key_list:
emit_count = emit_prob[state].get(text[t], 0)
else:
emit_count = 1
(prob, a) = max(
[(V[t - 1][s] * trans_prob[s].get(state, 0)* emit_count, s)
for s in state_list if V[t - 1][s] > 0])
V[t][state] = prob
newpath[state] = path[a] + [state]
path = newpath
# 根据末尾字的状态,判断最大概率状态序列
if emit_prob['M'].get(text[-1], 0) > emit_prob['S'].get(text[-1], 0):
(prob, a) = max([(V[len(text) - 1][s], s) for s in ('E', 'M')])
else:
(prob, a) = max([(V[len(text) - 1][s], s) for s in state_list])
return (prob, path[a])
3、分词
分词通过cut函数来实现。cut函数的参数text为待分词文本。cut函数利用JSON库中的loads函数调用已保存的JSON文件,再调用viterbi算法求得概率最大的位置状态序列,最后判断待分词文本每个字的位置状态,对文本进行分词并输出结果。
注意:每次程序运行结束后,如果需要再次运行,需要先删除已生成的JSON文件,否则会继续对原文件写入内容,出现解析错误。
def cut(text):
state_list = ['B', 'M', 'E', 'S']
model = 'E:/data/hmm_model.json'
# 先检查当前路径下是否有json文件,如果有json文件,需要删除
if os.path.exists(model):
f = open(model, 'rb')
trans_prob = json.loads(f.readline())
emit_prob = json.loads(f.readline())
init_prob = json.loads(f.readline())
f.close()
else:
trans_prob = {}
emit_prob = {}
init_prob = {}
# 利用维特比算法,求解最大概率状态序列
prob, pos_list = viterbi(text, state_list, init_prob, trans_prob, emit_prob)
# 判断待分词文本每个字的状态,输出结果
begin, follow = 0, 0
for index, char in enumerate(text):
state = pos_list[index]
if state == 'B':
begin = index
elif state == 'E':
yield text[begin: index+1]
follow = index + 1
elif state == 'S':
yield char
follow = index + 1
if follow < len(text):
yield text[follow:]
# 训练、分词
starttime = datetime.datetime.now()
train()
endtime = datetime.datetime.now()
print((endtime-starttime).seconds)
cut(text)
print(text)
print(str(list(cut(text))))
结果如下图
二、提取新闻文本中的高频词
如果一个词语在一片文档中频繁出现并且有意义,说明该词语能很好地代表这篇文档的主要特征,这样的词语称为高频词。
提取高频词时会常常遇到两个问题。一是分词前需要删除语句之间的标点符号;二是需要删除类似“是”“在”“的”等常用的停用词。
利用jieba提取高频词包含3个步骤:
(1)读取news.txt文件。
(2)加载停用词文件stopword.txt,对新闻内容进行jieba分词。
(3)提取出现频率最高的前10个词语,一次输出文档内容、分词后的文档和出现频率最高的10个词语。
import jieba
def word_extract():
# 读取文件
corpus = []
path = 'E:/data/news.txt'
content = ''
for line in open(path, 'r', encoding='gbk', errors='ignore'):
line = line.strip()
content += line
corpus.append(content)
# 加载停用词
stop_words = []
path = 'E:/data/stopword.txt'
for line in open(path, encoding='utf8'):
line = line.strip()
stop_words.append(line)
# jieba分词
split_words = []
word_list = jieba.cut(corpus[0])
for word in word_list:
if word not in stop_words:
split_words.append(word)
# 提取前10个高频词
dic = {}
word_num = 10
for word in split_words:
dic[word] = dic.get(word, 0) + 1
freq_word = sorted(dic.items(), key = lambda x: x[1],
reverse=True) [: word_num]
print('样本:' + corpus[0])
print('样本分词效果:' + '/ '.join(split_words))
print('样本前10个高频词:' + str(freq_word))
word_extract()
输出结果如图所示