Bagaimana cara membagi teks menjadi kalimat?

108

Saya memiliki file teks. Saya perlu mendapatkan daftar kalimat.

Bagaimana ini bisa diterapkan? Ada banyak kehalusan, seperti titik yang digunakan dalam singkatan.

Ekspresi reguler saya yang lama bekerja dengan buruk:

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)
Artyom
sumber
18
Tentukan "kalimat".
martineau
saya ingin melakukan ini, tetapi saya ingin berpisah di mana pun ada periode atau baris baru
yishairasowsky

Jawaban:

152

Perangkat Bahasa Alami ( nltk.org ) memiliki apa yang Anda butuhkan. Posting grup ini menunjukkan bahwa:

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

(Saya belum mencobanya!)

Ned Batchelder
sumber
3
@Artyom: Ini mungkin dapat bekerja dengan bahasa Rusia - lihat apakah NLTK / pyNLTK dapat berfungsi "per bahasa" (yaitu non-bahasa Inggris), dan bagaimana caranya? .
martineau
4
@Artyom: Berikut tautan langsung ke dokumentasi online untuk nltk .tokenize.punkt.PunktSentenceTokenizer.
martineau
10
Anda mungkin harus menjalankan nltk.download()terlebih dahulu dan mengunduh model ->punkt
Martin Thoma
2
Ini gagal pada kasus dengan tanda kutip akhir. Jika kita memiliki kalimat yang berakhir seperti "ini".
Fosa
1
Oke, Anda meyakinkan saya. Tapi saya baru saja menguji dan sepertinya tidak gagal. Masukan saya adalah 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'dan keluaran saya ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']Sepertinya benar untuk saya.
szedjani
101

Fungsi ini dapat membagi seluruh teks Huckleberry Finn menjadi beberapa kalimat dalam waktu sekitar 0,1 detik dan menangani banyak kasus tepi yang lebih menyakitkan yang membuat penguraian kalimat menjadi tidak sepele, misalnya " Tuan John Johnson Jr. lahir di AS tetapi memperoleh gelar Ph. D. di Israel sebelum bergabung dengan Nike Inc. sebagai insinyur. Dia juga bekerja di craigslist.org sebagai analis bisnis. "

# -*- 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
sumber
19
Ini adalah solusi yang luar biasa. Namun saya menambahkan dua baris lagi ke dalamnya digits = "([0-9])" dalam deklarasi ekspresi reguler dan text = re.sub (digit + "[.]" + Digit, "\\ 1 <prd> \ \ 2 ", teks) dalam fungsi tersebut. Sekarang tidak membagi garis pada desimal seperti 5,5. Terima kasih atas jawaban ini.
Ameya Kulkarni
1
Bagaimana Anda mengurai seluruh sirip Huckleberry? Dimana itu dalam format teks?
PascalVKooten
6
Solusi yang bagus. Dalam fungsinya, saya menambahkan if "eg" dalam text: text = text.replace ("eg", "e <prd> g <prd>") if "ie" in text: text = text.replace ("ie" , "i <prd> e <prd>") dan itu sepenuhnya memecahkan masalah saya.
Sisay Chala
3
Solusi hebat dengan komentar yang sangat membantu! Hanya untuk membuatnya meskipun sedikit lebih kuat: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)", danif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz
1
Dapatkah fungsi ini dibuat untuk melihat kalimat seperti ini sebagai satu kalimat: Ketika seorang anak bertanya kepada ibunya "Dari mana asalnya bayi?", Apa yang harus dibalas?
twhale
50

Alih-alih menggunakan regex untuk memisahkan teks menjadi kalimat, Anda juga dapat menggunakan pustaka 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
sumber
Contoh yang bagus, lebih sederhana dan lebih dapat digunakan kembali daripada jawaban yang diterima.
Jay D.
Jika Anda menghapus spasi setelah titik, tokenize.sent_tokenize () tidak berfungsi, tetapi tokenizer.tokenize () berfungsi! Hmm ...
Leonid Ganeline
1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Victoria Stuart
11

Anda dapat mencoba menggunakan Spacy, bukan regex. Saya menggunakannya dan itu berhasil.

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

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

for sent in tokens.sents:
    print(sent.string.strip())
Peri
sumber
1
Luar angkasa sangat bagus. tetapi jika Anda hanya perlu memisahkan ke dalam kalimat, meneruskan teks ke spasi akan memakan waktu terlalu lama jika Anda berurusan dengan pipa data
Berlines
@Berlines Saya setuju tetapi tidak dapat menemukan perpustakaan lain yang melakukan pekerjaan sebersih spaCy. Tetapi jika Anda punya saran, saya bisa mencoba.
Elf
Juga untuk pengguna AWS Lambda Tanpa Server di luar sana, file data dukungan spacy banyak 100MB (bahasa Inggris besar> 400MB) sehingga Anda tidak dapat menggunakan hal-hal seperti ini di luar kotak, sayangnya (penggemar berat Spacy di sini)
Julian H
9

Berikut adalah pendekatan jalan tengah yang tidak bergantung pada perpustakaan eksternal. Saya menggunakan pemahaman daftar untuk mengecualikan tumpang tindih antara singkatan dan terminator serta untuk mengecualikan tumpang tindih antara variasi penghentian, misalnya: '.' 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)

Saya menggunakan fungsi find_all Karl dari entri ini: Temukan semua kemunculan substring dengan Python

TennisVisuals
sumber
1
Pendekatan sempurna! Yang lainnya tidak menangkap ...dan ?!.
Shane Smiskol
6

Untuk kasus sederhana (di mana kalimat diakhiri secara normal), ini seharusnya berfungsi:

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

Regex adalah *\. +, yang mencocokkan titik yang dikelilingi oleh 0 atau lebih spasi di kiri dan 1 atau lebih di kanan (untuk mencegah sesuatu seperti titik di re.split dihitung sebagai perubahan dalam kalimat).

Jelas, bukan solusi yang paling kuat, tetapi itu akan baik-baik saja dalam banyak kasus. Satu-satunya kasus yang tidak tercakup adalah singkatan (mungkin dijalankan melalui daftar kalimat dan periksa apakah setiap string sentencesdiawali dengan huruf kapital?)

Rafe Kettler
sumber
29
Anda tidak dapat memikirkan situasi dalam bahasa Inggris di mana kalimat tidak diakhiri dengan titik? Bayangkan itu! Tanggapan saya untuk itu adalah, "pikirkan lagi." (Lihat apa yang saya lakukan di sana?)
Ned Batchelder
@Ned wow, tidak percaya aku sebodoh itu. Aku pasti mabuk atau apalah.
Rafe Kettler
Saya menggunakan Python 2.7.2 pada Win 7 x86, dan regex pada kode di atas memberi saya kesalahan ini:, SyntaxError: EOL while scanning string literalmenunjuk ke tanda kurung tutup (setelah text). Selain itu, regex yang Anda rujuk dalam teks Anda tidak ada dalam contoh kode Anda.
Sabuncu
1
Regex tidak sepenuhnya benar, sebagaimana mestinyar' *[\.\?!][\'"\)\]]* +'
fsociety
Ini dapat menyebabkan banyak masalah dan memotong kalimat menjadi bagian yang lebih kecil juga. Pertimbangkan kasus bahwa kita memiliki "Saya membayar $ 3,5 untuk es krim ini" mereka potongannya adalah "Saya membayar $ 3" ​​dan "5 untuk es krim ini". gunakan kalimat nltk default. Tokenizer lebih aman!
Reihan_amn
6

Anda juga dapat menggunakan fungsi tokenisasi kalimat di 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
sumber
2

@Bayu_joo

Hai! Anda dapat membuat tokenizer baru untuk bahasa Rusia (dan beberapa bahasa lainnya) menggunakan fungsi ini:

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

dan kemudian menyebutnya dengan cara ini:

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

Semoga beruntung, Marilena.

Marilena Di Bari
sumber
0

Tidak diragukan lagi bahwa NLTK adalah yang paling cocok untuk tujuan tersebut. Tetapi memulai dengan NLTK cukup menyakitkan (Tapi begitu Anda menginstalnya - Anda langsung menuai hasilnya)

Jadi, inilah kode berbasis ulang sederhana yang tersedia di 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
sumber
3
Ya, tapi ini gagal begitu mudah, dengan: "Tuan Smith tahu ini adalah kalimat."
Thomas
0

Saya harus membaca file subtitle dan membaginya menjadi beberapa kalimat. Setelah pra-pemrosesan (seperti menghapus informasi waktu dll dalam file .srt), variabel fullFile berisi teks lengkap dari file subtitle. Cara kasar di bawah ini dengan rapi membaginya menjadi beberapa kalimat. Mungkin saya beruntung bahwa kalimat selalu diakhiri (dengan benar) dengan spasi. Coba ini dulu dan jika ada pengecualian, tambahkan lebih banyak check and balances.

# 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! baik. Sekarang saya menyadari bahwa karena konten saya adalah bahasa Spanyol, saya tidak memiliki masalah berurusan dengan "Mr. Smith" dll. Namun, jika seseorang menginginkan pengurai yang cepat dan kotor ...

kishore
sumber
0

Saya harap ini akan membantu Anda dalam bahasa latin, cina, teks arab

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

Sedang mengerjakan tugas serupa dan menemukan kueri ini, dengan mengikuti beberapa tautan dan mengerjakan beberapa latihan untuk nltk, kode di bawah ini bekerja untuk saya seperti sulap.

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

keluaran:

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

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

Mazeen Muhammed
sumber