新词发现是 NLP 的基础任务之一,主要是通过统计,无监督学习等算法技术手段挖掘“未登陆词”。常见的方法有基于左右信息熵的方式、基于神经网络、基于分词的手段等等。
新词发现技术
- 基于点间互信息(Pointwise Mutual Information)和左右信息熵(Information Entropy)的方式。
- 基于神经网络。
基于互信息和左右熵
这种方法中,首先引入了两个概念:
- 点间互信息(Pointwise Mutual Information),它表示凝固程度,即多个词一起出现的可能性。
- 左右信息熵(Information Entropy),它表示自由程度,即其独立存在的程度。
点间互信息
首先我们来看一下点间互信息的公式:
\(PMI(x, y) = \log_2{p(x, y) \over p(x)*p(y)}\)
- 其中 p(x, y) 表示两个词一起出现的概率。
- 而 p(x) 和 p(y) 表示各词出现的概率。
举个例子,比如一份语料中,“机器学习”出现了18次,“机器”出现了26次,“学习”出现了39次。由于语料库总词数是个定值,那么“机器学习”这个词在“机器”,“学习”上的的点间互信息如下:
\(\log_2{18 * N \over 26 * 39}\)
- 其中 N 指总词数。
以上例子告诉我们一个结论就是点间互信息越大,说明这两个词经常出现在一起,意味着两个词的凝固程度越大,其组成一个新词的可能性也就越大。
左右信息熵
左右熵说白了其实就是信息熵,公式如下:
\(E_{left}(PreW) = - \sum_{\forall Pre \subseteq A}{P(PreW) * \log_2{P(PreW)}}\)
- 上述公式 PreW 我们需要拆开看,Pre 是 W 词前缀的集合。
我们都知道信息熵是反应不确定程度的一个量,信息熵越大说明不确定程度越高,把其转变为新词发现中可以理解为如下:
左右熵值越大,说明该词的周边词越丰富,意味着词的自由程度越大,其成为一个独立的词的可能性也就越大。
实战
算法的逻辑主要分为如下三个步骤:
- 将语料文本转换成一个字符串,然后一个生成 n_gram 的词典,并统计个词的词频。
- 利用点间互信息从之前的 n_gram 词典中筛选出(通过设置阈值)备选的新词。
- 最后通过左右熵从备选新词中筛选出(也通过设置阈值)最终输出的新词。
以下是 python 的实战相关代码:
import math
import re
from collections import Counter
def n_gram_words(text_list, n_gram):
"""
获取 n_gram 的词频字典
Args:
text_list: 中文文章列表
n_gram: 整数(int)类型的 n_gram 值
Returns:
返回词频字典
"""
words = []
for i in range(1, n_gram + 1):
for text in text_list:
words += [text[j:j + i] for j in range(len(text) - i + 1)]
words_freq = dict(Counter(words))
return words_freq
def pmi_filter(word_freq_dic, min_p):
"""
获取符合 pmi 阈值的词
Args:
word_freq_dic: 词频字典
min_p: PMI 阈值(最小值)
Returns:
返回符合条件的词列表
"""
new_words = []
for word in word_freq_dic:
if len(word) == 1:
# 单个字,跳过忽略
pass
else:
# p(x)*p(y),根据 PMI 公式,该部分越小,PMI 值越大,所以取最小值即可表示该 word 的最大 PMI 值
p_x_y = min([word_freq_dic.get(word[:i]) * word_freq_dic.get(word[i:]) for i in range(1, len(word))])
pmi = math.log2((word_freq_dic.get(word) * len(word_freq_dic)) / p_x_y)
# 也可以不计算 log2,加快统计速度,这时阈值需要新的量纲设置
# pmi = (word_freq_dic.get(word) * len(word_freq_dic)) / p_x_y
if pmi > min_p:
new_words.append(word)
return new_words
def calculate_entropy(char_list):
"""
计算 char_list 的信息熵
Args:
char_list: 出现词的列表
Returns:
出现词的信息熵
"""
char_freq_dic = dict(Counter(char_list))
entropy = (-1) * sum(
[char_freq_dic.get(i) / len(char_list) * math.log2(char_freq_dic.get(i) / len(char_list)) for i in
char_freq_dic])
return entropy
def entropy_left_right_filter(conditional_words, text_list, ngram, min_entropy):
"""
在符合 PMI 过滤条件的词中,获取符合最小信息熵条件的最终候选新词
Args:
conditional_words: 符合 PMI 过滤条件的词的列表
text_list: 原始文本列表
ngram: 指定的 ngram
min_entropy: 最小信息熵,符合该条件才可以作为候选新词
Returns:
最终的候选词,字典(dict)类型
"""
final_words = {}
for word in conditional_words:
left_char_list = []
right_char_list = []
for text in text_list:
left_right_char_tuple_list = re.findall('(?:(.{0,%d}))%s(?=(.{0,%d}))' % (ngram, word, ngram), text)
if left_right_char_tuple_list:
for left_right_char_tuple in left_right_char_tuple_list:
if left_right_char_tuple[0]:
left_char_list += [left_right_char_tuple[0][i:] for i in range(len(left_right_char_tuple[0]))]
if left_right_char_tuple[1]:
right_char_list += [left_right_char_tuple[1][i:] for i in range(len(left_right_char_tuple[1]))]
if left_char_list:
left_entropy = calculate_entropy(left_char_list)
if right_char_list:
right_entropy = calculate_entropy(right_char_list)
if left_char_list and right_char_list:
if min(right_entropy, left_entropy) > min_entropy:
final_words[word] = min(right_entropy, left_entropy)
elif left_char_list:
if left_entropy > min_entropy:
final_words[word] = left_entropy
elif right_char_list:
if right_entropy > min_entropy:
final_words[word] = right_entropy
sorted(final_words.items(), key=lambda x: x[1], reverse=False)
return final_words