ブラウザ上でデータ分析が出来る!Clojure/Gorilla入門


概要
この記事は、Gorillaという
ブラウザ上でClojureという言語を利用出来るライブラリを利用し、
ブラウザ上でデータ分析環境を構築するための入門記事です。
Clojureの事前知識は一切不要で、
ClojureやGorillaのインストールから、ブラウザ上で各種手法を
コピペだけで実践出来るになるまでを説明しています。
なお、各種分析手法の詳細には言及しておりません。
とにかくGorillaを動かす
「説明は良いから、とにかく動かしたい」
という方向けに要点だけ説明します。
leiningenをインストールし、作業フォルダにてlein new gorillaでプロジェクトを作り、
生成されたgoraillaフォルダ直下にあるproject.cljに下記をコピペし保存、
lein gorillaと打ち込み、少し待つと
Running at http://localhost:XXXXX/worksheet.html
と表示されるので、そのURLを叩くとこんな感じのページが表示されます。
以上。
(defproject gorilla-test "0.1.0-SNAPSHOT" :description "A test project for Gorilla REPL." :dependencies [[org.clojure/clojure "1.6.0"] [incanter "1.5.5"] [incanter-gorilla "0.1.0"] [cc.artifice/clj-ml "0.6.0-SNAPSHOT"] [org.atilika.kuromoji/kuromoji "0.7.7"]] :main ^:skip-aot gorilla-test.core :target-path "target/%s" :repositories [["Atilika Open Source repository" "http://www.atilika.org/nexus/content/repositories/atilika"]] :plugins [[lein-gorilla "0.3.3"]] :profiles {:uberjar {:aot :all}})
Clojure/Gorillaとは
Clojureとは、JVM上で動くプログラミング言語の一種です。
Clojureには統計解析ライブラリIncanterや機械学習ライブラリclj-mlなど、
継続して開発されているデータ分析用のライブラリが用意されています。
また、同じくJVM上で動くHadoopやHBaseなどのシステムとも親和性が高く、
それらを利用して大規模なデータ分析を行うのに適しています。
しかし、Clojureをいつでもどこでも誰にでも利用して貰うためには、
各端末にJDKのインストールやパスを通すなどの環境構築が必要であり、
各種ライブラリの更新の度全端末にそれを適用し続けるのは
ハードルが高いと言わざるを得ません。
また、データを個々の端末に移動するのも非常にコストがかかります。
勿論これはClojureに限った話ではなく
他の処理系においても同じ問題ではありますが。
そこで、ブラウザ上で誰でもアクセス出来て、
直ちにデータ分析を行えるようなシステムを簡単に構築出来れば
上記のハードルをクリアできます。
つまり、誰でも色んな端末(スマホやタブレットでも!)アクセス出来て、
最新のバージョンを利用したければサーバ側で更新を掛ければ済み、
データはサーバ上に置いたままなので一々解析するのに
全データをローカルに落とす必要も無く、
解析用の処理を送り結果を受け取るだけでよくなります。
そのような要望によって生み出されたのがGorillaです。
下記のGollira本家サイト、また、
GorillaにIncanterを組み込んだincanter-gorillaのサイトを
ご覧頂ければどのようなものかがご理解頂けると思います。
Gollira本家
incanter-gorilla
同様のプロジェクトはPythonやRにも存在しています。
特にIPython notebookはかなりの出来栄えですので、
特にJVM環境以外でも問題無く、
かつPythonに慣れ親しんでいる方は
そちらを利用するのも良いと思われます。
Clojureのインストール
主にWindows7向けのインストール方法について説明します。
まず最新のJavaSDK(Javaの開発環境)を入れる必要があります。
下記からご自分の端末に合わせたモノをインストールしてください。
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
次に、下記のページを参考にPath, JAVA_HOMEを設定します(windows向け)。
http://www.javadrive.jp/install/jdk/index4.html
次に、Clojureの開発ツールであるleiningenをインストールします。
公式サイトからlein.batをダウンロードして下さい。
http://leiningen.org/
(あるいはこれを利用)
最後に、パスの通っている作業フォルダ(例:c:\lein)にlein.batを移動して、
コマンドプロンプトから
lein self-install
を実行。
あとはインストール処理が自動で行われますので、終了するまで待ちます。
終了したら
lein -version
と打ち込み、2014/09/07時点では
Leiningen 2.4.3
と表示されればインストール作業終了です。
Gorillaを起動してみる
作業フォルダにて、コマンドプロンプトに
lein new <プロジェクト名>
と打ち込むと新規プロジェクトを作成出来ます。
新規プロジェクトには最低限必要なファイルや設定が自動で生成されています。
ここでは
lein new gorilla
と打ち込んでみましょう。
するとgorillaというフォルダ名の直下に
project.cljというファイルが生成されています。
これはこのプロジェクトの設定ファイルです。
設定を変更する場合はこのファイルを編集します。
ここでは下記のように編集して下さい。
(defproject gorilla-test "0.1.0-SNAPSHOT" :description "A test project for Gorilla REPL." :dependencies [[org.clojure/clojure "1.6.0"] [incanter "1.5.5"] [incanter-gorilla "0.1.0"] [cc.artifice/clj-ml "0.6.0-SNAPSHOT"] [org.atilika.kuromoji/kuromoji "0.7.7"]] :main ^:skip-aot gorilla-test.core :target-path "target/%s" :repositories [["Atilika Open Source repository" "http://www.atilika.org/nexus/content/repositories/atilika"]] :plugins [[lein-gorilla "0.3.3"]] :profiles {:uberjar {:aot :all}})
ここで重要なのはdependenciesとpluginsです。
dependenciesに利用するライブラリを記載します。
ここに記載されたライブラリをleiningenが
依存関係などを自動解決してインストールしてくれます。
ここではincanterとincanter-gorilla、clj-ml、そしてkuromoji*1
という形態素解析器を指定しています。
形態素解析器についてはこちらをご覧ください。
pluginsにlein-gorillaを記載し、gorillaを使えるようにしています。*2
これらが全部終わったら
lein gorilla
とコマンドプロンプトに打ち込むと
Running at http://localhost:xxxxx/worksheet.html
とURLが表示されるので、そのアドレスをブラウザで見ると
Clojure実行環境が表示されます。

なお、アドレスのxxxxxの部分はランダムで決まりますが、ipやportを指定したい場合は
lein gorilla :ip xxxxx :port yyyyy
とします。詳細なオプションは下記に記載されています。
http://gorilla-repl.org/configuration.html
動作するか確かめてみましょう。

表示された画面の灰色のブロックに
(+ 1 2 3)
と打ち込み、ShiftとEnterキーを同時押しすると処理が実行されます。
実行結果が6になったら成功!
これにてGorillaの設定作業は終わりです!お疲れ様でした!
Gorillaで統計量を求めてみる
(use '(incanter core stats charts datasets)) ;統計解析ライブラリIncanterを使う指定 (def iris (get-dataset :iris)) ;サンプルデータセットの中からirisを選択し、その内容をirisに束縛 iris ;irisの中身を見てみる。 ; :column-names から[:Sepal.Length :Sepal.Width :Petal.Length :Petal.Width :Species] ; という列があることが分かる。 ; irisとはあやめのデータで、sepalはがく、petalは花びら、speciesはあやめの種類である (mean ($ :Petal.Length iris)) ;花びらの長さの平均値を求める (sd ($ :Petal.Length iris)) ;花びらの長さの標準偏差を求める

Gorilla上で可視化してみる
(use '(incanter core stats charts datasets)) (use 'incanter-gorilla.render) ;Incanterで可視化したモノをGorilla上で表示するライブラリを指定 (def iris (get-dataset :iris)) (chart-view (histogram :Petal.Width :data iris)) ;chart-view関数でヒストグラムをGorilla上に表示 (view (histogram :Petal.Width :data iris)) ;view関数でヒストグラムをGUIで表示

こちらに掲載した可視化のviewの部分を
chart-viewに置き換えれば、全てブラウザ上で実行可能です
Gorilla上で機械学習してみる
(use 'clj-ml.classifiers 'clj-ml.utils 'clj-ml.io 'clj-ml.data) ; weka特有のデータ形式arffを利用します。 (def iris (-> (load-instances :arff "http://repository.seasr.org/Datasets/UCI/arff/iris.arff") (dataset-set-class :class))) ; サーバ上にあるCSV形式を読み込む場合は次のようにします。 ; (def iris (-> (load-instances :csv "iris.csv") (dataset-set-class :class))) ; 決定木(c4.5)を作ってみる (def decision-tree (-> (make-classifier :decision-tree :c45) (classifier-train iris))) ; 作成した決定木の中身を見てみます decision-tree ;クロスバリデーションに掛けてみます (def decision-tree-evaluation (classifier-evaluate decision-tree :cross-validation iris 10)) ;クロスバリデーションの結果を確認します (println (:summary decision-tree-evaluation)) (println (:confusion-matrix decision-tree-evaluation))


こちらに掲載した機械学習手法も
全てブラウザ上で実行可能です。RandomForestやK-meansなどもお試しください。
但し、ファイルを読み込む際、サーバ上でGorillaを立ち上げている場合はファイルパスにご注意ください。
ローカルファイル指定は自分の端末ではなくサーバ上で探索します。
Gorilla上で形態素解析してみる
(import [org.atilika.kuromoji Token Tokenizer]) (def tokenizer (.build (Tokenizer/builder))) (defn ma [sentence] (doseq [^Token token (.tokenize tokenizer sentence)] (println (str (.getSurfaceForm token) "\t" (.getAllFeatures token))))) (ma "あきつ丸改二で大発動艇欲しい") ; (ma "あきつ丸改二で大発動艇欲しい") ; の形態素解析結果が望みの結果になってないので ; 形態素解析用の辞書を整備する必要がある ; 以下の内容を記載したファイルを用意し、適当なファイル名を付ける ; ここでは一旦userdict.txtと名付ける。 ; -- ファイル内容ここから -- ; 大発動艇,大発動艇,ダイハツドウテイ,固有名詞 ; あきつ丸改二,あきつ丸 改二,アキツマル カイニ,カスタム名詞 ; -- ファイル内容ここまで -- ; 1列目:表層, ; 2列目:1列目をどう区切るか。区切りたい位置に半角スペースを入れる ; 3列目:読み(カタカナ) ; 4列目:品詞情報 ; ※windowsの場合はファイルのエンコードをSJISにする必要有り ; ファイルをc:\\lein\\gorilla\\userdict.txtに置いたとした場合、 ; 以下のようにすると望みの結果が得られる。 (def dic "c:\\lein\\gorilla\\userdict.txt") (def tokenizer (.build (.userDictionary (Tokenizer/builder) dic))) (defn ma [sentence] (doseq [^Token token (.tokenize tokenizer sentence)] (println (str (.getSurfaceForm token) "\t" (.getAllFeatures token))))) (ma "あきつ丸改二で大発動艇欲しい")


形態素解析の結果を用いてワードカウントなどをする場合は
こちらをご覧ください。
ここで紹介している内容もブラウザ上で実行可能です。
終わりに
ブラウザ上で様々な分析が出来る便利な時代になってきました。
外出先の貧弱な端末や回線からでも、インタラクティブなデータ分析が可能になり、
例えばMTGの最中に「あのデータってどうなってるんだっけ?」
って話題に上がったその場ですぐ集計・可視化して確認出来るなど夢が広がりまくりです。
実務上データ分析しててかなり面倒だと思うのがデータサイズです。
数十GB単位になるとファイルの移動だけで面倒なことになりますし、
ローカルに持ってくるとそれだけでSSD空き容量死ぬケースがあります。
あと分析ツールの更新管理は多分皆さんが想像するのの27倍ダルいです。
一つのユースケースとして想像して欲しいのですが、
分析チームに30人くらい人がいて、
そのうちの半分がコンサルやマーケター的な方、
残り半分がエンジニアだったとしましょう。
大体前半の方々は「更新したのでアップデートしてくださーい」
とアナウンスしても、面倒なので無視します。
だって現状で使えてるもん。慣れてるのが一番だよね!
大体後半の方々は「更新したのでアップデートしてくださーい」
とアナウンスすると、既に自力拡張してるので、
それが動かなくなると嫌だから無視します。
こういう時システム提供側が取り得る選択肢は
更新しない同僚をハンマーで一人ずつ殴って回るか
あるいはブラウザで実行できるようにするかです。
非常に僅差ですが二個めの方がまだ若干マシかなと思わなくもないです。
ブラウザ上での分析環境構築は、
同時接続数とか認証周りとかにまだまだ機能不足や問題があります。
これはClojure/Gorillaだけではなく、
RのShinyやPythonのIPython notebookでも同じ問題は生じています。*3
とはいえ、ブラウザ上で分析可能なメリットは結構大きい上に、
Clojure/GorillaはJVM環境で簡単に構築できるツールなので
是非チャレンジしていきたい+皆さんもチャレンジして欲しいなと思います。

それでは皆様、よきGorillaライフを!!
素敵なLispエイリアンイラストをマルタコEX@marutakoEX様からお借りしました!
マルタコEX様、利用のご快諾、誠に有難うございます!!!
*1:ちなみにここで指定しているのは最新版ではありません。これは単に私が今まで利用してて動作が理解出来てるというので、今回も惰性で指定しているだけです。Lucene Kuromojiの最新版を使う方が良いです
*2:私は「何でこれがdependenciesで何でこれがpluginsなの?」っていうのがいまいちわかってないんですが、その切り分けをご存知の方いらっしゃったら教えてください…。まぁとりあえず公式サイトの指示に従って設定すれば動きます
*3:IPython notebookは一応パスワード設定出来ますが、ここでいう認証は「Aさんはこのデータとこのデータにアクセス出来て、Bさんはさらにあのデータにもアクセス出来て、あ、Cさんは異動したからこのデータへのアクセス禁止して」とかやりたいって意味です。まぁ他で認証機能別途付けろと言われたらそりゃそうなんですが
自然言語処理の最新手法"word2vec"で艦これ加賀さんから乳を引いてみる
概要
この記事は自然言語処理という分野の最新手法word2vec
を利用して誰でも遊べるようにするための手順を説明するものです。
word2vecを利用すると意味の計算が実現できます。
例えば"king"から"man"を引いて"woman"を足すと"queen"が出てきたり、
"東京"から"日本"を引いて"フランス"を足すと"パリ"が出てくるという面白い手法です。
自然言語処理とは人間が日常的に用いる自然言語をコンピュータに処理させ、
翻訳や要約、文字入力支援や質問応答システムを作るなどに活用されている分野です。
自然言語処理と言うと耳慣れない言葉かもしれませんが、
実は検索や推薦などで私たちが日常的に利用しているなじみ深い技術でもあります。
自然言語処理の適用範囲や要素技術は幅広いのですが、
その中でもword2vecの特色は、
冒頭でも挙げたように「意味の計算」が出来ることです。
これは今までの技術では実現し得なかったことで、
今後研究が進むにつれどんどん応用が広がっていくことでしょう。
word2vecは2013年ごろ主にMikolov達が発表したもので、
自然言語処理研究者の中でも伸びしろの大きさから
今最も注目されている新技術だと思われます(筆者主観)。
本記事ではword2vecを利用することを主眼としており、
理論的な説明は下記を参照してください。
Statistical Semantics入門の発表をしました
さて、それでは今からword2vecで遊んでみる流れを紹介します。
遊ぶ前に、何を目的としてどのようなデータを利用するかなど
問題設定をして作業の流れを把握しましょう。
■理由、応用先
好みの萌えキャラに対して好みの属性を足したり引いたりすることによって
更に萌えるキャラを推薦することが可能になるため。
従来の推薦技術では同じようなジャンルのものばかりを薦めがちであったり、
どのような推薦をして欲しいかをユーザが自由に決定できないという欠点があります。
そこでword2vecを用いることの利点として、従来の推薦技術では成し得ない
「推薦の方向」を操作可能なことが特徴であることが挙げられます。
これによってより良い萌えキャラでブヒれるようになり、
各提督が艦娘の新たな魅力に気づくことによって鎮守府に愛と平和がもたらされる。
なお、数いる艦娘の中から特に加賀さんを選んだ理由は、
艦これの乳と言えば加賀さんであるという
全宇宙の総意に基づいた必然によるものである。
反論は一切認めない、全面的に拒否する。
■インストールするもの
python2.X(3系だと下記ライブラリが動かないかも…)
mecab(辞書はshift-jis,つまりデフォルト設定で)
gensim(入れる前にscipyなども入れろと言われるかもしれません)
■作業の流れ
- テキストデータを2ちゃんねるから取得する
- 取得したテキストをword2vecで処理できるよう加工する
- word2vecでデータを利用してモデルを作成する
- word2vecのモデルを利用して加賀さんから乳を引く
1. テキストデータを2ちゃんねるから取得する
word2vecはデータから学習して意味の計算が可能なモデルを作り出します。
そのためデータを用意する必要があります。
良いデータが無いと良い学習が出来ず、意味の計算結果も直感に沿わないものとなってしまいます。
良いデータとは、出来る限り大量かつゴミを取り除いたものです。
2ちゃんねるのデータのため、大量のタグやAAなどが入っています。
それを本来なら様々な手法とかなりの手作業でクリーニングしていくのですが、
本記事は初心者向けのため最低限の処理を施したものを用意しておいたので、
それを利用して下さい。
DLしてkankore.pyと名前を付けて保存します。
使い方は
python kankore.py url 板フォルダ キーワード(スレタイに指定したキーワード含まれてるスレを取得)
って感じです。
具体的には以下のようにします。
python kankore.py http://uni.2ch.net/ gameswf/ 艦これ > kankore.txt
これで2ちゃんねるのデータを取得してkankore.txtというファイルに保存できます。
2. 取得したテキストをword2vecで処理できるよう加工する
次に、取得したデータを分かち書きします。
英語は各単語の切れ目にスペースを入れますが、
日本語は単語の切れ目を明示的に与えない表記をします。
そのため、どこからどこまでが単語なのかを明示的に与える必要があります。
各単語を空白などで切り分けた表記を分かち書きと言います。
例として、「李も桃も桃のうち」を平仮名にした文を分かち書きすると
すもももももももものうち→すもも も もも も もも の うち
という感じになります。
これも本来ならこの分かち書きするのは結構難しくて色々煩雑な作業があるのですが、
今回は私の方で用意した艦これ辞書を使います。
分かち書きにはmecabを利用します。
これは自然言語処理界隈でデファクトスタンダードなツールです。
インストール作業は簡単なのでこちらなどを見て下さい。
http://handsrecs2nd.seesaa.net/article/140090025.html
mecabをインストール出来たら、用意しておいた艦これ辞書を使います。
これをダウンロードして、mecabフォルダの下にあるdicフォルダに置きます。
mecabフォルダの下にetcフォルダがあるのでそこのmecabrcファイルを開いて
; userdic = /home/foo/bar/user.dic
と書かれた行を
userdic = "C:\(mecabインストールしたフォルダ)\dic\kankore.dic"
と書き換えます。
これで辞書の準備はOKです。
「あきつ丸改二で大発動艇もっと欲しい」という一文をmecabに食わせる場合、
辞書追加前は
>あ フィラー,*,*,*,*,*,あ,ア,ア
>きつ 形容詞,自立,*,*,形容詞・アウオ段,ガル接続,きつい,キツ,キツ
>丸 名詞,固有名詞,人名,姓,*,*,丸,マル,マル
>改 名詞,一般,*,*,*,*,改,アラタメ,アラタメ
>二 名詞,数,*,*,*,*,二,ニ,ニ
>で 助詞,格助詞,一般,*,*,*,で,デ,デ
>大 接頭詞,名詞接続,*,*,*,*,大,ダイ,ダイ
>発動 名詞,サ変接続,*,*,*,*,発動,ハツドウ,ハツドー
>艇 名詞,接尾,一般,*,*,*,艇,テイ,テイ
>もっと 副詞,一般,*,*,*,*,もっと,モット,モット
>欲しい 形容詞,自立,*,*,形容詞・イ段,基本形,欲しい,ホシイ,ホシイ
となりますが、この辞書を追加すると
>あきつ丸 名詞,一般,*,*,*,*,あきつ丸,アキツマル,アキツマル
>改二 名詞,一般,*,*,*,*,改二,,
>で 助詞,格助詞,一般,*,*,*,で,デ,デ
>大発動艇 名詞,一般,*,*,*,*,大発動艇,,
>もっと 副詞,一般,*,*,*,*,もっと,モット,モット
>欲しい 形容詞,自立,*,*,形容詞・イ段,基本形,欲しい,ホシイ,ホシイ
という感じになります。
mecabの準備が整ったらコマンドプロンプトで
mecab -Owakati kankore.txt > wakati_kankore.txt
とやると分かち書きされたファイルが生成されます。
これでようやくword2vecに流し込めます、お疲れ様でした、
真の地獄はこれからだ。
3. word2vecでデータを利用してモデルを作成する
これからgensimを利用してword2vecを実践してみます。
word2vecはgoogle版の実装があってこれ使った方が1.5倍くらい速いのですが、
*2
とりあえず動かす分にはpythonから扱える方が簡単かと思いますので
ここではgensimというライブラリを利用します。
*3
pip install gensim
でgensimをインストールすることが出来ます。
次のコードをコピペして実行して下さい。
from gensim.models import word2vec import logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) # 今どれくらい処理が進んでるか確認する用 sentences = word2vec.Text8Corpus('wakati_kankore.txt') model = word2vec.Word2Vec(sentences, size=200) #MBAだと100MBで2分弱くらいかかりました def s(posi, nega=[], n=5): cnt = 1 result = model.most_similar(positive = posi, negative = nega, topn = n) print '順位', 'キャラ名', '類似度' for r in result: print cnt, r[0], r[1] cnt += 1
これで準備完了!gensimを使うととっても簡単ですね!
関数sの引数は
s([足したい属性], [引きたい属性(省略可。省略時引きたい属性無し)], 上位何個出力するか)
という感じで設定して下さい。
これで意味を計算して類似度が高い順に出力してくれます。
4. word2vecのモデルを利用して加賀さんから乳を引く
早速加賀さん…の前にちょっと霧島で試してみましょう。
霧島は戦艦のメガネ担当です。
艦これはメガネ担当が少なく、
特に霧島は主力を張れる実力かつ委員長タイプという
特異性を持った愛すべき艦娘です。
霧島と類似度が高いキャラ上位5名を上げていきましょう
>>> s(['霧島'])
>順位 キャラ名 類似度
>1 榛名 0.845242
>2 比叡 0.807935
>3 隼鷹 0.696393
>4 鳥海 0.676646
>5 ヒエー 0.668688
1位榛名、2位比叡は姉妹艦+同じ戦艦という艦種でもあり順当。
3位の隼鷹はどういう繋がりかよくわからないですね。
4位の鳥海はメガネ繋がりではないでしょうか。
4位に鳥海が出てきたので、霧島にメガネを足して
戦艦を引いてみるとどうなるか確認します。
>>> s(['霧島','メガネ'],['戦艦'])
>順位 キャラ名 類似度
>1 鳥海 0.507391
>2 深雪 0.49015
>3 比叡 0.482907
>4 文月 0.478213
>5 榛名 0.465617
見事鳥海が1位になりました。
そして先ほどは1位で類似度も非常に高かった榛名が
5位ギリギリまで落ち込んでいるのが面白いですね。
さらに鳥海の艦種である重巡を足してみると…。
>>> s(['霧島','重巡','メガネ'],['戦艦'])
>順位 キャラ名 類似度
>1 鳥海 0.666885
>2 由良 0.635467
>3 高雄 0.62329
>4 利根 0.615573
>5 古鷹 0.61162
鳥海1位は変わらないのですが、類似度がぐっと上がりました。
由良が上がってきてるのはなぜでしょうかね?
そして榛名は完全に落ちました。
色々適当に投げて遊んでみましょう
>>>> s(['おにおこ'])
>順位 キャラ名 類似度
>1 鬼怒 0.635817
>2 荒潮 0.61133
>3 黒潮 0.604449
>4 ひえー 0.603935
>5 カカオ 0.601855
鬼怒(きぬ)はその字面から「おにおこ」
と呼ばれることが多いので狙い通り。
>>>> s(['高雄']) #艦これおっぱい担当。心の底から愛すべきキャラ
>順位 キャラ名 類似度
>1 利根 0.76036
>2 鳥海 0.736499
>3 古鷹 0.729154
>4 愛宕 0.725223
>5 那智 0.718696
>>>> s(['長良']) #艦これスポーツ女子担当。全てを捧げて愛すべきキャラ
>順位 キャラ名 類似度
>1 球磨 0.838549
>2 多摩 0.792809
>3 名取 0.78698
>4 由良 0.742747
>5 羽黒 0.723103
>>>> s(['不知火']) #艦これツン担当。言うまでも無く最も愛すべきキャラ
>順位 キャラ名 類似度
>1 霰 0.791995
>2 黒潮 0.758737
>3 満潮 0.750464
>4 霞 0.748772
>5 荒潮 0.743553
単に2ちゃんねるのテキストを食わせただけで、
艦種メタデータを与えたわけではないのですが、
大体艦種が揃ってますね*4。
ん?なんでこの艦娘を選んだかって?
そうですね、愛かな。
>>>> s(['不幸','姉妹'])
>順位 キャラ名 類似度
>1 鶴 0.691494
>2 扶桑 0.68556
>3 金剛 0.645539
>4 伊勢 0.623382
>5 妹 0.618033
史実、作中台詞、二次創作で何かと不幸に遭遇することから
不幸姉妹と呼ばれる瑞/翔鶴、扶桑。
金剛とか伊勢は不幸と言うか姉妹が強く利いてきたのかなって感じですね。
>>>> s(['提督'])
>順位 キャラ名 類似度
>1 諸氏 0.472251
>2 紳士 0.451628
>3 変態 0.438056
>4 ブルネイ 0.430615
>5 新米 0.428584
提督同士で呼びかける時よく「提督諸氏」って使うからっぽいですね。
あと3位に変態が来てるんですが皆さんの言動を見るに
これが1位であるべきだと思いましたまる。
もっと良いモデルを作ればきっと一位になるでしょう。
さぁお待ちかね、加賀さんです。
>>>> s(['加賀'])
>順位 キャラ名 類似度
>1 赤城 0.813888
>2 飛龍 0.718207
>3 蒼龍 0.712446
>4 鳳翔 0.675858
>5 祥鳳 0.668783
空母が並んでてまぁ順当な感じです。
ちなみに1位の赤城が0.8超えてて、さらに2位に0.1近い大差つけてるの、
あんまり他で見たこと無いのでよほど頻出な組合せのようですね。
古来よりの言い伝えとして
「ケツの翔鶴、乳の加賀」
という名言があるのは皆さんもご存知の事と思われます。
ではその加賀さんから乳を引いてみたらどうなるでしょうか。
>>>> s(['加賀'],['乳'])
>順位 キャラ名 類似度
>1 赤城 0.492795
>2 飛龍 0.444622
>3 蒼龍 0.436392
>4 瑞鶴 0.411837
>5 Ju 0.408483
結果は、1~3位までは変わらず、4位にこれまでランク外だった瑞鶴が入りました。
1~3位までは加賀さんという属性に寄せられたもので、
そこから乳を引いた結果として寄せられた瑞鶴が
この場合加賀さんの神々しい乳の要素を取り除いた存在と解釈出来ます。
具体的に加賀さん

と瑞鶴

を見比べてみましょう。
この堅い胸当ての上からでもわかる加賀さん圧倒的な質量を誇る胸、
それに対比すると…いや、その、努力は感じる瑞鶴の胸。はい!
終わりに
なんかおもしろそー!と思って頂ければ幸いです。
word2vecはこれまで出来なかったことを可能にする技術です。
それゆえ今後どのように活用出来るだろうかワクワクがドキドキです。
ただ、まだ出たばかりの技術であるため、知見が殆ど溜まっていません。
あんまりにもデータ依存過ぎる(ちょっと前処理変えるだけで全然結果変わる)し、
結果が信用できるかと言われるとかなり微妙です。
チューニングの仕方も分からず手探りで、
ひたすらモデル作りまくってテストした結果を
目で見比べて良さそうな雰囲気のモデルを選択するという感じで動かしています。
非常にbadです。
だからこそチャンスで、今この技術を使って面白いことしたらワンチャンあるでよ。
ということで、みんなでword2vec触りまくって
「こんなこと出来るよ!」
「こんな使い方どうだろう?」
ってのをやっていければいいなーと思います。
参考文献
http://blog.unnono.net/2014/02/statistical-semantics.html
http://blog.unnono.net/2014/01/nips2013word2vec.html
http://www.slideshare.net/uchumik/rnnln
http://aclweb.org/anthology/N/N13/N13-1090.pdf *5
謝辞
ニコニコ学会で発表させて頂きました。
その場で色々コメント頂けて良かったです。
ベストLT有難う御座いました。
追記

「将来加賀さんに成長する可能性のある駆逐艦を抽出する」
というタスク、面白みあるので誰かやりましょう!!
進撃の巨人を読んだことない人がデータだけでキャラを推測してみる
はじめに
最近超人気の漫画として私のTwitter TLを賑わす作品、その名も「進撃の巨人」。
これだけ人気なんだからきっと面白いに違いないのですが、
なんか絵が怖そうだし、人がバンバン死んでてグロいっぽいという噂を聞くので、
なんとか漫画を読まずに、それでいて進撃の巨人のキャラについては知りたい、
そう願う潜在的進撃の巨人ファンも全国に70万人くらいいらっしゃると思います。
そこで、データから進撃の巨人にどんなキャラが登場するか推測してみましょう。
扱うデータとして、pixivのタグ情報を利用します。
商品レビューコメントなどとは違い、ファンの創作活動がダイレクトに反映されるサービスなので、
そこに付与されるタグ情報は、ファンの熱(過ぎる)いメッセージが込められているに違いありません。
今回、以下のような縛りを入れています。
1.勿論原作は見ない
2.pixivのタグ情報は参照するけど、絵は見ない
逆に、以下のような情報は取り入れています(取り入れているというか、意図せず入っちゃった感じです)
1.原作1話だけ無料で読めたので昔読んだ記憶がある(エレンが主人公、巨人が壁の外にいて人類と戦争してるというのは知っている)
2.twitterなどで進撃の巨人についてツイートされた場合は普通に見ていた
(進撃の巨人で検索した結果を眺めたりはしないが、普段フォローしてる人がツイートした場合、それをわざわざミュートしたりはしない。めんどいので…)
3.分析結果をツイートした結果、フォロワーさんから得られたフィードバック
(この記事のようにまとめる意図が最初は無かったので、FBはミュートすることなく見ていました。
ので、分析途中で何件かは答えを知ってしまうことがありました)
この条件の下、
1.主要キャラクタ名
2.主要キャラクタの特徴(特に性別)
3.各キャラクタの関係(カップリング。受け攻め)
を抽出してみたいと思います。
ちなみに、私はアニメ・マンガ・ゲームを殆ど見ない人であり(20歳超えてから見たアニメはまどかマギカくらい)、
また、BLとか同人界とか全く知らないど素人です。コミケという存在は知っているけど行ったことはありません。
そういう門外漢がデータだけでどこまで行けるか、どのような失敗するかを試してみたいと思います。
pixivからタグ情報を取得する準備
まずは進撃の巨人タグを次のようなプログラムで取得してみましょう。
今回データを取得したのは7/28の20時くらいです。
# -*- coding: utf-8 -*- import urllib import sys import time def main(): keyword = sys.argv[1] file_name = sys.argv[2] page_max = int(sys.argv[3]) if len(sys.argv) == 5: phpsessid = sys.argv[4] else: phpsessid = '' with open(file_name, 'w') as wfp: for p in range(page_max): a = urllib.urlopen("http://spapi.pixiv.net/iphone/search.php?s_mode=s_tag&p=" + str(p) + "&word=" + keyword + "&PHPSESSID=" + phpsessid) time.sleep(1) for line in a: line = line.split(',') if keyword in line[13]: wfp.write(line[12].replace('\"', '') +"\t"+ line[13].replace('\"', '') + "\n") if __name__=="__main__": if len(sys.argv) < 4: print "error occurred. please, 3 or more arguments. arguments is 1.tag-keyword 2.output-file-name 3. get-page-max 4.phpsessid" sys.exit() main()
ついでに単語頻度をカウントするプログラムも用意しましょう
#coding:utf-8 import sys import collections def main(): read_file = sys.argv[1] cnt = collections.Counter() with open(read_file) as fp: for line in fp: tag = line.split('\t')[1] for word in tag.split(): cnt[word] += 1 print "count,word" for word, count in sorted(cnt.items(), key = lambda x:x[1], reverse = True): #登録された単語を頻度降順で表示 print str(count) + "," + word #print word + "\t" + str(count) if __name__=="__main__": if len(sys.argv) < 2: print "error occurred. please, 2 or more arguments" sys.exit() main()
これで準備OKです。
タグ頻度のランキングを作る
取得したタグを名寄せ*1してから頻度カウントしてトップ30を並べた結果が次です。
(26060は全タグのべ数です)

この内容からさらにキャラクタ名を抽出していきます。
それには人物に紐付きそうなタグと共起*2しているタグを取得することによって実現したいと思います。
キャラクタ名を抽出してタグ頻度順にトップ15*3を出した結果が次です。

大抵は「エレン・イェーガー」のようにフルネームと紐付けられたので、
「『●●・△△』って表記ならまず人物名であろう」という妥当と思われる仮定に沿っているので問題無いと思います。
一点、「ユミル」がフルネームを抽出できなかったのでちょっと心配です。
とはいえ、後続の分析でも人物名っぽいという結果になったので、ここではユミルという登場人物がいるものとして進めます。
頻出カップリングトップ10
次に頻出カップリング抽出を行います。
実はカップリング抽出をやろうとは思ってなかったというか、「そもそもカップリングって何?」って状態だったのですが、
「エレン リヴァイ リヴァエレ」みたいなタグが頻出してて、
「はて、これは一体?」と悩んでいたところ、腐婦女子の方から
「それはカップリングであり、左側が攻め、右側が受けであり、その例で言うとリヴァイ攻めエレン受けだ」
と教えて頂きました。
ちなみにあとで出てきますが、エレンもリヴァイも男性同士なので、そのカップリングとは一体…。
という感じですが、まぁその疑問は一旦脇に置き、人気カップリングを抽出してみましょう。結果は以下の通り。

リヴァイを含むカップリングの合計が全体の6割以上を叩きだしています。
リヴァイ兵長、相当の人気者のようですね。色んな意味で。
各キャラクタの特徴を掴む(性別判定)
次に各主要キャラクタの特徴を掴んでみましょう。
対象キャラは先ほどの頻出トップ10のキャラクタです。
特徴と言っても色々あるので、ここではシンプルに各キャラの性別を判定しましょう。
このキャラクタの名前ごと(フルネームではなく、「エレン・イェーガー」なら「エレン」というようにもっとも書かれやすい名前に置き換えて検索しています)
にタグを収集した結果からトップ30を切り出したのが次です。
(キャラ名の右の数値は、そのキャラと共起したタグの総種類数です)


大抵こういうデータの取り方をすると、対象名に紐付く特徴が共起するのですが、
pixivタグの場合は一枚の絵に描かれたキャラクタ名やカップリングが主にタグ付けされるため、
対象名の特徴というよりも「このキャラはあのキャラと一緒に描かれている」というのが上位にくるようですね。
正直全てのキャラの特徴がすっかり把握できるとは言えない状況です。
もっと下位(上記表に入っていない30位未満)の方で特徴語といえるものがいくつか付与されている場合もあるので、そこから出来る限り推測していきましょう。
リヴァイ
頻出タグ:[兵長、人類最強、最強サンド、最強彼氏、調査兵団幹部チーム]
とあることから、恐らく作中最強ランクのキャラなのでしょう。
そして兵長や幹部ということから、
エレンの上司か師匠か、あるいは目標とする人にあたる人物でしょうか?
男性に紐付く形容詞が多いのでまず男性でしょう。
エレン
頻出タグ:[幼馴染3人組、ショタ、女体化]
エレンは幼馴染が二人いるようですね。
そしてショタ+女体化というのがあるので、結構中性的な外見をしている男性なんでしょうか
(エレンの外見は一度見た気もするけど完全に忘れている)。
ミカサ
頻出タグ:[幼馴染3人組、進撃の百合、女の子、最強サンド、女子力(物理)、腹筋系アイドル、おっぱいのついたイケメン]
エレンの幼馴染の一人で、女の子とか百合という語が見られることから、まず女性キャラで間違いないようですね。
面白かったのが、女性キャラに紐付きそうな「可愛い」とか「綺麗」とかそういうタグはほぼなく、
むしろ筋肉系の評価がなされているので、多分女性だけどムキムキキャラなのでしょう。
アルミン
頻出タグ:[幼馴染3人組、性別:アルミン、女装、女体化、男の娘]
というわけで、「エレン、ミカサ、アルミン」の3人は幼馴染のようですね。
で、どうも性別は男性のようなのですが、女体化タグが多く、エレン以上に女性的な外見をしているのではないかと。
(あれ、女性系キャラが男性的な形容詞多くて、男性系キャラに女性的な形容詞付与されてるのが多い気がするぞ…)
という感じでタグを見て性別を判定していきます。
女性キャラは「百合」というタグが付けられていることが多いようなので、
百合タグの数でだいたい性別を当てられそうです。
…と思っていたら。

うーむ、腐女子界、奥が深い(腐った方々ありがとうございました)…。
データを時系列で眺めてみる
6/21〜7/28の各キャラの各日付ごとのタグ数です。

リヴァイ、エレンが圧倒的ですね…。
次に、もっと長期のタグ数推移を見ることによって、
作品の認知度のようなものがいつぐらいから向上してきたのかを見てみたいと思います。
「進撃の巨人」タグの時系列推移を見たかったのですが、
どうも1万件以上遡ってタグ取得するのは出来ない(?)っぽいので、
遡りやすい「アルミン」タグの2013/1/1〜2013/07/28までの時系列推移を見てみます。

4月半ばから急上昇していますね。
これだけ明確に変化があったということは、強い外部要因(作品内の展開が変わったとかではなく)
があったと考えるのが妥当でしょう。
漫画の認知度が急激に上がるショックというと、アニメ化などを考えるのが妥当でしょうか。
また、日付を細かく見てみると、どうも日曜日に投稿数が跳ね上がるようです。
私は「もしかしてアニメが日曜にやっているからなのかな?」と考えたのですが、どうやら次のような理由があるようです。


なるほど、当たり前ですが絵を描くのにしっかり時間取らないといけませんし、
一般の方でしっかり時間取れるのって大抵土日ですよね。
また、pixivのランキングの仕組みも初めて知りました。
SNSだとこういうランキングなども意識して投稿されるというのは考慮に入れないといけませんね。
ちょっぴり統計解析してみる
頻度だけで分かることはこれくらいかなと思われるので、そろそろ統計学の手法を用いたいと思います。
階層型クラスタリングという手法を用いることによって、
どのタグとどのタグがどれくらい近いクラスタにいるかを把握したいと思います。
なんとなく近い位置に存在するタグが近しい関係にあると解釈して下さい。

次に共起ネットワークというものを見てみましょう。関連深いタグ同士でグラフを形成したものです。
ちなみにフルネームを取得する時にも使いました。

所々文字化けしているのは中国語のようです。中国の方にも人気なのでしょうか。
最後に、「進撃の巨人」をキーワードとしてリアルタイムでタグを取得してタグクラウドを作成して見た結果です。
これだけリアルタイムでデータ取得したため、この画像を作成するのに使ったのは8/4の18時頃のデータです。


所々東方や銀魂が入っているのはこういうことらしいです。

データからは全く分からなかったこと
「エレン右」など、○○右とか左とかいうタグが頻出していました。
これが何を意味するのか全く分からなくて頭を抱えていたのですが、

ということらしいです。
!!!!!!訳分かんねーよ!!!!!!!
結果
1.主要キャラクタ名
- リヴァイ兵長
- エレン・イェーガー
- ミカサ・アッカーマン
- アルミン・アルレルト
- ハンジ・ゾエ
- ジャン・キルシュタイン
- クリスタ・レンズ
- ベルトルト・フーバー
- アニ・レオンハート
- エルヴィン・スミス
- ユミル
- サシャ・ブラウス
- ライナー・ブラウン
- マルコ・ボット
- ペトラ・ラル
2.主要キャラクタの特徴(特に性別)
男性:リヴァイ、エレン、アルミン、ジャン、ベルトルト、エルヴィン
女性:ミカサ、クリスタ、アニ
不明:ハンジ
3.各キャラクタの関係(カップリング)
- リヴァエレ
- リヴァハン
- ユミクリ
- エルリ
- エレリ
- ミカエレ
- ベルユミ
- エレミカ
- リヴァペト
- アルアニ
さて、性別は何人正解でしょうか(そもそもこれらは本当にキャラなのかという時点で不明なのですが)。
割と真面目にハンジの性別が不明のままとなりました。
ハンジは男性に付与される「女体化」タグも、女性に付与される「百合」タグも共存し、
また、女性キャラとも男性キャラとも共起しているようです。
心残りです…。
終わりに
このような分析をしてTwitterに投げたところ、沢山の方からのフィードバックを得られました。
諸々の助言ありがとうございました。
皆さんが進撃の巨人を大変愛していることが伝わってきたのと、答え合わせしたいというのもあって、
ようやくスプラッタな描写が待ち構えているのではという恐怖を乗り越えて、
進撃の巨人を読んでみようという気になりました。
Amazonで1〜10巻まとめてぽちっとな。
あと、なぜ今回進撃の巨人について分析することになったかというと*4、
諸悪の根源はぜろゆ氏であり、進撃の腐人で検索すると面白いからやれと唆されたからです。



終わってしまえばすべては良い思い出です。
いい機会なので進撃の巨人を読んでみたいと思います。
はー、やっと、ついに分析しきれなかったハンジの性別が明かされる…!!
追記
ふぉろわーさんからしんげきのきょじんのさくしゃさんのこうしきぶろぐじょーほーをおしえていただきました。
http://blog.livedoor.jp/isayamahazime/archives/4388712.html

うおおおおおおおいいいいい!!!!!作者ッ!!!!最初から無理ゲーだったんじゃねぇか!!!!!!!!
*1: 指している内容は同じだけれど表記が違うものを、一つの表記に揃える処理です。例えば、「エレン」、「エレン・イェーガー」、「イェーガー」というタグがあれば、それを各々別の語として扱うではなく、全て「エレン・イェーガー」として扱うというものです。本来名寄せは結構難しい処理ですが、pixivでは「エレン・イェーガー」のようにフルネームっぽいものが簡単に取れるので結構楽でした
*2: 同時に出現する、程度の意味だと考えて下さい
*3:※タグ数はあくまで人気度合いを測るパラメタであって、イコール人気度合いというわけではありません。ここがテキストマイニングで難しい所ですが、単純に「頻度が高い=人気ではない」ではないということは常に意識しなければなりません。頻度が高い→描かれた枚数が多い→描き手が多い傾向にある→人気とタグ頻度は何らかの相関があるのでは?くらいのことは仮定できるかもしれませんが、あくまで仮定ですし、また、相関があるのは恐らく妥当な推論でしょうが、相関の強さについては全く分からないということに注意が必要です
*4: 本当の話をすると、テキストマイニングの練習としてやっています。こういう遊びを時々やると、仕事で「今まで全く扱ってこなかった商品分野だけど、Webのテキスト情報から最近のトレンドを把握してこい」とか無茶振りされても期待に応えられるようになるかもしれないので、トレーニングとして行うと良いですね。解答なんて用意されていない仕事のデータとは違って、後で答え合わせも出来るので気が楽です
SPSSで簡単テキストマイニング
SPSSはPythonと連携することが出来ます。SPSSは大変多機能ですが、業務で実際扱うデータは一筋縄にはいきません。様々な前処理が必要です。SPSSに落とし込めるよう、データの整備やクリーニングをPythonで簡単にやってしまいましょう。今回はテキストをSVMにかけるための下準備をPythonで行います。形態素解析にはMeCab-野良ビルドを用います。まずは頻度カウントしてみます
#coding:utf-8 import sys import MeCab #MeCabを呼んで使えるようにする tagger = MeCab.Tagger("-Owakati") #分かち書きをする指定 read_file = sys.argv[1] #コマンドラインから読み込むデータファイルを指定する all_text = open(read_file).read() #指定したファイルを読み込む word_list = tagger.parse(all_text).split() #読み込んだファイルを分かち書きし、生成された配列をword_listに格納 dictionary ={} #空の辞書作成 for word in word_list: #dictionaryに単語が登録されていれば頻度を+1し、登録されていなければ辞書に単語を登録し、その頻度を1とする if word in dictionary: dictionary[word] = dictionary[word] + 1 else: dictionary[word] = 1 for word, count in sorted(dictionary.items(), key = lambda x:x[1], reverse = True): #dictionaryに登録された単語を頻度降順で表示 print word + "\t -> " + str(count)
これで頻度カウントが出来ました。このデータを用いてSPSSで単語のヒストグラムを描くなどしてみるといいでしょう(SPSSお持ちの方は)。
次はテキストをSVMにかけてみましょう。SVMにかけるためには、テキストをID化しなければなりません。SVMで処理できるデータは、クラスとIDとIDの値という形式です。例"+1 :: ID1:12, ID2:4, ID3:9 ID4:4"
テキストのID化は色々なやり方がありますので、その一例を示します。「犬を連れて散歩」というテキストが与えられ、ID群が犬=ID1、猫=ID2、散歩=ID3と割り振られていた場合(そして「連れて」という単語にID振られてなければ)、「犬を連れて散歩」→「ID1:1, ID2:0, ID3:1」となります。このようなデータ形式に落とし込めるようなPythonコードを書きましょう。
#coding:utf-8 import sys import MeCab tagger = MeCab.Tagger("-Owakati") read_file = sys.argv[1] read_dictionary = sys.argv[2] #ID群が振り当てられた単語辞書 text_list = open(read_file).read().split('\n') dictionary = open(read_dictionary).read().split('\n') print ',' + ','.join(dictionary) def set_id(text): count = 0 id = [] for word in dictionary: count += 1 id.append(str(text.count(word))) return text + ',' + ','.join(id) for text in text_list: print set_id(text)
これでテキストデータをSVMに放り込めるようID化出来ます。今のはIDが事前割り振られていたという前提でしたが、実際はID辞書も自作する必要があります。面倒くさいのでそれも自動化してしまいましょう(目的に合わせて手作業した方が精度良いですが)。サンプルデータを食わせ、指定した下限値より出現頻度高い単語だけを抽出します。
#coding:utf-8 import sys import MeCab tagger = MeCab.Tagger("-Owakati") read_file = sys.argv[1] all_text = open(read_file).read() word_list = tagger.parse(all_text).split() dictionary = {} for word in word_list: if word in dictionary: dictionary[word] = dictionary[word] + 1 else: dictionary[word] = 1 #ここまでは同じ min = sys.argv[2] #頻度下限 for word, count in dictionary.items(): if int(count) >= int(min): #設定した下限以上出現した単語だけを出力 print word #出力結果をリダイレクトで取得するなど
この結果吐いたファイルを先ほどのコードの第二引数に指定します。実行するとこんな感じになります。
#ID辞書
犯罪
金
セックス
援助
交際
死
殺す
ドラッグ
シンナー
麻薬
#ID化
原文 犯罪 金 セックス 援助 交際 死 殺す ドラッグ シンナー 麻薬 援助交際してくれる人募集中〜 0 0 0 1 1 0 0 0 0 0 風邪気味なので風邪薬買いに薬局へ行ってきた 0 0 0 0 0 0 0 0 0 0 渋谷にドラッグの密売人がいるらしい 0 0 0 0 0 0 0 1 0 0 ふざけたこと言ってると殺すぞ、絶対殺す 0 0 0 0 0 0 2 0 0 0 麻薬体験ブログ公開中! 0 0 0 0 0 0 0 0 0 1
というわけで、こうやってテキストID化するとSPSSで簡単にSVMとかに放り込めて楽しいですね(SPSSお持ちの方は)。是非やってみましょう。といってもテキストデータ十分にお持ちではないケースもあると思うので、twitterからツイートを取ってくるコードも掲載しておきます。
# -*- coding: utf-8 -*- #■これは何? #twitterからパブリックなツイートを取得するツールです。 #取得する内容はツイートした時間、ツイートしたIDと名前、ツイート内容です。 #自分のアカウントとパスを書いたsetting.txtを用意して下さい #ストリーミングフローとして閲覧するだけではなく、DBファイル(tweet.db)に格納します。 #tweet.dbはPupSQLiteなどで中身を見ることが出来ます。 #https://www.eonet.ne.jp/~pup/software.html import base64 import simplejson import urllib2 import datetime import sqlite3 import os # ツイッターアカウント設定読み取り with open("setting.txt") as f: userID = f.readline().replace('\r','').replace('\n','') userPassword = f.readline().replace('\r','').replace('\n','') commitDoNum = int(f.readline().replace('\r','').replace('\n','')) #日本語のツイートだけ収集するため、ツイートが日本語かどうかチェック def is_japanese(text): def check_chr(x): return ((x >= 0x3040 and x <= 0x309f) or (x >= 0x30a0 and x <= 0x30ff)) return [ch for ch in text if check_chr(ord(ch))] #SQLite3のDB用意。既にDBファイルがある場合はそれを利用、無い場合は新規で作成する。Python2.5以上はSQLiteが組み込まれているため、通常はインストール作業不要 if os.path.exists('tweet.db'): connection = sqlite3.connect('tweet.db') cursor = connection.cursor() else: connection = sqlite3.connect('tweet.db') cursor = connection.cursor() cursor.execute("create table twitter (tweetTime text, create_dt text, user_screen_id text, user_name text, tweet text);") # Streaming APIに接続 streamingAPIURI = 'https://stream.twitter.com/1/statuses/sample.json' req = urllib2.Request(streamingAPIURI, headers={'Authorization': 'Basic %s' % (base64.encodestring('%s:%s' % (userID, userPassword))[:-1])}) streamingData = urllib2.urlopen(req) commitCnt = 0 #「commitDoNum個ツイートをinsertしたらDBにCommitする」という用途に用意したカウンタ for line in streamingData: data = simplejson.loads(line) text = data.get('text') if text and is_japanese(text): tweetTime = datetime.datetime.today() create_dt = data.get('created_at') user_screen_id = data['user']['screen_name'] user_name = data['user']['name'] try: tpl = (str(tweetTime), create_dt, user_screen_id, user_name, text) cursor.execute("insert into twitter values(?,?,?,?,?)", tpl) print str(tweetTime) + ":"+ user_name + "\n" + text + "\n" commitCnt += 1 if commitCnt == commitDoNum: #commitDoNum個ツイートをinsertしたらDBにCommitする connection.commit() commitCnt = 0 except: print "*** insert miss... ***"
なぜ「主人がオオアリクイに殺されて1年が過ぎました」なのか?
件名: 主人がオオアリクイに殺されて1年が過ぎました。
差出人: 久光いきなりのメール失礼します。
久光さやか、29歳の未亡人です。
お互いのニーズに合致しそうだと思い、連絡してみました。
自分のことを少し語ります。
昨年の夏、わけあって主人を亡くしました。
自分は…主人のことを…死ぬまで何も理解していなかったのが
とても悔やまれます。
主人はシンガポールに頻繁に旅行に向っていたのですが、
それは遊びの為の旅行ではなかったのです。
収入を得るために、私に内緒であんな危険な出稼ぎをしていたなんて。
一年が経過して、ようやく主人の死から立ち直ってきました。
ですが、お恥ずかしい話ですが、毎日の孤独な夜に、
身体の火照りが止まらなくなる時間も増えてきました。
主人の残した財産は莫大な額です。
つまり、謝礼は幾らでも出きますので、
私の性欲を満たして欲しいのです。
お返事を頂けましたら、もっと詳しい話をしたいと
考えています。連絡、待っていますね。
爆笑ですね:D。業務中に一服の清涼剤としてこんな楽しいメールを送りつけてきやがったスパム業者には、心の底から感謝の意を表したいです。
でもどうしてスパム業者はこんな意味不明なメールを送ってきたのでしょうか?私たちを楽しませるため?まさかね:D。スパム業者は己の利益のためだけに行動してるはずです。だから、このおかしなタイトルには何か意味があるはずです。その意味を自然言語処理的見地から考えてみましょう。
自然言語処理とは、人間が日常的に使う言語をコンピュータに理解させて、翻訳や検索や要約、そしてスパムフィルタなどを機械で自動的にしてもらうようにするという情報科学の一分野です。自然言語処理という名前はあまり馴染みが無いと思いますが、GoogleやAmazonの検索欄に一つ二つ単語を入れるだけで適切なページや商品を紹介してくれるのは自然言語処理の技術を応用しているからであり、実は皆さんが日常的に利用している技術なのです。
GmailやHotmailなど、今様々なメールサービスがありますが、どれも共通して提供している重要な機能があります。それはスパムフィルタです。スパムフィルタの原理をごく簡単に説明すると、スパムメールに特徴的な単語(特徴語と言います)を抽出しておき、届いたメールがスパムメールの特徴語を持つなら、それをスパムと判定します。よくあるのが「セックス」「無料キャンペーン」などの単語を含むメールをスパムと判定する等です。
原理はとっても簡単ですね。しかし、よく考えてみると、難点が二つあります。1つ目は、どのような単語がスパムメールの特徴語になるのでしょうか?また、それがわかったとしても、スパムメールのバリエーションは多岐に渡り、その上毎日のように新たなバリエーションが追加されていくため、どうやって特徴語の辞書をメンテナンスしていけばいいのでしょうか?という問題。二つめは、単に特徴語を含むメールを単純にスパムと判定して良いのかという問題です。「セックス」という単語は日常的なメールでもしばしば使われます(あ、非モテの皆さんは使う機会無いんでしたっけ、大変失礼しました、心よりお詫び申し上げます:D)し、「無料キャンペーン」もAmazonや楽天などから届くお得情報のメールで使われます。と言うことは、単純に「セックス」「無料キャンペーン」のような単語を含むメールをスパムだと判定すれば良いわけでは無く、何らかのスコアを導入して、ある程度以上特徴語を含んでいればスパムだと判定する、という仕組みが必要なようですね。これは中々難しそう…。昔はどうやっていたのでしょうか?
昔は人手で実際のスパムメールと普通のメールを見比べて、「この単語を含んでいるとスパムっぽい」というのを人力で辞書に追加していきました。そして「セックス +0.3」「援助交際+0.7」「サッカー-0.1」などのように各単語毎にスコアを付けておきます。それをプログラムでメール文書と付き合わせて、文書の合計スコアが設定した閾値を超えたものをスパムと判定するという事をしていました。つまり、単語抽出とスコアリングを人手でやっていたわけです。非常に大変ですね!そこで登場するのが自然言語処理です。自然言語処理にも色々な手法がありますが、ここではよく使われてて実装も簡単なベイジアンフィルタを紹介します。ベイジアンフィルタとは、ナイーブベイズという手法を用いて文書を自動で分類する仕組みのフィルタです。このナイーブベイズを用いると、人間がやる作業は、普通のメールとスパムメールの実例をナイーブベイズに与えるのみになります。ナイーブベイズは与えられた例から自動で「どのような単語がどの程度含まれていればそのメールをスパムと判定するべきか」を自動で算出してくれます。単語抽出もスコアリングも自動でやってくれるなんて有難いですね。昔のようにユーザーが自力でちまちまスパムの特徴語をメーラーに設定する必要は無く、「これとこれはスパムメールだよ」と初期に与えてあげるだけで良いのです。それどころか、Webメールサービスですと、サービス提供者が大量のスパムメールの実例を抱えているため、ユーザーは何一つ作業することなく高精度でスパムフィルタリングされた結果だけを見ることが出来ます。便利な世の中ですね! ベイジアンフィルタの詳しい説明は,ここやここを御参照下さい。
なるほど、現在はスパムメールの特徴語を自動で学習してくれるそうです。ユーザーは放っておいてもスパムを見る必要が無くなるわけですね。…となると、困るのはスパム業者。どんなにメールを送ってもスパム判定されてしまい、カモには届きません。スパム業者は商売あがったりです。そこでどうするか?スパムの特徴語ではない単語を用いて文書を組み立て、なおかつ援助交際や詐欺サイトに誘導するような文面を考えれば良い、となるわけです。スパム業者は自分のアドレス宛に沢山のプロトタイプを送って「ふむふむ、『セックス』や『援助交際』を沢山含むメールはスパム判定される。一回二回使うだけなら問題なしか」などとスパムフィルタを素通りするような文書はどういうものかを見出します(ちなみにこのようないたちごっこを自動で学習する手法を敵対的学習と言います)。「あれ、先週は『主人が転勤で寂しいので浮気しましょう』という文面ならスパムフィルタに引っかからなかったのに、今は引っかかるぞ。試して見ると『主人が転勤』というのはスパムだと判定されやすいようだな。では文面を変えるか。『主人が病気で〜』にしよう」→翌週→「『主人が病気』もスパムフィルタに引っ掛かるようになってしまった。え〜と、じゃあ次は『主人がトラックにはねられケガをしてしまい〜』にして〜」→翌週→…………という繰り返しの果て、スパム業者も疲れてきました。「こうなったら、我々も自然言語処理的な技術を使おう!」まぁしかし、スパム業者とスパムフィルタを作る企業とでは資本も研究力も桁が違いすぎるので、そんな大したことが出来るわけではありません。苦肉の策として編み出したのが「ワードサラダ法」です。仕組みは簡単で、ある程度文書の定型を作っておき、そこにどのような単語を埋め込むかは辞書からランダムで持ってくるというものです。例として「私は人妻です。夫が○○に△△されたため、人恋しいのです」という定型を作っておき、どこかからか持ってきた適当な辞書の単語を○○や△△に埋め込みます。そうして産み出された文書は、人間が見ると一発でおかしいとわかるのですが、スパムフィルタは基本的に文の意味情報や妥当性までチェックする機能は無く、単語マッチで判定するため、スパムの特徴語を含んでさえいなければ、中々スパムだと判定出来ません。そのため、スパムメールによく見られる特徴語を外した新しいスパムメールは、フィルタを素通りしてしまう可能性があります。
ここまで説明したらおわかり頂けたでしょうか…。そう、常識で考えて「オオアリクイ」なんて単語がスパムメールに登場するとはさしものGoogleやYahoo!であっても思いつかず、また、他のスパム業者もまさかそんな単語を用いようとは夢にも思いつかず(多分ね)、オオアリクイがスパムだという実例が溜まりにくかったのです。結果、オオアリクイメールがスパムだと判定されることが遅れ、割と流行することになりました。
長々と説明してきましたが、要するに、「『主人がオオアリクイに殺された』という一文は、スパム業者のスパムフィルタとの闘いの結果産み出された努力の結晶」なのです。決してギャグでやってるわけでもなんでもなく、大まじめにこれでカモを釣ろうとしていたのです。いやはや、スパム業者の努力には頭が下がりますね、下がらねーよ糞が潰れろ。
追記 2014/07/29
主人がオオアリクイに殺されて1年が過ぎる実例です。ご査収ください。
オオアリクイの襲撃で死亡例、遭遇リスク増に懸念 ブラジル
Human Death Caused by a Giant Anteater (Myrmecophaga trydactila) in Brazil
