⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议。
Skip to content

第17章 潜在语义分析

习题17.1

  试将图17.1的例子进行潜在语义分析,并对结果进行观察。


解答:

解答思路:

  1. 给出潜在语义分析算法
  2. 使用numpy的奇异值分解方法对图17.1的例子进行潜在语义分析
  3. 对结果进行观察及分析

解答步骤:

第1步:潜在语义分析算法

  根据书中第17.2节的潜在语义分析算法的计算步骤:

  1. 单词-文本矩阵
    给定文本集合D={d1,d2,,dn}和单词集合W={w1,w2,,wm}。潜在语义分析首先将这些数据表示成一个单词-文本矩阵
X=[x11x12x1nx21x22x2nxm1xm2xmn]

其中,元素xij表示单词wi在文本dj中出现的频数或权值。

  1. 截断奇异值分解
    根据确定的话题个数k对单词-文本矩阵X进行截断奇异值分解
XUkΣkVkT=[u1 u2  uk][σ10000σ200000000σk][v1Tv2TvkT]

其中knmUkm×k矩阵,它的列由X的前k个相互正交的左奇异向量组成,Σkk阶对角方阵,对角元素为前k个最大奇异值,Vkn×k矩阵,它的列由X的前k个相互正交的右奇异向量组成。

  1. 话题向量空间
    矩阵Uk的每一个列向量u1,u2,,uk表示一个话题,称为话题向量。由这k个话题向量张成一个子空间
Uk=[u1 u2  uk]

称为话题向量空间。

  1. 文本的话题空间表示
    矩阵X的第j列向量xj满足
xjl=ikσlvjlul,j=1,2,,n

  综上,可以通过对单位-文本矩阵的奇异值分解进行潜在语义分析

XUkΣkVkT=Uk(ΣkVkT)

得到话题空间Uk,以及文本在话题空间的表示(ΣkVkT)

第2步:对图17.1的例子进行潜在语义分析

python
import numpy as np


def lsa_svd(X, k):
    """
    潜在语义分析的矩阵奇异值分解
    :param X: 单词-文本矩阵
    :param k: 话题数
    :return: 话题向量空间、文本集合在话题向量空间的表示
    """
    # 单词-文本矩阵X的奇异值分解
    U, S, V = np.linalg.svd(X)
    # 矩阵的截断奇异值分解,取前k个
    U = U[:, :k]
    S = np.diag(S[:k])
    V = V[:k, :]

    return U, np.dot(S, V)
python
X = np.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]])

# 设置精度为2
np.set_printoptions(precision=2, suppress=True)
# 假设话题的个数是3个
U, SV = lsa_svd(X, k=3)
print("话题空间U:")
print(U)
print("文本在话题空间的表示SV:")
print(SV)
话题空间U:
[[-0.08  0.28  0.89]
 [-0.16  0.57 -0.45]
 [-0.14 -0.01  0.  ]
 [-0.73 -0.55  0.  ]
 [-0.15 -0.18  0.  ]
 [-0.63  0.51 -0.  ]]
文本在话题空间的表示SV:
[[-0.79 -1.57 -2.86 -2.96]
 [ 1.08  2.15 -0.1  -1.33]
 [ 1.79 -0.89  0.    0.  ]]

第3步:对结果进行观察及分析

  在假设话题个数为3的情况下,单词airplane在话题3上的权值最大为0.89,表示单词airplane在话题3中的重要度最高;文本d2在话题2中的权值最大为2.15,表示话题2在文本d2中的重要度最高。

习题17.2

  给出损失函数是散度损失时的非负矩阵分解(潜在语义分析)的算法。

解答:

解答思路:

  1. 给出散度的定义
  2. 给出散度损失的定义
  3. 给出损失函数是平方损失时的非负矩阵分解算法
  4. 写出损失函数是散度损失时的非负矩阵分解算法
  5. 自编程实现损失函数是散度损失时的非负矩阵分解算法

解答步骤:

第1步:散度的定义

  根据书中附录E的KL散度的定义:

  KL散度是描述两个概率分布Q(x)P(x)相似度的一种度量,记作D(QP)。对离散随机变量,KL散度定义为

(E.1)D(QP)=iQ(i)logQ(i)P(i)

对连续随机变量,KL散度定义为

(E.2)D(QP)=Q(x)logQ(x)P(x)dx

第2步:散度损失的定义

  根据书中第17章的定理17.2:

定理17.2 散度损失D(XWH)对下列乘法更新规则

HljHlji[WilXij/(WH)ij]iWilWilWilj[HljXij/(WH)ij]jHlj

是非增的。当且仅当WH是散度损失函数的稳定点时,函数的更新不变。

第3步:损失函数是平方损失时的非负矩阵分解算法

  根据书中第17章的算法17.1的非负矩阵分解的迭代算法

算法17.1 (非负矩阵分解的迭代算法)
输入:单词-文本矩阵X0,文本集合的话题个数k,最大迭代次数t
输出:话题矩阵W,文本表示矩阵H
(1)初始化
  W0,并对W的每一列数据归一化;H0
(2)迭代
  对迭代次数由1到t执行下列步骤:
  (a)更新W的元素,对l从1到ki从1到m按照如下公式更新Wil

Wil=Wil(XHT)il(WHHT)il,i=1,2,,m;l=1,2,,k

  (b)更新H的元素,对l从1到kj从1到n按照如下公式更新Hlj

Hlj=Hlj(WTX)lj(WTWH)lj,l=1,2,,k;j=1,2,,n

第4步:损失函数是散度损失时的非负矩阵分解算法

输入:单词-文本矩阵X0,文本集合的话题个数k,最大迭代次数t
输出:话题矩阵W,文本表示矩阵H
(1)初始化
  W0,并对W的每一列数据归一化;
  H0
(2)迭代
  对迭代次数由1到t执行下列步骤:
  (a)更新W的元素,对l从1到ki从1到m按照如下公式更新Wil

WilWilj[HljXij/(WH)ij]jHlj

  (b)更新H的元素,对l从1到kj从1到n按照如下公式更新Hlj

HljHlji[WilXij/(WH)ij]iWil

  (c)计算损失函数

i=0mj=0n(XijlogXij(WH)ijXij+(WH)ij)

第5步:自编程实现损失函数是散度损失时的非负矩阵分解算法

python
import numpy as np


class DivergenceNmfLsa:
    def __init__(self, max_iter=1000, tol=1e-6, random_state=0):
        """
        损失函数是散度损失时的非负矩阵分解
        :param max_iter: 最大迭代次数
        :param tol: 容差
        :param random_state: 随机种子
        """
        self.max_iter = max_iter
        self.tol = tol
        self.random_state = random_state
        np.random.seed(self.random_state)

    def _init_param(self, X, k):
        self.__m, self.__n = X.shape
        self.__W = np.random.random((self.__m, k))
        self.__H = np.random.random((k, self.__n))

    def _div_loss(self, X, W, H):
        Y = np.dot(W, H)
        loss = 0
        for i in range(self.__m):
            for j in range(self.__n):
                loss += (X[i][j] * np.log(X[i][j] / Y[i][j]) if X[i][j] * Y[i][j] > 0 else 0) - X[i][j] + Y[i][j]

        return loss

    def fit(self, X, k):
        """
        :param X: 单词-文本矩阵
        :param k: 话题个数
        :return:
        """
        # (1)初始化
        self._init_param(X, k)
        # (2.c)计算散度损失
        loss = self._div_loss(X, self.__W, self.__H)

        for _ in range(self.max_iter):
            # (2.a)更新W的元素
            WH = np.dot(self.__W, self.__H)
            for i in range(self.__m):
                for l in range(k):
                    s1 = sum(self.__H[l][j] * X[i][j] / WH[i][j] for j in range(self.__n))
                    s2 = sum(self.__H[l][j] for j in range(self.__n))
                    self.__W[i][l] *= s1 / s2

            # (2.b)更新H的元素
            WH = np.dot(self.__W, self.__H)
            for l in range(k):
                for j in range(self.__n):
                    s1 = sum(self.__W[i][l] * X[i][j] / WH[i][j] for i in range(self.__m))
                    s2 = sum(self.__W[i][l] for i in range(self.__m))
                    self.__H[l][j] *= s1 / s2

            new_loss = self._div_loss(X, self.__W, self.__H)
            if abs(new_loss - loss) < self.tol:
                break

            loss = new_loss

        return self.__W, self.__H
python
X = np.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]])

# 设置精度为2
np.set_printoptions(precision=2, suppress=True)
# 假设话题的个数是3个
k = 3
div_nmf = DivergenceNmfLsa(max_iter=1000, random_state=2022)
W, H = div_nmf.fit(X, k)
print("话题空间W:")
print(W)
print("文本在话题空间的表示H:")
print(H)
话题空间W:
[[0.   0.   1.39]
 [0.   1.47 0.  ]
 [0.35 0.   0.  ]
 [1.77 0.   0.  ]
 [0.35 0.   0.  ]
 [1.06 1.47 0.7 ]]
文本在话题空间的表示H:
[[0.   0.   1.41 1.41]
 [0.   1.36 0.   0.  ]
 [1.44 0.   0.   0.  ]]

习题17.3

  给出潜在语义分析的两种算法的计算复杂度,包括奇异值分解法和非负矩阵分解法。

解答:

解答思路:

  1. 给出潜在语义分析的奇异值分解法,并计算复杂度
  2. 给出潜在语义分析的非负矩阵分解法,并计算复杂度

解答步骤:

  给定文本集合D={d1,d2,,dn}和单词集合W={w1,w2,,wm},则单词-文本矩阵为

X=[x11x12x1nx21x22x2nxm1xm2xmn]

第1步:计算潜在语义分析的奇异值分解法的复杂度

奇异值分解法(含截断奇异值分解)的复杂度分析:

  1. 求行空间和零空间标准正交基,其复杂度是O(nm2)
  2. XXT进行特征值分解,其复杂度是O(m3)

可知,总复杂度为O(nm2)+O(m3)

第2步:计算潜在语义分析的非负矩阵分解法的复杂度

由于非负矩阵分解是迭代的,假设迭代次数为t

  • 损失函数是平方损失时的非负矩阵分解的复杂度

    1. 迭代次数从1到t次,复杂度包含t因子
    2. 更新W的元素,复杂度为O(km)
    3. 更新H的元素,复杂度为O(kn)

    可知,复杂度为O(tkm)+O(tkn)

  • 损失函数是散度损失时的非负矩阵分解的复杂度

    1. 迭代次数从1到t次,复杂度包含t因子
    2. 更新W的元素,复杂度为O(km)
    3. 更新H的元素,复杂度为O(kn)
    4. 计算损失函数,复杂度为O(mn)

    可知,复杂度为O(tkm)+O(tkn)+O(tmn)

习题17.4

  列出潜在语义分析与主成分分析的异同。

解答:

解答思路:

  1. 给出主成分分析的定义
  2. 给出潜在语义分析的定义
  3. 写出两者的相同点
  4. 写出两者的不同点

解答步骤:

第1步:主成分分析的定义

  根据书中第16章的主成分分析介绍:

  主成分分析(PCA)是一种常用的无监督学习方法,这一方法利用正交变换把由线性相关变量表示的观测数据转换为少数几个由线性无关变量表示的数据,线性无关的变量称为主成分。主成分分析属于降维方法。主成分分析主要用于发现数据中的基本结构,即数据中变量之间的关系,是数据分析的有力工具,也用于其他机器学习方法的前处理。主成分分析属于多元统计分析的经典方法。

  根据书中第16章的本章概要的主成分分析方法:

  主成分分析方法主要有两种,可以通过相关矩阵的特征值分解或样本矩阵的奇异值分解进行。
  (1)相关矩阵的特征值分解算法。针对m×n样本矩阵X,求样本相关矩阵

R=1n1XXT

再求样本相关矩阵的k个特征值和对应的单位特征向量,构造正交矩阵

V=(v1,v2,,vk)

V的每一列对应一个主成分,得到k×n样本主成分矩阵

Y=VTX

  (2)矩阵X的奇异值分解算法。针对m×n样本矩阵X

X=1n1XT

对矩阵X进行截断奇异值分解,保留k个奇异值、奇异向量,得到

X=USVT

V的每一列对应一个主成分,得到k×n样本主成分矩阵Y

Y=VTX

第2步:潜在语义分析的定义

  根据书中第17章的潜在语义分析介绍:

  潜在语义分析(LSA)是一个无监督学习方法,主要用于文本的话题分析,其特点是通过矩阵分解发现文本与单词之间的基于话题的语义关系。潜在语义分析使用的是非概率的话题分析模型,关键是对单词-文本矩阵进行矩阵因子分解(话题分析)。

  算法部分详见书中第17.2节的潜在语义分析算法的计算步骤。

第3步:两者的相同点

  1. 两者都属于无监督学习方法
  2. 两者都基于矩阵的奇异值分解
  3. 两者都可采用截断奇异值分解
  4. 两者都可用于降维

第4步:两者的不同点

  1. PCA对数据矩阵的协方差矩阵或者相关系数矩阵进行奇异值分解,而LSA直接对数据矩阵进行奇异值分解
  2. PCA需要对数据进行正规化,而LSA可以不对数据进行正规化
  3. PCA的原始矩阵一般不是稀疏矩阵,而LSA的矩阵一般是稀疏矩阵