Làm cách nào để chia văn bản thành các câu?


108

Tôi có một tập tin văn bản. Tôi cần lấy một danh sách các câu.

Làm thế nào điều này có thể được thực hiện? Có rất nhiều điều tinh tế, chẳng hạn như dấu chấm được sử dụng trong các chữ viết tắt.

Biểu thức chính quy cũ của tôi hoạt động không tốt:

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

18
Xác định "câu".
martineau

tôi muốn làm điều này, nhưng tôi muốn chia bất cứ nơi nào có một trong hai là giai đoạn hay một dòng mới
yishairasowsky

Câu trả lời:


152

Bộ công cụ Ngôn ngữ Tự nhiên ( nltk.org ) có những gì bạn cần. Bài đăng nhóm này chỉ ra điều này làm điều đó:

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

(Tôi chưa thử!)



4
@Artyom: Đây là liên kết trực tiếp đến tài liệu trực tuyến cho nltk .tokenize.punkt.PunktSentenceTokenizer.
martineau

10
Bạn có thể phải thực hiện nltk.download()mô hình đầu tiên và tải về ->punkt
Martin Thoma

2
Điều này không thành công đối với các trường hợp có dấu ngoặc kép kết thúc. Nếu chúng ta có một câu kết thúc như "this."
Fosa

1
Được rồi, bạn đã thuyết phục tôi. Nhưng tôi vừa thử nghiệm và nó dường như không thất bại. Đầu vào của tôi là 'This fails on cases with ending quotation marks. If we have a sentence that ends like "this." This is another sentence.'và đầu ra của tôi ['This fails on cases with ending quotation marks.', 'If we have a sentence that ends like "this."', 'This is another sentence.']Có vẻ đúng với tôi.
szedjani,

100

Chức năng này có thể chia toàn bộ văn bản của Huckleberry Finn thành các câu trong khoảng 0,1 giây và xử lý nhiều trường hợp phức tạp hơn khiến việc phân tích câu trở nên không tầm thường, ví dụ như " Ông John Johnson Jr sinh ra ở Mỹ nhưng đã đạt bằng Ph. D. ở Israel trước khi gia nhập Nike Inc. với tư cách là một kỹ sư. Anh ấy cũng làm việc tại craigslist.org với tư cách là một nhà phân tích kinh doanh. "

# -*- 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
Đây là một giải pháp tuyệt vời. Tuy nhiên, tôi đã thêm hai dòng vào nó là chữ số = "([0-9])" trong khai báo biểu thức chính quy và văn bản = re.sub (chữ số + "[.]" + Chữ số, "\\ 1 <thứ tự> \ \ 2 ", văn bản) trong hàm. Bây giờ nó không chia dòng ở số thập phân chẳng hạn như 5,5. Cảm ơn bạn vì câu trả lời này.
Ameya Kulkarni

1
Bạn đã phân tích cú pháp toàn bộ Huckleberry Fin như thế nào? Đó là ở đâu trong định dạng văn bản?
PascalVKooten

6
Một giải pháp tuyệt vời. Trong hàm, tôi đã thêm nếu "ví dụ" trong văn bản: text = text.replace ("ví dụ", "e <prd> g <prd>") nếu "ví dụ" trong văn bản: text = text.replace ("tức là" , "i <prd> e <prd>") và nó hoàn toàn giải quyết được vấn đề của tôi.
Sisay Chala

3
Giải pháp tuyệt vời với những bình luận rất hữu ích! Chỉ cần để làm cho nó một chút mặc dù mạnh mẽ hơn: prefixes = "(Mr|St|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|Mt)[.]", websites = "[.](com|net|org|io|gov|me|edu)", vàif "..." in text: text = text.replace("...","<prd><prd><prd>")
Dascienz

1
Chức năng này có thể được thực hiện để xem những câu như thế này như một câu: Khi một đứa trẻ hỏi mẹ "Em bé đến từ đâu?", Người ta nên trả lời mẹ điều gì?
twhale

50

Thay vì sử dụng regex để ghép văn bản thành câu, bạn cũng có thể sử dụng thư viện 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


Ví dụ tuyệt vời, đơn giản hơn và có thể tái sử dụng nhiều hơn câu trả lời được chấp nhận.
Jay D.

Nếu bạn xóa dấu cách sau dấu chấm, tokenize.sent_tokenize () không hoạt động, nhưng tokenizer.tokenize () hoạt động! Hừm ...
Leonid Ganeline

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

11

Bạn có thể thử sử dụng Spacy thay vì regex. Tôi sử dụng nó và nó thực hiện công việc.

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

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

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

1
Không gian là rất lớn. nhưng nếu bạn chỉ cần tách thành các câu thì việc chuyển văn bản vào khoảng
trắng

@Berlines Tôi đồng ý nhưng không thể tìm thấy bất kỳ thư viện nào khác thực hiện công việc sạch sẽ như spaCy. Nhưng nếu bạn có bất kỳ đề nghị nào, tôi có thể thử.
Elf

Ngoài ra, đối với người dùng AWS Lambda Serverless ngoài kia, các tệp dữ liệu hỗ trợ của spacy có rất nhiều 100MB (tiếng Anh lớn là> 400MB), vì vậy bạn không thể sử dụng những thứ như thế này ngay lập tức, rất đáng buồn (người hâm mộ lớn của Spacy ở đây)
Julian H

9

Đây là một cách tiếp cận giữa đường mà không dựa vào bất kỳ thư viện bên ngoài nào. Tôi sử dụng khả năng hiểu danh sách để loại trừ sự trùng lặp giữa các chữ viết tắt và các từ kết thúc cũng như để loại trừ sự trùng lặp giữa các biến thể trên các phần tử, ví dụ: '.' so với '. "'

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)

Tôi đã sử dụng hàm find_all của Karl từ mục này: Tìm tất cả các lần xuất hiện của một chuỗi con trong Python


1
Cách tiếp cận hoàn hảo! Những người khác không bắt ...?!.
Shane Smiskol,

6

Đối với các trường hợp đơn giản (khi câu được kết thúc bình thường), điều này sẽ hoạt động:

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

Regex là *\. +, phù hợp với một dấu chấm được bao quanh bởi 0 hoặc nhiều khoảng trắng ở bên trái và 1 hoặc nhiều hơn ở bên phải (để ngăn một cái gì đó như dấu chấm trong re.split được tính là một sự thay đổi trong câu).

Rõ ràng, không phải là giải pháp mạnh mẽ nhất, nhưng nó sẽ hoạt động tốt trong hầu hết các trường hợp. Trường hợp duy nhất mà điều này sẽ không đề cập đến là các chữ viết tắt (có thể chạy qua danh sách các câu và kiểm tra xem mỗi chuỗi trong sentencesbắt đầu bằng một chữ cái viết hoa?)


29
Bạn không thể nghĩ ra một tình huống bằng tiếng Anh mà một câu không kết thúc bằng dấu chấm? Tưởng tượng rằng! Câu trả lời của tôi cho điều đó sẽ là, "hãy nghĩ lại." (Xem những gì tôi đã làm đó?)
Ned Batchelder

@Ned wow, không thể tin được rằng tôi đã ngu ngốc như vậy. Tôi phải say rượu hay gì đó.
Rafe Kettler

Tôi đang sử dụng Python 2.7.2 trên Win 7 x86 và regex trong đoạn mã trên gây cho tôi lỗi này : SyntaxError: EOL while scanning string literal, trỏ đến dấu ngoặc đóng (sau text). Ngoài ra, regex bạn tham chiếu trong văn bản không tồn tại trong mẫu mã của bạn.
Sabuncu

1
Regex không hoàn toàn chính xác, đúng như vậyr' *[\.\?!][\'"\)\]]* +'
fsociety

Nó có thể gây ra nhiều vấn đề và chia nhỏ một câu thành những phần nhỏ hơn. Hãy xem xét trường hợp chúng ta có câu "Tôi đã trả 3,5 đô la cho món kem này" thì các đoạn là "Tôi đã trả 3 đô la" và "5 cho món kem này". sử dụng câu nltk mặc định.tokenizer an toàn hơn!
Reihan_amn 23/02/18

6

Bạn cũng có thể sử dụng chức năng mã hóa câu trong 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,

Chào! Bạn có thể tạo một tokenizer mới cho tiếng Nga (và một số ngôn ngữ khác) bằng chức năng này:

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

và sau đó gọi nó theo cách này:

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

Chúc may mắn, Marilena.


0

Không nghi ngờ gì rằng NLTK là phù hợp nhất cho mục đích. Nhưng bắt đầu với NLTK khá khó khăn (Nhưng một khi bạn cài đặt nó - bạn chỉ gặt hái được phần thưởng)

Vì vậy, đây là mã dựa trên đơn giản có sẵn tại 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
Yey nhưng điều này không thành công quá dễ dàng, với: "Ông Smith biết đây là một câu."
thomas

0

Tôi đã phải đọc các tệp phụ đề và chia chúng thành các câu. Sau khi xử lý trước (như xóa thông tin thời gian, v.v. trong tệp .srt), biến fullFile chứa toàn bộ văn bản của tệp phụ đề. Cách thô sơ dưới đây tách chúng thành câu. Có lẽ tôi đã may mắn rằng các câu luôn kết thúc (đúng) bằng một khoảng trắng. Hãy thử điều này trước và nếu nó có bất kỳ trường hợp ngoại lệ nào, hãy thêm các séc và số dư khác.

# 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! tốt. Bây giờ tôi nhận ra rằng vì nội dung của tôi là tiếng Tây Ban Nha nên tôi không gặp vấn đề gì khi giao dịch với "Mr. Smith", v.v. Tuy nhiên, nếu ai đó muốn một trình phân tích cú pháp nhanh và bẩn ...


0

tôi hy vọng điều này sẽ giúp bạn về văn bản latin, Trung Quốc, Ả Rập

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

Đang làm việc với nhiệm vụ tương tự và bắt gặp truy vấn này, bằng cách nhấp vào một vài liên kết và thực hiện một vài bài tập cho nltk, đoạn mã dưới đây đã hoạt động đối với tôi như một phép thuật.

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

đầu ra:

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

Nguồn: https://www.geeksforgeeks.org/nlp-how-tokezing-text-sentence-words-works/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.