【问题标题】:How do you parse a paragraph of text into sentences? (perferrably in Ruby)如何将一段文本解析成句子? (最好在 Ruby 中)
【发布时间】:2010-10-26 00:55:34
【问题描述】:

考虑到 Mr. and Dr. 和 U.S.A 等案例,您如何将段落或大量文本分解成句子(最好使用 Ruby)? (假设您只是将句子放入数组数组中)

更新: 我想到的一种可能的解决方案是使用词性标注器 (POST) 和分类器来确定句子的结尾:

从琼斯先生那里获取数据,当他走出意大利避暑别墅的阳台时,他感觉到温暖的阳光照在他的脸上。他很高兴能活着。

分类器 Mr./PERSON Jones/PERSON 感觉/O 暖/O 太阳/O 上/O 他/O 脸/O 作为/O 他/O 踩/O 出/O 上/O/O 阳台/O /O 他/O 夏天/O 家/O 在/O 意大利/LOCATION ./O 他/O 是/O 快乐/O 到/O 是/O 活着/O ./O

发布 Mr./NNP Jones/NNP 毡/VBD the/DT 暖/JJ sun/NN on/IN his/PRP$ face/NN as/IN he/PRP step/VBD out/RP on/IN/DT 阳台/NN的/IN他/PRP$夏天/NN家/NN在/IN意大利。/NNP他/PRP是/VBD高兴/JJ到/TO是/VB活着。/IN

我们可以假设,因为意大利是一个地点,句号是句子的有效结尾吗?自从以“先生”结束没有其他词性,我们可以假设这不是一个有效的句末期吗?这是对我的问题的最佳答案吗?

想法?

【问题讨论】:

  • 有没有具体的规定。如果您能用英语告诉我们规则,我相信我们(或您)将能够编写解决方案。例如:“abbr”等缩写词后面有句号吗?如果您要解析语法教科书,您可能会使用简单的解决方案,但如果您使用任意文本,那么每个解决方案都会有缺点,比如......你知道吗?
  • POS 标注器太过分了。使用基于 NLP 的分词器,您的规则会更简单。

标签: ruby text parsing split nlp


【解决方案1】:

尝试查看Ruby wrapper around the Stanford Parser。它有一个 getSentencesFromString() 函数。

【讨论】:

  • 我将继续使用斯坦福解析器——它就在某个地方!谢谢!
  • edu.stanford.nlp.process.DocumentPreprocessor,顺便说一句
  • 是的,通过 Ruby 包装器或直接调用 edu.stanford.nlp.process.DocumentPreprocessor(从代码或命令行:java edu.stanford.nlp.process.DocumentPreprocessor /u /nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt ,您可以将文本分成句子。(这是通过(良好但启发式的)FSM完成的,因此速度很快;您没有运行概率解析器。)
【解决方案2】:

为了清楚起见,没有简单的解决方案。这是 NLP 研究的主题,如 quick Google search 所示。

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

openNLP

补充说明:决定句子从哪里开始和结束的问题在natural language processing中也叫sentence boundary disambiguation(SBD)。

【讨论】:

  • 我无法为 openNLP 找到一个简单的 ruby​​ 包装器 - 你遇到过吗?他们确实有一个句子分割器......
  • @phillc:嗯,所谓的句子边界消歧“是自然语言处理中决定句子从哪里开始和结束的问题”。 (en.wikipedia.org/wiki/Sentence_boundary_disambiguation)
【解决方案3】:

看起来这颗红宝石可以解决问题。

https://github.com/zencephalon/Tactful_Tokenizer

【讨论】:

  • 我会试一试。谢谢!
【解决方案4】:

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

Punkt sentence tokenizer

它基于以下论文:

Kiss,Tibor 和 Strunk,1 月(2006 年):Unsupervised Multilingual Sentence Boundary Detection计算语言学 32: 485-525.

论文中的方法非常有趣。他们将句子分裂的问题简化为确定一个单词与以下标点符号的关联程度的问题。缩写词后句号的重载是造成大部分模棱两可句号的原因,所以如果你能识别出缩写词,你就很有可能识别出句子的边界。

我已经非正式地测试了这个工具,它似乎可以为各种(人类)语言提供良好的结果。

将其移植到 Ruby 中并非易事,但它可能会给您一些想法。

【讨论】:

  • 两个链接都坏了,有什么不能被删除的可以分享吗?
【解决方案5】:

如果您真的很想把它做好,这是一个难题。您会发现 NLP 解析器包可能提供此功能。如果您想要更快的东西,您最终需要使用经过训练的令牌窗口的概率函数来复制其中的一些功能(您可能希望将换行视为令牌,因为如果这是一段的结尾)。

编辑:如果您可以使用 Java,我推荐斯坦福解析器。我不推荐其他语言,但我很想知道还有什么是开源的。

【讨论】:

  • 是的,我使用过斯坦福 NLP 解析器,但没有找到分句器。如果你有兴趣使用它,有人在 github 上创建了一个 rjb(ruby 到 java 桥接)包装器,我可以相对轻松地使用它。这是您感兴趣的人的链接github.com/tiendung/ruby-nlp/tree/master 注意:在 Windows 上,加载 java 库时必须将冒号更改为分号。干杯。
  • 你是对的,解析器包中没有分句器,但是有一个分词器,它可以让你部分地到达那里。它处理那些提到的事情,“先生”。作为标记与“。”作为句子的结尾。
  • 有一个分句器:edu.stanford.nlp.process.DocumentPreprocessor。尝试命令: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt 。 (这是通过(良好但启发式的)FSM 完成的,因此速度很快;您没有运行概率解析器。)
【解决方案6】:

不幸的是,我不是一个 ruby​​ 人,但也许 perl 中的一个例子会让你朝着正确的方向前进。使用不匹配的look behind 结尾标点符号,然后一些特殊情况在 not behind 后跟任意数量的空格,然后是大写字母。我敢肯定这并不完美,但我希望它能为您指明正确的方向。不知道你怎么知道 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";
}

【讨论】:

    【解决方案7】:

    同意接受的答案,使用 Stanford Core NLP 是没有道理的。

    但是,在 2016 年,有一些 incompatibilitiesStanford Parser 与更高版本的 stanford core nlp 接口(我遇到了 Stanford Core NLP v3.5 的问题)。

    这是我使用 Ruby 与斯坦福核心 NLP 接口将文本解析为句子的操作

    1. 安装Stanford CoreNLP gem:

    gem install stanford-core-nlp

    1. 然后按照readme for Using the latest version of the Stanford CoreNLP 上的说明进行操作:

    使用最新版本的斯坦福 CoreNLP(3.5.0 版) 2014 年 10 月 31 日)需要一些额外的手动步骤:

    然后是把文本拆分成句子的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.
    

    【讨论】:

      【解决方案8】:

      也许尝试用句点后跟空格后跟大写字母来拆分它?我不知道如何找到大写字母,但这将是我开始研究的模式。

      编辑: Finding uppercase letters with Ruby.

      另一个编辑:

      检查不以大写字母开头的单词后面的句尾标点符号。

      【讨论】:

      • 如果你在不以大写字母开头的单词后面的句点处拆分它怎么办?
      • 这正是我想出的,但我想知道是否有更好的解决方案。当然,如果句子以“我去了意大利”之类的专有名词结尾,那它就行不通了。
      • 一个非常常见的失败案例是像“Mr. Dibbler”这样的名字
      【解决方案9】:

      如果您正在考虑 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 用于 HTML 等。

      【讨论】:

        【解决方案10】:

        我没有尝试过,但如果英语是您唯一关心的语言,我建议您看看 Lingua::EN::Readability

        Lingua::EN::Readability 是一个 Ruby 模块,用于计算英文文本的统计信息。它可以提供单词、句子和音节的计数。它还可以计算几个可读性度量,例如雾指数和 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
        

        【讨论】:

        • 其中列出的要点,虽然我发现为了加快大量文本的速度,而不是做这么多的正则表达式替换,让一组单词循环通过,然后根据您上面提到的条款和其他行尾选项进行比较。在我对大型文档的有限测试中,速度提高了大约 1000 倍。
        【解决方案11】:

        在句点后面加上一个空格和一个大写字母不会适用于“布朗先生”这样的标题。

        句号使事情变得困难,但一个容易处理的例子是感叹号和问号。但是,有些情况会使这不起作用。即雅虎的公司名称!

        【讨论】:

          【解决方案12】:

          显然paragraph.split('.') 不会削减它

          #split 将采用正则表达式作为答案,因此您可以尝试使用零宽度的lookbehind 来检查以大写字母开头的单词。当然,这会在专有名词上分裂,因此您可能不得不求助于像 /(Mr\.|Mrs\.|U\.S\.A ...) 这样的正则表达式,除非您以编程方式构建正则表达式,否则这将非常丑陋。

          【讨论】:

            【解决方案13】:

            我认为这并不总是可解决的,但您可以根据“.”(句点后跟空格)进行拆分,并验证句点之前的单词不在诸如 Mr、Dr、等等

            但是,当然,您的列表可能会遗漏一些单词,在这种情况下,您会得到不好的结果。

            【讨论】:

              【解决方案14】:

              我不是一个 Ruby 人,而是一个分裂的 RegEx

               ^(Mr|Mrs|Ms|Mme|Sta|Sr|Sra|Dr|U\.S\.A)[\.\!\?\"] [A-Z]
              

              将是我最好的选择,一旦你得到了段落(在 \r\n 上分割)。这假设你的句子是正确的。

              显然这是一个相当丑陋的 RegEx。在句子之间强制两个空格怎么样

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-05-21
                • 2012-09-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多