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)
Artyom
quelle
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!)

Ned Batchelder
quelle
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
D Greenberg
quelle
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

Hassan Raza
quelle
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())
Elf
quelle
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

TennisVisuals
quelle
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?)

Rafe Kettler
quelle
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)
amiref
quelle
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.

Marilena Di Bari
quelle
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 
Vaichidrewar
quelle
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 ...

Kishore
quelle
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()]
mamtimen
quelle
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/

Mazeen Muhammed
quelle