基本介绍:

  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);
    }
}

转自:http://blog.csdn.net/hxxiaopei/article/details/8034308

LDA基本介绍以及LDA源码分析(BLEI)的更多相关文章

  1. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  2. ArrayList的源码分析

    在项目中经常会用到list集合来存储数据,而其中ArrayList是用的最多的的一个集合,这篇博文主要简单介绍ArrayList的源码分析,基于JDK1.7: 这里主要介绍 集合 的属性,构造器,和方 ...

  3. 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 百篇博客分析OpenHarmony源码 | v64.01

    百篇博客系列篇.本篇为: v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么 ...

  4. 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 祝新的一年牛气冲天 ! | v32.02

    百篇博客系列篇.本篇为: v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...

  5. 介绍开源的.net通信框架NetworkComms框架 源码分析

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 售价249英镑 我曾经花了 ...

  6. MyBatis 源码分析——介绍

    笔者第一次接触跟MyBatis框架是在2009年未的时候.不过那个时候的他并不叫MyBatis,而是叫IBatis.2010年的时候改为现在的名字--MyBatis.这几年过去了,对于笔者来讲有一点陌 ...

  7. HashMap源码分析和应用实例的介绍

    1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...

  8. Hadoop2源码分析-YARN RPC 示例介绍

    1.概述 之前在<Hadoop2源码分析-RPC探索实战>一文当中介绍了Hadoop的RPC机制,今天给大家分享关于YARN的RPC的机制.下面是今天的分享目录: YARN的RPC介绍 Y ...

  9. ThreadLocal介绍以及源码分析

    ThreadLocal 线程主变量 前面部分引用其他优秀博客,后面源码自己分析的,如有冒犯请私聊我. 用Java语言开发的同学对 ThreadLocal 应该都不会陌生,这个类的使用场景很多,特别是在 ...

随机推荐

  1. FastReport.Net使用:[6]HTML标签使用

    使用HTML标签的基础知识 1.FastReport所支持的HTML标签包括: ●粗体:<b>...</b> ●斜体:<i>...</i> ●下划线:& ...

  2. 【BZOJ 4455】 4455: [Zjoi2016]小星星 (容斥原理+树形DP)

    4455: [Zjoi2016]小星星 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 426  Solved: 255 Description 小Y是 ...

  3. 较有意思的Apple XSS(CVE-2016-7762)漏洞

    文章作者:Avfisher0x00 前言应CVE作者的要求帮忙分析一下这个漏洞,实际上这是一个思路比较有意思的Apple XSS(CVE-2016-7762).漏洞作者确实脑洞比较大也善于尝试和发掘, ...

  4. django基础入门

    1. http协议 1.1 请求协议 请求协议格式: 请求首行: // 请求方式 请求路径 协议和版本,例如:GET /index.html HTTP/1.1 请求头信息: // 请求头名称:请求头内 ...

  5. 21.多源最短路(floyd算法)

    时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 已知n个点(n<=100),给你n*n的方阵,a[i,j] ...

  6. git -- git clone

    git clone 命令参数: usage: git clone [options] [--] <repo> [<dir>] -v, --verbose be more ver ...

  7. ADC for programmable logic uses one capacitor

    Many electronic devices require user input for setting the application properties. Typical input dev ...

  8. Spring MVC概述

    Spring MVC框架是一个开源的Java平台,为开发强大的基于Java的Web应用程序提供全面的基础架构支持非常容易和非常快速. Spring框架最初由Rod Johnson撰写,并于2003年6 ...

  9. jdk1.8 foreach

    lambda 表达式效率非常低,测试代码可以看到大概3~5倍的差距 遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的: public static voi ...

  10. cocos2d-x 中文乱码问题解决方案

    在windows环境下使用visual studio 开发cocos2d-x,由于visual studio 默认编码为GBK 格式,而cocos2d-x引擎默认编码为UTF-8, 如果有用到中文,在 ...