Python在Jupyter上使用HMM进行中文分词,将新闻文本分词后提取其中的高频词

首先,需要三个.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()

输出结果如图所示
请添加图片描述

  • 3
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

词一丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值