首页 > 其他分享 >一文拿捏点互信息(PMI)解决词分布式表示稀疏性问题

一文拿捏点互信息(PMI)解决词分布式表示稀疏性问题

时间:2022-12-03 11:01:15浏览次数:54  
标签:frac log 互信息 pmi 次数 text np PMI 分布式

前馈知识

之前在word embedding里浅浅的说了一下one-hot是怎么向词向量表示发展的,大家可以回顾一下。接下来我补充一下,接说二者之间还有一个阶段,词的分布式表示。


词的分布式表示

理论

分布式表示的发展

英国语言学家John Rupert Firth 在1957 年的《A synopsis of linguistic theory》中提到

You shall know a word by the company it keeps.

就是说我们人类可以通过上下文的含义来理解某一单词含义。

比如下边两个句子,人类看完之后就能直接知道两个杜鹃指的是哪个。

  • 树上有一只杜鹃在叫。
  • 漫山遍野开满了杜鹃。

image.png

所以John Rupert Firth提出我们可以使用词的上下文分布进行词的表示

怎么才能用到上下文信息?

我喜欢你。我爱你。

前后都是。那机器就可以知道喜欢之间肯定是有点关系的。

那你可以杠我一句:“那如果遇到 我恨你。 呢?”

如果只看这三个短句子肯定机器是分不出这些词的情感极性的,这就涉及到NLP的其他任务上边去了。

对于上下文,还有不同的选择方式。比如:

  • 在一个句子中选择一个固定大小的窗口作为其上下文。
  • 使用整个句子作为上下文。
  • 使用整个文档作为上下文。

不同的选择方式会获得不同的表示,比如前两个可以获得词的局部性质,而最后一个方法获得的词表示更倾向于代表主题信息。

这样之后分布式表示相对于独热码的好处在于:

  • 使用独热码,意思相近词的词也是完全不相干的表示,无法计算余弦相似度。
  • 使用分布式表示之后,因为上下文的缘故“喜欢”和“爱”可以获得相近的表示,之后可以通过余弦相似度计算词汇之间的距离。

举个例子

用书上一个例子讲一下如何使用上下文表示信息。

  • 我 喜欢 自然 语言 处理。
  • 我 爱 深度 学习。
  • 我 喜欢 机器 学习。

在这个例子里边,我们用一个句子作为上下文。

解析一下

  • 在第一个句子里上下文词汇有喜欢自然语言处理
  • 在第二个句子里上下文词汇有深度学习
  • 在第三个句子里上下文词汇有喜欢机器学习

搞成集合,然后看一下每一个词和其他词汇在同一个句子出现的概率,就可以得到下表。下表是个对角线对称矩阵,我们可以认为每一行或者每一列是一个词的表示。

$$ \begin{array}{ccccccccccc} \hline & \text { 我 } & \text { 喜欢 } & \text { 自然 } & \text { 语言 } & \text { 处理 } & \text { 爱 } & \text { 深度 } & \text { 学习 } & \text { 机器 } & \circ \ \hline \text { 我 } & 0 & 2 & 1 & 1 & 1 & 1 & 1 & 2 & 1 & 3 \ \text { 喜欢 } & 2 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 1 & 2 \ \text { 自然 } & 1 & 1 & 0 & 1 & 1 & 0 & 0 & 0 & 0 & 1 \ \text { 语言 } & 1 & 1 & 1 & 0 & 1 & 0 & 0 & 0 & 0 & 1 \ \text { 处理 } & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 1 \ \text { 爱 } & 1 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 0 & 1 \ \text { 深度 } & 1 & 0 & 0 & 0 & 0 & 1 & 0 & 1 & 0 & 1 \ \text { 学习 } & 2 & 1 & 0 & 0 & 0 & 1 & 1 & 0 & 1 & 1 \ \text { 机器 } & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 1 \ \text { 。 } & 3 & 2 & 1 & 1 & 1 & 1 & 1 & 2 & 1 & 0 \ \hline \end{array} $$

但是这样还存在一个问题,就是有些词天然会和其他词一起出现的频率很高。比如“我”、“你”这类词,而实际上他们对词汇的含义表示影响并不大,但是通过共同出现的次数这么表示,会导致不相干的词之间相似度提高。

举个计算的例子,比如我饿我可以

饿可以 没什么关系,但是因为的关系二者获得了同样的表示。这个例子中一个句子只有俩词,比较极端,加长句子之后,也会因为“我”这种词的天然特性而影响到其他词汇的表示。

$$ \begin{array}{cccc} \hline & \text { 我 } & \text { 饿 } & \text { 可以 }\ \hline \text { 我 } & 0 & 1 & 1 \ \text { 饿 } & 1 & 0 & 0 \ \text { 可以 } & 1 & 0 & 0 \ \hline \end{array} $$

要解决这个问题可以使用点互信息

点互信息

$$ \operatorname{PMI}(A, B)=\log _2 \frac{P(A, B)}{P(A) P(B)} $$

这个公式是将AB两个词共同出现的频率除以A出现的频率和B出现的频率。

这样操作可以实现:如果一个词和很多其他词汇共同出现,那就降低它的权重,反之提高它的权重。

$$ \begin{aligned} &P(A, B) =\frac{A和B一起出现的次数}{矩阵中所有元素的数量} \\ &P(A) =\frac{A出现的次数}{矩阵中所有的元素数量} \\ &P(B) =\frac{B出现的次数}{矩阵中所有的元素数量} \end{aligned} $$

为了计算看图方便,把这个表格搬下来了。以喜欢为例,算一下。

image.png

  • 喜欢 共同出现 = 2

  • 出现次数 = [我我] + [我喜欢] + [我自然] + ... + [我。] = 13

    • 其实就是所在行向量(或列向量)的和。
  • 喜欢出现次数 = [我喜欢] + [喜欢喜欢] + ... + [喜欢。] = 9

    • 其实就是所在行向量(或列向量)的和。
  • 所有元素数量 = 行向量和(或列向量和)再求和 = 69

$$ PMI(我,喜欢) = \log_2{\left(\frac{\frac{2}{69}}{\frac{13}{69} \times \frac{9}{69}}\right)} $$

所以简单来说某一元素的PMI可以用以下公式计算:

$$ \begin{aligned} \operatorname{PMI}(A, B) &=\log_2{\left(\frac{\frac{AB共同出现的次数}{所有元素数量}}{\frac{A出现的次数}{所有元素数量} \times \frac{B出现次数}{所有元素数量}}\right)} \\ & =\log_2{\left(AB共同出现的次数\times \frac{所有元素数量}{A出现的次数\times B出现次数 }\right)} \end{aligned} $$

当某个词与上下文之间共现次数较低时,可能会得到负的PMI值。考虑到这种情况下的PMI不太稳定(具有较大的方差),在实际应用中通常采用PPMI (Positive PMI)的形式

$$ \operatorname{PPMI}=\max (\operatorname{PMI}, 0) $$

代码实现

用代码实现一下。使用矩阵计算的话我们就不用挨个元素这么算了。直接使用矩阵并行计算即可。代码如下:

代码一

代码一是用numpy写的。代码二是用pytorch写的,除了框架不一样别的都完全一样,按需选择。

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, 0, 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, 0, 1, 1, 0, 1, 1],
              [1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
              [3, 2, 1, 1, 1, 1, 1, 2, 1, 0]])

np.set_printoptions(3)


def pmi(M, positive=True):
    # 计算出每个词出现的次数,得到一个向量,每个值都是一个词出现的次数
    single = M.sum(axis=0)
    
    # 计算元素出现的总次数
    total = single.sum()
    
    # 这样计算得到的是 A次数*B次数/总次数
    expected = np.outer(single,single) / total
    
    # 这一步看代码后边的解析
    M = M / expected
    
    # 计算log2
    with np.errstate(divide='ignore'):
        M = np.log(M)
        
    
    # 将M中的负无穷设置为0
    M[np.isinf(M)] = 0.0
    
    #PPMI 将M中的负数设置为0
    if positive:
        M[M < 0] = 0.0
    return M

M_pmi = pmi(M)

print(M_pmi)

补充解析:

  1. 代码 公式最后是 $\operatorname{PMI}(A, B) =\log_2{\left(AB共同出现的次数\times \frac{所有元素数量}{A出现的次数\times B出现次数 }\right)}$。

    而实际上我们在expected = np.outer(row_totals, col_totals) / total这一步中得到的是$\frac{A出现的次数\times B出现次数}{所有元素数量}$ 。

    小学知识 除以一个分数等于乘以它的倒数,所以这一步是M = M / expected

    也是这两行代码借助矩阵实现并行计算,不用for循环挨个元素算。

  2. np.outer是计算两个向量的外积。

    给你两个向量a = [a0, a1, ..., aM]b = [b0, b1, ..., bN]

    内积计算是一个数,等于a0*b0 + a1*b1 + ... + aN*bN

    外积是一个矩阵:

    [[a0*b0 a0*b1 ... a0*bN ]

    [a1*b0 ...

    [ ...

    [aM*b0 .......... aM*bN ]]

    比如

     vec = np.array([1,2,3])
     inn = np.vdot(vec,vec)
     out = np.outer(vec,vec)
    
     print('vec = ', vec)
     print('内积 = ',inn)
     print('外积 = ',out)
    

    结果是:

    vec = [1 2 3]

    内积 = 14

    外积 = [[1 2 3]

    $\quad\quad\quad$[2 4 6]

    $\quad\quad\quad$[3 6 9]]

  3. with np.errstate(divide='ignore')

    因为我们的矩阵中有0,因为$\log(0)=-\infty$,所以计算log的时候会有一个警告divide by zero encountered in log。 这里用with np.errstate(divide='ignore')包裹住M = np.log(M)就是让他忽略这一步操作的警告。

代码二

补一个pytorch 版本的代码。

和上边没啥区别,就是nptorch即可。主要区别在于做log计算那里。

pytorch中不会有这个log(0)的警告,pytorch 中也没有errstate方法。

import torch

M = torch.Tensor([[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, 0, 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, 0, 1, 1, 0, 1, 1],
                  [1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
                  [3, 2, 1, 1, 1, 1, 1, 2, 1, 0]])

torch.set_printoptions(3)


def pmi(M, positive=True):
    single = M.sum(axis=0)
    total = single.sum()
    expected = torch.outer(single, single) / total
    M = M / expected
    # pytorch中不会有这个log(0)的警告,pytorch 中也没有errstate方法
    M = torch.log(M)
    M[torch.isinf(M)] = 0.0

    if positive:
        M[M < 0] = 0.0
    return M


M_pmi = pmi(M)

print(M_pmi)

代码三

这段代码是书上写的,我觉得写的让人比较困惑,不多做解释,看看能看懂的。

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, 0, 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, 0, 1, 1, 0, 1, 1],
              [1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
              [3, 2, 1, 1, 1, 1, 1, 2, 1, 0]])

np.set_printoptions(3)


def pmi(M, positive=True):
    # 因为是对称矩阵,其实这俩的值完全是一样的。
    col_totals = M.sum(axis=0)
    row_totals = M.sum(axis=1)
    # 计算元素出现的总次数
    total = col_totals.sum()
    # 这样计算得到的是 A次数*B次数/总次数
    expected = np.outer(row_totals, col_totals) / total
    # 实现并行计算,不用for挨个元素算了
    M = M / expected
    # 计算log2
    with np.errstate(divide='ignore'):
        M = np.log(M)
    M[np.isinf(M)] = 0.0
    if positive:
        M[M < 0] = 0.0
    return M


M_pmi = pmi(M)

print(M_pmi)

看看使用PPMI前后的结果

image.png

左边是M,右边是M_pmi。

用个例子计算一下相似度:

可以看到在PPMI之前 语言机器 的相似度为0.671,PPMI之后变为0.207。

使用PPMI明显降低了不相干词汇的相似度。

image.png

代码

就是在上边代码一的后边加上下边这块代码即可:

def cos(a,b):
    f1 = np.vdot(a,b)
    f2 = np.vdot(a,a)**(1/2)
    f3 = np.vdot(b,b)**(1/2)

    return f1/(f2*f3)

print('\nPPMI前:')
print('语言 = ', M[3])
print('机器 = ', M[8])
print('余弦相似度 = ', cos(M[3], M[8]))

print('\nPPMI后:')
print('语言 = ', M_pmi[3])
print('机器 = ', M_pmi[8])
print('余弦相似度 = ', cos(M_pmi[3], M_pmi[8]))

标签:frac,log,互信息,pmi,次数,text,np,PMI,分布式
From: https://blog.51cto.com/Lolitann/5908497

相关文章

  • 分布式系统(唯一) ID 生成器实现方案
    分布式系统ID一个唯一ID在一个分布式系统中是非常重要的一个业务属性,其中包括一些如订单ID,消息ID,会话ID,他们都有一些共有的特性:全局唯一(唯一标识某个请求,某个业务)......
  • SpringCloud Alibaba(六) - Seata 分布式事务锁
    1、Seata简介1.1Seata是什么Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式......
  • SequoiaDB分布式数据库2022.11月刊
    产品能力再获认可,入围多个榜单、报告 近日,由国际数据公司IDC主办的第七届数字化转型年度盛典在北京如期举行。本届盛会,主办方首次面向非企业客户群体(方案提供商)颁发I......
  • 分布式数据库的具体实现与对比分析
    1.前言随着传统的数据库、计算机网络和数字通信技术的快速发展,以数据分布存储和分布处理为主要特征的分布式数据库系统的研究和开发越来越受到人们的关注。如何在一个数据库......
  • 分布式系统监控的四类黄金指标
    按照《SRE:Google运维解密》中描述的,分布式系统监控的四类黄金指标是:延迟(Latency)、流量(Traffic)、错误(Errors)、饱和度(Saturation)。从下图可以看到,对每一个系统来说,监控指标......
  • 15.第三章第12节: 2021.11.20 Redis分布式锁 redisson分布式锁,乐观锁+版本号 watch do
                                                 ......
  • 微服务之分布式搜索引擎elasticsearchDSL查询功能
    DSLQuery的分类Elasticsearch提供了基于JSON的DSL(DomainSpecific Language)来定义查询。常见的查询类型包括:查询所有:查询出所有数据,一般测试用。例如:match_all全文检......
  • .NET下实现分布式缓存系统Memcached
    【IT168技术文档】在Web应用程序中,数据通常保存在RDBMS中,应用服务器从数据库中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负载加重、数据......
  • 矩池云 | GPU 分布式使用教程之 Pytorch
    GPU分布式使用教程之PytorchPytorch官方推荐使用DistributedDataParallel(DDP)模块来实现单机多卡和多机多卡分布式计算。DDP模块涉及了一些新概念,如网络(WorldSize......
  • 【转】wcf系列学习5天速成——第四天 wcf之分布式架构
    wcf系列学习5天速成——第四天wcf之分布式架构 今天是wcf系列的第四天,也该出手压轴戏了。嗯,现在的大型架构,都是神马的,nginx鸡群,iis鸡群,wcf鸡群,DB鸡群,由一个人作......