Wie kann ich einen Text in Sätze aufteilen?


107

Ich habe eine Textdatei. Ich brauche eine Liste von Sätzen.

Wie kann dies umgesetzt werden? Es gibt viele Feinheiten, z. B. einen Punkt, der in Abkürzungen verwendet wird.

Mein alter regulärer Ausdruck funktioniert schlecht:

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

18
Definieren Sie "Satz".
Martineau

Ich möchte dies tun, aber ich möchte teilen, wo immer es entweder einen Punkt oder eine neue
Zeile

Antworten:


152

Das Natural Language Toolkit ( nltk.org ) bietet alles, was Sie brauchen. Diese Gruppenbuchung zeigt an, dass dies der Fall ist:

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

(Ich habe es nicht versucht!)


3
@Artyom: Es kann wahrscheinlich mit Russisch funktionieren - siehe, kann NLTK / pyNLTK "pro Sprache" (dh nicht Englisch) funktionieren und wie? .
Martineau

4
@Artyom: Hier ist ein direkter Link zur Online-Dokumentation für nltk .tokenize.punkt.PunktSentenceTokenizer.
Martineau

10
Möglicherweise müssen Sie nltk.download()zuerst ausführen und Modelle herunterladen ->punkt
Martin Thoma

2
Dies schlägt in Fällen mit endenden Anführungszeichen fehl. Wenn wir einen Satz haben, der so endet.
Fosa

1
Okay, du hast mich überzeugt. Aber ich habe gerade getestet und es scheint nicht zu scheitern. Meine Eingabe ist 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'und meine Ausgabe ist ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']Scheint für mich richtig.
Szedjani

99

Diese Funktion kann den gesamten Text von Huckleberry Finn in etwa 0,1 Sekunden in Sätze aufteilen und behandelt viele der schmerzhafteren Randfälle, die das Parsen von Sätzen nicht trivial machen, z. B. " Mr. John Johnson Jr. wurde in den USA geboren, hat aber seinen Doktortitel erworben. D. in Israel, bevor er als Ingenieur zu Nike Inc. kam. Er arbeitete auch als Business Analyst bei 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

18
Dies ist eine großartige Lösung. Ich habe jedoch zwei weitere Zeilen hinzugefügt, digits = "([0-9])" in der Deklaration der regulären Ausdrücke und text = re (digits + "[.]" + Digits, "\\ 1 <prd> \ \ 2 ", Text) in der Funktion. Jetzt wird die Linie nicht bei Dezimalstellen wie 5.5 geteilt. Vielen Dank für diese Antwort.
Ameya Kulkarni

1
Wie haben Sie die gesamte Huckleberry Fin analysiert? Wo ist das im Textformat?
PascalVKooten

6
Eine großartige Lösung. In der Funktion habe ich hinzugefügt, wenn "zB" im Text: text = text.replace ("zB", "e <prd> g <prd>"), wenn "dh" im Text: text = text.replace ("ie") , "i <prd> e <prd>") und es hat mein Problem vollständig gelöst.
Sisay Chala

3
Tolle Lösung mit sehr hilfreichen Kommentaren! Nur um es etwas robuster aber: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)"undif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz

1
Kann diese Funktion dazu gebracht werden, Sätze wie diesen als einen Satz zu sehen: Wenn ein Kind seine Mutter fragt "Woher kommen Babys?", Was sollte man ihr antworten?
Twhale

50

Anstatt Regex zum Aufteilen des Textes in Sätze zu verwenden, können Sie auch die nltk-Bibliothek verwenden.

>>> 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.']

Ref: https://stackoverflow.com/a/9474645/2877052


Tolles, einfacheres und wiederverwendbareres Beispiel als die akzeptierte Antwort.
Jay D.

Wenn Sie ein Leerzeichen nach einem Punkt entfernen, funktioniert tokenize.sent_tokenize () nicht, aber tokenizer.tokenize ()! Hmm ...
Leonid Ganeline

1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Victoria Stuart

11

Sie können versuchen, Spacy anstelle von Regex zu verwenden. Ich benutze es und es macht den Job.

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

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

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

1
Der Platz ist großartig. Aber wenn Sie nur in Sätze trennen müssen, die den Text an das Leerzeichen übergeben, wird es zu lange dauern, wenn Sie es mit einer
Datenpipe zu

@Berlines Ich stimme zu, konnte aber keine andere Bibliothek finden, die die Arbeit so sauber macht wie spaCy. Aber wenn Sie einen Vorschlag haben, kann ich es versuchen.
Elf

Auch für die AWS Lambda Serverless-Benutzer gibt es viele Support-Datendateien von spacy mit 100 MB (Englisch groß ist> 400 MB), so dass Sie solche Dinge leider nicht sofort verwenden können (großer Fan von Spacy hier)
Julian H.

9

Hier ist ein Ansatz mitten auf der Straße, der nicht auf externen Bibliotheken beruht. Ich verwende das Listenverständnis, um Überlappungen zwischen Abkürzungen und Terminatoren auszuschließen sowie um Überlappungen zwischen Variationen von Terminierungen auszuschließen, zum Beispiel: '.' '. "'

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)

Ich habe die Funktion find_all von Karl aus diesem Eintrag verwendet: Finde alle Vorkommen eines Teilstrings in Python


1
Perfekter Ansatz! Die anderen fangen nicht ...und ?!.
Shane Smiskol

6

In einfachen Fällen (in denen Sätze normal beendet werden) sollte dies funktionieren:

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

Der *\. +reguläre Ausdruck entspricht einem Punkt, der links von 0 oder mehr Leerzeichen und rechts von 1 oder mehr Leerzeichen umgeben ist (um zu verhindern, dass so etwas wie der Punkt in re.split als Satzänderung gezählt wird).

Natürlich nicht die robusteste Lösung, aber in den meisten Fällen reicht es aus. Der einzige Fall, der hier nicht behandelt wird, sind Abkürzungen (vielleicht die Liste der Sätze durchgehen und prüfen, ob jede Zeichenfolge sentencesmit einem Großbuchstaben beginnt?)


29
Sie können sich keine Situation auf Englisch vorstellen, in der ein Satz nicht mit einem Punkt endet? Stell dir das vor! Meine Antwort darauf wäre: "Denk noch einmal nach." (Sehen Sie, was ich dort getan habe?)
Ned Batchelder

@Ned wow, kann nicht glauben, dass ich so dumm war. Ich muss betrunken sein oder so.
Rafe Kettler

Ich verwende Python 2.7.2 unter Win 7 x86, und der reguläre Ausdruck im obigen Code gibt mir den folgenden Fehler: SyntaxError: EOL while scanning string literalund zeigt auf die schließende Klammer (nach text). Der Regex, auf den Sie in Ihrem Text verweisen, ist in Ihrem Codebeispiel nicht vorhanden.
Sabuncu

1
Die Regex ist nicht ganz korrekt, wie es sein sollter' *[\.\?!][\'"\)\]]* +'
Gesellschaft

Es kann viele Probleme verursachen und einen Satz auch in kleinere Teile aufteilen. Betrachten Sie den Fall, dass wir "Ich habe 3,5 Dollar für dieses Eis bezahlt" haben, die Stücke sind "Ich habe 3 Dollar bezahlt" und "5 für dieses Eis". Verwenden Sie den Standard-Satz nltk. Der Tokenizer ist sicherer!
Reihan_amn

6

Sie können die Satz-Tokenisierungsfunktion auch in NLTK verwenden:

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,

Hallo! Mit dieser Funktion können Sie einen neuen Tokenizer für Russisch (und einige andere Sprachen) erstellen:

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

und dann nenne es so:

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

Viel Glück, Marilena.


0

Kein Zweifel, dass NLTK für diesen Zweck am besten geeignet ist. Der Einstieg in NLTK ist jedoch ziemlich schmerzhaft (aber sobald Sie es installiert haben, ernten Sie einfach die Belohnungen)

Hier finden Sie einfachen rebasierten Code unter 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
Ja, aber das scheitert so leicht mit: "Mr. Smith weiß, dass dies ein Satz ist."
Thomas

0

Ich musste Untertiteldateien lesen und sie in Sätze aufteilen. Nach der Vorverarbeitung (wie dem Entfernen von Zeitinformationen usw. in den .srt-Dateien) enthielt die Variable fullFile den vollständigen Text der Untertiteldatei. Der folgende grobe Weg teilte sie ordentlich in Sätze auf. Wahrscheinlich hatte ich Glück, dass die Sätze immer (richtig) mit einem Leerzeichen endeten. Versuchen Sie dies zuerst und fügen Sie, wenn es Ausnahmen gibt, weitere Checks and Balances hinzu.

# 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;

Oh! Gut. Mir ist jetzt klar, dass ich, da mein Inhalt Spanisch war, nicht die Probleme hatte, mit "Mr. Smith" usw. umzugehen. Dennoch, wenn jemand einen schnellen und schmutzigen Parser will ...


0

Ich hoffe, dies wird Ihnen bei lateinischen, chinesischen und arabischen Texten helfen

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

Arbeitete an einer ähnlichen Aufgabe und stieß auf diese Abfrage, indem ich einigen Links folgte und an einigen Übungen für nltk arbeitete. Der folgende Code funktionierte für mich wie Magie.

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

Ausgabe:

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

Quelle: https://www.geeksforgeeks.org/nlp-how-tokenizing-text-sentence-words-works/

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.