LIVESENSE Data Analytics Blog

リブセンスのデータ分析、機械学習、分析基盤に関する取り組みをご紹介するブログです。

LivesenseDataAnalyticsBlog

MENU

Kubernetes を利用したコンテナベース機械学習基盤の構築

データプラットフォームチームの野本です。機械学習基盤の構築やその周辺アプリケーションの実装を行っています。以前は DOOR 賃貸の開発運用をしていてこんなことなどしてました。

機械学習システム運用の課題

リブセンスでは 2014 年ごろから機械学習システムの開発導入を行っており以降様々な機械学習システムを各サービスに導入してきました。また自社でのデータ分析基盤の運用も行うようになってから機械学習システムの開発の幅が広がり導入の要望も次第に増えてきました。(参考:リブセンスのデータ専門組織のこれまでとこれから)

当初は機械学習システムに対する運用知見などが少なかったため、専用のインフラというものは保持せず各サービスのインフラに相乗りし、サービスのアプリケーションと密に連携し機械学習システムを実装運用することが多かったです。各サービスは元々オンプレミスで運用されていたものが多かったのですが、現在は AWS などクラウド環境への移行を行ったり新規サービスは最初からクラウドを利用したりと各サービスのインフラは多様になってきています。 また、機械学習システム自体の数も増えており、次第に以下のような課題が顕著になってきました。

  • 導入するサービスのインフラに依存しがちになる
    • 利用したい言語 / ライブラリに制限
    • マシンリソースの制限
  • 似たようなシステムの複数サービスへの導入
    • 同じような実装が色々なところにちらばる
    • 運用コストが増える
  • 機械学習エンジニアが機械学習の実装以外のことにリソースを取られることがある
    • デプロイ作業
    • 各サービスのインフラ上でのオペレーション / 調査

これらの課題を解決するため、また運用の知見やノウハウも蓄積されてきたこともあり機械学習基盤を構築することとなりました。

機械学習基盤の構築

マルチコンテナ構成による機械学習アルゴリズムとアプリケーションの疎結合化でも触れていますが、GCP / GKE 上にコンテナを利用した機械学習基盤の構築を進めています。 現状のシステム概要図は以下です。

f:id:livesense-analytics:20180117130701p:plain

ざっくりとした概要図ですが、以下にその目的と全てではないですが実際にどのよう構成になっているかを簡単に解説をしていきます。

柔軟な機械学習システムの開発 / 検証 / 運用を目指す

今までの運用から、機械学習システムの開発において次のような課題解決の実現を目指しました。

  • 利用言語 / ライブラリの柔軟な選択
    • その為のプロビジョニング負荷の軽減
    • 開発者のローカル環境と本番の差異をなくす
  • モデルのアルゴリズム / パラメータを繰り返し調整しながら開発が出来る環境
  • 計算リソースの柔軟な確保
  • 本番同等の検証環境

これらを実現するのには現在はやはり Docker のようなコンテナを利用するのがベターであると考え、それを運用する環境として Kubernetes を採用することとしました。

Kubernetes の利用

Kubernetes の機能の詳細の紹介はここでは省きますが、Kubernetes を選んだ理由は次のようなものが上げられます。

  • ほぼ全ての設定をコード化 (yaml) 出来る
    • 開発 / 本番ごとに共通の設定で環境を構築出来る
  • デプロイ / ロールバックを素早く行える
  • クラスタ内の各 Pod / Service の連携が楽
  • ConfigMap / Secrets を利用して設定を共通化出来る
  • CronJob により簡易にバッチを運用出来る

実際に利用してみると Kubernetes 上でのコンテナのデプロイは本当に簡単/柔軟に行え、これであれば柔軟な機械学習システムの開発に十分活用出来ると思い採用に至りました。

GCP / GKE の採用

Kubernetes を運用する環境としては GCP / GKE を採用しました。

現在の機械学習基盤構築チームの前提として

  • 現状機械学習基盤には専任のインフラエンジニアがいない
  • アプリケーションや機械学習システムの開発運用も並行して行っている

といった状況で、出来るだけインフラ管理のコストがかからない GKE のようなマネージドサービスの利用が理想です。

他にも以下のような GCP 利用のメリットがあります。

  • リブセンスでは Google Apps を利用しているので Google のアカウント連携が出来る
  • GCE インスタンスの起動が速い
  • GKE の Stackdriver Logging との連携が便利
  • タグによるネットワークのルーティングが便利
  • Identity-Aware Proxy によるアプリケーションの認証

今回は 0 からの構築のため大規模な移行などのような状況よりも選択の自由度が大きかったこともあり GCP / GKE を全面的に利用することに大きな障壁はあまりなかったので採用出来ました。

コンテナレジストリ

リリース前の検証クラスタも構築しており、本番と同等のコンテナ / Kubernetes の設定を検証出来るようになっています。 Docker のレジストリには GCR (Google Container Registry) を利用しています。 GCR はそれ専用の独立した GCP プロジェクトで運用しており、本番 / 検証プロジェクト双方からアクセス出来るようになっています。 またコンテナのビルドには Container Builder を利用しており、基本的に GitHub 上のリポジトリで master へのマージが行われた際にビルドするようになっています。

helm / デプロイ

Kubernetes の設定は最初は標準の yaml で行っていましたが現在は helm を利用しています。 helm にすることにより、検証 / 本番環境の設定の切り替えが容易に行えます。 またデプロイは担当のエンジニアがまだ小人数であることもあり、各エンジニアのローカルマシンから行っています。 より多くのエンジニアがこの基盤を利用し始める場合には学習コストがそれなりにあったり不慮の事故にもつながるので今後改善すべき課題と思っています。

ちなみに手元で kubectl を操作する時 auto completion を使うととても便利です。

CronJob

CronJob は Kubernetes で提供されている機能で、いわゆる crontab の機能を Kubernetes 上で実現出来るものです。 現状稼動しているシステムはほぼバッチであり、その運用を考えた時フローがさほど複雑でないバッチの実行にワークフローエンジンのようなものの運用を行わないで CronJob を利用出来るのはとても便利です。 基盤の構築開始時には alpha クラスタのみでしか利用出来なく、実際に利用出来るようになるまでは crond で kubectl exec などをスケジュール実行するような簡易的なコンテナを作成して対応していましたが現在は CronJob に置き換えています。

データ分析基盤の活用

今までもブログなどで繰り返し紹介していますが、リブセンスでは自社でデータ分析基盤を構築運用しています。 データ分析基盤は AWS 上に構築していて、各サービスの各種ログはこの基盤上に構築している AWS Redshift に集約して保持しています。 各サービスの開発者 / 運用者は日常的にこのデータを使ってサイト性能などを分析しており、既存の機械学習システムも基本的にはこのデータを元に実装しています。 なので、機械学習基盤からも手軽にこの Redshift にアクセス出来る必要があるのですが、データ周りのことはこのデータ分析基盤を中心に考えることによりむしろ機械学習基盤の構築にフォーカスを絞ることが出来ます。

現在は機械学習基盤からの接続は Pgpool をプロキシとして利用し Redshift にアクセスするように構築しています。

  • Redshift へのアクセス許可を固定 IP で行う
  • 各コンテナからは Pgpool の URL でアクセス
  • ロードバランサを利用した冗長構成も取りやすい
  • 現在は利用していないが、必要であればクエリキャッシュも導入出来る
  • GCE での運用ではあるが、Container-Optimized OS を利用し Pgpool をコンテナ化して運用している

データ分析基盤の活用の現状と今後は以下のように考えています。

  • データが AWS 上に保持、計算は GCP 上で処理となっているが、現状バッチがメインなので転送コストやレイテンシは今のところそれほど問題ではない
  • リアルタイム性が必要になるなど機械学習固有の要求が発生するようなログ収集は機械学習基盤側で構築していくかもしれない
  • 各サービスのログ以外のデータも一部データ分析基盤には取り込まれているのでそれを利用出来る
    • 存在しないデータが出てきた時のために embulk を利用した取り込みも行えるようにはなっている

まとめ

よかったところ

  • Docker / Kubernetes の利用によるデプロイの早さ / 容易さ
  • Docker を利用することでアプリケーション個別のプロビジョニングがほぼ必要ない
  • コンテナ化 / helm による設定により環境のコード化を強制出来る
    • どのような環境を構築するのかが分かりやすい
    • 検証環境 / 本番での差異が出にくい
  • GCP の各サービスの活用も視野に入れやすい
    • Flexible Environment になり便利になった GAE の活用
    • データ分析基盤で補えないログに対する BigQuery の活用
    • ML 系サービスの活用

今後やっていきたいこと

  • CI / CD の確立
    • 出来るだけ Kubernetes のことを知らないエンジニアでも利用出来るように
    • 出来るだけ機械学習システムの開発フローに寄り添ったものに
    • Spinnaker などのツールも検証中
  • 計算リソースの柔軟なスケーリング
    • 機械学習にはつきものの潤沢な計算リソースの分配を手軽に
    • GPU の利用も機会があれば
  • 機械学習固有の問題解決
    • モデルの精度監視環境の構築
    • オンラインシステムの構築
  • などなど

機械学習基盤の構築はまだ始まったばかりで現状は既存のシステムの移行を行いながらこれら基盤の構築を進めているという現状です。変化のとても速い Kubernetes 周辺技術を適切に取り込んでいくとともに、基盤上で新たなシステムを実装しながらより柔軟な基盤を作っていければと思っています。(半年後には全然違う構成になってるかもしれません。)また、今後この基盤上で実装していく個別の機械学習システムに関しても紹介出来ればと思います。

Livesense Analyticsを支えるELT/ETL処理と運用

データプラットフォームグループの松原です。 弊社各サービスのデータ分析基盤であるLivesense Analytics(以降LA)の開発、運用を行っています。
今回はLAで行っているELT/ETL処理について紹介したいと思います。

LAでのELT/ETL処理概要

そもそもELT/ETLとは

ETLとはデータを整形してからDWH/データマートへ取り込む処理のことで、Extract(抽出) -> Transform(変換) -> Load(読み込み) の順で処理を行うためETLと呼ばれています。
ELTとはデータをDWH/データマートへ取り込んだのちに変換を行う処理のことで、Extract(抽出) -> Load(読み込み) -> Transform(変換) の順で処理を行うためELTと呼ばれています。

LAでのELT/ETL処理概要

LAではEMRを利用するまでは、ELTを中心に行っていましたが、EMRを利用してからはELT/ETLを両方行うようになりました。
LAでは大まかには以下のような構成でELT/ETL処理を行っています。

f:id:livesense-analytics:20180110112342p:plain:w500

LAでは主に3つELT/ETL処理があり概要は以下のようになっています。

  1. Redshiftで実行するSQLバッチによるELT
    LAの運用初期からあり、利用者向けのテーブルの作成などを行っています。
  2. EMR Sparkを利用したETL
    SQLでは実現しづらい複雑な処理(セッションの集計処理など)を実施しています。
  3. GlueによるELT/ETL
    コールドデータ(利用頻度の低いデータ)等をRedshift Spectrumで利用しやすいようにデータフォマットの変換などを実施しています。

LAでのELT/ETL処理

ワークフローエンジン

LAではETLのためのワークフローエンジンとしてAirflowを利用しスケジュール実行しています。
ちょうど導入を検討したタイミングで用途に合っていたから導入したという消極的な採用理由ですが、1.8にアップグレードしてからは1.7で不満があった以下の2点の不満も解消され運用しやすくなりました。

  1. DAG単位でCatchup設定が可能になり、Taskが詰まった際に過去のTaskを実行する必要がなくなった。
  2. Celery Backendを利用した際に、頻繁に実行されるヘルスチェック系のタスクを登録した際のCPU負荷が下がった。

Redshiftで実行するSQLバッチによるELT

LA運用初期からあるELT処理で、Redshiftに格納済みのデータから利用者向けのテーブルの生成や、分析者向けのテーブルの生成を主に行っています。
ここで生成している主要なデータは以下のようなものになります。

  1. RAWデータを加工し利用しやすくしたサマリーテーブル。
  2. 特定期間のみのデータを保持し、インターリーブソートキーを付けた利用者向けテーブル。
  3. 分析者向けのテーブル。

構成としては以下のようになります。

f:id:livesense-analytics:20180110104007p:plain:w400

EMR Sparkを利用したETL

主に以下の2点の理由で昨年ぐらいから、主要なサービスについてはSQLバッチから置き換えを行っています。

  1. ストリーム+SQLでデータを処理しづらいケースに対応する。
  2. 過去データのデータ定義の変更をRAWデータから再生成しやすくする。

将来的には 、サマリーテーブルのデータ定義の修正などで全データを再生成する際にRedshift上で再生成する運用コストが高い*1のでその辺りもEMRで処理をしたいと考えています。 ただし、SQLバッチによるELT処理も引き続き利用していくつもりです。

構成としては以下のようになります。

f:id:livesense-analytics:20180110143932p:plain

現状はストリームで処理は行っておらず定期バッチ処理が中心なためEMRは常時起動するのではなく、必要な時にAiflow経由で起動・停止させるという運用をしています。

Glueバッチ

昨年リリースされたGlueについても、Redshift Spectrumを利用してRedshiftのディスクを節約するために利用しています。
利用用途として以下の2つで、EMRを立てるほどでもないけどというような処理で利用しています。

  1. 終了してしまったサービスのデータをRedshiftからS3に移しParquetに変換を行う。
  2. コールドデータや、Redshiftに投入するにはデータ量が多すぎるものについては最初からRedshift Spectrumで利用できるようにParquetに変換を行いS3に保管する。

EMRの処理を置き換えることは現時点では想定しておらず、上記のようなデータフォーマットの変換という限定的な使い方をしています。

f:id:livesense-analytics:20180110144001p:plain:w500

上記の構成は利用用途の2番目の用途で利用しており、GlueのTrigger機能は利用せずにAirflowでGlue Jobの実行、異常検知を行っています。
1番目の用途については割と雑にGlue Scriptを書いて手動で実行しています。

まとめ

今回はLivesense AnalyticでのETL / ELT処理について紹介しました。
リブセンスのデータ分析基盤の全貌の資料公開から比べると比較的変更があり、紹介しきれていなかった部分も紹介できたかと思います。
今回紹介したELT / ELT処理や、前回紹介したエンコードタイプの変更の他にも色々な施策に取り組んでおります。それらについても別の機会にご紹介できればと思います

*1:ディスクの空き状況によっては別Redshiftクラスタを立ち上げ処理する必要がでたりするので、費用面よりも運用の手間という意味合いが強いです。

リブセンスのデータ専門組織のこれまでとこれから

はじめに

こんにちは。テクノロジカルマーケティング部の谷村です。

テクノロジカルマーケティング部(以下、テクマ部)は、 リブセンス内のデータ分析や機械学習、そのための基盤開発までデータまわりを手広くやっている部門です。

リブセンスはHR領域や不動産領域を中心として複数のメディアを運営しています。組織的にはメディア毎に事業部を編成する、いわゆる事業部制を採用しています。 メディア毎の意思決定スピードや戦略の柔軟性、等々が事業部制のメリットかと思いますが、テクマ部についてはこれら各事業を支援する形で横串の横断組織として編成されています。 横断の組織としているのは所属メンバーの専門性を高める目的や、全社状況にあわせたアサインメントを行う目的などがあります。 今回は、リブセンスがテクマ部を中心としてどのようにデータと向き合っているかをご紹介させていただきます。

リブセンスのデータ分析のこれまで

私がリブセンスに入社したのが2013年でしたが、リブセンスでは当時からデータドリブンな意思決定を大事にする文化がありました。 例えば、営業でもSQLを使うという文化、入社初日に目にして驚いたのを今も覚えています(営業さんまで、社員全員がSQLを使う 「越境型組織」 ができるまでの3+1のポイント)。

そんな中でここ数年のデータ関連のテクマ部での取り組みは、大きな流れとして下記のように進展してきました。

時期 概要 詳細・データ
~ 2014 アドホック分析中心 データは主に会員マスタや売上等のメディアのデータベースを中心に利用。各施策や広告効果などを多変量解析や時系列等で分析。
~ 2016 自社分析基盤の開発
機械学習のサービスへの適用
Web上での行動ログとメディアのデータベースを一元管理するようにDWHを整えたことで、全てのKPIを連続的に分析することが可能に。さらにユーザーに紐付いた行動ログが蓄積されたことで、レコメンドアルゴリズムや予測モデルのメディアへの組み込みが可能に。
2017 ~ 機械学習のインフラ整備
UX専門のグループ立ち上げ
データについてはアクセスログに限らず外部データを含めた取り込みを強化。各メディア毎にバラバラに開発してきた機械学習システムについて、基盤の統一を開始。

2015年には分析基盤となるDWHも整ったことで、個別の事業での機械学習の活用も進み、収益面でも大きな成果を残すことが出来ました。 そして昨年からは次のデータ活用フェーズを念頭に動きを開始しています。こちらの背景については後ほど説明させてください。

データに関わるメンバーの主な役割

弊社では各人を細かく職種で分けることはやっていませんが、大まかに意識している役割は下記のような形です。

役割 業務内容
データエンジニア 分析基盤開発・運用、データ収集、大量データの操作
機械学習基盤エンジニア 機械学習システム(インフラ・コード)の開発を担当
アルゴリズム開発者 アルゴリズムの実装、検証
データアナリスト データサイエンスを活用したプロダクト改善の企画立案
データベースマーケティングの推進
UXリサーチャー/UXデザイナー UXデザインの手法を駆使してサービスの課題・潜在ニーズの発見・解決方法の検討から実行を支援

正直なところ、呼称は定まっているわけではなく、役割も社内外の状況にあわせて年々変化しています。 例えば、機械学習基盤エンジニアとアルゴリズム開発者については以前は区別せずに「機械学習エンジニア」としていました。

各自のコアスキルの向上や、プロジェクトチームの編成を考える際にこれらの役割を意識していますが、現実には複数の役割を兼ねて動いているメンバーが多くなっています。

現在のテクマ部内のチーム構成

テクマ部では今年から3グループ体制で動いています。

グループ 所属メンバー ミッション
Data Marketing データアナリスト
UXリサーチャー/UXデザイナー
データからユーザー価値への転換の設計プロセスを担うミッション
Data Platform データエンジニア
機械学習基盤エンジニア
アルゴリズム開発者
データからユーザー価値への転換する仕組み(システム)を作る
Infra Structure インフラエンジニア 全サービスのインフラに責任を持つ

グループ化の背景については後述しますが、ここでテクマ部の中に社内の各サービスのインフラを担当するインフラエンジニアのグループを配置しているのが弊社の特徴です。 データに関わらずインフラ全般を担当しているため、データに関わる役割での紹介は省略しましたが、部内にインフラエンジニアがいることでシステム運用経験豊富なメンバーから助言、ノウハウ共有を行ってもらえると同時に、開発時のハブの役割も果たしてもらっています。

これからのこと

リブセンスでもここ数年で、既存の仕組みをより効率的にまわすことに対してデータ分析や機械学習が有効に機能するようになっています。 これからもそのようなアプローチは継続していきますが、一方でそのような効率化のアプローチは限界を抱えていると考えています。 特に大きいのが、データそのものに変化を生み出せないことです。 人工知能という言葉も流行し、各社とも取り組みを強化されていますが、大抵の場合、ユーザーへの恩恵が大きいのはアルゴリズムの洗練よりも、どのようなデータを収集するかということと、収集したデータからどのような価値への転換を行ってユーザーに提供するかということだと思います。 既存のデータを使って効率化のためにデータを使っているだけだと、この肝心のデータが成長しません。

そこで次のチャレンジとしては、データの使い方だけではなく、データそのものから考えていくフェーズに移りたいと考えています。 取得するデータの設計であったり、どうやったらそのデータを蓄積し続けることが出来るかというデータが集まる仕組みの設計、 そしてデータから価値への変換の設計、ひいてはサービス価値の定義、そしてそれを実行できるアルゴリズムの実装と、試行錯誤しながら取り組んでいきたいと考えています。下図でいうと、緑の吹き出し部分以外への注力をより高めるイメージです。 会社としても弊社代表の村上がリアルデータエンジニアリングという呼び声で推進している取り組みになりますが、事業部門と協力して進めていきます。

f:id:livesense-analytics:20180109095609p:plain

上記のような背景で、テクマ部でも「データから価値への変換の設計」を行っていくことを意識してUXDを専門に取り組むグループを昨年組織しました。 さらに今年はUXとアナリストを1つのグループに配置しData Marketingグループとしました。性格の異なる分野を1つにまとめた実験的な試みですが、事業部門と共同で新しいサービスの種を見つけていけることを期待しています。

また、コンセプトが決まればデータの性質や用途によって適切なアリゴリズムを開発する必要が出てきます。しかもなるべく高速に。 そこで、これまではアドホックに各事業への機械学習を導入してきましたが、 今後はアルゴリズムの開発・検証を高速でまわせるように分析基盤に加えて機械学習の基盤の開発にも力をいれるべく、Data Platformグループを編成しました。

さいごに

少しでもリブセンス(特にテクマ部)に興味を持っていただいた方向けに追加情報です。

仕事の進め方

役割によって異なりますが、データエンジニアのように運用すべきシステムを持っている場合はチームでタスクを分割して働いています。データアナリストやUXリサーチャーはプロジェクトチームを組んで動くことが多いです。

分析ツールや環境は、タスクの特性と各人の好みでわりと自由度高くやっています。Pythonを使うメンバーもいればRを使うメンバーもいます。最近ではJuliaも使い始めたりしています。 分析基盤のDWHはAWS Redshiftを中心に構成していますが、機械学習のインフラにはGCPを選んでいます。これらについては、今後のブログでも紹介していく予定です。

日常の学習

新しい技術もどんどん出てきている領域なので、業務を通じて学習しています。リブセンス自体が勉強会が盛んな会社なのですが、テクマ内でも毎週のように勉強会を行っています。 論文輪読会や、統計モデリングの勉強会等の分析・機械学習系の勉強をはじめ、Amazon Redshiftなどそれぞれの業務にあわせた勉強会を開催しています。隣に議論出来るメンバーがいるのは楽しいです。

募集について

リブセンスでは一緒に働いていただける仲間を募集しています。 変化の激しいデータ周辺の領域ですが、それにあわせてリブセンスでは組織もミッションも変化していますので、変化を楽しみたい方にはおすすめです。

少しでも興味を持っていただけた方は弊社採用サイトから、是非エントリーお願いします!

将棋盤を画像認識する

Analytics チームで転職会議のレコメンドを開発している @na_o_ys です。今回は業務のことは忘れて、趣味の将棋の話をしたいと思います。

この数年で将棋の学習環境はずいぶんリッチになりました。通勤電車では将棋アプリのネット対局をして、自宅ではオープンソースの強豪 AI を使って棋譜検討し、日々将棋を楽しんでいます。
一方で、顔を突き合わせて盤と駒を使って指す対局が一番楽しいのは変わりがありません。 リアルの対局を AI で検討するために、盤面を手軽にコンピュータに入力したい というのが今回のテーマの発端です。

TL;DR

f:id:na_o_s:20171220134558p:plain

盤上の駒を高い精度で推定することができました。

処理は大きく 2 つのステップからなります。

  1. 盤面の正規化
    • 盤面の四隅の座標を特定し、元画像から正規化画像への射影変換を得る
  2. マス目毎の内容を推定する
    • マス目毎に画像を切り出し、駒の有無・種類を推定する

ちなみに上記画像は私と同僚の将棋で、現局面は後手番です。後手玉は詰めろ飛車取りですが次の一手を考えてみてください。

ステップ 1. 盤面の正規化

正規化画像への射影変換を得るには、盤面の四隅の座標を特定できれば十分です。画像処理の要素技術としてエッヂ検出、輪郭検出、線分検出など多様な特徴抽出手法があります。今回は、 輪郭検出 + 線分検出 + 焼きなまし法 を組み合わせることで、高い精度で四隅の座標を特定することができました。

輪郭検出

将棋盤の大まかな位置を特定するために 輪郭検出 を利用します。

f:id:na_o_s:20171220182735p:plain

大まかな位置は特定できますが、マス目とぴったり一致しません。輪郭検出では罫線だけでなく将棋盤の端や机の角も検出してしまうためです。ここで特定した大まかな座標を出発点として、次に紹介する焼きなまし法でイテラティブに精度を高めていきます。

なお輪郭検出の結果は多角形ではなく曲線のため、直接四隅の座標は得られません。四角形の頂点座標を得るために以下の処理を行っています。

1. 輪郭検出
2. 凸包で近似
3. 凸包の頂点から四隅の座標候補 (最も正方形に近いもの) を選択

線分検出 + 焼きなまし法

焼きなまし法 はパラメータ (ここでは四隅の座標) を振動させながら、目的関数が最小となる値を探索する手法です。

以下の要件を満たす目的関数を設計する必要があります。

目的関数 f:
    f(四隅の座標候補) = 真の座標にどれだけ近いか

まず 線分検出 により将棋盤の罫線を大雑把に抽出します。これを 罫線画像 と呼ぶことにします。

f:id:na_o_s:20171220182805p:plain

目的関数の中で、与えられた四隅の座標をもとに マスク画像 を生成します。マスク画像は四隅の座標から仮定される罫線の位置に近ければ近いほど明るく、罫線から遠いと暗くなるようにします。

f:id:na_o_s:20171220182915p:plain

このマスク画像を罫線画像に重ねると、罫線の重なり具合が大きければ多くの罫線が透過されるのに対して、重なり具合が小さければ一部しか透過されないことが想像できます。 すなわち マスク画像と罫線画像の内積 が目的関数として利用できるということです。

この目的関数を利用して焼きなまし法を行うことで、輪郭検出結果からぴったりした座標が得られます。

f:id:na_o_s:20171220182927p:plain

四隅の座標が得られればマス目毎の画像を得るのは簡単です。

f:id:na_o_s:20171220183118p:plain

ステップ 2. マス目の内容推定

ディープラーニングします。

学習データ

f:id:na_o_s:20171220125829p:plain

学習データはインターネットで集めた約 400 枚の駒画像 (手作業でひと駒ずつトリミングしたのですが、この作業が一番大変でした) と、iPhone のカメラで撮影した盤面 10 枚です。盤面はテレビ画面やネット対局のキャプチャを含みます。

ステップ 1 で作成した盤面正規化プログラムを利用して事前にマス目毎の画像に分割し、手動で駒の有無・種類をラベリングしました。

ネットワーク

入力は単一のマスの画像で、画素数は 64 * 64 です。出力はマスの内容で、空 or 駒の種類 (先手後手それぞれ 15 種類) を判別する 31 のラベルです。ネットワークは畳み込み & プーリング層が 3 層と全結合層 3 層の構成です。

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), input_shape=input_shape))
model.add(BatchNormalization())
model.add(PReLU())
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, kernel_size=(3, 3), input_shape=input_shape))
model.add(BatchNormalization())
model.add(PReLU())
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(128, kernel_size=(3, 3), input_shape=input_shape))
model.add(BatchNormalization())
model.add(PReLU())
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())
model.add(Dense(1024))
model.add(BatchNormalization())
model.add(PReLU())
model.add(Dropout(0.25))
model.add(Dense(1024))
model.add(BatchNormalization())
model.add(PReLU())
model.add(Dropout(0.5))
model.add(Dense(NUM_CLASSES, activation='softmax'))

ネットワーク構成は こちら の漢字認識率 98.5 % のものを参考にさせて頂きました。

手書き漢字データによるファインチューニング

用意した学習データは駒の書体の種類が少ない (30種類程度) ため、局所特徴量の抽出を担う畳み込み層の学習に適しません。そこで、15 万文字の手書き漢字・ひらがなからなる 手書教育漢字データベース ETL8 を利用して、畳み込み層のパラメータを事前に学習しました。

学習・推定に使う将棋駒データはノイズが多い一方で、ETL8 は非常にノイズが少ないクリアな画像データです。そのため、そのまま学習してしまうと畳み込み層がノイズに対応できず将棋駒をうまく識別できませんでした。そこで、ETL8 に以下のノイズを乗せて学習を行いました。

  • ガウスノイズ (強)
  • ガウスノイズ (弱)
  • 白黒反転
  • 回転
  • 拡大縮小

その後畳み込み層を固定し、将棋駒データを使って全結合層のみを学習させます。これにより、高い精度の特徴抽出器 (畳み込み層) をそのまま将棋駒の分類に利用できます。

学習結果

Epoch 120/120
loss: 0.1981 - acc: 0.9379 - val_loss: 0.3054 - val_acc: 0.9245

92 % 程度の精度で駒の分類ができました。

今後の課題

いくつか大きな課題点を挙げます。一つは盤面検出の焼きなまし法に 10 秒程度の時間がかかることです。マスク画像を用いた目的関数は微分可能でないため確率的勾配降下法が利用できません。うまく微分を計算できる目的関数を設計すれば処理時間は大きく改善されるはずです。他にも、罫線の周期性を利用してスマートに盤面検出できないかなど考えましたが、具体的な手法は思いつきませんでした。

もう一つは、成銀・成桂・成香の分類問題です。将棋の駒には様々な書体がありますが、ものによっては人間でもそれらの識別が困難な場合があります。

f:id:na_o_s:20171220142832p:plain

特長が大きかったり一度学習した書体であれば間違えませんが、未知の書体について充分な精度を保証するのはまだ難しいようです。

最後に最も大きな課題は、持ち駒の検出ができないことです。画像によって持ち駒の位置や角度が異なるため、盤上の駒の推定とは違った要素技術が必要になります。

まとめ

将棋盤の画像から、盤面の状態を 92 % 程度の精度で得ることができました。盤面の正規化には輪郭検出・線分検出・焼きなまし法を使い、駒の推定にはディープラーニングを利用しました。普段業務で画像処理やディープラーニングを扱っていないため、今回の手法には冗長な手順や簡単な見落としもあるかと思います。お気づきの点やアイデアがあれば是非コメント頂ければ幸いです。

今回のプログラムは GitHub で参照頂けます。

github.com

おまけ

Mac で動く Electron 製の棋譜検討アプリ を絶賛開発中です。2018 年初頭には公開したいなあ。

Amazon Redshiftのデータ量監視とエンコードタイプ

データエンジニアリングチームのよしたけです。 弊社各サービスのデータ分析基盤であるLivesense Analyticsの開発、運用を行っています。

Livesense Analyticsのアーキテクチャ

Livesense AnalyticsはAWS上でシステムが構築されています。S3上にあるデータやtd-agent、Kinesis Firehoseなどを経由して集めたデータをAmazon Redshiftに格納し、データウェアハウスとして運用しています。詳細は、弊社大政がデータ分析基盤Night #1発表した内容をご参照ください。

https://speakerd.s3.amazonaws.com/presentations/c51a36e2acdc442ba4cf28d9df76b45b/slide_5.jpg

当時とは一部変更になっている部分もありますが、大枠は上記の図の構成になっています。

ディスク使用量

このLivesense Analyticsには、マッハバイト転職会議をはじめ、リブセンスで運用している多くのメディアの各種ログやデータが集められています。そのため、Redshift上にあるデータは日々増加の一途を辿っており、空き容量の確保や、ヤンチャなクエリがテンポラリテーブルでディスクを食いつぶすのを未然に止める、などの運用に日々追われています。

普段はRedshiftのコンソール画面からディスク空き容量などの監視を行っていますが、具体的にどのテーブルのサイズが増加しているのか、1ヶ月前と比べてどれくらい増加しているのか、といった分析がコンソール画面からは出来ません。

そこで、システムテーブルのSVV_TABLE_INFOのスナップショットを毎日取得するようにしています。このテーブルにはどのスキーマのどのテーブルがどれくらいの容量を使用しているのか、という情報が含まれているため、このデータを1日1回スナップショット格納用テーブルにSELECT INSERTすることでデータを貯めていきます。SVV_TABLE_INFOの持っているカラムの他に、スナップショットを取得した日時カラムも併せて保持しています。

このスナップショットを集めたテーブルを使い、re:dashでグラフ化して毎日のデータ増加量やスキーマごとのデータ量の割合、増加量を監視しています。弊社では、メディア、サービスごとにスキーマを分ける運用をしているため、どのメディアのデータが増加しているのかが分かるようになっています。

f:id:livesense-analytics:20171211153656p:plain f:id:livesense-analytics:20171211153709p:plain f:id:livesense-analytics:20171211153722p:plain

encode指定

では実際に使用量を削減していくにはどうしたらいいか、という話をしたいと思います。

Redshiftでテーブルを作成する際に、ENCODE指定を各カラムに行います。

ENCODEで指定できるエンコードタイプには以下の種類があります

VARCHAR SMALLINT INTEGER BIGINT DECIMAL REAL DOUBLE PRECISION DATE TIMESTAMP BOOLEAN
RAW
BYTEDICT ×
DELTA × × × ×
DELTA32K × × × × ×
LZO × × ×
MOSTLY8 × × × × × ×
MOSTLY16 × × × × × × ×
MOSTLY32 × × × × × × × ×
RUNLENGTH
TEXT255 × × × × × × × × ×
TEXT32K × × × × × × × × ×
ZSTD

各エンコードタイプの特徴はAWSのページを参照していただくとして、これらのうち、どのカラムにどのエンコードタイプを選択すればいいのでしょうか?

検証方法として、実際にそれぞれのエンコードタイプのカラムにデータを入れてみて、どれくらいのサイズになるのか確認してみるのがよさそうです。

以下のクエリはINTEGER型の値について、各エンコードタイプ指定がしてあるテーブルを作成し、対象カラムのデータをINSERTしてみる例です。

CREATE TABLE encode_test (
  value_raw INTEGER ENCODE RAW,
  value_bytedict INTEGER ENCODE BYTEDICT,
  value_delta INTEGER ENCODE DELTA,
  value_delta32k INTEGER ENCODE DELTA32K,
  value_lzo INTEGER ENCODE LZO,
  value_mostly8 INTEGER ENCODE MOSTLY8,
  value_mostly16 INTEGER ENCODE MOSTLY16,
  value_runlength INTEGER ENCODE RUNLENGTH,
  value_zstd INTEGER ENCODE ZSTD
);
INSERT INTO encode_test (
  value_raw,
  value_bytedict,
  value_delta,
  value_delta32k,
  value_lzo,
  value_mostly8,
  value_mostly16,
  value_runlength,
  value_zstd
)
SELECT
  target_value,
  target_value,
  target_value,
  target_value,
  target_value,
  target_value,
  target_value,
  target_value,
  target_value
FROM
  src_table;

次にこのテーブルの各カラムがどれくらいディスクを使用しているかを確認します。STV_BLOCKLISTというシステムテーブルから、列ごとにどれくらいのブロックが使用されているかMB単位で取得することが出来ます。

SELECT
  col,  -- 0オリジンで、encode_testテーブルに指定したカラムに対応
  MAX(blocknum)
FROM
  stv_blocklist AS b,
  stv_tbl_perm AS p
WHERE
  b.tbl = p.id
  AND p.id = (
    SELECT
      table_id
    FROM
      svv_table_info
    WHERE
      "table" = 'encode_test'
  )
  AND col < 9  -- encode_testテーブルで作成したカラムの数を指定
GROUP BY
  name,
  col
ORDER BY
  col;

実際のデータを用いて得られた結果をお見せします。弊社のあるサービスのアクセスログが格納されているテーブルについて、最適なエンコードタイプがどれになるか見てみようと思います。

各カラムについてデータの特性がイメージしてもらえるよう、以下の値を併せて出しています。

  • 平均文字列長(varchar型のみ)
  • ユニークデータ数
  • 値ごとの平均レコード数
  • NULL値列
  • 値ごとのレコード数の標準偏差

また、各エンコードタイプの圧縮率をまとめています。圧縮率はエンコードタイプrawのディスクサイズとの割合から算出しています。

検証を行った環境は、ノードタイプdc2.large、ノード数18台のRedshiftクラスタを使用しています。対象となるアクセスログテーブルは約3億件のレコードが格納されています。

varchar型カラム

カラム 平均文字列長 ユニークデータ数 値ごとの平均レコード数 NULL値率 raw lzo zstd bytedict runlength text255 text32k
access_id 36.00 305260271 1.00 0.00% 100.00% 77.95% 45.02% 95.47% 100.30% 120.54% 179.76%
session_id 36.00 94677977 3.22 0.00% 100.00% 85.50% 48.94% 95.17% 100.00% 122.36% 183.99%
user_id 31.99 35762700 8.54 0.01% 100.00% 56.19% 32.44% 93.98% 100.00% 117.06% 172.91%
ip 13.99 16101001 18.96 0.00% 100.00% 51.35% 33.11% 124.32% 100.68% 89.86% 95.27%
url 87.34 51500368 5.93 0.00% 100.00% 33.64% 22.82% 4091.82% 100.26% N/A 122.30%
url_host 8.04 3 101,768,565.67 0.00% 100.00% 0.95% 0.95% 8.57% 0.95% 77.14% 139.05%
url_path 14.22 5292074 57.69 0.00% 100.00% 41.77% 29.75% 1786.71% 97.47% N/A 100.00%
user_agent 130.20 471837 647.06 0.00% 100.00% 14.91% 10.16% 270.08% 96.07% N/A 93.69%
device 8.41 7 43,615,099.57 0.00% 100.00% 8.33% 3.70% 8.33% 25.93% 15.74% 30.56%
os 6.98 43 7,100,132.49 0.00% 100.00% 17.71% 9.38% 9.38% 68.75% 20.83% 39.58%
browser 7.10 29 10,527,782.66 0.00% 100.00% 16.33% 8.16% 9.18% 62.24% 17.35% 34.69%
page_type 7.57 50 6,106,113.94 0.00% 100.00% 25.74% 13.86% 8.91% 88.12% 28.71% 54.46%

上記の結果から、圧倒的にZstandardの圧縮率が高いことがわかります。今年の1月に新しいエンコードタイプとして追加され、それ以前は迷ったときのlzo頼みだったのですが、今やそのポジションは同じような特性を持ちかつ圧縮率のよいZstandardに置き換わったといえます。

一方、osやpage_type(URLによって付けられるページ種類のタグのようなもの)カラムのように、値の種類が限定されるカラムについてはbytedictが有効です。公式ページによると

このエンコードは、列に含まれる一意の値の数が制限されている場合に非常に効果的です。このエンコードは、列のデータドメインが一意の値 256 個未満である場合に最適です。

とのことですが、種類が少ないとあまり効果は出ないようです。50個程度であれば効果が期待できます。また、長い文字列など値のサイズが大きい場合、bytedictは極端に性能が悪化するので注意が必要です。

また特定の値(NULLとか)が殆どの場合は、runlengthが効果を発揮します。公式ページによると

たとえば、大きなディメンションテーブルの列に、予測どおりに小さなドメイン (10 個未満の可能値を持つ COLOR 列など) がある場合、データがソートされなくても、これらの値はテーブル全体にわたって長いシーケンスになる可能性が高くなります。

とありますが、こちらの傾向としては特定の値が99%以上を占めるようなデータの場合に効果が出るようで、deviceのように値の種類が10個未満でも効果が出ない場合があったり、逆に種類が多くても特定の値が99%を占めるようなデータであればrunlengthが効果的な場合もあります。

integer型カラム

カラム ユニークデータ数 値ごとの平均レコード数 NULL値率 raw lzo zstd bytedict delta delta32k mostly8 mostly16 runlength
access_count 24422 12,501.26 0.00% 100.00% 45.45% 24.24% 27.27% 27.27% 51.52% 30.30% 54.55% 109.09%
session_count 6088 50,148.77 0.00% 100.00% 45.45% 24.24% 27.27% 27.27% 51.52% 30.30% 54.55% 106.06%
elapsed_sec 42663 7,156.22 0.00% 100.00% 57.58% 39.39% 66.67% 81.82% 51.52% 69.70% 54.55% 112.12%
entry_id 1198370 254.77 99.33% 100.00% 100.00% 100.00% 100.00% 100.00% 100.00% 200.00% 200.00% 100.00%
item_id 1874261 162.89 62.79% 100.00% 100.00% 69.23% 115.38% 123.08% 123.08% 115.38% 115.38% 123.08%

integerについても圧倒的にZstandardの圧縮率が高いことがわかります。

varcharではNULLデータが多いカラムだとrunlengthが効果的でしたが、integerについては1データのサイズがvarchatと比べて小さいためか、entry_idのようにあまり差が出ませんでした。

date型カラム

カラム ユニークデータ数 値ごとの平均レコード数 NULL値率 raw lzo zstd bytedict delta delta32k runlength
access_day 1165 262,064.98 0.00% 100.00% 3.03% 3.03% 27.27% 27.27% 51.52% 3.03%
session_start_day 1165 262,064.98 0.00% 100.00% 3.03% 3.03% 27.27% 27.27% 51.52% 3.03%

dateカラムについてはZstandardだけでなく、LZO、runlengthも効果的なことがわかります。値によってデータ数に差があまりなく、同じ値が連続しているため、runlengthでも効果が大きかったのではないかと推測します。

timestamp型カラム

カラム ユニークデータ数 値ごとの平均レコード数 NULL値率 raw lzo zstd bytedict delta delta32k runlength
hit_time 85443858 3.57 0.00% 100.00% 81.54% 52.31% 112.31% 112.31% 124.62% 112.31%
visit_time 55451781 5.51 0.00% 100.00% 80.00% 53.85% 112.31% 112.31% 124.62% 112.31%

一方timestampカラムについてはZstandard一択と言えそうです。dateカラムと違い、値がミリ秒単位であるため、runlengthやdeltaの効果が出にくいのかもしれません。

結果

今回の結果を受け、このアクセスログテーブルのエンコードタイプ指定を見直してみました。RedshiftにZstandardが対応される前に作られたテーブルだったため、主にlzoを使っており、まだZstandard導入をしていませんでした。

エンコード指定を見直した結果、テーブルのディスク使用量が76,883MBから50,849MBと約2/3に削減されました。同じ手順で他のテーブルにもエンコードタイプ見直しを進めていくことで、効果的なサイズ削減が実現できそうです。

最後に

Amazon Redshiftの増加していくデータに対してどう向き合っていくかという話をさせていただきました。re:dashを使ってデータ増加を監視しつつ、エンコードタイプを見直してデータ量削減を効果的に行うことが出来ました。特にZstandardの導入が効果的であることが分かりました。

なお今回はデータ量についてフォーカスさせていただきましたので、エンコードタイプによる実行速度の比較は行いませんでした。こちらは別の機会にまとめてみたいと思います。

エンコードタイプ見直しの他にもDISTKEYの見直しやRedshift Spectrumの導入、データ移行など、色々な施策に取り組んでおります。こちらもまた別の機会にご紹介したいと思います。