前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移。因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3.6.0 ,PanGu分词也是对应Lucene3.6.0版本的。不过好在Lucene.net 已经有了Core 2.0版本(4.8.0 bate版),而PanGu分词,目前有人正在做,貌似已经做完,只是还没有测试~,Lucene升级的改变我都会加粗表示。

Lucene.net 4.8.0

PanGu分词(可以直接使用的)

JIEba分词(可以直接使用的)

Lucene.net 4.8.0 和之前的Lucene.net 3.6.0 改动还是相当多的,这里对自己开发过程遇到的问题,做一个记录吧,希望可以帮到和我一样需要升级Lucene.net的人。我也是第一次接触Lucene ,也希望可以帮助初学Lucene的同学。

目录

一,PanGu分词与JIEba分词

1.中文分词工具

Lucene的自带分词工具对中文分词的效果很是不好。因此在做中文的搜索引擎的时候,我们需要用额外的中文分词组件。这里可以总结一下中文分词工具有哪些,在下面这个衔接中,有对很多中文分词工具的性能测试:

可惜我们看不到PanGu分词的性能,在PanGu分词的官网我们可以看到:Core Duo 1.8 GHz 下单线程 分词速度为 390K 字符每秒,2线程分词速度为 690K 字符每秒。在上面的排行榜中属于中等吧。但由于我做的是基于.net的搜索引擎,所以我只找到了IK分词器,PanGu分词器,JIEba分词器的.net core2.0 版本。

1.1 PanGu分词 .net core 版

这是PanGu分词.net core 2.0版本的迁移项目:

这是一个没有迁移完全的项目,在使用过程中遇到了一些问题,前面的目录中记录过。我修改了一些bug,下面的是修改过后的可以直接使用的PanGu分词.net core2.0版本:

我提交了一个Pull Request ,作者还没有合并。我已经用了一段时间,很稳定。

1.2 JIEba分词 .net core 版

JIEba分词的.net core 版本迁移项目:

但是这是.net core1.0的版本,拿过来也不能直接给Lucene使用,所以我升级到了2.0并且做了一个接口,让其支持Lucene,经过测试可以稳定的进行分词和高亮。当然在其中也遇到了一些问题,在下文中会详细阐述。这是改过之后的Lucene版:

1.3 IK分词 .net core 版

在Nuget中可以搜索到(IKNetAnalyzer)

在GitHub中   https://github.com/stanzhai/IKAnalyzer.NET  显示正在开发中。由于一些原因,我并没有使用IK分词。所以也就没有细看了。

2.PanGu分词和JIEba分词的对比

Lucene和PanGu分词搭配,已经是Lucene.net 的经典搭配,但是PanGu分词已经很久没有更新,PanGu分词的字典也是很久以前维护的字典。在网上可以找到很多Lucene和PanGu分词搭配的例子。在PanGu分词和JIEba分词对比中,我选择了JIEba分词。因为我的搜索引擎一直是使用PanGu分词,然后却时常出现有些比较新的冷的词,无法被分词,导致搜索效果很差。究其原因,是PanGu分词的字典不够大,但是人工维护字典很烦。当然PanGu分词有新词录入的功能,我一直打开这个功能的开关:

MatchOptions m = new MatchOptions();

m.UnknownWordIdentify = true;

然而并没有改善。后来我使用了JIEba分词测试分词效果,发现JIEba分词使用搜索引擎模式,和PanGu分词打开多元分词功能开关时的分词效果如下:

测试样例:小明硕士毕业于中国科学院计算所,后在日本京都大学深造

结巴分词(搜索引擎模式):小明/ 硕士/ 毕业/ 于/ 中国/ 科学/ 学院/ 科学院/ 中国科学院/ 计算/ 计算所/ ,/ 后/ 在/ 日本/ 京都/ 大学/ 日本京都大学/ 深造

盘古分词(开启多元分词开关): 小 明 硕士 毕业 于 中国科学院 计算所 后 在 日本 京都 大学 深造

显然PanGu分词并没有细粒度分词,这是导致有些搜索召回率很低的原因。

这里就不对PanGu分词,和JIEba分词的具体分词方法进行比较了。本篇博文的还是主要讲解Lucene和JIEba分词

二,JIEba分词支持Lucene

在上面的JIEba分词.net core版本中,JIEba分词只是将给到的一个字符串进行分词,然后反馈给你分词信息,分词信息也只是一个一个字符串。显然这是无法接入到Lucene中。那么如何把一个分词工具成功的接入到Lucene中呢?

1.建立Analyzer类

所有要接入Lucene中的分词工具,都要有一个继承Lucene.Net.Analyzer的类,在这个类:JIEbaAnalyzer中,必须要覆写TokenStreamComponents函数,因为Lucene正是通过这个函数获取分词器分词之后的TokenStream(一些列分词信息的集合)我们可以在这个函数中给tokenStream中注入我们想要得到的属性,在Lucene.net 4.8.0中分词的概念已经是一些列分词属性的组合

public classJieBaAnalyzer

:Analyzer

{publicTokenizerMode mode;publicJieBaAnalyzer(TokenizerMode Mode)

:base()

{this.mode =Mode;

}protected override TokenStreamComponents CreateComponents(stringfiledName,TextReader reader)

{var tokenizer = newJieBaTokenizer(reader,mode);var tokenstream = (TokenStream)newLowerCaseFilter(Lucene.Net.Util.LuceneVersion.LUCENE_48, tokenizer);

tokenstream.AddAttribute();

tokenstream.AddAttribute();return newTokenStreamComponents(tokenizer, tokenstream);

}

}

}

这里可以看到,我只使用了ICharTermAttribute 和IOffsetAttribute 也就是分词的内容属性和位置属性。这里的Mode要提一下,这是JIEba分词的特性,JIEba分词提供了三种模式:

精确模式,试图将句子最精确地切开,适合文本分析;

全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;

搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

这里的Model只有Default和Search两种,一般的,写入索引的时候使用Search模式,查询的时候使用Default模式

上面的JieBaTokenizer类正是我们接下来要定义的类

1.建立Tokenizer类

继承Lucene.Net.Tokenizer 。Tokenizer 是正真将大串文本分成一系列分词的类,在Tokenizer类中,我们必须要覆写 Reset()函数,IncrementToken()函数,上面的Analyzer类中:

var tokenstream = (TokenStream)newLowerCaseFilter(Lucene.Net.Util.LuceneVersion.LUCENE_48, tokenizer);

tokenizer是生产tokenstream。实际上Reset()函数是将文本进行分词,IncrementToken()是遍历分词的信息,然后将分词的信息注入的tokenstream,这样就得到我们想要的分词流。在Tokenizer类中我们调用JIEba分词的Segment实例,对文本进行分词。再将获得分词包装,遍历。

public classJieBaTokenizer

: Tokenizer

{private static object _LockObj = new object();private static bool _Inited = false;private System.Collections.Generic.List _WordList = new List();private string_InputText;private bool _OriginalResult = false;privateICharTermAttribute termAtt;privateIOffsetAttribute offsetAtt;privateIPositionIncrementAttribute posIncrAtt;privateITypeAttribute typeAtt;private List stopWords = new List();private string stopUrl="./stopwords.txt";privateJiebaSegmenter segmenter;private System.Collections.Generic.IEnumeratoriter;private int start =0;privateTokenizerMode mode;publicJieBaTokenizer(TextReader input,TokenizerMode Mode)

:base(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY,input)

{

segmenter= newJiebaSegmenter();

mode=Mode;

StreamReader rd=File.OpenText(stopUrl);string s = "";while((s=rd.ReadLine())!=null)

{

stopWords.Add(s);

}

Init();

}private voidInit()

{

termAtt= AddAttribute();

offsetAtt= AddAttribute();

posIncrAtt= AddAttribute();

typeAtt= AddAttribute();

}private stringReadToEnd(TextReader input)

{returninput.ReadToEnd();

}public sealed overrideBoolean IncrementToken()

{

ClearAttributes();

Lucene.Net.Analysis.Token word=Next();if(word!=null)

{var buffer =word.ToString();

termAtt.SetEmpty().Append(buffer);

offsetAtt.SetOffset(CorrectOffset(word.StartOffset),CorrectOffset(word.EndOffset));

typeAtt.Type=word.Type;return true;

}

End();this.Dispose();return false;

}publicLucene.Net.Analysis.Token Next()

{int length = 0;bool res =iter.MoveNext();

Lucene.Net.Analysis.Token token;if(res)

{

JiebaNet.Segmenter.Token word=iter.Current;

token= newLucene.Net.Analysis.Token(word.Word, word.StartIndex,word.EndIndex);//Console.WriteLine("xxxxxxxxxxxxxxxx分词:"+word.Word+"xxxxxxxxxxx起始位置:"+word.StartIndex+"xxxxxxxxxx结束位置"+word.EndIndex);

start +=length;returntoken;

}else

return null;

}public override voidReset()

{base.Reset();

_InputText= ReadToEnd(base.m_input);

RemoveStopWords(segmenter.Tokenize(_InputText,mode));

start= 0;

iter=_WordList.GetEnumerator();

}public void RemoveStopWords(System.Collections.Generic.IEnumerablewords)

{

_WordList.Clear();foreach(var x inwords)

{if(stopWords.IndexOf(x.Word)==-1)

{

_WordList.Add(x);

}

}

}

}

一开始我写的Tokenizer类并不是这样,因为遇到了一些问题,才逐渐改成上面的样子,下面就说下自己遇到的问题。

3.问题和改进

3.1 JIEba CutForSearch

一开始在Reset函数中,我使用的是JIEba分词介绍的CutForSearch函数,CutForSearch的到是List ,所以位置属性OffsetAttribute得我自己来写:

publicLucene.Net.Analysis.Token Next()

{int length = 0;bool res =iter.MoveNext();

Lucene.Net.Analysis.Token token;if(res)

{

JiebaNet.Segmenter.Token word=iter.Current;

token= newLucene.Net.Analysis.Token(word.Word, word.StartIndex,word.EndIndex);

start+=length;returntoken;

}else

return null;

}

自己定义了start,根据每个分词的长度,很容易算出来每个分词的位置。但是我忘了CutForSearch是一个细粒度模式,会有“中国模式”,“中国”,“模式”同时存在,这样的写法就是错的了,如果是Cut就对了。分词的位置信息错误,带来的就是高亮的错误,因为高亮需要知道分词的正确的起始和结束位置。具体的错误就是:

at System.String.Substring(Int32 startIndex, Int32 length)

at Lucene.Net.Search.VectorHighlight.BaseFragmentsBuilder.MakeFragment(StringBuilder buffer, Int32[] index, Field[] values, WeightedFragInfo fragInfo, String[] preTags, String[] postTags, IEncoder encoder)in C:\BuildAgent\work\b1b63ca15b99dddb\src\Lucene.Net.Highlighter\VectorHighlight\BaseFragmentsBuilder.cs:line 195at Lucene.Net.Search.VectorHighlight.BaseFragmentsBuilder.CreateFragments(IndexReader reader, Int32 docId, String fieldName, FieldFragList fieldFragList, Int32 maxNumFragments, String[] preTags, String[] postTags, IEncoder encoder)in C:\BuildAgent\work\b1b63ca15b99dddb\src\Lucene.Net.Highlighter\VectorHighlight\BaseFragmentsBuilder.cs:line 146at Lucene.Net.Search.VectorHighlight.BaseFragmentsBuilder.CreateFragments(IndexReader reader, Int32 docId, String fieldName, FieldFragList fieldFragList, Int32 maxNumFragments)in C:\BuildAgent\work\b1b63ca15b99dddb\src\Lucene.Net.Highlighter\VectorHighlight\BaseFragmentsBuilder.cs:line 99

当你使用Lucene的时候出现这样的错误,大多数都是你的分词位置属性出错。

后来才发现JIEba分词提供了Tokenize()函数,专门提供了分词以及分词的位置信息,我很欣慰的用了Tokenize()函数,结果还是报错,一样的报错,当我尝试着加上CorrectOffset()函数的时候:

offsetAtt.SetOffset(CorrectOffset(word.StartOffset),CorrectOffset(word.EndOffset));

虽然不报错了,但是高亮的效果总是有偏差,总而言之换了Tokenize函数,使用CorrectOffset函数,都无法使分词的位置信息变准确。于是查看JIEba分词的源码。

Tokenize函数:

public IEnumerable Tokenize(string text, TokenizerMode mode = TokenizerMode.Default, bool hmm = true)

{var result = new List();var start = 0;if (mode ==TokenizerMode.Default)

{foreach (var w inCut(text, hmm: hmm))

{var width =w.Length;

result.Add(new Token(w, start, start +width));

start+=width;

}

}else{foreach (var w inCut(text, hmm: hmm))

{var width =w.Length;if (width > 2)

{for (var i = 0; i < width - 1; i++)

{var gram2 = w.Substring(i, 2);if(WordDict.ContainsWord(gram2))

{

result.Add(new Token(gram2, start + i, start + i + 2));

}

}

}if (width > 3)

{for (var i = 0; i < width - 2; i++)

{var gram3 = w.Substring(i, 3);if(WordDict.ContainsWord(gram3))

{

result.Add(new Token(gram3, start + i, start + i + 3));

}

}

}

result.Add(new Token(w, start, start +width));

start+=width;

}

}returnresult;

}

Cut函数:

public IEnumerable Cut(string text, bool cutAll = false, bool hmm = true)

{var reHan =RegexChineseDefault;var reSkip =RegexSkipDefault;

Func> cutMethod = null;if(cutAll)

{

reHan=RegexChineseCutAll;

reSkip=RegexSkipCutAll;

}if(cutAll)

{

cutMethod=CutAll;

}else if(hmm)

{

cutMethod=CutDag;

}else{

cutMethod=CutDagWithoutHmm;

}returnCutIt(text, cutMethod, reHan, reSkip, cutAll);

}

终于找到了关键的函数:CutIt

internal IEnumerable CutIt(string text, Func>cutMethod,

Regex reHan, Regex reSkip,boolcutAll)

{var result = new List();var blocks =reHan.Split(text);foreach (var blk inblocks)

{if (string.IsNullOrWhiteSpace(blk))

{continue;

}if(reHan.IsMatch(blk))

{foreach (var word incutMethod(blk))

{

result.Add(word);

}

}else{var tmp =reSkip.Split(blk);foreach (var x intmp)

{if(reSkip.IsMatch(x))

{

result.Add(x);

}else if (!cutAll)

{foreach (var ch inx)

{

result.Add(ch.ToString());

}

}else{

result.Add(x);

}

}

}

}returnresult;

}

在CutIt函数中JieBa分词都把空格省去,这样在Tokenize函数中使用start=0 start+=word.Length 显示不能得到正确的原始文本中的位置。

if (string.IsNullOrWhiteSpace(blk))

{continue;

}

JIEba分词也没有考虑到会使用Lucene的高亮,越是只能自己改写了CutIt函数和Tokenize函数:

在CutIt函数中,返回的值不在是一个string,而是一个包含string,startPosition的类,这样在Tokenize中就很准确的得到每个分词的位置属性了。

internal IEnumerable CutIt2(string text, Func>cutMethod,

Regex reHan, Regex reSkip,boolcutAll)

{//Console.WriteLine("*********************************我开始分词了*******************");

var result = new List();var blocks =reHan.Split(text);var start = 0;foreach(var blk inblocks)

{//Console.WriteLine("?????????????当前的串:"+blk);

if(string.IsNullOrWhiteSpace(blk))

{

start+=blk.Length;continue;

}if(reHan.IsMatch(blk))

{foreach(var word incutMethod(blk))

{//Console.WriteLine("?????blk 分词:" + word + "????????初始位置:" + start);

result.Add(newWordInfo(word,start));

start+=word.Length;

}

}else{var tmp =reSkip.Split(blk);foreach(var x intmp)

{if(reSkip.IsMatch(x))

{//Console.WriteLine("????? x reSkip 分词:" + x + "????????初始位置:" + start);

result.Add(newWordInfo(x,start));

start+=x.Length;

}else if(!cutAll)

{foreach(var ch inx)

{//Console.WriteLine("?????ch 分词:" + ch + "????????初始位置:" + start);

result.Add(newWordInfo(ch.ToString(),start));

start+=ch.ToString().Length;

}

}else{//Console.WriteLine("?????x 分词:" + x + "????????初始位置:" + start);

result.Add(newWordInfo(x,start));

start+=x.Length;

}

}

}

}returnresult;

}public IEnumerable Tokenize(string text, TokenizerMode mode = TokenizerMode.Default, bool hmm = true)

{var result = new List();if (mode ==TokenizerMode.Default)

{foreach (var w inCut2(text, hmm: hmm))

{var width =w.value.Length;

result.Add(new Token(w.value, w.position, w.position +width));

}

}else{var xx =Cut2(text, hmm: hmm);foreach (var w inCut2(text, hmm: hmm))

{var width =w.value.Length;if (width > 2)

{for (var i = 0; i < width - 1; i++)

{var gram2 = w.value.Substring(i, 2);if(WordDict.ContainsWord(gram2))

{

result.Add(new Token(gram2, w.position + i, w.position + i + 2));

}

}

}if (width > 3)

{for (var i = 0; i < width - 2; i++)

{var gram3 = w.value.Substring(i, 3);if(WordDict.ContainsWord(gram3))

{

result.Add(new Token(gram3, w.position + i, w.position + i + 3));

}

}

}

result.Add(new Token(w.value, w.position, w.position +width));

}

}returnresult;

}public classWordInfo

{public WordInfo(string value,intposition)

{this.value =value;this.position =position;

}//分词的内容

public string value { get; set; }//分词的初始位置

public int position { get; set; }

}

这样的话,终于可以正确的进行高亮了,果然搜索效果要比PanGu分词好很多。

4.停用词

是用JIEba的停用词的方法,是把停用词的文件里的内容读取出来,然后在Reset()函数里把停用词都过滤掉:

StreamReader rd =File.OpenText(stopUrl);string s = "";while((s=rd.ReadLine())!=null)

{

stopWords.Add(s);

}public override voidReset()

{base.Reset();

_InputText= ReadToEnd(base.m_input);

RemoveStopWords(segmenter.Tokenize(_InputText,mode));

start= 0;

iter=_WordList.GetEnumerator();

}public void RemoveStopWords(System.Collections.Generic.IEnumerablewords)

{

_WordList.Clear();foreach(var x inwords)

{if(stopWords.IndexOf(x.Word)==-1)

{

_WordList.Add(x);

}

}

}

5.索引速度

使用JIEba分词之后,虽然效果很好,但是写索引的速度很慢,考虑到时细粒度分词,相比以前一篇文章多出来很多分词,所以索引速度慢了8倍左右,但是感觉这并不正常,前面的开源代码测试结果中,CutForSearch很快的,应该是自己的代码哪里出了问题。

三,Lucene的高亮

这里再对Lucene的高亮的总结一下,Lucene提供了两种高亮模式,一种是普通高亮,一种是快速高亮。

1.普通高亮

普通高亮的原理,就是将搜索之后得到的文档,使用分词器再进行分词,得到的TokenStream,再进行高亮:

SimpleHTMLFormatter simpleHtmlFormatter = new SimpleHTMLFormatter("", "");

Lucene.Net.Search.Highlight.Highlighter highlighter= new Lucene.Net.Search.Highlight.Highlighter(simpleHtmlFormatter, newQueryScorer(query));

highlighter.TextFragmenter= new SimpleFragmenter(150);

Analyzer analyzer= newJieBaAnalyzer(TokenizerMode.Search);

TokenStream tokenStream= analyzer.GetTokenStream("Content", new StringReader(doc.Get("Content")));var frags = highlighter.GetBestFragments(tokenStream, doc.Get(fieldName), 200);

2.快速高亮

之所很快速,是因为高亮是直接根据索引储存的信息进行高亮,前面已经说过我们索引需要储存分词的位置信息,这个就是为高亮服务的,所以速度很快,当然带来的后果是你的索引文件会比较大,因为储存了位置信息。

FastVectorHighlighter fhl = new FastVectorHighlighter(false, false, simpleFragListBuilder, scoreOrderFragmentsBuilder);

FieldQuery fieldQuery=fhl.GetFieldQuery(query,_indexReader);

highLightSetting.MaxFragNum.GetValueOrDefault(MaxFragNumDefaultValue);var frags = fhl.GetBestFragments(fieldQuery, _indexReader, docid, fieldName, fragSize, maxFragNum);

快速高亮的关键源代码:

protected virtual string MakeFragment(StringBuilder buffer, int[] index, Field[] values, WeightedFragInfo fragInfo,string[] preTags, string[] postTags, IEncoder encoder)

{

StringBuilder fragment= newStringBuilder();int s =fragInfo.StartOffset;int[] modifiedStartOffset ={ s };string src =GetFragmentSourceMSO(buffer, index, values, s, fragInfo.EndOffset, modifiedStartOffset);int srcIndex = 0;foreach (SubInfo subInfo infragInfo.SubInfos)

{foreach (Toffs to insubInfo.TermsOffsets)

{

fragment

.Append(encoder.EncodeText(src.Substring(srcIndex, (to.StartOffset- modifiedStartOffset[0]) -srcIndex)))

.Append(GetPreTag(preTags, subInfo.Seqnum))

.Append(encoder.EncodeText(src.Substring(to.StartOffset- modifiedStartOffset[0], (to.EndOffset - modifiedStartOffset[0]) - (to.StartOffset - modifiedStartOffset[0]))))

.Append(GetPostTag(postTags, subInfo.Seqnum));

srcIndex= to.EndOffset - modifiedStartOffset[0];

}

}

fragment.Append(encoder.EncodeText(src.Substring(srcIndex)));returnfragment.ToString();

}

fragInfo储存了所有需要高亮的关键字和位置信息,src则是原始文本,而之前报的错误正是这里引起的错误,由于位置信息有误src.Substring就会报错。

四,结语

.net core2.0版的中文分词确实不多,相比较之下,java,c++,的分词工具有很多,或许可以用c++的速度快的特点,做一个单独分词服务,效果是不是会更好。

ik分词和jieba分词哪个好_Lucene.net(4.8.0) 学习问题记录五: JIEba分词和Lucene的结合,以及对分词器的思考...相关推荐

  1. Lucene之中文庖丁解牛(mmseg)分词器-yellowcong

    庖丁解牛分词器,分词器和Lucene的版本需要注意,有可能有冲突,报错,我最开始是1.8.5的mmseg4j和一个lucene有冲突,后来,换了Mmseg4j版本后,就好了 下载地址 #这个是包含有字 ...

  2. Lucene提供的几种分词器

    1.几种lucene提供的分词器 下面是几种分词器的代码实现示例. import org.apache.lucene.analysis.*; import org.apache.lucene.anal ...

  3. java结巴分词如何提高运行速度_结巴分词 java 高性能实现,优雅易用的 api 设计,性能优于 huaban jieba 分词...

    Segment Segment 是基于结巴分词词库实现的更加灵活,高性能的 java 分词实现. 创作目的 分词是做 NLP 相关工作,非常基础的一项功能. jieba-analysis 作为一款非常 ...

  4. Lucene快速入门第三讲——看看Lucene是如何支持中文分词的?

    在这一讲中,我们要看看Lucene到底是如何支持中文分词的?为了向大家阐述明白这个问题,咱们可先从分析器的执行过程入手. 分析器(Analyzer)的执行过程 如下图所示是语汇单元的生成过程: 从一个 ...

  5. 中文分词最佳记录刷新了,两大模型分别解决中文分词及词性标注问题丨已开源...

    伊瓢 发自 中关村 量子位 报道 | 公众号 QbitAI 中文分词的最佳效果又被刷新了. 在今年的ACL 2020上,来自创新工场大湾区人工智能研究院的两篇论文中的模型,刷新了这一领域的成绩. WM ...

  6. 深度学习核心技术精讲100篇(十七)-多标准中文分词( Multi-Criteria-CWS)

    前言 论文:https://arxiv.org/pdf/1712.02856.pdf  : 代码和语料:https://github.com/hankcs/multi-criteria-cws . 本 ...

  7. Lucene实现自定义中文同义词分词器

    ---------------------------------------------------------- lucene的分词_中文分词介绍 ------------------------ ...

  8. Lucene和ikanalyzer(中文分词器)的简单使用增删改查(提供maven依赖)

    --于2020.08.28从jar包更新为了maven依赖 什么是Lucene?  Lucene是apache下的一个开放源代码的全文检索引擎工具包,通过它可以实现全文检索. 什么是全文检索(Full ...

  9. Lucene的Smart CN实现分词、停用词、扩展词

    Lucene 中提供了 SmartCN 为中文提供分词功能,实际应用中还会涉及到停用词.扩展词(特殊词.专业词)等,因此本文将聚焦在 SmartCN 而暂时不考虑其他中文分词类库. 1 简介 anal ...

最新文章

  1. python 除法取模_Python的运算符和表达式(上)
  2. SpringMVC介绍之Validation
  3. Java基础---学Java怎么能不了解多线程---Lambda表达式
  4. php mysql encode_PHP json_encode mysql结果
  5. 【codevs1034】家园——网络流
  6. spring定时任务详解
  7. SPOJ Qtree系列
  8. (转贴)正则表达式学习心得体会(1)
  9. java语言的数据类型_Java语言的数据类型
  10. qq企业邮箱的发送邮件服务器地址,腾讯企业邮箱注册和SMTP发件设置教程
  11. bh1750采集流程图_多路BH1750光强检测系统的设计
  12. 西部数码网站备案幕布及核验单填写规范
  13. 4g内存php一般开多少个进程,Linux_4G内存服务器epoll并发量最大能达到多少?,按照题主的意思 是根据内存去 - phpStudy...
  14. 4. (5.22~6.8)2022年自动化保研信息+分析汇总(夏令营)
  15. 【重磅】DeepMind开源史上最全强化学习框架OpenSpiel(附安装方法)
  16. [免费专栏] 车联网基础理论之车联网安全车端知识科普
  17. Linksys路由器被曝多个漏洞
  18. 从黄金时代到没落尽头,“寻求出售”的GoPro遭遇了什么?
  19. 【c】分数类型的定义 c语言分数类型加减乘除的实现
  20. MFC 加载gif动态图片的方法

热门文章

  1. 《跟唐老师学习云网络》 - 什么是VLAN和VXLAN
  2. python回到初始位置_python之基础
  3. Spark之RDD实战篇3
  4. Error-backpropagation in temporally encoded networks of spiking neurons 误差传播在时间编码的脉冲神经网络
  5. TikZ绘图示例——尺规作图: 圆内接正九边形的近似画法
  6. WORD 如何在方框里打勾?
  7. android 自定义进度条 水量,Android自定义带水滴的进度条样式(带渐变色效果)...
  8. while用法_when 和 while 的用法区别
  9. MyCAT全局序列号-数据库方式
  10. linux复制duo文件,Linux打印目录下多个文件