在研究了一段时间中科院计算所张华平、刘群所开发的ICTCLAS分词系统(Free版)代码后,阅读了大量的相关资料,我开始着手将C++的ICTCLAS分词系统移植到.net平台下,并取得了较好的实验结果。这种移植并不容易,在研究了ICTCLAS分词理论的同时还要阅读C++代码实现,其中遇到了很多困惑、迷茫,也不得不重写了一小部分代码,我将在随后的文章中介绍具体实现。
目前除了最后的词性标注部分还没有完全完工外,其它部分已经接近尾声(包括初始切分、N最短路径、人名、地名的识别以及最终优化等),我们先来看看程序对以下句子的分词结果:
==== 原始句子:
王晓平在滦南大会上说的确实在理
==== 粗切分后的结果(N个结果):
始##始, 王, 晓, 平, 在, 滦, 南, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 滦, 南, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 滦, 南, 大, 会上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 滦, 南, 大会, 上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 滦, 南, 大, 会, 上, 说, 的, 确实, 在, 理, 末##末,
==== 加入对姓名、翻译人名以及地名的识别:
row: 0, col: 1, eWeight: 329805.00, nPOS: 1, sWord:始##始
row: 1, col: 2, eWeight: 218.00, nPOS: 0, sWord:王
row: 1, col: 4, eWeight: 10.86, nPOS: -28274, sWord:未##人
row: 2, col: 3, eWeight: 9.00, nPOS: 0, sWord:晓
row: 2, col: 4, eWeight: 13.27, nPOS: -28274, sWord:未##人
row: 3, col: 4, eWeight: 271.00, nPOS: 0, sWord:平
row: 4, col: 5, eWeight: 78484.00, nPOS: 0, sWord:在
row: 5, col: 6, eWeight: 1.00, nPOS: 27136, sWord:滦
row: 5, col: 7, eWeight: 20.37, nPOS: -28275, sWord:未##地
row: 6, col: 7, eWeight: 813.00, nPOS: 0, sWord:南
row: 7, col: 8, eWeight: 14536.00, nPOS: 0, sWord:大
row: 7, col: 9, eWeight: 1333.00, nPOS: 28160, sWord:大会
row: 8, col: 9, eWeight: 6136.00, nPOS: 0, sWord:会
row: 8, col: 10, eWeight: 469.00, nPOS: 0, sWord:会上
row: 9, col: 10, eWeight: 23706.00, nPOS: -27904, sWord:上
row: 10, col: 11, eWeight: 17649.00, nPOS: 0, sWord:说
row: 11, col: 12, eWeight: 358156.00, nPOS: 0, sWord:的
row: 12, col: 14, eWeight: 361.00, nPOS: 0, sWord:确实
row: 14, col: 15, eWeight: 78484.00, nPOS: 0, sWord:在
row: 14, col: 16, eWeight: 3.00, nPOS: 24832, sWord:在理
row: 15, col: 16, eWeight: 129.00, nPOS: 0, sWord:理
row: 16, col: 17, eWeight:2079997.00, nPOS: 4, sWord:末##末
==== 最终识别结果:
始##始, 王晓平, 在, 滦南, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
---------------------------------------------------
==== 原始句子:
馆内陈列周恩来和邓颖超生前使用过的物品
==== 粗切分后的结果(N个结果):
始##始, 馆内, 陈列, 周恩来, 和, 邓, 颖, 超, 生前, 使用, 过, 的, 物品, 末##末,
始##始, 馆内, 陈列, 周恩来, 和, 邓, 颖, 超生, 前, 使用, 过, 的, 物品, 末##末,
始##始, 馆内, 陈列, 周恩来, 和, 邓, 颖, 超, 生前, 使用, 过, 的, 物, 品, 末##末,
始##始, 馆内, 陈列, 周恩来, 和, 邓, 颖, 超生, 前, 使, 用, 过, 的, 物品, 末##末,
始##始, 馆内, 陈列, 周恩来, 和, 邓, 颖, 超, 生, 前, 使用, 过, 的, 物品, 末##末,
==== 加入对姓名、翻译人名以及地名的识别:
row: 0, col: 1, eWeight: 329805.00, nPOS: 1, sWord:始##始
row: 1, col: 3, eWeight: 24.00, nPOS: 0, sWord:馆内
row: 3, col: 5, eWeight: 70.00, nPOS: 0, sWord:陈列
row: 5, col: 8, eWeight: 1990.00, nPOS: 28274, sWord:周恩来
row: 8, col: 9, eWeight: 72562.00, nPOS: 0, sWord:和
row: 9, col: 10, eWeight: 90.00, nPOS: 28274, sWord:邓
row: 9, col: 12, eWeight: 15.93, nPOS: -28274, sWord:未##人
row: 10, col: 11, eWeight: 2.00, nPOS: 28274, sWord:颖
row: 11, col: 12, eWeight: 200.00, nPOS: 0, sWord:超
row: 11, col: 13, eWeight: 4.00, nPOS: 0, sWord:超生
row: 12, col: 13, eWeight: 532.00, nPOS: 0, sWord:生
row: 12, col: 14, eWeight: 175.00, nPOS: 29696, sWord:生前
row: 13, col: 14, eWeight: 5107.00, nPOS: 0, sWord:前
row: 14, col: 15, eWeight: 8224.00, nPOS: 0, sWord:使
row: 14, col: 16, eWeight: 1876.00, nPOS: 0, sWord:使用
row: 15, col: 16, eWeight: 5300.00, nPOS: 0, sWord:用
row: 16, col: 17, eWeight: 5090.00, nPOS: 0, sWord:过
row: 17, col: 18, eWeight: 358156.00, nPOS: 0, sWord:的
row: 18, col: 19, eWeight: 200.00, nPOS: 0, sWord:物
row: 18, col: 20, eWeight: 189.00, nPOS: 28160, sWord:物品
row: 19, col: 20, eWeight: 75.00, nPOS: 0, sWord:品
row: 20, col: 21, eWeight:2079997.00, nPOS: 4, sWord:末##末
==== 最终识别结果:
始##始, 馆内, 陈列, 周恩来, 和, 邓颖超, 生前, 使用, 过, 的, 物品, 末##末,
而C#本身对Unicode有很好的支持,所以只需要string.ToCharArray()方法就可以将一个一个字符拆分开来。但需要注意的是,在C#中一个汉字的长度是1,而C++实现中一个汉字的长度是2,这要求在移植过程中要仔细对待。
原有设计中为了判断一个字符串是否是数字需要很长的代码(例如Utility类中的IsAllNum方法),代码行数将近7~80行,而改用正则表达式后,一行代码就解决问题了。 移植后的代码使用了很多正则表达式简化类似代码。
而C#的字符串比较没有一个适合CCID方式的字符串比较,例如在原有设计中,“声”、“生”、“现”的大小关系是:“声” < “生” < “现”,而C#中string.Compare方法不管设置为StringComparison.Ordinal、StringComparison.CurrentCulture还是StringComparison.InvariantCulture都无法达到这个结果,因此不得已设计了自己的字符串比较函数。
2)重写了NShortPath代码。到现在我也没有完全弄明白原作者在实现NShortPath时的思路,干脆自己写吧。重写后的新代码比原有代码简化了不少,而且比较容易理解(至少我是这么认为的)。
4)对原有代码中部分属性、变量、字段的命名进行了调整,让其更具有实际意义。例如原有代码中nHandle和nPOS据我理解应当是一会事,所以新程序中全部使用nPOS这个命名。