F@N Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・nex8・viidleのエンジニア・技術ブログ

複数の機械学習モデルでCTR予測をしてみた。

こんにちは、弊社で2ヶ月間インターンとして働いているr_konumaです。
この記事では、2ヶ月間のインターンでやったことをまとめようと思います。

モデルを変えると精度は大きく変化するのか?

現状、弊社でCTR予測に使われている機械学習モデルがあるのですが、
他のモデルを使うことで精度が大きく変化するのかを検証しました。

2ヶ月間でやったこと

1. 大規模なデータベースへのアクセス

まず、CTR予測をするためのデータの取得します。
Treasure Dataにjupyter notebookから接続し、データをローカルの環境に引っ張ってきます。

ここで、初めてHive, Prestoに触れました。
(それぞれの言語に特徴があるので、処理したいクエリーに合わせて使い分ける必要がありました。)

CTR予測をするに当たって、クリックされたデータとクリックされなかったデータの割合には偏りがあったので、
そのまま訓練データとして扱うのには適していませんでした。

そこで、クリックされなかったデータをダウンサンプリングし、
訓練データとしました。

2. データ整形

引っ張ってきたままのデータでは学習することができないので、次にデータの整形をします。
ここは地道な作業になるので、もくもくとデータを綺麗にします。
具体的には、ダミー変数に置換したり、正規化を行ったりしました。

この段階では、データを綺麗にしつつ、
・どういうデータがあるのか
・あるデータはどういう形式で格納されているのか
・変数の値はどのようなものか, etc....
を見てたりしました。

3. データ中身を眺めてみる

せっかくデータを綺麗にしたので、少し中身を見てみました。
pythonのライブラリにはpandasがあるので、データを可視化が簡単にできました。
具体的には、デバイス比と報酬型広告かどうかの比率をみました。
f:id:fan_r_konuma:20180323162038p:plain:w400
デバイスはiPhoneが一番多く、また報酬型広告の方が多いことがわかりました。

さらに中身をみてみます。(ダウンサンプリングしているので現実の値とは異なります。)
f:id:fan_r_konuma:20180323120426p:plain:w400
クリック数が一番多いのはiPhoneですが、割合をみると全体的に大きな差があるようには感じられませんでした。
f:id:fan_r_konuma:20180323162252p:plain:w300
報酬型かどうかについては、報酬型である方がそうでない場合に比べて2倍違いました。
ただし、今回はダウンサンプリングをしているので実際は正確に2倍違う訳ではなく、
ここからわかることは、報酬型の方がクリックされる可能性があるということくらいです。

4. 複数のモデル作成と評価

評価方法には、ROC曲線とAUC値を用いました。
それぞれのモデルのROC曲線が違う色で示されており、右下にAUC値がまとめられています。
f:id:fan_r_konuma:20180323161109p:plain
ROC曲線を見てみるとわかるかと思うのですが、どれもさほど大きな変化はありませんでした。
しかし、下表のように、学習にかかる時間はロジスティック回帰が一番短かったので、今回試したモデルの中では、ロジスティック回帰が一番性能の良いモデルという結果となりました。

学習モデル 学習時間
ロジスティック回帰 58.0907 [sec]
LinearRegression 280.7520 [sec]
SGDRegressor 64.0105 [sec]
Ridge Regression 89.7170 [sec]
RandomForestRegressor 504.0530 [sec]

まとめ

  • 今回試したモデルの中ではロジスティック回帰が一番パフォーマンスのよいモデルである。
  • 機械学習モデルを変えるだけでは、精度に大きな影響はない。
  • 学習をさせる前に、モデルに対する特徴を理解して適切なモデルをいくつかに絞った方がよい。

機械学習モデルに関する理解をした上でライブラリを使わなければせっかくオープンにされている複雑なプログラムも有効に活用できない、というのが今回CTR予測をしてみた感想です。

ScalaのSくらいしか知らなかったモバイルアプリエンジニアですが、はじめてサーバーをつくりました

こんにちは、はじめましての方々ははじめまして。
3月5日〜3月9日まで1週間のインターンという形でお世話になりました。とやまです。

タイトルにもある通り今まで僕はAndroidのアプリ開発を中心に行ってきたいわゆるモバイルアプリエンジニアですが、Scalaへの興味とバックエンドエンジニアリングというまだ見ぬ領域について知りたくて今回のインターンに参加させていただきました。

その参加レポートを書かせていただきます。

1週間でやったこと

Play frameworkとFinagle(Finch)を使ったSSPの構築

1日目

 1日目はアドテク業界とアドテクサーバーの仕組みについて少し教えていただいた上で1週間のテーマを選定、その後ドワンゴ様が提供しているScala新卒研修テキストを頼りにScalaをおさらいしました。
聞いたところ、社内にはDSPに携わる方々は多い中、SSPに携わっている方は少ないこともあって、SSPを異なるフレームワークやライブラリを使ってベンチマークをする人がいなかったとのことです。
Scalaもあまり知らない上、アドテクについても全く知らなかったけど、せっかくインターンに参加するならわずかでも会社の役に立ちたいと思ってこのテーマをやらせていただきました。ちなみに、今回のSSPとDSPの実装仕様は以下のissueにあります。

github.com

2日目

 2日目は早速Play frameworkを使ってSSPを作ることからはじめました。
半日使ってそれっぽいものを作っていた感じはしていたのですが、僕が半日かけて作っていたものはHTTPサーバーではなく、ソケットサーバーだったことを1日のほぼ終わりどきに気づいて頑張って作り直しました。
最初は「やらかした」と思っていましたが、今となってはそれぞれの仕組みついて両方理解することができたので逆によかったなと感じます。

3日目

 3日目は、前日のPlayサーバー開発の続き、超簡易型DSPの開発を行いました。淡々とPlayとScalaを知るだけでなく、Scalaらしい関数型としての書き方も教えていただきました。特にfor式とyieldを使ったfor内包式のすばらしさにはとても感動しました。なるほど、mapやflatMapはこうやって実現できているのかと。改めてScalaの素晴らしさを思い知らされた1日でした。

4日目

 4日目はPlayサーバーのベンチマークテストとFinagleサーバーの開発を行いました。ベンチマークの結果についてはこの後書きます。
Finagleはそのまま使うのではなく、基本的にそのラッパライブラリを使うものだと聞いたのでFinchを使って実装を行いました。
はじめFinchを触って感じたことは、Playよりも応用がきいたライブラリなのか色々コードの自由度が高いなと感じました。JsonのパースもPlayの時のような最初からパーサーが付属されている感じではなく、circeなどのパーサーライブラリを使わなければならないことも自由度が高いと感じる部分でもありました。関数型に実装を近づけるならFinchだとということがよく分かりました。

5日目

 5日目はFinagleサーバーの実装をギリギリまでしました。本当はこのブログも最終日に書かなければいけなかったのですが、僕のムチャブリで後日提出する形にさせてもらいました。ありがとうございます。
褒められるほど綺麗なコードとまではいきませんでしたが、動くものが完成してベンチマークも行いました。最後はキリの良いところで終わることができたので良かったです。

1週間の成果

今回作成した2つのサーバーは僕のGithubにあげました。以下になります。

github.com

github.com

Finagleサーバーのベンチマークは後日酒井さんがプルリクエストを送っていただきました。

$ h2load -p http/1.1 -H Content-Type:application/json -d sample.json -c 100 -n 1000 http://localhost:9000/v1/ad
starting benchmark...
spawning thread #0: 100 total client(s). 1000 total requests
Application protocol: http/1.1
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 4.75s, 210.64 req/s, 30.79KB/s
requests: 1000 total, 1000 started, 1000 done, 859 succeeded, 141 failed, 0 errored, 0 timeout
status codes: 859 2xx, 0 3xx, 141 4xx, 0 5xx
traffic: 146.17KB (149681) total, 81.94KB (83911) headers (space savings 0.00%), 29.36KB (30065) data
                     min         max         mean         sd        +/- sd
time for request:    15.70ms       2.38s    363.72ms    654.46ms    89.90%
time for connect:      546us      4.49ms      2.50ms      1.02ms    60.00%
time to 1st byte:      2.25s       3.13s       2.43s    144.92ms    72.00%
req/s           :       2.11        3.10        2.75        0.14    88.00%

また、Playサーバーのベンチマークです。

$ h2load -p http/1.1 -H Content-Type:application/json -d sample.json -c 100 -n 1000 http://localhost:9000/
starting benchmark...
spawning thread #0: 100 total client(s). 1000 total requests
Application protocol: http/1.1
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 1.84s, 542.53 req/s, 203.50KB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 2 2xx, 998 3xx, 0 4xx, 0 5xx
traffic: 375.09KB (384096) total, 302.77KB (310038) headers (space savings 0.00%), 88B (88) data
                     min         max         mean         sd        +/- sd
time for request:     5.19ms    615.94ms    172.32ms    110.00ms    78.40%
time for connect:      364us      6.84ms      3.37ms      1.91ms    57.00%
time to 1st byte:   128.61ms    146.79ms    137.70ms     12.86ms   100.00%
req/s           :       5.43        6.84        5.81        0.38    83.00%

確実な結果ではありませんが、Finagleで実装したサーバーの方が早そうです。

最後に

1週間と短い間でしたが、メンターとしてテーマやScalaについて真摯に教えてくださった酒井さん、他社員のみなさんありがとうございました。
初めてのScala、初めてのサーバーサイドでかなり苦労しましたが、多くのことを学ぶことができました。
また、アドテク業界の奥の深さについても知ることができて、この業界の興味がとても湧きました。
今後は自分でもっとScalaやデファクトスタンダードであるCatsについて勉強してしていくことで関数型についての学習を深めていきたいと思います。

アドテクサービスのデータで時系列解析した話

初めまして、3/5から1週間インターンとしてお世話になりましたt_nakashimaです。
普段は大阪で音声信号処理や機械学習のお勉強をしたりgolangを書くバイトしたりトランペットを演奏したりしています。

  • インターンに参加した理由
  • 何をしたのか
    • データ分析・機械学習の基礎
    • 広告SDKの組込み
    • 実際のデータを用いた時系列解析
      • 自己相関
      • 偏自己相関
      • SARIMAモデル
    • 参考文献
  • 感想
    • 楽しかったところ
    • 苦労したところ
  • 謝辞
続きを読む

「そのアプリ、広告あるんですか?」2値分類

こんにちは。ピザが大好きg_takahashiです。イタリアではマフィアに襲われて5万円盗まれました。
前回はデータの可視化をしましたが、今回は2値分類に取り組みます。

目的

次の画像をご覧ください。
f:id:fan_g_takahashi:20180309092805p:plain
お分かりいただけただろうか。iTunesストアにあるアプリには広告があるかどうかが記載されていないです。GooglePlayStoreで販売されている Deemo には「広告を含む・アプリ内購入あり」という欄があるのに対して、iTunesストアで販売されている y_kawasaki さんのアプリにはそういった情報が書いていない。
これをデータを使って判定することが今回の目的です。
目標正解率は90%です。

手法

今回は広告あるなしの2値分類をしていきます。データ収集にはRubyを使い、解析にはPythonを使いました。機械学習にはscikit-learnとgensimを使いました。

データ概要

タイプ1: Google Playのカテゴリ毎のページをクロールして、レビュー部分のみスクレイプする。
タイプ2: タイプ1 + カテゴリの種類
取得したアプリ数は合計3403個。内訳は58%が広告を含む、42%が広告を含まない。

データ加工

形態素解析、TF-IDF、辞書作成、Bag-of-Wordsの順番で、自然言語から数値へ加工しました。一つのレビューが整数が入ったlistになるイメージです。

コーパスの作成は、現代語に強いmecab-ipadic-neologdを利用して、形態素解析を行い、TF-IDFの値が0.8以上のもののみを抜き出しました。メモリ容量の制約があり、制約を超えると分類機の学習の段階でカーネルがダウンしてしまうため、ダウンしないギリギリ数値がTF-IDFでは0.8でした。このときの特徴量は4800程です。

しかし、データの形式をlistからpandasのDataFrameに変更することでメモリ使用量が大幅にダウンすることが分かり、そもそも特徴量の制約はあまり考える必要がなかったということが分かりました。これは次回の改善点として、そのまま解析に移ります。

分類器と結果

各データ、各分類機での結果は以下のようになりました。

データタイプ1

auc 正解率(1SD)
ベイジアンフィルタ 0.65 70.0%(±0.14%)
ロジスティック回帰 0.60 67.9%(±0.37%)
SVM 0.58 68.5%(±0.17%)

データタイプ2

auc 正解率(1SD)
ベイジアンフィルタ 0.68 68.4%(±1.48%)
ロジスティック回帰 0.65 69.1%(±2.23%)
SVM 0.68 68.3%(±1.78%)

ベイジアンフィルタは多項分布を仮定したモデルの方が当てはまりが良かったです。5分割の交差検定は1分程度で終わります。
ロジスティック回帰は最適化にSGDを使い、正則化項にはL2を使いました。交差検定には1.5時間かかります。L1と比較したのですがL2のほうが当てはまりが良かったです。L1では従属変数のパラメータが0になることが多く、意味のない変数を排除できるといったことを考えると、TF-IDFの処理がその代わりを務めたためと考えられます。また、最適化の手法はsag,sagaとも比較してもSGDのほうが正解率、経過時間ともに良く、使いやすかったです。SVMでもL2を正則化項としました。


タイプ2のほうがaucが高く、カテゴリを特徴量として使うことは有意であるとわかります。各用語の説明はfan_k_arimuraさんのブログに素晴らしくまとまっています。
tech-blog.fancs.com

おまけ

自然言語処理を調べていたらWord2Vec、Doc2Vecに興味が湧き、2値分類に応用できないかを実験してみました。レビューの内容が広告の有無で変化するならdoc2vecが広告を持つレビューを判断してくれるのではないか、という発想です。作業の流れは、形態素解析 => Doc2Vec => K-Means です。
Doc2Vecによって、300次元にしたあとで、K-Meansによって2つのクラスターに分けます。PCAによって次元削減してグラフを作ると以下のようになります。

f:id:fan_g_takahashi:20180312113428p:plain

広告あり 広告なし
K-Meansによるクラスタ1
K-Meansによるクラスタ2 黄土色 水色

この図を見てみるとX軸の-0.3あたりでK-Meansによる分割がされていることがわかりますが、分割された集合の中には、広告ありなしに関わらず含まれているためこの手法は優れていないことがわかりました。実際に、正解率は54.0%でした。残念。


感想

・目標正解率には20%も足りなく、よろしくない結果になりました。力不足で残念です。

・Google Playストア用のスクレイプに大きく時間を掛けてしまいました。しかし、githubを覗いてみると、他の多数の方が既にそれらを作っていて、これを使えばよかったなと反省しています。車輪の再発明というやつをやってしまいました。

・pandasすごい。

・AdaBoostを使ってのモデルの改善、tf-idfの調整など多数の改善点が見つかり、調整を続けていきたいと思います。

・勉強することが多くエキサイティングな時間を過ごせました。プレゼンをする機会もいただいて、人として成長したと思います。ありがとうございました。今後はよりtech-drivenに頑張ります。