NLP从零开始------4基础文本处理之分词(2)

1.中文分词工具jieba库

        随着近年来NLP技术的快速发展,自然语言处理中实现中文分词的工具逐渐增多,其中包括Ansj、HanLP和盘古分词等分词工具。 由于在实际开发与研究过程中,使用jieba进行中文分词的人员占大多数,使用较为广泛。 相比其他分词工具而言,jieba不仅包含分词这一功能,而且提供了许多分词以外的算法。 jieba使用简单,并且支持Python、R、C++等多种编程语言的实现,对于新手而言是一个较好的入门分词工具。 在Github社区上,jieba长期有着较高的活跃度,社区中也有不少jieba分词实例,遇到问题时可以在社区中反馈。

        基于规则或词典、n-gram模型、隐马尔可夫模型、条件随机场模型等分词方法,在实际应用中效果差异并不大。 分词技术更多的是以一种分词方法为主,其余分词方法为辅达到更高的分词准确率。 最常用的是以基于词典的分词方法为主,以统计分词方法为辅进行中文分词,这种方法既能较好地处理未登录词和歧义词,又能避免词典准确率带来的问题。 中文分词工具jieba库就采用了这种方法进行中文分词。

        ieba分词结合了基于规则和基于统计的分词方法,分词过程包含3个步骤。 基于前缀词典快速扫描词图,搭建可能的分词结果的有向无环图,构成多条分词路径。如待分词语句为“天津市河西区有点远”,词典中出现了“天”字,以“天”开头的词语都会出现,如“天津”、继而出现“天津市”、“天津市河西区”,前缀词典是按照包含前缀词顺序进行的。有向无环图是指所有的分词路径都按照正向的顺序,如果将每一个词语看成一个节点,那么分词路径对应了从第一个字到最后一个字的分词方式,词语之间不能构成一个回路。 采用动态规划的方法寻找最大概率路径,从右往左反向计算最大概率,依此类推,得到概率最大的分词路径,作为最终的分词结果。 采用HMM模型处理未登录词,借助模型中语句构成的4个状态B、M、E、S推导,最后利用维特比算法求解最优分词路径。

        jieba支持精确模式,全模式和搜索引擎三种分词模式:

        精确模式采用最精确的方式将语句切开,适用于文本分析。

        全模式可以快速地扫描语句中所有可以成词的部分,但无法解决歧义问题。

        搜索引模式在精确模式的基础上再切分长词,适用于搜索引擎的分词。 通过对 “中文分词是自然语言处理的一部分!”采用三种模型进行分词,介绍jieba的分词模式。

        我们通过一段代码来看一看实现形式,看一看三种模式区别:

       (1)首先需要打开并运行nlp环境

conda activate nlp

        (2)conda下载库

conda install -c conda-forge jieba

        如果下载失败看下方文章:

Anaconda-Python安装jieba库_anaconda 安装jieba-CSDN博客

        (3)输入以下代码:

import jieba

text="中文分词是自然语言处理的一部分!"

seq_list=jieba.cut(text,cut_all=True)
print('全模式:','/'.join(seq_list))

seq_list=jieba.cut(text,cut_all=False)
print('精确模式:','/'.join(seq_list))

seq_list=jieba.cut_for_search(text)
print('搜索引擎模式:','/'.join(seq_list))

      (4)3种分词模式的结果如下:

D:\ana\envs\nlp\python.exe D:\pythoncode\nlp\demojiaba.py 
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\86130\AppData\Local\Temp\jieba.cache
全模式: 中文/分词/是/自然/自然语言/语言/处理/的/一部/一部分/部分/!
精确模式: 中文/分词/是/自然语言/处理/的/一部分/!
搜索引擎模式: 中文/分词/是/自然/语言/自然语言/处理/的/一部/部分/一部分/!
Loading model cost 0.541 seconds.
Prefix dict has been built successfully.

进程已结束,退出代码为 0

        全模式和搜索引擎模式会打印所有可能的分词结果,精确模式仅输出一种分词,除了一些适合全模式和搜索引擎模式的场合,一般情况下会较多地使用精确模式。

         这3种模式的分词主要用jieba.cut函数和jieba.cut_for_search函数。

         jieba.cut函数可输入3个参数,待分词字符串、cut_all参数选择是否采用全模式(默认为精确模式)、HMM参数控制是否使用HMM模型。

         jieba.cut_for_search函数可输入两个参数。待分词字符串、是否使用HMM模型。

2.HMM中文分词(数据文件在文章顶部)

        HMM中文分词是一种基于统计学习的分词技术,通过建立字与字之间的状态转移模型来识别词语边界。它利用隐马尔可夫模型(Hidden Markov Model, HMM)对文本进行序列标注,区分单字词、词首、词中和词尾,从而实现自动分词。HMM分词适用于处理未登录词,且在特定领域文本上表现良好,但需要大量标注数据进行训练。

       使用Python代码实现HMM分词的过程主要包括训练HMM、定义viterbi函数、调用HMM模型与viterbi函数进行分词3个步骤。

2.1训练HMM

        训练HMM 训练HMM过程定义了train函数,用于在给定语料下,统计并计算各个位置状态的初始概率、转移概率和发射概率。函数train定义了3个用于存放初始概率、转移概率和发射概率的字典,将结果存至json文件当中。训练HMM的过程包含4个步骤。

        1.加载需要的库,输入待分词文本。

        2.读取语料。语料收集了国内2012年6月和7月期间搜狐新闻中国际、体育、社会、娱乐等18个频道的新闻内容,对其进行预处理后存放于trainCorpus.txt文件中,语料中每句话中的每个词都以空格隔开,读取每一行中的词语并标注其位置状态信息,共有B、E、M、S四种位置状态。

        3.计算概率参数。统计每个出现在词头的位置状态的次数,得到初始状态概率;统计每种位置状态转移至另一种状态的次数,得到转移概率;统计每个位置状态下对应的字以及其出现次数,计算时采用加法平滑,得到其发射概率。

         4.存储概率参数。将初始概率、转移概率和发射概率写入json文件当中,dumps函数用于将字典转化为字符串格式,enumerate函数用于将对象组合为一个索引序列。

2.2定义viterbi函数

        viterbi函数用于实现维特比算法。将待分词文本输入其中,可以得到最大概率时每个字的位置状态序列。viterbi函数包含4个参数,分别是待分词文本、4个位置状态、初始概率、转移概率和发射概率。 定义viterbi函数包含以下3个步骤。

        1.对待分词文本的第一个字,计算4个位置状态下的初始状态概率,在当前语料下,寻找每个字在上述发射概率字典中对应的概率值,计算其发射概率。

         2.求解在4个位置状态下,待分词文本中每个字的最大概率位置状态,求得最大概率的位置状态序列。

        3.根据待分词文本末尾字的位置状态,从状态序列中选取其中概率最大的。函数结果将返回最大的概率值和对应的位置状态序列。

2.3分词

        分词通过cut函数实现。cut函数的参数text为待分词文本。cut函数利用json库中的loads函数调用已保存的json文件,再调用viterbi算法求得概率最大的状态序列,最后判断待分词文本每个字的位置状态,对文本进行分词并输出结果。

         需要注意的是每次程序运行结束后,如果需要再次运行时,需要先删除已生成的json文件后再运行程序,否则会继续对原文件写入内容,出现解析错误。

2.4HMM中文分词代码模板

import json
from collections import defaultdict
import math

# 定义HMM模型参数
class HMMModel:
    def __init__(self):
        self.initial_prob = {}      # 初始状态概率
        self.transition_prob = {}   # 状态转移概率
        self.emission_prob = {}     # 观察概率

# 训练HMM模型
def train_hmm(corpus_path, model):
    with open(corpus_path, 'r', encoding='utf-8') as f:
        sentences = f.readlines()
    
    # 初始化统计变量
    tag_count = {'B': 0, 'M': 0, 'E': 0, 'S': 0}
    transitions = {'B': {}, 'M': {}, 'E': {}, 'S': {}}
    emissions = defaultdict(lambda: defaultdict(int))

    # 统计数据
    for sentence in sentences:
        words = sentence.strip().split()
        tags = 'S' if len(words) == 1 else 'B' + 'M' * (len(words) - 2) + 'E'
        for i, tag in enumerate(tags):
            tag_count[tag] += 1
            for j in range(i, len(words)):
                if transitions[tag].get(words[j], None) is None:
                    transitions[tag][words[j]] = defaultdict(int)
                transitions[tag][words[j]]['BME'[j+1]] += 1
            emissions[words[0]]['S'] += 1
    
    # 计算初始状态概率
    for tag, count in tag_count.items():
        model.initial_prob[tag] = math.log(count / sum(tag_count.values()))
    
    # 计算状态转移概率和观察概率
    for tag, words in transitions.items():
        for word, next_tags in words.items():
            total = sum(next_tags.values())
            for next_tag in next_tags:
                model.transition_prob[(tag, word)] = {next_tag: math.log(next_tags[next_tag] / total)}
    
    for word, tags in emissions.items():
        total = sum(tags.values())
        for tag in tags:
            model.emission_prob[(word, tag)] = math.log(tags[tag] / total)

    return model

# Viterbi算法
def viterbi(observed, model):
    V = [defaultdict(float) for _ in observed]
    V[0][model.initial_prob] = 1.0
    for t, observation in enumerate(observed):
        for y in model.transition_prob:
            word, tag = y
            if V[t].get((model.emission_prob[(word, 'S')], 'S')) is not None:
                V[t+1][(model.emission_prob[(word, 'S')], 'S')] += V[t][y] * model.emission_prob[(word, 'S')]
            elif V[t].get((model.emission_prob[(word, 'B')], 'B')) is not None:
                V[t+1][(model.emission_prob[(word, 'B')], 'B')] += V[t][y] * model.emission_prob[(word, 'B')]
            for z in model.transition_prob[y]:
                for z_tag, z_prob in model.transition_prob[y][z].items():
                    if V[t].get((z_prob, z_tag), 0) > 0:
                        V[t+1][(model.emission_prob[(observation, z_tag)], z_tag)] += V[t][(z_prob, z_tag)] * model.emission_prob[(observation, z_tag)]
    return max(V[-1].items(), key=lambda x: x[1])

# 分词函数
def cut(text, model):
    words = []
    observed = []
    for char in text:
        if char.isalpha() or char.isdigit():  # 假设文本已经分好词
            observed.append(char)
        else:
            result, _ = viterbi(observed, model)
            words.append(''.join(observed))
            observed = []
    if observed:
        result, _ = viterbi(observed, model)
        words.append(''.join(observed))
    return ' '.join(words)

# 使用示例
if __name__ == "__main__":
    model = HMMModel()
    model = train_hmm('path_to_corpus.txt', model)  # 训练模型
    corpus = "这是一个HMM中文分词的示例"  # 待分词文本
    print(cut(corpus, model))  # 分词

3.提取新闻文本的高频词(数据文件在文章顶部)

        如果一个词语在一篇文档中频繁出现并且有意义,说明该词能很好地代表这篇文档的主要特征,这样的词语称为高频词。

         在单篇文档中是关键词,在类似于新闻的文章是热词。字词的重要性随着它在文档中出现次数的增加而上升,随着它在语料库中出现频率的升高而下降。

         高频词的提取时常常会遇到两个问题。 一是语句之间的标点符号,分词前需要删除。 二是类似于“是”“在”“的”等常用的停用词,需要删除。

        利用jieba提取高频词包含3个步骤,详情如下。

        1. 读取news.txt文件。

        2.这是一个存放新闻文本的文件,新闻内容来自搜狐新闻。 加载停用词文件stopword.txt,对新闻内容进行jieba分词。

        3.提取出现频次最高的前十个词语,依次输出文档内容、分词后的文档和出现频次最高的十个词语。

        示例代码:

import jieba
from collections import Counter

# 读取新闻文本文件
with open('news.txt', 'r', encoding='utf-8') as file:
    news_content = file.read()

# 读取停用词文件
with open('stopword.txt', 'r', encoding='utf-8') as file:
    stopwords = set([line.strip() for line in file])

# 使用jieba进行分词
words = jieba.cut(news_content)

# 过滤停用词并统计词频
word_counts = Counter(word for word in words if word not in stopwords)

# 获取出现频次最高的前十个词语
top_ten_words = word_counts.most_common(10)

# 输出结果
print("原始文档内容:")
print(news_content)
print("\n分词后的文档:")
print(' '.join(words))
print("\n出现频次最高的十个词语:")
for word, freq in top_ten_words:
    print(word, freq)

4.思考示例代码

 思考一:《西游记》字符过滤

         在进行中文分词前,数据格式要求全部是中文,此时需要对文本的以下内容进行过滤处理。 特殊符号 标点 英文 数字 读者也可以根据自己的要求过滤自定义字符。

import re

text = "这是《西游记》中的一段文字,包含特殊符号@#$%^&*(),标点,英文Hello World,数字123456。"

# 过滤特殊符号、标点、英文、数字
filtered_text = re.sub(r'[^\w\s\u4e00-\u9fa5]', '', text)

print(filtered_text)

思考二:自动提取人名与电话号码

        文本人名正则表达式匹配方法如下。 名字首先匹配第一个大写字母采用[A-Z]表示。 名字第二个字符只包含英文句号、空格或字母,统一采用[\.a-zA-Z]表示。 最后用+表示允许匹配多个。 电话号码正则表达式匹配方法如下。 以“(”或数字开头,采用[0-9(]表示。 第二个字符接数字或“)”或英文句号,采用[0-9- ).]表示。 最后用+表示允许匹配多个。

import re

text = "联系人:Alice Smith,电话号码:(123) 456-7890。"

# 提取人名
names = re.findall(r'[A-Z][\.a-zA-Z]+', text)

# 提取电话号码
phone_numbers = re.findall(r'\([0-9- ).]+\)?', text)

print("人名:", names)
print("电话号码:", phone_numbers)

思考三:提取网页标签信息

          例如,一个网页标签内容为“<a href=”http://www.baidu.com“>百度</a> <a href=”http://www.google.com“>谷歌</a>”。 需要提取网页标签中的网址和文本。 通过观察,网址信息保存在href属性中,文本内容则是在特殊字符中间。

import re

html_content = '<a href="http://www.baidu.com">百度</a> <a href="http://www.google.com">谷歌</a>'

# 提取网址
urls = re.findall(r'href="(http[s]?://[^"]+)"', html_content)

# 提取文本
texts = re.findall(r'>([^<]+)<', html_content)

print("网址:", urls)
print("文本:", texts)

  • 38
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值