学习心得
(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