如何将一段文本解析成句子?(最好使用Ruby)

22
如何将段落或大量文本分成句子(最好使用Ruby),考虑到像Mr.和Dr.以及U.S.A这样的情况? (假设您只是将句子放入一个数组中的数组)
更新:我想到的一种可能的解决方案涉及使用词性标注器(POST)和分类器来确定句子的结尾:
从Jones先生那里获取数据时,他感觉到温暖的阳光照在他的脸上,当他走出意大利的夏季住所的阳台时,他很高兴还活着。
分类器 Mr./PERSON Jones/PERSON felt/O the/O warm/O sun/O on/O his/O face/O as/O he/O stepped/O out/O onto/O the/O balcony/O of/O his/O summer/O home/O in/O Italy/LOCATION ./O He/O was/O happy/O to/O be/O alive/O ./O
POST Mr./NNP Jones/NNP felt/VBD the/DT warm/JJ sun/NN on/IN his/PRP$ face/NN as/IN he/PRP stepped/VBD out/RP onto/IN the/DT balcony/NN of/IN his/PRP$ summer/NN home/NN in/IN Italy./NNP He/PRP was/VBD happy/JJ to/TO be/VB alive./IN
我们可以假设,由于意大利是一个位置,因此句号是句子的有效结尾吗?由于“Mr.”结尾没有其他词性,我们可以假设这不是有效的句子结尾句号吗?这是我的问题的最佳答案吗?
您有什么想法?

有没有具体的规则。如果您能用英语告诉我们规则,我相信我们(或您)将能够编写解决方案。例如:缩写词如“abbr”后面是否有句号?如果您要解析语法教科书,简单的解决方案可能就足够了,但如果您处理任意文本,则每个解决方案都会存在缺陷,您知道吗? - Marcin
POS标注器有些过头了。使用基于NLP的分词器,你的规则会更简单。 - Kevin Peterson
14个回答

13

我会继续尝试使用斯坦福解析器 - 它一定在那里!谢谢! - henry74
2
edu.stanford.nlp.process.DocumentPreprocessor,顺便说一句。 - Stompchicken
4
可以通过Ruby包装器或直接调用edu.stanford.nlp.process.DocumentPreprocessor来将文本分成句子。可以通过以下方式从代码或命令行运行: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt 这使用了一种良好但不精确的有限状态机算法,因此速度很快,而且不需要运行概率解析器。 - Christopher Manning

8

为了明确,这并不是一个简单的问题。如快速谷歌搜索所示,这是自然语言处理研究的主题。

然而,似乎有一些开源项目处理NLP支持句子检测,我找到了以下基于Java的工具集:

openNLP

附加评论:决定句子何时开始和结束的问题在自然语言处理中也被称为句子边界消歧(SBD)。


我找不到一个易于使用的openNLP的Ruby包装器 - 你遇到过吗?不过他们确实有一个句子拆分工具... - henry74
@phillc:所谓的句子边界消歧是自然语言处理中决定句子起始和结束位置的问题。(http://en.wikipedia.org/wiki/Sentence_boundary_disambiguation) - Dirk Vollmar

6

5
看看NLTK(自然语言工具包)中的Python句子分割器:

Punkt句子分词器

它基于以下论文:

Kiss, Tibor和Strunk, Jan(2006):无监督多语句子边界检测计算语言学 32:485-525。

该论文的方法非常有趣。他们将句子拆分的问题简化为确定单词与后续标点符号相关程度的问题。缩写后的句点过载导致大部分歧义性句点,因此,如果您能够识别缩写词,则可以高概率地识别句子边界。

我已经非正式地测试了这个工具,并且似乎对各种(人类)语言都能给出良好的结果。

将其移植到Ruby可能是不容易的,但它可能会给您一些思路。


两个链接都失效了,你有什么可以分享的内容不会被删除吗? - lacostenycoder

4
这是一个艰难的问题,如果你真的关心正确性的话。你会发现NLP解析器包可能提供这种功能。如果你想要更快的速度,你需要用训练有素的概率函数来复制一些标记窗口的功能(你可能想把换行符计算为一个标记,因为如果它是段落的结尾,我可能会省略句号)。
编辑:如果你能使用Java,我推荐Stanford解析器。我没有其他语言的推荐,但我非常想知道还有哪些开源的选择。

是的,我已经使用过斯坦福自然语言处理解析器,但没有找到句子分割器。如果你有兴趣使用它,在github上有人创建了一个rjb(ruby to java bridge)包装器,我能够相对容易地让它工作。这是链接,供有兴趣的人参考http://github.com/tiendung/ruby-nlp/tree/master 注意:在Windows上,加载Java库时必须将冒号更改为分号。干杯。 - henry74
你说得对,在解析器包中确实没有句子分割器,但是有一个分词器,可以帮助你解决部分问题。它可以处理像你提到的那些情况,比如将"Mr."作为一个标记而不是句子结束符"."。 - Kevin Peterson
2
有一个句子分割器:edu.stanford.nlp.process.DocumentPreprocessor。尝试使用以下命令:java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt。(这是通过一个(好的但启发式的)FSM完成的,因此速度很快;您不会运行概率解析器。) - Christopher Manning

2

很遗憾,我不是Ruby的开发人员,但也许Perl的示例可以指引你朝着正确的方向前进。使用非匹配后置项来查找结束标点符号,然后在未在后部的特殊情况下跟随任意数量的空格并查找大写字母。我相信这并不完美,但希望它能指引你朝着正确的方向前进。不确定如何知道U.S.A.是否实际上位于句子的末尾...

#!/usr/bin/perl

$string = "Mr. Thompson is from the U.S.A. and is 75 years old. Dr. Bob is a dentist. This is a string that contains several sentances. For example this is one. Followed by another. Can it deal with a question?  It sure can!";

my @sentances = split(/(?:(?<=\.|\!|\?)(?<!Mr\.|Dr\.)(?<!U\.S\.A\.)\s+(?=[A-Z]))/, $string);

for (@sentances) {
    print $_."\n";
}

2

同意接受的答案,使用斯坦福核心 NLP 是一件很容易的事情。

然而,在 2016 年,使用 斯坦福解析器 与较新版本的斯坦福核心 NLP 存在一些不兼容问题(我遇到了 斯坦福核心 NLP v3.5 的问题)。

以下是我的做法,使用 Ruby 接口斯坦福核心 NLP 解析文本:

  1. 安装 斯坦福 CoreNLP gem

gem install stanford-core-nlp

2. 然后按照使用最新版本的Stanford CoreNLP的自述文件中的说明进行操作:
使用最新版本的Stanford CoreNLP(截至2014年10月31日的版本3.5.0)需要一些额外的手动步骤:
- 从http://nlp.stanford.edu/下载Stanford CoreNLP版本3.5.0。 - 将提取的存档内容放置在stanford-core-nlp gem的/bin/文件夹中(例如[...]/gems/stanford-core-nlp-0.x/bin/),或者放置在通过设置StanfordCoreNLP.jar_path配置的目录位置中。 - 从http://nlp.stanford.edu/下载完整的Stanford Tagger版本3.5.0。 - 在stanford-core-nlp gem的/bin/文件夹中(例如[...]/gems/stanford-core-nlp-0.x/bin/)或通过设置StanfordCoreNLP.jar_path配置的目录中创建一个名为“taggers”的目录。 - 将提取的存档内容放置在taggers目录中。 - 从https://github.com/louismullie/stanford-core-nlp下载bridge.jar文件。 - 将下载的bridger.jar文件放置在stanford-core-nlp gem的/bin/taggers/文件夹中(例如[...]/gems/stanford-core-nlp-0.x/bin/taggers/),或者放置在通过设置StanfordCoreNLP.jar_path配置的目录中。

接下来是将文本分成句子的 Ruby 代码:

require "stanford-core-nlp"

#I downloaded the StanfordCoreNLP to a custom path:
StanfordCoreNLP.jar_path = "/home/josh/stanford-corenlp-full-2014-10-31/"
  
StanfordCoreNLP.use :english
StanfordCoreNLP.model_files = {}
StanfordCoreNLP.default_jars = [
  'joda-time.jar',
  'xom.jar',
  'stanford-corenlp-3.5.0.jar',
  'stanford-corenlp-3.5.0-models.jar',
  'jollyday.jar',
  'bridge.jar'
]

pipeline =  StanfordCoreNLP.load(:tokenize, :ssplit)

text = 'Mr. Josh Weir is writing some code. ' + 
  'I am Josh Weir Sr. my son may be Josh Weir Jr. etc. etc.'
text = StanfordCoreNLP::Annotation.new(text)
pipeline.annotate(text)
text.get(:sentences).each{|s| puts "sentence: " + s.to_s}
  
#output:
#sentence: Mr. Josh Weir is writing some code.
#sentence: I am Josh Weir Sr. my son may be Josh Weir Jr. etc. etc.

1
我没有尝试过,但如果你只关心英语,我建议看一下Lingua::EN::Readability
Lingua::EN::Readability是一个Ruby模块,用于计算英文文本的统计信息。它可以提供单词、句子和音节的计数。它还可以计算几种可读性指标,如Fog指数和Flesch-Kincaid级别。该包括模块Lingua::EN::Sentence,将英文文本分成句子并注意缩写,以及Lingua::EN::Syllable,可以猜测书面英语单词的音节数。如果有发音字典,则可以查找字典中的音节数以提高准确性。
你需要的部分在sentence.rb文件中,如下所示:
module Lingua
module EN
# The module Lingua::EN::Sentence takes English text, and attempts to split it
# up into sentences, respecting abbreviations.

module Sentence
  EOS = "\001" # temporary end of sentence marker

  Titles   = [ 'jr', 'mr', 'mrs', 'ms', 'dr', 'prof', 'sr', 'sen', 'rep', 
         'rev', 'gov', 'atty', 'supt', 'det', 'rev', 'col','gen', 'lt', 
         'cmdr', 'adm', 'capt', 'sgt', 'cpl', 'maj' ]

  Entities = [ 'dept', 'univ', 'uni', 'assn', 'bros', 'inc', 'ltd', 'co', 
         'corp', 'plc' ]

  Months   = [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 
         'aug', 'sep', 'oct', 'nov', 'dec', 'sept' ]

  Days     = [ 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun' ]

  Misc     = [ 'vs', 'etc', 'no', 'esp', 'cf' ]

  Streets  = [ 'ave', 'bld', 'blvd', 'cl', 'ct', 'cres', 'dr', 'rd', 'st' ]

  @@abbreviations = Titles + Entities + Months + Days + Streets + Misc

  # Split the passed text into individual sentences, trim these and return
  # as an array. A sentence is marked by one of the punctuation marks ".", "?"
  # or "!" followed by whitespace. Sequences of full stops (such as an
  # ellipsis marker "..." and stops after a known abbreviation are ignored.
  def Sentence.sentences(text)

    text = text.dup

    # initial split after punctuation - have to preserve trailing whitespace
    # for the ellipsis correction next
    # would be nicer to use look-behind and look-ahead assertions to skip
    # ellipsis marks, but Ruby doesn't support look-behind
    text.gsub!( /([\.?!](?:\"|\'|\)|\]|\})?)(\s+)/ ) { $1 << EOS << $2 }

    # correct ellipsis marks and rows of stops
    text.gsub!( /(\.\.\.*)#{EOS}/ ) { $1 }

    # correct abbreviations
    # TODO - precompile this regex?
    text.gsub!( /(#{@@abbreviations.join("|")})\.#{EOS}/i ) { $1 << '.' }

    # split on EOS marker, strip gets rid of trailing whitespace
    text.split(EOS).map { | sentence | sentence.strip }
  end

  # add a list of abbreviations to the list that's used to detect false
  # sentence ends. Return the current list of abbreviations in use.
  def Sentence.abbreviation(*abbreviations)
    @@abbreviations += abbreviations
    @@abbreviations
  end
end
end
end

1
那里列出了很好的观点,但我发现在处理大量文本时,与其进行如此多的正则表达式替换,不如将一组单词放入数组中进行循环,然后按照您上面提到的条件以及其他行尾选项进行比较。在我的有限测试中,对于大型文档,速度快了约1000倍。 - JayCrossler

1

如果您正在考虑JAVA(以及Ruby也是一种较难的方式),那么Manning博士的答案是最合适的。它在这里 -

有一个句子分割器: edu.stanford.nlp.process.DocumentPreprocessor . 尝试使用命令:java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt

oneTokenizedSentencePerLine.txt . (这是通过一个(好的但启发式的)FSM完成的,因此速度很快;您不会运行概率解析器。)

但是,如果我们修改命令 java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt TO java edu.stanford.nlp.process.DocumentPreprocessor -file /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt ,这是一个小建议。它将按照您需要指定呈现为输入的文件类型。 因此,对于文本文件,-file用于HTML等。


1
也许尝试通过句点、空格,再跟一个大写字母来划分它?我不确定如何找到大写字母,但那将是我开始寻找的模式。 编辑:使用Ruby找到大写字母。 另一个编辑: 检查是否存在以小写字母开头的单词后面有句子结尾标点符号。

如果您将其拆分为不以大写字母开头的单词后面的句点,会怎样呢? - Jarrod
这正是我想到的,但我想知道是否有更好的解决方案。当然,如果句子以专有名词结尾,例如“我去了意大利”,它将无法工作。 - henry74
1
一个非常常见的情况是像“Dibbler先生”这样的名称会失败。 - Martin DeMello

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接