<jupyter_start><jupyter_text>概率潜在语义分析 概率潜在语义分析（probabilistic latent semantic analysis, PLSA）,也称概率潜在语义索引（probabilistic latent semantic indexing, PLSI）,是一种利用概率生成模型对文本集合进行话题分析的无监督学习方法。模型最大特点是用隐变量表示话题，整个模型表示文本生成话题，话题生成单词，从而得到单词-文本共现数据的过程；假设每个文本由一个话题分布决定，每个话题由一个单词分布决定。  **18.1.2 生成模型**假设有单词集合 $W = $ {$w_{1}, w_{2}, ..., w_{M}$}， 其中M是单词个数；文本（指标）集合$D = $ {$d_{1}, d_{2}, ..., d_{N}$}, 其中N是文本个数；话题集合$Z = $ {$z_{1}, z_{2}, ..., z_{K}$}，其中$K$是预先设定的话题个数。随机变量 $w$ 取值于单词集合；随机变量 $d$ 取值于文本集合，随机变量 $z$ 取值于话题集合。概率分布 $P(d)$、条件概率分布 $P(z|d)$、条件概率分布 $P(w|z)$ 皆属于多项分布，其中 $P(d)$ 表示生成文本 $d$ 的概率，$P(z|d)$ 表示文本 $d$ 生成话题 $z$ 的概率，$P(w|z)$ 表示话题 $z$ 生成单词 $w$ 的概率。   每个文本 $d$ 拥有自己的话题概率分布 $P(z|d)$，每个话题 $z$ 拥有自己的单词概率分布 $P(w|z)$；也就是说**一个文本的内容由其相关话题决定，一个话题的内容由其相关单词决定**。      生成模型通过以下步骤生成文本·单词共现数据：     （1）依据概率分布 $P(d)$，从文本（指标）集合中随机选取一个文本 $d$ , 共生成 $N$  个文本；针对每个文本，执行以下操作；     （2）在文本$d$ 给定条件下，依据条件概率分布 $P(z|d)$, 从话题集合随机选取一个话题 $z$, 共生成 $L$ 个话题，这里 $L$ 是文本长度；     （3）在话题 $z$ 给定条件下，依据条件概率分布 $P(w|z)$ , 从单词集合中随机选取一个单词 $w$.     注意这里为叙述方便，假设文本都是等长的，现实中不需要这个假设。 生成模型中， 单词变量 $w$ 与文本变量 $d$ 是观测变量， 话题变量 $z$ 是隐变量， 也就是说模型生成的是单词-话题-文本三元组合 ($w, z ,d$)的集合， 但观测到的单词-文本二元组 （$w, d$）的集合， 观测数据表示为单词-文本矩阵 $T$的形式，矩阵 $T$ 的行表示单词，列表示文本， 元素表示单词-文本对（$w, d$）的出现次数。  从数据的生成过程可以推出，文本-单词共现数据$T$的生成概率为所有单词-文本对($w,d$)的生成概率的乘积：  $P(T) = \prod_{w,d}P(w,d)^{n(w,d)}$  这里 $n(w,d)$ 表示 ($w,d$)的出现次数，单词-文本对出现的总次数是 $N*L$。 每个单词-文本对（$w,d$）的生成概率由一下公式决定：  $P(w,d) = P(d)P(w|d)$   $= P(d)\sum_{z}P(w,z|d)$  $=P(d)\sum_{z}P(z|d)P(w|z)$  **18.1.3 共现模型**$P(w,d) = \sum_{z\in Z}P(z)P(w|z)P(d|z)$ 虽然生成模型与共现模型在概率公式意义上是等价的，但是拥有不同的性质。生成模型刻画文本-单词共现数据生成的过程，共现模型描述文本-单词共现数据拥有的模式。  如果直接定义单词与文本的共现概率 $P(w,d)$, 模型参数的个数是 $O(M*N)$, 其中 $M$ 是单词数， $N$ 是文本数。 概率潜在语义分析的生成模型和共现模型的参数个数是 $O(M*K + N*K)$, 其中 $K$ 是话题数。 现实中 $K<<M$, 所以**概率潜在语义分析通过话题对数据进行了更简洁的表示，减少了学习过程中过拟合的可能性**。  算法 18.1 （概率潜在语义模型参数估计的EM算法） 输入： 设单词集合为 $W = ${$w_{1}, w_{2},..., w_{M}$}, 文本集合为 $D=${$d_{1}, d_{2},..., d_{N}$}, 话题集合为 $Z=${$z_{1}, z_{2},..., z_{K}$}, 共现数据 $\left \{ n(w_{i}, d_{j}) \right \}, i = 1,2,..., M, j = 1,2,...,N;$  输出： $P(w_{i}|z_{k})$ 和 $P(z_{k}|d_{j})$.1. 设置参数 $P(w_{i}|z_{k})$ 和 $P(z_{k}|d_{j})$ 的初始值。2. 迭代执行以下E步，M步，直到收敛为止。   E步：      $P(z_{k}|w_{i},d_{j})=\frac{P(w_{i}|z_{k})P(z_{k}|d_{j})}{\sum_{k=1}^{K}P(w_{i}|z_{k})P(z_{k}|d_{j})}$     M步：      $P(w_{i}|z_{k})=\frac{\sum_{j=1}^{N}n(w_{i},d_{j})P(z_{k}|w_{i},d_{j})}{\sum_{m=1}^{M}\sum_{j=1}^{N}n(w_{m},d_{j})P(z_{k}|w_{m},d_{j})}$          $P(z_{k}|d_{j}) = \frac{\sum_{i=1}^{M}n(w_{i},d_{j})P(z_{k}|w_{i},d_{j})}{n(d_{j})}$  习题 18.3<jupyter_code>import numpy as np
X = [[0,0,1,1,0,0,0,0,0], 
     [0,0,0,0,0,1,0,0,1], 
     [0,1,0,0,0,0,0,1,0], 
     [0,0,0,0,0,0,1,0,1], 
     [1,0,0,0,0,1,0,0,0], 
     [1,1,1,1,1,1,1,1,1], 
     [1,0,1,0,0,0,0,0,0], 
     [0,0,0,0,0,0,1,0,1], 
     [0,0,0,0,0,2,0,0,1], 
     [1,0,1,0,0,0,0,1,0], 
     [0,0,0,1,1,0,0,0,0]]
X = np.asarray(X);X
X.shape
X = X.T;X
class PLSA:
    def __init__(self, K, max_iter):
        self.K = K
        self.max_iter = max_iter
        
    def fit(self, X):
        n_d, n_w = X.shape
        
        # P(z|w,d)
        p_z_dw = np.zeros((n_d, n_w, self.K))
        
        # P(z|d)
        p_z_d = np.random.rand(n_d, self.K)  
        
        # P(w|z)
        p_w_z = np.random.rand(self.K, n_w) 
        
        
        for i_iter in range(self.max_iter):
            # E step
            for di in range(n_d):
                for wi in range(n_w):
                    sum_zk = np.zeros((self.K))
                    for zi in range(self.K):
                        sum_zk[zi] = p_z_d[di, zi] * p_w_z[zi, wi]
                    sum1 = np.sum(sum_zk)
                    if sum1 == 0:
                        sum1 = 1
                    for zi in range(self.K):
                        p_z_dw[di, wi, zi] = sum_zk[zi] / sum1


            # M step

            # update P(z|d)
            for di in range(n_d):
                for zi in range(self.K):
                    sum1 = 0.
                    sum2 = 0.

                    for wi in range(n_w):
                        sum1 = sum1 + X[di, wi] * p_z_dw[di, wi, zi]
                        sum2 = sum2 + X[di, wi]

                    if sum2 == 0:
                        sum2 = 1
                    p_z_d[di, zi] = sum1 / sum2

            # update P(w|z)
            for zi in range(self.K):
                sum2 = np.zeros((n_w))
                for wi in range(n_w):
                    for di in range(n_d):
                        sum2[wi] = sum2[wi] + X[di, wi] * p_z_dw[di, wi, zi]
                sum1 = np.sum(sum2)
                if sum1 == 0:
                    sum1 = 1
                    for wi in range(n_w):
                        p_w_z[zi, wi] = sum2[wi] / sum1
                    
                    
        return p_w_z, p_z_d
    
# https://github.com/lipiji/PG_PLSA/blob/master/plsa.py
model = PLSA(2, 100)
p_w_z, p_z_d = model.fit(X)
p_w_z
p_z_d<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.22292603187576623, 'fraction_numerical': 0.03555373927257867, 'mean_word_length': 2.2786336235766913, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '7890', 'license': 'MIT', 'n_tokens_mistral': 3028, 'n_tokens_neox': 3215, 'n_words': 362, 'path': '第18章 概率潜在语义分析/PLSA.ipynb', 'repo_name': 'hktxt/Learn-Statistical-Learning-Method', 'size': '4894'}