我的最新分词进展和接口设计~
以前写了个关于MMSEG实现的,那个写的很烂,本身我也没有实现完全。最近在工作中对原有系统的分词进行了代码重构和算法优化。并且针对电子商务网站搜索的特征,给出对于离线没有识别出歧义的片段进行全切分,而且与IK的全切分不同,同时我们还要保证路径的完整,即我们不需要找到所有的路径,但是我们要确定所有的全切分可能路径出现的词都会被切出,而IK的切法只是给出所有有意义的词,并不考虑完整路径问题,比如 “中华人民” 按照IK的切法,“中华” “华人” “人民”,而我们希望的结果是“中华,中,华人,人民,民” 即“中华,人民”“中,华人,民”全部两个可能路径上词的词包括可能不在字典中的单字为了完整的拼接路径都要切出来。其实电子商务网站的搜索由于是单品索引导向的相对全网搜索引擎数据量小太多了,对于分词的精度并没有那么高的要求,切错了也没那么严重,但是要和搜索时候的切分保持一致即可,因此索引的切分更看重召回率,当然了切单字召回率最高了,折中一下,我觉得这种全切分方式还是比较可行的,因为还是有大量不存在歧义的片段的,整体上比切单字索引量会小很多,绝大多数的词是有意义的,减轻后续排序的负担,同时能够保证高召回,没有高深的算法和数学,其它的会继续在当前框架下学习研究实现:)
只是展示下效果,注意字典里面有“女”,“ 刀”字作为一个有意义的单字词:)
喜剧情人海岸
瑞士军刀
女士运动鞋
最大切分:
喜剧 情人 海岸
瑞士军刀
女士 运动鞋
全切分(仿照IK,尽量不切出无意义的单字):
喜剧 剧情 情人 人海 海岸
瑞士 瑞士军刀 军刀 刀
女 女士 运动 运动鞋 鞋
全切分(完全版本,完全补全路径):
喜剧 喜 剧情 情人 情 人海 人 海岸 岸
瑞士 瑞士军刀 军刀 军 刀
女 女士 士 运动 运动鞋 鞋
我是在服务器上跑的,对于大量商品的标题做实验,完全补全路径的全切分可以达到8M/S,当然速度快慢还和歧义字典有关系,另外商品标题一般不会太长,因为有大量离线处理好歧义的地方不需要再全切分,预计没有歧义字典的话速度也应该能达到几M/S的。
我对自己的分词算法系统的架构很满意,我已经越来越喜欢基于策略设计模式,喜欢C++ template (这是底层算法库不能缺少的利器,避免代码重复,并且编译其间决定代码完全不影响速度),同时effective c++里面的 traits手法也是经常需要用到的。
//使用示例
template<typename Segmentor> void test_seg(Segmentor& segmentor, ifstream& ifs, ofstream& ofs) { string line; // vector<segment::Term> out; //直接你可以选择分词结果的存储方式,避免后续再处理的时间代价 vector<string> out; while (getline(ifs, line)) { segmentor.maxSegment(line, out); Prange(out); segmentor.omniSegment_fullAmbiPath(line, out); Prange(out, cout, "/"); } }
//接口设计
template<typename SegmentorImpl, template<typename Ter> class ResultFunc_> class SegmentorT { //第一个参数是你要用的具体分词器(具体的分词器其实还有一个可配置模板参数即是否英文数字分开),第二个参数是我打算怎么处理分词过程中的临时结果,存为term还是直接存string? public: SegmentorT(const string& datrie_file, const string& encoder_file) : m_seg(datrie_file, encoder_file) { } /** * 最大切分,不考虑离线岐义 */ template<typename T> void maxSegment_withoutAmbi(const string& key, T& out) { segment(key, out, MaxSegType()); //before we use MaxSeg } /** * 模仿IK的全切分,尽量不提出无意义单字 */ template<typename T> void omniSegment_IKAmbi(const string& key, T& out) { segment(key, out, OmniSegWithAmbiIK()); //traits手法 I like it:) } /** * 全切分并且完全补全路径 */ template<typename T> void omniSegment_fullAmbiPath(const string& key, T& out) { segment(key, out, OmniSegWithAmbiFullPath()); } template<typename Ter, typename SegType> void segment(const string& key, vector<Ter>& out, SegType seg_type) { int len = key.size(); if (len > kStackAllocMax) { short * code = new short[len + KEYCNTMAX]; if (!code) return; m_seg.segment(key, out, code, ResultFunc_<Ter > (key), seg_type); delete [] code; } else { short code[len + KEYCNTMAX]; m_seg.segment(key, out, code, ResultFunc_<Ter > (key), seg_type); } } private: SegmentorImpl m_seg; const static int kStackAllocMax = 1024; }; #define SEGMENTOR_TYPE(Name, SegImpl, PuncSeg, ResultFunc)\ typedef SegmentorT< SegImpl<PuncSeg>, ResultFunc > Name #define SEG_TYPE(Name, PuncSeg)\ typedef SegmentorT< SegmentorImpl_Basic<PuncSeg>, ResultFunc > Name SEG_TYPE(Segmentor, PuncSegmentor_AlNumNoSeg); //我希望英文数字不被分开 SEG_TYPE(Segmentor_AlNumSeg, PuncSegmentor_AlNumSeg); //我希望英文数字被分开