1:分词技术

1.1:规则分词

基于规则的分词是一种机械分词的方法,主要是通过维护词典,在切分语句时,将语句的每个字符串与词表中的词进行逐一匹配,找到则切分,否则不切分。

1.1.1 正向最大匹配法

正向最大匹配法(Maximum Match Method,MM法)的基本思想:假定分词词典中的最长词有i个汉字字符,则用被处理文档的当前子串中的前i个字作为匹配字段,查找字典。如果字典中存在这样的一个i字词,则匹配成功,匹配字段被作为一个词切分出来。如果词典中找不到这样的一个i字词,则匹配失败,将匹配字段中的最后一个字去掉,对剩下的字串重新进行匹配处理。如此进行下去,直到匹配成功,即切分出一个词或剩余字串的长度为0为止。这样就完成了一轮匹配,然后取下一个i字字串进行匹配处理,直到文档被扫描完为止。

算法描述:

1:从左向右取待切分汉语句的m个字符作为匹配字段,m为机器词典中最长词条的字符数。

2:查找机器词典并进行匹配。若匹配成功,则将这个匹配字段作为一个词切分出来。若匹配不成功,则将这个匹配字段的最后一个字去掉,剩下的字符串作为新的匹配字段,进行再次匹配,重复以上过程明知道切分出所有词为止。

例如:

现在有个词典,最长词的长度是5,词典中有“南京市长”和“长江大桥”两个词。

现对“南京市长江大桥”

正向最大匹配法结果:“南京市长”,“江”,“大桥”

示例代码:

class MM(object):
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = 0
        text_length = len(text)
        dic = ['研究', '研究生', '生命', '命', '的', '起源', '牛皮', '厉害', '学习', '呕吼']
        while text_length > index:
            for size in range(self.window_size + index, index, -1):  # 4,0,-1
                piece = text[index:size]
                if piece in dic:
                    index = size - 1
                    break
            index = index + 1
            result.append(piece + '----')
        print(result)


if __name__ == '__main__':
    text = '研究生命的起源真牛皮车呀'
    tokenizer = MM()
    print(tokenizer.cut(text))

输出结果:

['研究生----', '命----', '的----', '起源----', '真----', '牛皮----', '车----', '呀----']

如此可看结果并不能让人很满意。

1.1.2 逆向最大匹配法

逆向最大匹配法(Reverse Maximum Match Method,RMM法)原理:基本原理与MM法相同,不同的是分词切分的方向与MM法相反。

现对“南京市长江大桥”进行逆向最大匹配法,结果:“南京市”,“长江大桥”。

这样的结果看似是对的,但是如果是 南京的市长 姓名是 "江大桥" 呢?这样分词 还是不对的。

示例代码:

class RMM(object):
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = len(text)
        dic = ['研究', '研究生', '生命', '命', '的', '起源', '牛皮', '厉害', '学习', '呕吼']
        while index > 0:
            for size in range(index - self.window_size, index):
                piece = text[size:index]
                if piece in dic:
                    index = size + 1
                    break
            index = index - 1
            result.append(piece + '----')
        result.reverse()
        print(result)


if __name__ == '__main__':
    text = '研究生命的起源真牛皮车呀'
    tokenizer = RMM()
    print(tokenizer.cut(text))

输出:

['研究----', '生命----', '的----', '起源----', '真----', '牛皮----', '车----', '呀----']

这样的话,输出结果就靠谱了点。

1.1.3 双向最大匹配法

双向最大匹配法(Bi-directction Matching method)是将正向最大匹配法得到的分词结果和逆向最大匹配法得到的结果进行比较,然后按照最大匹配原则,选取次数切分最少的作为结果。

例如:还是 “南京市长江大桥”,

正向结果:“南京市长”,“江”,“大”,“桥”

逆向结果:南京市,长江大桥。

示例代码:

class MM():
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = 0
        text_length = len(text)
        dic = ['研究', '研究生', '生命', '命', '的', '起源']
        while text_length > index:
            for size in range(self.window_size + index, index, -1):
                piece = text[index:size]
                if piece in dic:
                    index = size - 1
                    break
            index = index + 1
            result.append(piece + '----')
        return (result)

class RMM():
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = len(text)
        dic = ['研究', '研究生', '生命', '命', '的', '起源']
        while index > 0:
            for size in range(index - self.window_size, index):
                piece = text[size:index]
                if piece in dic:
                    index = size + 1
                    break
            index = index - 1
            result.append(piece + '----')
        result.reverse()
        return (result)

if __name__ == '__main__':
    text = '研究生命的起源'
    count1 = count2 = 0
    First = MM()
    Second = RMM()
    # result1 和 result2 分别是正向和逆向切词结果
    result1 = First.cut(text)
    result2 = Second.cut(text)
    # 如果两个结果一样,随便返回一个
    if result1 == result2:
        print(result1)
    else:
        # 获取 两个结果的长度
        # len() 方法返回对象(字符、列表、元组等)长度或项目个数。
        a = len(result1)
        b = len(result2)
        # 如果相同 循环两个数组 并比较 单词数量
        # 因为返回的分词都有后缀 '----',所以比较时==5
        if a == b:
            for A in result1:
                if len(A) == 5:
                    count1 = count1 + 1
            for B in result2:
                if len(B) == 5:
                    count2 = count2 + 1
            if count1 > count2:
                print(result2)
            else:
                print(result1)
        elif a > b:
            print(result2)
        elif a < b:
            print(result1)

结果:

['研究----', '生命----', '的----', '起源----']

1.2:统计分词

简介:

把每个词看做是由词的最小单位的各个字组成,如果相连的字在不同的文本中出现的次数越多,就证明这相连的字很有可能就是一个词。

利用字与字相邻出现的频率来反应成词的可靠度,统计语料中相邻垂涎的各个字的组合的频度,当这个组合达到某个临界值时,就认为这个词组是可以构成一个词语。

主要步骤:

1):建立统计语言模型。

2):对句子进行单词划分,然后对划分结果进行概率计算,获得概率最大的分词方式。用到了统计学习算法,如 隐含马尔可夫(HMM),条件随机场(CRF)等。

1.2.1:语言模型(详情自行百度了解)

1.2.2:HMM模型(详情自行百度了解)

1.2.3:其他统计分词算法(详情自行百度了解)

2:中文分词工具——Jieba

jieba分词官方地址:https://github.com/fxsjy/jieba

安装方式:

pip install jieba,或 pip3 install jieba

2.1:Jieba三种分词模式

  • 精确模式:试图将句子最精确的切分,适合文本分析。
  • 全模式:把句子中所有可以成词的词语都扫描出来,速度非常快,但是不能解决歧义。
  • 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

示例代码:

import jieba

sent = '中文分词是文本处理不可或缺的一步!'
seg_list = jieba.cut(sent, cut_all=True)
print('全模式', '/'.join(seg_list))
seg_list = jieba.cut(sent, cut_all=False)
print('精确模式', '/'.join(seg_list))
seg_list = jieba.cut(sent)
print('默认模式', '/'.join(seg_list))
seg_list = jieba.cut_for_search(sent)
print('搜索引擎模式', '/'.join(seg_list))

返回:

全模式 中文/分词/是/文本/文本处理/本处/处理/不可/不可或缺/或缺/的/一步/!
精确模式 中文/分词/是/文本处理/不可或缺/的/一步/!
默认模式 中文/分词/是/文本处理/不可或缺/的/一步/!
搜索引擎模式 中文/分词/是/文本/本处/处理/文本处理/不可/或缺/不可或缺/的/一步/!

2.2:实战之高频词提取

高频词:一般是指文档中出现频率较高且非无用的词语,其一定程度上代表了文档的焦点所在。

高频词提取其实就是自然语言处理的TF(Term Frequency)策略。主要有以下干扰项:

  • 标点符号:一般标点符号无任何价值,需要去除。
  • 停用词:诸如“的”“是”“了”等常用无任何意义,也需要提出。

代码示例:

# 读取数据
def get_content(path):
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        content = ''
        for l in f:
            l = l.strip()
            content += l
        return content


# 定义高频词统计的函授,输入为一个词的数组
def get_TF(words, topK=10):
    tf_dic = {}
    for w in words:
        tf_dic[w] = tf_dic.get(w, 0) + 1
    return sorted(tf_dic.items(), key=lambda x: x[1], reverse=True)[:topK]


if __name__ == '__main__':
    import glob
    import random
    import jieba

    files = glob.glob('../Desktop/1.txt')
    corpus = [get_content(x) for x in files]
    sample_inx = random.randint(0, len(corpus))
    split_words = list(jieba.cut(corpus[sample_inx]))

    print('样本之一:' + corpus[sample_inx])
    print('样本分词效果:' + '/'.join(split_words))
    print('样本的topk(10) 词:' + str(get_TF(split_words)))

输出结果:

样本之一:在苹果最新获批的一项技术专利中,展示了屏幕检测光线的技术细节。该技术专利可能帮助苹果将 Touch ID 指纹传感器和 Face ID 面部识别传感器整合到屏幕下方,从而取消目前 iPhone 机型中的刘海区域。在技术专利名称为“用于检测电子设备显示屏发光层所受光线的传感系统”,简而言之,苹果计划在显示屏下方嵌入一个光线检测传感器,该传感器可用于 Face ID 以及 Touch ID。该公司分享了该技术的两种潜在实现细节。在第一种情况下,显示屏背后的光线传感器可以组合出触摸屏幕的物体的图像。这暗示了Touch ID的回归。除此之外,苹果专利中还提到了使用环境光传感器进行操作。第二种实施方式将使用光电二极管或太阳能电池阵列来组装图像的像素。此外,该系统将积累来自光电二极管的电信号,以感知光的强度以及颜色。第二种实现方式指向Face ID,它可能会在未来的iPhone迭代中出现。需要注意的是,苹果公司申请了很多专利,不一定所有的专利都能商用。然而,该专利确实让人看到了该公司的未来前景和该技术的潜在引入。
样本分词效果:在/苹果/最新/获批/的/一项/技术/专利/中/,/展示/了/屏幕/检测/光线/的/技术细节/。/该/技术/专利/可能/帮助/苹果/将/ /Touch/ /ID/ /指纹/传感器/和/ /Face/ /ID/ /面部/识别/传感器/整合/到/屏幕/下方/,/从而/取消/目前/ /iPhone/ /机型/中/的/刘海/区域/。/在/技术/专利/名称/为/“/用于/检测/电子设备/显示屏/发光/层所受/光线/的/传感/系统/”/,/简而言之/,/苹果/计划/在/显示屏/下方/嵌入/一个/光线/检测/传感器/,/该/传感器/可/用于/ /Face/ /ID/ /以及/ /Touch/ /ID/。/该/公司/分享/了/该/技术/的/两种/潜在/实现/细节/。/在/第一种/情况/下/,/显示屏/背后/的/光线/传感器/可以/组合/出/触摸/屏幕/的/物体/的/图像/。/这/暗示/了/Touch/ /ID/的/回归/。/除此之外/,/苹果/专利/中/还/提到/了/使用/环境光/传感器/进行/操作/。/第二种/实施/方式/将/使用/光电/二极管/或/太阳能/电池/阵列/来/组装/图像/的/像素/。/此外/,/该/系统/将/积累/来自/光电/二极管/的/电信号/,/以/感知/光/的/强度/以及/颜色/。/第二种/实现/方式/指向/Face/ /ID/,/它/可能/会/在/未来/的/iPhone/迭代/中/出现/。/需要/注意/的/是/,/苹果公司/申请/了/很多/专利/,/不/一定/所有/的/专利/都/能/商用/。/然而/,/该/专利/确实/让/人/看到/了/该/公司/的/未来/前景/和/该/技术/的/潜在/引入/。
样本的topk(10) 词:[('的', 17), (' ', 15), (',', 13), ('。', 12), ('该', 8), ('专利', 7), ('了', 6), ('ID', 6), ('传感器', 6), ('在', 5)]

通过上面的结果,发现诸如,“的”“,”“。”“该”等词占据着很高的位置,而这类词对文章焦点并没有太大意义。

因此,我们可以自定义词典,然后进行优化。

首先定义一个自定义停用词库:

文本分词词频统计Java 文本分词技术_自然语言处理

添加代码:

def stop_words(path):
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        return [l.strip() for l in f]

修改主函数:

if __name__ == '__main__':
    import glob
    import random
    import jieba

    files = glob.glob('../Desktop/1.txt')
    corpus = [get_content(x) for x in files]
    sample_inx = random.randint(0, len(corpus))
    split_words = [x for x in jieba.cut(corpus[sample_inx]) if
                   x not in stop_words('../Desktop/stop_words.utf8')]
    print('样本之一:' + corpus[sample_inx])
    print('样本分词效果:' + '/'.join(split_words))
    print('样本的topk(10) 词:' + str(get_TF(split_words)))

最终结果:

样本的topk(10) 词:[(' ', 15), ('专利', 7), ('ID', 6), ('传感器', 6), ('技术', 5), ('苹果', 4), ('光线', 4), ('屏幕', 3), ('检测', 3), ('将', 3)]