• 自己动手写中文分词解析器完整教程,并对出现的问题进行探讨和解决(附完整c#代码和相关dll文件、txt文件下载)


    中文分词插件很多,当然都有各自的优缺点,近日刚接触自然语言处理这方面的,初步体验中文分词。

    首先感谢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");
            }
    
        }
    }
    WordTree.cs

    第二,构建一个支持中文的分析器,

    需要停用词表 :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;
            }
    
        }
    }
    ChineseAnalyzer.cs

    第三,进行文本切分,文本切分的基本方法:输入字符串,然后返回一个词序列,然后把词封装成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;
            }
    
        }
    }
    ChineseTokenizer.cs

    第四,编写测试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() + "
     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");
            }
        }
    }
    Program.cs

    控制器输出显示:

    可见被测试的文本为红色框里面所示。

    测试结果:

     

    问题出现了,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.rarLucene.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() + "
     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");
            }
        }
    }
    Program.cs

    测试过程中会发现如上问题,解决方法也是按上面的方式解决。

    最后,附上完整测试demo项目源码下载,Lucene.China.rar

    注:如果是下载项目源码,运行后发现有个空白窗体弹出,不要理它,关注控制台的输出。

    @_@|| 终于写完了!!! ~_~zzZ

    声明:转载请注明出处:http://www.cnblogs.com/lmei/p/3519242.html

  • 相关阅读:
    小细节--Extjs中,renderTo 和applyTo的区别
    asp.net下用js实现弹出子窗口选定值并返回
    asp.net下用js实现弹出子窗口选定值并返回
    浅析js的执行顺序
    Hdu 1010 Tempter of the Bone 分类: Translation Mode 2014-08-04 16:11 82人阅读 评论(0) 收藏
    Hdu 1009 FatMouse' Trade 分类: Translation Mode 2014-08-04 14:07 74人阅读 评论(0) 收藏
    HDU 1532 Drainage Ditches 分类: Brush Mode 2014-07-31 10:38 82人阅读 评论(0) 收藏
    Hdu 1507 Uncle Tom's Inherited Land* 分类: Brush Mode 2014-07-30 09:28 112人阅读 评论(0) 收藏
    Poj 1255 覆盖的面积 2014-07-28 12:29 116人阅读 评论(0) 收藏
    Poj 2528 Mayor's posters 分类: Brush Mode 2014-07-23 09:12 84人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/lmei/p/3519242.html
Copyright © 2020-2023  润新知