ฉันจะแบ่งข้อความเป็นประโยคได้อย่างไร


109

ฉันมีไฟล์ข้อความ ฉันต้องการรายชื่อประโยค

วิธีนี้สามารถนำไปใช้? มีรายละเอียดปลีกย่อยมากมายเช่นจุดที่ใช้ในตัวย่อ

นิพจน์ทั่วไปเก่าของฉันทำงานได้ไม่ดี:

re.compile('(\. |^|!|\?)([A-Z][^;↑\.<>@\^&/\[\]]*(\.|!|\?) )',re.M)

18
กำหนด "ประโยค"
martineau

ฉันต้องการทำสิ่งนี้ แต่ฉันต้องการแยกทุกที่ที่มีทั้งช่วงเวลาหรือบรรทัดใหม่
yishairasowsky

คำตอบ:


152

Natural Language Toolkit ( nltk.org ) มีสิ่งที่คุณต้องการ การโพสต์กลุ่มนี้บ่งชี้ว่าสิ่งนี้ทำได้:

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

(ยังไม่ได้ลอง!)



4
@Artyom: nltk .tokenize.punkt.PunktSentenceTokenizerนี่คือการเชื่อมโยงโดยตรงกับเอกสารออนไลน์
martineau

10
คุณอาจต้องดำเนินการnltk.download()ก่อนและดาวน์โหลดโมเดล ->punkt
Martin Thoma

2
สิ่งนี้ล้มเหลวในกรณีที่มีเครื่องหมายคำพูดลงท้าย ถ้าเรามีประโยคที่ลงท้ายแบบ "this."
Fosa

1
โอเคคุณทำให้ฉันเชื่อ แต่ฉันเพิ่งทดสอบและดูเหมือนจะไม่ล้มเหลว อินพุตของฉันคือ'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'และผลลัพธ์ของฉัน['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']ดูเหมือนว่าถูกต้องสำหรับฉัน
szedjani

102

ฟังก์ชั่นนี้สามารถแบ่งข้อความทั้งหมดของ Huckleberry Finn ออกเป็นประโยคได้ภายในเวลาประมาณ 0.1 วินาทีและจัดการกับหลาย ๆ กรณีที่เจ็บปวดมากขึ้นซึ่งทำให้การแยกประโยคเป็นเรื่องไม่สำคัญเช่น " Mr.John Johnson Jr. เกิดในสหรัฐอเมริกา แต่ได้รับปริญญาเอก D. ในอิสราเอลก่อนจะร่วมงานกับ Nike Inc. ในตำแหน่งวิศวกรนอกจากนี้เขายังทำงานที่ 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

19
นี่เป็นทางออกที่ยอดเยี่ยม อย่างไรก็ตามฉันได้เพิ่มอีกสองบรรทัดให้กับมันหลัก = "([0-9])" ในการประกาศนิพจน์ทั่วไปและ text = re.sub (หลัก + "[.]" + หลัก, "\\ 1 <prd> \ \ 2 ", ข้อความ) ในฟังก์ชัน ตอนนี้มันไม่ได้แบ่งบรรทัดที่ทศนิยมเช่น 5.5 ขอบคุณสำหรับคำตอบนี้
Ameya Kulkarni

1
คุณแยกวิเคราะห์ Huckleberry Fin ทั้งหมดได้อย่างไร? อยู่ที่ไหนในรูปแบบข้อความ?
PascalVKooten

6
ทางออกที่ดี ในฟังก์ชันฉันเพิ่ม if "เช่น" ในข้อความ: text = text.replace ("เช่น", "e <prd> g <prd>") if "ie" ในข้อความ: text = text.replace ("ie" , "i <prd> e <prd>") และมันช่วยแก้ปัญหาของฉันได้อย่างสมบูรณ์
Sisay Chala

3
ทางออกที่ดีพร้อมความคิดเห็นที่เป็นประโยชน์มาก! เพียงเพื่อให้มันเล็ก ๆ น้อย ๆ แต่มีประสิทธิภาพมากขึ้น: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)"และif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz

1
ฟังก์ชั่นนี้สามารถทำให้เห็นประโยคแบบนี้เป็นประโยคเดียวได้ไหม: เมื่อเด็กถามแม่ว่า "เด็กมาจากไหน" ควรตอบว่าอย่างไร
ชักว่าว

50

แทนที่จะใช้ regex ในการแยกข้อความออกเป็นประโยคคุณยังสามารถใช้ nltk library

>>> 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.']

อ้างอิง: https://stackoverflow.com/a/9474645/2877052


ตัวอย่างที่ยอดเยี่ยมง่ายกว่าและใช้ซ้ำได้มากกว่าคำตอบที่ยอมรับ
Jay D.

หากคุณลบช่องว่างหลังจุด tokenize.sent_tokenize () จะไม่ทำงาน แต่ tokenizer.tokenize () ใช้งานได้! อืม ...
Leonid Ganeline

1
for sentence in tokenize.sent_tokenize(text): print(sentence)
Victoria Stuart

11

คุณสามารถลองใช้Spacyแทน regex ฉันใช้มันและได้ผล

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

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

for sent in tokens.sents:
    print(sent.string.strip())

1
อวกาศนั้นยอดเยี่ยมมาก แต่ถ้าคุณต้องการแยกเป็นประโยคการส่งข้อความไปยังช่องว่างจะใช้เวลานานเกินไปหากคุณกำลังจัดการกับท่อข้อมูล
Berlines

@Berlines ฉันเห็นด้วย แต่ไม่พบห้องสมุดอื่นที่ทำงานได้สะอาดเท่า spaCy แต่ถ้าคุณมีข้อเสนอแนะใด ๆ ฉันสามารถลองได้
เอลฟ์

นอกจากนี้สำหรับผู้ใช้ AWS Lambda Serverless ที่นั่นไฟล์ข้อมูลสนับสนุนของ spacy มีจำนวนมากถึง 100MB (ภาษาอังกฤษใหญ่> 400MB) ดังนั้นคุณจึงไม่สามารถใช้สิ่งต่างๆเช่นนี้ได้นอกกรอบน่าเศร้ามาก (แฟนตัวยงของ Spacy ที่นี่)
Julian H

9

นี่คือแนวทางกลางทางที่ไม่ต้องพึ่งพาห้องสมุดภายนอกใด ๆ ฉันใช้การทำความเข้าใจรายการเพื่อยกเว้นการทับซ้อนระหว่างตัวย่อและตัวยุติรวมถึงยกเว้นการทับซ้อนระหว่างรูปแบบต่างๆของการยุติตัวอย่างเช่น "." เทียบกับ '. "'

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)

ฉันใช้ฟังก์ชัน find_all ของ Karl จากรายการนี้: ค้นหาการเกิดขึ้นทั้งหมดของสตริงย่อยใน Python


1
แนวทางที่สมบูรณ์แบบ! คนอื่น ๆ ไม่จับและ... ?!
Shane Smiskol

6

สำหรับกรณีง่ายๆ (โดยปกติประโยคจะถูกยกเลิก) สิ่งนี้ควรได้ผล:

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

นิพจน์ทั่วไปคือ*\. +ซึ่งจับคู่กับจุดที่ล้อมรอบด้วยช่องว่าง 0 หรือมากกว่าทางด้านซ้ายและ 1 หรือมากกว่าทางด้านขวา (เพื่อป้องกันไม่ให้บางสิ่งเช่นจุดใน re.split ถูกนับเป็นการเปลี่ยนแปลงในประโยค)

เห็นได้ชัดว่าไม่ใช่วิธีแก้ปัญหาที่แข็งแกร่งที่สุด แต่ในกรณีส่วนใหญ่จะทำได้ดี กรณีเดียวที่จะไม่ครอบคลุมคือคำย่อ (อาจเรียกใช้ในรายการประโยคและตรวจสอบว่าแต่ละสตริงsentencesขึ้นต้นด้วยอักษรตัวใหญ่หรือไม่)


29
คุณไม่สามารถนึกถึงสถานการณ์ในภาษาอังกฤษที่ประโยคไม่ได้ลงท้ายด้วยจุด? ลองนึกดูสิ! คำตอบของฉันคือ "คิดใหม่อีกครั้ง" (ดูว่าฉันทำอะไรที่นั่น)
Ned Batchelder

@ เน็ดว้าวไม่อยากจะเชื่อเลยว่าฉันโง่ขนาดนั้น ฉันต้องเมาหรืออะไรสักอย่าง
Rafe Kettler

ฉันใช้ Python 2.7.2 บน Win 7 x86 และ regex ในโค้ดด้านบนทำให้ฉันเกิดข้อผิดพลาด: SyntaxError: EOL while scanning string literalชี้ไปที่วงเล็บปิด (หลังtext) นอกจากนี้นิพจน์ทั่วไปที่คุณอ้างอิงในข้อความของคุณไม่มีอยู่ในตัวอย่างโค้ดของคุณ
Sabuncu

1
regex ไม่ถูกต้องอย่างสมบูรณ์อย่างที่ควรจะเป็นr' *[\.\?!][\'"\)\]]* +'
fsociety

มันอาจทำให้เกิดปัญหามากมายและแยกประโยคเป็นชิ้นเล็ก ๆ เช่นกัน ลองพิจารณากรณีที่เราได้ "ฉันจ่ายเงิน 3.5 เหรียญสำหรับไอศกรีมนี้" ชิ้นส่วนคือ "ฉันจ่ายเงิน 3 เหรียญ" และ "5 ก้อนสำหรับไอศกรีมนี้" ใช้ประโยคเริ่มต้น nltk โทเค็นเซอร์ปลอดภัยกว่า!
Reihan_amn

6

คุณยังสามารถใช้ฟังก์ชัน tokenization ประโยคใน 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)

2

@Artyom,

ไฮ! คุณสามารถสร้างโทเค็นใหม่สำหรับภาษารัสเซีย (และภาษาอื่น ๆ ) โดยใช้ฟังก์ชันนี้:

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

แล้วเรียกในลักษณะนี้:

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

ขอให้โชคดี Marilena


0

ไม่ต้องสงสัยเลยว่า NLTK เหมาะสมที่สุดสำหรับวัตถุประสงค์ แต่การเริ่มต้นกับ NLTK นั้นค่อนข้างเจ็บปวด (แต่เมื่อคุณติดตั้งแล้วคุณก็จะได้รับรางวัล)

ดังนั้นนี่คือโค้ด re based ง่ายๆที่มีอยู่ที่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 

3
ใช่ แต่สิ่งนี้ล้มเหลวอย่างง่ายดายโดย: "มิสเตอร์สมิ ธ รู้ว่านี่เป็นประโยค"
thomas

0

ฉันต้องอ่านไฟล์คำบรรยายและแยกเป็นประโยค หลังจากการประมวลผลล่วงหน้า (เช่นการลบข้อมูลเวลา ฯลฯ ในไฟล์. srt) ตัวแปร fullFile จะมีข้อความเต็มของไฟล์คำบรรยาย วิธีหยาบด้านล่างแบ่งออกเป็นประโยคอย่างเรียบร้อย ฉันอาจจะโชคดีที่ประโยคนั้นลงท้ายด้วยการเว้นวรรค (อย่างถูกต้อง) เสมอ ลองใช้สิ่งนี้ก่อนและหากมีข้อยกเว้นให้เพิ่มการตรวจสอบและยอดคงเหลือเพิ่มเติม

# 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;

โอ้! ดี. ตอนนี้ฉันรู้แล้วว่าเนื่องจากเนื้อหาของฉันเป็นภาษาสเปนฉันจึงไม่มีปัญหาในการจัดการกับ "Mr. Smith" เป็นต้น แต่หากมีใครต้องการตัวแยกวิเคราะห์ที่รวดเร็วและสกปรก ...


0

ฉันหวังว่านี่จะช่วยคุณเกี่ยวกับข้อความภาษาละตินจีนและภาษาอาหรับ

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

0

กำลังทำงานในงานที่คล้ายกันและพบกับข้อความค้นหานี้โดยทำตามลิงก์ไม่กี่ลิงก์และทำงานกับแบบฝึกหัดบางส่วนสำหรับ nltk โค้ดด้านล่างนี้ใช้ได้ผลกับฉันเหมือนเวทมนตร์

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

เอาต์พุต:

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

ที่มา: https://www.geeksforgeeks.org/nlp-how-tokenizing-text-sentence-words-works/

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.