Как разбить абзац текста на предложения? (желательно на Руби)

Как вы берете абзац или большой объем текста и разбиваете его на предложения (желательно с помощью Ruby), принимая во внимание такие случаи, как мистер и доктор и США? (Предполагая, что вы просто поместили предложения в массив массивов)

ОБНОВЛЕНИЕ. Одно из возможных решений, о котором я подумал, включает использование тега частей речи (POST) и классификатора для определения конца предложения:

Получив данные от мистера Джонса, он почувствовал теплое солнце на своем лице, когда вышел на балкон своего летнего дома в Италии. Он был счастлив быть живым.

КЛАССИФИКАТОР Г-н/ЧЕЛОВЕК Джонс/ЧЕЛОВЕК почувствовал/О/О тепло/О солнце/О на/О его/О лицо/О как/О он/О вышел/О вышел/О на/О/О балкон/О из/O его/O лето/O дом/O в/O Италия/МЕСТО ./O Он/O был/O счастливым/O к/O быть/O живым/O ./O

ПОСТ Г-н/NNP Джонс/NNP чувствовал/VBD в/DT теплый/JJ солнце/NN на/В его/PRP$ лицо/NN как/В он/PRP вышел/VBD вышел/RP на/В/DT балкон/ NN из/В его/PRP$ летом/NN дома/NN в/В Италии./NNP Он/PRP был/VBD счастлив/JJ to/Быть/VB жив./IN

Можем ли мы предположить, что, поскольку Италия является местом, точка является допустимым концом предложения? С момента окончания на "г-н." не будет иметь других частей речи, можем ли мы предположить, что это недопустимая точка в конце предложения? Это лучший ответ на мой вопрос?

Мысли?


person henry74    schedule 13.05.2009    source источник
comment
Есть ли определенные правила. Если вы расскажете нам правила на английском языке, я уверен, что мы (или вы) сможете запрограммировать решение. Например: есть ли после аббревиатур, таких как «abbr», точка? Если вы собираетесь разбирать учебники по грамматике, вам могут подойти простые решения, но если вы берете произвольный текст, то у каждого решения будут недостатки, например... понимаете?   -  person Marcin    schedule 14.05.2009
comment
Теггер POS — это излишество. Используйте токенизатор на основе NLP, и ваши правила станут проще.   -  person Kevin Peterson    schedule 14.05.2009


Ответы (15)


Попробуйте взглянуть на оболочку Ruby для Stanford Parser. Он имеет функцию getSentencesFromString().

person Stompchicken    schedule 14.05.2009
comment
Я продолжу играть с парсером Стэнфорда - он где-то там! Спасибо! - person henry74; 14.05.2009
comment
edu.stanford.nlp.process.DocumentPreprocessor, кстати - person Stompchicken; 14.05.2009
comment
Да, либо через оболочку Ruby, либо напрямую, вызвав edu.stanford.nlp.process.DocumentPreprocessor (из кода или из командной строки: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument .txt › oneTokenizedSentencePerLine.txt , вы можете разделить текст на предложения (это делается с помощью (хорошего, но эвристического) FSM, так что это быстро; вы не запускаете вероятностный синтаксический анализатор.) - person Christopher Manning; 16.08.2010

Просто чтобы было понятно, что нет простого решения для этого. Это тема исследования НЛП, как показывает быстрый поиск в Google.

Тем не менее, похоже, что есть несколько проектов с открытым исходным кодом, связанных с НЛП, поддерживающих обнаружение предложений. Я нашел следующий набор инструментов на основе Java:

openNLP

Дополнительный комментарий: проблема определения того, где начинается и заканчивается предложение, также называется устранением неоднозначности границы предложения (SBD ) в обработке естественного языка.

person Dirk Vollmar    schedule 13.05.2009
comment
Мне не удалось найти простую рубиновую оболочку для openNLP — вы сталкивались с ней? Хотя у них был разделитель предложений... - person henry74; 14.05.2009
comment
@philllc: Что ж, так называемое устранение неоднозначности границ предложения - это проблема обработки естественного языка, связанная с определением, где предложения начинаются и заканчиваются. (en.wikipedia.org/wiki/Sentence_boundary_disambiguation) - person Dirk Vollmar; 19.06.2009

Взгляните на разделитель предложений Python в NLTK (набор инструментов для работы с естественным языком):

Токенизатор предложений Punkt

Он основан на следующем документе:

Кисс, Тибор и Странк, Ян (2006 г.): Обнаружение границы многоязычного предложения без присмотра . Вычислительная лингвистика 32: 485-525.

Подход в статье довольно интересный. Они сводят проблему разделения предложения к проблеме определения того, насколько сильно слово связано с последующей пунктуацией. Перегрузка точек после аббревиатур является причиной большинства неоднозначных точек, поэтому, если вы можете идентифицировать аббревиатуры, вы можете с высокой вероятностью определить границы предложения.

Я немного протестировал этот инструмент неофициально, и, кажется, он дает хорошие результаты для различных (человеческих) языков.

Перенести его на Ruby было бы нетривиально, но это может дать вам некоторые идеи.

person Community    schedule 25.05.2009
comment
обе ссылки не работают, вы можете поделиться чем-то, что не будет удалено? - person lacostenycoder; 02.02.2021

Похоже, этот рубиновый драгоценный камень может помочь.

https://github.com/zencephalon/Tactful_Tokenizer

person aantix    schedule 06.05.2010
comment
Я попробую. Спасибо! - person henry74; 31.05.2010

Это трудная задача, если вы действительно заботитесь о том, чтобы решить ее правильно. Вы обнаружите, что пакеты синтаксического анализатора NLP, вероятно, предоставляют эту функциональность. Если вы хотите что-то более быстрое, вам нужно в конечном итоге продублировать некоторые из этих функций с помощью обученной вероятностной функции окна токенов (вы, вероятно, захотите считать перевод строки как токен, поскольку я могу удалить точку, если это конец абзаца).

Изменить: я рекомендую анализатор Stanford, если вы можете использовать Java. У меня нет рекомендаций для других языков, но мне очень интересно узнать, что еще есть с открытым исходным кодом.

person Kevin Peterson    schedule 13.05.2009
comment
Да, я играл с парсером Stanford NLP, но не нашел разделителя предложений. Если вы заинтересованы в его использовании, есть обертка rjb (Ruby to Java Bridge), созданная кем-то на github, которую я смог относительно легко заставить работать. Вот ссылка для тех, кому интересно: github.com/tiendung/ruby-nlp/tree /master ПРИМЕЧАНИЕ. В Windows при загрузке библиотек Java необходимо заменить двоеточие на точку с запятой. Ваше здоровье. - person henry74; 14.05.2009
comment
Вы правы, в пакете синтаксического анализатора нет разделителя предложений, но есть токенизатор, который поможет вам в этом. Он обрабатывает такие вещи, как упомянутые, г-н как токен по сравнению с . как конец предложения. - person Kevin Peterson; 14.05.2009
comment
Есть разделитель предложений: edu.stanford.nlp.process.DocumentPreprocessor. Попробуйте выполнить команду: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt › oneTokenizedSentencePerLine.txt. (Это делается с помощью (хорошего, но эвристического) FSM, так что это быстро, вы не запускаете вероятностный синтаксический анализатор.) - person Christopher Manning; 16.08.2010

К сожалению, я не специалист по рубинам, но, возможно, пример на Perl поможет вам двигаться в правильном направлении. Использование несоответствующего взгляда позади для конечного знака препинания, затем некоторых особых случаев в не позади, за которым следует любое количество пробелов, за которым следует просмотр вперед для заглавной буквы. Я уверен, что это не идеально, но я надеюсь, что это укажет вам правильное направление. Не уверен, как вы узнаете, действительно ли США стоит в конце предложения...

#!/usr/bin/perl

$string = "Mr. Thompson is from the U.S.A. and is 75 years old. Dr. Bob is a dentist. This is a string that contains several sentances. For example this is one. Followed by another. Can it deal with a question?  It sure can!";

my @sentances = split(/(?:(?<=\.|\!|\?)(?<!Mr\.|Dr\.)(?<!U\.S\.A\.)\s+(?=[A-Z]))/, $string);

for (@sentances) {
    print $_."\n";
}
person Copas    schedule 13.05.2009

Может быть, попробовать разделить его точкой, за которой следует пробел, за которым следует заглавная буква? Я не уверен, как найти заглавные буквы, но я бы начал искать именно с этого шаблона.

Изменить: Поиск заглавные буквы с Ruby.

Еще одно изменение:

Проверьте знаки препинания в конце предложения, которые следуют за словами, не начинающимися с заглавных букв.

person Jarrod    schedule 13.05.2009
comment
Что, если вы разделите его на точки, которые следуют за словами, которые не начинаются с заглавных букв? - person Jarrod; 14.05.2009
comment
Это было именно то, что я придумал, но я хотел знать, есть ли еще лучшие решения. Конечно, это не сработало бы, если бы предложение заканчивалось именем собственным, например, я отправился в Италию. - person henry74; 14.05.2009
comment
один очень распространенный случай, который не сработает, - это такие имена, как мистер Дибблер. - person Martin DeMello; 20.05.2009

Ответ доктора Мэннинга является наиболее подходящим, если вы рассматриваете JAVA (и Ruby тоже тяжело;)). Это здесь-

Есть разделитель предложений: edu.stanford.nlp.process.DocumentPreprocessor. Попробуйте выполнить команду: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt

oneTokenizedSentencePerLine.txt . (Это делается с помощью (хорошего, но эвристического) FSM, так что это быстро, вы не запускаете вероятностный синтаксический анализатор.)

Но небольшое предложение, если мы изменим команду java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt TO java edu.stanford.nlp.process.DocumentPreprocessor < em>-file /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt . Это будет работать нормально, так как вам нужно указать, какой файл представляется в качестве входных данных. Итак, -file для текстового файла, -html для HTML и т. д.

person Prakash Pimpale    schedule 23.02.2011

Я не пробовал, но если английский — единственный язык, который вас интересует, я бы посоветовал предоставить Lingua::EN ::Читаемость внешний вид.

Lingua::EN::Readability — это модуль Ruby, который вычисляет статистику по английскому тексту. Он может подсчитывать количество слов, предложений и слогов. Он также может рассчитать несколько показателей удобочитаемости, таких как индекс тумана и уровень Флеша-Кинкейда. Пакет включает в себя модуль Lingua::EN::Sentence, который разбивает английский текст на предложения с учетом аббревиатур, и Lingua::EN::Syllable, который может угадать количество слогов в написанном английском слове. Если доступен словарь произношения, он может искать количество слогов в словаре для большей точности.

Бит, который вам нужен, находится в sentence.rb следующим образом:

module Lingua
module EN
# The module Lingua::EN::Sentence takes English text, and attempts to split it
# up into sentences, respecting abbreviations.

module Sentence
  EOS = "\001" # temporary end of sentence marker

  Titles   = [ 'jr', 'mr', 'mrs', 'ms', 'dr', 'prof', 'sr', 'sen', 'rep', 
         'rev', 'gov', 'atty', 'supt', 'det', 'rev', 'col','gen', 'lt', 
         'cmdr', 'adm', 'capt', 'sgt', 'cpl', 'maj' ]

  Entities = [ 'dept', 'univ', 'uni', 'assn', 'bros', 'inc', 'ltd', 'co', 
         'corp', 'plc' ]

  Months   = [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 
         'aug', 'sep', 'oct', 'nov', 'dec', 'sept' ]

  Days     = [ 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun' ]

  Misc     = [ 'vs', 'etc', 'no', 'esp', 'cf' ]

  Streets  = [ 'ave', 'bld', 'blvd', 'cl', 'ct', 'cres', 'dr', 'rd', 'st' ]

  @@abbreviations = Titles + Entities + Months + Days + Streets + Misc

  # Split the passed text into individual sentences, trim these and return
  # as an array. A sentence is marked by one of the punctuation marks ".", "?"
  # or "!" followed by whitespace. Sequences of full stops (such as an
  # ellipsis marker "..." and stops after a known abbreviation are ignored.
  def Sentence.sentences(text)

    text = text.dup

    # initial split after punctuation - have to preserve trailing whitespace
    # for the ellipsis correction next
    # would be nicer to use look-behind and look-ahead assertions to skip
    # ellipsis marks, but Ruby doesn't support look-behind
    text.gsub!( /([\.?!](?:\"|\'|\)|\]|\})?)(\s+)/ ) { $1 << EOS << $2 }

    # correct ellipsis marks and rows of stops
    text.gsub!( /(\.\.\.*)#{EOS}/ ) { $1 }

    # correct abbreviations
    # TODO - precompile this regex?
    text.gsub!( /(#{@@abbreviations.join("|")})\.#{EOS}/i ) { $1 << '.' }

    # split on EOS marker, strip gets rid of trailing whitespace
    text.split(EOS).map { | sentence | sentence.strip }
  end

  # add a list of abbreviations to the list that's used to detect false
  # sentence ends. Return the current list of abbreviations in use.
  def Sentence.abbreviation(*abbreviations)
    @@abbreviations += abbreviations
    @@abbreviations
  end
end
end
end
person Dave Sag    schedule 12.02.2013
comment
В этом перечислены замечательные моменты, хотя я обнаружил, что для ускорения работы с большими объемами текста вместо того, чтобы делать так много замен регулярных выражений, хорошо работало иметь массив слов, которые циклически повторялись, а затем сравнивались с терминами, которые вы упомянули выше, и с другими вариантами окончания строки. Примерно в 1000 раз быстрее в моих ограниченных тестах для документов большого размера. - person JayCrossler; 07.08.2013

Согласитесь с принятым ответом, использование Stanford Core NLP не составляет труда.

Однако в 2016 году возникли некоторые несовместимости, взаимодействующие с Stanford Parser с более поздними версиями stanford core nlp (у меня были проблемы с Stanford Core NLP v3.5).

Вот что я сделал, чтобы разобрать текст на предложения, используя интерфейс Ruby и Stanford Core NLP:

  1. Установите жемчуг Stanford CoreNLP — он все еще поддерживается и работает, это была борьба чтобы найти рубиновые жемчужины НЛП, которые работают в последнее время:

    gem install stanford-core-nlp

  2. Затем следуйте инструкциям на странице . readme для использования последней версии Stanford CoreNLP:

Использование последней версии Stanford CoreNLP (версия 3.5.0 от 31.10.2014) требует выполнения некоторых дополнительных действий вручную:

  • Загрузите Stanford CoreNLP версии 3.5.0 с http://nlp.stanford.edu/.

  • Поместите содержимое извлеченного архива в папку /bin/ гема stanford-core-nlp (например, [...]/gems/stanford-core-nlp-0.x/bin/) или в настроенное местоположение каталога установив StanfordCoreNLP.jar_path.

  • Загрузите полную версию Stanford Tagger 3.5.0 с http://nlp.stanford.edu/.
  • Создайте каталог с именем taggers внутри папки /bin/ гема stanford-core-nlp (например, [...]/gems/stanford-core-nlp-0.x/bin/) или внутри каталога, сконфигурированного установка StanfordCoreNLP.jar_path.
  • Поместите содержимое извлеченного архива в каталог taggers.
  • Загрузите файл bridge.jar из https://github.com/louismullie/stanford-core-nlp .
  • Поместите загруженный файл bridger.jar в папку /bin/ гема stanford-core-nlp (например, [...]/gems/stanford-core-nlp-0.x/bin/taggers/) или в каталог настраивается путем установки StanfordCoreNLP.jar_path.

Затем рубиновый код для разделения текста на предложения:

require "stanford-core-nlp"

#I downloaded the StanfordCoreNLP to a custom path:
StanfordCoreNLP.jar_path = "/home/josh/stanford-corenlp-full-2014-10-31/"

StanfordCoreNLP.use :english
StanfordCoreNLP.model_files = {}
StanfordCoreNLP.default_jars = [
  'joda-time.jar',
  'xom.jar',
  'stanford-corenlp-3.5.0.jar',
  'stanford-corenlp-3.5.0-models.jar',
  'jollyday.jar',
  'bridge.jar'
]

pipeline =  StanfordCoreNLP.load(:tokenize, :ssplit)

text = 'Mr. Josh Weir is writing some code. ' + 
  'I am Josh Weir Sr. my son may be Josh Weir Jr. etc. etc.'
text = StanfordCoreNLP::Annotation.new(text)
pipeline.annotate(text)
text.get(:sentences).each{|s| puts "sentence: " + s.to_s}

#output:
#sentence: Mr. Josh Weir is writing some code.
#sentence: I am Josh Weir Sr. my son may be Josh Weir Jr. etc. etc.
person joshweir    schedule 27.12.2016

Разбивка на точку, за которой следует пробел и заглавная буква, не подходит для таких титулов, как «Мистер Браун».

Точки усложняют задачу, но проще всего справиться с восклицательными и вопросительными знаками. Однако есть случаи, когда это не сработает. т. е. корпоративное название Yahoo!

person Evan Meagher    schedule 13.05.2009

Ну, очевидно, paragraph.split('.') этого не сделает.

#split примет регулярное выражение в качестве ответа, поэтому вы можете попробовать использовать поиск нулевой ширины, чтобы проверить слово, начинающееся с заглавной буквы. Конечно, это будет разбито на имена собственные, поэтому вам, возможно, придется прибегнуть к регулярному выражению, подобному этому /(Mr\.|Mrs\.|U\.S\.A ...), которое было бы ужасно уродливым, если бы вы не создали регулярное выражение программно.

person Community    schedule 13.05.2009

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

Но, конечно, в вашем списке могут быть пропущены некоторые слова, и в этом случае вы получите плохие результаты.

person eKek0    schedule 13.05.2009

Я не парень Ruby, а регулярное выражение, которое разделяется на

 ^(Mr|Mrs|Ms|Mme|Sta|Sr|Sra|Dr|U\.S\.A)[\.\!\?\"] [A-Z]

будет моим лучшим выбором, как только вы получите абзац (разделенный на \r\n). Это предполагает, что ваши предложения правильно написаны.

Очевидно, что это довольно уродливое регулярное выражение. Как насчет двух пробелов между предложениями?

person Eric    schedule 13.05.2009

СПАСИБО!

Мне очень понравилось это обсуждение, поэтому я заинтересовался парсером. Я попробовал и записал несколько заметок о том, как заставить все работать с Ruby и/или Rails!

Попытка использовать регулярное выражение была кошмаром.

мои 2 цента

person Community    schedule 30.07.2009
comment
Эта страница больше не существует... Зеркало? - person DickieBoy; 16.07.2013