Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 512|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-25 16:44:45 | 显示全部楼层 |阅读模式

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

    首先感谢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() + "\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");
            }
        }
    }
    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() + "\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");
            }
        }
    }
    Program.cs

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

     

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

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

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

     

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

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-18 12:22 , Processed in 0.138214 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表