前文对SharpICTCLAS中的一些主要内容做了介绍,本文介绍一下SharpICTCLAS中一些其它考虑,包括事件机制以及如何使用SharpICTCLAS。
1、SharpICTCLAS中的事件
分词过程比较复杂,所以很可能有人希望能够追踪分词的过程,设置代码断点比较麻烦,因此SharpICTCLAS中提供了事件机制,可以在分词的不同阶段触发相关事件,使用者可以订阅这些事件并输出中间结果供查错使用。
事件的阶段被定义在SegmentStage枚举当中,代码如下:
{
BeginSegment, //开始分词
AtomSegment, //原子切分
GenSegGraph, //生成SegGraph
GenBiSegGraph, //生成BiSegGraph
NShortPath, //N最短路径计算
BeforeOptimize, //对N最短路径进一步整理得到的结果
OptimumSegment, //初始OptimumSegmentGraph
PersonAndPlaceRecognition, //人名与地名识别后的OptimumSegmentGraph
BiOptimumSegment, //生成BiOptimumSegmentGraph
FinishSegment //完成分词,输出结果
}
分别对应分词过程中的10个阶段。
SharpICTCLAS中还定义了一个EventArgs,里面包含了两个元素,分别用来记录该事件元素所处的分词阶段以及该阶段的相关中间结果信息。中间结果信息使用的是string类型数据,日后可以考虑采用更复杂的表示形式输出中间结果。该事件元素定义如下:
{
public SegmentStage Stage;
public string Info = "";
......
}
剩下的工作就是定义委派并发布事件了。由于分词过程主要集中在两个类中:WordSegment类与Segment类,而用户通常只需要与WordSegment类打交道,因此WordSegment类中转发了Segment类产生的事件。
委派的定义以及事件的定义如下(部分):
public delegate void SegmentEventHandler(object sender, SegmentEventArgs e);
//---定义事件
public event SegmentEventHandler OnSegmentEvent;
//---发布事件的方法
private void SendEvents(SegmentEventArgs e)
{
if (OnSegmentEvent != null)
OnSegmentEvent(this, e);
}
//---开始分词
private void OnBeginSegment(string sentence)
{
SendEvents(new SegmentEventArgs(SegmentStage.BeginSegment, sentence));
}
......
//---结束分词
private void OnFinishSegment(List<WordResult[]> m_pWordSeg)
{
StringBuilder sb = new StringBuilder();
for (int k = 0; k < m_pWordSeg.Count; k++)
{
for (int j = 0; j < m_pWordSeg[k].Length; j++)
sb.Append(string.Format("{0} /{1} ", m_pWordSeg[k][j].sWord,
Utility.GetPOSString(m_pWordSeg[k][j].nPOS)));
sb.Append("\r\n");
}
SendEvents(new SegmentEventArgs(SegmentStage.FinishSegment, sb.ToString()));
}
有了这些事件,用户可以根据需要订阅不同的事件来获取分词的中间结果,极大方便了程序调试工作。
2、SharpICTCLAS的使用
下面是一个使用SharpICTCLAS的示例代码:
using System.Collections.Generic;
using System.Text;
using SharpICTCLAS;
public class WordSegmentSample
{
private int nKind = 1; //在NShortPath方法中用来决定初步切分时分成几种结果
private WordSegment wordSegment;
//=======================================================
// 构造函数,在没有指明nKind的情况下,nKind 取 1
//=======================================================
public WordSegmentSample(string dictPath) : this(dictPath, 1) { }
//=======================================================
// 构造函数
//=======================================================
public WordSegmentSample(string dictPath, int nKind)
{
this.nKind = nKind;
this.wordSegment = new WordSegment();
//---------- 订阅分词过程中的事件 ----------
wordSegment.OnSegmentEvent += new SegmentEventHandler(this.OnSegmentEventHandler);
wordSegment.InitWordSegment(dictPath);
}
//=======================================================
// 开始分词
//=======================================================
public List<WordResult[]> Segment(string sentence)
{
return wordSegment.Segment(sentence, nKind);
}
//=======================================================
// 输出分词过程中每一步的中间结果
//=======================================================
private void OnSegmentEventHandler(object sender, SegmentEventArgs e)
{
switch (e.Stage)
{
case SegmentStage.BeginSegment:
Console.WriteLine("\r\n==== 原始句子:\r\n");
Console.WriteLine(e.Info + "\r\n");
break;
case SegmentStage.AtomSegment:
Console.WriteLine("\r\n==== 原子切分:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.GenSegGraph:
Console.WriteLine("\r\n==== 生成 segGraph:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.GenBiSegGraph:
Console.WriteLine("\r\n==== 生成 biSegGraph:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.NShortPath:
Console.WriteLine("\r\n==== NShortPath 初步切分的到的 N 个结果:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.BeforeOptimize:
Console.WriteLine("\r\n==== 经过数字、日期合并等策略处理后的 N 个结果:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.OptimumSegment:
Console.WriteLine("\r\n==== 将 N 个结果归并入OptimumSegment:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.PersonAndPlaceRecognition:
Console.WriteLine("\r\n==== 加入对姓名、翻译人名以及地名的识别:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.BiOptimumSegment:
Console.WriteLine("\r\n==== 对加入对姓名、地名的OptimumSegment生成BiOptimumSegment:\r\n");
Console.WriteLine(e.Info);
break;
case SegmentStage.FinishSegment:
Console.WriteLine("\r\n==== 最终识别结果:\r\n");
Console.WriteLine(e.Info);
break;
}
}
}
从中我们可以看出,首先添加对SharpICTCLAS命名空间的引用,然后创建WordSegment类的一个实例。如果需要拦截分词过程中的事件的话,那么可以订阅WordSegment类的OnSegmentEvent事件,上面的代码用OnSegmentEventHandler方法订阅了事件,并且输出了所有分词Stage的中间结果。
WordSegmentSample类中的 nKind 属性是在NShortPath方法中用来决定初步切分时分成几种结果。如果不特殊指明,nKind取1,用户也可以自己定义一个1~10之间的整数(超过10,系统自动取10),数越大分词准确率越高(可以参考张华平的论文),但系统执行效率会下降。
WordSegment类的InitWordSegment方法主要用来初始化各个词典,用户在这里需要提供词典所在的目录信息,系统自动到该目录下搜索所有词典。
有了WordSegmentSample类,主程序如下:
using System.Collections.Generic;
using System.Text;
using System.IO;
using SharpICTCLAS;
class Program
{
static void Main(string[] args)
{
List<WordResult[]> result;
string DictPath = Path.Combine(Environment.CurrentDirectory, "Data") +
Path.DirectorySeparatorChar;
Console.WriteLine("正在初始化字典库,请稍候...");
WordSegmentSample sample = new WordSegmentSample(DictPath, 5);
result = sample.Segment(@"王晓平在1月份滦南大会上说的确实在理");
//---------- 输出结果 ----------
//Console.WriteLine("\r\n==== 最终识别结果:\r\n");
//for (int i = 0; i < result.Count; i++)
//{
// for (int j = 0; j < result[i].Length; j++)
// Console.Write("{0} /{1} ", result[i][j].sWord, Utility.GetPOSString(result[i][j].nPOS));
// Console.WriteLine();
//}
Console.Write("按下回车键退出......");
Console.ReadLine();
}
}
内容比较简单,此处就不再多说。由于我们在WordSegmentSample中订阅了所有阶段的事件,因此程序会输出整个过程各个阶段的中间结果,也包括最终分词结果,因此上面代码中我将输出结果部分的代码注释起来了。如果没有订阅任何事件的话,可以使用注释起来的这段代码输出分词最终结果。
该程序的执行结果如下:
//==== 原始句子:
王晓平在1月份滦南大会上说的确实在理
//==== 原子切分:
始##始, 王, 晓, 平, 在, 1, 月, 份, 滦, 南, 大, 会, 上, 说, 的, 确, 实, 在, 理, 末##末,
//==== 生成 segGraph:
row: 0, col: 1, eWeight: 329805.00, nPOS: 1, sWord:始##始
row: 1, col: 2, eWeight: 218.00, nPOS: 0, sWord:王
row: 2, col: 3, eWeight: 9.00, nPOS: 0, 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: 0.00, nPOS: -27904, sWord:未##数
row: 6, col: 7, eWeight: 1900.00, nPOS: 0, sWord:月
row: 6, col: 8, eWeight: 11.00, nPOS: 28160, sWord:月份
row: 7, col: 8, eWeight: 1234.00, nPOS: 0, sWord:份
row: 8, col: 9, eWeight: 1.00, nPOS: 27136, sWord:滦
row: 9, col: 10, eWeight: 813.00, nPOS: 0, sWord:南
row: 10, col: 11, eWeight: 14536.00, nPOS: 0, sWord:大
row: 10, col: 12, eWeight: 1333.00, nPOS: 28160, sWord:大会
row: 11, col: 12, eWeight: 6136.00, nPOS: 0, sWord:会
row: 11, col: 13, eWeight: 469.00, nPOS: 0, sWord:会上
row: 12, col: 13, eWeight: 23706.00, nPOS: 0, sWord:上
row: 13, col: 14, eWeight: 17649.00, nPOS: 0, sWord:说
row: 14, col: 15, eWeight: 358156.00, nPOS: 0, sWord:的
row: 14, col: 16, eWeight: 210.00, nPOS: 25600, sWord:的确
row: 15, col: 16, eWeight: 181.00, nPOS: 0, sWord:确
row: 15, col: 17, eWeight: 361.00, nPOS: 0, sWord:确实
row: 16, col: 17, eWeight: 357.00, nPOS: 0, sWord:实
row: 16, col: 18, eWeight: 295.00, nPOS: 0, sWord:实在
row: 17, col: 18, eWeight: 78484.00, nPOS: 0, sWord:在
row: 17, col: 19, eWeight: 3.00, nPOS: 24832, sWord:在理
row: 18, col: 19, eWeight: 129.00, nPOS: 0, sWord:理
row: 19, col: 20, eWeight:2079997.00, nPOS: 4, sWord:末##末
//==== 生成 biSegGraph:
row: 0, col: 1, eWeight: 4.18, nPOS: 1, sWord:始##始@王
row: 1, col: 2, eWeight: 11.46, nPOS: 0, sWord:王@晓
row: 2, col: 3, eWeight: 13.93, nPOS: 0, sWord:晓@平
row: 3, col: 4, eWeight: 11.25, nPOS: 0, sWord:平@在
row: 4, col: 5, eWeight: 3.74, nPOS: 0, sWord:在@未##数
row: 5, col: 6, eWeight: -27898.79, nPOS: -27904, sWord:未##数@月
row: 5, col: 7, eWeight: -27898.75, nPOS: -27904, sWord:未##数@月份
row: 6, col: 8, eWeight: 9.33, nPOS: 0, sWord:月@份
row: 7, col: 9, eWeight: 13.83, nPOS: 28160, sWord:月份@滦
row: 8, col: 9, eWeight: 9.76, nPOS: 0, sWord:份@滦
row: 9, col: 10, eWeight: 14.46, nPOS: 27136, sWord:滦@南
row: 10, col: 11, eWeight: 5.19, nPOS: 0, sWord:南@大
row: 10, col: 12, eWeight: 10.17, nPOS: 0, sWord:南@大会
row: 11, col: 13, eWeight: 7.30, nPOS: 0, sWord:大@会
row: 11, col: 14, eWeight: 7.30, nPOS: 0, sWord:大@会上
row: 12, col: 15, eWeight: 2.11, nPOS: 28160, sWord:大会@上
row: 13, col: 15, eWeight: 8.16, nPOS: 0, sWord:会@上
row: 14, col: 16, eWeight: 3.42, nPOS: 0, sWord:会上@说
row: 15, col: 16, eWeight: 4.07, nPOS: 0, sWord:上@说
row: 16, col: 17, eWeight: 4.05, nPOS: 0, sWord:说@的
row: 16, col: 18, eWeight: 7.11, nPOS: 0, sWord:说@的确
row: 17, col: 19, eWeight: 4.10, nPOS: 0, sWord:的@确
row: 17, col: 20, eWeight: 4.10, nPOS: 0, sWord:的@确实
row: 18, col: 21, eWeight: 11.49, nPOS: 25600, sWord:的确@实
row: 19, col: 21, eWeight: 11.63, nPOS: 0, sWord:确@实
row: 18, col: 22, eWeight: 11.49, nPOS: 25600, sWord:的确@实在
row: 19, col: 22, eWeight: 11.63, nPOS: 0, sWord:确@实在
row: 20, col: 23, eWeight: 3.92, nPOS: 0, sWord:确实@在
row: 21, col: 23, eWeight: 10.98, nPOS: 0, sWord:实@在
row: 20, col: 24, eWeight: 10.97, nPOS: 0, sWord:确实@在理
row: 21, col: 24, eWeight: 10.98, nPOS: 0, sWord:实@在理
row: 22, col: 25, eWeight: 11.17, nPOS: 0, sWord:实在@理
row: 23, col: 25, eWeight: 5.62, nPOS: 0, sWord:在@理
row: 24, col: 26, eWeight: 14.30, nPOS: 24832, sWord:在理@末##末
row: 25, col: 26, eWeight: 11.95, nPOS: 0, sWord:理@末##末
//==== NShortPath 初步切分的到的 N 个结果:
始##始, 王, 晓, 平, 在, 1, 月份, 滦, 南, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 1, 月份, 滦, 南, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 1, 月份, 滦, 南, 大, 会上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 1, 月份, 滦, 南, 大会, 上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 1, 月, 份, 滦, 南, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
//==== 经过数字、日期合并等策略处理后的 N 个结果:
始##始, 王, 晓, 平, 在, 1月份, 滦, 南, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 1月份, 滦, 南, 大会, 上, 说, 的, 确实, 在, 理, 末##末,
始##始, 王, 晓, 平, 在, 1月份, 滦, 南, 大, 会上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 1月份, 滦, 南, 大会, 上, 说, 的, 确实, 在理, 末##末,
始##始, 王, 晓, 平, 在, 1月, 份, 滦, 南, 大, 会上, 说, 的, 确实, 在, 理, 末##末,
//==== 加入对姓名、翻译人名以及地名的识别:
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: 7, eWeight: 0.00, nPOS: -29696, sWord:未##时
row: 5, col: 8, eWeight: 0.00, nPOS: -29696, sWord:未##时
row: 7, col: 8, eWeight: 1234.00, nPOS: 0, sWord:份
row: 8, col: 9, eWeight: 1.00, nPOS: 27136, sWord:滦
row: 8, col: 10, eWeight: 20.37, nPOS: -28275, sWord:未##地
row: 9, col: 10, eWeight: 813.00, nPOS: 0, sWord:南
row: 10, col: 11, eWeight: 14536.00, nPOS: 0, sWord:大
row: 10, col: 12, eWeight: 1333.00, nPOS: 28160, sWord:大会
row: 11, col: 13, eWeight: 469.00, nPOS: 0, sWord:会上
row: 12, col: 13, eWeight: 23706.00, nPOS: -27904, sWord:未##数
row: 13, col: 14, eWeight: 17649.00, nPOS: 0, sWord:说
row: 14, col: 15, eWeight: 358156.00, nPOS: 0, sWord:的
row: 15, col: 17, eWeight: 361.00, nPOS: 0, sWord:确实
row: 17, col: 18, eWeight: 78484.00, nPOS: 0, sWord:在
row: 17, col: 19, eWeight: 3.00, nPOS: 24832, sWord:在理
row: 18, col: 19, eWeight: 129.00, nPOS: 0, sWord:理
row: 19, col: 20, eWeight:2079997.00, nPOS: 4, sWord:末##末
//==== 生成 biSegGraph:
row: 0, col: 1, eWeight: 4.18, nPOS: 1, sWord:始##始@王
row: 0, col: 2, eWeight: 2.88, nPOS: 1, sWord:始##始@未##人
row: 1, col: 3, eWeight: 11.46, nPOS: 0, sWord:王@晓
row: 1, col: 4, eWeight: 3.88, nPOS: 0, sWord:王@未##人
row: 3, col: 5, eWeight: 13.93, nPOS: 0, sWord:晓@平
row: 2, col: 6, eWeight: -28270.43, nPOS: -28274, sWord:未##人@在
row: 4, col: 6, eWeight: -28270.43, nPOS: -28274, sWord:未##人@在
row: 5, col: 6, eWeight: 11.25, nPOS: 0, sWord:平@在
row: 6, col: 7, eWeight: 4.01, nPOS: 0, sWord:在@未##时
row: 6, col: 8, eWeight: 4.01, nPOS: 0, sWord:在@未##时
row: 7, col: 9, eWeight: -29690.16, nPOS: -29696, sWord:未##时@份
row: 8, col: 10, eWeight: -29690.16, nPOS: -29696, sWord:未##时@滦
row: 9, col: 10, eWeight: 9.76, nPOS: 0, sWord:份@滦
row: 8, col: 11, eWeight: -29690.17, nPOS: -29696, sWord:未##时@未##地
row: 9, col: 11, eWeight: 9.76, nPOS: 0, sWord:份@未##地
row: 10, col: 12, eWeight: 14.46, nPOS: 27136, sWord:滦@南
row: 11, col: 13, eWeight: -28267.95, nPOS: -28275, sWord:未##地@大
row: 12, col: 13, eWeight: 5.19, nPOS: 0, sWord:南@大
row: 11, col: 14, eWeight: -28266.85, nPOS: -28275, sWord:未##地@大会
row: 12, col: 14, eWeight: 10.17, nPOS: 0, sWord:南@大会
row: 13, col: 15, eWeight: 7.30, nPOS: 0, sWord:大@会上
row: 14, col: 16, eWeight: 4.81, nPOS: 28160, sWord:大会@未##数
row: 15, col: 17, eWeight: 3.42, nPOS: 0, sWord:会上@说
row: 16, col: 17, eWeight: -27898.75, nPOS: -27904, sWord:未##数@说
row: 17, col: 18, eWeight: 4.05, nPOS: 0, sWord:说@的
row: 18, col: 19, eWeight: 4.10, nPOS: 0, sWord:的@确实
row: 19, col: 20, eWeight: 3.92, nPOS: 0, sWord:确实@在
row: 19, col: 21, eWeight: 10.97, nPOS: 0, sWord:确实@在理
row: 20, col: 22, eWeight: 5.62, nPOS: 0, sWord:在@理
row: 21, col: 23, eWeight: 14.30, nPOS: 24832, sWord:在理@末##末
row: 22, col: 23, eWeight: 11.95, nPOS: 0, sWord:理@末##末
//==== 最终识别结果:
王晓平 /nr 在 /p 1月份 /t 滦南 /ns 大会 /n 上 /v 说 /v 的 /uj 确实 /ad 在 /p 理 /n
非常高兴在这最后一篇文章写完之时得到了张华平老师的授权。我会尽可能快的将SharpICTCLAS源文件放上来供大家测试使用的。