Comment puis-je diviser un texte en phrases?

108

J'ai un fichier texte. J'ai besoin d'une liste de phrases.

Comment cela peut-il être mis en œuvre? Il y a beaucoup de subtilités, comme un point utilisé dans les abréviations.

Mon ancienne expression régulière fonctionne mal:

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)
Artyom
la source
18
Définissez «phrase».
martineau
je veux faire cela, mais je veux diviser là où il y a un point ou une nouvelle ligne
yishairasowsky

Réponses:

152

La boîte à outils Natural Language ( nltk.org ) a ce dont vous avez besoin. Cette publication de groupe indique que:

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

(Je ne l'ai pas essayé!)

Ned Batchelder
la source
3
@Artyom: Il peut probablement fonctionner avec le russe - voir NLTK / pyNLTK peut-il fonctionner «par langue» (c'est-à-dire non anglais), et comment? .
martineau
4
@Artyom: Voici un lien direct vers la documentation en ligne pour nltk .tokenize.punkt.PunktSentenceTokenizer.
martineau
10
Vous devrez peut-être exécuter d' nltk.download()abord et télécharger des modèles ->punkt
Martin Thoma
2
Cela échoue sur les cas avec des guillemets de fin. Si nous avons une phrase qui se termine par «ceci».
Fosa
1
D'accord, vous m'avez convaincu. Mais je viens de tester et cela ne semble pas échouer. Mon entrée est 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'et ma sortie ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']semble correcte pour moi.
szedjani
101

Cette fonction peut diviser le texte entier de Huckleberry Finn en phrases en environ 0,1 seconde et gère la plupart des cas les plus douloureux qui rendent l'analyse des phrases non triviale, par exemple " M. John Johnson Jr. est né aux États-Unis mais a obtenu son doctorat. D. en Israël avant de rejoindre Nike Inc. en tant qu'ingénieur. Il a également travaillé chez craigslist.org en tant qu'analyste commercial. "

# -*- 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
la source
19
C'est une solution formidable. Cependant, j'y ai ajouté deux autres lignes digits = "([0-9])" dans la déclaration des expressions régulières et text = re.sub (digits + "[.]" + Digits, "\\ 1 <prd> \ \ 2 ", texte) dans la fonction. Maintenant, il ne divise pas la ligne en décimales telles que 5,5. Merci pour cette réponse.
Ameya Kulkarni
1
Comment avez-vous analysé l'intégralité de Huckleberry Fin? Où est-ce au format texte?
PascalVKooten
6
Une excellente solution. Dans la fonction, j'ai ajouté si "eg" dans le texte: text = text.replace ("eg", "e <prd> g <prd>") si "ie" dans le texte: text = text.replace ("ie" , "i <prd> e <prd>") et cela a complètement résolu mon problème.
Sisay Chala
3
Excellente solution avec des commentaires très utiles! Juste pour en faire un peu plus si robuste: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)"etif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz
1
Cette fonction peut-elle être conçue pour voir des phrases comme celle-ci comme une seule phrase: Quand un enfant demande à sa mère «D'où viennent les bébés?», Que doit-on lui répondre?
twhale
50

Au lieu d'utiliser regex pour diviser le texte en phrases, vous pouvez également utiliser la bibliothèque 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.']

réf: https://stackoverflow.com/a/9474645/2877052

Hassan Raza
la source
Excellent exemple, plus simple et plus réutilisable que la réponse acceptée.
Jay D.
Si vous supprimez un espace après un point, tokenize.sent_tokenize () ne fonctionne pas, mais tokenizer.tokenize () fonctionne! Hmm ...
Leonid Ganeline
1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Victoria Stuart le
11

Vous pouvez essayer d'utiliser Spacy au lieu de regex. Je l'utilise et il fait le travail.

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

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

for sent in tokens.sents:
    print(sent.string.strip())
Elfe
la source
1
L'espace est génial. mais si vous avez juste besoin de séparer en phrases, passer le texte dans l'espace prendra trop de temps si vous avez affaire à un tube de données
Berlines
@Berlines Je suis d'accord, mais je n'ai trouvé aucune autre bibliothèque qui fasse le travail aussi propre que spaCy. Mais si vous avez des suggestions, je peux essayer.
Elf
De plus, pour les utilisateurs d'AWS Lambda Serverless, les fichiers de données de support de spacy font beaucoup 100 Mo (la taille de l'anglais est> 400 Mo), vous ne pouvez donc pas utiliser des choses comme celle-ci hors de la boîte, très malheureusement (grand fan de Spacy ici)
Julian H
9

Voici une approche intermédiaire qui ne repose sur aucune bibliothèque externe. J'utilise la compréhension de liste pour exclure les chevauchements entre les abréviations et les terminateurs ainsi que pour exclure les chevauchements entre les variations de terminaisons, par exemple: '.' contre. '."'

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)

J'ai utilisé la fonction find_all de Karl à partir de cette entrée: Trouver toutes les occurrences d'une sous-chaîne en Python

TennisVisuels
la source
1
Approche parfaite! Les autres n'attrapent pas ...et ?!.
Shane Smiskol
6

Pour les cas simples (où les phrases se terminent normalement), cela devrait fonctionner:

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

L'expression régulière est *\. +, qui correspond à un point entouré de 0 ou plus d'espaces à gauche et de 1 ou plus à droite (pour éviter que quelque chose comme le point dans re.split soit compté comme un changement de phrase).

Évidemment, ce n'est pas la solution la plus robuste, mais cela fonctionnera bien dans la plupart des cas. Le seul cas que cela ne couvrira pas est celui des abréviations (peut-être parcourez la liste des phrases et vérifiez que chaque chaîne sentencescommence par une lettre majuscule?)

Rafe Kettler
la source
29
Vous ne pouvez pas penser à une situation en anglais où une phrase ne se termine pas par un point? Imagine ça! Ma réponse à cela serait: «détrompez-vous». (Vous voyez ce que j'ai fait là-bas?)
Ned Batchelder
@Ned wow, je ne peux pas croire que j'étais aussi stupide. Je dois être ivre ou quelque chose comme ça.
Rafe Kettler le
J'utilise Python 2.7.2 sur Win 7 x86, et l'expression régulière dans le code ci-dessus me donne cette erreur SyntaxError: EOL while scanning string literal:, pointant vers la parenthèse fermante (après text). En outre, l'expression régulière que vous référencez dans votre texte n'existe pas dans votre exemple de code.
Sabuncu
1
Le regex n'est pas tout à fait correct, comme il se doitr' *[\.\?!][\'"\)\]]* +'
fsociety
Cela peut causer de nombreux problèmes et fragmenter une phrase en plus petits morceaux. Considérez le cas où nous avons «J'ai payé 3,5 $ pour cette glace», les morceaux sont «J'ai payé 3 $» et «5 pour cette glace». utilisez la phrase nltk par défaut.tokenizer est plus sûr!
Reihan_amn
6

Vous pouvez également utiliser la fonction de tokenisation de phrase dans 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
la source
2

@Artyom,

Salut! Vous pouvez créer un nouveau tokenizer pour le russe (et certaines autres langues) en utilisant cette fonction:

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

puis appelez-le de cette manière:

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

Bonne chance, Marilena.

Marilena Di Bari
la source
0

Nul doute que NLTK est le plus adapté à cette fin. Mais commencer avec NLTK est assez pénible (mais une fois que vous l'avez installé, vous en récoltez les fruits)

Voici donc un simple code basé sur Re disponible sur 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
la source
3
Yey mais cela échoue si facilement, avec: "M. Smith sait que c'est une phrase."
thomas
0

J'ai dû lire les fichiers de sous-titres et les diviser en phrases. Après le prétraitement (comme la suppression des informations de temps, etc. dans les fichiers .srt), la variable fullFile contenait le texte intégral du fichier de sous-titres. La manière brute ci-dessous les divise proprement en phrases. J'ai probablement eu de la chance que les phrases se terminent toujours (correctement) par un espace. Essayez ceci d'abord et s'il y a des exceptions, ajoutez plus de freins et contrepoids.

# 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. Je me rends compte maintenant que puisque mon contenu était en espagnol, je n'avais pas les problèmes de traiter avec "M. Smith" etc. Pourtant, si quelqu'un veut un analyseur rapide et sale ...

Kishore
la source
0

j'espère que cela vous aidera sur le texte latin, chinois, arabe

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
la source
0

Travaillait sur une tâche similaire et est tombé sur cette requête, en suivant quelques liens et en travaillant sur quelques exercices pour nltk, le code ci-dessous a fonctionné pour moi comme par magie.

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

production:

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

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

Mazeen Muhammed
la source