如何使用Stanford CoreNLP Java库结合Ruby进行情感分析?

4

我正在尝试对本地 MongoDB 实例中的大量推文语料进行情感分析,使用的是 Ruby on Rails 4、Ruby 2.1.2 和 Mongoid ORM。

我已经使用了 Mashape.com 上免费提供的 https://loudelement-free-natural-language-processing-service.p.mashape.com API,但它在快速连续推送几百条推文后就开始超时 -- 很明显它不适合处理数万条推文,这是可以理解的。

因此,接下来我想使用在这里推广的 斯坦福 CoreNLP 库http://nlp.stanford.edu/sentiment/code.html

默认用法除了在 Java 1.8 代码中使用库之外,似乎还需要使用 XML 输入和输出文件。对于我的用例来说,这很麻烦,因为我有成千上万条短推文,而不是长文本文件。我想像使用方法一样使用 CoreNLP 并执行类似于 tweets.each 的循环。

我想一种方法是构建一个包含所有推文的XML文件,然后从Java进程中获取一个推文,解析它并将其放回到DB中,但这感觉很陌生,而且需要大量工作。
所以,我很高兴在上面链接的网站上找到了一种方法,可以从命令行运行CoreNLP并接受文本作为stdin,这样我就不必开始摆弄文件系统,而是将文本作为参数提供。然而,为每个推文单独启动JVM与使用无声元素免费情感分析API相比增加了巨大的开销。
现在,我编写的代码又丑又慢,但它能正常工作。尽管如此,我想知道是否有更好的方法在Ruby中运行CoreNLP java程序,而不必开始摆弄文件系统(创建临时文件并将它们作为参数)或编写Java代码?
以下是我正在使用的代码:
def self.mass_analyze_w_corenlp # batch run the method in multiple Ruby processes
  todo = Tweet.all.exists(corenlp_sentiment: false).limit(5000).sort(follow_ratio: -1) # start with the "least spammy" tweets based on follow ratio
  counter = 0

  todo.each do |tweet|
    counter = counter+1

    fork {tweet.analyze_sentiment_w_corenlp} # run the analysis in a separate Ruby process

    if counter >= 5 # when five concurrent processes are running, wait until they finish to preserve memory
      Process.waitall
      counter = 0
    end

  end
end

def analyze_sentiment_w_corenlp # run the sentiment analysis for each tweet object
  text_to_be_analyzed = self.text.gsub("'"){" "}.gsub('"'){' '} # fetch the text field of DB item strip quotes that confuse the command line

  start = "echo '"
  finish = "' | java -cp 'vendor/corenlp/*' -mx250m edu.stanford.nlp.sentiment.SentimentPipeline -stdin"
  command_string = start+text_to_be_analyzed+finish # assemble the command for the command line usage below

  output =`#{command_string}` # run the CoreNLP on the command line, equivalent to system('...')
  to_db = output.gsub(/\s+/, "").downcase # since CoreNLP uses indentation, remove unnecessary whitespace
  # output is in the format of "neutral, "positive", "negative" and so on

  puts "Sentiment analysis successful, sentiment is: #{to_db} for tweet #{text_to_be_analyzed}."

  self.corenlp_sentiment = to_db # insert result as a field to the object
  self.save! # sentiment analysis done!
end

考虑编写Java服务(WSDL,SOAP,REST或简单的基于TCP),并从Ruby中调用它。这是最常见的方法。如果您能够使用JRuby,则似乎可以直接调用Java方法。这里描述了在不使用JRuby的情况下从Ruby调用Java代码的方法,但它们看起来很复杂。 - Qualtagh
你有看过这个CoreNLP的Ruby移植版吗? - diasks2
@diasks2,我想我已经看过它了,但根据自述文件,它似乎没有实现情感分析。我对CoreNLP声称默认具有的深度学习模型非常感兴趣:http://nlp.stanford.edu/sentiment/ - herb
@herb 这个 在你的链接评论中被提到过:“刚发布了一个快速解析和导入情感树库的 Ruby 模块,请参见 https://github.com/someben/treebank/” - 我还没有详细查看,但可能值得一看。 - diasks2
2个回答

0

你可以通过使用IO.popen打开和与外部进程通信,来避免丑陋和危险的命令行操作,例如:

input_string = "
foo
bar
baz
"

output_string =
    IO.popen("grep 'foo'", 'r+') do |pipe|
        pipe.write(input_string)
        pipe.close_write
        pipe.read
    end

puts "grep said #{output_string.strip} but not bar"

编辑:为避免在每个项目上重新加载Java程序的开销,您可以在todo.each循环周围打开管道,并像这样与进程通信。

inputs = ['a', 'b', 'c', 'd']

IO.popen('cat', 'r+') do |pipe|

    inputs.each do |s|
        pipe.write(s + "\n")
        out = pipe.readline

        puts "cat said '#{out.strip}'"
    end
end

那就是,如果Java程序支持这样的行缓冲“批量”输入。但如果不支持,修改它实现这一点也不应该很难。

0

根据@Qualtagh的评论建议,我决定使用JRuby

起初,我尝试使用Java来使用MongoDB作为接口(直接从MongoDB读取数据,用Java / CoreNLP进行分析,然后写回MongoDB),但是与我在Ruby中使用的Mongoid ORM相比,MongoDB Java驱动程序更复杂,所以我觉得JRuby更合适。

如果要为Java创建一个REST服务,我首先需要学习如何在Java中创建REST服务,这可能很容易,也可能不是。我不想花时间去弄清楚这个。

所以,我需要运行我的代码的代码是:

  def analyze_tweet_with_corenlp_jruby
    require 'java'
    require 'vendor/CoreNLPTest2.jar' # I made this Java JAR with IntelliJ IDEA that includes both CoreNLP and my initialization class

    analyzer = com.me.Analyzer.new # this is the Java class I made for running the CoreNLP analysis, it initializes the CoreNLP with the correct annotations etc.
    result = analyzer.analyzeTweet(self.text) # self.text is where the text-to-be-analyzed resides

    self.corenlp_sentiment = result # adds the result into this field in the MongoDB model
    self.save!
    return "#{result}: #{self.text}" # for debugging purposes
  end

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