【词的分布式表示】点互信息PMI和基于SVD的潜在语义分析

学习心得

(1)为了解决高频词误导计算机结果的问题(如“我”、“。”与其他词的共现频次很高,以至于有些木有关系的词语之间也会产生联系,即相似度)——用PMI
(2)为了解决共现频次无法反应词之间高阶关系的问题(如a和b共现,b和c共现,c和d共现,通过共现频次,可能只能获得a和c共现,而不能得到a和d共现)——用大名鼎鼎的SVD。


我们熟悉的词的独热表示(One-hot Encoding,独热编码)即对第i个词向量——在第i个维度上设置为1,其余维均为0,这里有两个问题:
(1)即使两个词在语义上相似,但是通过余弦函数度量两者之间的相似度时可能值为0;
(2)容易导致数据稀疏问题,ex:如果在训练集中见过“机智”,而在测试集中出现了“聪明”,虽然两个词语语义相似,但是系统无法对“聪明”进行加权——即当训练数据有限时,不能充分地学习语言现象。

为了解决上面的数据稀疏问题,传统的方法是引入特征(提取更多和词相关的泛化特征,如词性特征、词义特征和词聚类特征等),但是这类做法耗时耗力;所以到了我们今天的主题——词的分布式表示:

John在1957年提出分布式语义假设:词的含义可以通过上下文的分布进行表示,我们举个例子:

(1)计算词语共现频次矩阵

首先假设语料库有三句话:

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

我们根据上面的3个句子创建一个如下的【共现矩阵】,即词与词之间在同一个句子中同时出现——即共现,这种矩阵的构造就能“喜欢”和“爱”之间由于有共同的上下文“我”和“学习”,从而之间有一定的相似性,不至于啥关系都木有。
在这里插入图片描述
上面矩阵的元素即如下所示:

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

(2)点互信息PMI

为了解决高频词误导计算机结果的问题(如“我”、“。”与其他词的共现频次很高,以至于有些木有关系的词语之间也会产生联系,即相似度),有一种做法:如果一个词和很多词共现,则降低权重;反之,如果一个词与个别词共现,则提高其权重。信息论的【点互信息】Pointwise Mutual Information, PMI就能完成这事:

# -*- coding: utf-8 -*-
"""
Created on Fri Sep 24 10:24:30 2021

@author: 86493
"""
# 一、解决高频词误导计算结果问题——PMI点互信息
from matplotlib import font_manager as fm, rcParams
import matplotlib.pyplot as plt

# 以下代码从全局设置字体为SimHei(黑体),解决显示中文问题【Windows】
# 设置font.sans-serif 或 font.family 均可
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文标签
# plt.rcParams['font.family']=['SimHei']
# 解决中文字体下坐标轴负数的负号显示问题
plt.rcParams['axes.unicode_minus'] = False 


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, 2],
              [1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
              [3, 2, 1, 1, 1, 1, 1, 2, 1, 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):
    # 不显示log(0)的警告
    with np.errstate(divide='ignore'):
        M = np.log(M)
    M[np.isinf(M)] = 0.0  # log(0) = 0,将log(0)置为0
    if positive:
        M[M < 0] = 0.0
    return M

M_pmi = pmi(M)
# 将打印结果保留两位小数
np.set_printoptions(precision=2)
print(M_pmi)

输出的处理后的新矩阵为:

[[0.   0.18 0.07 0.07 0.07 0.3  0.3  0.3  0.3  0.22]
 [0.18 0.   0.44 0.44 0.44 0.   0.   0.   0.66 0.18]
 [0.07 0.44 0.   1.03 1.03 0.   0.   0.   0.   0.07]
 [0.07 0.44 1.03 0.   1.03 0.   0.   0.   0.   0.07]
 [0.07 0.44 1.03 1.03 0.   0.   0.   0.   0.   0.07]
 [0.3  0.   0.   0.   0.   0.   1.48 0.78 0.   0.3 ]
 [0.3  0.   0.   0.   0.   1.48 0.   0.78 0.   0.3 ]
 [0.3  0.   0.   0.   0.   0.78 0.78 0.   0.78 0.3 ]
 [0.3  0.66 0.   0.   0.   0.   0.   0.78 0.   0.3 ]
 [0.22 0.18 0.07 0.07 0.07 0.3  0.3  0.3  0.3  0.  ]]

(3)奇异值分解

为了解决共现频次无法反应词之间高阶关系的问题(如a和b共现,b和c共现,c和d共现,通过共现频次,可能只能获得a和c共现,而不能得到a和d共现),有一种方法是:大名鼎鼎的SVD(Singular Value Decompsition,奇异值分解):
对上一步的共现矩阵M进行奇异值分解:
M = U Σ V ⊤ \boldsymbol{M}=\boldsymbol{U} \boldsymbol{\Sigma} \boldsymbol{V}^{\top} M=UΣV其中U和V都是正交矩阵,而 Σ {\Sigma} Σ是由r个奇异值构成的对角矩阵。
(1)py就直接调用np.linalg.svd即可求得三个对应的矩阵;
(2)潜在语义分析LSA:通过截断奇异值分解得到的矩阵U中的每一行,则为相应词的d维向量表示(性质:连续、低维、稠密),由于U的各列相互正交,即词表示的每一维表达了该词的一种独立的“潜在语义”。
(3) Σ V ⊤ \Sigma V^{\top} ΣV的每一列可以作为相应上下文的向量表示。

# 二、解决共现频次无法反应词之间高阶关系——奇异值分解,潜在语义分析
U, s, Vh = np.linalg.svd(M_pmi)

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

words = ["我", "喜欢", "自然", "语言", "处理", "爱", "深度", "学习", "机器", "。"]

my_font = FontProperties(fname=r"D:\anaconda1\envs\tensorflow\Lib\site-packages\matplotlib\mpl-data\fonts\ttf\SimHei.ttf",
                         size=12)

for i in range(len(words)):
    plt.text(U[i, 0], U[i, 1], words[i], fontproperties=my_font)
    #print(U[i, 0])
    #print(U[i, 1])
plt.xlim(-0.5, 0.0)
plt.ylim(-0.5, 0.6)
plt.savefig('svd.pdf')
plt.show()

SVD分解得到的正交矩阵U的每一行:相应词经过奇异值分解后的向量表示。如果仅保留前2维,从可视化图看出,上下文比较相近的词在空间上的距离比较接近,如“深度”和“学习”比较接近,而“我”、“。”等高频词和其他词语距离比较远。
在这里插入图片描述

(4)上面方法的毛病

(1)当共现矩阵很大时,SVD的运算速度很慢;
(2)在原来语料库上增加数据时,需要重新运行SVD算法;
(3)分布式表示适合表示较短的词或短语,而段落、句子与其共现的上下文会很少(无法获得有效的分布式表示),所以大佬们又想出新的词表示方法——词嵌入表示,见下文分解!

Reference

(1)《自然语言处理——基于预训练模型的方法》车万翔
(2)SVD(奇异值分解)与LSA(潜在语义分析)
(3)LSA,pLSA原理及其代码实现
(4)自然语言处理 - 潜在语义分析LSA背后的奇异值分解SVD

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

山顶夕景

小哥哥给我买个零食可好

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

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

打赏作者

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

抵扣说明:

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

余额充值