RCC 2013年度入会 知能情報学科2回生のRND.cppです.

twitterアカウントは@RND_cppです.

@RND_pngもあります.

初歩的なC++初心者以下のRubyを書いていることが多いですが,

技術系じゃなくてお絵かき系メインの人間なので大目に見てくださるとありがたいです.


はじめに

私は夏休みに,夏期制作として

2人のtwitterアカウントの最近のツイートから共通する語句を抜き出す

ツイート解析プログラムを制作していました.

今回はそれを拡張してRCC会員のツイートを解析し,

RCC全体の特徴というかRCCのトレンドのようなものを

抽出してみたいと思います.

言語はRubyです.

めかぶ(MeCab)

めかぶとは,オープンソースの形態素解析エンジンMeCabのことです.

導入方法についてはこの記事では詳しく述べませんが,

mecabとmecab-ipadicをインストールして使います.

端末から直接叩くこともできます.

形態素解析エンジン?

形態素解析とは Wikipedia先生曰く

対象言語の文法の知識(文法のルールの集まり)や辞書(品詞等の情報付きの単語リスト)を情報源として用い、自然言語で書かれた文を形態素(Morpheme, おおまかにいえば、言語で意味を持つ最小単位)の列に分割し、それぞれの品詞を判別する作業を指す。

(Wikipedia-形態素解析 から引用)

とのこと.

説明するより実行結果を見たほうが早いと思います.

MeCabで形態素解析すると以下のような情報が得られます.

$ mecab
ぱちおくん退院おめでとう
ぱちおくん   名詞,一般,*,*,*,*,*
退院  名詞,サ変接続,*,*,*,*,退院,タイイン,タイイン
おめでとう   感動詞,*,*,*,*,*,おめでとう,オメデトウ,オメデトー
EOS

こんな感じのことをしてくれるのが形態素解析エンジンです.

納豆(Natto)

Nattoは,RubyからMecabを使うためのインタフェースの一つです.

RubyからMecabを使うにはmecab-rubyというのでもできるらしいですが,

Nattoのほうが名前が可愛いので今回はNattoを使います.

導入方法や詳細な使い方はこの記事では詳しく述べませんが,

gem install でnattoを入れたら使えます.

NattoでMeCabを扱う方法は,下のコードにちょっとだけ書いておきます.

# coding: utf-8
require 'natto'
nat = Natto::MeCab.new
nat.parse("ぱちおくん退院おめでとう") do |word|
  p "surface:" + word.surface.to_s
  p "feature:" + word.feature.to_s
end

実行結果

$ ruby mecabtest.rb 
"surface:ぱちおくん"
"feature:名詞,一般,*,*,*,*,*"
"surface:退院"
"feature:名詞,サ変接続,*,*,*,*,退院,タイイン,タイイン"
"surface:おめでとう"
"feature:感動詞,*,*,*,*,*,おめでとう,オメデトウ,オメデトー"
"surface:"
"feature:BOS/EOS,*,*,*,*,*,*,*,*"

surface で抽出した形態素自身が, feature でその品詞や分析結果がそれぞれ文字列で得られます.

それ以上の説明は面倒なので割愛します.察して.

ツイート分析(TF-IDF)

今回はTF-IDF法を用いてRCC会員のツイートを分析し,

トレンド的なものを抽出できないかと試みます.

TF-IDF?

TF-IDF法は,文書内での単語の重要度を分析するための方法です.(たぶん)

TF(Term Frequency)…文書内でのその単語の出現頻度

DF(Document Frequency)…その単語を含む文書の出現頻度

の2つを用いてその単語がその文書内でどの程度重要性があるかを求めます.

要するに,文書中で何回も登場する単語ほど,他の文書であまり登場しない単語ほど,重要である.

といった考え方に基づいた分析方法です.(たぶん)

ある単語の TF = (文書中のその単語の出現回数)/(文書中の全単語数)

ある単語の DF= (その単語を含む文書の数)/(全ての文書数)

ですね.

この時点でわかると思いますが,TF-IDF法では重要度を分析したい文書だけでなく,「その他の関係ないたくさんの文書の集まり」(ここでは,文書集合と呼びます)を分析する必要があります.

IDF(Inverse Document Frequency)は,DFの逆数,すなわちその単語の珍しさ(出現しにくさ)を表しています.

「今日」という単語は色んな文書中に登場するのでIDFは小さくなります.

一方で「ぱちおくん」という単語(?)はRCC界隈でしか登場しませんのでIDFは大きくなります.

実際には

IDF = log(1/DF)

とか

IDF = log(1/DF)+1

とするみたいです.logは文書全体の数による影響を緩和するために使ってるみたい.

(DF≤1より,log(1/DF)の最小値は0になる.0を重みとして使用したくない場合はlog(1/DF)+1をIDFとして使用する.)

TF(単語の出現頻度に基づいた重要度)とIDF(単語の珍しさに基づいた重要度)をかけることでその単語の文書中での重要度を表すのがTF-IDF法です(間違ってたらゴメンナサイ)

ツイートの取得

RubyでTwitterAPIを扱うには,Ruby Twitter Gem という便利なgemがあります.それを使って特定のアカウントのツイートを200件ずつ取得するコードを書きます.

require 'twitter'
module TL
  extend self
  
  @@client = Twitter::REST::Client.new do |config|
    config.consumer_key        = "******"
    config.consumer_secret     = "******"
    config.access_token        = "******"
    config.access_token_secret = "******"
  end
  def get_tweet(user,limit)#引数はUserIDとリクエストを送る回数
    results=[]
    max_id=nil
    for num in 0...limit
        begin
          puts "get_tweet from "+user.to_s
          if max_id
            @@client.user_timeline(user,{"count"=>200,"max_id"=>max_id}).each do |t|
              unless t.text.match(/(^RT|@|http)/)
                results.push(t.text) 
              end
              max_id=t.id-1
            end
          else
            @@client.user_timeline(user,{"count"=>200}).each do |t|
              unless t.text.match(/(^RT|@|http)/)#URLを含むツイート,リツイート,リプライを除外する
                results.push(t.text) 
              end
              max_id=t.id-1
            end
          end
          Kernel.sleep(5.1)#API上限対策.Application-only authentication使えばこんなに待たなくてもいい
        rescue => e
          p e.message
        end
    end
    return results
  end
  def get_documents_tweet#Botを大量にフォローして大量にフォロバされてるアカウント「Tweet_TFIDF」を作ったのでそれを利用
    results=[]
    @@client.follower_ids("Tweet_TFIDF").each_with_index do |uid,ind|
      results.concat(get_tweet(uid,5))
      #break if ind > 1 デバッグ用 
    end
    return results
  end
  def get_group_tweets(screen_name_list)#ユーザーのスクリーンネームが入った配列をわたすとツイートをまとめてくれるメソッド
    #正直トレンドを出したいならtwitterのリストからツイート取得で良かったと思うんだ.
    results=[]
    screen_name_list.each do |screen_name|
      results.concat(get_tweet(@@client.user(screen_name).id,3))
    end
    return results
  end
end

get_documents_tweetメソッドで使用している「Tweet_TFIDF」は私がBOTをフォローしまくったアカウントです.

放置していたらなんかよくわからないBOTに大量にフォローされていたのでせっかくなので利用してやろうと思い

これらBOTのツイートを文書集合(DFを算出する時につかう文書)に含めることにしました.

BOTのツイートは何かしら偏っていたり,RTが多かったりするので本当は良くないと思いますが他の解決策が思いつきませんでした.

follower_idsではなくfriend_idsを使えばフォロワーではなくフォローの一覧で文書集合を作ることができます.まぁ結局BOTしかフォローしてないので大差ないですが.

TFの算出

今回はわかりやすく2文字以上の名詞だけを抽出しようと思います.幾つものツイートがStringで格納された配列から

それぞれの名詞の数を数え,TFを割り出してハッシュに格納します.

ざっと書くと下のコードみたいな感じです.

module TFIDF #TFとかIDFとかするモジュール(名前は適当)
 extend self
 @@nat = Natto::MeCab.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)#2文字以上の固有名詞と一般名詞のみ抽出
            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

TF-IDFの算出

まず,DFを算出するもとになる「単語が含まれる文書の数」を数え上げます.

たぶんTFの算出の時に使用した単語だけを数えた方がいいと思いますが,面倒なので登場する名詞全部数えます(オイ).

文書集合から文書を取り出す→登場する全ての名詞をキーとしたハッシュを作成→作成したハッシュをもとに数え上げる.みたいなかんじ.

#module TFIDFの中
  def df_counts(documents)#ある単語が含まれる文書の数を数え上げる
    df = Hash.new#単語とその単語が登場する文書数を記録するHash
    documents.each do |e|
      word_hash = Hash.new
      @@nat.parse(e) do |word|
        if (word.feature.match("名詞"))
          word_hash[word.surface]=1
        end
      end
      word_hash.each_key do |key|
        df[key]||=0
        df[key]+=1
      end
    end
    return df
  end

たぶんもっといい実装方法がいくらでもあると思いますがざっくりとこんな感じです.

TF-IDFの算出はこんな感じ.

#module TFIDFの中
def tfidf(tf,df,d_num)#tf=TF,df=単語をキーとし,その単語が登場する文書数を格納したハッシュ,d_num=全体の文書数
  word_hash=Hash.new
  df_cpy = df.dup
  tf.each do |key,value|
    if df_cpy[key]
      df_cpy[key]=1 if df_cpy[key]==0#文書数が0の時(実際に使うときは文書集合にRCCのツイートを含めているので0にはならないと思う)
      word_hash[key] = value*(Math.log10(d_num.to_f/df_cpy[key])+1)
    end
  end
  return word_hash
end

TF*(log(全体の文書数/単語が含まれる文書数)+1)で計算してます.

いよいよRCC会員のツイートを分析

#moduleの定義は省略
rcc_acounts=[
#RCC会員のアカウント名を羅列
]
rcc_tweets = TL.get_group_tweets(rcc_acounts)
tf = TFIDF.cnt(rcc_tweets)
documents_set = TL.get_documents_tweet
documents_set.concat(rcc_tweets)
df = TFIDF.df_counts(documents_set)
words = TFIDF.tfidf(tf,df,documents_set.length).sort_by{|key,val| -val}
words.each do |key,value|
  puts ""+key+":"+value.to_s
end

$ruby TweetRCC.rb > result.txt

RCC(実行結果)

なお実行結果が出るまでに1時間強かかって糞っぽいです.

実行結果(GoogleDrive)

感じ:0.02565952096957314
これ:0.025401341569093946
自分:0.017377724593458783
is:0.014882230050430326
RCC:0.014191585649419892
gt:0.01410490116943747
guidance:0.013834015655746403
あと:0.01296730662739286
Yonkoma:0.012882232799413008
みんな:0.012341011560737471
やつ:0.011888851099403222
RT:0.01105592118317222
情報:0.011050691762001397
大学:0.010826544595789706
先生:0.010669630837960225
英語:0.010494360545439039
kanjava:0.010225054186510847
ツイート:0.009768806229088295
寿司:0.009646966395637462
それ:0.009432659862264176
ゲーム:0.009159097844847287
なん:0.008965365263996477
kc:0.00882769403468479
サークル:0.00848512950900431
先輩:0.008466574880185467
動画:0.00846268280283059
学校:0.008385315303283042
バイト:0.008316203327496668
トマト:0.0079955414152423
PC:0.00790082318653882
コード:0.007863179020334505
久しぶり:0.007708408615447071
画像:0.007646907240659183
ゼミ:0.007387938792006584
お腹:0.007333269302560361
どこ:0.007271379762993175
アニメ:0.007221120047586259
Twitter:0.0069568889627118005
部室:0.006804332851483256
続き:0.006571555413456901
バス:0.00648897669528573
アプリ:0.006404451983717253
技術:0.00631871024871125
yonkoma:0.006245845231912513
課題:0.006232811961256246
あれ:0.00618211173552463
ここ:0.006111920026521414
単位:0.006106196615963269
京都:0.006102122132052065
コマ:0.006004388604940881

「大学」「英語」「サークル」「ゼミ」「課題」「単位」といった語句を見ると,結構普通の大学生があつまるサークルっぽく見えます.

しかし,「kanjava」「コード」「アプリ」「技術」など見ると,やはり情報系だなぁという感じがしますね.

「寿司」が上位に入っていることから,やっぱり情報系と寿司は切っても切れない縁があるような気がします.

「これ」や「あと」,「やつ」「それ」「ここ」など余計なものが入っているのはやはり文書集合をBOTから生成しているからじゃないかなぁとおもいます.

抽出する名詞の条件をもっと細かく指定すれば除外できるかもしれません.

他に面白かった部分を紹介します.

RCCのアイドルぱちおくん

ぱちおくん(patio-kun)は恐るべきコンテンツ力を持ったRCC会員です.

イラスト,DTM,プログラミング,ギター,歌ってみた,小説などその活動範囲は幅広く,彼自身もコラ画像の素材として数多くのコンテンツを生み出し続けています.

ぱちお:0.00457366529676746

ぱちおくんは88位,RCC会員の名前では一番上に登場しています.

阿部:0.004275571474040004

次が,阿部くんで98位でした.

ちなみに

ぱちおくん:0.0031607594517423835

が167位に入っていました.

「ぱちお」を含む語句は全部で21語はいっており,「ぱちお」はどうやらかなり分散した上での88位のようです.

さすがアイドル…

プログラミング言語とか

Java:0.005517810905020213(59番目)
Ruby:0.0023725937288058313(255番目)
Unity:0.0023128209813912367(272番目)
Haxe:0.002148998364519493(303番目)
php:0.0014521023680710786(541番目)
js:0.0014521023680710786(548番目)
Haskell:0.0007697504962871577(1181番目)

JavaFXが263番目に入ってました.C++は多分単語として認識されなかったんだと思います.

Javaが高い位置にいるのは最近kanjavaとかあったからだと思います.

Rubyが高い位置にいるのはRCC会員が制作したWebアプリ,YonkomaがRubyで組まれていて最近ずっと開発していたからだと思います.

Unityはなんか常に人気がありますね.

Haxeとかphpとかたぶんツイートしてる人1人だけなんだけど…いったいどれだけ呟いたんでしょうか.

Haskellはみんなやりたいと言いつつRCC内ではまだ始めてるひとは少ないみたいですね.

エディタ戦争

vim:0.0016714431724040506(417番目)
emacs:0.0010021515240133875(862番目)
SublimeText:0.0007697504962871577(1275番目)

これに関しては何も言えません.私はgedit派です.

おまけpatioくんとツイート解析

patioくんのアカウントから,延べ8000件のツイートをもとに解析しました.(個人情報を解析する屑)

最初の解析の際に文書集合としてBOTから取得してきた大量のツイートをローカルでデータベースに保存していたので2回目以降は短い時間で解析できました.

感じ:0.038826417651119174
イラスト:0.03059618987582529
あと:0.026607125281923893
タスク:0.025755621226112555
ギター:0.02506022909922245
GUMI:0.021047992813551446
Yonkoma:0.019521779574939727
自分:0.019242426101834686
みんな:0.01906844195759251
アニメ:0.018898610301805677
これ:0.018795238299285384
エピソード:0.018662681548802382
ボカロ:0.018598127640313617
TL:0.017383129435288712
久しぶり:0.01732512655941398
キャラ:0.01580775705733405
yonkoma:0.014900151724284274
プリキュア:0.014618055290496314
マリオ:0.01450407917737668
情報:0.01429799666352229
pixiv:0.014211720079576263
やつ:0.013975311582912705
localhost:0.013549029440572903
シール:0.01320146504271945
続き:0.012936929366987341
ぁぼした:0.012867469313509596
コマ:0.012710628864704256
阿部:0.012665128965641634
あれ:0.012545763622099847
ぁぼごとに:0.01218159864892639
動画:0.011836232518499695
DTM:0.011809244646544223
RCC:0.011491147696944148
ゲーム:0.01140253957444079
師走:0.01129732772951444
最高:0.011260417336265302
ワン:0.011078964250103292
原稿:0.010986742388635813
ペン:0.010908413018890284
英語:0.010893321605758736
タブ:0.010845433097188736
コミ:0.010795810665649988
漫画:0.010686489275846568
Twitter:0.010651769430130353
ハッカソン:0.010095237973844285

ぱちおくんがどんな人間かなんとなくわかるかなぁと思います.

Yonkoma

抽出結果に何度かyonkomaやらYonkomaというのが出てきていますね.

YonkomaはRCC会員数人で制作した,Twitterのリプライでリレー四コマをまわすWebアプリです.

http://yonkoma.abcang.net/

ぱちおくんも制作チームに入っているプロジェクトです.

私もDBとかModelまわりでちょっとだけコード書いてました(ほとんどサボってた).

ぜひ使ってやってください.


おわりに

今回のツイート解析ですがもともとは

2人のtwitterアカウントをから,2人の共通点を見つけて

話題を提供するプログラムを作ろうと思い立って始めたものでした.

今度はあるツイートが誰のツイートに一番近いかを分類するベイズフィルタ的なものを作って

一番話が合いそうな人を探すプログラムでも作ろうと思います.

参考

TF-IDFで文書内の単語の重み付け-http://blog.takuti.me/2014/01/tf-idf/

自然言語処理 (IT Text)…自然言語処理の教科書

tf-idf – Wikipediahttp://ja.wikipedia.org/wiki/Tf-idf


次の記事はXiPHiAさんです。

Twitterでフォローしよう

おすすめの記事