Python - 用正则表达式将文本拆分为句子(句子分词)
我想从一个字符串中提取出句子列表,然后把它们打印出来。我不想用NLTK这个工具来实现这个功能。所以我需要在句子的末尾用句号来分割,而不是在小数点、缩写、名字的标题或者包含 .com 的句子中进行分割。这是我尝试用正则表达式做的,但没有成功。
import re
text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)
for stuff in sentences:
print(stuff)
这是我希望输出的例子
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it.
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
10 个回答
这里介绍了一种简单的方法,用来处理正确的英语句子,确保句子不以非字母开头,也不包含引号中的词性部分:
import re
text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
EndPunctuation = re.compile(r'([\.\?\!]\s+)')
NonEndings = re.compile(r'(?:Mrs?|Jr|i\.e)\.\s*$')
parts = EndPunctuation.split(text)
sentence = []
for part in parts:
if len(part) and len(sentence) and EndPunctuation.match(sentence[-1]) and not NonEndings.search(''.join(sentence)):
print(''.join(sentence))
sentence = []
if len(part):
sentence.append(part)
if len(sentence):
print(''.join(sentence))
通过稍微扩展“非结束符”的范围,可以减少错误拆分的情况。不过,其他一些情况可能需要额外的代码来处理。用这种方法处理拼写错误会比较困难。
用这种方法你永远无法达到完美。但根据具体任务的不同,这种方法可能“足够好”……
sent = re.split('(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)',text)
for s in sent:
print s
这里使用的正则表达式是:(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)
第一部分:(?<!\w\.\w.)
:这个模式是在一个负反馈循环中查找,意思是它会找出所有以单词(\w)
后面跟着一个句号(\.)
,再后面又跟着其他单词的情况。
第二部分:(?<![A-Z][a-z]\.)
:这个模式同样是在负反馈循环中查找,它会找出任何以大写字母([A-Z])
开头,后面跟着小写字母([a-z])
,直到找到一个句号(\.)
的情况。
第三部分:(?<=\.|\?)
:这个模式是在一个反馈循环中查找句号(\.)
或问号(\?)
。
第四部分:(\s|[A-Z].*)
:这个模式是在第三部分找到的句号或问号后面进行查找。它会寻找空格(\s)
或者任何以大写字母([A-Z].*)
开头的字符序列。这个部分很重要,因为它可以帮助我们分开输入内容,比如:
你好,世界。嗨,我今天在这里。
也就是说,不管句号后面有没有空格,这个模式都能处理。
试着根据空格来分割输入,而不是用点或者?
。这样做的话,点或者?
就不会出现在最后的结果里。
>>> import re
>>> s = """Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't."""
>>> m = re.split(r'(?<=[^A-Z].[.?]) +(?=[A-Z])', s)
>>> for i in m:
... print i
...
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it.
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
好的,句子分词器是我稍微研究过的一些东西,使用了正则表达式、nltk、CoreNLP和spaCy等工具。最终你可能会自己写一个,这取决于你的应用场景。这些东西比较复杂,但也很有价值,很多人不会轻易分享他们的分词器代码。(归根结底,分词并不是一个确定性的过程,而是一个概率性的过程,并且很大程度上依赖于你的语料库或领域,比如法律/金融文件、社交媒体帖子、Yelp评论和生物医学论文等……)
一般来说,你不能依赖一个单一的完美正则表达式,你需要写一个函数,使用多个正则表达式(包括正向和负向的);还需要一个缩写词典,以及一些基本的语言解析,知道像“I”、“USA”、“FCC”、“TARP”这样的词在英语中是大写的。
为了说明这件事情有多复杂,我们来试着写一个功能规范,专门用来判断单个句号('.')或多个句号('...')是否表示句子的结束,或者其他情况:
function isEndOfSentence(leftContext, rightContext)
- 对于数字或货币中的小数点,例如1.23、$1.23、"That's just my $.02",返回False。还要考虑像1.2.A.3.a这样的章节引用,像09.07.2014这样的欧洲日期格式,像192.168.1.1这样的IP地址,MAC地址等等。
- 对于已知的缩写,例如“U.S. stocks are falling”,返回False(并且不要将其分解为单个字母);这需要一个已知缩写的词典。任何不在这个词典中的内容你都会出错,除非你添加代码来检测未知缩写,比如A.B.C.并将其添加到列表中。
- 句子末尾的省略号'...'是终止符,但在句子中间则不是。这并不像你想的那么简单:你需要查看左侧和右侧的上下文,特别是右侧是否是大写,并再次考虑像“I”这样的词和缩写。这里有一个例子证明了这种模糊性:She asked me to stay... I left an hour later.(这是一个句子还是两个句子?无法确定)
- 你可能还想写一些模式来检测和拒绝各种非句子结束的标点符号使用:表情符号:-)、ASCII艺术、间隔省略号. . .以及其他东西,尤其是Twitter上的内容。(让这个适应性更强就更难了)。我们怎么判断@midnight是Twitter用户、Comedy Central的节目、文本缩写,还是简单的不需要的/垃圾/拼写错误的标点符号?这真的是个不简单的问题。
- 在处理完所有这些负面情况后,你可以随意说,任何孤立的句号后面跟着空格的地方很可能是句子的结束。(最终,如果你真的想要更高的准确性,你会写自己的概率句子分词器,使用权重,并在特定的语料库上进行训练(例如法律文本、广播媒体、StackOverflow、Twitter、论坛评论等)。然后你必须手动审查示例和训练错误。可以参考Manning和Jurafsky的书或Coursera课程[a]。最终,你得到的正确性取决于你愿意付出多少代价。)
- 以上所有内容显然是针对英语/缩写、美国的数字/时间/日期格式。如果你想让它独立于国家和语言,那就更复杂了,你需要语料库、母语人士来标注和质量检查等等。
- 以上所有内容仍然只是ASCII,实际上只有96个字符。如果允许输入为Unicode,事情就会变得更加复杂(而且训练集必须要么大得多,要么稀疏得多)。
在简单(确定性)情况下,function isEndOfSentence(leftContext, rightContext)
将返回布尔值,但在更一般的情况下,它是概率性的:它返回一个0.0到1.0之间的浮点数(表示该特定句号是句子结束的置信度)。
参考资料:[a] Coursera视频:“基本文本处理 2-5 - 句子分割 - 斯坦福NLP - Dan Jurafsky教授和Chris Manning” [更新:以前在YouTube上的非官方版本已被删除]