65.9K
CodeProject 正在变化。 阅读更多。
Home

Lucene.Net – 自定义同义词分析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (24投票s)

2009年1月3日

Apache

4分钟阅读

viewsIcon

116912

downloadIcon

3449

如何使用 Lucene.net 搜索来处理同义词

什么是 Lucene.Net?

Lucene.Net 是一个高性能的信息检索(IR)库,也称为搜索引擎库。Lucene.Net 包含强大的 API,用于创建全文本索引并为您的程序实现高级而精确的搜索技术。有些人可能会将 Lucene.net 与现成的应用程序(如网络搜索/爬虫或文件搜索应用程序)混淆,但 Lucene.Net 并不是这样的应用程序,它是一个框架库。Lucene.Net 提供了一个框架,让您可以自己实现这些困难的技术。Lucene.Net 对您可以索引和搜索的内容不做区分,这与其他全文本索引/搜索实现相比,为您提供了更大的灵活性;您可以索引任何可以表示为文本的内容。还有一些方法可以让 Lucene.Net 索引 HTML、Office 文档、PDF 文件等等。

Lucene.Net 是原始 Lucene 项目(用 Java 编写)的 API 对 API 移植。甚至单元测试也被移植过来以保证质量。此外,Lucene.Net 索引与 Lucene 索引完全兼容,两个库可以一起使用在同一个索引上,没有任何问题。许多产品都使用 Lucene 和 Lucene.Net 来构建它们的搜索;一些知名的网站包括 WikipediaCNETMonster.comMayo ClinicFedEx,以及 更多。但不仅仅是网站使用了 Lucene;还有一个产品使用了 Lucene.Net,名为 Lookout,它是一个用于 Microsoft Outlook 的搜索工具,它只是让 Outlook 的集成搜索显得非常缓慢且不准确。

Lucene.Net 目前正在 Apache 软件基金会进行孵化。其源代码保存在一个 subversion 存储库中,可以在 这里 找到。如果您需要帮助下载源代码,可以使用免费的 TortoiseSVNRapidSVN。Lucene.Net 项目始终欢迎新贡献者。而且,请记住,除了编写代码之外,还有 多种方式 可以为开源项目做出贡献。

如何让 Lucene.Net 处理同义词?

目标是能够搜索一个词,并能够检索包含与您搜索词含义相同的词的结果。这将使您能够通过含义进行搜索,而不是通过关键词进行搜索。

我们可以通过创建一个自定义的 Analyzer 类来轻松地让 Lucene.Net 处理同义词。该 Analyzer 将能够将同义词注入到全文本索引中。有关 Analyzer 内部细节的一些信息,请参阅我之前的文章 Lucene.Net – 文本分析

创建分析器

我们首先要做的就是抽象化获取同义词的工作。所以我们将创建一个简单的接口来完成这个任务。

    public interface ISynonymEngine
    {
        IEnumerable<string> GetSynonyms(string word);
    } 

太好了,现在让我们来处理同义词引擎的一个实现。

public class XmlSynonymEngine : ISynonymEngine
    {
        //this will contain a list, of lists of words that go together
        private List<ReadOnlyCollection<string>> SynonymGroups =
            new List<ReadOnlyCollection<string>>();

        public XmlSynonymEngine(string xmlSynonymFilePath)
        {
            // create an XML document object, and load it from the specified file.
            XmlDocument Doc = new XmlDocument();
            Doc.Load(xmlSynonymFilePath);

            // get all the <group> nodes
            var groupNodes = Doc.SelectNodes("/synonyms/group");

            //enumerate groups
            foreach (XmlNode g in groupNodes)
            {
                //get all the <syn> elements from the group nodes.
                XmlNodeList synNodes = g.SelectNodes("child::syn");

                //create a list that will hold the items for this group
                List<string> synonymGroupList = new List<string>();

                //enumerate them and add them to the list,
                //and add each synonym group to the list
                foreach (XmlNode synNode in g)
                {
                    synonymGroupList.Add(synNode.InnerText.Trim());
                }

                //add single synonym group to the list of synonm groups.
                SynonymGroups.Add(new ReadOnlyCollection<string>(synonymGroupList));
            }

            // clear the XML document
            Doc = null;
        }

        #region ISynonymEngine Members

        public IEnumerable<string> GetSynonyms(string word)
        {
            //enumerate all the synonym groups
            foreach (var synonymGroup in SynonymGroups)
            {
                //if the word is a part of the group return 
                //the group as the results.
                if (synonymGroup.Contains(word))
                {
                    //gonna use a read only collection for security purposes
                    return synonymGroup;
                }
            }

            return null;
        }

        #endregion
    }

现在让我们看看一个示例文档,我们的 XmlSynonymEngine 将读取它

<?xml version="1.0" encoding="utf-8" ?>
<synonyms>
  <group>
    <syn>fast</syn>
    <syn>quick</syn>
    <syn>rapid</syn>
  </group>

  <group>
    <syn>slow</syn>
    <syn>decrease</syn>
  </group>

  <group>
    <syn>google</syn>
    <syn>search</syn>
  </group>

  <group>
    <syn>check</syn>
    <syn>lookup</syn>
    <syn>look</syn>
  </group>
  
</synonyms>

在考虑创建任何 analyzer 来为 Lucene 提供新功能时,最好不要将逻辑放在 Analyzer 类中,而是将其放在 Tokenizer TokenFilter 类中。同义词的注入更多的是 TokenFilter 的领域,所以我将创建一个 SynonmFilter 类,它将充当一个 TokenFilter。这个 TokenFilter 的实现只需要我们重写 TokenFilter 基类的一个方法,那就是返回一个 token 的 Next() 方法。这是 SynonymFilter 类的实现

 public class SynonymFilter : TokenFilter
    {
        private Queue<Token> synonymTokenQueue
            = new Queue<Token>();

        public ISynonymEngine SynonymEngine { get; private set; }

        public SynonymFilter(TokenStream input, ISynonymEngine synonymEngine)
            : base(input)
        {
            if (synonymEngine == null)
                throw new ArgumentNullException("synonymEngine");

            SynonymEngine = synonymEngine;
        }

        public override Token Next()
        {
            // if our synonymTokens queue contains any tokens, return the next one.
            if (synonymTokenQueue.Count > 0)
            {
                return synonymTokenQueue.Dequeue();
            }

            //get the next token from the input stream
            Token t = input.Next();

            //if the token is null, then it is the end of stream, so return null
            if (t == null)
                return null;

            //retrieve the synonyms
            IEnumerable<string> synonyms = SynonymEngine.GetSynonyms(t.TermText());
            
            //if we don't have any synonyms just return the token
            if (synonyms == null)
            {
                return t;
            }

            //if we do have synonyms, add them to the synonymQueue, 
            // and then return the original token
            foreach (string syn in synonyms)
            {
                //make sure we don't add the same word 
                if ( ! t.TermText().Equals(syn))
                {
                    //create the synonymToken
                    Token synToken = new Token(syn, t.StartOffset(), 
                              t.EndOffset(), "<SYNONYM>");
                    
                    // set the position increment to zero
                    // this tells lucene the synonym is 
                    // in the exact same location as the originating word
                    synToken.SetPositionIncrement(0);

                    //add the synToken to the synonyms queue
                    synonymTokenQueue.Enqueue(synToken);
                }
            }

            //after adding the syn to the queue, return the original token
            return t;
        }
    } 

最后是 SynonymAnalyzer

    public class SynonymAnalyzer : Analyzer
    {
        public ISynonymEngine SynonymEngine { get; private set; }

        public SynonymAnalyzer(ISynonymEngine engine)
        {
            SynonymEngine = engine;
        }

        public override TokenStream TokenStream
		(string fieldName, System.IO.TextReader reader)
        {
            //create the tokenizer
            TokenStream result = new StandardTokenizer(reader);

            //add in filters
            // first normalize the StandardTokenizer
            result = new StandardFilter(result); 

            // makes sure everything is lower case
            result = new LowerCaseFilter(result);

            // use the default list of Stop Words, provided by the StopAnalyzer class.
            result = new StopFilter(result, StopAnalyzer.ENGLISH_STOP_WORDS); 

            // injects the synonyms. 
            result = new SynonymFilter(result, SynonymEngine); 

            //return the built token stream.
            return result;
        }
    }

现在让我们看看结果

分析器查看器,使用 StandardAnalyzer 查看令牌

lucene_custom_analyzer/standardview.jpg

分析器查看器,使用 SynonymAnalyzer 查看令牌

lucene_custom_analyzer/synviewjpg.jpg

关注点

SynonymAnalyzer 非常适合索引,但我认为如果您打算使用 SynonymAnalyzer 来构造查询(使用 QueryParser ),它可能会让查询变得混乱。一种解决办法是修改 SynonymFilter SynonymAnalyzer ,使其具有一个布尔开关来打开和关闭同义词注入。这样,您就可以在使用 QueryParser 时关闭同义词注入。

随附的代码包括了我上一篇文章中的 Analyzer Viewer 应用程序,但它还包括了一个更新,增加了我们全新的同义词分析器。

历史

  • 2009/1/2 - 初始发布
© . All rights reserved.