topic model本质上就一个套路,在doc-word user-url user-doc等关系中增加topic层,扩充为2层结构,一方面可以降维,另一方面挖掘深层次的关系,用户doc word user url的聚类。
LDA的理论知识不介绍太多,基本就讲了原理以及推导两个内容,原理比较简单,推导过程貌似很简单,就一个变分加上一些参数估计的方法就搞定了,但是具体的细节还没明白,以后慢慢研究。
简单介绍下基本原理以及有意义的几个公式:

plsa作为topic-model ,每篇文档对应一系列topics,每个topic对应一批terms,有如下问题:
1.每篇文档及其在topic上的分布都是模型参数,也就是模型参数随着文档的数目增加而增加,这样容易导致overfitting
2.对于new doc,如何确定其topic 分布

LDA解决这个问题,没必要把每个doc-topic分布作为模型参数,为doc-topic分布增加一个先验概率,限制整体上文档的topic分布,具体先验分布的作用,之前已经介绍过。
doc-topic分布服从多项分布,狄利克雷分布是其共轭先验。
这样参数的个数就变成K + N*K, N为词个数,K为topic个数,与文档个数无关。
如果我想知道一个文档的topic分布怎么办?下面介绍下train以及predic的方法。
作者采用了varitional inference进行推导,过程就免了,列出来几个重要的公式:

论文中几个重要公式:
概率模型:
D 表示文档集合,最后就是保证P(D|α,β)最大。


phi的迭代公式,表示文档中单词n在topic i上的分布:

gamma的迭代公式,文档在topic上的分布

Beta的迭代公式,model中topic-word分布:

alpha的迭代公式,model中文档-topic分布的先验参数,利用梯度下降法即可求解:





LDA最核心的迭代公式,针对每一篇文档,计算每个词的topic分布,从而计算文档的topic分布:

变分后,计算出来的似然函数,其似然值用户判断迭代的收敛程度:





基本逻辑:
1.初始模型参数,开始迭代,执行2,3,4,直至收敛
2.针对每一篇文档,初始gamma以及phi参数,迭代该参数,直至收敛
     2.1.计算文档中每个单词在topic上的分布,利用model中beta以及文档-topic分布(2.2)
     2.2.计算文档-topic分布,利用模型参数alpha以及word-topic分布(2.1结果)
3.update模型参数beta,利用word-topic分布。
4.update模型参数alpha,利用2.2的结果gamma


源码介绍

模型参数:
var_gamma:文档-topic分布,每篇文档都会计算其topic分布
phi:              word-topic分布,针对每篇文档,计算文档中每个word的topic分布
lda_mode:    lad的模型参数,里面包括beta以及alpha
lda_suffstats: 记录统计信息,比如每个topic上每个word出现的次数,这是为了计算lda_model而存在
corpus:        全部文档信息
document:    文档的具体信息,包括word信息

corpus_initialize_ss:初始化 ss,随机选择一些文档,利用文档词信息update ss 里面topic上term的频度

    for (k = 0; k < num_topics; k++)

    {

        for (i = 0; i < NUM_INIT; i++)

        {

            d = floor(myrand() * c->num_docs);

            printf("initialized with document %d\n", d);

            doc = &(c->docs[d]);

            for (n = 0; n < doc->length; n++)

            {

                ss->class_word[k][doc->words[n]] += doc->counts[n];

            }

        }

        for (n = 0; n < model->num_terms; n++)

        {

            ss->class_word[k][n] += 1.0;

            ss->class_total[k] = ss->class_total[k] + ss->class_word[k][n];

        }

    }

random_initialize_ss:随机选取一些值初始化ss

run_em:执行EM算法,针对每篇文档,利用其单词以及初始化的β和α信息 更新模型,直至收敛。
该函数是一个框架性质的,具体的见下。
void run_em(char* start, char* directory, corpus* corpus)

{



    int d, n;

    lda_model *model = NULL;

    double **var_gamma, **phi;



    // allocate variational parameters



    var_gamma = malloc(sizeof(double*)*(corpus->num_docs));

    for (d = 0; d < corpus->num_docs; d++)

     var_gamma[d] = malloc(sizeof(double) * NTOPICS);



    int max_length = max_corpus_length(corpus);

    phi = malloc(sizeof(double*)*max_length);

    for (n = 0; n < max_length; n++)

     phi[n] = malloc(sizeof(double) * NTOPICS);



    // initialize model



    char filename[100];



    lda_suffstats* ss = NULL;

    if (strcmp(start, "seeded")==0)

    {

        model = new_lda_model(corpus->num_terms, NTOPICS);

        ss = new_lda_suffstats(model);

        corpus_initialize_ss(ss, model, corpus);

        lda_mle(model, ss, 0);

        model->alpha = INITIAL_ALPHA;

    }

    else if (strcmp(start, "random")==0)

    {

        model = new_lda_model(corpus->num_terms, NTOPICS);

        ss = new_lda_suffstats(model);

        random_initialize_ss(ss, model);

        lda_mle(model, ss, 0);

        model->alpha = INITIAL_ALPHA;

    }

    else

    {

        model = load_lda_model(start);

        ss = new_lda_suffstats(model);

    }



    sprintf(filename,"%s/000",directory);

    save_lda_model(model, filename);



    // run expectation maximization



    int i = 0;

    double likelihood, likelihood_old = 0, converged = 1;

    sprintf(filename, "%s/likelihood.dat", directory);

    FILE* likelihood_file = fopen(filename, "w");



    while (((converged < 0) || (converged > EM_CONVERGED) || (i <= 2)) && (i <= EM_MAX_ITER))

    {

        i++; 

        likelihood = 0;

        zero_initialize_ss(ss, model);



        // e-step



//这里是核心,针对每篇文档计算相关模型参数

        for (d = 0; d < corpus->num_docs; d++)

        {

   

            likelihood += doc_e_step(&(corpus->docs[d]),

                                     var_gamma[d],

                                     phi,

                                     model,

                                     ss);

        }



        // m-step



        lda_mle(model, ss, ESTIMATE_ALPHA);




        // check for convergence



        converged = (likelihood_old - likelihood) / (likelihood_old);

        if (converged < 0) VAR_MAX_ITER = VAR_MAX_ITER * 2;

        likelihood_old = likelihood;


doc_e_step:执行EM中的E-step,干了两件事情
1.基于文档的单词,update φ以及γ,得到doc-topic 分布以及 word-topic分布(lda_inference)
2.利用γ信息,更新α,这里的α只有一个参数,所以计算方式不同。


本来应该是αi = αi + ,但是实际α只有一个,所以作者通过在所有topic上的分布计算出α。
每篇文档迭代一次,遍历完所有文档后,就计算出最后的α。

double doc_e_step(document* doc, double* gamma, double** phi,

                  lda_model* model, lda_suffstats* ss)

{

    double likelihood;

    int n, k;



    // posterior inference



    likelihood = lda_inference(doc, model, gamma, phi);



    // update sufficient statistics



    double gamma_sum = 0;

    for (k = 0; k < model->num_topics; k++)

    {

        gamma_sum += gamma[k];

        ss->alpha_suffstats += digamma(gamma[k]);

    }

    ss->alpha_suffstats -= model->num_topics * digamma(gamma_sum);



    for (n = 0; n < doc->length; n++)

    {

        for (k = 0; k < model->num_topics; k++)

        {

            ss->class_word[k][doc->words[n]] += doc->counts[n]*phi[n][k];

            ss->class_total[k] += doc->counts[n]*phi[n][k];

        }

    }




    ss->num_docs = ss->num_docs + 1;



    return(likelihood);

}

lad_inference:这是最核心的code,基于每个doc,计算对应γ和φ,也就是doc-topic以及word-topic分布。也就是如下代码:
利用topic个数以及word个数,初始化γ以及φ。
严格实现这个过程,工程做了优化,对φ取了对数logφ,这样降低计算复杂度,同时利用log_sum接口,计算log(φ1) log(φ2)...log(φk)计算出log(φ1+φ2.....+φk),这样利用(logφ1)-log(φ1+φ2.....+φk)即可完成归一化。
利用:

解释下代码:
     针对文档中的每一个词 n:
          针对每个topic i:
               单词n在topic i上的分布为:φni = topic i在word n上的分布 × exp(Digamma(该文档在toic i上的分布))
         归一化 φni
         文档在topic上的分布γi = 其先验分布αi + 文档内所有词在topic i上的分布值和

lda-infernce,不只是在train的时候使用,对于一片新的文档,同样是通过该函数/方法计算文档在tpoic上的分布,只依赖于模型参数α以及β
利用:
double lda_inference(document* doc, lda_model* model, double* var_gamma, double** phi)

{

    double converged = 1;

    double phisum = 0, likelihood = 0;

    double likelihood_old = 0, oldphi[model->num_topics];

    int k, n, var_iter;

    double digamma_gam[model->num_topics];



    // compute posterior dirichlet

     //init gama and php

    for (k = 0; k < model->num_topics; k++)

    {

//初始化 γ以及φ

        var_gamma[k] = model->alpha + (doc->total/((double) model->num_topics));

        digamma_gam[k] = digamma(var_gamma[k]);

        for (n = 0; n < doc->length; n++)

            phi[n][k] = 1.0/model->num_topics;


    }

    var_iter = 0;



    while ((converged > VAR_CONVERGED) &&

           ((var_iter < VAR_MAX_ITER) || (VAR_MAX_ITER == -1)))

    {

     var_iter++;

     for (n = 0; n < doc->length; n++)

     {

            phisum = 0;

            for (k = 0; k < model->num_topics; k++)

            {

                oldphi[k] = phi[n][k];

               //对于每个word,更新对应的topic,也就是公式中的对数结果

               

                phi[n][k] = digamma_gam[k] + model->log_prob_w[k][doc->words[n]];

                   

                if (k > 0)

              //为归一化做准备,通过log(a) +log(b)计算log(a+b)

                    phisum = log_sum(phisum, phi[n][k]);


                else

                    phisum = phi[n][k]; // note, phi is in log space

            }

              

            for (k = 0; k < model->num_topics; k++)

            {

                phi[n][k] = exp(phi[n][k] - phisum);//归一化



//update γ,这里面没有用到α,原始公式不同

                var_gamma[k] =var_gamma[k] + doc->counts[n]*(phi[n][k] - oldphi[k]);

                // !!! a lot of extra digamma's here because of how we're computing it

                // !!! but its more automatically updated too.

                digamma_gam[k] = digamma(var_gamma[k]);

                    printf("%d:%d: gmama: %f php: %f\n", n, k, var_gmama[k], php[n][k]);

            }

        }

//计算似然结果,观察是否收敛,计算采用公式

        likelihood = compute_likelihood(doc, model, phi, var_gamma);

        assert(!isnan(likelihood));

        converged = (likelihood_old - likelihood) / likelihood_old;

        likelihood_old = likelihood;



        // printf("[LDA INF] %8.5f %1.3e\n", likelihood, converged);

    }

    return(likelihood);

}

compute likehood:这个函数实现的是blei LDA 公式15,也就是定义的似然函数,比较复杂,但是严格按照这个实现。
需要注意的是,blei的代码,k个α值相同,计算时 包含α的计算进行了简化。
利用:

double

compute_likelihood(document* doc, lda_model* model, double** phi, double* var_gamma)

{

    double likelihood = 0, digsum = 0, var_gamma_sum = 0, dig[model->num_topics];

    int k, n;



    for (k = 0; k < model->num_topics; k++)

    {

     dig[k] = digamma(var_gamma[k]);

     var_gamma_sum += var_gamma[k];


    }

    digsum = digamma(var_gamma_sum);



lgamma(α*k) - k*lgamma(alpha)

    likelihood =

     lgamma(model->alpha * model -> num_topics)

     - model -> num_topics * lgamma(model->alpha)

     - (lgamma(var_gamma_sum));



    for (k = 0; k < model->num_topics; k++)

    {

     likelihood +=

         (model->alpha - 1)*(dig[k] - digsum) + lgamma(var_gamma[k])

         - (var_gamma[k] - 1)*(dig[k] - digsum);



     for (n = 0; n < doc->length; n++)

     {

            if (phi[n][k] > 0)

            {

                likelihood += doc->counts[n]*

                    (phi[n][k]*((dig[k] - digsum) - log(phi[n][k])

                                + model->log_prob_w[k][doc->words[n]]));

            }

        }

    }

    return(likelihood);

}


lda_mle:针对每个文档执行do_e_step后,更新了ss,也就是计算模型所需要数据,topic-word对的次数

void lda_mle(lda_model* model, lda_suffstats* ss, int estimate_alpha)

{

    int k; int w;



    for (k = 0; k < model->num_topics; k++)

    {

        for (w = 0; w < model->num_terms; w++)

        {

            if (ss->class_word[k][w] > 0)

            {

                model->log_prob_w[k][w] =

                    log(ss->class_word[k][w]) -

                    log(ss->class_total[k]);

            }

            else

                model->log_prob_w[k][w] = -100;

        }

    }

    if (estimate_alpha == 1)

    {

        model->alpha = opt_alpha(ss->alpha_suffstats,

                                 ss->num_docs,

                                 model->num_topics);



        printf("new alpha = %5.5f\n", model->alpha);

    }

}

LDA实现的更多相关文章

  1. 用scikit-learn进行LDA降维

    在线性判别分析LDA原理总结中,我们对LDA降维的原理做了总结,这里我们就对scikit-learn中LDA的降维使用做一个总结. 1. 对scikit-learn中LDA类概述 在scikit-le ...

  2. 线性判别分析LDA原理总结

    在主成分分析(PCA)原理总结中,我们对降维算法PCA做了总结.这里我们就对另外一种经典的降维方法线性判别分析(Linear Discriminant Analysis, 以下简称LDA)做一个总结. ...

  3. word2vec参数调整 及lda调参

     一.word2vec调参   ./word2vec -train resultbig.txt -output vectors.bin -cbow 0 -size 200 -window 5 -neg ...

  4. PCA与LDA的区别与联系

    由于涉及内容较多,这里转载别人的博客: http://blog.csdn.net/sunmenggmail/article/details/8071502 其实主要在于:PCA与LDA的变换矩阵不同, ...

  5. 计算LDA模型困惑度

    http://www.52nlp.cn/lda-math-lda-%E6%96%87%E6%9C%AC%E5%BB%BA%E6%A8%A1 LDA主题模型评估方法--Perplexity http:/ ...

  6. LDA的Python实现源码

    #-*- coding:utf-8 -*- import logging import logging.config import ConfigParser import numpy as np im ...

  7. LDA( Latent Dirichlet Allocation)主题模型 学习报告

    1     问题描述 LDA由Blei, David M..Ng, Andrew Y..Jordan于2003年提出,是一种主题模型,它可以将文档集中每篇文档的主题以概率分布的形式给出,从而通过分析一 ...

  8. 关于LDA的几何表示——MATLAB实现

    承接这个PCA的练习,还有一个关于LDA的几何表示. 题目如下: 代码实现LDA如下:LDA.m clear clc % 生成training sample MU1 = [6 10]'; MU2 = ...

  9. Gensim LDA主题模型实验

    本文利用gensim进行LDA主题模型实验,第一部分是基于前文的wiki语料,第二部分是基于Sogou新闻语料. 1. 基于wiki语料的LDA实验 上一文得到了wiki纯文本已分词语料 wiki.z ...

  10. [综] Latent Dirichlet Allocation(LDA)主题模型算法

    多项分布 http://szjc.math168.com/book/ebookdetail.aspx?cateid=1&&sectionid=983 二项分布和多项分布 http:// ...

随机推荐

  1. Mybatis源码分析之参数映射及处理ParameterHandler

    ParameterHandler是用来设置参数规则的,当StatementHandler调用prepare方法之后,接下来就是调用它来进行设置参数. ParameterHandler接口: publi ...

  2. ECharts, PHP, MySQL, Ajax, JQuery 实现前后端数据可视化

    ECharts 下载js代码 工作原理浅析 在项目中引入ECharts 后台处理 数据库端MySQL PHP端 JQuery Ajax处理 ECharts 端处理 前端全部代码 演示结果 总结 最近要 ...

  3. Premake可生成vcxproj.filters

    Premake可生成vcxproj.filters (金庆的专栏) 添加 vcxproj.filters 文件可以用目录结构组织源文件. 例如premake5添加所有文件: files {       ...

  4. 【安卓开发】Layout Inflation不能这么用

    Layout inflation在Android上下文环境下转换XML文件成View结构对象的时候需要用到. LayoutInflater这个对象在Android的SDK中很常见,但是你绝对没想到竟然 ...

  5. FORM实现中打开图片,链接,文档(参考自itpub上一篇帖子,整理而来)

    FORM实现中打开图片,链接,文档 参考自itpub上一篇帖子,整理而来 1.添加PL程序库D2kwutil.pll 2.主要实现程序 /*过程参数说明: v_application --打开文件的应 ...

  6. EJB_开发单表映射的实体bean

    开发单表映射的实体bean 实体bean 它属于java持久化规范(JPA)里的技术,实体bean通过元数据在Javabean和数据库表之间建立起映射关系,然后Java程序员就可以随心所欲的使用面向对 ...

  7. ROS连接ABB机械臂调试详细教程-ROS(indigo)和ABB RobotStudio 6.03.02-

    在ROS industrial介绍中,给出了ROS和常用机械臂的连接方式.具体信息可以参考:http://wiki.ros.org/Industrial ROS连接ABB机械臂调试详细教程-ROS(i ...

  8. static,this,private关键字用法

    1:成员变量和局部变量的区别(理解) (1)在类中的位置不同 成员变量:类中方法外 局部变量:方法定义中或者方法声明上 (2)在内存中的位置不同 成员变量:在堆中 局部变量:在栈中 (3)生命周期不同 ...

  9. Dynamics CRM 插件注册时报Assembly must be registered in isolation的解决方法

    在插件注册的时候经常会遇到"Assembly must be registered in isolation"的问题导致无法注册,之前经常会被同事或者朋友问到这个问题,遇到这个问题 ...

  10. 验证码程序Demo

    小伙伴都有这样的经历,册各种网站,总是输不对验证码,双十一那天狂买的小伙伴是不是对输入验证码有着不一样的感触呢,以前觉得验证码真是个麻烦鬼,一个不小心,一个眼拙,哎呦,没有输入正确,又是一阵子大眼瞪小 ...