这两天在做高级数据结构(ADS)课程的大作业,内容是做一个推荐系统。我们选取的是C. Wang and D. Blei. Collaborative topic modeling for recommending scientific articles.一文中描述的模型。文章的作者C. Wang在个人主页上发布了论文中使用系统的部分代码,代码使用C++语言完成的。而我们的系统是用Java搭建的,因此我们需要将C++代码移植到Java中。
本以为这是一个简单的工作,只需要简单的改写即可,但没想到第一次移植的版本跑出来的结果的准确率只有作者程序的1/10。于是我意识到程序改错了。经过漫长的人工静态debug,终于把程序改对了。
下面总结一下移植的一点体会:
1.C++程序中使用了GSL(GNU Scientific Libarary)。通过Google,Java下有一个叫JGSL的库,但是下载下来后,才发现JGSL只实现了GSL的一小部分功能,而且都不是我们这个项目所需要的。考虑到程序中主要需要的是GSL的线性代数模块,我找到了JAMA这个项目。然而,由于GSL出于效率的考虑,再结合C++语言的种种特性,大量使用了指针和引用,因此我们需要在JAMA中找到合适的方法来处理。例如,对于
gsl_blas_dger(a_minus_b, &v.vector, &v.vector, A);
这样的代码,我们可以改写为:
A.plusEquals(v.transpose().times(v).timesEquals(a_minus_b));
可以看到,一方面需要理解函数的意义,另一方面,也需要根据库与库之间的差异进行改写,在数学表达式中,向量通常都指列向量,而在JAMA中,是没有向量的,这时只能通过1×n的矩阵来模拟向量,这样就会产生行向量和列向量的问题,改写时需要格外注意。
2.C++中传递引用,可以修改变量的值。而Java中虽然也传递引用,但却与C++有着非常大的不同。例如,我们可以改变对象的值,但不能改变对象本身。这就给移植带来了一定的困难。
3.对对象的隐示修改。
const c_document doc = c->m_docs[j];
likelihood += doc_inference(doc, &theta_v.vector, log_beta, phi, gamma, word_ss, true);
optimize_simplex(gamma, &v.vector, param->lambda_v, &theta_v.vector);
这里,我们看到在optimize_simplex
函数中,传递了两个地址,但实际上,通过查看参数声明,第一个指针实际上是const
类型,所以只有第二个指针可能会在代码中被修改。
经过阅读代码,我们发现,这里theta_v
确实会被修改,因此,在改写为Java代码时,还需要加上额外的一行:
likelihood += doc_inference(corpus.getDoc(j), theta_v, log_beta, phi, gamma, word_ss, true);
optimize_simplex(gamma, v, lambda_v, theta_v);
theta.setMatrix(j, j, 0, num_factors-1, theta_v);
这样才能保证对theta_v
的更新能正确的更新到theta
中。
总的来说,在改写中,一定要特别注意引用类型和指针类型的传递,搞清楚哪些会有可能修改传入对象的值,哪些不会,这样才能有针对的改写为Java代码。