| areal's profileiamcrfBlogLists | Help |
|
November 06 crf++为什么浪费内存:都是stl惹的祸利用休假读了下crf++代码,特别是有关存储训练数据的内存结构部分。很久以前,我就通过匆匆扫过一遍的映像,认为crf++这部分的浪费非常严重。近期的阅读证实了这一观点。我还是用去年发布的0.44版。
一般来说,系统训练需要缓存两部分的内容,一部分是关于训练数据的信息,主要包括特征的表达式以及相应的频率,另外一部分用来LBFGS训练的矩阵缓存,包括lambda权值向量。后面一部分内容是不可或缺的。如果要节省内存耗费,可以考虑把第一部分内容在记录到磁盘后,仅仅留下特征的代号和对应的频率信息在内存中。
问题是,我发现, crf++没有这么做。所有的信息都保留在内存中,直到训练结束的时候一口气写入model文件。当然,crf++也不是什么都没有做。crf++在开始训练前,函数
bool TaggerImpl::shrink()
{ CHECK_FALSE(feature_index_->buildFeatures(*this)) << feature_index_->what(); std::vector <std::vector <const char *> >(x_).swap(x_);
std::vector <std::vector <Node *> >(node_).swap(node_); std::vector <unsigned short int>(answer_).swap(answer_); std::vector <unsigned short int>(result_).swap(result_); return true;
} 中,
第一句对特征进行编码,后边4句企图释放掉已经无用的训练数据缓存。taku使用了标准的swap方式来释放stl管理的内容。不幸的是,这些内存其实根本就没有得到释放。注释掉这4句后,内存的使用也不会增长。简单来说,这组内存没有得到释放。
由于对stl不熟悉---实际上,我还不会用allocator---我使用标准c替代了相关部分。
首先,我用一个简单的自己管理的char *代替了std::vector <const char *> > TaggerImpl::x_,结果期望中的内存强制释放马上获得了10%的内存减少。
然后,我把model存盘分为两次,第一次在最开始的时候存下特征的表达式及其id,第二次在训练结束的时候存下lambda向量。在第一次存盘结束后,就释放掉特征表达式的缓存。这个缓存原始的crf++使用std::map <std::string, std::pair<int, unsigned int> > EncoderFeatureIndex::dic_;来管理的。我自己用标准c++ without stl重写了一个map。存盘结束后的内存释放又获得了10%的内存减少。
现在,主要的问题是,虽然只是影响训练数据装载的速度,但是我自己写的map在插入数据的时候比stl的要慢10倍。难道我要去偷看一遍stl? 困挠中。。。
November 05 Accepted papers of SIGHAN-6仅有9篇,但是,bakeoff-4会带来28篇。。。 Stochastic Dependency Parsing Based on A* Admissible Search Mining Transliterations from Web Query Results: An Incremental Approach Use of Event Types for Temporal Relation Identification in Chinese text Analyzing Chinese Synthetic Words with Tree-based Information and a Survey on Chinese Morphologically Derived Words An Example-based Decoder for Spoken Language Machine Translation Which Performs Better on In-Vocabulary Word Segmentation: Based on Word or Character? November 02 pocket crf的二阶crf特征的结果及其讨论重复一下试验的设置。
语料:ctb segmented corpus from bakeoff-4,
训练语料 642246词
测试语料 80700词
6-标注集,b,b2,b3,m,e,s for each character in corpus
6-特征模版:C_{-1}, C_0,C_1, C_{-1}C_0,C_0C_1,C_{-1}C_1
一律用f-score度量结果
运行的cpu是core duo T7600, 2.33G, 3.2G mem
pocket crf ver 0.30 给出的结果
(a) +1阶状态转移特征 %y[-1]%y[0]: 0.949298
(b) +1,2阶状态转移特征 %y[-1]%y[0]+%y[-2]%y[-1]%y[0]: 0.949590
(c) +1,2阶状态转移特征 %y[-1]%y[0] +%y[-2]%y[0]: 0.949636
(d) +2阶状态转移特征 +%y[-2]%y[-1]%y[0]: 0.949661
注意到,按照pocket crf的结果,2阶CRF对于分词学习的贡献微乎其微。(a)运行时间大约3小时,(b),(c),(d)大约运行20-24小时。
内存使用,均为450M左右。
crf++ ver 0.44 给出的结果
(e) +1阶状态转移特征 %y[-1]%y[0]: 0.963407 运行时间,pocket crf和 crf++ 相当。内存使用,crf++在(e)上要1G。
注:
ctb语料的bakeoff-4官方结果最好成绩为0.9596
我注意到,pocket crf生成的model文件远远小于crf++,大约只有后者的1/3。难道这就是 crf++ 保持精度领先的秘诀?
pocket crf作者的blog
上面报告了msra2005/bakeoff-2语料上的结果
“ 原来是1order CRF, 训练5小时,消耗内存836M,测试结果0.959
加了一个%y[-2]%y[0]模板,训练18小时,消耗内存988M,测试结果0.961 ”
我猜测,2阶crf之所以在作者报告的这一情形导致显著的改进的原因是n-gram学习在这一语料上没有达到性能饱和,msra2005语料上bakeoff-2的最好成绩是0.964,使用我上面的6-tag+6-feature templates,性能可以轻易达到0.972.
作者的这一结果我理解为,当基线性能很低的时候,所获得改进不是实质性的。
|
|
|