【问题标题】:Using default and custom stop words with Apache's Lucene (weird output)在 Apache 的 Lucene 中使用默认和自定义停用词(奇怪的输出)
【发布时间】:2021-01-27 00:08:23
【问题描述】:

我正在使用 Apache 的 Lucene (8.6.3) 和以下 Java 8 代码从字符串中删除停用词:

private static final String CONTENTS = "contents";
final String text = "This is a short test! Bla!";
final List<String> stopWords = Arrays.asList("short","test");
final CharArraySet stopSet = new CharArraySet(stopWords, true);

try {
    Analyzer analyzer = new StandardAnalyzer(stopSet);
    TokenStream tokenStream = analyzer.tokenStream(CONTENTS, new StringReader(text));
    CharTermAttribute term = tokenStream.addAttribute(CharTermAttribute.class);
    tokenStream.reset();

    while(tokenStream.incrementToken()) {
        System.out.print("[" + term.toString() + "] ");
    }

    tokenStream.close();
    analyzer.close();
} catch (IOException e) {
    System.out.println("Exception:\n");
    e.printStackTrace();
}

这会输出期望的结果:

[这个] [是] [a] [bla]

现在我想同时使用默认的英文停止设置,它还应该删除“this”、“is”和“a”(根据github)和上面的自定义停止设置(我实际是会使用更长的时间),所以我尝试了这个:

Analyzer analyzer = new EnglishAnalyzer(stopSet);

输出是:

[thi] [是] [a] [bla]

是的,“this”中的“s”不见了。这是什么原因造成的?它也没有使用默认的停止设置。

以下更改同时删除了默认和自定义停用词:

Analyzer analyzer = new EnglishAnalyzer();
TokenStream tokenStream = analyzer.tokenStream(CONTENTS, new StringReader(text));
tokenStream = new StopFilter(tokenStream, stopSet);

问题:执行此操作的“正确”方法是什么?在自身内部使用tokenStream(参见上面的代码)会导致问题吗?

额外问题:我如何以正确的大写/小写输出剩余的单词,因此它们在原始文本中使用什么?

【问题讨论】:

    标签: java lucene stop-words


    【解决方案1】:

    我将分两部分解决这个问题:

    • 停用词
    • 保留原案

    处理组合停用词

    要处理 Lucene 的英文停用词列表,加上自己的自定义列表,可以创建一个合并列表,如下所示:

    import org.apache.lucene.analysis.en.EnglishAnalyzer;
    
    ...
    
    final List<String> stopWords = Arrays.asList("short", "test");
    final CharArraySet stopSet = new CharArraySet(stopWords, true);
    
    CharArraySet enStopSet = EnglishAnalyzer.ENGLISH_STOP_WORDS_SET;
    stopSet.addAll(enStopSet);
    

    上面的代码只是将 Lucene 捆绑的英文停用词合并到你的列表中。

    给出以下输出:

    [bla]
    

    处理单词大小写

    这涉及更多。正如您所注意到的,StandardAnalyzer 包含一个将所有单词转换为小写的步骤 - 所以我们不能使用它。

    此外,如果您想维护自己的自定义停用词列表,并且该列表大小不限,我建议将其存储在自己的文本文件中,而不是将列表嵌入到您的代码中。

    所以,假设您有一个名为stopwords.txt 的文件。在此文件中,每行一个单词 - 该文件已包含您的自定义停用词的合并列表和英文停用词的官方列表。

    您需要自己手动准备此文件(即忽略此答案第 1 部分中的注释)。

    我的测试文件是这样的:

    short
    this
    is
    a
    test
    the
    him
    it
    

    我也更喜欢将CustomAnalyzer 用于类似的事情,因为它让我可以非常简单地构建分析器。

    import org.apache.lucene.analysis.custom.CustomAnalyzer;
    
    ...
    
    Analyzer analyzer = CustomAnalyzer.builder()
            .withTokenizer("icu")
            .addTokenFilter("stop",
                    "ignoreCase", "true",
                    "words", "stopwords.txt",
                    "format", "wordset")
            .build();
    

    执行以下操作:

    1. 它使用“icu”标记器org.apache.lucene.analysis.icu.segmentation.ICUTokenizer,它负责对 Unicode 空白进行标记,并处理标点符号。

    2. 它应用了停用词列表。注意true 用于ignoreCase 属性,以及对停用词文件的引用。 wordset 的格式表示“每行一个单词”(也有其他格式)。

    这里的关键是上面的链中没有任何改变单词大小写的东西。

    所以,现在,使用这个新的分析器,输出如下:

    [Bla]
    

    最后说明

    你把停止列表文件放在哪里?默认情况下,Lucene 期望在应用程序的类路径中找到它。因此,例如,您可以将其放在默认包中。

    但请记住,该文件需要由您的构建过程处理,以便它与应用程序的类文件一起结束(而不是与源代码一起留下)。

    我主要使用 Maven - 因此我在我的 POM 中有这个,以确保根据需要部署“.txt”文件:

        <build>  
            <resources>  
                <resource>  
                    <directory>src/main/java</directory>  
                    <excludes>  
                        <exclude>**/*.java</exclude>  
                    </excludes>  
                </resource>  
            </resources>  
        </build> 
    

    这告诉 Maven 将文件(Java 源文件除外)复制到构建目标 - 从而确保复制文本文件。

    最后一点 - 我没有调查您为什么会收到截断的 [thi] 令牌。如果有机会我会仔细看看。


    后续问题

    合并后我必须使用 StandardAnalyzer,对吧?

    是的,没错。我在答案的第 1 部分中提供的注释与您问题中的代码以及您使用的 StandardAnalyzer 直接相关。

    我想将停用词文件保留在特定的非导入路径上 - 怎么做?

    您可以告诉 CustomAnalyzer 在“资源”目录中查找停用词文件。该目录可以位于文件系统上的任何位置(如您所述,便于维护):

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    ...
    
    Path resources = Paths.get("/path/to/resources/directory");
    
    Analyzer analyzer = CustomAnalyzer.builder(resources)
            .withTokenizer("icu")
            .addTokenFilter("stop",
                    "ignoreCase", "true",
                    "words", "stopwords.txt",
                    "format", "wordset")
            .build();
    

    我们现在使用.builder(resources),而不是使用.builder()

    【讨论】:

    • 我已经更新了答案以提供更多注释。希望对您有所帮助。
    • 您需要 ICU4J - 您可以使用 Maven 或下载 jar filehere 获取文件。 Lucene 8.6.3 使用该库的 62.1 版本。我强烈建议使用 Maven(或 Gradle,或类似的)——它会自动为您下载所有此类传递依赖项。
    • 旁注:关于thisthi 的意外转换。当您使用 EnglishAnalyzer 时,您创建的令牌流会自动使用 Porter 词干分析器(请参阅 JavaDoc for createComponents())。其中一个词干规则涉及从某些单词中删除尾随“s”(如果单词是复数,则更多:horses 变为 hors,例如)。
    • 注(2):添加额外的语言会使事情复杂化(不管第 1 点)。例如,您可能需要在分析器中包含一个 ascii 折叠过滤器:.addTokenFilter("asciiFolding")。但这些问题与您原来的问题不同。实际上,您最好创建一个全新的问题来关注这些特定项目(更多人会以这种方式看到您的问题)。希望这在此期间有所帮助。
    • 优秀。很高兴你取得了进展。使用 Windows-1252 编码的英语单词与 UTF-8 的工作方式相同,因为“a-z”和“A-Z”的所有代码点在两种编码中都是相同的。但是,只要您有超出该范围的字符(例如任何带有重音的字符),那么是的,编码方案变得更加重要,正如您所见。
    猜你喜欢
    • 2013-07-05
    • 2013-08-03
    • 2013-10-05
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多