#java #lucene #stop-words
#java #lucene #стоп-слова
Вопрос:
Я удаляю стоп-слова из строки, используя Lucene от Apache (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] [is] [a] [bla]
Да, буква «s» в «this» отсутствует. Что является причиной этого? Он также не использовал стоп-набор по умолчанию.
Следующие изменения удаляют как стандартные, так и пользовательские стоп-слова:
Analyzer analyzer = new EnglishAnalyzer();
TokenStream tokenStream = analyzer.tokenStream(CONTENTS, new StringReader(text));
tokenStream = new StopFilter(tokenStream, stopSet);
Вопрос: Каков «правильный» способ сделать это? Вызовет ли использование tokenStream
внутри себя (см. Код Выше) проблемы?
Бонусный вопрос: как мне вывести оставшиеся слова с правильным верхним / нижним регистром, следовательно, что они используют в исходном тексте?
Ответ №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();
Это делает следующее:
-
Он использует токенизатор «icu»
org.apache.lucene.analysis.icu.segmentation.ICUTokenizer
, который заботится о маркировке пробелов в Юникоде и обработке знаков препинания. -
Применяется список стоп-слов. Обратите внимание на использование
true
ignoreCase
атрибута for и ссылку на файл стоп-слова. Формат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()
мы теперь используем .builder(resources)
.
Комментарии:
1. Я обновил ответ, чтобы предоставить еще несколько заметок. Надеюсь, это поможет.
2. Вам нужен ICU4J — вы можете получить файл отсюда , либо используя Maven, либо загрузив файл jar . Lucene 8.6.3 использует версию 62.1 этой библиотеки. Я настоятельно рекомендую использовать Maven (или Gradle, или аналогичный), который автоматически позаботится о загрузке всех таких транзитивных зависимостей для вас.
3. Примечание: относительно неожиданного преобразования
this
tothi
. Когда вы используете EnglishAnalyzer, создаваемый вами поток токенов автоматически использует стеммер Porter (см. JavaDoc для createComponents() ). Одно из основных правил включает удаление завершающего «s» из некоторых слов (и более, если слово является множественным числом:horses
становитсяhors
, например).4. Примечание (2): добавление дополнительных языков может усложнить ситуацию (независимо от пункта 1). Например, вам может потребоваться включить фильтр свертки ascii в ваш анализатор :
.addTokenFilter("asciiFolding")
. Но это разные вопросы из вашего первоначального вопроса. Возможно, вам лучше создать совершенно новый вопрос, чтобы сосредоточиться на этих конкретных элементах (таким образом, больше людей увидят ваши вопросы). Надеюсь, это помогло в то же время.5. Отлично. Рад, что вы добились прогресса. Английские слова, закодированные с помощью Windows-1252, будут работать так же, как UTF-8, потому что все кодовые точки для «a-z» и «A-Z» одинаковы в обеих кодировках. Но как только у вас появятся символы за пределами этого диапазона (например, что-нибудь с акцентом), тогда да, схемы кодирования становятся намного важнее, как вы видели.