NLP之【点互信息PMI】——衡量两变量之间的相关性

本文介绍了点互信息(PMI)的概念,用于衡量两个词语之间的相关性。通过计算PMI,可以判断词语是否独立出现。文中展示了如何使用Python的nltk库计算PMI,并通过例子解释了PMI值的意义,以及如何自定义函数进行计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

绪论

在自然语言处理中, 想要探讨两个字之间,是否存在某种关系,例如:某些字比较容易一起出现, 这些字一起出现时,可能带有某种讯息。

例如,在新闻报导中,有New 、York,这两个字一起出现,可以代表一个地名New York,所以当出现了New这个字, 则有可能出现York,这可以用Pointwise Mutual Information(PMI)来计算New 、York一起出现的相关性。

一、PMI的基本概念

点互信息(Pointwise Mutual Information,PMI): 在数据挖掘或者信息检索的相关资料里,经常会 利用PMI(Pointwise Mutual Information)这个指标来衡量两个事物之间的相关性。比如:两个词,两个句子等。

点互信息(Pointwise Mutual Information,PMI):

P M I ( x ; y ) = l o g p ( x , y ) P ( x ) × P ( y ) = l o g p ( x ∣ y ) p ( x ) = l o g p ( y ∣ x ) p ( y ) PMI(x;y) = log\frac{p(x,y)}{P ( x) × P ( y )} = log\frac{p(x|y)}{p(x)} = log\frac{p(y|x)}{p(y)} PMI(x;y)=logP(x)×P(y)p(x,y)=logp(x)p(xy)=logp(y)p(yx)

  • 如果某两个字的出现是独立事件(即:满足 x x x y y y相互独立),则 p ( x , y ) = p ( x ) p ( y ) p(x,y)=p(x)p(y) p(x,y)=p(x)p(y),此时PMI为0,其计算过程如下:
    P ( x , y ) = P ( x ) × P ( y ) P ( x, y ) = P ( x) × P ( y ) P(x,y)=P(x)×P(y)
    P M I ( x ; y ) = l o g p ( x , y ) P ( x ) × P ( y ) = l o g P ( x ) × P ( y ) P ( x ) × P ( y ) = l o g 1 = 0 PMI(x;y) =log\frac{p(x,y)}{P ( x) × P ( y )} =lo g \frac{P ( x) × P ( y )}{P ( x) × P ( y )}= lo g 1 = 0 PMI(x;y)=logP(x)×P(y)p(x,y)=logP(x)×P(y)P(x)×P(y)=log1=0

  • 如果有两个字出现的机率不是独立事件,即:某个字出现时提升另一个字的出现的机率,则PMI大于0,其计算过程如下:

P ( x , y ) > P ( x ) × P ( y ) P ( x, y ) > P ( x) × P ( y ) P(x,y)>P(x)×P(y)
P M I ( x ; y ) = l o g p ( x , y ) P ( x ) × P ( y ) > 0 PMI(x;y) =log\frac{p(x,y)}{P ( x) × P ( y )} > 0 PMI(x;y)=logP(x)×P(y)p(x,y)>0

综上可知, 如果这两个字的出现越不是偶然, 则PMI算出来的值越高, 越有可能带有某种讯息。

通常我们可以用一个Co-occurrence Matrix词语共现频次表)来表示对一个语料库中两个单词出现在同一份文档的统计情况,例如:

在这里插入图片描述
说明:

  1. 表格中的数字, 代表左方的Word和上方的Word, 一起出现的次数;
  2. 假设这篇文章总共只有19个字;

接下来,以计算information和data这两Word的PMI 为例,计算过程如下:

  1. 单词information和单词data同时出现的概率:
    P ( x = i n f o r m a t i o n , y = d a t a ) = 6 19 = 0.32 P ( x= information , y = data )=\frac{6}{19}=0.32 P(x=information,y=data)=196=0.32
  2. 单词information出现的概率:
    P ( x = i n f o r m a t i o n ) = 6 + 4 + 1 19 = 11 19 = 0.58 P ( x= information )=\frac{6 + 4 + 1}{19}=\frac{11}{19}=0.58 P(x=information)=196+4+1=1911=0.58
  3. 单词data出现的概率:
    P ( y = d a t a ) = 6 + 1 19 = 7 19 = 0.37 P ( y = da ta )=\frac{6 + 1}{19}=\frac{7}{19}=0.37 P(y=data)=196+1=197=0.37
  4. 计算information和data这两Word的PMI:
    P M I ( x = i n f o r m a t i o n , y = d a t a ) = l o g P ( x = i n f o r m a t i o n , y = d a t a ) P ( x = i n f o r m a t i o n ) × P ( y = d a t a ) = l o g 2 0.32 0.58 × 0.37 = l o g 2 1.49 = 0.57 \begin{aligned} PMI( x= inform a ti o n , y = da ta ) &=log\frac{P ( x= i n fo rm a ti o n , y = da ta )}{P ( x= i n fo rm a ti o n ) × P ( y = da ta )}\\ &=log_2\frac{0.32}{0.58×0.37}\\ &=log_21.49\\ &=0.57 \end{aligned} PMI(x=information,y=data)=logP(x=information)×P(y=data)P(x=information,y=data)=log20.58×0.370.32=log21.49=0.57
    算出来的数字大于0 , 表示information和data这两个字的出现, 不是独立事件。

其他中间结果如下表所示:

在这里插入图片描述
但是从上表中你可能会发现一个问题,那就是你有可能会去计算 l o g 2 0 = − i n f log_20 = -inf log20=inf,即得到一个负无穷。为此人们通常会计算一个 PPMI(Positive PMI) 来避免出现 -inf,即:

P M I ( w , c ) = l o g 2 p ( w , c ) P ( w ) × P ( c ) ⇒ P P M I ( w , c ) = m a x ( l o g 2 p ( w , c ) P ( w ) × P ( c ) , 0 ) PMI(w,c) = log_2\frac{p(w,c)}{P ( w) × P ( c )} \Rightarrow PPMI(w,c) =max(log_2\frac{p(w,c)}{P ( w) × P ( c )},0) PMI(w,c)=log2P(w)×P(c)p(w,c)PPMI(w,c)=max(log2P(w)×P(c)p(w,c),0)

二、调用Python nltk来计算两个词的PMI

我们用 python nltk 的brown corpus新闻类别文章, 来计算New , York的PMI和New , The的PMI , 并比较两者差异。

执行代码如下:

import nltk
from nltk.corpus import brown
from nltk import WordNetLemmatizer
from math import log
wnl = WordNetLemmatizer()

# nltk.download('brown')  下载对应的数据集
# nltk.download('omw-1.4')

"""
参数:
_Fdist:是单字出现的频率;
_Sents:是文章中所有的句子;
p(x):计算单字x出现的概率;
pxy(x,y):计算单字x和单字y出现在同一个句子的概率;
pim(x,y):计算单字x和单字y的Pointwise Mutual Information
"""
_Fdist = nltk.FreqDist([wnl.lemmatize(w.lower()) for w in brown.words(categories='news')])
_Sents = [[wnl.lemmatize(j.lower()) for j in i] for i in brown.sents(categories='news')]

def p(x):
    return _Fdist[x]/float(len(_Fdist))

def pxy(x,y):
    return (len(list(filter(lambda s: x in s and y in s, _Sents)))+1)/float((len(_Sents)))

def pmi(x,y):
    return log(pxy(x,y)/(p(x)*p(y)),2)

# 先来分别计算new和york各别出现的概率
p('new')
>>>
0.020265724857046755
p('york')
>>>
0.004372687521022536
# 再来计算new,york出现在同一句子的概率
pxy('new','york')
>>>
0.011031797534068787
# 计算new , york的PMI
pmi ( 'new' , 'york' ) 
>>>
6.959890136179789

# 再来看看, new , the出现在同一句子的机率
pxy('new','the')
>>>
0.04023361453601557
# 再算一下the出现的机率
p('the')
>>>
0.5369996636394214
# 计算new , the的PMI 
pmi('new','the')
>>>
1.8863664858873235

以上结果显示,PMI可以得出, new和york经常一起出现,并不是偶然事件,表示这两个词一起出现可能带有较多讯息;另外,虽然new和the同时出现的频率比较多,但the单独出现的频率也比较多,计算结果PMI可以把the的出现频率除掉,得出new和the的出现,比较像是独立事件。

参考链接:自然语言处理-- Pointwise Mutual Information

Python自然语言处理库NLTK下的Collocations模块,提供了PMI计算的方法。Collocations中有两个类BigramCollocationFinder和TrigramCollocationFinder分别可以识别2词短语和3词短语。

参考链接:Jupyter Notebook使用Python做PMI点互信息计算

"""
实现以下功能:

读取txt、xls、xlsx文件的数据(其中excel形式的数据,其数据是存储在某一列)

对文本数据进行分词、英文小写化、英文词干化、去停用词

按照两元语法模式,计算所有文本两两词语的pmi值

将pmi值保存到csv文件中
"""
import re
import csv
import jieba
import pandas as pd 
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.collocations import BigramAssocMeasures, BigramCollocationFinder

def chinese(text):
    """
    对中文数据进行处理,并将计算出的pmi保存到文件“中文pmi计算.csv”中
    """
    content = ''.join(re.findall(r'[\u4e00-\u9fa5]+', text))
    words = jieba.cut(content)
    words = [w for w in words if len(w)>1]
    bigram_measures = BigramAssocMeasures()
    finder = BigramCollocationFinder.from_words(words)
    
    with open('/Users/Documents/NLP_data_projects/data/pmi.txt',
             'a+', encoding='utf-8', newline='') as csvf:
        writer = csv.writer(csvf)
        writer.writerow(('word1','word2','pmi_score'))
        for row in finder.score_ngrams(bigram_measures.pmi):
            data = (*row[0],row[1])
            try:
                writer.writerow(data)
            except:
                pass
def english(text):
    """
    对英文数据进行处理,并将计算出的pmi保存到文件'english_pmi.txt'
    """
    stopwordss = set(stopwords.words('english'))
    stemmer = nltk.stem.snowball.SnowballStemmer('english')
    tokenizer = nltk.tokenize.RegexpTokenizer('\w+')
    words = tokenizer.tokenize(text)
    words = [w for w in words if not w.isnumeric()]
    words = [w.lower() for w in words]
    words = [stemmer.stem(w) for w in words]
    words = [w for w in words if w not in stopwordss]
    bigram_measures = BigramAssocMeasures()
    finder = BigramCollocationFinder.from_words(words)
    with open('/Users/Documents/NLP_data_projects/data/english_pmi.txt',
             'a+', encoding='utf-8', newline='') as csvf:
        writer = csv.writer(csvf)
        writer.writerow(('word1','word2','pmi_score'))
        for row in finder.score_ngrams(bigram_measures.pmi):
            data = (*row[0],row[1])
            try:
                writer.writerow(data)
            except:
                pass

def pmi_score(file, lang, column='entity'):
    """
    计算pmi:
    param file:原始文本数据文件
    param lang:数据的语言,参数为chinese或english
    param column:如果文件为excel形式的文件,column为excel中的数据列
    """
    # 读取数据
    text = ''
    if 'csv' in file:
        df = pd.read_csv(file)
        rows = df.iterrows()
        for row in rows:
            text += row[1][column]
    elif ('xlsx' in file) or ('xls' in file):
        df = pd.read_excel(file)
        rows = df.iterrows()
        for row in rows:
            text += row[1][column]
    else:
        text = open(file).read()
        
    # 对该语言的文本数据计算pmi
    globals()[lang](text)

# 计算pmi
pmi_score(file='/Users/yangyang/Documents/NLP_data_projects/data/pmi.txt', lang='chinese')

参考链接:PMI点互信息计算

三、根据词语的共现频次表自定义PMI函数计算

# Defined in Section 2.1.2
import numpy as np
M = np.array([[0, 2, 1, 1, 1, 1, 1, 2, 1, 3],
              [2, 0, 1, 1, 1, 0, 0, 1, 1, 2],
              [1, 1, 0, 1, 1, 0, 0, 0, 0, 1],
              [1, 1, 1, 0, 1, 0, 0, 0, 0, 1],
              [1, 1, 1, 1, 1, 0, 0, 0, 0, 1],
              [1, 0, 0, 0, 0, 0, 1, 1, 0, 1],
              [1, 0, 0, 0, 0, 1, 0, 1, 0, 1],
              [2, 1, 0, 0, 1, 1, 1, 0, 1, 2],
              [1, 1, 1, 0, 1, 0, 0, 1, 0, 1],
              [3, 2, 1, 1, 1, 1, 1, 2, 2, 0]])
def pmi(M, positive=True):
    col_totals = M.sum(axis=0)
    row_totals = M.sum(axis=1)
    total = col_totals.sum()
    expected = np.outer(row_totals, col_totals)/total
    M = M/expected
    # Silence distracting warnings about log(0):
    with np.errstate(divide='ignore'):  # np.errstate()用于浮点错误处理的上下文管理器。
        M = np.log(M)
    M[np.isinf(M)] = 0.0 # log(0)=0  # np.isinf()用于按元素测试正无穷或负无穷
    if positive:
        M[M<0] = 0.0
    return M

M_pmi = pmi(M)
np.set_printoptions(precision=2)  # np.set_printoptions()用于控制Python中小数的显示精度
print(M_pmi)
>>>
[[0.   0.25 0.   0.14 0.   0.37 0.37 0.37 0.14 0.29]
 [0.25 0.   0.33 0.51 0.04 0.   0.   0.04 0.51 0.25]
 [0.14 0.51 0.   1.1  0.63 0.   0.   0.   0.   0.14]
 [0.14 0.51 0.92 0.   0.63 0.   0.   0.   0.   0.14]
 [0.   0.33 0.73 0.92 0.45 0.   0.   0.   0.   0.  ]
 [0.37 0.   0.   0.   0.   0.   1.54 0.85 0.   0.37]
 [0.37 0.   0.   0.   0.   1.54 0.   0.85 0.   0.37]
 [0.25 0.   0.   0.   0.04 0.73 0.73 0.   0.51 0.25]
 [0.   0.33 0.73 0.   0.45 0.   0.   0.45 0.   0.  ]
 [0.21 0.17 0.   0.07 0.   0.29 0.29 0.29 0.76 0.  ]]
# np.linalg.svd(a, full_matrices=True, compute_uv=True) 奇异值分解
U, s, Vh = np.linalg.svd(M_pmi)   

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']

words = ['我', '喜欢', '自然','语言','处理','爱','深度','学习','机器','。']
for i in range(len(words)):
    plt.text(U[i, 0], U[i, 1], words[i])
plt.xlim(-0.7, 0)
plt.ylim(-0.5, 0.8)
# plt.savefig('/Users/yangyang/Documents/NLP_learning/svg.pdf')
plt.show()

在这里插入图片描述

附录:nltk.download(‘omw-1.4’)下载’omw-1.4’文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yale曼陀罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值