GibbsLDA model.cpp分析
刚刚是英格兰和冰岛的比赛,我也是醉了,本来以为大英格兰会血虐冰岛,但是目前为止看上去似乎冰岛在血虐英格兰的样子。目前冰岛2:1英格兰。
冰岛第一个传中,前锋抢点铲射。第二个随便抡一脚,哈特就成了背景帝。我越来越觉得哈特具有国际奉献精神了,给各国球员送上值得纪念的经典入球。
头文件
这个cpp是包含了所有的头文件,可以看出是个大boss。
函数
函数1:
model::~model();
首先看到的是析构函数,因为这其中包含诸多指针,所以需要一个一个的释放,首先就是一个sampling的时候的小变量p。
然后是文本数据:ptrndata和pnewdata
之后是z,也就是tassign。
nw,nd都是数组删除。
nwsum,ndsum直接删除。
theta:M*K
phi:K*V
new的一大串也可以直接删除。
函数2:
void model::set_default_values();
对于几个后缀的赋值。我们看到的存储文件的suffix就是这么出来的。(不过怎么从来没看到过trainlog呢?)
函数3:
int model::parse_args(int argc, char ** argv);
对于输入参数的解析。是调用了utils中的parse_args,同时传入this,可以对于this的参数进行修改。
函数4:
int model::init(int argc, char ** argv) {
// call parse_args
if (parse_args(argc, argv)) {
return 1;
}
if (model_status == MODEL_STATUS_EST) {
// estimating the model from scratch
if (init_est()) {
return 1;
}
} else if (model_status == MODEL_STATUS_ESTC) {
// estimating the model from a previously estimated one
if (init_estc()) {
return 1;
}
} else if (model_status == MODEL_STATUS_INF) {
// do inference
if (init_inf()) {
return 1;
}
}
return 0;
}
如果解析有异常或者不同的模式下初始化有异常,那么久直接返回异常值,似乎都是1.。。
函数5:
int model::load_model(string model_name);
读取model是读取的tassign这个文件。让我们仔细来研读一下。
首先回忆一下tassign这个文件中包含有什么内容,首先tassign有M行,每一行对应一篇文章,对于每一篇文章来说,所有的词语都被进行切分,而且给出了对应的topic。
在读取model的阶段已经知道了诸多参数,比如说M和V,所以应该是先读取了其他文件,获取了这些参数。
对于tassign文件,一行一行地进行读取。
对于每一行,切分之后再切分,然后push进入words和topics,而且应该是将所有的单词和topics都push进去。
那么这个时候vector[int] words就是文章的代表了,而且都是数字表示的。
这时候调用传入参数为vector的构造document的函数构造pdoc,然后加入ptrndata这个文档集合中。
对于z的数组纪录来说,z纪录的是tassign,也就是这个位置的单词对应纪录着的是什么topic,所以数组大小为M*doc.size(),也就是说,其实Z不是一个标准的矩阵大小,而是一个变长的行索引。
函数6:
int model::save_model(string model_name);
存储模型,其实就是分别在不同的文件中存储不同的矩阵。
主要存储:
article->topic
topic->words
word:topic
params
top n words
函数7:
int model::save_model_tassign(string filename);
对于文档中的tassign不规则矩阵进行存储。
函数8:
int model::save_model_theta(string filename);
对于theta矩阵进行存储。
函数9:
int model::save_model_phi(string filename);
对于phi矩阵进行存储。
函数10:
int model::save_model_others(string filename);
对于参数进行存储,所以我想,读取tassign之前应该是先进行model的读取。
函数11:
int model::save_model_twords(string filename);
这个函数居然有点长。
研读一下。
首先是如果twords的个数如果大于词典的树木的大小,那么肯定是要去掉的。
下面就是一个id2word的迭代器出现了。
下面一个双层for循环做了什么呢?让我们首先来想一想如果自己要是想要完成这个功能会怎么做?我需要看的仅仅是phi这个topic->words矩阵,对于每一行,只需要找到对应的prob最大的几个word即可。
所以变成一个pair的形式就是为了能够方便的使用utils进行排序的调用。
按照降序根据prob进行排序,第一个参数是int类型,也就是对应的单词,第二个参数是对应的概率。调用quicksort降序排序之后,然后获取前面的单词的id,使用id2word进行映射,然后输出。最外面的循环是K个,所以是对于K个topic分别进行top n words的输出。
函数12:
int model::save_inf_model(string model_name);
save_inf_model大同小异。无非是map函数变了等等。只放头文件,不做解释。
函数13:
int model::save_inf_model_tassign(string filename);
函数14:
int model::save_inf_model_newtheta(string filename);
函数15:
int model::save_inf_model_newphi(string filename);
函数16:
int model::save_inf_model_others(string filename);
函数17:
int model::save_inf_model_twords(string filename);
函数18:
int model::init_est();
本函数是对于传入参数是-est的初始化操作。
首先是构建p这个临时变量的数组,可以看到:
p = new double[K];
不过这有什么用呢?往后看。
接下来申请了ptrndata,然后进行文件的读取。
在ptrndata中存储了诸多参数,所以可以对于model中的变量进行赋值。
nw我们回忆一下,是V*K的矩阵,对于这个矩阵来说,都将其初始化成0.
nd是M*K的矩阵,我们也初始化成0.
nwsum和ndsum分别是计数向量,都初始化成0。
初始化随机种子。
z是tassign的数组,所以建立一个指针数组。初始化的时候也不一般哦。这个随机步骤其实就是给文本中的词首先初始化称为一个随机的类型。对于对应的类型来说,计数也要分别进行+1操作。
看一看矩阵nw和nd。nw就是某一个单词对应的topic的个数,而且这个对应是具有全局性质的,也就意味着,这是和文档无关的,只是对于词语的所属的统计。而nd就不一样了,nd就是不关注词语,只关注类型,也就是这篇文章到底可能是属于哪一个类别的。
在document和word之间目前是没有直接的联系的,事实上最后似乎也是没有的,而是通过topic进行联系的。
theta和phi数组都是申请了空间,但是没有使用。
函数19:
int model::init_estc();
其实和前面的init_est()大同小异。
函数20:
void model::estimate();
最为核心的只有一部分:
// for all z_i
for (int m = 0; m < M; m++) {
for (int n = 0; n < ptrndata->docs[m]->length; n++) {
// (z_i = z[m][n])
// sample from p(z_i|z_-i, w)
int topic = sampling(m, n);
z[m][n] = topic;
}
}
也就是说,对于在z中的每一个单词,那么对于其进行sampling采样,sampling输入的参数是第m篇文章的第n个单词,返回值为topic,最后将tassign[m][n]设置为topic.
其他的就是存储模型之类的东西,并没有什么太大作用。
函数21:
int model::sampling(int m, int n);
首先先-3,也就是在不同的数组集合中进行-1操作,记录的w也就是对应的单词。
首先计算不同topic的概率,然后进行修改,那么每一个topic的概率由两部分乘积所得,分别是本文档属于这个topic的概率和这个单词属于这个topic的概率。这个的成绩最后决定了概率。
之后计算概率密度的累积函数。
然后通过一个随机数进行选择。
选择的结果作为topic进行返回。
函数22:
void model::compute_theta();
对于theta的计算也相对来说比较简单,也就是对于nd和ndsum的统计。nd计算的是每一篇文章的所有的单词的数目,而ndsum是一个数组,纪录的是文章中不同的单词属于不同topic的概率。
alpha我想是为了平滑作用吧。
函数23:
void model::compute_phi();
其实计算phi和计算theta差不多。theta是记录的document中的单词和单词的topic分布,对英语document和topic。
计算phi使用的是nw和nwsum。回忆一下,nw是V*K矩阵,也就是不同的单词对应于不同topic的概率,而nwsum就是不同topic的总的单词数目,那么phi也就意味着不同的topic对应的单词的分布概率。
inference不做解释,大同小异。
函数24:
int model::init_inf();
函数25:
void model::inference();
函数26:
int model::inf_sampling(int m, int n);
函数27:
void model::compute_newtheta();
函数28:
void model::compute_newphi();