ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

自然语言处理 -分词初窥

2021-01-18 10:31:34  阅读:163  来源: 互联网

标签:word 处理 text len 切分 匹配 自然语言 分词


中文分词是指的是将一段文本拆分为一系列单词的过程,这些单词顺序拼接后等于原文本。粗略的讲,中文分词方法分为基于字符串匹配的分词算法,基于理解的分词算法和基于统计的分词算法。

  1. 基于字符串匹配的分词算法
    也称机械分词算法,它会提前维护一个大的字典,然后将句子和字典中的词进行匹配,若匹配成功,则可以进行分词处理。当字典足够大的时候,就需要考虑不同的匹配算法,通常会基于 Trie 树结构,来实现高效的词图扫描。

  2. 基于统计的分词算法
    给出大量已经分词的文本,利用统计机器学习模型学习词语切分的规律(称为训练),从而实现对未知文本的切分。随着大规模语料库的建立,统计机器学习方法的研究和发展,基于统计的中文分词方法渐渐成为了主流方法。常用算法有HMM、CRF、SVM、深度学习等。

  3. 基于理解的分词算法
    这种分词方法是通过让计算机,模拟人对句子的理解,达到识别词组的效果。基本思想是在分词的同时进行句法、语义的分析,利用句法和语义信息来处理歧义现象。通常包含三部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息,来对分词歧义进行判断,模拟人对句子的理解过程。

当下常用的分词器都是使用机器学习算法和词典相结合,一方面能够提高分词准确率,另一方面能够改善领域适应性。分词领域的研究已有诸多文献、书籍及相关参考资料,也存在很多成熟的技术与工具包,本文抛砖引玉,仅简要介绍基于字典的分词理论与方法的实现。

中文分词的难点

中文分词难点主要体现在三个方面:分词的规范、歧义词的切分和未登录词识别

  1. 分词的规范。
    中文因其自身语言特性的局限,字(词)的界限往往很模糊,关于字(词)的抽象定义和词边界的划定尚没有一个公认的、权威的标准。这种不同的主观分词差异,给汉语分词造成了极大的困难。

  2. 歧义词切分。

中文中的歧义词是很普遍,即同一个词有多种切分方式。普遍认为中文歧义词有三种类型。

  • 交集型切分歧义,汉语词如 AJB 类型,满足 AJ 和 JB 分别成词。如“兵乓球拍卖完了”可切分为“乒乓球/拍卖/完了”,也可以切分为“乒乓球拍/卖/完了”,很难去判定哪种切分正确,即使是人工切分也只能依据上下文,类似的有“美国会”等。

  • 组合型切分歧义,分词是有不同的粒度的,指某个词条中的一部分也可以切分为一个独立的词条。

  • 混合型切分歧义,汉语词包含如上两种共存情况。

3.未登录词(新词)识别。

未登录词又称新词。这类词通常指两个方面,一是词库中没有收录的词,二是训练语料没有出现过的词。未登录词主要体现在以下几种:

  • 新出现的网络用词。如“李姐万岁”、“月入一狗”、“打工人”、“低端人口”等。
  • 研究领域名称:特定领域和新出现领域的专有名词。如“冠性病毒”、“埃博拉”、“三聚氰胺”等。
  • 其他专有名词:诸如城市名、公司企业、职称名、电影、书籍、专业术语、缩写词等。如“砍爷”、“星链”、“一钞钟”等。

基于字典的分词方法

基于词典的方法是经典的传统分词方法,这种方式先从大规模的训练语料中提取分词词库,将词语的词频统计出来,之后便可以通过最大匹配、N-最短路径或者 N-Gram 模型等分词方法对句子进行切分。

基于词典的分词方法优势是比较直观,操作简单。当某个新出现的名词无法被正确切分的时候,我们可以直接在词典当中进行添加,以达到正确切分的目的。但由于过于依赖于词典,这种分词方法对于未登录词的处理不是很好,并且当词典当中的词出现公共子串的时候,就会出现歧义切分的问题,这需要语料库足够的丰富,从而能够对每个词的频率有一个很好的设置。

主要策略有以下几种:

  1. 最大匹配法(又分为正向最大匹配、逆向最大匹配、双向最大匹配)
  2. N-最短路径
  3. N-gram 语法模型

最大匹配算法

  1. 正向最大匹配算法的策略是以某个下标为起点递增查词,优先输出更长的单词。
  2. 逆向最大匹配算法则从后向前匹配,优先输出最长的单词。
  3. 双向最大匹配算法是一种融合两种匹配方法的复杂规则集,流程为:
    • 同时执行正向和逆向最长匹配,若两者的词数不同,则返回词数更少的那一个。
    • 否则,返回两者中单字更少的那一个。当单字数也相同时,优先返回逆向最长匹配的结果。

我们考虑使用 HanLP 附带的迷你核心词典为例:https://github.com/NLP-LOVE/Introduction-NLP/blob/master/data/dictionnary/CoreNatureDictionary.mini.txt。词典格式是一种以空格分隔的表格形式,第一列是单词本身,之后每两列分别表示词性与相应的词频。

龙胆	n	1
龙胆科	n	1
龙胆紫	n	1
龙脉	n	2
龙腾虎跃	i	8
龙舞	n	1

正向、逆向、双向最大匹配算法代码实现:

def load_dictionary():
    dic = set()
    for line in open("data/dict.txt","r"):
        dic.add(line.split('\t')[0].rstrip())
    return dic

def forward_segment(text, dic):
    word_list = []
    i = 0
    while i < len(text):
        longest_word = text[i]                      # 当前扫描位置的单字
        for j in range(i + 1, len(text) + 1):       # 所有可能的结尾
            word = text[i:j]                        # 从当前位置到结尾的连续字符串
            if word in dic:                         # 在词典中
                if len(word) > len(longest_word):   # 并且更长
                    longest_word = word             # 则更优先输出
        word_list.append(longest_word)              # 输出最长词
        i += len(longest_word)                      # 正向扫描
    return word_list

def backward_segment(text, dic):
    word_list = []
    i = len(text) - 1
    while i >= 0:                                   # 扫描位置作为终点
        longest_word = text[i]                      # 扫描位置的单字
        for j in range(0, i):                       # 遍历[0, i]区间作为待查询词语的起点
            word = text[j: i + 1]                   # 取出[j, i]区间作为待查询单词
            if word in dic:
                if len(word) > len(longest_word):   # 越长优先级越高
                    longest_word = word
                    break
        word_list.insert(0, longest_word)           # 逆向扫描,所以越先查出的单词在位置上越靠后
        i -= len(longest_word)
    return word_list

def count_single_char(word_list: list):  # 统计单字成词的个数
    return sum(1 for word in word_list if len(word) == 1)


def bidirectional_segment(text, dic):
    f = forward_segment(text, dic)
    b = backward_segment(text, dic)
    if len(f) < len(b):                                  # 词数更少优先级更高
        return f
    elif len(f) > len(b):
        return b
    else:
        if count_single_char(f) < count_single_char(b):  # 单字更少优先级更高
            return f
        else:
            return b             

if __name__ == '__main__':
    text = "结婚的和尚未结婚的"
    text = "工信处女干事"
    dt = load_dictionary()
    print(forward_segment(text, dt))
    print(backward_segment(text, dt))
    print(bidirectional_segment(text, dt))

返回结果:

  • 正向匹配:[‘结婚’, ‘的’, ‘和尚’, ‘未’, ‘结婚’, ‘的’]
  • 逆向匹配:[‘结婚’, ‘的’, ‘和’, ‘尚未’, ‘结婚’, ‘的’]
  • 双向匹配:[‘结婚’, ‘的’, ‘和’, ‘尚未’, ‘结婚’, ‘的’]

注:以上代码中我们用了基于哈希表(Python 中的 dict)的实现,真实项目中会考虑使用字典树(Trie)结构来进行匹配。

基于字的分词方法

同样是基于词典,由薛念文等人于2002年提出的基于字的分词方法将分词过程看作是字的分类问题,其认为每个字在构造一个特定词语时都占据着一个确定的构词位置(词位)。一般情况下,我们认为每个字的词位有4种情况:B(Begin)、E(End)、M(Middle)、S(Single),那么我们对于一个句子的切分就可以转为对句子中每个字打标签的过程,比如以下例子:

  • 中文分词有很多方法,比如机器学习
  • 中 B 文 E 分 B词 E 有 S 很 B 多 E 方 B 法 E 比 B 如 E 机 B 器 M 学 M 习 E

我们对句子中的每个字赋予了一个词位,即 BEMS 中的一个标签,这样就完成了分词的目的。

基于字的方法将传统的语言学问题转换为了一个更加容易建模的序列标注问题,我们可以用最大熵模型为每个字进行标签分类;也可以利用 HMM 将其看作一个解码问题;或者考虑句子间的时序关系,利用判别模型 CRF 或者 LSTM 进行建模。这种方法在各种分词大赛上取得了不错的成绩,尤其是对未登录词问题的处理,召回率一直很高。

参考

HanLP附带的迷你核心词典为例 http://git.io/JtfDd

标签:word,处理,text,len,切分,匹配,自然语言,分词
来源: https://blog.csdn.net/SLP_L/article/details/112763427

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有