wake-up-neo.net

Python - RegEx zum Aufteilen von Text in Sätze (Satz-tokenizing)

Ich möchte eine Liste von Sätzen aus einer Zeichenfolge erstellen und sie dann ausdrucken. Ich möchte nicht NLTK verwenden, um dies zu tun. Es muss also nach einem Punkt am Ende des Satzes aufgeteilt werden und nicht nach Dezimalzahlen oder Abkürzungen oder Titel eines Namens oder wenn der Satz eine .com hat. Dies ist der Versuch einer Regex, der nicht funktioniert.

import re

text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)

for stuff in sentences:
        print(stuff)    

Beispielausgabe wie es aussehen soll

Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. 
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
17
user3590149
(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s

Versuche dies. Teilen Sie Ihre Zeichenfolge hier auf. Sie können auch die Demo überprüfen.

http://regex101.com/r/nG1gU7/27

24
vks

Okay, Satz-Tokenizer sind etwas, das ich mir mit Regexes, nltk und CoreNLP genauer angesehen habe. Am Ende schreiben Sie Ihre eigenen und es hängt von der Anwendung ab. Dieses Zeug ist knifflig und wertvoll und die Leute geben nicht nur ihren Tokenizer-Code weiter. (Letztendlich ist die Tokenisierung kein deterministisches Verfahren, sondern wahrscheinlich und hängt auch sehr stark von Ihrem Korpus oder Ihrer Domain ab, z. B. Social-Media-Posts im Vergleich zu Yelp-Bewertungen im Vergleich zu ...)

Im Allgemeinen kann man sich nicht auf einen einzigen unfehlbaren Regex von Great White verlassen , man muss eine Funktion schreiben, die mehrere (sowohl positive als auch negative) Regexe verwendet; auch ein Wörterbuch der Abkürzungen und einige grundlegende Sprachanalyse, die weiß, dass z.B. 'I', 'USA', 'FCC', 'TARP' werden in englischer Sprache geschrieben.

Um zu veranschaulichen, wie leicht dies ernsthaft kompliziert werden kann, versuchen wir, Ihnen diese Funktionsspezifikation für einen deterministischen Tokenizer zu schreiben nur, um zu entscheiden, ob einzelne oder mehrere Punkte ('.'/'...') gibt das Ende des Satzes oder etwas anderes an:

function isEndOfSentence(leftContext, rightContext)

  1. Return False für Dezimalstellen innerhalb von Zahlen oder Währungen, z. 1.23, $ 1.23, "Das ist nur mein $ .02" Beachten Sie auch Abschnittsreferenzen wie 1.2.3, europäische Datumsformate wie 09.07.2014, IP-Adressen wie 192.168.1.1, MAC-Adressen ...
  2. Geben Sie für bekannte Abkürzungen, z. "US-Aktien fallen"; Dies erfordert ein Wörterbuch mit bekannten Abkürzungen. Alles, was sich außerhalb dieses Wörterbuchs befindet, wird falsch sein, es sei denn, Sie fügen Code hinzu, um unbekannte Abkürzungen wie A.B.C. und füge sie einer Liste hinzu.
  3. Ellipsen '...' am Satzende sind endständig, in der Satzmitte jedoch nicht. Dies ist nicht so einfach, wie Sie vielleicht denken: Sie müssen sich den linken und den rechten Kontext ansehen, insbesondere wird die RHS großgeschrieben und es werden wieder großgeschriebene Wörter wie 'I' und Abkürzungen berücksichtigt. Hier ist ein Beispiel, das Unklarheiten beweist: Sie hat mich gebeten zu bleiben ... Ich bin eine Stunde später gegangen. (War das ein oder zwei Sätze? Unmöglich zu bestimmen)
  4. Vielleicht möchten Sie auch ein paar Muster schreiben, um verschiedene nicht satzende Interpunktionszeichen zu erkennen und abzulehnen: Emoticons :-), ASCII art, beabstandete Ellipsen ... und andere Dinge, insb. Twitter. (Adaptiv zu machen ist noch schwieriger.) Woran erkennt man, dass @midnight ein Twitter-Benutzer ist, das auf Comedy Central anzeigen , eine Textkürzel oder einfach eine unerwünschte/Junk/Tippfehler-Interpunktion? -trivial.
  5. Nachdem Sie alle diese negativen Fälle behandelt haben, können Sie willkürlich sagen, dass jede einzelne Periode, auf die ein Leerzeichen folgt, wahrscheinlich ein Ende des Satzes bedeutet. (Wenn Sie wirklich mehr Genauigkeit wünschen, schreiben Sie am Ende Ihren eigenen Wahrscheinlichkeitssatz-Tokenizer, der Gewichte verwendet, und trainieren ihn auf einem bestimmten Korpus (z. B. Gesetzestexte, Rundfunkmedien, StackOverflow, Twitter, Foren-Kommentare usw.). ) Dann müssen Sie die Beispiele und Übungsfehler manuell überprüfen. Siehe Manning- und Jurafsky-Buch oder Coursera-Kurs [a]. Letztendlich erhalten Sie so viel Korrektheit, wie Sie bereit sind zu bezahlen.
  6. Alle obigen Angaben beziehen sich eindeutig auf die englischsprachigen/abgekürzten US-amerikanischen Zahlen-/Zeit-/Datumsformate. Wenn Sie es landes- und sprachunabhängig machen möchten, ist dies eine größere Herausforderung. Sie benötigen Korpora, Muttersprachler, die es kennzeichnen und die Qualitätssicherung durchführen usw.
  7. Alles oben Genannte ist immer noch nur ASCII. Zulassen, dass die Eingabe Unicode ist und es noch schwieriger wird (und die Trainingsmenge muss entweder viel größer oder viel sparsamer sein)

Im einfachen (deterministischen) Fall würde function isEndOfSentence(leftContext, rightContext) einen Booleschen Wert zurückgeben, im allgemeineren Sinne ist dies jedoch probabilistisch: Es wird ein Gleitkommawert von 0.0-1.0 zurückgegeben (Konfidenzniveau, bei dem dieses bestimmte '.' Ein Satzende ist). .

Referenzen: [a] Coursera-Video: "Grundlegende Textverarbeitung 2-5 - Satzsegmentierung - Stanford NLP - Professor Dan Jurafsky & Chris Manning" [UPDATE: eine inoffizielle Version, die früher auf YouTube war, wurde entfernt]

27
smci

Versuchen Sie, die Eingabe nach den Leerzeichen und nicht nach einem Punkt oder ? aufzuteilen. Wenn Sie dies tun, wird der Punkt oder ? nicht im Endergebnis gedruckt.

>>> import re
>>> s = """Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't."""
>>> m = re.split(r'(?<=[^A-Z].[.?]) +(?=[A-Z])', s)
>>> for i in m:
...     print i
... 
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it.
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
4
Avinash Raj
sent = re.split('(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)',text)
for s in sent:
    print s

Hier ist der verwendete Regex: (?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)

Erster Block: (?<!\w\.\w.): Dieses Muster sucht in einer negativen Rückkopplungsschleife (?<!) nach allen Wörtern (\w), gefolgt von fullstop (\.), gefolgt von anderen Wörtern (\.).

Zweiter Block: (?<![A-Z][a-z]\.): Dieses Muster sucht in einer negativen Rückkopplungsschleife nach etwas, das mit Großbuchstaben ([A-Z]) beginnt, gefolgt von Kleinbuchstaben ([a-z]), bis ein Punkt (\.) gefunden wird.

Dritter Block: (?<=\.|\?): Dieses Muster sucht in einer Rückkopplungsschleife von Punkt (\.) OR Fragezeichen (\?)

Vierter Block: (\s|[A-Z].*): Dieses Muster sucht nach dem Fragezeichen OR aus dem dritten Block. Es sucht nach Leerzeichen (\s) OR, einer beliebigen Zeichenfolge, die mit einem Großbuchstaben ([A-Z].*)..__ beginnt. Dieser Block ist wichtig zu teilen, wenn die Eingabe as ist 

Hallo world.Hi ich bin heute hier.

d.h. wenn nach dem Punkt Platz oder kein Platz vorhanden ist.

2
Mehul Gupta

Versuche dies:

(?<!\b(?:[A-Z][a-z]|\d|[i.e]))\.(?!\b(?:com|\d+)\b)
0
walid toumi

Mein Beispiel basiert auf dem Beispiel von ALi, angepasst an brasilianisches Portugiesisch. Danke ALi.

ABREVIACOES = ['sra?s?', 'exm[ao]s?', 'ns?', 'nos?', 'doc', 'ac', 'publ', 'ex', 'lv', 'vlr?', 'vls?',
               'exmo(a)', 'ilmo(a)', 'av', 'of', 'min', 'livr?', 'co?ls?', 'univ', 'resp', 'cli', 'lb',
               'dra?s?', '[a-z]+r\(as?\)', 'ed', 'pa?g', 'cod', 'prof', 'op', 'plan', 'edf?', 'func', 'ch',
               'arts?', 'artigs?', 'artg', 'pars?', 'rel', 'tel', 'res', '[a-z]', 'vls?', 'gab', 'bel',
               'ilm[oa]', 'parc', 'proc', 'adv', 'vols?', 'cels?', 'pp', 'ex[ao]', 'eg', 'pl', 'ref',
               '[0-9]+', 'reg', 'f[ilí]s?', 'inc', 'par', 'alin', 'fts', 'publ?', 'ex', 'v. em', 'v.rev']

ABREVIACOES_RGX = re.compile(r'(?:{})\.\s*$'.format('|\s'.join(ABREVIACOES)), re.IGNORECASE)

        def sentencas(texto, min_len=5):
            # baseado em https://stackoverflow.com/questions/25735644/python-regex-for-splitting-text-into-sentences-sentence-tokenizing
            texto = re.sub(r'\s\s+', ' ', texto)
            EndPunctuation = re.compile(r'([\.\?\!]\s+)')
            # print(NonEndings)
            parts = EndPunctuation.split(texto)
            sentencas = []
            sentence = []
            for part in parts:
                txt_sent = ''.join(sentence)
                q_len = len(txt_sent)
                if len(part) and len(sentence) and q_len >= min_len and \
                        EndPunctuation.match(sentence[-1]) and \
                        not ABREVIACOES_RGX.search(txt_sent):
                    sentencas.append(txt_sent)
                    sentence = []

                if len(part):
                    sentence.append(part)
            if sentence:
                sentencas.append(''.join(sentence))
            return sentencas

Vollständiger Code in: https://github.com/luizanisio/comparador_elastic

0
Luiz Anísio

Naiver Ansatz für korrekte englische Sätze, die nicht mit Nicht-Alphas beginnen und keine zitierten Wortteile enthalten:

import re
text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
EndPunctuation = re.compile(r'([\.\?\!]\s+)')
NonEndings = re.compile(r'(?:Mrs?|Jr|i\.e)\.\s*$')
parts = EndPunctuation.split(text)
sentence = []
for part in parts:
  if len(part) and len(sentence) and EndPunctuation.match(sentence[-1]) and not NonEndings.search(''.join(sentence)):
    print(''.join(sentence))
    sentence = []
  if len(part):
    sentence.append(part)
if len(sentence):
  print(''.join(sentence))

Falsch positive Aufteilung kann reduziert werden, indem NonEndings etwas erweitert wird. Für andere Fälle wird zusätzlicher Code benötigt. Ein vernünftiger Umgang mit Tippfehlern wird bei diesem Ansatz schwierig sein.

Mit diesem Ansatz werden Sie niemals Perfektion erreichen. Aber je nach Aufgabe klappt es vielleicht "genug" ...

0
Ali

Ich habe dies unter Berücksichtigung der oben genannten Kommentare von smci geschrieben. Es ist ein Mittelweg, der keine externen Bibliotheken erfordert und keinen Regex verwendet. Damit können Sie eine Liste von Abkürzungen und Konten für Sätze bereitstellen, die durch Abschlusszeichen in Wrappern beendet werden, z. B. ein Punkt und ein Zitat: [. ",? ',.)].

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 Karls find_all-Funktion aus diesem Eintrag verwendet: Alle Vorkommen eines Teilstrings in Python suchen

0
TennisVisuals

Ich bin nicht gut in regulären Ausdrücken, aber eine einfachere Version, "rohe Gewalt", ist eigentlich von oben 

sentence = re.compile("([\'\"][A-Z]|([A-Z][a-z]*\. )|[A-Z])(([a-z]*\.[a-z]*\.)|([A-Za-z0-9]*\.[A-Za-z0-9])|([A-Z][a-z]*\. [A-Za-z]*)|[^\.?]|[A-Za-z])*[\.?]")

was bedeutet akzeptable Einheiten sind '[A-Z] oder "[A-Z]
Bitte beachten Sie, dass die meisten regulären Ausdrücke gierig sind, daher ist die Reihenfolge sehr wichtig, wenn wir |(oder). Deshalb habe ich zuerst geschrieben. Regulärer Ausdruck, dann kommen Formen wie Inc.

0
Priyank Pathak