Как разбить текст на предложения?

109

У меня есть текстовый файл. Мне нужен список предложений.

Как это можно реализовать? Есть много тонкостей, например, использование точки в сокращениях.

Мое старое регулярное выражение работает плохо:

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)
Артём
источник
18
Дайте определение «приговору».
Мартино 01
Я хочу это сделать, но хочу разделить, где есть точка или
новая строка

Ответы:

152

В наборе средств естественного языка ( nltk.org ) есть все, что вам нужно. Эта групповая публикация указывает, что это делает это:

import nltk.data

tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
fp = open("test.txt")
data = fp.read()
print '\n-----\n'.join(tokenizer.tokenize(data))

(Я не пробовал!)

Нед Батчелдер
источник
3
@Artyom: Вероятно, он может работать с русским языком - посмотрите, может ли NLTK / pyNLTK работать «на каждом языке» (т.е. не на английском) и как? .
Мартино 02
4
@Artyom: Вот прямая ссылка на онлайн-документацию для nltk .tokenize.punkt.PunktSentenceTokenizer.
Мартино 02
10
Возможно, вам придется nltk.download()сначала выполнить и загрузить модели ->punkt
Мартин Тома
2
Это не работает в случаях с закрывающими кавычками. Если у нас есть предложение, которое заканчивается как «это».
Fosa
1
Ладно, вы меня убедили. Но я только что проверил, и вроде бы ничего не вышло. Мой ввод 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'и мой вывод Мне ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']кажется правильным.
szedjani
102

Эта функция может разбить весь текст Гекльберри Финна на предложения примерно за 0,1 секунды и обрабатывает многие из наиболее болезненных крайних случаев, которые делают синтаксический анализ предложений нетривиальным, например: « Мистер Джон Джонсон-младший родился в США, но получил докторскую степень. Д. в Израиле до прихода в Nike Inc. в качестве инженера. Он также работал на сайте craigslist.org бизнес-аналитиком ».

# -*- coding: utf-8 -*-
import re
alphabets= "([A-Za-z])"
prefixes = "(Mr|St|Mrs|Ms|Dr)[.]"
suffixes = "(Inc|Ltd|Jr|Sr|Co)"
starters = "(Mr|Mrs|Ms|Dr|He\s|She\s|It\s|They\s|Their\s|Our\s|We\s|But\s|However\s|That\s|This\s|Wherever)"
acronyms = "([A-Z][.][A-Z][.](?:[A-Z][.])?)"
websites = "[.](com|net|org|io|gov)"

def split_into_sentences(text):
    text = " " + text + "  "
    text = text.replace("\n"," ")
    text = re.sub(prefixes,"\\1<prd>",text)
    text = re.sub(websites,"<prd>\\1",text)
    if "Ph.D" in text: text = text.replace("Ph.D.","Ph<prd>D<prd>")
    text = re.sub("\s" + alphabets + "[.] "," \\1<prd> ",text)
    text = re.sub(acronyms+" "+starters,"\\1<stop> \\2",text)
    text = re.sub(alphabets + "[.]" + alphabets + "[.]" + alphabets + "[.]","\\1<prd>\\2<prd>\\3<prd>",text)
    text = re.sub(alphabets + "[.]" + alphabets + "[.]","\\1<prd>\\2<prd>",text)
    text = re.sub(" "+suffixes+"[.] "+starters," \\1<stop> \\2",text)
    text = re.sub(" "+suffixes+"[.]"," \\1<prd>",text)
    text = re.sub(" " + alphabets + "[.]"," \\1<prd>",text)
    if "”" in text: text = text.replace(".”","”.")
    if "\"" in text: text = text.replace(".\"","\".")
    if "!" in text: text = text.replace("!\"","\"!")
    if "?" in text: text = text.replace("?\"","\"?")
    text = text.replace(".",".<stop>")
    text = text.replace("?","?<stop>")
    text = text.replace("!","!<stop>")
    text = text.replace("<prd>",".")
    sentences = text.split("<stop>")
    sentences = sentences[:-1]
    sentences = [s.strip() for s in sentences]
    return sentences
Д. Гринберг
источник
19
Это отличное решение. Однако я добавил к нему еще две строки digits = "([0-9])" в объявлении регулярных выражений и text = re.sub (digits + "[.]" + Digits, "\\ 1 <prd> \ \ 2 ", текст) в функции. Теперь он не разделяет строку на десятичные дроби, такие как 5.5. Спасибо за этот ответ.
Ameya Kulkarni
1
Как вы разобрали весь Гекльберри Фин? Где это в текстовом формате?
PascalVKooten
6
Отличное решение. В этой функции я добавил if «eg» в текст: text = text.replace («eg», «e <prd> g <prd>») if «ie» в тексте: text = text.replace («ie» , "i <prd> e <prd>"), и это полностью решило мою проблему.
Sisay Chala
3
Отличное решение с очень полезными комментариями! Просто , чтобы сделать его немного более надежным , хотя: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)"иif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz
1
Можно ли заставить эту функцию видеть такие предложения как одно предложение: Когда ребенок спрашивает свою мать: «Откуда берутся дети?», Что ей следует ответить?
twhale
50

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

>>> from nltk import tokenize
>>> p = "Good morning Dr. Adams. The patient is waiting for you in room number 3."

>>> tokenize.sent_tokenize(p)
['Good morning Dr. Adams.', 'The patient is waiting for you in room number 3.']

ссылка: https://stackoverflow.com/a/9474645/2877052

Хасан Раза
источник
Отличный, более простой и более пригодный для повторного использования пример, чем принятый ответ.
Джей Д.
Если вы удалите пробел после точки, tokenize.sent_tokenize () не будет работать, но tokenizer.tokenize () будет работать! Хм ...
Леонид Ганелин 08
1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Виктория Стюарт,
11

Вы можете попробовать использовать Spacy вместо регулярного выражения. Я использую его, и он выполняет свою работу.

import spacy
nlp = spacy.load('en')

text = '''Your text here'''
tokens = nlp(text)

for sent in tokens.sents:
    print(sent.string.strip())
Эльф
источник
1
Пространство мега великое. но если вам просто нужно разделить на предложения, передача текста в пробел займет слишком много времени, если вы имеете дело с конвейером данных
Berlines
@Berlines Я согласен, но не нашел другой библиотеки, которая бы выполняла такую ​​же работу, как spaCy. Но если у вас есть предложения, я могу попробовать.
Эльф
Также для пользователей AWS Lambda Serverless файлы данных поддержки spacy имеют размер 100 МБ (большой размер на английском -> 400 МБ), поэтому вы не можете использовать такие вещи из коробки, к сожалению (большой поклонник Spacy здесь)
Джулиан Х.
9

Вот середина пути, который не полагается на какие-либо внешние библиотеки. Я использую понимание списка, чтобы исключить перекрытия между аббревиатурами и терминаторами, а также для исключения совпадений между вариантами завершения, например: '.' против '. "'

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)

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

ТеннисВизуалы
источник
1
Идеальный подход! Остальные не ловят ...и ?!.
Шейн Смискол
6

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

import re
text = ''.join(open('somefile.txt').readlines())
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)

Регулярное выражение *\. + соответствует периоду, окруженному 0 или более пробелами слева и 1 или более справа (чтобы что-то вроде точки в re.split не учитывалось как изменение предложения).

Очевидно, не самое надежное решение, но в большинстве случаев оно подойдет. Единственный случай, который это не касается, - это сокращения (возможно, просмотрите список предложений и убедитесь, что каждая строка в нем sentencesначинается с заглавной буквы?)

Рэйф Кеттлер
источник
29
Вы не можете представить себе ситуацию на английском языке, когда предложение не заканчивается точкой? Представьте себе, что! Я бы ответил на это «подумай еще раз». (Видите, что я там делал?)
Нед Батчелдер
@ Нед вау, не могу поверить, что я был таким глупым. Я должен быть пьян или что-то в этом роде.
Rafe Kettler
Я использую Python 2.7.2 в Win 7 x86, и регулярное выражение в приведенном выше коде дает мне эту ошибку:, SyntaxError: EOL while scanning string literalуказывая на закрывающую скобку (после text). Кроме того, регулярное выражение, на которое вы ссылаетесь в своем тексте, не существует в вашем примере кода.
Sabuncu
1
Регулярное выражение не совсем правильное, каким должно бытьr' *[\.\?!][\'"\)\]]* +'
fsociety
Это может вызвать множество проблем, а также разбить предложение на более мелкие части. Рассмотрим случай, когда у нас есть «Я заплатил 3,5 доллара за это мороженое», а кусками - «Я заплатил 3 доллара» и «5 за это мороженое». используйте предложение nltk по умолчанию. токенизатор безопаснее!
Reihan_amn
6

Вы также можете использовать функцию токенизации предложений в NLTK:

from nltk.tokenize import sent_tokenize
sentence = "As the most quoted English writer Shakespeare has more than his share of famous quotes.  Some Shakespare famous quotes are known for their beauty, some for their everyday truths and some for their wisdom. We often talk about Shakespeare’s quotes as things the wise Bard is saying to us but, we should remember that some of his wisest words are spoken by his biggest fools. For example, both ‘neither a borrower nor a lender be,’ and ‘to thine own self be true’ are from the foolish, garrulous and quite disreputable Polonius in Hamlet."

sent_tokenize(sentence)
амиреф
источник
2

@ Артём,

Здравствуй! Вы можете создать новый токенизатор для русского (и некоторых других языков) с помощью этой функции:

def russianTokenizer(text):
    result = text
    result = result.replace('.', ' . ')
    result = result.replace(' .  .  . ', ' ... ')
    result = result.replace(',', ' , ')
    result = result.replace(':', ' : ')
    result = result.replace(';', ' ; ')
    result = result.replace('!', ' ! ')
    result = result.replace('?', ' ? ')
    result = result.replace('\"', ' \" ')
    result = result.replace('\'', ' \' ')
    result = result.replace('(', ' ( ')
    result = result.replace(')', ' ) ') 
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.strip()
    result = result.split(' ')
    return result

а затем назовите его так:

text = 'вы выполняете поиск, используя Google SSL;'
tokens = russianTokenizer(text)

Удачи, Марилена.

Марилена Ди Бари
источник
0

Несомненно, НЛТК наиболее подходит для этой цели. Но начать работу с NLTK довольно болезненно (но как только вы его установите - вы просто пожнете плоды)

Итак, вот простой код на основе re, доступный по адресу http://pythonicprose.blogspot.com/2009/09/python-split-paragraph-into-sentences.html

# split up a paragraph into sentences
# using regular expressions


def splitParagraphIntoSentences(paragraph):
    ''' break a paragraph into sentences
        and return a list '''
    import re
    # to split by multile characters

    #   regular expressions are easiest (and fastest)
    sentenceEnders = re.compile('[.!?]')
    sentenceList = sentenceEnders.split(paragraph)
    return sentenceList


if __name__ == '__main__':
    p = """This is a sentence.  This is an excited sentence! And do you think this is a question?"""

    sentences = splitParagraphIntoSentences(p)
    for s in sentences:
        print s.strip()

#output:
#   This is a sentence
#   This is an excited sentence

#   And do you think this is a question 
Vaichidrewar
источник
3
Да, но это так легко терпит неудачу со словами: «Мистер Смит знает, что это приговор».
Томас
0

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

# Very approximate way to split the text into sentences - Break after ? . and !
fullFile = re.sub("(\!|\?|\.) ","\\1<BRK>",fullFile)
sentences = fullFile.split("<BRK>");
sentFile = open("./sentences.out", "w+");
for line in sentences:
    sentFile.write (line);
    sentFile.write ("\n");
sentFile.close;

Ой! хорошо. Теперь я понимаю, что, поскольку мой контент был испанским, у меня не было проблем с «мистером Смитом» и т. Д. Тем не менее, если кому-то нужен быстрый и грязный парсер ...

Кишор
источник
0

Я надеюсь, что это поможет вам с латинским, китайским, арабским текстом

import re

punctuation = re.compile(r"([^\d+])(\.|!|\?|;|\n|。|!|?|;|…| |!|؟|؛)+")
lines = []

with open('myData.txt','r',encoding="utf-8") as myFile:
    lines = punctuation.sub(r"\1\2<pad>", myFile.read())
    lines = [line.strip() for line in lines.split("<pad>") if line.strip()]
мамтимены
источник
0

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

from nltk.tokenize import sent_tokenize 
  
text = "Hello everyone. Welcome to GeeksforGeeks. You are studying NLP article"
sent_tokenize(text) 

вывод:

['Hello everyone.',
 'Welcome to GeeksforGeeks.',
 'You are studying NLP article']

Источник: https://www.geeksforgeeks.org/nlp-how-tokenizing-text-sentence-words-works/

Мазин Мухаммед
источник