1.单词向量空间模型通过单词的向量表示文本的语义内容。以单词-文本矩阵
X
X
X为输入,其中每一行对应一个单词,每一列对应一个文本,每一个元素表示单词在文本中的频数或权值(如TF-IDF)
X
=
[
x
11
x
12
⋯
x
1
n
x
21
x
22
⋯
x
2
n
⋮
⋮
⋮
x
m
1
x
m
2
⋯
x
m
n
]
X = \left[ \begin{array} { c c c c } { x _ { 11 } } & { x _ { 12 } } & { \cdots } & { x _ { 1 n } } \\ { x _ { 21 } } & { x _ { 22 } } & { \cdots } & { x _ { 2 n } } \\ { \vdots } & { \vdots } & { } & { \vdots } \\ { x _ { m 1 } } & { x _ { m 2 } } & { \cdots } & { x _ { m n } } \end{array} \right]
X=
x11x21⋮xm1x12x22⋮xm2⋯⋯⋯x1nx2n⋮xmn
单词向量空间模型认为,这个矩阵的每一列向量是单词向量,表示一个文本,两个单词向量的内积或标准化内积表示文本之间的语义相似度。
TF-IDF 是词频-逆文档频率(Term Frequency-Inverse Document Frequency),这个值越大,表示词语 t i t_i ti 在文档 d j d_j dj 中越重要。它结合了词语在单个文档中的频率和词语在整个文档集中出现的稀有程度,能够有效地衡量词语的重要性。公式如下:
TF-IDF i j = t f i j t f j × log ( d f d f i ) \text{TF-IDF}_{ij} = \frac{tf_{ij}}{tf_{j}} \times \log \left( \frac{df}{df_i} \right) TF-IDFij=tfjtfij×log(dfidf)
-
词频部分:
t f i j t f j \frac{tf_{ij}}{tf_{j}} tfjtfij- t f i j tf_{ij} tfij:词语 t i t_i ti 在文档 d j d_j dj 中出现的次数。
- t f j tf_{j} tfj:文档 d j d_j dj 中所有词语出现的总次数。
这个部分计算的是词语 t i t_i ti 在文档 d j d_j dj 中的相对词频(Term Frequency, TF),即词语在文档中出现的频率。
-
逆文档频率部分:
log ( d f d f i ) \log \left( \frac{df}{df_i} \right) log(dfidf)- d f df df:文档集中所有文档的总数。
- d f i df_i dfi:包含词语 t i t_i ti 的文档数。
这个部分计算的是词语 t i t_i ti 的逆文档频率(Inverse Document Frequency, IDF)。当词语 t i t_i ti 出现在较少的文档中时,IDF值较高,表示该词语对区分文档的重要性较大。反之,如果词语 t i t_i ti 出现在许多文档中,IDF值较低,表示该词语对区分文档的重要性较小。
2.话题向量空间模型通过话题的向量表示文本的语义内容。假设有话题文本矩阵
Y
=
[
y
11
y
12
⋯
y
1
n
y
21
y
22
⋯
y
2
n
⋮
⋮
⋮
y
k
1
y
k
2
⋯
y
k
n
]
Y = \left[ \begin{array} { c c c c } { y _ { 11 } } & { y _ { 12 } } & { \cdots } & { y _ { 1 n } } \\ { y _ { 21 } } & { y _ { 22 } } & { \cdots } & { y _ { 2 n } } \\ { \vdots } & { \vdots } & { } & { \vdots } \\ { y _ { k 1 } } & { y _ { k 2 } } & { \cdots } & { y _ { k n } } \end{array} \right]
Y=
y11y21⋮yk1y12y22⋮yk2⋯⋯⋯y1ny2n⋮ykn
其中每一行对应一个话题,每一列对应一个文本,每一个元素表示话题在文本中的权值。话题向量空间模型认为,这个矩阵的每一列向量是话题向量,表示一个文本,两个话题向量的内积或标准化内积表示文本之间的语义相似度。假设有单词话题矩阵
T
T
T
T
=
[
t
11
t
12
⋯
t
1
k
t
21
t
22
⋯
t
2
k
⋮
⋮
⋮
t
m
1
t
m
2
⋯
t
m
k
]
T = \left[ \begin{array} { c c c c } { t _ { 11 } } & { t _ { 12 } } & { \cdots } & { t _ { 1 k } } \\ { t _ { 21 } } & { t _ { 22 } } & { \cdots } & { t _ { 2 k } } \\ { \vdots } & { \vdots } & { } & { \vdots } \\ { t _ { m 1 } } & { t _ { m 2 } } & { \cdots } & { t _ { m k } } \end{array} \right]
T=
t11t21⋮tm1t12t22⋮tm2⋯⋯⋯t1kt2k⋮tmk
其中每一行对应一个单词,每一列对应一个话题,每一个元素表示单词在话题中的权值。
给定一个单词文本矩阵
X
X
X
X
=
[
x
11
x
12
⋯
x
1
n
x
21
x
22
⋯
x
2
n
⋮
⋮
⋮
x
m
1
x
m
2
⋯
x
m
n
]
X = \left[ \begin{array} { c c c c } { x _ { 11 } } & { x _ { 12 } } & { \cdots } & { x _ { 1 n } } \\ { x _ { 21 } } & { x _ { 22 } } & { \cdots } & { x _ { 2 n } } \\ { \vdots } & { \vdots } & { } & { \vdots } \\ { x _ { m 1 } } & { x _ { m 2 } } & { \cdots } & { x _ { m n } } \end{array} \right]
X=
x11x21⋮xm1x12x22⋮xm2⋯⋯⋯x1nx2n⋮xmn
潜在语义分析的目标是,找到合适的单词-话题矩阵
T
T
T与话题-文本矩阵
Y
Y
Y,将单词-文本矩阵
X
X
X近似的表示为
T
T
T与
Y
Y
Y的乘积形式。
X
≈
T
Y
X \approx T Y
X≈TY
等价地,潜在语义分析将文本在单词向量空间的表示X通过线性变换 T T T转换为话题向量空间中的表示 Y Y Y。
潜在语义分析的关键是对单词-文本矩阵进行以上的矩阵因子分解(话题分析)
3.潜在语义分析的算法是奇异值分解。通过对单词文本矩阵进行截断奇异值分解,得到
X
≈
U
k
Σ
k
V
k
T
=
U
k
(
Σ
k
V
k
T
)
X \approx U _ { k } \Sigma _ { k } V _ { k } ^ { T } = U _ { k } ( \Sigma _ { k } V _ { k } ^ { T } )
X≈UkΣkVkT=Uk(ΣkVkT)
矩阵 U k U_k Uk表示话题空间,矩阵 ( Σ k V k T ) ( \Sigma _ { k } V _ { k } ^ { T } ) (ΣkVkT)是文本在话题空间的表示。
4.非负矩阵分解也可以用于话题分析。非负矩阵分解将非负的单词文本矩阵近似分解成两个非负矩阵
W
W
W和
H
H
H的乘积,得到
X
≈
W
H
X \approx W H
X≈WH
矩阵 W W W表示话题空间,矩阵 H H H是文本在话题空间的表示。
非负矩阵分解可以表为以下的最优化问题:
min
W
,
H
∥
X
−
W
H
∥
2
s.t.
W
,
H
≥
0
\left. \begin{array} { l } { \operatorname { min } _ { W , H } \| X - W H \| ^ { 2 } } \\ { \text { s.t. } W , H \geq 0 } \end{array} \right.
minW,H∥X−WH∥2 s.t. W,H≥0
非负矩阵分解的算法是迭代算法。乘法更新规则的迭代算法,交替地对
W
W
W和
H
H
H进行更新。本质是梯度下降法,通过定义特殊的步长和非负的初始值,保证迭代过程及结果的矩阵
W
W
W和
H
H
H均为非负。
非负矩阵分解(NMF)
NMF 是一种矩阵分解技术,用于将一个非负矩阵 X X X分解为两个非负矩阵 W W W和 H H H的乘积,即:
X ≈ W ⋅ H X \approx W \cdot H X≈W⋅H
其中:
- X X X是原始的非负矩阵,维度为 m × n m \times n m×n。
- W W W是左因子矩阵,维度为 m × k m \times k m×k。
- H H H是右因子矩阵,维度为 k × n k \times n k×n。
- k k k是分解的潜在特征的数量,也就是我们选择的维度。
这种分解方法在文本挖掘、图像处理和推荐系统中广泛应用。
import numpy as np
# 保留两位小数,不使用科学计数法
np.set_printoptions(precision=2, suppress=True)
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
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)
model = MyNMF()
W, H = model.fit(X, 3, 200)
print('W:\n', W)
print('H:\n', H)
# 重构
X_ = inverse_transform(W, H)
print('X:\n', X)
print('X_:\n', X_)
print('重构损失:', loss(X, X_))
W:
[[0. 0.39 0. ]
[0. 0.78 0. ]
[0. 0. 0.4 ]
[1.44 0. 0.75]
[0.49 0. 0. ]
[0.46 0.97 0.78]]
H:
[[0. 0. 0.08 2.05]
[1.03 2.06 0.01 0.01]
[0.01 0.01 2.5 0.07]]
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_:
[[0.4 0.8 0.01 0. ]
[0.8 1.6 0.01 0.01]
[0. 0. 1. 0.03]
[0. 0.01 2. 3. ]
[0. 0. 0.04 1. ]
[1. 2. 2. 1. ]]
重构损失: 4.002696167437877
# 使用sklearn
from sklearn.decomposition import NMF
model = NMF(n_components=3, init='random', random_state=0)
W = model.fit_transform(X)
H = model.components_
print('W:\n', W)
print('H:\n', H)
# 重构
X_ = inverse_transform(W, H)
print('X:\n', X)
print('X_:\n', X_)
print('重构损失:', loss(X, X_))
W:
[[0. 0.54 0. ]
[0. 1.08 0. ]
[0.7 0. 0. ]
[1.4 0. 1.97]
[0. 0. 0.66]
[1.4 1.35 0.66]]
H:
[[0. 0. 1.43 0. ]
[0.74 1.49 0. 0. ]
[0. 0. 0. 1.52]]
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_:
[[0.4 0.8 0. 0. ]
[0.8 1.6 0. 0. ]
[0. 0. 1. 0. ]
[0. 0. 2. 3. ]
[0. 0. 0. 1. ]
[1. 2. 2. 1. ]]
重构损失: 4.000001672582457
习题17.1
试将图17.1的例子进行潜在语义分析,并对结果进行观察。
# X=U*(Sigma*Vt)=T*Y
import numpy as np
# 保留两位小数,不使用科学计数法
np.set_printoptions(precision = 2, suppress = True)
def lsa_svd(X, k):
"""
潜在语义分析的矩阵奇异值分解
:param X: 单词-文本矩阵
:param k: 话题数
:return: 话题向量空间、文本集合在话题向量空间的表示
"""
# 单词-文本矩阵X的奇异值分解
U, S, Vt = np.linalg.svd(X)
# 矩阵的截断奇异值分解,取前k个
U = U[:, :k]
S = np.diag(S[:k])
Vt = Vt[:k, :] # 取V的前k列即为取V^T的前k行
return U, np.dot(S, Vt)
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)
T, Y = lsa_svd(X, 3)
print("单词话题矩阵T(话题空间):\n", T)
print("话题文本矩阵Y(文本在话题空间的表示):\n", Y)
单词话题矩阵T(话题空间):
[[-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. ]]
话题文本矩阵Y(文本在话题空间的表示):
[[-0.79 -1.57 -2.86 -2.96]
[-1.08 -2.15 0.1 1.33]
[ 1.79 -0.89 -0. 0. ]]
在假设话题个数为3的情况下,单词airplane在话题3上的权值最大为0.89,表示单词airplane在话题3中的重要度最高;文本 d 2 d2 d2在话题2中的权值最大为-2.15,表示话题2在文本 d 2 d2 d2中的重要度最高。
# 或者可以直接使用sklearn的截断奇异值分解类
from sklearn.decomposition import TruncatedSVD
# 可惜的是这个类不会显式地计算出左奇异矩阵U
svd = TruncatedSVD(n_components=3, n_iter=7, random_state=42)
svd.fit(X)
Sigma = np.diag(svd.singular_values_)
# svd.components_返回的是已截断的Vt,形状为(n_components,X的列数)
Y = np.dot(Sigma, svd.components_)
print("话题文本矩阵Y(文本在话题空间的表示):\n", Y)
话题文本矩阵Y(文本在话题空间的表示):
[[ 0.79 1.57 2.86 2.96]
[ 1.08 2.15 -0.1 -1.33]
[ 1.79 -0.89 -0. 0. ]]
习题17.2
给出损失函数是散度损失时的非负矩阵分解(潜在语义分析)的算法。
损失函数是散度损失时的非负矩阵分解算法
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
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=200, 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,习题17.4
解答见链接
使用书籍:李航《机器学习方法》
习题解答:https://datawhalechina.github.io/statistical-learning-method-solutions-manual/#/