First we try, then we trust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

在研究了一段时间中科院计算所张华平、刘群所开发的ICTCLAS分词系统(Free版)代码后,阅读了大量的相关资料,我开始着手将C++的ICTCLAS分词系统移植到.net平台下,并取得了较好的实验结果。这种移植并不容易,在研究了ICTCLAS分词理论的同时还要阅读C++代码实现,其中遇到了很多困惑、迷茫,也不得不重写了一小部分代码,我将在随后的文章中介绍具体实现。

目前除了最后的词性标注部分还没有完全完工外,其它部分已经接近尾声(包括初始切分、N最短路径、人名、地名的识别以及最终优化等),我们先*看看程序对以下句子的分词结果:

SharpICTCLAS程序分词结果
==== 原始句子:

王晓平在滦南大会上说的确实在理

==== 粗切分后的结果(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:末##末

==== 最终识别结果:

始##始, 馆内, 陈列, ***, 和, 邓颖超, 生前, 使用, 过, 的, 物品, 末##末,

从上面结果可以看出,切分效果还是令人满意的(当然这完全是由原有ICTCLAS的良好设计理论所决定)。

在移植的过程中,比较突出的问题包括:

1、C#支持Unicode,而原有设计是基于单字节表示

在原有设计中使用了大量的字符数组,而且一个汉字在字符数组中占两个字符位置。为了取出一个字符,必须考虑是半角字符还是全角汉字。所以随处可见类似代码:

C++代码实现取一个字符
char tchar[3];
tchar[2] = 0;

tchar[0] = sWord[k];
tchar[1] = 0;
if (sWord[k] < 0)
{
  tchar[1] = sWord[k + 1];
  k += 1;
}
k += 1;

为了判断是否是汉字,使用了“if (sWord[k] < 0) ”等手段异常繁琐。

而C#本身对Unicode有很好的支持,所以只需要string.ToCharArray()方法就可以将一个一个字符拆分开*。但需要注意的是,在C#中一个汉字的长度是1,而C++实现中一个汉字的长度是2,这要求在移植过程中要仔细对待。

2、使用正则表达式简化了部分设计

原有设计中为了判断一个字符串是否是数字需要很长的代码(例如Utility类中的IsAllNum方法),代码行数将近7~80行,而改用正则表达式后,一行代码就解决问题了。 移植后的代码使用了很多正则表达式简化类似代码。

3、字符串比较问题

由于原有设计中,对汉字大小的比较是基于CCID的(尤其是对词典库进行检索时),一个汉字的CCID计算方式如下:

CCID计算方法(C#)
//====================================================================
// 根据汉字返回对应的CC_ID
//====================================================================
public static int CC_ID(char c)
{
   byte[] b = Encoding.GetEncoding("gb2312").GetBytes(c.ToString());
   if (b.Length != 2)
      return -1;
   else
      return (Convert.ToInt32(b[0]) - 176) * 94 + (Convert.ToInt32(b[1]) - 161);
}

而C#的字符串比较没有一个适合CCID方式的字符串比较,例如在原有设计中,“声”、“生”、“现”的大小关系是:“声” < “生” < “现”,而C#中string.Compare方法不管设置为StringComparison.Ordinal、StringComparison.CurrentCulture还是StringComparison.InvariantCulture都无法达到这个结果,因此不得已设计了自己的字符串比较函数。

4、重写了部分代码

由于原有ICTCLAS系统代码的繁琐和不易理解(可以参考《天书般的ICTCLAS分词系统代码(一)》、《天书般的ICTCLAS分词系统代码(二)》) ,我重写了部分代码,主要包括:

1)重写了DynamicArray代码。新代码使用了三个类实现了原有代码,将不同功能分离开,使得代码简单易读。

2)重写了NShortPath代码。到现在我也没有完全弄明白原作者在实现NShortPath时的思路,干脆自己写吧。重写后的新代码比原有代码简化了不少,而且比较容易理解(至少我是这么认为的)。

3)Segment类中重写了GenerateWord方法,使用了链表而不是数组记录结果,并采用了管道式的处理流程,这简化了后续的合并逻辑。

4)对原有代码中部分属性、变量、字段的命名进行了调整,让其更具有实际意义。例如原有代码中nHandle和nPOS据我理解应当是一会事,所以新程序中全部使用nPOS这个命名。

5、保留了相当一部分原有代码

对于某些逻辑结构异常复杂的情况,在新代码中保留了原有的设计内容。

例如Segment类中对日期、年份、时间等的合并策略,其if条件嵌套有5层之多,为了保留原有逻辑,在移植过程中仅做了微小的调整。

另外CSpan、Unknown等类中的代码几乎没有做任何调整(其中包含了大量的计算逻辑),保留了原汁原味的内容。

 

我会在后续的文章中,分多次内容介绍SharpICTCLAS的实现手段以及对原有ICTCLAS代码改造的地方。

posted on 2007-03-07 22:38  吕震宇  阅读(10126)  评论(13编辑  收藏  举报