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






4.92/5 (24投票s)
如何使用 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 来构建它们的搜索;一些知名的网站包括 Wikipedia、CNET、Monster.com、Mayo Clinic、FedEx,以及 更多。但不仅仅是网站使用了 Lucene;还有一个产品使用了 Lucene.Net,名为 Lookout,它是一个用于 Microsoft Outlook 的搜索工具,它只是让 Outlook 的集成搜索显得非常缓慢且不准确。
Lucene.Net 目前正在 Apache 软件基金会进行孵化。其源代码保存在一个 subversion 存储库中,可以在 这里 找到。如果您需要帮助下载源代码,可以使用免费的 TortoiseSVN 或 RapidSVN。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 查看令牌
分析器查看器,使用 SynonymAnalyzer 查看令牌
关注点
SynonymAnalyzer
非常适合索引,但我认为如果您打算使用 SynonymAnalyzer
来构造查询(使用 QueryParser
),它可能会让查询变得混乱。一种解决办法是修改 SynonymFilter
和 SynonymAnalyzer
,使其具有一个布尔开关来打开和关闭同义词注入。这样,您就可以在使用 QueryParser
时关闭同义词注入。
随附的代码包括了我上一篇文章中的 Analyzer Viewer 应用程序,但它还包括了一个更新,增加了我们全新的同义词分析器。
历史
- 2009/1/2 - 初始发布