Быстрое разделение текста на предложения (Java)

У меня есть набор описаний статей, где я должен разделить тексты на предложения. В первой реализации используется инструмент opennlp senddetect, который работает очень хорошо, но слишком медленно для моей цели. Есть ли что-нибудь похожее на это, которое работает быстрее и дает результат такого же или немного худшего качества?

Примечание: я работаю с (огромным количеством) коротких редактируемых текстов на немецком языке.


person Chris    schedule 07.04.2014    source источник
comment
Насколько оно должно быть точным? Насколько хорошо написан текст? качество книги/журнала - качество комментариев на YouTube? Можно ли вызывать внешние программы, отличные от Java?   -  person Daniel Mahler    schedule 11.04.2014
comment
Его короткие редакционные тексты, описывающие, например, одежду. Было бы лучше, если бы это можно было сделать с помощью java, но если есть хорошая программа, отличная от java, которая обрабатывает тексты быстро и точно, я бы обязательно попробовал ее. Кажется, что точность и производительность здесь работают друг против друга, поэтому в этом случае я бы отдал приоритет общей производительности.   -  person Chris    schedule 11.04.2014
comment
Если текст достаточно высокого качества, а точность не является главным приоритетом, тогда регулярные выражения, вероятно, будут правильным выбором, особенно если вы используете реализацию регулярных выражений, которая под прикрытием компилирует регулярные выражения в DFA. Если вы хотите что-то более сложное, а OpenNLP не работает, вам, вероятно, придется выйти за пределы Java.   -  person Daniel Mahler    schedule 11.04.2014
comment
Вы хотите записать предложения в файл/сохранить его в памяти (что, я не думаю, вы исключаете, поскольку оно большое). Содержит ли ваш текст какие-либо символы EOL? или просто текст   -  person Mani    schedule 16.04.2014
comment
Тексты находятся в базе данных. Основная цель состоит в том, чтобы оценить качество текста и отбросить те части, в которых мало осмысленного содержания (например, много стоп-слов и прилагательных), и таким образом сократить тексты до основного содержания (возможно, мы должны сделать это с нашими политиками). выступления иногда ;-))   -  person Chris    schedule 17.04.2014


Ответы (5)


Да, полезно упомянуть, что вы работаете с немецким :)

Детектор предложений на основе регулярных выражений со списком сокращений можно найти в GATE. Он использует три файла, расположенных здесь. Регулярные выражения довольно просты:

//more than 2 new lines
(?:[\u00A0\u2007\u202F\p{javaWhitespace}&&[^\n\r]])*(\n\r|\r\n|\n|\r)(?:(?:[\u00A0\u2007\u202F\p{javaWhitespace}&&[^\n\r]])*\1)+

//between 1 and 3 full stops
\.{1,3}"?

//up to 4 ! or ? in sequence
(!|\?){1,4}"?

Код, который использует эти 3 файла, можно найти здесь.

Я бы улучшил регулярные выражения тем, что можно найти в Интернете, например этим< /а>.

Тогда я бы подумал обо всех немецких переводах слов в списке GATE. Если этого недостаточно, я бы просмотрел несколько из этих списков сокращений: 1, 2, и создать список самостоятельно.

РЕДАКТИРОВАТЬ:

Если производительность так важна, я бы не стал использовать весь GATE для разделителя предложений — потребовалось бы время и память, чтобы переключиться на их документы, создать аннотации, затем разобрать их обратно и т. д.

Я думаю, что лучший способ для вас — получить код из класса RegexSentenceSplitter (ссылка выше) и настройте его в соответствии с вашим контекстом.

Я думаю, что код слишком длинный, чтобы вставить его сюда. Вы должны увидеть метод execute(). В общем, он находит все совпадения для внутренних, внешних и блокирующих регулярных выражений, затем повторяет и использует только те внутренние и внешние, которые не пересекаются ни с одним из блокирующих.

Вот некоторые фрагменты, которые вы должны посмотреть/повторно использовать:

  • Как разбираются файлы

    // for each line
    if(patternString.length() > 0) patternString.append("|");
    patternString.append("(?:" + line + ")");
    
    //...
    return Pattern.compile(patternString.toString());
    
  • В методе execute, как заполняются разделители блокировки:

    Matcher nonSplitMatcher = nonSplitsPattern.matcher(docText);
    //store all non split locations in a list of pairs
    List<int[]> nonSplits = new LinkedList<int[]>();
    while(nonSplitMatcher.find()){
       nonSplits.add(new int[]{nonSplitMatcher.start(), nonSplitMatcher.end()});
    }
    

Также проверьте метод veto, который «Проверяет, наложено ли вето на возможное совпадение нерасщепленным совпадением. На возможное совпадение наложено вето, если оно не пересекается с областью вето».

Надеюсь это поможет.

person Yasen    schedule 10.04.2014
comment
Я отредактировал свой ответ, добавив несколько рекомендаций по реализации вашего детектора предложений. Я бы не стал использовать для этого весь GATE, просто повторно использовал бы части их кода. - person Yasen; 11.04.2014

Может быть, String.split("\\. |\\? |! "); делает это?

person ifloop    schedule 07.04.2014
comment
Я думал об использовании регулярных выражений, так как это намного быстрее, но эта конкретная версия слишком проста. Что-то, что может обрабатывать сокращения, также было бы неплохо, поскольку они регулярно встречаются в используемых текстах. - person Chris; 07.04.2014
comment
Можем ли мы узнать ваше определение предложения или окончания предложения? Я уверен, что здесь есть некоторые специалисты по регулярным выражениям, которые могут создать шаблон, прежде чем вы это узнаете ^_^ - person ifloop; 07.04.2014
comment
Предложение в моем определении заканчивается на .! или ? после пробела и начинается с заглавной буквы. Я не знаю, как включить сокращения, хотя их сотни. Если это поможет: я работаю с немецкими текстами. - person Chris; 07.04.2014

В общем, я думаю, что OpenNLP будет лучше (с точки зрения производительности), чем сегментаторы на основе правил, такие как сегментатор Стэнфорда, или реализация регулярных выражений для решения задачи. Сегментаторы на основе правил обязательно пропустят некоторые исключения. Как, например, немецкое предложение «Ich wurde am 17. Dezember geboren» (Я родился 17 декабря) будет ошибочно разбито на 2 предложения после 17. многими сегментаторами, основанными на правилах, особенно если они построены по английским правилам, а не по немецким. Предложения, подобные этим, будут встречаться, даже если качество вашего текста действительно отличное, поскольку они представляют собой грамматически правильный немецкий язык. Поэтому очень важно проверить, на какой языковой модели построен сегментер, который вы хотите использовать.

PS: среди OpenNLP, сегментатора BreakIterator и сегментатора Stanford OpenNLP работал лучше всего для меня.

person Menezes Sousa    schedule 16.04.2014

Вероятно, стоит упомянуть, что стандартная библиотека API Java предоставляет зависящие от локали функции для обнаружения теста. границы. BreakIterator можно использовать для определения границ предложений. .

person Alexey Gavrilov    schedule 15.04.2014

Есть еще одно решение. Не знаю, как с производительностью по сравнению с вашим решением, но наверняка наиболее полным. Вы можете использовать библиотеку ICU4J и файлы srx. Библиотеку можно скачать здесь http://site.icu-project.org/download/52#TOC-ICU4J-Download. Работает как шарм, многоязычный.

package srx;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import net.sf.okapi.common.ISegmenter;
import net.sf.okapi.common.LocaleId;
import net.sf.okapi.common.Range;
import net.sf.okapi.lib.segmentation.LanguageMap;
import net.sf.okapi.lib.segmentation.Rule;
import net.sf.okapi.lib.segmentation.SRXDocument;

public class Main {

/**
 * @param args
 */
public static void main(String[] args) {

    if(args.length != 2) return;

    SRXDocument doc = new SRXDocument();

    String srxRulesFilePath = args[0];
    String text = args[1];
    doc.loadRules(srxRulesFilePath);
    LinkedHashMap<String, ArrayList<Rule>> rules =  doc.getAllLanguageRules();
    ArrayList<LanguageMap> languages = doc.getAllLanguagesMaps();
    ArrayList<Rule> plRules = doc.getLanguageRules(languages.get(0).getRuleName());     
    LocaleId locale = LocaleId.fromString("pl_PL");     
    ISegmenter segmenter = doc.compileLanguageRules(LocaleId.fromString("pl_PL"), null);


    segmenter.computeSegments(text);

    List<Range> ranges = segmenter.getRanges();

    System.out.println(ranges.size());
    for (Range range : ranges) {
        System.out.println(range.start);
        System.out.println(range.end);
    }
}

}
person CezaryDraus    schedule 16.04.2014
comment
Мне нравится это решение (поскольку оно не требует громоздкого OpenNLP, но дает почти идентичные результаты для разделения на предложения), но оно требует некоторого пояснения. На самом деле вам не нужна библиотека ICU4J, но вам нужна пара библиотек okapi, расположенных в okapiframework.org, чтобы ваш импорт работал (okapi-lib-0.32.jar, slf4j-api-1.7.13.jar, slf4j-jdk14-1.7.13.jar, вот и все). Там же можно достать srx-файлы (скачать okapi-lib_all-platforms_0.32.zip, они лежат в папке config). - person RoK; 08.04.2017