当前位置:   article > 正文

发现新词 | NLP之无监督方式构建词库(三)_词库迭代

词库迭代

前言

  上文中提到的发现新词的方法默认认为:文本的相关性仅有相邻两字(2-grams)来决定,这在很多时候是不合理的。比如“林心如”中的“心如”、“共和国”中的“和国”,凝固度(相关性)都不是很强,容易错切。因此,本文就是在前文的基础上改进。

一、算法大意

  上文只考虑了相邻字的凝固度,这里同时考虑多字的内部的凝固度n-grams),比如,定义三字的字符串内部凝固度为:
m i n P ( a b c ) P ( a b ) P ( c ) P ( a b c ) P ( a ) P ( b c ) min{\frac{P(abc)}{P(ab)P(c)}\frac{P(abc)}{P(a)P(bc)}} minP(ab)P(c)P(abc)P(a)P(bc)P(abc)
这个定义其实也就是说,要枚举所有可能的切法,因为一个词应该是处处都很“结实”的。4字或以上的字符串凝固度类似定义。一般地,我们只需要考虑到4字(4-grams)就好(但是注意,我们依旧是可以切出4字以上的词来的)。
  考虑了多字后,我们可以设置比较高的凝固度阈值,同时防止诸如“共和国”之类的词不会被切错,因为考虑三字凝固度,“共和国”就显得相当结实了,所以,这一步就是“宁放过,勿切错”的原则。
  但是,“各项”和“项目”这两个词,它们的内部凝固度都很大,因为前面一步是“宁放过,勿切错”,因此这样会导致“各项目”也成词,类似的例子还有“支撑着”、“球队员”、“珠海港”等很多例子。但这些案例在3grams中来看,凝固度是很低的,所以,我们要有一个“回溯”的过程,在前述步骤得到词表后,再过滤一遍词表,过滤的规则就是,如果里边的n字词,不在原来的高凝固度的ngrams中,那么就得“出局”。
  所以,考虑ngrams的好处就是,可以较大的互信息阈值情况下,不错切词,同时又排除模凌两可的词。就比如“共和国”,三字互信息很强,两字就很弱了(主要还是因为“和国”不够结实),但是又能保证像“的情况”这种不会被切出来,因为阈值大一点,“的情”和“的情况”都不结实了。

二、算法步骤

  完整的算法步骤如下:

  1. 第一步,统计:选取某个固定的n,统计2grams3grams、…、ngrams,计算它们的内部凝固度,只保留高于某个阈值的片段,构成一个集合G;这一步,可以为2grams、3grams、…、ngrams设置不同的阈值,不一定要相同,因为字数越大,一般来说统计就越不充分,越有可能偏高,所以字数越大,阈值要越高;
  2. 第二步,切分:用上述grams对语料进行切分(粗糙的分词),并统计频率。切分的规则是,只要一个片段出现在前一步得到的集合G中,这个片段就不切分,比如“各项目”,只要“各项”和“项目”都在G中,这时候就算“各项目”不在G中,那么“各项目”还是不切分,保留下来;
  3. 第三步,回溯:经过第二步,“各项目”会被切出来(因为第二步保证宁放过,不切错)。回溯就是检查,如果它是一个小于等于n字的词,那么检测它在不在G中,不在就出局;如果它是一个大于n字的词,那个检测它每个n字片段是不是在G中,只要有一个片段不在,就出局。还是以“各项目”为例,回溯就是看看,“各项目”在不在3grams中,不在的话,就得出局。

每一步的补充说明

  1. 使用较高的凝固度,但综合考虑多字,是为了更准,比如两字的“共和”不会出现在高凝固度集合中,所以会切开(比如“我一共和三个人去玩”,“共和”就切开了),但三字“共和国”出现在高凝固度集合中,所以“中华人民共和国”的“共和”不会切开;
  2. 第二步就是根据第一步筛选出来的集合,对句子进行切分(你可以理解为粗糙的分词),然后把“粗糙的分词结果”做统计,注意现在是统计分词结果,跟第一步的凝固度集合筛选没有交集,我们认为虽然这样的分词比较粗糙,但高频的部分还是靠谱的,所以筛选出高频部分;
  3. 第三步,例如因为“各项”和“项目”都出现高凝固度的片段中,所以第二步我们也不会把“各项目”切开,但我们不希望“各项目”成词,因为“各”跟“项目”的凝固度不高(“各”跟“项”的凝固度高,不代表“各”跟“项目”的凝固度高),所以通过回溯,把“各项目”移除(只需要看一下“各项目”在不在原来统计的高凝固度集合中即可,所以这步计算量是很小的)

三、代码实现

1. 数据介绍

  本文针对一万多条商品名称语料来进行实验,数据格式如下:
在这里插入图片描述

2.python实现

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: admin
@file: main.py
@time: 2022/09/09
@desc:
    算法步骤:
        1.单词计数,并计算内部凝固度,保留高于某个阈值的片段,并保存到集合ngrams_
        2.用ngrams_对语料进行粗糙分词,生成初始词典word;切分原则是宁放过勿切错
        3.对词典word进行回溯,原则是如果它是一个小于等于n字的词,那么检测它在不在G中,不在就出局;
          如果它是一个大于n字的词,那个检测它每个n字片段是不是在G中,只要有一个片段不在,就出局
"""
import re
import codecs
from tqdm import tqdm
import hashlib

input_data = codecs.open("data/file_corpus.txt", 'r', encoding="utf-8")


# TODO:1.写迭代器输出文章
def texts():
    texts_set = set()
    # 引入tqdm是为了显示进度
    for a in tqdm(input_data):
        if a.encode('utf-8') in texts_set:
            continue
        else:
            texts_set.add(a.encode('utf-8'))
            # 引入正则表达式re是为了预先去掉无意义字符(非中文、非英文、非数字)
            for t in re.split(u'[^\u4e00-\u9fa50-9a-zA-Z]+', a):
                if t:
                    yield t
    print(u'最终计算了%s个商品名称' % len(texts_set))


# TODO:2.单词计数,分别为1-4grams,即算法步骤中的第一步统计
import numpy as np
from collections import defaultdict

n = 4  # 最长片段的子树
min_count = 3  # 片段阈值
ngrams = defaultdict(int)

for t in texts():
    for i in range(len(t)):
        for j in range(1, n + 1):
            if i + j <= len(t):
                ngrams[t[i:i + j]] += 1

ngrams = {i: j for i, j in ngrams.items() if j >= min_count}
total = 1. * sum([j for i, j in ngrams.items() if len(i) == 1])  # 总字数

# 计算它们的内部凝固度,只保留高于某个阈值的片段,构成一个集合ngrams_
min_proba = {2: 5, 3: 25, 4: 125}


def is_keep(s, min_proba):
    if len(s) >= 2:
        score = min([total * ngrams[s] / (ngrams[s[:i + 1]] * ngrams[s[i + 1:]]) for i in range(len(s) - 1)])
        if score > min_proba[len(s)]:
            return True
    else:
        return False


ngrams_ = set(i for i, j in ngrams.items() if is_keep(i, min_proba))
# 此时,input_data文件中的指针在最后,需要先关闭文件,再重新打开
input_data.close()
input_data = codecs.open("data/file_corpus.txt", 'r', encoding="utf-8")


# TODO:3.用上述ngrams_对语料进行切分(粗糙的分词),并统计频率;即算法步骤中的第二步:切分
# 宁放过,不切错
def cut(s):
    r = np.array([0] * (len(s) - 1))
    for i in range(len(s) - 1):
        for j in range(2, n + 1):
            a = s[i:i + j]
            if a in ngrams_:
                r[i:i + j - 1] += 1
    w = [s[0]]
    for i in range(1, len(s)):
        # 切分的规则是,只要一个片段出现在前一步得到的集合G中,这个片段就不切分
        # 宁放过,不切错
        if r[i - 1] > 0:
            w[-1] += s[i]
        else:
            w.append(s[i])
    return w


words = defaultdict(int)
for t in texts():
    for i in cut(t):
        words[i] += 1

words = {i: j for i, j in words.items() if j >= min_count}
print(len(words))


# TODO:4.回溯,
# 回溯就是检查,如果它是一个小于等于n字的词,那么检测它在不在G中,不在就出局;
# 如果它是一个大于n字的词,那个检测它每个n字片段是不是在G中,只要有一个片段不在,就出局
def is_real(s):
    if len(s) >= 3:
        for i in range(3, n + 1):
            for j in range(len(s) - i + 1):
                # 检测它每个n字片段是不是在G中
                if s[j:j + i] not in ngrams_:
                    return False
        return True
    else:
        return True


w = {i: j for i, j in words.items() if is_real(i) and len(i) > 1}
print(len(w))

# 将输出字典写入文件中
output = codecs.open("output.txt", "w", encoding="utf-8")
for i, j in sorted(w.items(), key=itemgetter(1), reverse=True):
    output.write(str(i) + "\t" + str(j) + "\n")

output.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
16762it [00:01, 11819.13it/s]
最终计算了16762个商品名称
16762it [00:02, 6871.14it/s]
最终计算了16762个商品名称
13438
10539
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

参考于

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/861212
推荐阅读
相关标签
  

闽ICP备14008679号