病みつきエンジニアブログ

機械学習、Python、Scala、JavaScript、などなど

ニューラル言語モデルは何を目的としているのか? 〜 「A Neural Probabilistic Language Model」を途中まで読んだ

word2vecでさんざん遊んだ皆さん、こんにちは。

今日は、word2vecの元になった論文の元になった論文の先行研究になっている論文「A Neural Probabilistic Language Model(Yoshua Bengio)」の紹介です。

word2vecは、単語の素性で足し算・引き算ができたり、単語の類推(アナロジー)ができたり、単語の素性の面白さが注目されています。とは言え、ニューラルネットによる言語モデルは、別に単語の素性で遊ぶために作られたわけではありません

ということで、ニューラルネットによる言語モデルの本家(?)である「確率的ニューラル言語モデル(Bengio先生)」の論文から、「そもそも何を目的にモデリングしているのか」「なぜニューラル言語モデルが必要なのか」というあたりを、紹介したいと思います(主にIntroductionの部分、ということになります。)。

誤訳はないように心がけているつもりですが、何かありましたらご指摘ください。また、「元論文-数式+例え」な感じで書いているので、元論文と合わせて読むと良いと思います。

何を目的にモデリングしているのか

ざっくり言うと「次に来る単語を予測すること」です。これはニューラルネットによる言語モデルに限った話ではありません。統計的言語モデル一般について言っていると思います。

もう少し厳密に言うと、「ある単語列が与えられたとき、次にどんな単語が来るか、の条件付き確率を学習すること」です。論文中でも「統計的言語モデルは、言語における単語の連なりの条件付き確率関数を学習することが目的だ」と書かれています。

数式で書くと、 p( word _ { t } | word _ { t-1 }, word _ { t-2 }, word _ { t-3 }, ..., word _ {t-n + 1})ということです(n:直前の単語何個を扱うか)。

これは、語順をBag-of-Wordsのようには捨てていないことも意味します。例えば「私、は、今日、料理、を、」という単語列の次に続く単語は、「した」とか「する」とか、「したい」とか、多分そういう単語です。間違っても「にんじん」とかじゃないですね。このような「単語列に対して、どういう単語が来そうか」という確率を求めるようなモデルを学習します*1

余談ですが、一方で、LDAとかは異なります。こちらは文書(≠文章)が持つトピック(話題など)の分布を推定したり、トピックごとの単語の出現確率を推定していますが、「Bag-of-Words」という形で文書を扱うので、語順を捨てています。ニューラルネットによる言語モデルとLDAは、「単語の素性を獲得できる」という一面は少しだけ似ていますが、それ以外は大きく異なります。

なぜニューラルネット言語モデルが必要なのか

先ほど述べた統計的言語モデルの文脈の中で、なぜニューラルネットによる言語モデルが必要とされたのか、という話です。

理由としては、ニューラル言語モデルよりも前の統計的言語モデルだと「次元の呪い」が起こり、学習困難だから、と書かれています。

まず、既存(当時)手法の問題点を説明します。例えば、「私、は、今日、料理、を、」の次に来る単語を予測したいとすると、「私、は、今日、料理、を、(何かの単語)」という連なりをたくさん収集して、(「私、は、今日、料理、を、(ある単語)」の発生数÷「私、は、今日、料理、を」の発生数)を計算しなければなりません。この例では5単語の連なりパターンが考えられます。語彙の数が仮に10万あったとすると、単語列のパターンは 100,000^{5} - 1 = 10^{25} - 1ぐらいあります。このような膨大なパターンの中から「私、は、今日、料理、を、(何かの単語)」という奇跡的な連なりを観測して、(何かの単語)がどれくらいの確率でやってくるのか、というのをカウントしなければなりません(もちろん、DBに保存するのもしんどいですね)。

しかしパターン数が膨大すぎると「ほとんどのパターンは発生しない」という問題が起こります。とはいえ、ただ観測されないだけであって、本当に0とは言えません。そこで「ほぼ0」の代わりに「0.1%」みたいな値でスムージングしたり、次元を削減したりします。

次元削減の考えとしては、例えば単純には、「を、」の次に来る単語を予測してあげるようなものにするとよいです。もう少し正確に言えば、n-gramモデルにおけるnを、1とか2とか、より低次なものにして、組み合わせパターンを減らします。ただし、「を、」の次に来る単語を予測できるよりも「私、は、今日、料理、を、」の次に来る単語を予測できる方が嬉しいのは明らかですし、そちらの方がより文脈を汲み取れるはずです。そういったトレードオフの元で、n-gramのnを減らすことになります。

また、スムージングについては言語モデル入門 - Topics Related to Computers and NLPに紹介されています。スムージングによる手法が意外とperplexityが低くてびっくりしました。Kneser-Ney スムージングによる文書生成 - Mi manca qualche giovedi`?も面白い結果がわかると思います。

さて、ニューラル言語モデル(そしてニューラルネットを使った言語モデル)は上の問題をどう解消しているのか、という説明に入ります。

単純な説明としては、「単語列」を「(単語の特徴を十分によく表すような)ベクトル列」として扱います。

これを例示的な説明に変えてみます。「私、は、今日、料理、を、」という列と全く同じ列は発生しにくいけど、似ている列なら発生しやすい、ということを利用します。具体的には「俺、は、明日、料理、を、」とか「彼、は、昨日、料理、を、」とか。そして「俺」「私」「彼」が似たような語順や文脈で使われることが期待されるので、獲得されたこれらの単語の特徴量は似てきます。他の単語も同様です。各単語をただの文字列として扱ったとき、コンピューターには各文字列の関係性はわかりませんが、特徴量ベクトルは類似します*2。結果として、「ある特徴量ベクトル列と似たような特徴量ベクトル列」というふうに一般化されれば、「ある単語列と全く同じ単語列」よりも確率が"スムージング"されます。

また、パラメータの増加の仕方も抑えられます。下の図は、論文に載っていたニューラルネットの構造に、注釈を付け加えたものです。

f:id:yamitzky:20140426101955p:plain

ニューラル言語モデルの中間層の出力はtanh(bias+Hx)で表わせるのですが、xは特徴量ベクトルの列 x = \left  [ C(w _ {t-1}), C(w _ {t-2}), ..., C(w _ {t-n+1}) \right ] という、(特徴量次元)がn個ならんだ行列です。特徴量次元は自分で設定できます(word2vecのときはたかだか100次元ぐらいとかで設定しました)。結果として、Cのパラメータ数(この場合、行列の大きさ)は語彙数に線形に増加し、Hのパラメータ数は予測に使用する単語列の数(n)に線形に増加します。一方で、先ほどの既存手法ではパラメータは vocab^{n} に指数的に増加してしまっていました。結果として、ニューラルネットを使ったモデルの方が次元の呪いを回避できる、という算段です。

なぜ単語の素性が獲得できるのか

ここでいう単語の素性とは「単語ごとを表すベクトル」です。word2vecでは、これを足し引きして、アナロジーとか類似度計算をしていたわけですね。

すごーくざっくり言うと、モデル(ニューラルネット)が特徴量(素性)抽出をするから、と言うといい気がします。

このモデルは、最終的に精度よく「次の単語」を予測できるように、単語の特徴を十分によく表すように圧縮した特徴量ベクトルを獲得します。「エントロピーを下げてくれるような特徴量」と言うといいような気もします。

先ほどの「私、は、今日、料理、を、」の列のたとえに戻ります。例えば、「(無意味な特徴量)、(無意味な特徴量)、(無意味な特徴量)、(無意味な特徴量)、(無意味な特徴量)、」という列の次の単語は、全く予測できるような気がしません。その逆に各単語を十分に表すような良い特徴量が獲得できれば、次に来る単語を予測できるようになります。

ニューラル言語モデルの学習時には、正しく予測できるように、Back propagationによって特徴量が学習され、最終的に良い特徴量(素性)が完成します。

なぜ単語の素性で足し算/引き算ができるのか

ニューラルネットによる単語のベクトル表現の学習 〜 Twitterのデータでword2vecしてみた - 病みつきエンジニアブログについてです。

答えについては、知らないので、誰か教えてください。。。(というのを聞きたくてこのブログを書いたに等しいかも)

ちなみにこのニューラル言語モデルでもそれができるのかも知りません。このモデルができた当時にはそのような話はないような気がしますが、僕は当時小学生だったので知りません。でも多分、Tomas MikolovによるRecurrent Neural Network Language Modelが初出な気がしますが、歴史的経緯はあまり知らないのでご指摘いただけると。。。

終わりに

ニューラルネットは研究で使ったことがないので、誤りを含むかもしれません。何か間違いや不正確な表現、またはわかりにくい表現がありましたら指摘していただけると幸いです。

参考文献

*1:一応、学習結果として、重みの出方で語順が葬り去られることもなくはないと思いますが

*2:明確に文法的特徴がここに置かれるわけではありませんが、特徴量ベクトルの類似度の結果を見ると、同じ品詞のものが似ているとされたりします。例えば「北海道」と「名古屋」など。その反例もありますが、学習データ量が足りないと顕著になります

LDAを使って、Twitterでスパムに使われそうな単語を推定する

教師なしLDAでTwitterのスパム判別をしてみる(予備実験編) - 病みつきエンジニアブログ の続きになります!

モチベーション

前回の記事で、LDA(latent Dirichlet allocation)のモデルを獲得したので、獲得したモデルを使って「どんな単語がスパムによく使われるのか?」というのを推定しようと思った。

そんなにちゃんとモチベーションない気がする*1

考えられる使い道は、

  • スパム判定のルールを作ることができる
  • スパム判定されなさそうな単語を選択することができる(スパマー側の気持ちに立ってw)

なのかなあ、あんまり使えなさそう。でも、ここらへんの教師データってちゃんとないから、前者は普通にありかなあと思ったり。

推定方法

{ \displaystyle
p(spam|word) \propto p(spam) \sum_{z} p(word | z) p(z|spam)
}

という式に基いて行っている。順番に説明すると、

  •  p(spam|word)は「ある単語がスパムっぽい確率」
  •  p(spam)は「そもそもスパムが発生する確率」、つまりスパムの事前確率
  •  p(word|z)は「あるトピックにおける単語の発生確率」
  •  p(z|spam)は、「あるトピックがスパムである尤度」

となる(spamは変数なので、non-spamと置き換えても良い)。

このうち、尤度は「1か0」にしている。つまり、 \sum_{z} p(word | z) p(z|spam) は、「スパムっぽいトピックでの単語の発生確率を足しあわせたもの」と言い換えられる。

で、スパムの時、スパムでない時、それぞれ足しあわせた結果を、比較して、大きいほうを採択する。そのときの基準を決めるのが、 p(spam)になり、これをハイパーパラメータとした。

こうするメリットというのがあって、「どれぐらい明らかにスパムっぽかったらスパムと決定するか」というのをパラメータ一つで柔軟に設定できる*2

以上をもう少し簡単に言うと、スパムっぽいトピックでのみ頻出する単語はスパムであるとする。その境界は p(spam)で決まる、と言っているようなもの、かな。

データセット

データは、自分で収集したもので、「URLつきのツイート」50919件

教師なしLDAでTwitterのスパム判別をしてみる(予備実験編) - 病みつきエンジニアブログ とほぼ同じデータになります(LDA回し直したけど)

f:id:yamitzky:20140216225341p:plain

実装

githubに置いた。汚い。

スパム単語の検知はspam_detect.py

実験結果

 p(spam) = 0.1ぐらいにすると*3

レポート
#followmejp
#goen
インフォゼロ
アフィリエイト
月収
4
0
万
円
9
不労所得
構築
方法
ハズレ
残念
チャレンジ
楽天
ラッキー
スクラッチ
総額
ポイント
当たる
抽選
www.rakuten-apps.jp
稼げる
毎月
以上
公開
対応
ツール
関係
3
atq.ck.valuecommerce.com
#ヤフオク
送料
[
a.r10.to
#RakutenIchiba

twitter-lda/spam-words-0.1.txt at master · yamitzky/twitter-lda · GitHub

という感じ。結構スパムに使われそうな単語が並んでいる感じはある。「楽天」とか「ヤフオク」って結構スパムに使われるんですね。別に楽天が悪いわけじゃないけど・・・。

とはいえ、「ツール」とかは、スパムにのみ使われる単語ではないので、これをルールとして採用して、厳密に取り除いたりすると、ちょっときついかも。それでもいいと妥協するか、もう少し賢いやり方を考える必要がある。

今回の場合は「URLつきツイートのコーパス」を使っているので、「全ツイートのコーパス」でやると、もっとそれっぽい単語だけに絞られるかもしれない。なぜなら、"ツール"という単語がスパム以外にも使われれば、「スパムっぽさ」の事後確率がだんだん下がるから。

次回は、「LDAを使って、Twitterのスパムを効果的に取り除く単語を推定する」というテーマで書きます!

補足:LDA使う意味があるのか?

この疑問なんですが、個人的にはあるかなあ、と。

理由としては、たかだか、LDAで生成されるトピック数分(100個とか、もちろん柔軟に決められる)の教師データを作ればよいから。

一方で、スパム単語の教師データ(ルール)をちゃんと作るのは、結構大変です。全単語見るわけにはいかないし、スパムっぽいかどうか判断できない単語が多いし。

このことから、LDAで次元圧縮して必要な教師データ数を絞る、というのは結構ありな戦略なんじゃないかなあと思っていますが、アンサーまさかりをお願い致します。

補足:判別式の計算

算出したいのは  p(spam|word) になる。これが何を表すかというと、「ある単語は、スパムっぽいか否か?(スパムに使われそうか?)」ということ。

で、ベイズの公式から

{ \displaystyle
p(spam|word) \propto p(word|spam) p(spam)
}

となるので、右辺の2つの関数を求めたい。

 p(word|spam) は、

{ \displaystyle
p(word|spam) = \sum_z p(word, z|spam) = \sum_z p(word | z) p(z|spam)  { \displaystyle
 = \sum_z p(word|z) \frac{ p(spam|z) p(z) } { p(spam) }  = \frac{ p(z) }{ p(spam) }  \sum_{z} p(word|z) p(spam|z)

ただしここには、 p(z) はトピック(z)に依存しないという仮定を置いている(全てのトピックは等しく出やすい)し、実際これは正しいはず(LDAの \alphaがどのトピックにも等しく振られるから)。そうすると、これを最初の右辺に戻してあげて

{ \displaystyle
p(word|spam) p(spam) =  p(spam) \frac{ p(z) } { p(spam) }  \sum_{z} p(word|z) p(spam|z)
}

{ \displaystyle
= p(z) \sum_{z} p(word | z) p(spam | z)
}

となる。 p(word|z)は既にLDAで獲得済みなので、あとは、 p(spam|z)、つまり「トピック(z)がスパムかどうか」という確率が推定できればよい。これもベイズの公式を使って

{ \displaystyle
p(spam|z)= \frac{ p(z|spam)p(spam) } { p(z) }
}

これを最初の式に戻すと、

{ \displaystyle
p(spam|word) \propto p(z) \sum_{z} p(word | z) \frac{ p(z|spam) p(spam) } { p(z) }
}

{\displaystyle
= p(spam) \sum_{z} p(word | z) p(z|spam)
}

となり、判別のための式ができた。

このうち尤度p(z|spam)に関しては人力で、スパムかどうかを「0or1」で判断したもの。事前分布p(spam)に関しては、スパムだったら\gamma、スパムじゃなかったら1 - \gammaと、ハイパーパラメータ扱いをした(ちょうど、スパム/スパムじゃないの境界面を引く感じになると思う)。

また、最後の式は、解釈的にも難しくなくて、「スパムのときと非スパムのときの単語の出現確率をそれぞれ足しあわせて、その比率が一定以上だったらスパム」というような判別をやっているだけ、ということになる。

さて、ここまでの数式はド派手に間違えているかもしれないので、ツッコミをお待ちしております(震え声)

補足:注意

トピックに割り当てられる番号はランダムなので、LDAで再度学習すると「このトピック番号はスパムっぽい」という努力は水の泡になる。

したがって、再学習したときにもトラッキングできるような仕組みは作らないといけない。

例えば、1度計算して、その中から明らかにスパムっぽいものを「スパム単語のルール」として決定しておく。そこから、そのスパム単語の確率が p以上のものをスパムトピックとする、等。

*1:目的に基づいた実装じゃなくて、どちらかと言えば思考実験だったので

*2:本来的な事前確率としての意味合いは崩壊している気もする

*3:陽に事後確率を推定して、その確率の降順とかで出すこともできる

.gitignore作るなら、giboを使おう

最近技術研修でJavaやってます。

で、.classとかをコミットしてしまう人が居て、そこは.gitignoreをちゃんと設定すべき、です。

で、「ちゃんと.gitignoreを作る」って結構面倒くさいです。例えば、Macだったら .DS_Store を.gitignoreするべきだし、Javaだったら *.class とか、Eclipseまで手をだすと bin とか。

そこで、 gibo というソフトウェアが超おすすめです!

できること

$ gibo Java

とやると、

### https://raw.github.com/github/gitignore/master/Java.gitignore

*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

という文字列が作られます。これが何を表してるかというと、githubが考える、Javaについての.gitignoreのベストプラクティス」ということ。なので結構信頼性高いよね、という感じあります。

で、さらに「 Macの.DS_Storeも無視したい 」「 Vimで作られるスワップファイルとかも無視したい 」「 Eclipseのも無視したい 」とか思った時は、

$ gibo Java OSX Vim Eclipse

という感じで指定できます。

で、giboがするのは、ターミナル上に文字列を表示するだけなので、

$ gibo Java OSX Vim Eclipse > .gitignore

とかにすると、giboが生成した文字列を、.gitignoreファイルに保存できます

インストール方法

Macでhomebrew使ってるんだったら

$ brew install gibo

で終わり。

追記

gitignoreを生成するgiboとgiを比較 - nFact

gi(gitignore.io)という、似たようなツールとの比較。詳細。

giはgiboより使えるものが増えているメリットがありつつ、ネットワークアクセスに依存してるのが辛いっぽい(多分、curl のラッパーにすぎないから。giのラッパーがありそう)

文章読むとき、選択しながら読む人いるよね?

これ俺のことなんだけど。

ときどきはてなスター見ると、文章の変なところ選択して、はてなスターが付けている人がいる。

これって、多分、選択しながら文章読んでて、うっかりはてなスターつけちゃった人なんじゃないかと思うわけですよ。

ニューラルネットによる単語のベクトル表現の学習 〜 Twitterのデータでword2vecしてみた

最近にわかにword2vecが流行っています。ので、乗っかってみました的記事です。

理論に関してはあまり詳しくしらないので、印象だけで語っているかもしれません。何かありましたらTwitterかコメント等でご指摘いただけますと幸いです。

ちなみに、失敗した話が多いです

word2vecと単語のベクトル表現

word2vecは、機械学習の分野で使われる、ニューラルネットというモデルを使ったツール/ライブラリです*1。名前の通り、wordをvectorにします。vectorにする、というのは、ベクトル表現を獲得するということで、意味(みたいなもの)の獲得というか、素性の獲得というか。

単語のベクトル表現の獲得自体は、別にword2vecにしかないわけではありません。言い換えると、昔からあります。LDAを使って単語のトピック分布のようなものを学習したり(vingowでやりました)。余談ですが、この方法で、スパム単語の獲得を今度やろうと考えています。

しかしword2vecで面白いのが、単語の演算が(精度高く)できることです。例えば「king-man+woman」をベクトル演算してみると、「女性でいうところのking」つまり「queen」が出てきますよ、と*2

単語のベクトル表現を1段落で大雑把に正当化すると、「ある単語は、ある文脈で出やすいはずだ、共起してるはずだ」ということだと理解しています。例えば「初音ミク最高!!!」「ミクはニコ動でブレイク」といった文章から、ミクは「最高」で「ニコ動」という素性が推測できたり、「ミクはボカロ」「KAITOはボカロ」といった文章から、ミクもKAITOも「ボカロ」という素性が大きい、と推測できたり(結局は、どう共起するか、ということ)。

このあたりのお話は、海野さんによるこちらのslideshareが詳しいです。

Statistical Semantic入門 ~分布仮説からword2vecまで~

ということで、実験に移ります。

コーパス

コーパスは、表題の通り、Twitterのツイートです。ただし、

  • 形態素解析の辞書は、vingowのタグも含む(つまり"初音ミク"のような新語がコーパスにあったりします)
  • 検索ワードは "。,!,?+-\n+-笑+-「+-」+-w+-w+-(+-(+-http+-https+exclude:retweets"
  • URL投稿やリツイートを含まない
  • 「(」「「」「w」「#」あたりを含むと形態素解析失敗しそうなので、含まない*3
  • 10単語以下のツイートも文章として崩壊してそうなので、含まない
  • 半角・全角は正規化(ア1→ア1)
  • 132万1252ツイート、174MB

だいたいこんな感じです。

f:id:yamitzky:20140311222046p:plain

実験プログラム

yamitzky/word2vec-japanese-twitter · GitHub

プログラムは、github上にアップロードしてあります(ツイートのデータはDBからダンプしているので、取得プログラムはありません)。

また、実験結果のモデルファイルも置いてあります。余談ですが、モデルデータであれば著作権法上も大丈夫であると考えています(参考)。

また、あんちべさんによる自然言語処理の最新手法"word2vec"で艦これ加賀さんから乳を引いてみる - あんちべ!の記事を参考にしています。ありがとうございます。

実験1:とりあえず実験

例えば、各単語に似ている単語を出してみます。左から順に近いですが、

akb:nmb ske hkt 48 乃木坂 exile 東海 hy 集い 背番号
ミク:ザク 初音 誕生 レミオロメン 米津 杏子 sug happybirthday lat グフ
北海道:名古屋 京都 新潟 仙台 長野 大阪 札幌 長崎 東京 神戸
スルー:無視 補導 放置 拒否 敬遠 削除 退会 ブロック 解除 解放

など、なんか良さそうです。しかし、全然ダメなのもあって、

スク水:似非 赤ずきん 純潔 トランクス 革靴 紺 壷 剣士 カーディガン 赤毛
しょこたん:兼ね合い ひれ伏す デール 木製 atsushi 松たか子 gu ストライプ 島田 念願

あと、リア充に関しては「ハゲ」が一番近いという結果が出たのですが、定性的な解釈をお待ちしております

リア充:ハゲ ナルシスト 腐女子 ババア ニート イケメン キモオタ 帝京 リスニング 反則

「近い単語」をどう解釈できるかで言うと、「それを素性ベクトルで表現した時、距離が近そうか」ということだと思います。つまり、似た要素を持っているか。必ずしも、直感的な「意味」を表すかはわからない、と思います。

また、ここらへんのダメな理由は、だいたい出現頻度の少なさ(コーパス性質)で理由付けできる気はします。逆に、出現が多い単語(≒ツイッターでよくつぶやかれる単語)ほど、精度が高くなりそうです。

次に単語の演算をしてみます。A-B+Cは、単語のアナロジー「BにおけるAは、Cにおける何か」を表します。

良かったものとしては、

akb-東京+大阪:nmb hkt ske 乃木坂 jump spyair しょこたん 48 立見 exile
彼氏-男+女:彼女 友達 弟 恋人 知り合い お父さん お母さん リア充 旦那 誰々

こちらもひどいものはひどくて(というか、酷いのがほとんど)、

日本-東京+ロンドン:戦車 自国 国々 国外 世論 在り方 有数 ウイグル 最高峰 従来

Twitterでよくつぶやかれそうな場所に関するものでも、このように失敗するのはちょっと残念でしたが、そういう共起の仕方をしないのかもしれません*4

実験2:コーパスの量の変更

コーパス量が減ると、どう失敗していくか、というのを確認したかったです。ツイート数を、10万ツイートに絞ります。ファイルサイズはだいたい12MBです。

すると、

akb:ワンピース 司令 唯一 ed 雄 王様 パーカー 沢 ガ ガラス
ミク:たった 前半 丸 ラブライブ 級 プレミアム 講座 23 発売 誕生
北海道:長野 rad ギリ みなさん 後藤 夕食 ジル ゲリラ 大阪 セガ

など、結構ダメですね!

僕の最初の状態とかまさにこれで、コーパスの量が少ないと、まともに動きません*5。特に、これはコーパス性質によっても変わると思います(後述)。もう少し厳密に言うと、ほしい情報についてのコーパスの量が少ないと、まともに動かないのではないかと予想しています(後述)。

逆に、もっとデータ量が増えたら、もっと有意義かもしれません。今後に期待!

その他の実験:

window、sizeなどを変えてみたのですが、それほど大きな変更ないように感じました。ただし、sizeは少なすぎるとダメです。windowは多すぎるとダメです。このあたりを気をつけていただければ大丈夫かな、と。

気になる方は、モデルファイルを突っ込んであるので、試してみてください(ちゃんと評価をやろうと思ってたけど、あ、コーパスの時点でこりゃダメそうだなと思って諦めました)。言語によっても違いますので、このあたり、誰かがちゃんと定量的に見てくださると非常にありがたいです。

あとは、例えば体言を原形にするとか、単語を単語+品詞にするとか、コーパスづくりのところで工夫できるかな、と思います(やってないし多分やらない)

雑感

雑感としては、コーパスについてもっとちゃんと考えるべきだったなと思います。

という意見があったりして(というか私が不勉強でそういう意識を全く持てていなかった)、それを実感した構図です。

で、この場合だとどういうことかというと、「艦これのベクトル表現獲得」だったら「艦これスレコーパス」は妥当だし、「英辞郎データ」を使えば「日英翻訳」ができるだろう、と。じゃあ、「ツイートコーパス」だと何ができるの?という話です。

これに関して、明確な答えは出てないんですが、広く取ってきたツイートというのは「一般人の評判を反映してるかも」とか「新語の意味がわかるかも」とか、そういったあたりの情報が取り出せるかもな、ということです。そうはいっても、「広く浅く」なコーパスでできることってほぼないですね、word2vecの場合*6

また、コーパスに関してもう一つ感じるのが、ドメインを絞っている方が、より少ないコーパスで動きそうだ、と思います。あんちべさんの艦これのデータは6.5MBくらいしかないのですが、艦娘*7に関する素性はよく獲得できています(逆に一般名詞はひょっとしたら弱い、かも)。

これらを注意点としてまとめるなら、先にどういう情報を知りたいか決めてからコーパスを作ると、もっと有意になると思います。例えば評判を反映したいなら、トヨタなら「車」「中古」「道路」「タイヤ」をトラックワードにしてコーパスを作れば、そういう評判の素性獲得が、もっと少ないコーパスでできるかもしれません(本当に例えばですけど、商品を「満足度」の素性で比較できるかも、とか)。

(追記) [Mikolov+ 2013]でも指摘されていますが、単語のベクトル表現は自然言語処理のアプリケーション(応用)に非常に役に立つだろう、と言及されています(符号としての単語だったものが、素性の塊として扱えるので、夢広がります)。そういう用途でword2vecはそもそも使えると思いますが、そういう用途だとTwitterコーパスは少し不適切だと思います的エクスキューズを書いておきます。

参考文献

追記

データ量を182万ツイートまで増やして、ちょっと遊んでみました。

腐女子-女+男:プルート ホモ 貧乳 ヲタク 語感 腐男子 ブス 巨乳 根暗 清楚
北海道-雪+海:福岡 長野 沖縄 住ん 東京 札幌 名古屋 宮城 青森 仙台
リア充:サイテー 爆発 腐女子 ブサメン テメー 三角関係 ブス 君達 ぱみ 四散
カップル:続く 別れる 女 コーギー ♡」 長く line 男 ~♡」 未読
おっぱい:お尻 乳首 マイスター アナル 乳 揉む 揉み 貧乳 胸 巨乳
宇宙:異世界 異次元 未来 能楽 呪術 情報化 ロボット 形而下 機械 陽電子
イケメン:男前 美人 ブス 惚れる 色白 イケボ 変態 病弱 ブサメン ナルシスト
g-a+貧乳:巨乳 触手 非力 古参 低身長 もてる ?????! 上と下 三角形 獣
魚-海+空:卵 出汁 用水路 粥 味噌汁 肉 しるし 箸 皮 カッチカチ
ボカロ:アーティスト アニソン 洋楽 邦楽 レゲエ バンド カップリング 特撮 イラストレーター
おっぱい-女+男:お尻 乳首 アナル マイスター 貧乳 乳 揉む 巨乳 胸 揉み
妊娠-女+男:フリーズ 気絶 ズキズキ 解散 入院 幻滅 発症 悪化 接近 発狂
寒い-北海道+沖縄:暑い さむい 暖かい 眠たい 積もる はやい あったかい 寒かっ ねむい 忙しい

リア充はサイテーです。各位参考にしてください。

*1:補足:「手法だ」という風に書いてしまっていたのですが、論文では手法名として明記されておりません。また、オリジナル実装とgensimの実装がありますので、単一のツールを指す名詞として表現するのも、少し違和感があります

*2:これ、LSAでもできるみたいですね

*3:「は文分割に影響があったため外していました。しかし現在は、文を分割していないので、使用するべきでした

*4:ツイート文中には、「日本が金メダル」「イギリスが金メダル」「東京に遊びに来た」はあっても「イギリスのロンドン」とかそういう表現がないから、とか、そんな感じの予想です

*5:この原因、最初はコーパスのロードの仕方が間違ってたのかと思いましたが、おそらくコーパスの量の問題でした

*6:逆に、Wikipediaコーパスとした時に取れない情報があるはずなんです。その差分が、Twitterコーパスの強みかなと思います。アイディアを募集しています。

*7:かんむすめって読むんですかこれ?

教師なしLDAでTwitterのスパム判別をしてみる(予備実験編)

※普通は「教師なしLDA」という言い方はしないです

モチベーション

元々は、TwitterからURLつきのツイートを取りたかった。某ニュースアプリがTwitter上で(?)話題になっているニュース記事を(法的な是非があるとはいえ)配信しており、そんな感じのマイニングがしたかった。

ただ、普通に「http,https」でTwitter上で検索すると、量が膨大だった。加えて、ほとんどがスパム。なーにが「このサイトすごすぎwwwww」じゃ。

f:id:yamitzky:20140216225341p:plain

ということで、検索の段階でスパミーなキーワードを取り除き、純度の高いURL投稿マイニングをしたいわけだが、キーワードは既知なものには限らない。例えば「無料」とか「アフィリエイト」とかがスパムなのはそうなんだけど、「パズドラ」とか「魔法石」とか、未知のキーワードとか出てきた時に対応できない。

そこで、教師なし学習のアプローチを使って、スパムなキーワードを抽出する、ということを目的として、実験を行った。

モデル

モデルは、latent Dirichlet allocation(以下、LDA)[1]を使った。LDA自体の説明は、星の数ほどあるので、ここではほぼ行わない。

LDAでは、「文書には潜在的なトピックがある。潜在的なトピックは、単語を生成する」というような生成過程を踏む。逆に言うと、「ある文書に、ある単語が出やすいのは、そこに潜在的なトピックがあるからだ」とも言える。

具体的な例を出すと、「無料レポート:インフォゼロのアフィリエイトで月収40万円と月9万円の不労所得を構築した方法」というツイートは、その背後に「スパム」というトピックが存在するからだ、とする。逆に言うと、「スパム」というトピックを持つ文書は、「アフィリエイト」みたいな単語を生成しやすいはず。

ちなみに、LDAを使って得られるものは、簡潔に2つある。「文書ごとのトピック分布」と「トピックごとの単語分布」だ。

「とりあえずLDA」を使って学習してみて、スパムなトピックが学習できるか、そして、そのトピックから特徴的な単語を炙り出せるか、を確かめる。今回は予備実験として、トピックの出方を確認してみて、スパムなトピックが決定づけられそうかを見てみる。

前処理

各ツイートを、MeCabを使ったりして次のように単語分割した。

無料レポート:インフォゼロのアフィリエイトで月収40万円と月9万円の不労所得を構築した方法 http://t.co/****** #followmejp #goen

が元のツイートだとした場合、

無料 レポート : インフォゼロ の アフィリエイト で 月収 4 0 万 円 と 月 9 万 円 の 不労所得 を 構築 し た 方法 example.com #followmejp #goen

つまり、「通常の単語」+「URLを展開してドメイン部を取り出したもの」+「ハッシュタグ」を、文書=単語列とした(語順は関係ないのだが、先にURLとハッシュタグを除いてから形態素解析しているので、形態素解析が失敗している可能性あり)。

実験設定

プログラムは、自分で実装したLDA使ってもよかったんだけど、多分遅いので、GibbsLDA++を使った。実験設定は以下。

  • 文書数:50,919ぐらい
  • トピック数:100
  • 学習回数:20,000
  • α:50 / トピック数
  • β:0.1

注意点として、

  • トピック数は少なすぎるとうまくいかない
  • 学習回数は多すぎるかも(多すぎて困ることはない。perplexityの確認はしていない)
  • α、βはデフォルトのパラメータ

LDAとツイート収集以外のソースは、全てgithub上に置いた

実験結果

結果はgithub上に置いた。これは、トピックごとの単語分布\phiのうち、頻出上位30件を書いたもの。

多分、そもそもスパムが多すぎて、トピックがスパムばっかなんだけど、特徴的なものもいくつか。

例えば、30番目のトピックは、

で を 自動 ツイッター bit.ly フォロワー 方法 収入 万

ということで、スパムっぽいトピック。45番目とか51番目とかのトピックは、

に bit.ly 裏 と 無限 ワザ 4 手 アイテム 最強

~ bit.ly の を で た 報酬 ん アフィリエイト

と、bit.lyみたいな短縮URLは、スパムっぽい傾向があることがつかめる。

逆に、42番目のトピックを見ると、

bit.ly → 無料 プレ #ニュース ソチ 五輪 ( #スポーツ 「

と、ソチの話題にも関わらず、bit.lyとか、「無料」とか、キーワードの誤爆が出てきそう。

また、

まし fb.me 写真 し 投稿 新しい Facebook

(@ 4sq.com ) 店 pic ]: [ ))

 r.gnavi.co.jp   の … goo.gl  店 。 な : 味 ランチ

とか、ドメイン名とトピックが結構関係するというのも、狙い通り。

あと、意外だったのが、stopwordsがスパムトピックの上位に残ってしまっている。本来なら、stopwordのトピックが作られてほしかった。ここらへんは、ツイートという性質の問題かもしれない。

【追記】実験その2

コメント欄でid:Kesinにぐぅ正論なアドバイスをいただいたので、再実験した。

前処理として、語彙を「名詞(おそらく記号含む)」「動詞(原形)」とハッシュタグ、URLのみに制限した。すなわち、

無料レポート:インフォゼロのアフィリエイトで月収40万円と月9万円の不労所得を構築した方法 http://t.co/****** #followmejp #goen

が元のツイートだとした場合、

無料 レポート : インフォゼロ アフィリエイト 月収 4 0 万 円 月 9 万 円 不労所得 構築 する 方法 example.com #followmejp #goen

となる。

【追記】実験結果その2

結果はgithub上に置いた

登録 ポイント 獲得 中 小遣い ギフト 券 キャンペーン

] [ 楽天 a.r10.to #RakutenIchiba ゚ 送料 #ダイエット

♡ bit.ly 女性 完全 , 4 限定 友達 ここ 今

する フォロー 方法 ツイッター bit.ly アカウント 自動 つぶやき

ここらへんなど、スパムっぽいトピックは同様に出現している。特に顕著なのが、定性的な解釈がしやすくなったことだ。マイニングで定性的に確認したいという場合は特に、ちゃんとstopwordsが取り除かれるようにしたほうがいいかもしれない。

ただ、単語分布を素性として扱うと考えると、どっちがいいのかは今のところわからないので、後々の検証の余地がある。

結論

以上から、スパムなトピックは学習できてるっぽい(ここはそんなにかっちりした結論はいらない)。

Future work

今後やろうと考えているのは、

  • ツイートのスパム分類(寄り道)
  • スパムキーワードの学習(本丸)

あとは、biterm topic model[2]みたいな、短文向けのトピックモデルも提案されているので(※読んだことない)、こちらを使ってみるのも面白いかもしれない(けど、あまり興味ないので、誰か!)

モチベーション2

ビジネス的要件で、何かを判別しましょう、機械学習しましょう、とすると、結構教師あり学習でーSVMでーみたいな流れになるような気がする。もしくはRandom Forestでー、みたいな。

この理由は、使いやすくて、使われてきたから、だと思う(違ったらごめんなさい)。

でも、LDAみたいな教師なし学習・生成モデルも結構簡単に実験できる。ので、カジュアルに使ってみても面白いんじゃないかなーと思ったり。

参考文献

  • [1] Blei, David M., Andrew Y. Ng, and Michael I. Jordan. "Latent dirichlet allocation." the Journal of machine Learning research 3 (2003): 993-1022.(PDF)
  • [2] Yan, Xiaohui, et al. "A biterm topic model for short texts." Proceedings of the 22nd international conference on World Wide Web. International World Wide Web Conferences Steering Committee, 2013.(PDF)