Как использовать java-библиотеку Stanford CoreNLP с Ruby для анализа настроений?

Я пытаюсь выполнить анализ настроений для большого набора твитов в локальном экземпляре MongoDB с Ruby on Rails 4, Ruby 2.1.2 и Mongoid ORM.

Я использовал бесплатно доступный https://loudelement-free-natural-language-processing-service.p.mashape.com API на Mashape.com, однако время ожидания истекает после быстрой отправки нескольких сотен твитов — очевидно, что оно не предназначено для обработки десятков тысячи твитов и это понятно.

Затем я решил использовать библиотеку Stanford CoreNLP, представленную здесь: http://nlp.stanford.edu/sentiment/code.html

Использование по умолчанию, в дополнение к использованию библиотеки в коде Java 1.8, по-видимому, заключается в использовании входных и выходных файлов XML. В моем случае это раздражает, учитывая, что у меня десятки тысяч коротких твитов, а не длинных текстовых файлов. Я хотел бы использовать CoreNLP как метод и выполнять цикл типа tweets.each.

Я предполагаю, что одним из способов было бы создать файл XML со всеми твитами, а затем получить один из процесса Java, проанализировать его и вернуть обратно в БД, но это кажется мне чуждым и потребует много работы.

Итак, я был счастлив найти на сайте, указанном выше, способ запустить CoreNLP из командной строки и принять текст как стандартный ввод, чтобы мне не пришлось возиться с файловой системой, а скорее передать текст в качестве параметра. Однако запуск JVM отдельно для каждого твита добавляет огромные накладные расходы по сравнению с использованием API анализа тональности без Loudelement.

Код, который я написал, уродлив и медленен, но он работает. Тем не менее, мне интересно, есть ли лучший способ запустить java-программу CoreNLP из Ruby без необходимости возиться с файловой системой (создавать временные файлы и указывать их в качестве параметров) или писать код 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

person herb    schedule 11.02.2015    source источник
comment
Рассмотрите возможность написания службы Java (WSDL, SOAP, REST или простая на основе TCP) и вызова ее из Ruby. Это самый обычный способ. Если вы умеете использовать JRuby, вызов методов Java напрямую кажется простым. Здесь описаны методы вызова Java-кода из Ruby без использования JRuby, но они выглядят сложными.   -  person Qualtagh    schedule 12.02.2015
comment
Вы видели этот Ruby-порт CoreNLP?   -  person diasks2    schedule 12.02.2015
comment
@diasks2 diasks2, я думаю, что видел это, но, судя по ридми, не похоже, чтобы в нем был реализован анализ настроений. Меня очень интересует модель глубокого обучения, которая, по утверждению CoreNLP, включена в нее по умолчанию: nlp.stanford.edu/ настроение   -  person herb    schedule 12.02.2015
comment
@herb Это упоминалось в комментариях к вашей ссылке Только что выпустил быстрый модуль Ruby для анализа и импорта дерево настроений, см. github.com/someben/treebank — я не смотрел его в подробно, но, возможно, стоит проверить.   -  person diasks2    schedule 13.02.2015


Ответы (2)


По крайней мере, вы можете избежать уродливой и опасной командной строки, используя 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 поддерживает такой "пакетный" ввод с линейной буферизацией. Тем не менее, это не должно быть очень сложно изменить, чтобы сделать это, если нет.

person oseiskar    schedule 13.02.2015

Как было предложено в комментариях @Qualtagh, я решил использовать JRuby.

Сначала я попытался использовать Java для использования MongoDB в качестве интерфейса (чтение непосредственно из MongoDB, анализ с помощью Java/CoreNLP и запись обратно в MongoDB), но Java-драйвер MongoDB оказался более сложным в использовании, чем Mongoid ORM, который я использую с Ruby, поэтому вот почему я чувствовал, что JRuby больше подходит.

Выполнение службы REST для Java потребовало бы от меня сначала узнать, как сделать службу REST на Java, что могло быть легко, а затем нет. Я не хотел тратить время на выяснение этого.

Итак, код, который мне нужно было сделать для запуска моего кода, был:

  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
person herb    schedule 22.02.2015