Python - RegEx для разделения текста на предложения (предложение-токенизация)

Я хочу составить список предложений из строки, а затем распечатать их. Я не хочу использовать NLTK для этого. Поэтому он должен разбиваться на период в конце предложения, а не на десятичные знаки, аббревиатуры или название имени, или если предложение имеет .com. Это попытка регулярного выражения, которая не работает.

import re

text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)

for stuff in sentences:
        print(stuff)    

Пример вывода того, как он должен выглядеть

Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. 
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.

Ответ 1

(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s

Попробуйте это. разделите свою строку this.You также можете проверить демо.

http://regex101.com/r/nG1gU7/27

Ответ 2

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

В общем, вы не можете полагаться на одно непогрешимое регулярное выражение Great White, вам нужно написать функцию, которая использует несколько регулярных выражений (как положительных, так и отрицательных); также словарь аббревиатур, а также некоторый базовый анализ языка, который знает, что, например, "I", "США", "FCC", "TARP" капитализируются на английском языке.

Чтобы проиллюстрировать, насколько легко это может серьезно осложниться, попробуйте написать вам эту функциональную спецификацию для детерминированного токенизатора, чтобы решить, указывает ли один или несколько периодов ('.'/'...') конец предложения или что-то еще еще:

function isEndOfSentence(leftContext, rightContext)

  1. Возврат False для десятичных чисел внутри чисел или валюты, например 1.23, $ 1.23, "Это только мои $.02". Рассмотрим также ссылки на разделы, такие как 1.2.3, европейские форматы даты, такие как 09.07.2014, IP-адреса, такие как 192.168.1.1, MAC-адреса...
  2. Возврат False (и не обозначать отдельные буквы) для известных сокращений, например, "акции США падают"; для этого требуется словарь известных сокращений. Все, что за пределами этого словаря вы ошибетесь, если вы не добавите код для обнаружения неизвестных аббревиатур, таких как ABC, и добавьте их в список.
  3. Эллипсы "..." на концах предложений являются терминальными, но в середине предложений нет. Это не так просто, как вы могли бы подумать: вам нужно взглянуть на левый контекст и правильный контекст, в частности, заглавный RHS и снова рассмотреть заглавные слова типа "я" и аббревиатуры. Вот пример, доказывающий двусмысленность: она попросила меня остаться... Я ушел через час. (Было ли это одно предложение или два? Невозможно определить)
  4. Вы также можете написать несколько шаблонов, чтобы обнаруживать и отклонять разные применения пунктуации, не связанные с предложениями: смайлики :-), искусство ASCII, промежуточные эллипсы. , , и другие вещи esp. Twitter. (Сделать эту адаптивность еще сложнее). Как мы узнаем, является ли @midnight пользователем Twitter, шоу на Comedy Central, сокращенное текстовое сообщение или просто пунктуация нежелательной/нежелательной/опечатки? Серьезно нетривиально.
  5. После того, как вы справитесь со всеми этими негативными случаями, вы можете произвольно сказать, что любой изолированный период, за которым следует пробел, скорее всего, будет завершением предложения. (В конечном счете, если вы действительно хотите приобрести дополнительную точность, вы в конечном итоге пишете свой собственный вероятностный фреймворк предложения, который использует веса и обучает его конкретному корпусу (например, юридические тексты, широковещательные СМИ, StackOverflow, Twitter, комментарии к форуму и т.д.), ) Затем вам нужно вручную просмотреть образцы и ошибки обучения. См. Книгу Маннинга и Джурафского или курс Курсера [a]. В конечном итоге вы получаете столько же корректности, сколько готовы платить.
  6. Все вышесказанное четко относится к англоязычным/аббревиатурам, форматам US number/time/date. Если вы хотите сделать это country- и независимым от языка, это большее предложение, вам понадобятся корпуса, люди, говорящие на родном языке, для маркировки и QA, и т.д.
  7. Все вышесказанное по-прежнему остается только ASCII. Разрешите ввод Unicode, и все становится еще сложнее (и набор для обучения обязательно должен быть либо намного большим, либо намного реже)

В простом (детерминированном) случае function isEndOfSentence(leftContext, rightContext) возвращает логическое значение, но в более общем смысле оно вероятностно: оно возвращает float 0.0-1.0 (уровень уверенности, что этот конкретный "." - конец предложения),

Ссылки: [a] Видео Coursera: "Обработка основного текста 2-5 - Сегментация предложений" - Стэнфордский НЛП - Профессор Дэн Юрафски и Крис Мэннинг " [UPDATE: неофициальная версия, которая использовалась на YouTube, была снята]

Ответ 3

Попробуйте разбить вход в соответствии с пробелами, а не точкой или ?, если вы сделаете это так, точка или ? не будут напечатаны в конечном результате.

>>> import re
>>> s = """Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't."""
>>> m = re.split(r'(?<=[^A-Z].[.?]) +(?=[A-Z])', s)
>>> for i in m:
...     print i
... 
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it.
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.

Ответ 4

sent = re.split('(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)',text)
for s in sent:
    print s

Здесь используется регулярное выражение: (?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)

Первый блок: (?<!\w\.\w.): этот шаблон ищет в контуре с отрицательной обратной связью (?<!) для всех слов (\w), за которым следует fullstop (\.), за которым следуют другие слова (\.)

Второй блок: (?<![A-Z][a-z]\.): этот шаблон ищет в цикле отрицательной обратной связи для чего-либо, начиная с букв верхнего регистра ([A-Z]), а затем строчных алфавитов ([A-Z]) до тех пор, пока не будет найдена точка (\.).

Третий блок: (?<=\.|\?): этот шаблон ищет в петле обратной связи точки (\.) ИЛИ вопросительный знак (\?)

Четвертый блок: (\s|[A-Z].*): этот шаблон ищет после метки точки ИЛИ вопроса из третьего блока. Он ищет пустое пространство (\s) ИЛИ любую последовательность символов, начиная с алфавита верхнего регистра ([A-Z].*). Этот блок очень важен для разделения, если вход имеет значение

Привет, мир. Привет, я здесь сегодня.

то есть. если после точки есть пробел или пробел.

Ответ 5

Попробуйте следующее:

(?<!\b(?:[A-Z][a-z]|\d|[i.e]))\.(?!\b(?:com|\d+)\b)

Ответ 6

Наивный подход для правильных английских предложений, не начинающийся с не-альфа и не содержащий цитируемых частей речи:

import re
text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
EndPunctuation = re.compile(r'([\.\?\!]\s+)')
NonEndings = re.compile(r'(?:Mrs?|Jr|i\.e)\.\s*$')
parts = EndPunctuation.split(text)
sentence = []
for part in parts:
  if len(part) and len(sentence) and EndPunctuation.match(sentence[-1]) and not NonEndings.search(''.join(sentence)):
    print(''.join(sentence))
    sentence = []
  if len(part):
    sentence.append(part)
if len(sentence):
  print(''.join(sentence))

Ложное положительное расщепление может быть уменьшено путем расширения NonEndings немного. В других случаях потребуется дополнительный код. При таком подходе обработка опечаток разумным способом окажется сложной.

Вы никогда не достигнете совершенства при таком подходе. Но в зависимости от задачи он может просто работать "достаточно"...

Ответ 7

Я написал это с учетом комментариев smci выше. Это подход среднего класса, который не требует внешних библиотек и не использует регулярное выражение. Он позволяет вам предоставить список сокращений и счетов для предложений, заканчивающихся терминаторами в оболочках, таких как период и цитата: [. ",? ',.)].

abbreviations = {'dr.': 'doctor', 'mr.': 'mister', 'bro.': 'brother', 'bro': 'brother', 'mrs.': 'mistress', 'ms.': 'miss', 'jr.': 'junior', 'sr.': 'senior', 'i.e.': 'for example', 'e.g.': 'for example', 'vs.': 'versus'}
terminators = ['.', '!', '?']
wrappers = ['"', "'", ')', ']', '}']


def find_sentences(paragraph):
   end = True
   sentences = []
   while end > -1:
       end = find_sentence_end(paragraph)
       if end > -1:
           sentences.append(paragraph[end:].strip())
           paragraph = paragraph[:end]
   sentences.append(paragraph)
   sentences.reverse()
   return sentences


def find_sentence_end(paragraph):
    [possible_endings, contraction_locations] = [[], []]
    contractions = abbreviations.keys()
    sentence_terminators = terminators + [terminator + wrapper for wrapper in wrappers for terminator in terminators]
    for sentence_terminator in sentence_terminators:
        t_indices = list(find_all(paragraph, sentence_terminator))
        possible_endings.extend(([] if not len(t_indices) else [[i, len(sentence_terminator)] for i in t_indices]))
    for contraction in contractions:
        c_indices = list(find_all(paragraph, contraction))
        contraction_locations.extend(([] if not len(c_indices) else [i + len(contraction) for i in c_indices]))
    possible_endings = [pe for pe in possible_endings if pe[0] + pe[1] not in contraction_locations]
    if len(paragraph) in [pe[0] + pe[1] for pe in possible_endings]:
        max_end_start = max([pe[0] for pe in possible_endings])
        possible_endings = [pe for pe in possible_endings if pe[0] != max_end_start]
    possible_endings = [pe[0] + pe[1] for pe in possible_endings if sum(pe) > len(paragraph) or (sum(pe) < len(paragraph) and paragraph[sum(pe)] == ' ')]
    end = (-1 if not len(possible_endings) else max(possible_endings))
    return end


def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1:
            return
        yield start
        start += len(sub)

Я использовал функцию Karl find_all из этой записи: Найти все вхождения подстроки в Python

Ответ 8

Я не очень хорош в регулярных выражениях, но более простая версия, "грубая сила" на самом деле, выше

sentence = re.compile("([\'\"][A-Z]|([A-Z][a-z]*\. )|[A-Z])(([a-z]*\.[a-z]*\.)|([A-Za-z0-9]*\.[A-Za-z0-9])|([A-Z][a-z]*\. [A-Za-z]*)|[^\.?]|[A-Za-z])*[\.?]")

что означает начало приемлемых единиц: "[A-Z] или" [A-Z]
обратите внимание, большинство регулярных выражений жадные, поэтому порядок очень важен, когда мы делаем | (или). Вот почему я написал сначала i.e. регулярное выражение, а затем формы, такие как Inc.

Ответ 9

Мой пример основан на примере Али, адаптированном к бразильскому португальцу. Спасибо Али.

ABREVIACOES = ['sra?s?', 'exm[ao]s?', 'ns?', 'nos?', 'doc', 'ac', 'publ', 'ex', 'lv', 'vlr?', 'vls?',
               'exmo(a)', 'ilmo(a)', 'av', 'of', 'min', 'livr?', 'co?ls?', 'univ', 'resp', 'cli', 'lb',
               'dra?s?', '[a-z]+r\(as?\)', 'ed', 'pa?g', 'cod', 'prof', 'op', 'plan', 'edf?', 'func', 'ch',
               'arts?', 'artigs?', 'artg', 'pars?', 'rel', 'tel', 'res', '[a-z]', 'vls?', 'gab', 'bel',
               'ilm[oa]', 'parc', 'proc', 'adv', 'vols?', 'cels?', 'pp', 'ex[ao]', 'eg', 'pl', 'ref',
               '[0-9]+', 'reg', 'f[ilí]s?', 'inc', 'par', 'alin', 'fts', 'publ?', 'ex', 'v. em', 'v.rev']

ABREVIACOES_RGX = re.compile(r'(?:{})\.\s*$'.format('|\s'.join(ABREVIACOES)), re.IGNORECASE)

        def sentencas(texto, min_len=5):
            # baseado em https://stackoverflow.com/questions/25735644/python-regex-for-splitting-text-into-sentences-sentence-tokenizing
            texto = re.sub(r'\s\s+', ' ', texto)
            EndPunctuation = re.compile(r'([\.\?\!]\s+)')
            # print(NonEndings)
            parts = EndPunctuation.split(texto)
            sentencas = []
            sentence = []
            for part in parts:
                txt_sent = ''.join(sentence)
                q_len = len(txt_sent)
                if len(part) and len(sentence) and q_len >= min_len and \
                        EndPunctuation.match(sentence[-1]) and \
                        not ABREVIACOES_RGX.search(txt_sent):
                    sentencas.append(txt_sent)
                    sentence = []

                if len(part):
                    sentence.append(part)
            if sentence:
                sentencas.append(''.join(sentence))
            return sentencas

Полный код: https://github.com/luizanisio/comparador_elastic

Ответ 10

Если вы хотите разбить предложения на 3 периода (не уверен, что это то, что вы хотите), вы можете использовать это регулярное выражение:

import re

text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
sentences = re.split(r'\.{3}', text)

for stuff in sentences:
     print(stuff)