はじめまして!リンクバルでエンジニアとして働いている磯谷と申します。
先日行った機械学習系の仕事について書かせていただきます。
何番煎じかわかりませんが、よければ最後までお付き合いください( ´ ▽ ` )
やったこと
投稿された文章を自動でカテゴライズする。
なお言語はPythonです、jupyter notebook上でコードを書いています。
目的
弊社が展開しているCouplinkというサービスにおいて、投稿内容に規約違反がないかどうかをチェックしています。
今までは全て人力で行っていた分類作業に対し、マシーンラーニング導入でコスト削減!
今回は「規約違反(以下、カテゴリX)か否か」の2値分類を行った際の話を書かせていただきます。
大まかな流れ
ランダムフォレスト とナイーブベイズを軸に調査を進めました。
学習データとテストデータは過去に人力で分類を行ったデータたちです。(おなじみ7対3に分割です。)
評価指標は、カテゴリXを検出したいイベントとして、Recallをメインの指標としました。(できるだけカテゴリXを検出したい。)
①Preprocessing
②Training
③Predicting(運用)
⓪前処理のさらに前に
・分類済(ラベリング済)の教師データは手元にたくさんあるものの、過去数年の間に判定基準が何度か変わったため、
「去年の基準ではカテゴリAだけど、今の基準ではカテゴリBです」のようなデータが存在していました。
このラベルの振り直しがもっとも大変な作業でした。。
(でも判定精度に大きな影響を与えるため頑張りました!!)
・各カテゴリにおけるデータ数の不均衡がかなりありました。早速大変そうな予感です。
※ちなみに規約違反データはもともとかなり少ないです。全体の1%にも満たない。
①Preprocessing
・まずはデータクリーニングです。
アルファベットの小文字を大文字にしたり、半角を全角に統一したり、スペースや句読点を削除したり、、、なおライブラリはmojimojiを使用しました。
・日本語の文章ですので、次は形態素解析です。
流行りの単語も多く使用されていたため、mecabのipadicを使用することにしました。
(以前Tensorflowをインストールした時もそうでしたが、ライブラリ間のバージョンが合っていないなどの問題で、本題に入る前にそこそこ時間を取られることが多いです。)
教師データとして用いる単語は色々試しました。名詞のみを用いたり、動詞や形容詞も加えたり、数はのぞいてみたり。
データの特性にもよるとは思いますが、今回はどれも大差なかった印象です。
②Training
ここは大きく2つのフェーズに別れると思っています。
1つ目:特徴量の選定(これは前処理の扱いかもしれませんが、アルゴリズムとの組み合わせに依る部分も大きいため、Trainingの範疇として記載します。)
2つ目:アルゴリズムの選定
そして総合的な選択肢は N(1つ目のフェーズの選択肢)✖N(2つ目のフェーズの選択肢) だけあることになります。(機能しない組み合わせもあるのでざっくりですが。)
※さらにハイパーパラメータの考慮も必要ですが、そこはGrid Search、Bayesian Optimizationの出番です。
【特徴量の選定】
基本的にgensimを用いて特徴量を探っていきました。
・まずは辞書の作成です。
単に単語だけを抜き出すと、数万次元というものすごい数です。。
全体の80%以上のデータに出現する単語は頻出すぎるので削除、5回以下しか出現しないレアな単語も落としました。こんな感じです。
dictionary.filter_extremes(no_below=5,no_above=0.8)
それでもまだ次元数は数千あります、削減しなければ。
またカテゴリXはデータ数がかなり少なかったため、no_belowを設定してしまうことで大事なキーワードも落としてしまいそうで怖いです。
テストの結果、no_belowが5以上だと結果に大差なく、4以下だと精度がかなり下がってしまいました。
おそらくデータ数が豊富なカテゴリX以外において、意味のない単語も特徴量に入ってしまい場が荒れたものと思われます。(推測にすぎませんが。。)
次にいよいよ特徴ベクトルの作成です。
・特徴量候補の一つめはシンプルなbowです。でも数千次元にも及ぶスパースなベクトルなのであまり使えないかも。。
・次にtfidfを作成しました。
ただ、のちに出てくるナイーブベイズに適用した時、ベルヌーイ分布と多項分布では使えないとPythonに怒られてしまいました。残念。(負の値はダメみたいです。)
・やはり次元数が多いので、LSIとLDAで次元削減を試みました。
劇的な精度改善は見られなかったものの、やはり学習時間は大幅に完全されました!
処理時間の問題でbowでは諦めていたGrid Searchも現実的な時間で回すことができました!
ちなみに今回の対象データは学習データにない未知語も頻出すると予想されるので、LDAの方が性能がいいと思っていましたが、
いざ結果を見るとLSIに軍配が上がりました。
この後さらに、Under Sampling、OverSampling、SMOTEと、なんとかデータの不均衡を小さくなるようにしました。
データ不均衡の割合をループで変えつつGrid Searchをまわす感じです。
さらにLSIのトピック数も色々変えてループしてます。
【アルゴリズムの選定】
アルゴリズムは以下の4種を試しました。右側は結果と感想です。
①ランダムフォレスト :精度は悪くない、ハイパーパラメータが結構あって大変。処理時間の都合上、あたりをつけてGrid Searchを行ったため、最適なパラメータは他にあったのではないかという気持ちもある。
②ナイーブベイズ:精度は悪くない、ランダムフォレスト よりも実装が楽な印象を受けた。今回は次元数が多いので多項分布が1番いい結果を出すと思ったが、なぜか正規分布が1番性能がよかったのが不思議。
③SVM:精度はよくなかった。何より処理時間が膨大なので少しやって諦めました。そもそも今回のデータは線形分離不可能と予想されるので、どうしてもSVMを用いるならば主成分分析を行ってからにすべきだった。(LSIやLDAは次元は下げられますが、線形分離可能な状態には持って行っていないと認識しています。間違ってたらどなたかご指摘いただけると幸いです。)
④KNN:SVMと同様。
ランダムフォレスト とナイーブベイズの2択ですが、性能がよかったランダムフォレスト を採用しました。
業務の流れに乗せるため、カテゴリXと予想されたデータは人の目で確認するようにしました。そのため誤まってカテゴリXと予測してしまう数も重視しています。
ランダムフォレスト、ナイーブベイズとどちらもRecallは大差なかったのですが、誤まってカテゴリXと予測してしまう数の面でランダムフォレスト 採用しました。
※sklearn使用
③Predicting(というか運用)
学習ずみのモジュールをAPIサーバで起動しておき、そこにJSON形式でデータを定期的に送って予測結果を返す仕組みにしました。
なお、sklearnでのシンプルなpredictを採用するのではなく、「logpredictの比が○○以上だとカテゴリXと予想(人の目を介す)」という流れにしました。
(グレーゾーンのものはカテゴリXと判断。)
この辺りは業務へのインパクトによって、また性能の改善とともに定期的に変更していく予定です。
一通り終えての感想
技術面でいえば、自然言語処理は前処理(言葉の標準化と言うのでしょうか?)がとても大変&肝のように思います。
そして1回の学習時間をどうにか短くしないと良い結果に巡り会うことができません。(ハイスペックな環境準備、次元削減処理、etc…)
次回の自然言語処理系の仕事に活かしたいと思います。
一方技術面以外での感想ですが、今回の仕事は結果が目に見えやすく、他の部署への影響もクリアでした。
機械学習系の仕事は、特に時系列分析などの場合、外部要因で業績が変化したのか、機械学習のおかげで変化したのか判断が難しい場合があると思います。
それに比べると今回は経費削減に直結しているため、機械学習の成果をある種定量的に測ることができたのではないかと思います。(100%のaccuracyではないので、そう簡単にはいかない!という声が聞こえてきそうですが。)
難しいことに挑戦するのも素敵ですが、他の部署の方、機械学習に興味のない方にその素晴らしさをわかりやすく伝えていくことも、我々エンジニアの仕事の一つのように感じます。
・
・
・
・
・
最後までご覧いただきありがとうございました!
弊社に興味のある方はぜひこちらまで。ご連絡お待ちしています。