PyCon mini Osakaで異常検知システム構築の裏側について発表しました

現在仕事で作っている異常検知システムについてPyCon mini Osakaで登壇してきました。異常検知というマイナーなトピックですが、多くの人に聞いてもらえてよかったです。

はい、はてなのMackerelチームの中の人です。

機械学習の人からすると「なんだただの混合ガウス分布か」と思われるかもしれませんが、異常検知のシステムを実際に作ろうとすると考えることが色々あります。今回の発表では

  • ユーザーのどのような要望から異常検知機能を作るに至ったか
  • 異常検知とはそもそも何か、どういった問題設定か
  • 異常検知手法にはどういったものがあるか、どのようなメリット/デメリットがあるか
    • どの手法だとコストをなるべく抑えながらサービス要件を満たせるか
  • 実際のサービス提供するためにどのようなアーキテクチャ設計にしたのか
    • 高頻度かつ不定期な学習Jobに対応するためにAWS Batchを採用
    • 誤検知を少なくするためには、荒い粒度ではなく生データが重要。独自の時系列DBを構築

といった内容について話しました。

合わせて読みたい

最近の砂場活動その2: ECS/Fargate/AWS Batch編

ゴールデンウィークということもあり、前回に引き続き砂場活動をやりました。前回はCloudFormationを使い、ElastiCache/RDSを立てて、EC2の上でWebアプリを動かすところまでをやりました。

今回はECSやFargate、AWS Batchなどコンテナの恩恵をより受けられるようにしてみようと思います。大分モダンになってきた。

コンテナ化

ECSで動かすことを見越して、Webアプリをコンテナ化しました。といってもDockerfileを書く程度なので、大したことはない。中間ファイルに吐いていたような状態はRDS/ElastiCache/S3などの外部のDBやストレージに持たせます。作ったコンテナはあとでECSで使えるようにECRのリポジトリにpushしておきます。

ECSで動かす on EC2

コンテナ化したので、ECSの上で動かします。ECSはあまり馴染がないので、概念を少し整理します。ECSはコントロールプレーンというものの一つで、コンテナを管理するものです。コンテナの管理とは

  • どこでコンテナを動かすか
  • コンテナの生死の判定
  • デプロイ時のコンテナの配置戦略

などのことを指します。最近Preview版になったEKS(Amazon Elastic Container Service for Kubernetes)もコントロールプレーンの一つです。また、実際のコンテナが稼動する場所のことをデータプレーンと呼びます。データプレーンの具体例としては、EC2やFargateがあります。コントロールプレーンとデータプレーンの組み合わせがあるので

  • ECS on EC2
  • ECS on Fargate
  • EKS on EC2
  • EKS on Fargate
    • これはまだっぽい?

が可能な選択肢のようです(xxx on yyyの呼び方は適切か分からない...)。一気にFargateまで行くとよく分からないことになりそうだったので、データプレーンに馴染みのあるECS on EC2を選択してひとまず進めます。

ECSを構成する概念

大きく分けると3つあります。

  • クラスタ
  • ジョブ定義
  • サービス

ジョブ定義ではどのイメージを使うか、cpu/memoryの制限、コンテナのEntryPointの設定などをします。サービスは指定されたジョブ定義に対して、どのようなネットワーク構成(セキュリティグループやサブネット)で動かすか、走らせるジョブの個数、(必要であれば)ロードバランサーの設定などをするところです。今回はALBをロードバランサーとして選びました。

ECS用のEC2の準備

ECSのクラスタにEC2インスタンスをjoinさせるには準備が必要です。ECSに最適化されたAMIがあるので、それを使うことにしました。リージョン毎に用意されていることに注意。

また、立ち上げたインスタンスがどのECSクラスタに所属するかをユーザーデータに記述する必要があります。CloudFormationだと以下のような感じ。

          UserData:
            Fn::Base64:
              !Sub |
                #!/bin/bash
                echo ECS_CLUSTER=my-cluster >> /etc/ecs/ecs.config

ECSのクラスタを作ると、このインスタンスが使える状態として見えるようになります。

タスク定義/サービス定義

タスク定義にはdocker-compose.ymlを書くようなイメージです。サンプルを真似すれば大体書ける。クラスタの準備ができあがっていれば、個々のタスクを走らせることができます。ホストのEC2にsshしてdocker statsで様子を見ていました。

サービス定義は単発で終わるようなタスクではなく、ずっといくつかのコンテナを走らせ続けたいようなWebアプリなどに使う定義です(理解がちょっと怪しい)。あるコンテナが不調で死んでしまった場合、全体でいくつのコンテナが立ち上がっているのが望ましい状態なのか、deploy中は最大何個までコンテナが立ち上がっているのを許すのか、ロードバランサーのターゲットグループとして指定するものはどれかといったことを書いていきます。CloudFormationで具体的に書くとこんな感じです。

      ServerService:
        Type: AWS::ECS::Service
        Properties:
          Cluster: !Ref ECSCluster
          DeploymentConfiguration:
            MaximumPercent: 200
            MinimumHealthyPercent: 100
          DesiredCount: 1
          TaskDefinition: !Ref ServerTaskDefinition
          LoadBalancers:
            - TargetGroupArn: 
                Fn::ImportValue:
                  !Sub "${ALBStackName}:DefaultTargetGroup"
              ContainerPort: 7777
              ContainerName: go-active-learning-container
          NetworkConfiguration:
            AwsvpcConfiguration:
              AssignPublicIp: ENABLED
              SecurityGroups:
                - Fn::ImportValue:
                    !Sub "${VPCStackName}:WebSecurity7777Group"
                - Fn::ImportValue:
                    !Sub "${VPCStackName}:EC2SecurityGroup"
              Subnets:
                - Fn::ImportValue:
                    !Sub "${VPCStackName}:PublicSubnetAZa"

サービスを起動して、ALBのDNS名にアクセスしてページが見れていればokです。サービス部分のデバッグは最初少し自分には難しく、「ALBのターゲットグループにipが上がってこないから、サービスのネットワーク設定がいけないのか??」といった具合に試行錯誤していました。大分勘でやっていました。

オートスケール

アプリケーションサーバー(コンテナ)の台数を増減させて遊びたかったので、オートスケールもできるようにしようと思いました。少し調べてみると、ECSのオートスケールは

  • ECSクラスタのオートスケール
    • データプレーンであるEC2の台数自体の増減
    • 台数は変化させずにEC2のスペックをもっと強力なものにすることもできる
  • サービスのオートスケール
    • 立ち上げるコンテナの数の増減

があることが分かりました。

「2つの変数を自分でいい感じにチューニングするの絶対ダルいな…自分が欲しいのは可変にできるコンピューティングリソースなんだけどな…」と思ったところでFargateの存在を思い出しました。FargateならばEC2の管理は自分でやる必要がないので、自分で管理する変数が一つ減りますね!

ECSで動かす on Fargate

Fargateまでやるつもりはなかったんですが、EC2捨てたいという気持ちが高まったので、Fargate対応していきます。Fargateは東京リージョンにまだきていなかったので、VPCなどこれまでの設定をバージニアで作りなおしました。手で作りなおそうとすると結構面倒ですが、CloudFormationで構成を書いていたので、同じ構成がすぐに別リージョンで立ち上がってお手軽でした。

Fargateにするといっても、元々ECSで動く状態だったので、それほどやることは多くありません。ひとまず以下の設定を追加しました。

  • LaunchType: FARGATE
  • NetworkMode: awsvpc

Fargateになると完全にEC2の存在が見えなくなるので、ホストにsshしてdocker logsで様子を見るといったことができなくなります。Data Volumeも指定できないので、ログはCloudwatch Logsに吐くようにします。CloudFormationだとこんな感じ。

              LogConfiguration:
                LogDriver: awslogs
                Options:
                  awslogs-group: !Ref LogGroup
                  awslogs-region: !Sub "${AWS::Region}"
                  awslogs-stream-prefix: ecs

AWSのコンソール画面からログを見るのはつらいので、awslogsを使ってtail -fっぽく使うのがいいみたいですね。

慣れない場合はいきなりFargate上で動かすのではなく、デバッグしやすいEC2上で動くことを確認してからFargateにするのがいいかもしれません。自分がいきなりFargateにしていたら動作確認がちょっとしんどくなっていた可能性が高い。

Fargateの準備が整ったので、負荷をかけて遊びます。abコマンドのようなベンチマークツールでリクエストを飛ばしまくります。そうすると、ALBのターゲットグループでhealthyだったステータスがunhealthyになったり、drainingになったりします(つまり、過負荷でコンテナが死んだ)。しばらくすると新しいコンテナが立ち上がって、ALBのターゲットグループにhealthyの状態で登録されていきます。あまりにかわいいので、何回も殺してしまった…。下のスクリーンショットが死んだり新しいのが生えてきている様子です。

f:id:syou6162:20180506200529p:plain

手元でdocker runする感覚でコンテナがすぐ立ち上がるかと思ったけどそういうわけではなく、多少待たされます。docker pullの分時間がかかったりするのかな。

必要なコンテナの数やデプロイ時の設定はサービスに書いていきます。DesiredCountを適当に増やすと立ち上がるコンテナも増えます。余裕ができたらBlue-Green Deploymentみたいなこともやってみたいと思います。

      ServerService:
        Type: AWS::ECS::Service
        Properties:
          Cluster: !Ref ECSCluster
          DeploymentConfiguration:
            MaximumPercent: 200
            MinimumHealthyPercent: 100
          DesiredCount: 1
          LaunchType: FARGATE

Batch処理

私が動かしているのは機械学習を利用したWebアプリケーションなので、推薦の結果をWeb UIに表示させるだけではなく、学習のプロセスなどが必要です。学習はメモリもCPUもある程度必要なバッチ処理なので、CloudWatch Eventから定期的にAWS Batchを起動させるようにします。AWS Batchは仕事で使っているのでさすがにスムーズに行きました。AWS Batchは以下の3つから構成されています。

  • コンピューティング環境
    • VPCやサブネット、必要なVCpu数などを管理。リソースが足りなければ追加でEC2インスタンスを立ち上げ、不要になったら落としてくれる
  • ジョブキュー
    • キューのくせにどのコンピューティング環境にどういう優先順位でジョブを投入するかなどが指定できる
    • キューに状態が管理されているので、EC2が立ち上がりまくってAWS破産するといった心配がない
  • ジョブ定義
    • ECSのタスク定義と大体似たようなものです。最近、タイムアウトのサポートもされて安心感が増した

察しのいい方は分かるかもしれませんが、AWS Batchの裏側ではECSのクラスタやサービス、ジョブ定義が勝手に作られます。

学習ジョブや推薦結果の計算のジョブを定期的に投下していきたいのですが、そういったcron的なことまではAWS Batchは面倒を見てくれません。Cloudwatch Event経由でジョブをkickさせます。CloudFormationを使っていると、Cloudwatch Eventから直接AWS Batchのジョブをkickすることはできないので、AWS BatchのジョブをkickするLambdaをkickさせるようにしました。コンソールからは直接指定できるようです。

AWS Batchのモニタリングについてはジョブキューの各statusに存在しているジョブの個数を見るプラグインや、ECSのモニタリングをするプラグインがあるので是非ご利用ください(宣伝)。

困ったこと

  • Fargate、早く東京リージョンにきて!
  • そもそもcloudwatch logsにログが出ていないような場合にFargateでのよいデバッグ方法が分からない
  • 個人の遊びとしてはまあまあお金がかかる。勉強代と思って割切る
    • RDS/ElastiCache/ALB/Fargate、今回は登場していないけどクロールしてきたデータを貯めるAmazon Elasticsearch Serviceもいる…
  • モニタリングの仕方がこれまでと結構変わってくるのでベストプラックティスを確立していく必要がある
    • 例: mackerelのcheck-logでログ監視していたものが同じようにはできなくなる
  • ちょっとしたWebアプリを作っているつもりだったけど、CloudFormationのymlが合わせると1000行近く & スタックが9つもできていてどうしてこんなことに…

f:id:syou6162:20180506195347p:plain

まとめ

今年の自分の目標の一つに自分の砂場を作るというのをかかげていましたが、ひとまずそれなりの砂場はできました。いきなり完成系を目指すのではなく、incrementalに改善していけたことはよかったところとして挙げられるかなと思います。具体的には以下の通りに進めていきました。

  • 学習事例やキャッシュをファイルで保持するcli機械学習アプリケーションを書く
    • 去年までにやっていた
  • cliアプリのまま、学習事例はpostgresに保存、キャッシュはredisに保持するように
  • cliアプリをwebアプリとしても使えるように
  • ローカルで立ち上げていたwebアプリを1台のEC2上で動くように
    • 最初は手動でやっていたprovisioningをansibleでやるように
  • 同一のEC2上で動いていたpostgres/redisをRDS/ElastiCacheを利用するように
  • EC2で動かしていたwebアプリをECS on EC2で動かすように
  • ロードバランサーとしてALBを使うように
  • EC2の管理が不要となるようにECS on Fargateで動かすように
  • 重たい処理はAWS Batchで動かすように

亀のような進捗でしたが、自分の理解力だと一気に飛ばしてやるのはしんどかったと思います。一個一個確実に進めていったのがよかった。2018年の上期で自分が遊ぶ砂場ができたので、下期ではこの砂場を使ってインフラ/ミドルウェアの理解をさらに進められるように遊んでいこうと思います。

最近の砂場活動その1: CloudFormation/RDS/ElastiCache編

機械学習っぽいエントリばっかり書いているけど、普段はWebアプリケーションエンジニアをやっている。そちらの方面の勉強もちょびちょび進めていく。圧倒的に知識と経験が足りなくて、危機感しかない。

アプリケーションエンジニアがインフラオーナーシップを高めていく機運が社内で高まっていたり、ミドルウェアやインフラの知識を高めたかったり、コンテナ関連の手頃な練習台が欲しいといったあたりがモチベーション。直近の業務での必要性も高まっているので、正直あと半年早くやっておけばよかったという気持ちがある…。とにかく気軽に変更してぶっ壊して障害起こして復帰させられるような砂場を作るのが目標。砂場ができればそこで遊べる。

お題

wordpressアプリなどがよくお手軽事例で取り上げられるが、あれこれやるのに愛着がないアプリを触っていても面白くない。三日で飽きる。ちょっと前に自分が作っていたアプリがあったので、それを題材にする。

cliの機械学習アプリで、教師データはテキストファイル、キャッシュはバイナリファイルに読み書きという素朴すぎる作りになっていた。この時はgoでcliアプリを練習をするのが目的だったので何も間違ってはいないのだが。これをひとまず

  • cliアプリをwebアプリ化
  • テキストファイルで管理していた教師データをPostgresで管理
  • バイナリファイルで管理していたキャッシュをRedisで管理

するようにした。砂場活動の本題とは直接関係ないものではあるが、少しずつ書き換えている。ORMやWebフレームワークのようなものは使わず、なるべくgoの標準ライブラリの範囲で収まるようにシンプルになるようにしてる。

CloudFormation

あれこれサーバーの設定を(後述する)ansibleでいじって壊れたらEC2のサーバーを新しく立てなおして、というのを手でやっていた。いつどういう目的で作ったのかよく分からないVPCやセキュテイィグループがとっちらかっていて治安が悪かった…。何度もやっているとさすがにAWS Consoleをぽちぽちやっているのもダルくなってきたので、CloudFormationをやってみることにした。仕事でもCloudFormationを触るので、ymlファイルを見てあれこれ分かるようになると助かる。

第一段階

最初は素朴に1つのEC2インスタンスに

  • アプリサーバー
  • バッチサーバー
  • Nginx
  • Redis
  • Postgres

が同居している構成にした。素朴ではあるものの、CloudFormation以前にAWSでのネットワークやセキュティグループへの理解が不足していたので、本で勉強した。

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

かなり分かりやすかったが、コンソールの画面で設定していく形式だったので、それをCloudFormationのコード(yml)に落としていく必要がある。だるいなーと思っていたが、CloudFormation化したものをgithubに上げている先人がいたので助かりまくった!!!

両方とも同僚で同じチームだし、Mackerelチームのアウトプット最高ですね、負けられない。

a-knowさんのコードは各セクション毎にPull Requestが分かれていて分かりやすかったし、shibayuさんのエントリはクロススタックをどう扱えばいいかが書かれていて助かった。これらを参考にMakefileにコマンドを書いておいて、VPCおよびセキュリティグループのスタック、EC2インスタンスのスタックを作れるようにした。makeは最高のタスクランナーです。

make create-vpc
make create-ec2

一台のEC2インスタンスで済ませるために、アプリケーションサーバーとDBサーバーが同一のpublic subnetにいる。セキュリティ的によろしくない作りですね。

第二段階(今ココ)

1つのEC2インスタンスに全部入りのCloudFormationだと、例えばアプリサーバーの台数を増やそうと思ったときにDBやCacheも別々になってしまうのでダメ。次の構成では

  • アプリサーバー兼バッチサーバー兼Nginxが同居しているEC2サーバー
  • ElastiCache(Redis)
  • RDS(Postgres)

を目指した。今後、コンテナ化していくことを考えると、状態をRDSなど外に逃がせられるようにしたのでよかった(ref: The Twelve-Factor App (日本語訳))。構成変化に合わせ、subnetの構成も変えていく。

  • private subnetにRDSとElastiCscheを配置
  • public subnetにEC2を配置。このサブネットからのみRDS/ElastiCscheが接続できるセキュリティグループを作る
    • 80版のportを公開、sshは自宅のIPからのみに制限

RDSなどのマネージドサービスを使わない場合、セキュリティ関係のパッチを当てるためにNATサーバーが必要だった気がする。

第三段階(これから)

EC2で動いているアプリサーバーをECSで動くようにする。ひとまずコンテナ化する必要がある。サーバーと別個でデーモン的に動いていたプロセスをコンテナでどう扱うのがいいかよく分かってない…。別個でコンテナ立ち上げるのが定石なんだろうなと思いつつも、本当にちょっとしたものなので、そこでお金ならべくケチりたい。

コンテナ化したらECRにリポジトリを作って、イメージをpush。ここはやるだけ。

セキュリティグループやsubnetはEC2でやってたときのを使いまわせそう。オートスケールはとりあえず必要ないけど、いくつかコンテナ立ち上げてロードバランサでいい感じにやる方法を調べる。

Webサーバ以外にもバッチジョブ(クローラー)を走らせたいけど、その方法も調べる必要がある。常時走らせるわけではないので、この辺調べる必要がある。

コンテナ動かすのはEC2じゃなくてFargateで試してもいいけど、tokyo regionにまだきてないからひとまずいいか。EC2はsshログインして最悪色々チェックできるけど、Fargateになるとそうも行かなくなりそうなので、うまく行かないときのデバッグ方法も模索して行きたい。ログをどこに置くのかも考える必要がある。

Ansible

CloudFormationでインスタンスが立ったら、インスタンスの設定をAnsibleでやっていく。

  • Webアプリをデーモン化するためにsupervisorを入れる
  • Webアプリはgoで書いているので、シングルバイナリに固めてscpでサーバーに送る手抜きdeploy
    • リクエスト送るのは大体自分なので送られていたリクエストがあっても叩き落とす
    • graceful restartとかはあとで考える…
  • ログ取り目的でnginxを入れる
  • モニタリング用途にmackerel-agentと入れる
    • accesslog/check_disk/check_uptimeあたりをプラグインで入れておく
    • postgres/redisとの疎通もcheck監視しておく
    • APIKEYはどうやって渡すのがいいかな…
      • 暗号化するのかなー

docker化して、ECSで動かすようになったらansible捨てられそう。捨てたい。

困ったこと

  • データベースはアプリと切り離したり、RDSなどマネージドサービスを使うことでいい感じになったものの、自分しかアクセスしない割にお金がかかるようになった
  • 手元とciと本番環境でそれぞれきちんとうまく動くようにするのがちょっと面倒
    • 手元はmacのpostgres、ciサーバでの設定、本番はRDSなど
  • RDSをcloudformationで作るとちょっとしたことで作り直されてしまって、デバッグに時間がかかってしまった
  • 可視化目的でCloudFormationのデザイナーを使っていたけど、クロススタックを使っている場合の可視化が分からない
  • アクセスキーとかその辺の管理
  • アクセスするのが自分とcronだけなので、緊張感がない

今後やる予定のこと

  • EC2で動いているアプリサーバーをECSに乗せる
    • 複数台配置してロードバランサー置く
  • RDSの複数台構成/Multi-AZ化、レプリケーション、Failover、バックアップ、リストアなどを試す
    • ミドルウェア関係で障害を意図的に起こして、そこからの復帰方法を学ぶ
  • Nginxの前にALBを配置
    • ヘルスチェック
  • public ipにRoute 53で名前付ける
  • パフォーマンスチェック。ボトルネックの把握

NLP(言語処理学会)2018で面白かった論文メモ

あれこれ雑にメモっていて、きちんとしてから外に出そうと思っていましたが、下書きのままお蔵入りしそうだった。出せるところで出す...!すでに開催から一ヶ月も経ってしまった。

A1-1: ニューラルヘッドライン生成における誤生成問題の改善

  • ○清野舜 (東北大), 高瀬翔, 鈴木潤 (NTT), 岡崎直観 (東工大), 乾健太郎 (東北大/理研AIP), 永田昌明 (NTT)
  • 論文PDF: http://anlp.jp/proceedings/annual_meeting/2018/pdf_dir/A1-1.pdf
  • ニューラルヘッドライン生成はAttention付きのEncoder-Decoderモデルが使われることが多いが、問題点もある
    • 繰り返し同じ単語を生成する、無関係な単語を生成してしまう、固有表現など重要な単語が欠損するなど
  • それぞれ個別の問題に対応するような既存研究はあるが、本研究ではこれらを統一的に扱える枠組みを提案
  • 出力側の単語に対応する入力側の単語を予測する
    • 正解のアライメント(入力単語と出力単語の対応)は必要ない
  • 全体最適化は文単位でのロスを最適化
    • 通常は単語単位でのクロスエントロピーを最適化していく
    • 文単位のロス: 入力系列と出力系列のMSEの和
  • はてなのトピック機能でもヘッドライン生成に近いことを行なっているので、興味深く発表を聞かせてもらいました

B2-4: ニューラルネットを用いた多方言の翻訳と類型分析

  • ○阿部香央莉, 松林優一郎 (東北大), 岡崎直観 (東工大), 乾健太郎 (東北大/理研AIP)
  • 論文PDF: http://anlp.jp/proceedings/annual_meeting/2018/pdf_dir/B2-4.pdf
  • 対話アプリケーションの盛り上がりとともに、方言で入力されるケースも増えてきているが、方言で入力された場合には解析精度がどうしても落ちてしまう
  • 方言から共通語への翻訳ができればよいが、いくつか問題点がある
    • 方言から共通語への十分な対訳コーパスを用意するコストが高い
    • 小規模なコーパスではSMTのほうが精度がよいと言われているが、比較検証が行なわれていない
  • 方言毎にモデルを作るとデータ数が足りないため、複数方言のコーパスをまとめて学習する
  • 国語研が発行している全国の方言の対話の書き起しデータベースを使用
  • 翻訳している地域を区別するために、先頭のトークンに地域トークンを付加する
    • 地域トークンに対応する地域ベクトルができる
  • 方言から共通語への翻訳の性能向上だけでなく、地域ベクトルの可視化(図4)が方言研究としても面白かったです
    • 都市部から同心円状に広がっている?

D4-3: サンプリング戦略に基づく単語ベクトルの意味成分とスタイル成分の分離

  • ○赤間怜奈 (東北大), 横井祥 (東北大/理研AIP), 渡邉研斗 (東北大), 小林颯介 (PFN), 田然 (東北大), 乾健太郎 (東北大/理研AIP)
  • 論文PDF: http://anlp.jp/proceedings/annual_meeting/2018/pdf_dir/D4-3.pdf
  • 対話などの文生成タスクでは、何を(意味内容)どう表現するか(スタイル)が重要である
  • スタイルは多様な特徴があり、教師あり学習では網羅することが難しい
  • 分布仮説と発話内はスタイルが一貫しているという仮説を一つのモデルで表現することで、単語埋め込みベクトルを意味成分とスタイル成分に明示的に分けて学習する
  • 意味とスタイルで近傍の定義を距離を変える
    • ウィンドウ幅が一定の距離以内では、意味ベクトルとスタイルベクトルを更新
    • 一定距離以上ではスタイルベクトルを更新
  • 仮定はとてもシンプルですが、教師なしでうまく意味成分とスタイル成分を分けられているように見えました
  • 質疑では「意味ベクトルとスタイルベクトルの相互情報量が0になるような制約を目的関数に入れるとよいのでは?」という指摘も面白いなと感じました

深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)

深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)

社内でKaggleの布教活動をやっている話

最近、社内勉強会で機械学習についてエンジニアに説明する機会があり、その際にKaggleについても説明しました。一方で

という話もあり、(特にデータサイエンティスト以外の職種の人が)Kaggleをやる意義/メリットについてまとめてみました。ガッと勢いで書いたので、項目に結構被りがあります。なお、書いている本人はKaggleほぼ初心者であまり説得力がないです。Kaggle Masterの人がもっといいエントリを書いてくれるのを期待しています、議論の叩き台エントリです!!

Kaggleをやる意義/メリット

様々なデータセットを触ることができる

Kaggleには開催中/終了したものを含め様々なコンペがあり、データセットも多種多様です。データサイズがかなり大きいものもあります。

「新しい機械学習機能ができるか社内で検討したいが、手元には手頃なデータがまだない(あっても少量)」といったときに、Kaggleのデータセットを使ってあれこれ試してみることができます。例えば、はてなであれば画像関連のタスクはまだ経験がほとんどなくアノテーションデータも特に存在しませんが、画像を使うとどういう機能ができそうか/どのくらいの精度でできるのか、といったことを試せるといった具合です。どのくらいできるかも分からない状態でいきなり仕事でやるのはなかなかリスクが高いので、Kaggleで練習できることは仕事でのリスクを下げることにも役に立つと思います。最近見かけた参考になりそうなタスク、面白そうなタスクを少し紹介してみます。

  • Toxic Comment Classification Challenge | Kaggle
    • 嫌がらせのようなコメントを識別する典型的なNLPの練習になりそうなタスク
    • CGMを運用しているサービスではどこでも悩むことだと思うので、Kaggleの人がどういったアプローチで取り組んでいるのか参考になることが多そう
    • 単純に2値分類ではなく、toxic, severe_toxic, obscene, threat, insult, identity_hateといった複数のラベルを予測する問題になっていて、タスク設計という観点でも参考になる
  • Mercari Price Suggestion Challenge | Kaggle
    • メルカリ主催の価格推定タスク。商品名、カテゴリ、descriptionなどが与えられた状態で価格を推定する典型的な回帰のタスク
    • 特徴としては学習と予測もサーバー側でやる制約。サーバーのスペックや計算時間に制約があるというのは製品開発ではよくある話なので、そういった制約がある状況でどういうアプローチを取っているかは参考になりそうですね
    • 一位の人のコード: pjankiewicz/mercari-solution: Mercari 1st place solution (kaggle)
  • Text Normalization Challenge - English Language | Kaggle
    • 口語表現などを含むテキストが与えられたときに正規化するNLP系のタスク
    • CGMのテキストを入力として何かをやろうとするとき、各社正規化を頑張ることが多いと思う。その必要が大きい場合、こういったコンペを覗いてみると発見があるかも
  • Planet: Understanding the Amazon from Space | Kaggle
    • 航空写真が与えられ、どの地域が違法に密林を伐採されている区域かなどを当てるタスク
    • 素人目から見ると違いが全然分からんな...という気持ちになるし、画像タスクでどう取り組んでいけばよいかを考えるよい機会になりそう
    • データも圧縮済みで数十GBあるので、(半強制的に)データハンドリングをどうするかなどの勉強にもなる
    • Kaggle まとめ: Planet, Understanding the Amazon from Space - Qiita

自分もCTR予測のタスクの練習がしたかったので、Kaggleで練習した話を先日書きました。

こういった練習を繰り返すことで

  • どういった特徴量をDBやログに吐いておくと機械学習的観点でよいか
  • 有効な特徴量を継続的に保存していくためのデータ基盤はどうなっているとよいか

といったことも少し検討が付いてきたというのはよい発見でした。

kernelでデータ分析のやり方を知れる

Kaggleにはkernelというページがあり、他の人がどういうモデルや特徴量を作ったか、その特徴量を作るに至るにはどういった分析を行なったかを見れることができます。こういった試行錯誤は探索的データ分析(EDA: Exploratory data analysis)と呼ばれ、参加者は大体やっているのではないかと思います。

機械学習をproductionで使う際には「この特徴量や前処理はこういった側面から必要で入れている」といった説明をチームメイトに行なう必要がある場面が度々あると思います*1。こういった際にどういう分析や可視化を行なうと効果的なのか、事例をたくさん見ることができるというのは大きなメリットであると言えると思います。

場数を踏める

データ分析/機械学習案件を受けるとしましょう。案件に対して正確に早く動けるかどうかは機械学習に関する教科書的/理論的な知識も重要ですが、やはり踏んだ場数は成功に欠かせない要素だと思います。場数を踏まないとなかなか分からない具体例としては、例えば時系列データの学習データ(train)と検証用データ(dev)の分割の話があると思います。経験が少ないと意図せずleakしてしまい、テストデータでうまくいかないといったことがよく起きます。自分もついこないだやってしまいました...。

社内でたくさん機械学習プロジェクトが走っているならばそういった経験が積めるのでよいですが、そうでない場合にはなかなか経験を積むことが難しいです。Kaggleをやると、ある程度リアルな(多少ノイズを含むデータだったり、巨大なデータだったり)データをいじることになるので、場数を踏むことができます。仕事でぽんぽん失敗するのはなかなか辛いですが、Kaggleだと失敗してもあまり痛手ではありません。そのため、どんどん場数を踏むことができ、結果として仕事の案件でのスピードアップや正確性の向上に寄与することができるのではないかと思います。

様々な評価指標について知ることができる

一つの分野のタスクばかりやっていると、評価指標も同じものを使ってしまいがちです。Kaggleのコンペでは評価指標も様々なものがあります。これまで自分が知らなかった評価指標を使ってみると

  • どのタスクにそもそもどういった指標が使われうるのか
  • どういうときにどういう指標が向いているのか

といった知見が溜まってきます。その結果、数ある評価指標の中から適切な指標を選べるようになり、仕事にも役に立ってくるのではないかと思います。不適切な指標に対してチューニングを頑張っても、productionに出したときに泣くことになります...。

実験のログや再現性について気を使えるようになる

Kaggleのコンペは2-3ヶ月の長期戦です。あるとき自分がsubmitした2週間前の結果がよかったので、手元で再現したいということはよくあります。実験設定について何も記録していないと、よかった結果がどういう条件で出てきたのかがあっという間に分からなくなります。Kaggleをやっていると、どういう設定で実験を行なったのか(ハイパラ/データ数やデータ分割方法/特徴量の種類など)ログを取る重要性を身を持って学習できます。また、実験設定だけあっても結果が再現できないと意味がないので、実験の再現性についても気を使えるようになります。

実験設定の記録や再現性は機械学習のサービス開発でも難しい部分であるというのは以前エントリに書きましたが、こういった点について自然と対応できるようになってくるというのはKaggleのよいところであると思います。

自分の実力が客観的に分かる

Kaggleのコンペはよくも悪くも客観的な数値で自分の成績が分かります。社内で一人で機械学習を進めている状況だと「自分のやっているアプローチは正しいのだろうか。この方法で出ている性能は客観的に見ると高いんだろうか/低いんだろうか」と悩むことも少なくないと思います。Kaggleでは、自分の提出したもののスコアがよければ自信になりますし、思ったよりも悪ければKernelやディスカッションを見てどういう改善ができそうか探ることができます。

楽しい

精度が上がった下がったで一喜一憂するのは、まあ結構楽しいです。ISUCONなどで地道にスコアを改善するコンペが好きな人は趣味としても楽しめるし、仕事に役に立つ要素もあるので一石二鳥では?!と思います。

賞金がもらえる

まだ賞金がもらえるようなランクに行ったことがないので、省略します!!!もらったことがある人が書いてくれ!!!

まとめ

Kaggleの上位に入るためには、最近だとアンサンブルやハイパラチューニングが必須になってきています。しかし、そういったインクリメンタルな改善の要素以外にも、これまで書いたようにKaggleには自分の実力を正しく計測したり、力を向上させられる要素がたくさんあります。データサイエンティストに限らず、色んな人がKaggleに挑戦してもらえると自分もうれしいです。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

*1:不必要な特徴量はメモリや計算時間も無駄になるだけですし、その特徴量を取るために複雑性も増してしまいます。なるべく取り除きたい

KaggleのCTR予測コンペで上位10%に入るまでの試行錯誤

週末KagglerとしてavazuのCTR予測コンペに参加しました。Kaggleは機械学習版のISUCONだと思ってもらえばよいです。コンペ自体は終わっているので、late submiteであまり意味はないかもしれません、練習です。leaderboard上で上位10%以内に行けたので、そこまでの試行錯誤をメモしておきます。謎ノウハウ(?)を持っているガチ勢じゃないと上位に行けないものかと思っていましたが、基本に忠実にやればこれくらいの順位(上位7.6%)に行けましたし、他の人の工夫を垣間見えるという意味でも現場の機械学習やり始めたエンジニアにお薦めできそうでした。

参加の動機

そもそもなぜ今のタイミングでkaggleに取り組むのか、参加への動機を書いておきます。

  • 手掛けたことのあるタスクの幅を広げたい
    • nlpだと土地勘があって、うまくいかなくても何となく対処法が分かることが多いけど*1、そうでないドメインでもある程度できるように
    • 今回はCTR予測だけど、他のコンペもぼちぼちやっていきたいですね
  • 大きめのデータセットでの学習に慣れる
    • nlpだと教師データが数万文でやることが多いし*2、社内タスクでも人手でアノテーションするとそのくらいのオーダーのデータしか扱わないことが多い
    • 数千万規模のデータで試行錯誤(特徴量チューニング/ハイパラチューニング)の肌感覚を掴む
  • 使ったことのない手法やツールを試してみる
    • 仕事だと期日もあるし、使い慣れてる手法やツールを選びがち
  • 対外的な自分の実力を客観的に知る
    • 社内だと同じタスクをやる人が多くて数人、一人ということもありがちなので、自分の作った分類器がどれくらいいいのか客観的に知る機会が実はあまりない
    • 同じタスクに1000人規模でチャレンジして、その過程や結果がディスカッションやコードの形として残っているので、それらを自分の血肉としたい
    • もちろん、プロダクションで入れるときは目視して十分な精度あるかは確認しますが…

目標感: 頑張りすぎずに上位10%以内に入る

コンペなので順位が出ますが、あまり凝った方法を使わないで上位10%以内に入ることを目標としました。理由としては以下の通りです。

  • 週末くらいしかまとまった時間をそもそも取りにくい
  • リッチな計算機環境はない
    • 手元のmacbookのみ(メモリは使えて10GB程度)で戦うので、あまり凝ったチューニングはできない
    • 一時的にEC2を借りる手もあるけど、それは開催中のコンペに参加する時でいいかな…
  • 上位勢はそもそもプロダクションでは使えなさそうな(kaggleでしか使えなさそうな)特徴量を使っているので、あまり深追いしすぎても仕事に生かせる部分は減っていく
    • 例: ある日の午前3時のCTR予測をしたいのに、なぜかその日全体の閲覧数/クリック数が特徴量に入ってる(3時台のおすすめアイテムを計算したい時には使えない特徴量)
  • 終わってるコンペなので、賞金は手に入らない

残りは取り組んだ方法について時系列で書いていきます。以下、スコアはLeaderboardのPrivate Scoreのことを意味しています。

試行錯誤

AthenaとRedashによる探索的データ解析

やみくもに機械学習やっていくのは筋悪だなと思い、まず探索的データ解析(EDA: Exploratory Data Analysis)をすることにしました。コンペによってはカーネルでEDAした結果を置いてくれている人もいるので、それを参考にするのもよいと思いますが、練習なので今回は自分でやります。時系列要素があるので、なるべくシャッフルやサンプリングしないで解析したいなと思うものの、学習データが4000万件以上あるので手元で解析するのはなかなか大変です。そこで、今回はAthenaとRedashを使ってデータ解析をすることにしました。

学習用データを解凍してcsvにして、S3に置きます。圧縮済みのものを置いてもAthenaは動きますが、SELECTが返ってくるのは解凍済みのほうが早かったので、Kaggle用途には直接csvを置いておけばよさそうです(圧縮済み1GB、解凍後5GBくらいなので、スキャン量は多少多くなる)。スキーマはこんな感じで適当に作ります。

Athena用のテーブルのスキーマ定義

DROP TABLE ctr_practice.ctr_practice;
CREATE EXTERNAL TABLE ctr_practice.ctr_practice (
  `id` string,
  `click` int,
  `hour` string,
  `C1` string,
  `banner_pos` int,
  `site_id` string,
  `site_domain` string,
  `site_category` string,
  `app_id` string,
  `app_domain` string,
  `app_category` string,
  `device_id` string,
  `device_ip` string,
  `device_model` string,
  `device_type` string,
  `device_conn_type` string,
  `C14` string,
  `C15` string,
  `C16` string,
  `C17` string,
  `C18` string,
  `C19` string,
  `C20` string,
  `C21` string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = ',',
  'field.delim' = ','
) LOCATION 's3://ctr-practice/'
TBLPROPERTIES (
  'has_encrypted_data'='false',
  'skip.header.line.count'='1'
);

こんな感じで日付毎のクリック率の推移を可視化したり、カテゴリ変数毎のクリック数をテーブルに出してデータ分析を進めていきました。

手元で解析するのが億劫になるようなデータサイズでも、Athenaでは簡単なSELECTなら5秒程度、cardinatityの高いGROUP BYなどが入っても3分程度で返ってくるので、かなりお手軽です。5GB程度のデータをかなりフルスキャンしてデータ分析しましたが、200円程度で週末Kagglerのお財布にも優しくて安心。

見所としてはAthenaはウィンドウ関数にも対応している(Presto互換)ので、SQLだけで比較的強力な特徴量が作れるといったことでしょうか。例えば同じdevice_idが前回に登場した時刻を出して、その差分を計算し、それを特徴量として追加するといったことも簡単にできます(手元でpandasでやるのは若干面倒)。ウィンドウ関数は10年戦えるデータ分析入門で勉強しました。普通に実務のデータ分析で便利。

SELECT id,
       device_id,
       hour,
       prev
FROM
  (SELECT id,
          device_id,
          hour,
          lag(hour, 1) over(partition BY device_id
                            ORDER BY hour) AS prev,
   FROM ctr_practice.ctr_practice
   ORDER BY id LIMIT 1000000)
WHERE (NOT prev IS NULL)
  AND (hour != prev);

BigQueryで同じように探索的データ解析をYouTubeで紹介されている方もいらっしゃるので、こちらも参考になりました。


BigQueryでKaggle入門 Part 1(コンペ解説 + EDA紹介)

ベンチマークをまず超える

全部0.5を返すのがベンチマークだったので、学習データでのCTRの平均値をひたすら返す君を最初のsubmitにしました。評価指標はlogloss(クロスエントロピー)で、0/1の結果だけではなく結果の確信度も考慮した指標で評価されます。この時のスコアが0.4405302でした。後段の学習ベースの方法がきちんとうまく行っているかの判断材料になるので、初期段階でやっておきたいですね。

線形分類器でシンプルな特徴量

次に学習データに与えられているカテゴリカルな特徴量をonehot encodingして、線形分類器に食わせるだけのものを作りました。学習にはvwを使いました。

学習が高速で、省メモリでラップトップ参加者にも非常に優しいツールでした。また、コマンドラインから組み合わせ特徴量を作ることができるというのも嬉しいポイントでした。学習データが4000万行あるので、テキストファイルに組み合わせ特徴量を吐いていると30分程度かかってしまい、試行錯誤が非常にやりにくくなってしまいます。コマンドラインから組み合わせ特徴量を作れると、テキストファイルに組み合わせ特徴量を吐き出す手間が省けて助かりました。

awkでvw形式に吐き出します。文字列から特徴量idへの変換もvwが中でやってくれるので本当にお手軽ですね。

% awk -F, 'NR > 1 {print ($2*2)-1, "|a", "a_"$4, "|b", "b_"$5, "|c", "c_"$6, "|d", "d_"$7, "|e", "e_"$8, "|f", "f_"$9, "|g", "g_"$10, "|h", "h_"$11, "|i", "i_"$12, "|j", "j_"$13, "|k", "k_"$14, "|l", "l_"$15, "|m", "m_"$16, "|n", "n_"$17, "|o", "o_"$18, "|p", "p_"$19, "|q", "q_"$20, "|r", "r_"$21, "|s", "s_"$22, "|t", "t_"$23, "|u", "u_"$24, "|v", "v_"substr($3, 7, 2)}'

シンプルな特徴量のみで学習。スコアは0.4153354程度でした。正則化項は結構小さめ(1e-7くらいのオーダー)でないと正則化が強すぎるようで、学習がうまくいきませんでした。

時系列要素を忘れていて過学習発生

vwにハイパラチューニング用のスクリプトが付属しているので、これを使うと便利です。(その1/その2)。これを使ってハイパラチューニングをしていると、手元の開発用データではよかった設定が、submitしてみるとだいぶ悪化しているという現象に遭遇しました。データを眺めてみたところ、時系列的な要素を考慮し忘れているのが原因でした。学習データをシャッフルして8:2に分割して開発用データを作っていましたが、新しい広告やユーザーがどんどん入ってくるので、シャッフルしてしまうと時系列要素が壊れてしまいます。学習データの最終日(10月30日)を開発用データとしたところ、leaderboardの傾向と一致しました。時系列要素のあるチューニングは難しいことが多いので、Kaggleはちょうどよい練習になりますね。

特徴量エンジニアリングに苦しむ

ここからスコアを上げるのに難儀しました。vwでは組み合わせ特徴量にワイルドカードを使うことができます。発火している特徴量の全ての組み合わせを考えてくれるってやつです(例えば-q ::-q a:など。後者はaと他の全ての特徴量の組み合わせを考慮)。何も考えずに実行するもスコアはあまり変わらず…。ディスカッションの様子を見ていると、組み合わせ特徴量が重要であることは疑いようがなかったので、ワイルドカードにしたことにより不必要な特徴量も入ってしまったのでしょう。匿名化されている特徴量もありますが、意味を考えながら有効そうな組み合わせを人手で追加していきました。スコアは0.3945532まで上がりました(とはいえまだまだ750位くらい…)。

かなりスパースなデータなので、登場頻度の少ない特徴量は足切り(頻度10回未満のものはUNKNOWNにする)すれば汎化されて良いのではと思って試しましたが、自分の手元ではあまり効果がありませんでした。なんでだ…。

Factrization Machine(その1)

組み合わせ特徴量といえばFactorization Machine(FM)だよねと思ったので試してみます。vwがFMをサポートしていたので、そのままやってみました(::8のようなワイルドカードを使った)。スコアは0.3936758で650位前後で思ったよりは上がらず。1位の人もFMだし、きっと何か勘違いしているかチューニングの仕方が悪かったんだろう…。libfmなど本家のものを使ってみるといい結果が得られるかもしれません。

追記: やり方を変えたFactrization Machine(その2)ではもう少し精度が上がりました。

重みの更新方法を変える

sparseなデータで登場頻度も結構異なるので、重みの更新方法をsgdのように特徴量全部同じ幅でやるのではなくadagrad的な更新方法にすればよくなるのではと思って試しました。vwでのオプションは--adaptive --invariant--ftrl --ftrl_alpha 0.01 --ftrl_beta 1といったやつです。これはまあまあうまくいってスコアが少し改善(0.3938853)しました。関連論文は恐らくこの辺。

しかし、これでもまだ700位前後。

GBTMで特徴量の組み合わせを考慮

人間による特徴量チューニングに疲れてきたので、決定木ベースの方法で組み合わせを見ることにしました。決定木ベースの方法を使うのは初めてだったので、色々分からないことがだらけでした。

baggingとboostingの違いについても今回初めて知りました。baggingは学習データや使用する特徴量をサンプリングすることで少し異なる識別器を多数学習するような方法です。各学習器は独立に学習することができます(random forestはこっちっぽい)。一方、boostingは最初に学習器を作ったらその学習器の出力と学習データの損失を元に次の学習器を作るような逐次的な学習を行なっていきます(gradient boosting tree modelはこちら)。Gradient Boosting from scratchなどを参考にしました。

使うならrandom forestかxgboostかなと思っていましたが、最近はlightgbmというのが省メモリで高速っぽかったのでこれを試しました。vwは1GBくらいでも動いてくれましたが、lightgbmは省メモリになるような設定をしても8GBくらい持って行くので、ラップトップではこの辺が限界っぽい…。xgboostはOOMでshellに殺されてしまいました。

vwとは違って特徴量idへの変換は自分で行う必要があります。MurmurHash3を使ってfeature hashingすることで対応しました。最初はpython組み込みのhash関数を使っていましたが、セキュリティ要件で実行の度に結果が変わってしまうということに気付かず、精度が全然出なくてしばらく悩んだというのは笑い話です。hashのbinのサイズは大きいほうがcollisionしなくなりますが、大きすぎると学習の際にメモリを食うようになります。vwを使っているときに24程度(次元数が224で大体800万程度)でも十分精度は出ていたので、ここでも同じものを使いました。

お手軽feature hashingスクリプト

import csv
import sys
import mmh3

input_file = sys.argv[1]
hash_bin = 2**24

with open(input_file, "r") as i:
    reader = csv.reader(i, delimiter=",")
    header = next(reader)

    for words in reader:
        result = []
        click = None
        for idx, word in enumerate(words):
            h = header[idx]
            feature = word
            if h == "click":
                click = feature
            else:
                feature = mmh3.hash("%s_%s" % (h, word)) % hash_bin
                result.append(feature)
        result.sort()
        if click is not None:
            print("%s " % click, end="")
        print(" ".join(map(lambda x: "%s:1" % x, result)))

木の数を増やしまくると学習時間がどんどん増えていくので、leafの数を増やす方向でやっていきました。サンプリングレートなど色々チューニング要素がありますが、手元であれこれ試すのが厳しかったので、他はディフォルトパラメータでやりました。スコアは0.3930893で500位程度。チューニングを頑張ればこれはもう少し上がると思います。

% lightgbm data=xgboost_train_shuf.txt objective=binary metric=binary_logloss max_bin=127 num_trees=300 num_leaves=127

余談ですが、決定木ベースの手法はデコード(予測パート)が遅いですね…。全部の木をleafまで舐めないといけないからそりゃそうかって感じですが、こんだけ遅いとnlpのようなデコード速度もそれなりに求められるアプリケーションにはなかなか難しいという発見が今さらながらありました。

カリブレーション

これも初めて知りました。学習器の出力する確率の平均とテストデータの平均が一致するかはやってみないと分かりません。一致して欲しい気はするけど、一致する保証など特にない。平均がズレている場合にそこの補正をしましょうというのがprobability calibrationという方法です。難しそうな響きがするけど、やることは簡単。少しですが、精度が上がりました。テストデータの平均は分からないので、学習データの平均はと一致するようにカリブレーションしました。

カリブレーションするための簡単なPythonスクリプト

import sys
import math

def inv_logit(x):
    return math.log(x / (1.0 - x))

def logit(x):
    return 1.0 / (1.0 + math.exp(- x))

train_avg_ctr = 0.169806
pred_sum_ctr = 0.0
cnt = 0.0

result = []
for line in sys.stdin:
    ctr = float(line)
    result.append(ctr)
    pred_sum_ctr += ctr
    cnt += 1.0

pred_avg_ctr = pred_sum_ctr / cnt

for item in result:
    intercept = inv_logit(pred_avg_ctr) - inv_logit(train_avg_ctr)
    # print("%s\t%s" %(item, logit(inv_logit(item) - intercept)))
    print(logit(inv_logit(item) - intercept))

Factrization Machine(その2)

ワイルドカードで全ての組み合わせを考慮しているからFactrization Machineの精度が思ったほど出ていないのではと思ったので、多少組み合わせ方を考えてみることにしました。データのフィールド名に

  • siteに関するもの
  • appに関するもの
  • deviceに関するもの
  • categoryに関するもの

といった種類が分かれていたので、それら毎に組み合わせてみることにしました。上から仮にa/b/c/dと名前だとすると、ab/bc/cdといった感じでフィールドの組み合わせ方を指定してFactrization Machineの学習を行ないました。スコアは0.3916204で230位まで上がりました。

アンサンブル

性質の異なる3つのモデル(人手による組み合わせ特徴量/LightGBM/Factorization Machine)ができたので、アンサンブルをやってみることにしました。データからそれぞれの学習器の重みを決定してもよかったのですが、今回はお手軽にやるのが目的なので、単純にmodel averagingします。これが意外と効いてスコアは0.3895500(122位で上位7.6%)まで伸ばすことができました。

ちなみにvwでパラメータだけ変えたものをアンサンブルしても、同じような出力を出すのでアンサンブルしても精度向上はほとんどありませんでした。アンサンブルで性能が上がるかどうかは性質の異なる学習器を複数作れるかどうにかかっていそうです。

他のチームで参考になった方法

GBMの特徴量をvwに入れる

各データをGBMの各木に食わせて、それぞれどのleafにたどり着いたかというカテゴリカルな変数を特徴量にしてvwに食わせるという特徴量エンジニアリングです。1位の人もやっているようで、GBDT featureと呼ばれている。

後段でGBMの結果とアンサンブルする方法と精度がどれくらい違うのかは自分でも試してみたいですね。

カテゴリカル変数を実数の特徴量に置き換えていく

こちらを参考に。集計が必要な分ちょっと面倒ですが、one-hotとは違った特徴量になるので、アンサンブルすると効きそうですね。自分の試行錯誤では出てこないアイディアだったので、なるほどという感じでした。id:ultraistさんも書かれていますが、集計期間は少し気を使う必要がありそうですね。

追記: 識者に教えてもらいましたが、この手の特徴量エンジニアリングはCount Encoding/LabelCount Encoding/Target Encodingなどと呼ばれているそうです。以下に詳しく書いてありました。一つコンペに参加すると色々情報が集まってきてお得!

まとめ

  • 手元のMacbookだけ&基本的なテクニックで上位10%以内に入ることができた
    • 今回はログデータでしたが、画像データの場合はさすがに無理かも...
  • 他者の回答や技術を参考にできるので、本に載っていないような生きた技術を学ぶことができる
  • 自分が使ったことがないツールや初めてのタスクを練習するのにはうってつけ
    • 今回はFactorization MachineやGradient Boosting Decision Treeの様子が分かってよかったです

というわけでKaggleオススメです。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

*1:CoNLL2015のShared Taskだけはチームで一度出たことがあった

*2:機械翻訳は例外的

S3のログをAmazon Athenaを経由してRedashで分析/可視化

DWHやログ基盤を色々試してみたいけど、Amazon Redshiftよりお手軽に始められるツールとしてAmazon Athenaを教えてもらいました。S3にtsv/csv/json...を置いておくと、SQLで分析できるってやつです。BigQueryと同じくクエリ単位で課金だけど、ログはS3に上がっていることが多いので、お手軽に試せそうということで試しました。N番煎じ感しかないやつです。やったこと。

  • S3に分析したい対象のデータを置く
    • 大きいデータなら日毎などに分けておくとあとのパーティションで役に立つ
  • Athenaでtsv/csvのカラムの型を定義しておく
    • インデックスの指定などは特にない
    • パーティション分けてなければ基本的にフルスキャン
  • 数万件くらいの簡単なSQLで手元だと一瞬で返ってくるようなクエリでも3秒くらい時間かかるときもある
    • S3のバケットなめるからそういうものっぽい
  • 参考

可視化

データの傾向を見たいときはSELECTした結果だけじゃなくて、それをグラフにしたいことも多いです。可視化ツールへの繋ぎ込みを調べてみたところ、AWSスタックだとQuickSightというのがいい感じのBIツールのようです。

が、個人で試したいのでもっとお手軽なものはないかなと思って調べたところ、RedashのデータソースでAthenaにも対応しているということが分かりました。docker-composeで手元のMacからAthenaに接続。

% docker-compose -f docker-compose.production.yml run --rm server create_db
% docker-compose -f docker-compose.production.yml up
  • AWSのコンソールからAWSQuicksightAthenaAccessAmazonS3ReadOnlyAccessの権限を持つユーザーを作る
  • 作ったユーザーのaws_access_key_idaws_secret_access_keyをRedashのデータソースの設定に入力

社内だとkibanaで可視化することも多いけど、様々なデータソースをダッシュボードで一元的に可視化したい場合はRedashもなかなかよいですね。

まとめ

Athenaのことを特に意識せずにS3にログを置いておけば分析できて気持ちとしてはとっかかりやすいですね。色々感覚が掴めてきたらRedshiftとかも試してみたい(RedashのデータソースとしてRedshiftも選択できるようだ)。

10年戦えるデータ分析入門 SQLを武器にデータ活用時代を生き抜く (Informatics &IDEA)

10年戦えるデータ分析入門 SQLを武器にデータ活用時代を生き抜く (Informatics &IDEA)