【问题标题】:How to add options for Analyze in Apache Lucene?如何在 Apache Lucene 中添加分析选项?
【发布时间】:2011-11-24 14:57:12
【问题描述】:

Lucene 具有在索引时基本上标记和过滤语料库的分析器。操作包括将标记转换为小写、词干提取、去除停用词等。

我正在进行一项实验,我想尝试所有可能的分析操作组合:仅提取、仅停止、提取和停止……

总共有 36 种组合我想尝试。

我怎样才能轻松优雅地做到这一点?

我知道我可以扩展 Analyzer 类并实现 tokenStream() 函数来创建自己的 Analyzer:

public class MyAnalyzer extends Analyzer
{

public TokenStream tokenStream(String field, final Reader reader){
return new NameFilter(
    CaseNumberFilter(
            new StopFilter(
                    new LowerCaseFilter(
                            new StandardFilter(
                                    new StandardTokenizer(reader)
                    )
            ), StopAnalyzer.ENGLISH_STOP_WORDS)
    )
);
}

我想做的是编写一个这样的类,它可以以某种方式为每个可能的操作(doStopping、doStemming 等)获取布尔值。我不想编写 36 个不同的 Analyzer 类,每个类都执行 36 种组合中的一种。困难在于过滤器在其构造函数中组合在一起的方式。

关于如何优雅地做到这一点的任何想法?

编辑:“优雅地”是指我可以轻松地在某种循环中创建一个新的分析器:

analyzer = new MyAnalyzer(doStemming, doStopping, ...)

doStemming 和 doStopping 随每次循环迭代而变化。

【问题讨论】:

    标签: lucene


    【解决方案1】:

    Solr 通过使用 Tokenizer 和 TokenFilter 工厂解决了这个问题。你也可以这样做,例如:

    public interface TokenizerFactory {
        Tokenizer newTokenizer(Reader reader);
    }
    
    public interface TokenFilterFactory {
        TokenFilter newTokenFilter(TokenStream source);
    }
    
    public class ConfigurableAnalyzer {
    
        private final TokenizerFactory tokenizerFactory;
        private final List<TokenFilterFactory> tokenFilterFactories;
    
        public ConfigurableAnalyzer(TokenizerFactory tokenizerFactory, TokenFilterFactory... tokenFilterFactories) {
            this.tokenizerFactory = tokenizerFactory;
            this.tokenFilterFactories = Arrays.asList(tokenFilterFactories);
        }
    
        public TokenStream tokenStream(String field, Reader source) {
            TokenStream sink = tokenizerFactory.newTokenizer(source);
            for (TokenFilterFactory tokenFilterFactory : tokenFilterFactories) {
                sink = tokenFilterFactory.newTokenFilter(sink);
            }
            return sink;
        }
    
    }
    

    这样,您可以通过传递一个标记器的工厂和 0 到 n 个过滤器作为构造函数参数来配置您的分析器。

    【讨论】:

    • 感谢 jpountz。我不明白这是如何工作的,确切地说;如何轻松创建具有一组布尔值的新分析器?
    • 不需要提供布尔值,只需要提供token过滤器工厂列表,更加灵活。如果您有三个工厂class StandardTokenizerFactory implements TokenizerFactoryclass StopFilterFactory implements TokenFilterFactoryclass LowerCaseFilterFactory implements TokenFilterFactory,您可以这样创建分析器:new ConfigurableAnalyzer(new StandardTokenizerFactory(), new StopFilterFactory(), new LowerCaseFilterFactory());。此分析器将使用StandardTokenizer 进行标记化并使用StopFilter 进行过滤,然后使用LowerCaseFilter
    • 我明白了。您的方法优雅而灵活,但对我的特定应用程序不太理想。由于我想以编程方式尝试标记器和过滤器的所有组合,因此我需要为每个可能的组合调用new ConfigurableAnalyzer(...),这既乏味又难以维护。我添加的解决方案允许我循环执行此操作。
    【解决方案2】:

    将一些类变量添加到自定义分析器类中,可以轻松地即时设置和取消设置。然后,在 tokenStream() 函数中,使用这些变量来确定要执行哪些过滤器。

    public class MyAnalyzer extends Analyzer {
    
        private Set customStopSet; 
        public static final String[] STOP_WORDS = ...;
    
        private boolean doStemming = false;
        private boolean doStopping = false;
    
        public JavaSourceCodeAnalyzer(){
                super();
                customStopSet = StopFilter.makeStopSet(STOP_WORDS);
        }
    
        public void setDoStemming(boolean val){
                this.doStemming = val;
        }
    
        public void setDoStopping(boolean val){
                this.doStopping = val;
        }
    
        public TokenStream tokenStream(String fieldName, Reader reader) {
    
                // First, convert to lower case
                TokenStream out = new  LowerCaseTokenizer(reader);
    
                if (this.doStopping){
                        out = new StopFilter(true, out, customStopSet);
                }
    
                if (this.doStemming){
                        out = new PorterStemFilter(out);
                }
    
                return out;
        }
    }
    

    有一个问题:LowerCaseTokenizer 将读取器变量作为输入,并返回一个 TokenStream。这对于以下过滤器(StopFilter、PorterStemFilter)来说很好,因为它们将 TokenStreams 作为输入并将它们作为输出返回,因此我们可以很好地将它们链接在一起。但是,这意味着在返回 TokenStream 的 LowerCaseTokenizer 之前不能有过滤器。就我而言,我想将 camelCase 单词拆分为多个部分,这必须在转换为小写之前完成。我的解决方案是在自定义 Indexer 类中手动执行拆分,所以当 MyAnalyzer 看到文本时,它已经被拆分了。

    (我还在我的客户索引器类中添加了一个布尔标志,因此现在两者都可以仅基于标志工作。)

    有更好的答案吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-11
      • 2017-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多