DynamicArray与NShortPath是ICTCLAS中的基础类,本人在完成了基础改造工作后,就着手开始对Segment分词进行移植与改造。SharpICTCLAS中的改造主要体现在以下几方面:
原有ICTCLAS中使用了SegGraph与Segment两个类完成分词过程,SegGraph类负责完成原子分词与segGraph的生成,而Segment类负责BiSegGraph的生成和NShortPath优化,而最终的人名、地名识别以及Optimum优化又分散在了Segment类与WordSegment类中。
SharpICTCLAS将原有SegGraph类与Segment合二为一,因为它们所作的工作仅仅是分词中的几个步骤而已。而WordSegment类中基本保留了原有内容,因为这个类更多的做一些外围工作。
由于某些过程的调用根本不需要建立对象,这些过程仅仅完成例行计算而已,因此将这些方法声明为静态方法更合适,何况静态方法的调用效率比实例方法高。因此本人在将ICTCLAS移植到C#平台上时,将尽可能的方法定义成静态方法。
//==== 原始句子:
他在1月份大会上说的确实在理
//==== 原子切分:
始##始, 他, 在, 1, 月, 份, 大, 会, 上, 说, 的, 确, 实, 在, 理, 末##末,
//==== 生成 segGraph:
row: 0, col: 1, eWeight: 329805.00, nPOS: 1, sWord:始##始
row: 1, col: 2, eWeight: 19823.00, nPOS: 0, sWord:他
row: 2, col: 3, eWeight: 78484.00, nPOS: 0, sWord:在
row: 3, col: 4, eWeight: 0.00, nPOS: -27904, sWord:未##数
row: 4, col: 5, eWeight: 1900.00, nPOS: 0, sWord:月
row: 4, col: 6, eWeight: 11.00, nPOS: 28160, sWord:月份
row: 5, col: 6, eWeight: 1234.00, nPOS: 0, sWord:份
row: 6, col: 7, eWeight: 14536.00, nPOS: 0, sWord:大
row: 6, col: 8, eWeight: 1333.00, nPOS: 28160, sWord:大会
row: 7, col: 8, eWeight: 6136.00, nPOS: 0, sWord:会
row: 7, col: 9, eWeight: 469.00, nPOS: 0, sWord:会上
row: 8, col: 9, eWeight: 23706.00, nPOS: 0, sWord:上
row: 9, col: 10, eWeight: 17649.00, nPOS: 0, sWord:说
row: 10, col: 11, eWeight: 358156.00, nPOS: 0, sWord:的
row: 10, col: 12, eWeight: 210.00, nPOS: 25600, sWord:的确
row: 11, col: 12, eWeight: 181.00, nPOS: 0, sWord:确
row: 11, col: 13, eWeight: 361.00, nPOS: 0, sWord:确实
row: 12, col: 13, eWeight: 357.00, nPOS: 0, sWord:实
row: 12, col: 14, eWeight: 295.00, nPOS: 0, sWord:实在
row: 13, col: 14, eWeight: 78484.00, nPOS: 0, sWord:在
row: 13, col: 15, eWeight: 3.00, nPOS: 24832, sWord:在理
row: 14, col: 15, eWeight: 129.00, nPOS: 0, sWord:理
row: 15, col: 16, eWeight:2079997.00, nPOS: 4, sWord:末##末
//==== 生成 biSegGraph:
row: 0, col: 1, eWeight: 3.37, nPOS: 1, sWord:始##始@他
row: 1, col: 2, eWeight: 3.37, nPOS: 0, sWord:他@在
row: 2, col: 3, eWeight: 3.74, nPOS: 0, sWord:在@未##数
row: 3, col: 4, eWeight: -27898.79, nPOS: -27904, sWord:未##数@月
row: 3, col: 5, eWeight: -27898.75, nPOS: -27904, sWord:未##数@月份
row: 4, col: 6, eWeight: 9.33, nPOS: 0, sWord:月@份
row: 5, col: 7, eWeight: 13.83, nPOS: 28160, sWord:月份@大
row: 6, col: 7, eWeight: 9.76, nPOS: 0, sWord:份@大
row: 5, col: 8, eWeight: 13.83, nPOS: 28160, sWord:月份@大会
row: 6, col: 8, eWeight: 9.76, nPOS: 0, sWord:份@大会
row: 7, col: 9, eWeight: 7.30, nPOS: 0, sWord:大@会
row: 7, col: 10, eWeight: 7.30, nPOS: 0, sWord:大@会上
row: 8, col: 11, eWeight: 2.11, nPOS: 28160, sWord:大会@上
row: 9, col: 11, eWeight: 8.16, nPOS: 0, sWord:会@上
row: 10, col: 12, eWeight: 3.42, nPOS: 0, sWord:会上@说
row: 11, col: 12, eWeight: 4.07, nPOS: 0, sWord:上@说
row: 12, col: 13, eWeight: 4.05, nPOS: 0, sWord:说@的
row: 12, col: 14, eWeight: 7.11, nPOS: 0, sWord:说@的确
row: 13, col: 15, eWeight: 4.10, nPOS: 0, sWord:的@确
row: 13, col: 16, eWeight: 4.10, nPOS: 0, sWord:的@确实
row: 14, col: 17, eWeight: 11.49, nPOS: 25600, sWord:的确@实
row: 15, col: 17, eWeight: 11.63, nPOS: 0, sWord:确@实
row: 14, col: 18, eWeight: 11.49, nPOS: 25600, sWord:的确@实在
row: 15, col: 18, eWeight: 11.63, nPOS: 0, sWord:确@实在
row: 16, col: 19, eWeight: 3.92, nPOS: 0, sWord:确实@在
row: 17, col: 19, eWeight: 10.98, nPOS: 0, sWord:实@在
row: 16, col: 20, eWeight: 10.97, nPOS: 0, sWord:确实@在理
row: 17, col: 20, eWeight: 10.98, nPOS: 0, sWord:实@在理
row: 18, col: 21, eWeight: 11.17, nPOS: 0, sWord:实在@理
row: 19, col: 21, eWeight: 5.62, nPOS: 0, sWord:在@理
row: 20, col: 22, eWeight: 14.30, nPOS: 24832, sWord:在理@末##末
row: 21, col: 22, eWeight: 11.95, nPOS: 0, sWord:理@末##末
//==== NShortPath 初步切分的到的 N 个结果:
始##始, 他, 在, 1, 月份, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1, 月份, 大会, 上, 说, 的, 确实, 在理, 末##末,
始##始, 他, 在, 1, 月份, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1, 月, 份, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1, 月份, 大, 会上, 说, 的, 确实, 在理, 末##末,
//==== 经过数字、日期合并等策略处理后的 N 个结果:
始##始, 他, 在, 1月份, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1月份, 大会, 上, 说, 的, 确实, 在理, 末##末,
始##始, 他, 在, 1月份, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1月, 份, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 他, 在, 1月份, 大, 会上, 说, 的, 确实, 在理, 末##末,
原子分词看起来应当是程序中最简单的部分,无非是将汉字逐一分开。但是也是最值得改进的地方。SharpICTCLAS目前仍然沿用了原有ICTCLAS的算法并做了微小调整。但我对于 这种原子分词方法不太满意,如果有机会,可以考虑使用一系列正则表达式将某些“原子”词单独摘出来。比如“甲子”、“乙亥”等年份信息属于原子信息,还有URL、Email等都可以预先进行原子识别,这可以大大简化后续工作。因此日后可以考虑这方面的处理。
ICTCLAS与SharpICTCLAS都通过NShortPath计算最短路径并将结果以数组的方式进行输出,数组仅仅记录了分词的位置,我们还需要通过一些后续处理手段将这些数组转换成“分词”结果。
本人在SharpICTCLAS的改造过程中发现在这里数组的表述方式给后续工作带来了很大的困难(可以考虑一下,让你合并链表中两个相邻结点简单呢还是数组中两个相邻结点简单?),所以我决定在SharpICTCLAS中将BiPath转换为链表结构供后续使用,实践证明简化了不少工作。
数字、日期等合并以及拆分策略的实施是在GenerateWord方法中实现的,原有ICTCLAS中,该方法是一个超级庞大的方法,里面有不下6、7层的if嵌套、while嵌套等,分析其内部功能的工作异常复杂。经过一番研究后,我将其中的主要功能部分提取出来,改用了“管道”方式进行处理,简化了代码复杂度。但对于部分逻辑结构异常复杂的日期时间识别功能,SharpICTCLAS中仍然保留了绝大多数原始内容。
SharpICTCLAS中,对这段超长代码进行了功能剥离,采用一种“流水线”式的处理流程,不同工作部分负责处理不同功能,而将处理结果节节传递(很象设计模式中的职责链模式),这样使得整体结构变的清晰起来。SharpICTCLAS中GenerateWord方法定义如下:
从中可以看到linkedArray作为“绣球”在多个处理流程中被传递和加工,最终输出相应的结果。只是CheckDateElement方法内容涉及到的东西太多,因此目前看来其实现仍有些臃肿,日后可以进一步进行功能的剥离。