¿Cómo puedo dividir un texto en oraciones?

108

Tengo un archivo de texto. Necesito obtener una lista de oraciones.

¿Cómo se puede implementar esto? Hay muchas sutilezas, como el uso de un punto en las abreviaturas.

Mi antigua expresión regular funciona mal:

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)
Artyom
fuente
18
Defina "oración".
martineau
quiero hacer esto, pero quiero dividir donde haya un punto o una nueva línea
yishairasowsky

Respuestas:

152

El kit de herramientas de lenguaje natural ( nltk.org ) tiene lo que necesita. Esta publicación grupal indica que esto lo hace:

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

(¡No lo he probado!)

Ned Batchelder
fuente
3
@Artyom: Probablemente pueda funcionar con ruso; vea, ¿ puede NLTK / pyNLTK funcionar “por idioma” (es decir, no inglés) y cómo? .
martineau
4
@Artyom: aquí hay un enlace directo a la documentación en línea de nltk .tokenize.punkt.PunktSentenceTokenizer.
martineau
10
Puede que tenga que ejecutar nltk.download()primero y descargar modelos ->punkt
Martin Thoma
2
Esto falla en los casos con comillas finales. Si tenemos una oración que termina así.
Fosa
1
Está bien, me convenciste. Pero acabo de probar y no parece fallar. Mi entrada es 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'y mi salida ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']parece correcta para mí.
szedjani
100

Esta función puede dividir todo el texto de Huckleberry Finn en oraciones en aproximadamente 0.1 segundos y maneja muchos de los casos extremos más dolorosos que hacen que el análisis de oraciones no sea trivial, por ejemplo, "El Sr. John Johnson Jr. nació en los EE. UU. Pero obtuvo su Ph. D. en Israel antes de unirse a Nike Inc. como ingeniero. También trabajó en craigslist.org como analista de negocios " .

# -*- 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
fuente
19
Esta es una solución asombrosa. Sin embargo, agregué dos líneas más digits = "([0-9])" en la declaración de expresiones regulares y text = re.sub (digits + "[.]" + Digits, "\\ 1 <prd> \ \ 2 ", texto) en la función. Ahora no divide la línea en decimales como 5.5. Gracias por esta respuesta.
Ameya Kulkarni
1
¿Cómo analizó todo el Huckleberry Fin? ¿Dónde está eso en formato de texto?
PascalVKooten
6
Una gran solucion. En la función, agregué si "eg" en texto: text = text.replace ("eg", "e <prd> g <prd>") if "ie" en texto: text = text.replace ("ie" , "i <prd> e <prd>") y resolvió completamente mi problema.
Sisay Chala
3
¡Gran solución con comentarios muy útiles! Sólo para que quede un poco más, aunque robusta: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)"yif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz
1
¿Se puede hacer esta función para ver oraciones como esta como una sola oración: Cuando un niño le pregunta a su madre "¿De dónde vienen los bebés?", ¿Qué debería responderle?
twhale
50

En lugar de usar expresiones regulares para dividir el texto en oraciones, también puede usar la biblioteca 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.']

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

Hassan Raza
fuente
Ejemplo genial, más simple y más reutilizable que la respuesta aceptada.
Jay D.
Si elimina un espacio después de un punto, tokenize.sent_tokenize () no funciona, ¡pero tokenizer.tokenize () funciona! Hmm ...
Leonid Ganeline
1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Victoria Stuart
11

Puede intentar usar Spacy en lugar de regex. Lo uso y hace el trabajo.

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

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

for sent in tokens.sents:
    print(sent.string.strip())
Duende
fuente
1
El espacio es mega grandioso. pero si solo necesita separar en oraciones, pasar el texto al espacio llevará demasiado tiempo si se trata de una tubería de datos
Berlines
@Berlines Estoy de acuerdo, pero no pude encontrar ninguna otra biblioteca que haga el trabajo tan limpio como spaCy. Pero si tienes alguna sugerencia, puedo intentarlo.
Elf
También para los usuarios de AWS Lambda Serverless, los archivos de datos de soporte de Spacy son muchos de 100 MB (el tamaño en inglés es> 400 MB), por lo que no puede usar cosas como esta lista para usar, muy tristemente (gran fan de Spacy aquí)
Julian H
9

Este es un enfoque intermedio que no depende de bibliotecas externas. Utilizo la comprensión de listas para excluir superposiciones entre abreviaturas y terminadores, así como para excluir superposiciones entre variaciones en terminaciones, por ejemplo: '.' vs. '. "'

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)

Usé la función find_all de Karl de esta entrada: Encuentra todas las apariciones de una subcadena en Python

TenisVisuales
fuente
1
¡Enfoque perfecto! Los demás no captan ...y ?!.
Shane Smiskol
6

Para casos simples (donde las oraciones terminan normalmente), esto debería funcionar:

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

La expresión regular es *\. +, que coincide con un punto rodeado por 0 o más espacios a la izquierda y 1 o más a la derecha (para evitar que algo como el punto en re.split se cuente como un cambio en la oración).

Obviamente, no es la solución más sólida, pero funcionará bien en la mayoría de los casos. El único caso que esto no cubrirá son las abreviaturas (¿tal vez revise la lista de oraciones y verifique que cada cadena sentencescomience con una letra mayúscula?)

Rafe Kettler
fuente
29
¿No puedes pensar en una situación en inglés donde una oración no termine con un punto? ¡Imagina eso! Mi respuesta a eso sería, "piénselo de nuevo". (¿Ves lo que hice allí?)
Ned Batchelder
@Ned wow, no puedo creer que fuera tan estúpido. Debo estar borracho o algo así.
Rafe Kettler
Estoy usando Python 2.7.2 en Win 7 x86, y la expresión regular en el código anterior me da este error:, SyntaxError: EOL while scanning string literalapuntando al paréntesis de cierre (después text). Además, la expresión regular a la que hace referencia en su texto no existe en su ejemplo de código.
Sabuncu
1
La expresión regular no es completamente correcta, como debería serr' *[\.\?!][\'"\)\]]* +'
fsociety
Puede causar muchos problemas y dividir una oración en partes más pequeñas. Considere el caso de que tenemos "Pagué $ 3.5 por este helado", los trozos son "Pagué $ 3" ​​y "5 por este helado". use la sentencia nltk predeterminada. ¡El tokenizador es más seguro!
Reihan_amn
6

También puede utilizar la función de tokenización de frases en 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)
amiref
fuente
2

@Artyom,

¡Hola! Puede crear un nuevo tokenizador para ruso (y algunos otros idiomas) usando esta función:

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

y luego llámalo de esta manera:

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

Buena suerte, Marilena.

Marilena Di Bari
fuente
0

Sin duda, NLTK es el más adecuado para este propósito. Pero comenzar con NLTK es bastante doloroso (pero una vez que lo instalas, solo cosechas las recompensas)

Así que aquí hay un código re basado simple disponible en 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
fuente
3
Sí, pero esto falla tan fácilmente, con: "El Sr. Smith sabe que esto es una oración".
Thomas
0

Tuve que leer archivos de subtítulos y dividirlos en oraciones. Después del preprocesamiento (como eliminar información de tiempo, etc. en los archivos .srt), la variable fullFile contenía el texto completo del archivo de subtítulos. La siguiente manera cruda los dividió cuidadosamente en oraciones. Probablemente tuve suerte de que las frases siempre terminaran (correctamente) con un espacio. Pruebe esto primero y, si tiene alguna excepción, agregue más controles y equilibrios.

# 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! bien. Ahora me doy cuenta de que, dado que mi contenido era español, no tuve problemas con el "Sr. Smith", etc. Aún así, si alguien quiere un analizador rápido y sucio ...

kishore
fuente
0

Espero que esto te ayude con el texto latino, chino y árabe.

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
fuente
0

Estaba trabajando en una tarea similar y encontré esta consulta, siguiendo algunos enlaces y trabajando en algunos ejercicios para nltk, el siguiente código funcionó para mí como magia.

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

salida:

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

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

Mazeen Muhammed
fuente