中文分词插件很多,当然都有各自的优缺点,近日刚接触自然语言处理这方面的,初步体验中文分词。
首先感谢harry.guo楼主提供的学习资源,博文链接http://www.cnblogs.com/harryguo/archive/2007/09/26/906965.html,在此基础上进行深入学习和探讨。
接下来进入正文。。。大牛路过别喷,菜鸟有空练练手~~完整的项目源码下载在文章末尾~~
因为是在Lucene.Net下进行中文分词解析器编写的,新建项目Lucene.China,然后将Lucene.Net.dll添加到项目中。(附:资源Lucene.Net.rar)
与英文不同,中文词之间没有空格,于是对于中文分词就比英文复杂了些。
第一,构建树形词库,在所建项目目录下的bin/Debug文件下新建一个文件夹data(如果文件夹已经存在,则不用新建),然后在data文件夹中加入sDict.txt。
(附:资源sDict.rar,解压后得到是sDict.txt文档,放入指定文件夹中)
构建树形词库实现代码如下:
using System; using System.Collections.Generic; using System.Text; using System.Collections; using System.Text.RegularExpressions; using System.IO; namespace Lucene.China { /// <summary> /// 词库类,生成树形词库 /// </summary> public class WordTree { /// <summary> /// 字典文件的路径 /// </summary> //private static string DictPath = Application.StartupPath + "\\data\\sDict.txt"; private static string DictPath = Environment.CurrentDirectory + "\\data\\sDict.txt"; /// <summary> /// 缓存字典的对象 /// </summary> public static Hashtable chartable = new Hashtable(); /// <summary> /// 字典文件读取的状态 /// </summary> private static bool DictLoaded = false; /// <summary> /// 读取字典文件所用的时间 /// </summary> public static double DictLoad_Span = 0; /// <summary> /// 正则表达式 /// </summary> public string strChinese = "[\u4e00-\u9fa5]"; public string strNumber = "[0-9]"; public string strEnglish = "[a-zA-Z]"; /// <summary> /// 获取字符类型 /// </summary> /// <param name="Char"></param> /// <returns> /// 0: 中文,1:英文,2:数字 ///</returns> public int GetCharType(string Char) { if (new Regex(strChinese).IsMatch(Char)) return 0; if (new Regex(strEnglish).IsMatch(Char)) return 1; if (new Regex(strNumber).IsMatch(Char)) return 2; return -1; } /// <summary> /// 读取字典文件 /// </summary> public void LoadDict() { if (DictLoaded) return; BuidDictTree(); DictLoaded = true; return; } /// <summary> /// 建立树 /// </summary> private void BuidDictTree() { long dt_s = DateTime.Now.Ticks; string char_s; StreamReader reader = new StreamReader(DictPath, System.Text.Encoding.UTF8); string word = reader.ReadLine(); while (word != null && word.Trim() != "") { Hashtable t_chartable = chartable; for (int i = 0; i < word.Length; i++) { char_s = word.Substring(i, 1); if (!t_chartable.Contains(char_s)) { t_chartable.Add(char_s, new Hashtable()); } t_chartable = (Hashtable)t_chartable[char_s]; } word = reader.ReadLine(); } reader.Close(); DictLoad_Span = (double)(DateTime.Now.Ticks - dt_s) / (1000 * 10000); System.Console.Out.WriteLine("读取字典文件所用的时间: " + DictLoad_Span + "s"); } } }
第二,构建一个支持中文的分析器,
需要停用词表 :String[] CHINESE_ENGLISH_STOP_WORDS,下面代码只是构造了个简单的停用词表。 (附资源:相对完整的停用词表stopwords.rar)
具体实现代码如下:
using System; using System.Collections.Generic; using System.Text; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; namespace Lucene.China { /**//// <summary> /// /// </summary> public class ChineseAnalyzer:Analyzer { //private System.Collections.Hashtable stopSet; public static readonly System.String[] CHINESE_ENGLISH_STOP_WORDS = new System.String[] { "a", "an", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "no", "not", "of", "on", "or", "s", "such", "t", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with", "我", "我们" }; /**//// <summary>Constructs a {@link StandardTokenizer} filtered by a {@link /// StandardFilter}, a {@link LowerCaseFilter} and a {@link StopFilter}. /// </summary> public override TokenStream TokenStream(System.String fieldName, System.IO.TextReader reader) { TokenStream result = new ChineseTokenizer(reader); result = new StandardFilter(result); result = new LowerCaseFilter(result); result = new StopFilter(result, CHINESE_ENGLISH_STOP_WORDS); return result; } } }
第三,进行文本切分,文本切分的基本方法:输入字符串,然后返回一个词序列,然后把词封装成Token对象。
当然,要判断将要进行切分的词是中文、英文、数字还是其他。
实现源码如下:
using System; using System.Collections.Generic; using System.Text; using Lucene.Net.Analysis; using System.Collections; using System.Text.RegularExpressions; using System.IO; namespace Lucene.China { class ChineseTokenizer : Tokenizer { private int offset = 0, bufferIndex = 0, dataLen = 0;//偏移量,当前字符的位置,字符长度 private int start;//开始位置 /// <summary> /// 存在字符内容 /// </summary> private string text; /// <summary> /// 切词所花费的时间 /// </summary> public double TextSeg_Span = 0; /// <summary>Constructs a tokenizer for this Reader. </summary> public ChineseTokenizer(System.IO.TextReader reader) { this.input = reader; text = input.ReadToEnd(); dataLen = text.Length; } /// <summary>进行切词,返回数据流中下一个token或者数据流为空时返回null /// </summary> /// public override Token Next() { Token token = null; WordTree tree = new WordTree(); //读取词库 tree.LoadDict(); //初始化词库,为树形 Hashtable t_chartable = WordTree.chartable; string ReWord = ""; string char_s; start = offset; bufferIndex = start; while (true) { //开始位置超过字符长度退出循环 if (start >= dataLen) { break; } //获取一个词 char_s = text.Substring(start, 1); if (string.IsNullOrEmpty(char_s.Trim())) { start++; continue; } //字符不在字典中 if (!t_chartable.Contains(char_s)) { if (ReWord == "") { int j = start + 1; switch (tree.GetCharType(char_s)) { case 0://中文单词 ReWord += char_s; break; case 1://英文单词 j = start + 1; while (j < dataLen) { if (tree.GetCharType(text.Substring(j, 1)) != 1) break; j++; } ReWord += text.Substring(start, j - offset); break; case 2://数字 j = start + 1; while (j < dataLen) { if (tree.GetCharType(text.Substring(j, 1)) != 2) break; j++; } ReWord += text.Substring(start, j - offset); break; default: ReWord += char_s;//其他字符单词 break; } offset = j;//设置取下一个词的开始位置 } else { offset = start;//设置取下一个词的开始位置 } //返回token对象 return new Token(ReWord, bufferIndex, bufferIndex + ReWord.Length - 1); } //字符在字典中 ReWord += char_s; //取得属于当前字符的词典树 t_chartable = (Hashtable)t_chartable[char_s]; //设置下一循环取下一个词的开始位置 start++; if (start == dataLen) { offset = dataLen; return new Token(ReWord, bufferIndex, bufferIndex + ReWord.Length - 1); } } return token; } } }
第四,编写测试demo的main函数,代码如下:
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using Analyzer = Lucene.Net.Analysis.Analyzer; using SimpleAnalyzer = Lucene.Net.Analysis.SimpleAnalyzer; using StandardAnalyzer = Lucene.Net.Analysis.Standard.StandardAnalyzer; using Token = Lucene.Net.Analysis.Token; using TokenStream = Lucene.Net.Analysis.TokenStream; namespace Lucene.China { class Program { [STAThread] public static void Main(System.String[] args) { try { // Test("*在1949年建立,从此开始了新中国的伟大篇章。长春市长春节致词", true); Test("hello world, a better day, never give up.", true); /*Test("一直在酝酿 new 一直在盼望 爸爸和妈妈唯一的理想 二月第一天 一九八一年 " + "我第一次对他们眨了眨眼 等待快点过去多少个明天" + "希望这个宝贝快快长大一点一点 身体要健康所有的事情都如所愿 Baby长大以后就是小轩" + "I will find my way I want a different way " + "I‘ll change the wind and rain There be a brand new day" + "小时候受伤有人心痛失落有人安慰 现在遇到困难自己就要学会面对", true); */ } catch (System.Exception e) { System.Console.Out.WriteLine(" caught a " + e.GetType() + "\n with message: " + e.Message + e.ToString()); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } internal static void Test(System.String text, bool verbose) { System.Console.Out.WriteLine(" Tokenizing string: " + text); Test(new System.IO.StringReader(text), verbose, text.Length); } internal static void Test(System.IO.TextReader reader, bool verbose, long bytes) { //Analyzer analyzer = new StandardAnalyzer(); Analyzer analyzer = new Lucene.China.ChineseAnalyzer(); TokenStream stream = analyzer.TokenStream(null, reader); System.DateTime start = System.DateTime.Now; int count = 0; for (Token t = stream.Next(); t != null; t = stream.Next()) { if (verbose) { System.Console.Out.WriteLine("Token=" + t.ToString()); } count++; } System.DateTime end = System.DateTime.Now; long time = end.Ticks - start.Ticks; System.Console.Out.WriteLine(time + " milliseconds to extract " + count + " tokens"); System.Console.Out.WriteLine((time * 1000.0) / count + " microseconds/token"); System.Console.Out.WriteLine((bytes * 1000.0 * 60.0 * 60.0) / (time * 1000000.0) + " megabytes/hour"); } } }
控制器输出显示:
可见被测试的文本为红色框里面所示。
测试结果:
问题出现了,never被拆分成n,ever。于是测试多几次,发现只要是n开头的单词,n都会被拆开,如nnno,会变成n,n,n,o。
那么为什么会这样呢?
回想一下,之前我们构建了字典树。其实一般情况下我们会觉得说中文分析器所需要构建的字典树,当然就是纯文字,但是其实不是这样的。
打开sDict.txt文件,会发现下面这些词语:
发现问题了吧!!!其实是包含字母的,所以英文单词的n总会被单独切分出来。
那么应该怎么解决呢??
解决方法,就是在sDict.txt文件中加入以n开头的单词表,这样就可以完美切分啦!!
测试一下吧!在文件中加入单词never,如下:
测试结果:
可见单词never已经完美切除。
接下来再来看另外一个在测试过程出现的问题,
测试文本如下:‘开’和‘始’中间有空格,且这段文本最后没有标点和空格。
测试结果如下:
依然完美切分,而且没有报错提示。
然后继续测试英文文本,如下:依然留空格,然后文本末尾没有空格跟标点。
测试结果:出现异常
问题产生的原因是,英文是一个或多个单词相连的,如never,在判断第一个字母是属于英文的时候,会自动继续判断下一个,
当刚好这个单词是最后一个的时候,它依然会去查找下一个是否还是属于英文单词,这样文本要是以英文结束,且后面没有空格跟标点的话,
它就会出现超出索引的错误。
出现问题的代码是下面这句:在ChineseTokenizer.cs中
注:由于数字的切分和英文的切分采用的方法相同,所以也会出现同样问题
解决方法:就是在测试的文档的最后加上一个空格。因为空格不会影响到切分,所以只要把要切分的文本都进行事先处理,在文本末尾加多个空格给它,这样就不会出现上面异常。
懒人大礼包^_^
如果你不想进行上面那些多步骤,也是可以的。
第一,还是要把sDict.txt文件放到项目目录/bin/Debug/data文件夾中;
第二,下载Lucene.Fanswo.rar和Lucene.Net.rar,然后将Lucene.Fanswo.dll和Lucene.Net.dll添加到项目中;
注:Lucene.Fanswo.dll实现的功能跟上面写的一样,直接调用就行。
第三,编写Programs.cs测试代码
关键语句:
using Lucene.Fanswo;
创建支持中文的分析器,
Analyzer analyzer = new Lucene.Fanswo.ChineseAnalyzer();
完整代码如下:
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using Lucene.Fanswo; using Analyzer = Lucene.Net.Analysis.Analyzer; using SimpleAnalyzer = Lucene.Net.Analysis.SimpleAnalyzer; using StandardAnalyzer = Lucene.Net.Analysis.Standard.StandardAnalyzer; using Token = Lucene.Net.Analysis.Token; using TokenStream = Lucene.Net.Analysis.TokenStream; namespace Lucene.China { class Program { [STAThread] public static void Main(System.String[] args) { try { //Test("*在1949年建立,从此开 始了新中国的伟大篇章。长春市长春节致词", true); Test("hello world, a better day, never give up", true); /*Test("一直在酝酿 new 一直在盼望 爸爸和妈妈唯一的理想 二月第一天 一九八一年 " + "我第一次对他们眨了眨眼 等待快点过去多少个明天" + "希望这个宝贝快快长大一点一点 身体要健康所有的事情都如所愿 Baby长大以后就是小轩" + "I will find my way I want a different way " + "I‘ll change the wind and rain There be a brand new day" + "小时候受伤有人心痛失落有人安慰 现在遇到困难自己就要学会面对", true); */ } catch (System.Exception e) { System.Console.Out.WriteLine(" caught a " + e.GetType() + "\n with message: " + e.Message + e.ToString()); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } internal static void Test(System.String text, bool verbose) { System.Console.Out.WriteLine(" Tokenizing string: " + text); Test(new System.IO.StringReader(text), verbose, text.Length); } internal static void Test(System.IO.TextReader reader, bool verbose, long bytes) { //Analyzer analyzer = new StandardAnalyzer(); Analyzer analyzer = new Lucene.Fanswo.ChineseAnalyzer(); TokenStream stream = analyzer.TokenStream(null, reader); System.DateTime start = System.DateTime.Now; int count = 0; for (Token t = stream.Next(); t != null; t = stream.Next()) { if (verbose) { System.Console.Out.WriteLine("Token=" + t.ToString()); } count++; } System.DateTime end = System.DateTime.Now; long time = end.Ticks - start.Ticks; System.Console.Out.WriteLine(time + " milliseconds to extract " + count + " tokens"); System.Console.Out.WriteLine((time * 1000.0) / count + " microseconds/token"); System.Console.Out.WriteLine((bytes * 1000.0 * 60.0 * 60.0) / (time * 1000000.0) + " megabytes/hour"); } } }
测试过程中会发现如上问题,解决方法也是按上面的方式解决。
最后,附上完整测试demo项目源码下载,Lucene.China.rar
注:如果是下载项目源码,运行后发现有个空白窗体弹出,不要理它,关注控制台的输出。
@_@|| 终于写完了!!! ~_~zzZ
声明:转载请注明出处:http://www.cnblogs.com/lmei/p/3519242.html