AppBrew Tech Blog

AppBrewのエンジニアチームの日々です

ナイーブベイズを使って1日で100万件の投稿をジャンル分けした話

こんにちは、AppBrewでアルバイトをしている@Leoです。 自然言語処理の研究室に最近入った大学生で、趣味はKaggle競技プログラミングです。 AppBrewでは、LIPSの投稿を使ったデータ分析をしています。

今日の記事では、弊社のアプリLIPSにて投稿ジャンルを機械学習を使って自動推定した方法を紹介します。

自然言語処理・確率関係全然わからない!という人でも読みやすい内容になっていると思うので、最後まで読んでいただけると幸いです!

LIPSにおけるジャンル

最近、LIPSにジャンル機能が追加されました。 これは投稿されたクチコミにジャンルを設定できる機能です。 適切にジャンルを設定すると、投稿を検索するときにジャンルを使って絞り込めるなどの利点があります。

ジャンルは7種類(コスメ、スキンケア、ヘアケア・ヘアアレンジ、ネイル、ダイエット、恋愛、その他)のいずれかです。

f:id:bluexleoxgreen:20190907205117p:plain:w500
LIPSにおけるジャンル

ジャンル機能追加後の投稿にはユーザがジャンルを付与してくれるのですが、それ以前の投稿にはジャンルが付いていないという問題がありました。

LIPSには既存の投稿が100万件ほどあったので、古い投稿に手動でジャンルを付与するのは現実的ではありません。 そこで、ジャンルがついている最近の投稿を使って、古い投稿にも自動でジャンルを付与することにしました。

教師データの作成

機械学習用の教師データを作るために、直近2ヶ月間の投稿におけるジャンル比率を確認しました。

f:id:bluexleoxgreen:20190901064239p:plain:w450
投稿ジャンルの分布

ジャンル機能実装前の投稿のジャンルはすべて「コスメ」になっています。

この図を見ると以下のことがわかります。

  • 6月25日から「コスメ」以外のジャンルの投稿が増えている
    • この頃、ジャンル機能を実装した
  • 7月10日からジャンル「その他」の投稿が増えている
    • この頃、ジャンル「その他」を追加した

今回は、ジャンル機能を導入し終え、アプリのアップデートが十分浸透した、7月13日以降のデータを使ってモデルを学習することにしました。

ナイーブベイズ

ジャンル推定のためのモデルとしては、ナイーブベイズという古典的な機械学習モデルを使うことにしました。

ナイーブベイズとは、ベイズの定理 \Pr(Y \mid X) = \frac{\Pr(X \mid Y)\Pr(Y)}{\Pr(X)} を利用した分類手法です。 仕組みがシンプルで実装が簡単で計算も軽いので、教師データを使ってテキストを分類するときにはベースラインとなるアルゴリズムです。

この節は数式多めなので、ニガテな人は次の節まで読み飛ばしちゃってください。

f:id:spinute:20190910114420p:plain
sklearn のアルゴリズム選択フローチャートhttps://scikit-learn.org/stable/tutorial/machine_learning_map/index.html より)

今は投稿の本文からそのジャンルを推定しようとしているので、ベイズの定理において、X を投稿の本文 content に、Y を投稿のジャンル genre に置き換えます。

この等式に現れる各項を見てみましょう。

  • \Pr(genre \mid content):ある投稿の本文が content であるとき、そのジャンルが genre である確率
  • \Pr(content \mid genre):ある投稿のジャンルが genre であるとき、その投稿本文が content である確率
  • \Pr(genre):ある投稿のジャンルが genre である確率
  • \Pr(content):ある投稿の本文が content である確率

今知りたいのは、ある投稿本文 content が与えられたとき、最も \Pr(genre \mid content) が大きくなるような(= 最もそのジャンルっぽい)ジャンル genre です。

先述したように、\Pr(content) は固定値なので判断に影響せず、無視しても結果はかわりません。また、\Pr(genre) は学習データにおけるそのジャンルの投稿数割合で推定できます。

あとは、\Pr(content \mid genre) さえ計算できれば、最も \Pr(genre \mid content) が大きくなるジャンル genre を計算できます。

この値を計算するために、投稿を単語に分割します1。 投稿本文を構成する単語を w_1, w_2, \cdots, w_n とします。 ここで、投稿本文とその本文に現れる全単語の集合を同一視します。つまり、本文にどの単語が現れたかだけを考慮し、単語の並び順や位置等を無視することにします。 すると、\Pr(content \mid genre) = \Pr(w_1, w_2, \cdots, w_n \mid genre) となります。

さらに、各単語の出現確率の独立性を仮定します(つまり、実際には一緒に使われやすい・使われにくい単語等がありますが、それを無視します)。 すると、\Pr(w_1, w_1, \cdots, w_n \mid genre) = \Pr(w_1 \mid genre) \times \Pr(w_2 \mid genre) \times \cdots \times \Pr(w_n \mid genre) となります。

\Pr(w_i \mid genre) は、学習データにおけるジャンルが genre である投稿のうち、w_i が現れるものの割合として推定できます。

こうして、学習データを使って各ジャンルの \Pr(content \mid genre) を計算できました。

以上のように、投稿の本文からその投稿のジャンルを推定する手法がナイーブベイズです。

単語分割

ナイーブベイズを使うためには、投稿本文を単語に分割する必要があります。 英語の場合は単に単語間のスペースで区切れば済むのですが、日本語の場合は文を解析して単語の区切りを見つけなければいけません。

今回はMeCabを使って単語の区切り調べました。試しにMeCabを使ってみましょう。

$ mecab
すもももももももものうち
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ

しっかり「すもも」や「もも」を区切れていますね。

MeCabで使える辞書2はいくつかありますが、LIPSを解析するには、Web上のデータを新語として日々追加してくれるNEologdが良いでしょう。

最近のNEologdを使うとYouTuberの名前も認識できるらしいです。 例えばYouTuberの「ゆきりぬ」さんに関する「今日のゆきりぬの動画で〜」という文を考えてみましょう。

「今日のゆきりぬの動画」を他の辞書(IPAdic)を使って分割すると、「ゆきりぬ」が「ゆき、り、ぬ」に分けられてしまいます。

今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
の 助詞,連体化,*,*,*,*,の,ノ,ノ
ゆき 名詞,一般,*,*,*,*,ゆき,ユキ,ユキ
り 助動詞,*,*,*,文語・リ,基本形,り,リ,リ
ぬ 助動詞,*,*,*,不変化型,基本形,ぬ,ヌ,ヌ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
動画 名詞,一般,*,*,*,*,動画,ドウガ,ドーガ

一方NEologdを使うと「ゆきりぬ」を人名として認識してくれます。

今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
の 助詞,連体化,*,*,*,*,の,ノ,ノ
ゆきりぬ 名詞,固有名詞,人名,一般,*,*,ゆきりぬ,ユキリヌ,ユキリヌ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
動画 名詞,一般,*,*,*,*,動画,ドウガ,ドーガ

LIPSにはコスメを紹介するYouTuberに関する投稿もあります。 そのような投稿を正しく単語に分割するためにも、今回はNEologdを使いました。

モデルの実装

実装にはscikit-learnを使いました。

CountVectorizerを使って投稿をベクトル化し、Complement Naive Bayesを使ってモデルを学習しました。

ここで使ったComplement Naive Bayes3はナイーブベイズの亜種です。 これは今回のように、ジャンルが3つ以上ありジャンルごとのデータ量の偏りが大きいときに有効なモデルです。 実際に、素朴なナイーブベイズを使った場合と比べて予測精度が大きく改善すること4を確認できたため、今回はこのモデルを使いました。

分類結果

LIPSに投稿された全投稿のジャンルを予測する前に、分類結果を検証しました。

今回は過去の投稿のジャンルを予想したいので、7月14日以降のデータを訓練用、7月13日のデータを検証用としました。5

予測精度指標としては、Accuracy、Precision、Recall、F値を使いました(各指標の説明は例えばここにあります)。

Precision Recall F値 Accuracy
コスメ 0.93 0.90 0.92 0.86
スキンケア 0.72 0.90 0.80
ヘアケア 0.81 0.92 0.86
ネイル 0.90 0.74 0.81
ダイエット 0.78 0.92 0.84
恋愛 0.89 0.19 0.31
その他 0.52 0.34 0.41

「コスメ」〜「ダイエット」ではF値が0.8を超えていて良さそうです。

結果を掘り下げるため、どのクラスをどのクラスと混同しているのかを確認してみました。

f:id:bluexleoxgreen:20190906154228p:plain
実際のジャンルと予想したジャンル

  • 「恋愛」の投稿は1%未満しかなく、データ不足が原因で上手く分類が出来なかったようです。データが増えたら学習し直すと良いかもしれません
  • 「その他」の投稿が「コスメ」や「スキンケア」に分類されることが多いようです。間違って分類された投稿を見てみると、これは「その他」の定義がフワッとしていることに起因するようでした

最後に、実際に目視で結果を確認しましたが、「コスメ」~「ダイエット」の投稿は十分な精度が出ていると言えそうでした6

おわりに

投稿のジャンルを推定するにあたり、Complement Naive Bayesを使うと上手くいったという話でした。ナイーブベイズは実装が簡単で計算も軽く、今回の作業は一日程度で終えられました。

最近の自然言語処理機械学習界隈では、強くて重いモデルで殴ることが多いのですが、タスクに合った手法を選べば少ないリソースでも十分なパフォーマンスを出せることがあります。

弊社AppBrewでは現在、350万DL突破のコスメ口コミアプリ「LIPS」をはじめとした「ユーザーが熱狂するプロダクト」を、「再現性をもって開発すること」に携わるエンジニアを積極採用中です。

下記ページにぜひ一度、目を通してみてください!


  1. 単語以外の特徴量を使うこともできます。特徴量の独立性を仮定し、ベイズの定理を使って最尤推定をする手法がナイーブベイズです

  2. 形態素解析に使える辞書については https://tech.appbrew.io/entry/2019/09/04/120000 でアルバイトの星野さんが記事を書いてくれました

  3. Complement Naive Bayes を提案した論文:https://people.csail.mit.edu/jrennie/papers/icml03-nb.pdf

  4. sklearnの他のナイーブベイズ亜種と比べて、特にデータ量が少ないジャンルでComplement Naive Bayesは有効でしたf:id:bluexleoxgreen:20190910001310j:plain:w300

  5. 学習データ: 約32000、検証データ: 約8000

  6. ユーザがジャンルを付け間違えたものを、モデルが正しいジャンルに分類しているケースも散見されました