基于.NET的分词软件设计与实现V1.0--总体思路及算法实现
首先介绍一下这个分词软件的总体思路。
查阅了网上的一些相关资料,普遍采用的都是“正向最大匹配算法”和“逆向最大匹配算法”,不了解的朋友先来这里看下两个算法的基本思想:
正向最大匹配算法基本思想是:首先在词库里查找文本是否存在,如果存在,直接提取出来,而如果不存在,则删除文本的最后一个字,检查是否是单字,若是输出此字并将短语减去此字,若不是则继续判断看字库是否存在这个词,如此反复循环,直到输出一个词,这样就可以将一个短语分成词语的组合了。
而逆向最大匹配算法自然是相反的,如果发现待分词文本在词典中不存在,那么删除文本的第一个字,进行再次匹配操作。
实验证明:正向最大匹配对歧义识别比较差,分词的准确性不高,所以我的这个分词软件使用了“逆向最大匹配算法”的思想。
---------------------------------------------------------------------------------------------------------------------------
但如果将拿到任意的一段文本作为一个整体直接使用逆向最大匹配算法进行拆分,那么复杂度可想而知,效率肯定是惨不忍睹的,那么应该如何进行分词呢,这里我采用了“标点断句法”,即用正则表达式匹配文本,找到文本中的标点,然后以此为标志进行断句,把一段很长的文本分割成一段一段的小文本,然后再使用逆向最大匹配算法进行分词操作。
好了,有了思路就要付诸实践了:
1、首先,我从网上下载了“标点符号词典”。截图如下:
当然,这是中文的标点自然好说,但由于是使用正则表达式进行匹配,所以对于英文的标点就不能这么对待了,这里我进行了转义处理:
这样可以防止这些英文标点被正则表达式解析为具有特殊意义的字符。
2、利用正则表达式断句:将待分词文本与标点符号词典进行匹配,并以匹配成功的位置为标识进行断句。
1 /// <summary>
2 /// 创建标点符号组成的字符串
3 /// </summary>
4 /// <param name="dictPath">标点符号词典存放路径</param>
5 string GetPunctuationDictionary(string dictPath)
6 {
7 StringBuilder strBuilder = new StringBuilder();
8 foreach (string s in File.ReadAllLines(dictPath))
9 {
10 strBuilder.Append(s);
11 }
12 return strBuilder.ToString();
13 }
1 /// <summary>
2 /// 标点符号字符串
3 /// </summary>
4 string _splitters;
5
6 /// <summary>
7 /// 获得字符串中匹配的标点符号的集合(去除重复项)
8 /// </summary>
9 List<string> GetMatchedSpiltters()
10 {
11 List<string> matchedSplitters = new List<string>();
12 Regex regex = new Regex("[" + _splitters + "]");
13 foreach (var item in regex.Matches(_inputStr))
14 {
15 if (!matchedSplitters.Contains(item.ToString()))
16 matchedSplitters.Add(item.ToString());
17 }
18 return matchedSplitters;
19 }
如“示例:这是我做的分词软件,谢谢使用!”这句话,经过上述的匹配操作后,得到的matchedSplitters即为:,和!组成的集合。
3、以正则表达式匹配的标点位置为标志,去除文本标点。
1 /// <summary>
2 /// 去除待分词文本中的标点符号并分割成独立的句子(去除空串)
3 /// </summary>
4 List<string> GetStripedPunctuationString()
5 {
6 List<string> matchedSplitters = GetMatchedSpiltters();
7 foreach (string str in matchedSplitters)
8 {
9 _inputStr = _inputStr.Replace(str, " ");
10 }
11 return _inputStr.Split(new string[] { " " },StringSplitOptions.RemoveEmptyEntries).ToList();
12 }
如“示例:这是我做的分词软件,谢谢使用!”经过上述操作就变为了“示例 这是我做的分词软件 谢谢使用 ”
4、首先判断待分词文本是否在分词词典中存在,若存在,则保存在待输出集合中,否则采用逆向最大匹配算法对待分词文本进行分词操作。
1 /// <summary>
2 /// 获得分词结果
3 /// </summary>
4 /// <param name="str">待分词文本</param>
5 public string GetResult()
6 {
7 List<string> strList = GetStripedPunctuationString();
8 List<string> splitterResult = new List<string>();
9 StringBuilder strBuilder = new StringBuilder();
10
11 foreach (string str in strList)
12 {
13 if (_dict.Keys.Contains(str))
14 {
15 //为了输出结果易读,这里换行输出
16 strBuilder.Append(str + "\r\n");
17 }
18 else
19 {
20 splitterResult = Spiltter(str);
21 for (int q = splitterResult.Count - 1, p = q; p >= 0; p--)
22 {
23 strBuilder.Append(splitterResult[p]);
24 }
25 strBuilder.Append("\r\n");
26 }
27 }
28 return strBuilder.ToString();
29 }
如这里的“示例”显然是一个词,所以_dict.Keys.Contains(str)为true,直接进入输出结果集,但第二个子句——“这是我做的分词软件”显然不是个词,所以就进行逆向最大匹配算法操作,删除第一个字“这”,然后得到“是我做的分词软件”,发现依然不是一个词,于是继续进行删除首字操作,直到“软件”,与词典中的词成功匹配,所以此时将“软件”保存到待输出集合中,并将其中原子句中删除,然后对“这是我做的分词”进行分词操作,得到“分词”,然后对“这是我做的”进行分词操作,这里删到“的”字发现依然没有在词典中出现任何匹配,但其已是个单字,不可再分,此时initStr.Length == 1为true,所以这里将其保存到待输出集合中,而对于“这是我做”四个字也是一样,所以进行分词操作后,将得到的结果集进行拼接得到的strBuilder。ToString()结果为:“软件\分词\的\做\我\是\这”,但在真正输出结果集时,应采用逆序输出的方式,所以得到的最终结果为“这\是\我\做\的\分词\软件\”。
总结
程序流程图:
分词类设计:
分词效果:
分词不准确等问题在后续文章中介绍改进方案。
后续更新请参看本系列目录:基于.NET的分词软件设计与实现--索引及说明
出处:http://www.cnblogs.com/RockyMyx/
本文版权归作者和博客园共有,欢迎转载,但请在文章明显位置给出原文连接,否则保留追究法律责任的权利。