第17章 潜在语义分析
本文是李航老师的《统计学习方法》一书的代码复现。作者:黄海广
备注:代码都可以在github中下载。我将陆续将代码发布在公众号“机器学习初学者”,可以在这个专辑在线阅读。
LSA 是一种无监督学习方法,主要用于文本的话题分析,其特点是通过矩阵分解发现文本与单词之间的基于话题的语义关系。也称为潜在语义索引(Latent semantic indexing, LSI)。
LSA 使用的是非概率的话题分析模型。将文本集合表示为单词-文本矩阵,对单词-文本矩阵进行奇异值分解,从而得到话题向量空间,以及文本在话题向量空间的表示。
非负矩阵分解(non-negative matrix factorization, NMF)是另一种矩阵的因子分解方法,其特点是分解的矩阵非负。
单词向量空间
word vector space model
给定一个文本,用一个向量表示该文本的”语义“, 向量的每一维对应一个单词,其数值为该单词在该文本中出现的频数或权值;基本假设是文本中所有单词的出现情况表示了文本的语义内容,文本集合中的每个文本都表示为一个向量,存在于一个向量空间;向量空间的度量,如内积或标准化内积表示文本之间的相似度。
单词向量空间模型的优点是模型简单,计算效率高。因为单词向量通常是稀疏的,单词向量空间模型也有一定的局限性,体现在内积相似度未必能够准确表达两个文本的语义相似度上。因为自然语言的单词具有一词多义性(polysemy)及多词一义性(synonymy)。
话题向量空间
1. 话题向量空间:
直观上,潜在语义分析是将单词向量空间的表示通过线性变换转换为在话题向量空间中的表示。这个线性变换由矩阵因子分解式的形式体现。
潜在语义分析算法
潜在语义分析利用矩阵奇异值分解,具体地,对单词-文本矩阵进行奇异值分解,将其左矩阵作为话题向量空间,将其对角矩阵与右矩阵的乘积作为文本在话题向量空间的表示。
非负矩阵分解算法
非负矩阵分解也可以用于话题分析。对单词-文本矩阵进行非负矩阵分解,将其左矩阵作为话题向量空间,将其右矩阵作为文本在话题向量空间的表示。 非负矩阵分解
图例 17.1
import numpy as np
from sklearn.decomposition import TruncatedSVD
X = [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 1, 0], [0, 0, 2, 3], [0, 0, 0, 1], [1, 2, 2, 1]]
X = np.asarray(X);X
array([[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 1, 0], [0, 0, 2, 3], [0, 0, 0, 1], [1, 2, 2, 1]])
# 奇异值分解
U,sigma,VT=np.linalg.svd(X)
U
array([[-7.84368672e-02, -2.84423033e-01, 8.94427191e-01, -2.15138396e-01, -6.45002451e-02, -2.50012770e-01], [-1.56873734e-01, -5.68846066e-01, -4.47213595e-01, -4.30276793e-01, -1.29000490e-01, -5.00025540e-01], [-1.42622354e-01, 1.37930417e-02, -1.25029761e-16, 6.53519444e-01, 3.88575115e-01, -6.33553733e-01], [-7.28804669e-01, 5.53499910e-01, -2.24565656e-16, -1.56161345e-01, -3.23288048e-01, -1.83248673e-01], [-1.47853320e-01, 1.75304609e-01, 8.49795536e-18, -4.87733411e-01, 8.40863653e-01, 4.97204799e-02], [-6.29190197e-01, -5.08166890e-01, -1.60733896e-16, 2.81459486e-01, 1.29000490e-01, 5.00025540e-01]])
sigma
array([4.47696617, 2.7519661 , 2. , 1.17620428])
VT
array([[-1.75579600e-01, -3.51159201e-01, -6.38515454e-01, -6.61934313e-01], [-3.91361272e-01, -7.82722545e-01, 3.79579831e-02, 4.82432341e-01], [ 8.94427191e-01, -4.47213595e-01, -2.23998094e-16, 5.45344065e-17], [-1.26523351e-01, -2.53046702e-01, 7.68672366e-01, -5.73674125e-01]])
# 截断奇异值分解
svd = TruncatedSVD(n_components=3, n_iter=7, random_state=42)
svd.fit(X)
TruncatedSVD(algorithm='randomized', n_components=3, n_iter=7, random_state=42, tol=0.0)
print(svd.explained_variance_ratio_)
[0.39945801 0.34585056 0.18861789]
print(svd.explained_variance_ratio_.sum())
0.933926460028446
print(svd.singular_values_)
[4.47696617 2.7519661 2. ]
非负矩阵分解
def inverse_transform(W, H):
# 重构
return W.dot(H)
def loss(X, X_):
#计算重构误差
return ((X - X_) * (X - X_)).sum()
# 算法 17.1
class MyNMF:
def fit(self, X, k, t):
m, n = X.shape
W = np.random.rand(m, k)
W = W/W.sum(axis=0)
H = np.random.rand(k, n)
i = 1
while i < t:
W = W * X.dot(H.T) / W.dot(H).dot(H.T)
H = H * (W.T).dot(X) / (W.T).dot(W).dot(H)
i += 1
return W, H
model = MyNMF()
W, H = model.fit(X, 3, 200)
W
array([[0.00000000e+00, 4.27327705e-01, 6.30117924e-27], [5.11680721e-97, 8.57828102e-01, 0.00000000e+00], [2.97520805e-88, 2.39454414e-18, 4.36332453e-01], [2.15653741e+00, 3.38756557e-21, 8.38350315e-01], [7.36106818e-01, 1.00339294e-54, 8.72892573e-38], [6.78344810e-01, 1.07009504e+00, 8.57259947e-01]])
H
array([[7.14647214e-10, 1.01233436e-03, 3.76097657e-02, 1.35755597e+00], [9.30509415e-01, 1.86788842e+00, 1.16682319e-02, 4.54479182e-03], [4.95440453e-03, 6.18432747e-04, 2.28890170e+00, 8.61836630e-02]])
# 重构
X_ = inverse_transform(W, H);X_
array([[3.97632453e-01, 7.98200474e-01, 4.98615876e-03, 1.94211546e-03], [7.98217125e-01, 1.60232718e+00, 1.00093372e-02, 3.89865014e-03], [2.16176748e-03, 2.69842277e-04, 9.98722093e-01, 3.76047290e-02], [4.15352814e-03, 2.70160021e-03, 2.00000833e+00, 2.99987233e+00], [5.26056687e-10, 7.45186228e-04, 2.76848049e-02, 9.99306203e-01], [9.99980721e-01, 2.00003500e+00, 2.00018226e+00, 9.99636205e-01]])
# 重构误差
loss(X, X_)
4.002356735601103
使用 sklearn 计算
from sklearn.decomposition import NMF
model = NMF(n_components=3, init='random', max_iter=200, random_state=0)
W = model.fit_transform(X)
H = model.components_
W
array([[0. , 0.53849498, 0. ], [0. , 1.07698996, 0. ], [0.69891361, 0. , 0. ], [1.39782972, 0. , 1.97173859], [0. , 0. , 0.65783848], [1.39783002, 1.34623756, 0.65573258]])
H
array([[0.00000000e+00, 0.00000000e+00, 1.43078959e+00, 1.71761682e-03], [7.42810976e-01, 1.48562195e+00, 0.00000000e+00, 3.30264644e-04], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.52030365e+00]])
X__ = inverse_transform(W, H);X__
array([[3.99999983e-01, 7.99999966e-01, 0.00000000e+00, 1.77845853e-04], [7.99999966e-01, 1.59999993e+00, 0.00000000e+00, 3.55691707e-04], [0.00000000e+00, 0.00000000e+00, 9.99998311e-01, 1.20046577e-03], [0.00000000e+00, 0.00000000e+00, 2.00000021e+00, 3.00004230e+00], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00011424e+00], [1.00000003e+00, 2.00000007e+00, 2.00000064e+00, 9.99758185e-01]])
loss(X, X__)
4.000001672582457
本章代码来源:https://github.com/hktxt/Learn-Statistical-Learning-Method
下载地址
https://github.com/fengdu78/lihang-code
参考资料:
[1] 《统计学习方法》: https://baike.baidu.com/item/统计学习方法/10430179
[2] 黄海广: https://github.com/fengdu78
[3] github: https://github.com/fengdu78/lihang-code
[4] wzyonggege: https://github.com/wzyonggege/statistical-learning-method
[5] WenDesi: https://github.com/WenDesi/lihang_book_algorithm
[6] 火烫火烫的: https://blog.csdn.net/tudaodiaozhale
[7] hktxt: https://github.com/hktxt/Learn-Statistical-Learning-Method