Topic Model 的复杂度计算(时间和空间)

来源:互联网 发布:在淘宝点击卖家没反应 编辑:程序博客网 时间:2024/06/05 08:53

算法复杂度包含两个方面,时间复杂度和空间复杂度,也就是常称的计算复杂度和内存空间占用两方面。下面先说主题模型的计算复杂度,再说内存占用。

时间复杂度

    本来这个问题挺简单的,但是有搞统计的同学不知道CS里面的复杂度是怎么计算,就写到这里吧。本文比较三个topic model时间复杂度的计算方法,分别是LDA,Biterm,和hdLDA。

    时间复杂度的计算很好掌握,就是把算法中的最基本操作作为单元操作,设定其时间复杂度为O(1)。最简单的可以是赋值、加减乘除等操作;复杂的也可以将之前文章中讲到的一次Gibbs sampling抽样(一个变量t+1时刻的值,由其它变量t时刻的值抽得),作为一个单元操作。最后看该操作总共执行了多少次,若执行了M次,则算法的时间复杂度就是O(M)。

    在Topic Model中,单元操作可看作是一次Gibbs抽样(其实可以更细粒度到加减乘除运算,但是本次所对比的LDA和Biterm模型的单次抽样复杂度相同,所以不需要比较更细粒度的运算)。那么计算时间复杂度就是计算该抽样所执行的次数。所以接下来就是看一下套在单元计算外面的计算步骤。

int last_iter = liter;for (liter = last_iter + 1; liter <= niters + last_iter; liter++) {    printf("Iteration %d ...\n", liter);    for (int m = 0; m < M; m++) {        for (int n = 0; n < ptrndata->docs[m]->length; n++) {            int topic = sampling(m, n);            z[m][n] = topic;        }    }}

    由上面代码可以看出,最外层是一个迭代次数Niter, 内层是文档个数M,也即ND,再内层是相应文档的长度(不重复单词的个数)。由于每一篇文档的长度不同,因此取平均值l¯。然后再看一下sampling()代码:

int model::sampling(int m, int n) {    // remove z_i from the count variables    int topic = z[m][n];    int w = ptrndata->docs[m]->words[n];    nw[w][topic] -= 1;    nd[m][topic] -= 1;    nwsum[topic] -= 1;    ndsum[m] -= 1;    double Vbeta = V * beta;    double Kalpha = K * alpha;        // do multinomial sampling via cumulative method    for (int k = 0; k < K; k++) {        p[k] = (nw[w][k] + beta) / (nwsum[k] + Vbeta) *            (nd[m][k] + alpha) / (ndsum[m] + Kalpha);    }    // cumulate multinomial parameters    for (int k = 1; k < K; k++) {    p[k] += p[k - 1];    }    double u = ((double)random() / RAND_MAX) * p[K - 1];    for (topic = 0; topic < K; topic++) {        if (p[topic] > u) {            break;        }    }    nw[w][topic] += 1;    nd[m][topic] += 1;    nwsum[topic] += 1;    ndsum[m] += 1;         return topic;}

    可以看到核心计算过程也就是在为p[k]赋值的过程,而k一共有K个。所以最终的复杂度也就是:O(NiterNDKl¯)

    那么计算Biterm topic model也就很简单了。由于该模型的主题分布不在文档上,而是在biterm上,而biterm的个数为NDl¯(l¯1)/2。因此时间复杂度就是O(NiterKNDl¯(l¯1)/2)

    对于hdLDA模型来说,其抽样步骤比较繁琐。具体代码为:

printf("Sampling %d iterations!\n", niters);int last_iter = liter;for (liter = last_iter + 1; liter <= niters + last_iter; liter++) {     for (int m = 0; m < M; m++) {         for (int n = 0; n < ptrndata->contents[m]->length; n++) {            int topic = sampling_step_one(m, n);            z[m][n] = topic;         }     }     for (int m = 0; m < M; m++) {         for (int c = 0; c < ptrndata->comments[m]->C; c++) {             xdc[m][c] = sampling_step_two_x(m, c);             ydc[m][c] = sampling_step_two_y(m, c);         }     }     for (int m = 0; m < M; m++) {         for (int c = 0; c < ptrndata->comments[m]->C; c++) {             sampling_step_three(m, c);         }     }}

    可以看到每一个sampling step在外层都共享了NiterND次循环(其中用NDM)。而sampling_step_one()接下来的循环次数为:

ptrndata->contents[m]->length

这与LDA代码部分相似。

    而sampling_step_two()和sampling_step_three()则各自循环下面次数,也就是每篇正文的跟帖条数:

ptrndata->comments[m]->C

由于不是每篇正文的跟帖数目都是相同的,因此这里也取平均值,记作C¯

    接下来再看看每一个抽样步骤中的时间复杂度。sampling_step_one()的代码是这样的:

int model::sampling_step_one(int m, int n) {    int topic = z[m][n];    int w = ptrndata->contents[m]->words[n];    ndk[m][topic] -= 1;    nkv[topic][w] -= 1;    ndksum[m] -= 1;    nkvsum[topic] -= 1;    for (int k = 0; k < K; k++) {    p[k] = exp(log(ndk[m][k] + gdk[m][k] + alpha) - log((ndksum[m] + gdksum[m] + Kalpha)) + log((nkv[k][w] + gkv[k][w] + betaFM[k][w])) - log((nkvsum[k] + gkvsum[k] + VbetaF[k])));    }    // cumulate multinomial parameters    for (int k = 1; k < K; k++) {        p[k] += p[k - 1];    }    // scaled sample because of unnormalized p[]    double u = ((double)rand() / RAND_MAX) * p[K - 1];    for (topic = 0; topic < K; topic++) {        if (p[topic] >= u) {            break;        }    }    if (topic == K){        topic = K - 1;    }    ndk[m][topic] += 1;    nkv[topic][w] += 1;    ndksum[m] += 1;    nkvsum[topic] += 1;    return topic;}

可以看到其中核心计算步骤迭代了K次,与LDA和Biterm相同。因此对于sampling_step_one()步来说,其具有复杂度NiterNDl¯

    对于sampling_step_two_x()和sampling_step_two_y()来说,其代码非常相似,节省篇幅起见,下面只给出step_two_x()的代码:

int model::sampling_step_two_x(int m, int c){    int topic = 0;    for (int i = 0; i < ptrndata->comments[m]->commlines[c]->length; i++) {        if(I[m][c][i]) {            topic = xdc[m][c];            gdk[m][topic] -= 1;            gdksum[m] -= 1;            gkv[topic][ptrndata->comments[m]->commlines[c]->words[i]] -= 1;            gkvsum[topic] -= 1;        }    }    for (int k = 0; k < K; k++) {        p[k] = log(1.0);        double left = log(ndk[m][k] + gdk[m][k] + alpha) - log(ndksum[m] + gdksum[m] + Kalpha);        for (int i = 0; i < ptrndata->comments[m]->commlines[c]->length; i++) {            if (I[m][c][i]){            int w = ptrndata->comments[m]->commlines[c]->words[i];            p[k] += (log(nkv[k][w] + gkv[k][w] + betaFM[k][w]) - log(nkvsum[k] + gkvsum[k] + VbetaF[k]));        }    }     p[k] += left;     p[k] = exp(p[k]);    }    // cumulate multinomial parameters    for (int k = 1; k < K; k++) {        p[k] += p[k - 1];    }    double u = ((double)rand() / RAND_MAX) * p[K - 1];    for (topic = 0; topic < K; topic++) {        if (p[topic] >= u) {            break;        }    }    if (topic == K){        topic = K - 1;    }    for (int i = 0; i < ptrndata->comments[m]->commlines[c]->length; i++) {        if(I[m][c][i]) {            gkv[topic][ptrndata->comments[m]->commlines[c]->words[i]] += 1;            gkvsum[topic] += 1;            gdk[m][topic] += 1;            gdksum[m] += 1;        }    }    return topic;}

可以看出这一步中,计算时间复杂度为C¯lc¯,其中lc¯为跟帖的平均长度。同理sampling_step_two_y()也具有相同的复杂度。

    而sampling_step_three()的复杂度为lc¯。因此hdLDA的时间复杂度为:NiterND(Kl¯+2KC¯lc¯+C¯lc¯)

空间复杂度

    空间复杂度就是程序在计算时,需要放在内存中用于存储中间结果的矩阵、向量等数据类型所占的空间。一般省略掉无关重要的变量,如标记变量等。而只关注与算法核心相关的内存空间占用(好像更贴近伪代码)。

    在LDA中,算法需要维护三个矩阵:θ,ϕ 和字典映射矩阵。它们分别为文档在主题上的分布矩阵,规模为NDK;主题在词上的分布,规模为 WK和文档次的编号映射矩阵,规模为NDl¯。因此其空间复杂度就是NDK+WK+NDl¯了。

    Biterm因为没有了文档的概念,没有了矩阵θ, 而ϕ又变成了主题在biterm上的分布,因而复杂度相对于LDA有一点调整,是K+WK+NDl¯(l¯1)/2

    hdLDA所需要的不重要变量特别多,但是如果不计入核心算法的话,有ϕ矩阵,规模为WKθ矩阵,规模为NDKψ矩阵,规模为JW,其中K表示formal topic 的个数,J表示leisure topic 的个数;以及文档保存矩阵ptrndata(结构体),规模为NDC¯lc¯因此空间复杂度为WK+NDK+JW+NDC¯lc¯

发现好简单啊。

1 0
原创粉丝点击