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

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

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

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

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)

20 ответов

Решение

В Natural Language Toolkit ( 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))

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

Эта функция может разбить весь текст Гекльберри Финна на предложения примерно за 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

Вместо использования регулярных выражений для разбиения текста на предложения, вы также можете использовать библиотеку 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.']

ссылка: /questions/42001460/kak-razbit-abzats-po-predlozheniyam-v-python/42001468#42001468

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

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

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

for sent in tokens.sents:
    print(sent.string.strip())

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

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

Вы также можете использовать функцию токенизации предложений в 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)

Я до смерти люблю spaCy, но недавно обнаружил два новых подхода к токенизации предложений. Один из них — BlingFire от Microsoft (невероятно быстрый), а другой — PySBD (в высшей степени точный).

      text = ...

from blingfire import text_to_sentences
sents = text_to_sentences(text).split('\n')

from pysbd import Segmenter
segmenter = Segmenter(language='en', clean=False)
sents = segmenter.segment(text)

Я разделил 20 тысяч предложений, используя пять разных методов. Вот прошедшее время:

  • SpaCy Sentencizer: 1,16934 с
  • spaCy Анализ: 25,97063 с
  • ПиСБД: 9.03505с
  • NLTK: 0,30512 с
  • BlingFire: 0,07933 с

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

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

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

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

Использование простора :

      import spacy

nlp = spacy.load('en_core_web_sm')
text = "How are you today? I hope you have a great day"
tokens = nlp(text)
for sent in tokens.sents:
    print(sent.string.strip())

Если NLTK sent_tokenize не является чем-то особенным (например, для длинного текста требуется много ОЗУ графического процессора) и регулярное выражение не работает должным образом на разных языках, можно попробовать разделитель предложений .

Использование Stanza — библиотеки обработки естественного языка, которая работает со многими человеческими языками.

      import stanza

stanza.download('en')
nlp = stanza.Pipeline(lang='en', processors='tokenize')

doc = nlp(t_en)
for sentence in doc.sentences:
    print(sentence.text)

Можно также добавить это, так как это первое сообщение, которое появилось с предложением, разделенным на n предложений.

Это работает с переменной длиной разделения, что указывает на предложения, которые в конце объединяются.

      import nltk
//nltk.download('punkt')
from more_itertools import windowed

split_length = 3 // 3 sentences for example 

elements = nltk.tokenize.sent_tokenize(text)
segments = windowed(elements, n=split_length, step=split_length)
text_splits = []
for seg in segments:
          txt = " ".join([t for t in seg if t])
          if len(txt) > 0:
                text_splits.append(txt)

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

Итак, вот простой пере-основанный код, доступный по адресу 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 

@Artyom,

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

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)

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

Используя Spacy v3.5:

      import spacy

nlp_sentencizer = spacy.blank("en")
nlp_sentencizer.add_pipe("sentencizer")

text = "How are you today? I hope you have a great day"
tokens = nlp_sentencizer(text)
[str(sent) for sent in tokens.sents]

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

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()]

Работал над аналогичной задачей и наткнулся на этот запрос, следуя нескольким ссылкам и работая над несколькими упражнениями для 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/

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

Например,.info, .biz, .ru, .online выдадут некоторые синтаксические анализаторы предложений, но они не включены выше.

Вот некоторая информация о частоте доменов верхнего уровня: https://www.westhost.com/blog/the-most-popular-top-level-domains-in-2017/

Эту проблему можно решить, отредактировав приведенный выше код следующим образом:

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|ai|edu|co.uk|ru|info|biz|online)"

Мне пришлось читать файлы субтитров и разбивать их на предложения. После предварительной обработки (например, удаления информации о времени и т. Д. В файлах.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;

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

используя просторный

      import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(u'This is first.This is second.This is Thired ')
for sentence in doc.sent:
  print(sentence)

Но если вы хотите получить предложение по индексу Пример:

      #don't work
 doc.sents[0]

Использовать

      list( doc.sents)[0]
Другие вопросы по тегам