基于PLSA主题模型的文本聚类

杂谈:当年未竟之遗憾,小段代码笔记。当时苦思冥想未解当中奥妙,如今轻松信手拈来,丝毫不费力气。感叹机缘未到纵使踏破铁鞋也无用,转角回眸,那人却在灯火阑珊处。

需求分析

        文本聚类如果使用传统空间向量模型,很难解决同义词、多义词的情况,例如“这款苹果多少钱?”询问的是“手机iphone”而不是“水果Apple”。这时我们可以考虑从词汇的隐含语义、文本的隐含主题方面来入手解决问题。

特征表示

        特征,就是用来描述事物的东西,具有相同或相似特征值的事物可以归为一类。本文使用中文分词工具,对一个文本进行分词,形成字典向量,字典中的词汇作为文本的特征对其进行描述。

特征选取

        特征的选择可以使用基于领域知识规则的方法,也可以使用基于统计机器学习的方法。由于领域知识库搭建的困难度很高,所以一般采用基于统计机器学习的方法。在文本聚类项目中,特征选取的筛选可以使用TF-IDF逆文档频率,也可以使用信息熵(详见基于机器学习方法的POI品类推荐算法)。

模型建立

        有了文本、分词之后,我们可以构建一个文本-词汇矩阵。根据第1节 需求分析,我们打算从词汇隐含语义、文本隐含主题方面入手,沿着这种思路,这时我们可以假设一个文本以一定概率选中一个隐含主题,一个隐含主题以一定概率选中一个词汇(该隐含主题作为词汇的隐含语义),进一步我们可以联想到PLSA主题模型。

        在PLSA主题模型中,有P(z|d) * P(w|z) = P(w|z),其中z代表隐含主题,d代表文本document,w代表词汇word,P(z|d)和P(w|z)就是我们需要估计的参数,它们是未知数,我们使用极大似然估计来调参,使得P(w|z)的结果更加符合文本-词汇矩阵。

        PLSA主题模型的推导过程如下:

PLSA中完全数据(complete data)的似然函数:

其中,表示文档i中的第j个单词的主题是否为k,如果为k则为1,否则为0;n(di, wj)表示词汇wj在文本di中出现的次数。

相应地,对数似然函数如下

基于PLSA主题模型的文本聚类

然后Q function就是

又有两个约束条件:

基于PLSA主题模型的文本聚类

下面利用拉格朗日乘法求参数

拉格朗日函数:

基于PLSA主题模型的文本聚类

然后分别对参数求导:

基于PLSA主题模型的文本聚类

求解过程:以(1)为例,将(1)变换成如下形式:

这样的系数就变成了1,那么我们就求得了

然后再将结果代入(1),得到

类似地,可以求得

基于PLSA主题模型的文本聚类

然后有

推导完毕

        在上述推导中,我们可以发现,每一步都需要求出,根据贝叶斯定理,我们可知

        这就是PLSA模型EM算法中的E-Step(求期望),然后根据P(z|d,w)极大似然估计参数,

基于PLSA主题模型的文本聚类

        这就是PLSA模型EM算法中的M-Step(极大化),其中θ 表示所要估计的参数,详见《应用概率统计》极大似然法 一节的参数含义。

算法实现

        PLSA模型EM参数估计算法具体步骤如下:首先我们给P(w|z)、P(z|d)一个随机值,根据E-Step求出P(z|d,w)。然后根据M-Step依次更新P(z|d)、P(w|z)等|Z||D|+|W||Z| 个参数,每更新一个参数就进行一次E-Step,不断迭代M-Step直至算法收敛达到极大似然化,调参迭代次数可以自己设定。

package plsa;

public class Plsa {
    double[][][] Pz_dw;
    double[][] Pz_d;
    double[][] Pw_z;
    int[][] nd_w;
    int kz, kd, kw;

    public Plsa(int kz, int kd, int kw, int[][] nd_w) {
        this.kz = kz;
        this.kd = kd;
        this.kw = kw;

        this.Pz_dw = new double[kz][kd][kw];
        this.Pz_d = new double[kz][kd];
        this.Pw_z = new double[kw][kz];

        for (int i = 0; i < kz; i++) {
            for (int j = 0; j < kd; j++) {
                for (int k = 0; k < kw; k++) {
                    this.Pz_dw[i][j][k] = (double) 1 / kz / kd / kw;
                }
            }
        }

        for (int i = 0; i < kz; i++) {
            for (int j = 0; j < kd; j++) {
                this.Pz_d[i][j] = (double) 1 / kz / kd;
            }
        }

        for (int i = 0; i < kw; i++) {
            for (int j = 0; j < kz; j++) {
                this.Pw_z[i][j] = (double) 1 / kw / kz;
            }
        }

        this.nd_w = nd_w;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            MStep();
        }

        for (int i = 0; i < kz; i++) {
            for (int j = 0; j < kd; j++) {
                System.out.printf("%.2f\t", Pz_d[i][j]);
            }
            System.out.println();
        }
    }

    public void EStep() {
        for (int z = 0; z < kz; z++) {
            for (int d = 0; d < kd; d++) {
                for (int w = 0; w < kw; w++) {
                    double Pt = 0;
                    for (int ptz = 0; ptz < kz; ptz++) {
                        Pt += Pz_d[ptz][d] * Pw_z[w][ptz];
                    }
                    Pz_dw[z][d][w] = Pz_d[z][d] * Pw_z[w][z] / Pt;
                }
            }
        }
    }

    public void MStep() {
        for (int w = 0; w < kw; w++) {
            for (int z = 0; z < kz; z++) {
                double Pta = 0;
                for (int id = 0; id < kd; id++) {
                    Pta += nd_w[id][w] * Pz_dw[z][id][w];
                }

                double Ptb = 0;
                for (int id = 0; id < kd; id++) {
                    for (int iw = 0; iw < kw; iw++) {
                        Ptb += nd_w[id][iw] * Pz_dw[z][id][iw];
                    }
                }

                Pw_z[w][z] = Pta / Ptb;
                EStep();
            }
        }

        for (int z = 0; z < kz; z++) {
            for (int d = 0; d < kd; d++) {
                double Pta = 0;
                for (int iw = 0; iw < kw; iw++) {
                    Pta += nd_w[d][iw] * Pz_dw[z][d][iw];
                }

                double Ptb = 0;
                for (int iw = 0; iw < kw; iw++) {
                    for (int iz = 0; iz < kz; iz++) {
                        Ptb += nd_w[d][iw] * Pz_dw[iz][d][iw];
                    }
                }

                Pz_d[z][d] = Pta / Ptb;
                EStep();
            }
        }
    }
}

Reference

http://blog.tomtung.com/2011/10/plsa/

http://www.xuebuyuan.com/551762.html

你可能感兴趣的:(主题模型,文本聚类,PLSA,潜语义分析)