代码改变世界

nlp PPMI

2022-04-05 14:35  jym蒟蒻  阅读(268)  评论(0编辑  收藏  举报

使用PPMI改进共现矩阵

共现矩阵的元素表示两个单词同时出现的次数,这里的次数并不具备好的性质,举个例子,有短语叫the car,因为the是个常用词,如果以两个单词同时出现的次数为衡量相关性的标准,与drive 相比,the和car的相关性更强,这是不对的。

点互信息(Pointwise Mutual Information,PMI):表达式如下,P(x)表示x发生的概率,P(y)表示y发生的概率,P(x,y)表示x和y同时发生的概率。PMI的值越高,表明x与y相关性越强。

在这里插入图片描述

用共现矩阵重写PMI表达式:将共现矩阵表示为C,将单词X和Y的共现次数表示为C(x,y),将单词x和y的出现次数分别表示为C(x)、C(y),将语料库的单词数量记为N。表达式如下。

在这里插入图片描述

正的点互信息(Positive PMI,PPMI):当两个单词的共现次数为0时, log0=-∞。为解决这个问题,实践上会使用下述正的点互信息。可以将单词间的相关性表示为大于等于0的实数。

在这里插入图片描述

共现矩阵转化为PPMI矩阵的函数实现:代码中防止 np.log2(0)=-inf 而使 用了微小值 eps。

分析一下源码,函数里加一个print:

	M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    print(C)
    print(M)
    print(N)
    print(S)
    print(total)

输出如下,可见N是把共现矩阵C中所有数相加;M是维度和C相同的全为0的数组,用来存PPMI矩阵;S是记录每个词和别的词共现的次数,total是等于7*7,这个是输出进展情况时候用的,用来判断当前进度。

[[0 1 0 0 0 0 0]
 [1 0 1 0 1 1 0]
 [0 1 0 1 0 0 0]
 [0 0 1 0 1 0 0]
 [0 1 0 1 0 0 0]
 [0 1 0 0 0 0 1]
 [0 0 0 0 0 1 0]]
 
[[0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0.]]
 
14

[1 4 2 2 2 2 1]

49

下面这句代码,S[j]和S[i]指的是i和j和别的词共现的次数,这里就知道了,代码的实现和PMI表达式定义其实还是有差别的,代码是在共现范围内判断两个词的相关性。举个例子,a和b共现了x次,b和其他人现了y次,a和其他人现了j次,群体中人的个数是n,那在这个群体里b和a现的程度就是(x * n)/(y * j) 。(‘现’理解成XO)。

pmi = np.log2(C[i, j] * N / (S[j]*S[i]) + eps)

完整代码:

def ppmi(C, verbose=False, eps = 1e-8):
    '''生成PPMI(正的点互信息)

    :param C: 共现矩阵
    :param verbose: 是否输出进展情况
    :return:
    '''
    M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    cnt = 0

    for i in range(C.shape[0]):
        for j in range(C.shape[1]):
            pmi = np.log2(C[i, j] * N / (S[j]*S[i]) + eps)
            M[i, j] = max(0, pmi)

            if verbose:
                cnt += 1
                if cnt % (total//100 + 1) == 0:
                    print('%.1f%% done' % (100*cnt/total))
    return M

例子:

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C)

np.set_printoptions(precision=3)  # 有效位数为3位
print('covariance matrix')
print(C)
print('-'*50)
print('PPMI')
print(W)

输出:PPMI矩阵各个元素均为大于等于0的实数。

covariance matrix
[[0 1 0 0 0 0 0]
 [1 0 1 0 1 1 0]
 [0 1 0 1 0 0 0]
 [0 0 1 0 1 0 0]
 [0 1 0 1 0 0 0]
 [0 1 0 0 0 0 1]
 [0 0 0 0 0 1 0]]
--------------------------------------------------
PPMI
[[0.    1.807 0.    0.    0.    0.    0.   ]
 [1.807 0.    0.807 0.    0.807 0.807 0.   ]
 [0.    0.807 0.    1.807 0.    0.    0.   ]
 [0.    0.    1.807 0.    1.807 0.    0.   ]
 [0.    0.807 0.    1.807 0.    0.    0.   ]
 [0.    0.807 0.    0.    0.    0.    2.807]
 [0.    0.    0.    0.    0.    2.807 0.   ]]


随着语料库词汇量增加,各个单词向量的维数也会增加,这个矩阵很多元素都是0,表明向量中的绝大多数元素并不重要,对于这些问题,一个常见的方法是向量降维。