自然语言处理

实现ICTCLAS到C#平台的移植

字号+ 作者:hanyufeng 来源: 2015-08-22 15:55:34 我要评论( ) 阅读:

实现ICTCLAS到C 平台的移植...

在研究了一段时间中科院计算所张华平、刘群所开发的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代码改造的地方。

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 天书般的ICTCLAS分词系统代码(一)

    天书般的ICTCLAS分词系统...

  • 天书般的ICTCLAS分词系统代码(二)

    天书般的ICTCLAS分词系统...

  • SharpICTCLAS分词系统简介(1)读取词典库

    SharpICTCLAS分词系统简...

  • ICTCLAS(五)

    ICTCLAS(五)

  • ICTCLAS(六)

    ICTCLAS(六)