Як можна розділити текст на речення?


108

У мене є текстовий файл. Мені потрібно отримати список речень.

Як це можна реалізувати? Існує маса тонкощів, наприклад, крапка, яка використовується в абревіатурах.

Мій старий регулярний вираз працює погано:

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

18
Визначте «речення».
мартіно

Я хочу це зробити, але я хочу поділитись там, де є період або новий рядок
yishairasowsky

Відповіді:


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 працювати "на кожну мову" (тобто, не англійською), і як? .
мартіно

4
@Artyom: Ось пряме посилання на онлайн-документацію для nltk .tokenize.punkt.PunktSentenceTokenizer.
мартіно

10
Можливо, вам доведеться спочатку виконати nltk.download()і завантажити моделі ->punkt
Мартін Тома

2
Це не вдається для випадків із закінченням лапок. Якщо у нас є речення, яке закінчується як "це".
Фоса

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

101

Ця функція може розділити весь текст Гекльберрі Фінна на речення приблизно за 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
Це приголомшливе рішення. Однак я додав до нього ще два рядки цифр = "([0-9])" у декларації регулярних виразів та text = re.sub (цифри + "[.]" + Цифри "\\ 1 <prd> \ \ 2 ", текст) у функції. Тепер він не розділяє лінію на десяткових знаках, таких як 5.5. Дякую за цю відповідь.
Амея Кулкарні

1
Як ти розібрав весь плавник Гекльберрі? Де це в текстовому форматі?
PascalVKooten

6
Прекрасне рішення. У функції я додав, якщо "наприклад" у тексті: text = text.replace ("наприклад", "e <prd> g <prd>"), якщо "тобто" у тексті: text = text.replace ("тобто" , "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 () працює! Хм ...
Леонід Ганелін

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 Я погоджуюся, але не зміг знайти жодної іншої бібліотеки, яка б робила цю роботу так само чисто, як spaCy. Але якщо у вас є якісь пропозиції, я можу спробувати.
Ельф

Також для користувачів AWS Lambda Serverless, файли даних про підтримку spacy - це багато 100MB (англійська велика> 400MB), тому ви не можете використовувати такі речі поза коробкою, дуже сумно (величезний шанувальник 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
Ви не можете придумати ситуацію в англійській мові, коли речення не закінчується періодом? Уяви що! Моя відповідь на це була б "подумай ще раз". (Дивіться, що я там робив?)
Нед Батчердер

@Ned wow, не можу повірити, я був таким дурним. Я повинен бути п’яний чи щось таке.
Рейф Кеттлер

Я використовую Python 2.7.2 на Win 7 x86, і регулярний вираз у наведеному вище коді надає мені цю помилку:, SyntaxError: EOL while scanning string literalвказуючи на круглі дужки (після text). Крім того, у зразку коду відсутній регулярний вираз, на який ви посилаєтесь у своєму тексті.
Сабунку

1
Регекс не зовсім коректний, як це має бутиr' *[\.\?!][\'"\)\]]* +'
fsociety

Це може спричинити багато проблем, а також пропозицію подати на менші шматки. Розглянемо випадок, що у нас "я заплатив 3,5 долара за це морозиво", вони були "я заплатив $ 3" ​​і "5 за це морозиво". використовуйте за замовчуванням предложение nltk.tokenizer безпечніше!
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

@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)

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


0

Без сумніву, NLTK є найбільш підходящим для цієї мети. Але розпочати роботу з 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 

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-tokeising-text-sentence-words-works/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.