質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.45%

  • Ruby

    9710questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

rubyでTF-IDF

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,370

kamatmt

score 17

プログラミング初心者です。
rubyでtf-idfを求めようとしているのですが、tf(単語の出現回数)を求めるところから躓いています。
このプログラムを実行すると
{}
このような結果が返ってきます。
何がいけないのかわからず困っています。
解決策をお願いします。

require"MeCab"
module TFIDF 
 extend self
 @@nat = MeCab::Tagger.new
  def cnt(documents)
    word_hash = Hash.new
    terms_count = 0
    documents.each do |e|
      @@nat.parse(e) do |word|
        if /[^!-@\[-`{-~ 「」]/ =~ word.surface
          if (word.feature.match(/(固有名詞|名詞,一般)/)) and (word.surface.length>1)#固有名詞と一般名詞のみ抽出
            word_hash[word.surface]||=0
            word_hash[word.surface]+=1
            terms_count+=1
          end
        end
      end
    end
    word_hash.each{|key,value| word_hash[key] = value.to_f / terms_count }
    return word_hash
  end
end

t1 =["今テレビで町田特集やってる!","【ひみつの","【ひみつの","御嶽海だ~(*^^*) あ。"]
tf = TFIDF.cnt(t1)
puts tf

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

ちょっと色々ありますが、私の手元でとりあえず動くようになった版を貼り付けます。

require 'mecab'
module TFIDF
  extend self
  @@nat = MeCab::Tagger.new
  def cnt(documents)
    word_hash = {}
    terms_count = 0
    documents.each do |e|
      node = @@nat.parseToNode(e)
      while node = node.next
        if node.feature =~ /^(?:名詞,固有名詞|名詞,一般)/
          # 固有名詞と一般名詞のみ抽出
          word_hash[node.surface] ||= 0
          word_hash[node.surface] += 1
          terms_count += 1
        end
      end
    end
    word_hash.each { |key, value| word_hash[key] = value.to_f / terms_count }
    word_hash
  end
end

解説

環境は、Mac OS X 10.11.2 + Ruby 2.3.0p0 に下記で入れています。

brew install mecab
brew install mecab-ipadic
gem install mecab
  • require "MeCab"じゃなくてrequire "mecab"

私の環境(Mac OS X)だと"mecab"でした。Windowsだったら大文字小文字を無視するのでうまく言っているかでかも知れません。

  • paresじゃなくてparseToNode

MeCab::Tagger#parseは解析した言葉全てを改行("\n")区切りで一つの文字列で返ります。do...endとかは対応していません。parseの文字列をsplitで分割してeachしてもいいのですが、MeCab::Tagger#parseToNodeMeCab::Nodeを取得した方がいいでしょう。MeCab::Node#surfaceでその言葉を、MeCab::Node#featureでCSVになっている部分を取得できます。

このMeCab::Nodeですが、非常にRubyっぽくありません。

  1. parseToNodeで取得されるのは文頭という特殊なノードです。文頭なので、surfaceは""(空文字)です。
  2. 次のノードはMeCab::Node#nextで取得します。最後だとnilが返るようです。

このことから、下記のように最初の1つは飛ばして、whileで次々に入れていけばうまくいきます。

node = @@nat.parseToNode(e)
while node = node.next
  # ここに各nodeに対する処理を書く
end
  • if の分岐は冗長すぎない?

node.featureに対して固有名詞か一般名詞かをみるだけでいいと思います。記号が入ることはあり得ないです(そんなのがあったら、MeCabのバグでしょ、たぶん)し、長さがないこともあり得ない(そんなのがあったら、これもMeCabのバグでしょ、たぶん)ので、条件は一つだけでいいと思います。先頭の"^"を使い、"(?:...)"を使ってちょっとだけ高速になったような気がします。たぶん。

おまけ。

mecab-extというもっとRubyっぽくMeCabを操作できるGemがあるようです。こっちを使ってもいいかもしれません。
参考: Qiita: Mecab をもっと手軽に Ruby で扱える Gem

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.45%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Ruby

    9710questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。