First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3315 评论 :: 358 引用

在研究了一段时间中科院计算所张华平、刘群所开发的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 吕震宇 阅读(5848) 评论(13)  编辑 收藏

评论

#1楼 2007-03-07 23:02 西门子乌      
Good job.
 回复 引用 查看   

#2楼 2007-03-07 23:33 JesseZhao      
Very good
 回复 引用 查看   

#3楼 2007-03-08 01:02 neoragex2002      
看了下张华平在2001年中文信息学报上发的paper,很有意思,呵呵,期待吕老师的作品
 回复 引用 查看   

#4楼 2007-03-08 16:33 冬冬      
功德无量!!
一直想做这件事情,现在等现成的,呵呵。加油了!
 回复 引用 查看   

#5楼 2007-03-15 16:17 sinboy[未注册用户]
半个月时间,就移植到C#平台上了,了不起
 回复 引用   

#6楼 2007-03-24 09:13 李赟[未注册用户]
不知您是否可以把CCStringCompare函数贴出来,晚辈初学C#不久,有点细节还是处理不好,谢谢。

或者发到我的邮箱中:liyun935cn@yahoo.com.cn
 回复 引用   

#7楼 2007-06-05 11:26 Tiger[未注册用户]
1.0中保存词典有一处错误,
for (int i = j; i < item.nCount; i++)
if (item.WordItems[j].nFrequency != -1)
SaveWordItem(writer, item.WordItems[i]);

其中item.WordItems[j].nFrequency 应为
item.WordItems[i].nFrequency
 回复 引用   

#8楼 2007-06-05 11:29 Tiger[未注册用户]
1.0中另一处错误:(在DelItem方法中)

if (modifyTable == null)
{
modifyTable = new ModifyTableItem[Predefine.CC_NUM];
}
...modifyTable[x]没有初始化.
 回复 引用   

#9楼 2007-07-27 11:09 Gang[未注册用户]
不错的移植工作,真正的ReWriter
 回复 引用   

#10楼 2007-08-13 17:42 fanrsh
吕老师是真的强@!!!!
我代表全国.net程序员感谢你
 回复 引用   

#11楼 2008-09-03 16:36 suyuan[未注册用户]
对汉字大小的比较是基于CCID 请问什么是CCID
 回复 引用   

#12楼 2011-02-13 16:00 张峻      
请问现在 移植完工了么??
 回复 引用 查看