DWH | AI tech studio https://cyberagent.ai AI事業本部は、サイバーエージェントグループのデジタルマーケティング分野におけるサービス開発を行う組織です。 Mon, 13 Sep 2021 08:53:22 +0000 ja hourly 1 https://wordpress.org/?v=5.3.26 AirTrack 内定者アルバイト 「広告ログ集計基盤をSnowflakeへの移行による集計プロセスの改善」 https://cyberagent.ai/blog/pr/intern/15344/ Tue, 31 Aug 2021 03:06:24 +0000 https://ai-blog-admin.cyberagent.ai/?post_type=blog&p=15344 はじめに

初めまして、22卒エンジニア職内定者の松岡穂高と申します。

2021年6月1日から3ヶ月間、サイバーエージェントのAI事業本部にあるAirTrackという部署で、サーバーサイドの内定者アルバイトをさせていただきました。

今回は振り返りの意味も兼ねて、取り組んだ内容と学んだ事や知見について書き記したいと思います。

AirTrackとは

AirTrackとはAI事業本部の小売DX本部に位置し、スマートフォン端末から取得できる位置情報を活用し来店計測や広告配信を行うサービスを展開しているプロダクトです。位置情報を使って顧客属性を推測しターゲティングを行なっており、「人を動かす広告をつくる」ことを目指しています。

今回は、DSPと呼ばれる広告主向けの広告配信プラットフォームの開発に携わりました。DSP及びアドテクに関する詳しい解説はこちらの記事に書かれています。興味のある方はぜひご覧ください。

今回の目標

まず、今回AirTrackで働くにあたって成し遂げたいことは以下でした。

  • 大規模なデータを扱い、パフォーマンスチューニングができるようになりたい
  • 何かしらのビジネスインパクトを与えたい

学生レベルでは、どうしても事業目線で開発・運用を行う機会はないので、この内定者アルバイトではできる限り社員の方々と同じような視座を持つことを意識して取り組みました。

開発言語に関しては自分自身、特にこだわりはなく、プロダクトに適した言語を選定し利用できるのが望ましいと考えています。理由としては、サーバーサイドの言語はトレンドが激しく、現在使用している言語が必ずしも将来のデファクトスタンダードであり続けるとは限らないからです。そのため、一つの言語に固執したくない、今まで触れてこなかった技術にも触りたいと思い、Scalaでのサーバーサイドやインフラレイヤーの実装に挑戦しました。

AirTrackで行ったこと

今回私がメインとして行ったことは、既存の広告ログ集計のシステム基盤をSnowflakeに移行するというタスクでした。SnowflakeとはSnowflake社が提供するクラウド型データウェアハウスです。この移行によって、運用コストの削減、集計プロセスの安定化、より信頼性の高いモニタリングや精度の高いCTR予測が可能になり、もともと私が成し遂げたかった「大規模データを触りたい」「ビジネスインパクトを与えたい」という目標が達成できると考えました。テーブルの移行自体はクエリを書くだけで簡単でしたが、移行したテーブルが元のテーブルと同等の内容になっているか(join や where などの集計処理が等しいものになっているか)という精度の評価で苦戦し自身の成長にもつながる部分でした。この検証の部分に力を入れていたのでこのブログで紹介していきます。

精度検証と評価

まずは、試験的にSnowflakeで新しいテーブルを作成し、既存のものと同等の精度であるかを検証します。両者のテーブルから8月の1ヶ月間でデータを絞り込み、1日ごとにグループ化します。そして、既存テーブルと今回作成した新規テーブルから日ごとのidの数を算出し、抽出したcount数から両者の差分の割合を算出します。

WITH new AS (
  SELECT
    DATE_TRUNC(DAY, requested_at) AS date, 
    COUNT(id) AS count
  FROM
    new_table_log
  WHERE
   requested_at BETWEEN '2021-08-01' AND '2021-09-01'
  GROUP BY 1 
  ORDER BY 1
), 
old AS (
  SELECT 
    DATE_TRUNC(DAY, requested_at) AS date, 
    COUNT(id) AS count 
  FROM
    old_table_log
  WHERE 
   requested_at BETWEEN '2021-08-01' AND '2021-09-01'
  GROUP BY 1 
  ORDER BY 1
) 
SELECT
  old.date, 
  old.count AS old_count, 
  new.count AS new_count, 
  new_count / old_count AS rate,
  new_count - old_count AS difference
FROM 
  new 
  FULL OUTER JOIN old ON new.date = old.date 
ORDER BY 1
;

以下は、SnowsightというSnowflakeのデータ結果をグラフで可視化するツールを使用し、検証結果をグラフ化したものになります。この結果から、ほとんどの差分率が1.0に近い範囲に収まり、既存のデータと新しく作成したデータがほとんど一致している事が示されました。

新旧のレコード数の差分率を表したグラフ(8月)

実装

検証結果を評価した後は実装に入ります。試験的に作成したクエリが先程の検証で精度的に適正であることが証明されたので、これを定常的に作動させます。今回は、CloudWatch Eventsで定刻になればECSタスクを実行するようにしました。また、インフラ構築はAWS CDKを用いてクラウドアプリケーションリソースを定義しました。

学び

技術力も含めて設計力の大切さ

まず何よりもこの内定者アルバイトで学んだことは、設計の大切さです。AirTrackに来てまず驚いたことは、要件定義・システム設計のドキュメントの多さでした。私自身、これまでシステム設計を行わずに最初から実装ということが多々あったため、設計に対するスキルは皆無でした。実際に設計のレビューを社員の方々に見てもらい、改めて認識の齟齬があったり、より効率的で効果的な実装方法を提案してくださったりしていただきました。実現したい事を達成するために、どのような技術を用いて、どのように課題解決へとアプローチするかという方法を考え、文書に落とし込むことは簡単そうに見えてとても難しかったです。

仮説検証の重要性

次に学んだことは、仮説検証の大切さです。実装したものが果たして、どのような効果をもたらし、何がボトルネックであるかを明確にする必要がありました。

今回は特にSQLを使ってデータを集計することがメインでした。既存のものと刷新したデータベーステーブルを比較し、レコード数やデータの欠損率を計測。そして仮説検証を行い原因追求の繰り返し行う。そのようにして課題解決を行なっていくという経験ができたのは、私自身にとって大きな成果だと思います。少ないデータ量であれば、数字を出さなくとも、実際目で見て比較すれば良い話ですが、億を超えるレコード数を扱うため、欲しい結果を得るためのクエリを書くことは難しかったです。大規模なデータを扱えるのはとても楽しく、広告事業ならではの醍醐味だと感じました。

さいごに

このメインのタスク以外にも、サーバーサイドの開発ではScalaに挑戦しました。関数型言語は扱ったことがなく、慣れない概念が多くて難しかったですが、エンジニアとしてのレベルをより一層高めることができたと思います。また、個人開発ではなかなか使用する機会がないAWSのサービスを扱えたのも貴重な経験だと思います。

今回は、私自身初めて内定者アルバイトをさせていただきました。参画当初はとても不安でチームの力になれるか心配でしたが、AirTrackの皆さんはとても優しく働きやすい環境でした。また、わからない質問に対しては自分の成長を考えた受け答えをしていただきとても学びになりました。しっかりアウトプットを出せたのも、自分一人の力ではなくメンターさんや先輩の社員の方々のおかげであり、とても感謝しています。そして、この3ヶ月で身につけたことや学んだことはこれからも活かして自分の成長に繋げたいと思います。短い間でしたがお世話になりました。本当にありがとうございました。

 

]]>
DruidをBigQueryの代わりに使えないか試してみた https://cyberagent.ai/blog/tech/oss/4095/ Mon, 26 Feb 2018 05:05:31 +0000 https://cyberagent.ai/techblog/?p=4095 こんにちは。アドテクスタジオの和田です。

現在私の所属しているチームではBigQueryでアクセスログ分析を行っているのですが、コスト問題、アドホックなクエリを実行したい、大量join時の待ち時間など運用上の課題を幾つか抱えていました。

そこで代替手段を探していたのですが、Druidというデータストアがあることを知ったので課題をDruidで解決できないか検証してみました。

 

Druidとは

OLAP向けのデータストアです。データソースとしてS3, GCSのようなFileベースのデータに加えてkafkaなどのstreamingデータも扱うことができます。

http://druid.io

 

Druidのアーキテクチャ

Druidは以下のコンポーネントで構成されます。

  • Coordinator Node
    • セグメント管理ノード
    • セグメントはHistoricalノード上で管理されているが、古いセグメントの削除や負荷分散などの管理を行う
  • Historical Node
    • セグメントを実際に持ち、ロードなどを行う
  • Indexing Service
    • セグメント生成ノード。以下コンポーネントをまとめて Indexing Service と呼ぶ
      • Overlord:データ取り込みのリクエストを受け付けるエンドポイント
      • MiddleManager:Peonを管理する
      • Peon:taskを実行する。taskとはデータロードしてセグメント生成する一連の処理
  • Broker Node
    • queryを受け付け、読み込み対象となるセグメントを持つノードへルーティングを行う
  • Realtime Node
    • リアルタイムインデックスを提供する
    • セグメントを生成してHistoricalノードに保存する
  • Zookeeper
    • セグメント情報などの管理用

※引用:  http://druid.io/docs/0.11.0/design/indexing-service.html より

 

検証構成

以下のインスタンスを用意し、quickstartを参考に各コンポーネントを起動します。

  • Node1 2vCPU / 8GBmem
    • Coodinator
    • Overlord
    • Zookeeper
  • Node2 8vCPU / 48GBmem
    • MiddleManager
    • Historical
  • Node3 2vCPU / 8GBmem
    • Broker
  • 可視化用
    • superset Node

Coodinatorプロセスが8090ポートをLISTENしているのでブラウザからアクセスするとタスク状況が確認できます。

http://Node1:8090/console.html

 

データロード

データロードは上記にもあったtaskという単位で管理します。

定義ファイルをjsonで記述してOverlordプロセスへHTTP POSTすることでtaskを登録できます。

taskのtypeは以下のようになります。

  • index
    • 小さなデータセット向けの取り込みタスク。大きいものはindex_hadoopを使う
    • firehoseというデータロード向けのコンポーネントを利用する
      • ローカルファイルやhttp、既存セグメントのcombineタスクなどが定義できる
      • 外部extension(jar)を使うことでS3 / GCSデータロードを実現する
  • Hadoop
    • 大きいデータセット向けの取り込みタスク
    • 取り込み元ファイルの拡張が難しい?(firehoseのようなextensionはありませんでした)
    • ローカルファイル / S3はロードすることができる。
    • http://druid.io/docs/latest/ingestion/batch-ingestion.html
  • 以下のようなタスクもある
    • append / merge / same_interval_merge / kill / noop

 

データロード検証

index (GCS firehose)

druid-google-extensionsをインストールし、GCS firehoseでGCSからデータロードするindexタスクを定義します。

以下のjson定義をOverloadプロセスにPOSTで登録することでgzip圧縮済みのアクセスログを1日分ロードしてみます。

{
  "type": "index",
  "spec": {
    "ioConfig" : {
      "type" : "index",
      "firehose" : {
        "type" : "static-google-blobstore",
	"blobs": [
          {"bucket": "test-druid-store", "path": "test/deployment.2018-01-29_00.log.gz"},
          {"bucket": "test-druid-store", "path": "test/deployment.2018-01-29_01.log.gz"},
          ...ロードファイル数分記述...
	]
      }
    },
    "dataSchema" : {
      "dataSource" : "test",
      "granularitySpec" : {
        "type" : "uniform",
        "segmentGranularity" : "day",
        "queryGranularity" : "none"
      },
      "parser" : {
        "type" : "string",
        "parseSpec" : {
          "format" : "json",
          "dimensionsSpec" : {
            "dimensions" : [
              "id",
              "time",
              "reqid",
              "uid",
              "os_version",
              "processing_time"
            ]
          },
          "timestampSpec" : {
            "format" : "auto",
            "column" : "time"
          }
        }
      },
      "metricsSpec" : [
        {
          "name" : "count",
          "type" : "count"
        }
      ]
    },
    "tuningConfig" : {
      "type" : "index",
      "maxRowsInMemory": 750,
      "targetPartitionSize": 100000
    }
  }
}

 

task完了まで7時間程度かかりました。。データサイズは 750MB程度でした。

root@Node2:~/druid-0.11.0# du -sh var/druid/segments/test/
755M var/druid/segments/test/

 

index_hadoop

次に index_hadoop type でのデータロードで高速化できないか試してみます。

firehoseは利用できなかったので、node2サーバにgsutil cpでgzファイルをコピーしておいてstatic typeでロードしました。こっちはワイルドカード使えて良いですね。

{
  "type": "index_hadoop",
  "spec": {
    "ioConfig" : {
      "type" : "hadoop",
      "inputSpec": {
        "type": "static",
	"paths": "file:/mnt/disks/druid/gcs/*/*.log.gz"
      }
    },
    "dataSchema" : {
      "dataSource" : "all-hadoop",
      "granularitySpec" : {
        "type" : "uniform",
        "segmentGranularity" : "day",
        "queryGranularity" : "none",
	"intervals": ["2018-01-29/2018-02-03"]
      },
      "parser" : {
        "type" : "hadoopyString",
        "parseSpec" : {
          "format" : "json",
          "dimensionsSpec" : {
            "dimensions" : [
              "id",
	      "reqid",
	      "ip",
	      "os"
            ]
          },
          "timestampSpec" : {
            "format" : "auto",
            "column" : "time"
          }
        }
      },
      "metricsSpec" : [
        {
          "name" : "count",
          "type" : "count"
        }
      ]
    },
    "tuningConfig" : {
      "type" : "hadoop",
      "numBackgroundPersistThreads": 1
    }
  }
}

動作を見ていたところLocalTaskRunnerのようなプロセスが1つだけ起動され、1ファイルずつMapReduceが走っていたため高速化は期待できなさそうです。

ちゃんとHadoopクラスタを用意して、DeepStorageもLocalではなくHDFSにすることでスケールできそうです。

 

課題

データロードが遅い

ロード検証の通り、通常のindex typeでは遅すぎると感じました。

index_hadoop typeでGCSからロードしてHDFSをDeepStorageとすればよさそうですが、GCSからのロードは作り込みが必要そうです。(S3はいけそう)

また、Druidクラスタ + 別途Hadoopクラスタを用意するとなるとかなり大掛かりになってしまいます。

1 peon : 1 taskの処理しか行わないようなので、1taskの処理をスケールさせようと思うとMapReduceを利用するのが良さそうです。

 

DeepStorageのロケーション

デフォルトのLocalだと、MiddleManagerホストのディスクにセグメントと呼ばれるデータロード済みのデータが保存されます。

複数台ある場合だとtaskを担当するpeonが動いているノードにのみ保存され、分散されません。

GCS, HDFSをDeepStorageとしないとスケーラビリティがありません。

 

joinができない

残念ながらテーブル間のjoinはサポートされていません。

全結合したデータを1テーブルにロードするような形で実現するのが良さそうですが、データロード時にjoinすることはできませんでした。

updateはDruidでサポートされていますが、特定のkeyのjsonを作ってPOSTする形なので、既存テーブルに対して今回ロードするgzファイルの特定キーを元にupdate(merge)する、というのは実現できなさそうでした。

1つのテーブルに複数ログの全カラムを用意して全データをロードしてみましたが、マージ処理はできないため全レコードがロードされます。

 

可視化

Druidに対応しているSupersetで可視化しました。SupersetにBrokerノードのインターフェースをセットすると、Superset側でDruidメタデータからスキーマ定義など読み込むことができます。

クエリは書けないため、Dimensionでのgroup byができるくらいです。

値を使った演算が必要なものはログに含める or task定義のメトリクスとして記述する必要があり、supersetのGUIからはできませんでした。

まとめ

というわけで、BigQueryによる分析の代替手段としてDruidを検証してみましたが750MB程度のデータロードに7時間程度かかってしまい、Druidクラスタ単体での運用は厳しいと判断せざるを得ませんでした。また、joinもできないため事前にデータ整形が必要となり、今のアーキテクチャでは運用コストが高そうです。

次回はDruidクラスタ + Hadoopクラスタで本格的な大規模データ処理も試してみたいと思っています。

]]>
[Strata Data Conference 2017 in New York] – Tutorials https://cyberagent.ai/blog/tech/3036/ Fri, 27 Oct 2017 06:20:04 +0000 https://cyberagent.ai/techblog/?p=3036 アドテク本部の水野です。

9/25 – 9/29にニューヨークで開催されたStrata Data Conference 2017に参加してきました.

Strata Data Conferenceは、機械学習、データ管理、ストリーム処理、分散処理といった、大規模データに関するトピックを幅広く扱うカンファレンスの1つです.この記事では本会議前日に受講させていただいたチュートリアルの内容を簡単に紹介したいと思います.

 

今回私が受講したのは下記のチュートリアルです.

Unraveling data with Spark using deep learning and other algorithms from machine learning,   Vartika Singh (Cloudera), Jeffrey Shmain (Cloudera)

一言で言うと,SparkMLで特徴量の抽出のための前処理、モデルパラメータの学習、モデルのバリデーションといった機械学習でよく用いられる操作が、大規模データにも適用可能な分散環境でも簡単に実現できますよという内容のチュートリアルでした.

以前からSparkにはMLlibという機械学習ライブラリが付属していたのですが、そのうちAPIがDataFrameベースのものをSparkMLと呼んでいるようです.ちなみにDataFrameは、Spark固有の分散処理向けのデータ構造であるRDDにスキーマの概念が追加されたもので、RDBのようにカラム名や型が付いています.

val session = SparkSession.builder().master("local[2]").getOrCreate() // SparkSessionの作成 (masterはここでは2スレッドのstand-aloneモードで起動していますが、ここは適宜)
val df = session.read.json("gs://spark-ml-test/data.json")            // DataFrameにJSONファイルをロード
df.printSchema()                                                     
df.show()

root
|-- name: string (nullable = false)
|-- age: integer (nullable = false)
|-- occupation: string (nullable = false)
|-- label: integer (nullable = false)
 
+-----+---+----------+-----+
| name|age|occupation|label|
+-----+---+----------+-----+
|Alice| 25| engineer | 1   |
| Bob | 22| student  | 0   |
|Cathy| 25| banker   | 1   |
+-----+---+----------+------

 

SparkMLでは各種TransformerやEstimatorを組み合わせることによって,DataFrameに対する処理として前処理やモデルパラメータの学習を実装することができます.機械学習で一般的によく使われるような処理は組み込みのコンポーネントとして実装されており,簡単なものならそれらを組み合わせるだけでも作ることができます.

例えば,カテゴリカル変数に対する前処理としてしばしば用いられるインデクシング処理は次のようにStringIndexerを使うだけで簡単に実現することができます.

import org.apache.spark.ml.feature.StringIndexer
val indexers = Seq("name", "age", "occupation").map { name => 
    new StringIndexer().setInputCol(name).setOutputCol(s"${name}_index") 
} 
val indexedDF = new Pipeline().setStages(indexers.toArray).fit(df).transform(df)
indexedDF.show()

+-----+---+----------+-----+----------+---------+----------------+
| name|age|occupation|label|name_index|age_index|occupation_index|
+-----+---+----------+-----+----------+---------+----------------+
|Alice| 25| engineer | 1   | 0.0      | 0.0     | 2.0            |
| Bob | 22| student  | 0   | 2.0      | 1.0     | 1.0            |
|Cathy| 25| banker   | 1   | 1.0      | 0.0     | 0.0            |
+-----+---+----------+-----+----------+---------+----------------+

このように,StringIndexer#setInputColに渡したカラムの内容を値ごとにindex値に変換したものが,StringIndexer#setOutputColで設定したカラムに格納されます.

ちなみに,StringIndexerなどのEstimatorはPipelineStageというtraitのサブクラスになっているので,上記の例のようにパイプラインとして一度に適用することもできるようになっています.

 

次に,後続の学習に使用する特徴量を選択し,一つのカラムにまとめる必要があります.

これもVectorAssemblerを使えば次のように簡単に実現できます.

import org.apache.spark.ml.feature.VectorAssembler
val assembler = new VectorAssembler()
   .setInputCols(Array("name_index", "age_index", "occupation_index"))
   .setOutputCol("features")

val assembledDF = assembler.transform(indexedDF)
assembledDF.show()

+-----+---+----------+-----+----------+---------+----------------+-------------+
| name|age|occupation|label|name_index|age_index|occupation_index| features    |
+-----+---+----------+-----+----------+---------+----------------+-------------+
|Alice| 25| engineer | 1   | 0.0      | 0.0     | 2.0            |[0.0,0.0,2.0]|
| Bob | 22| student  | 0   | 2.0      | 1.0     | 1.0            |[2.0,1.0,1.0]|
|Cathy| 25| banker   | 1   | 1.0      | 0.0     | 0.0            |[1.0,0.0,0.0]|
+-----+---+----------+-----+----------+---------+----------------+-------------+

この処理により, VectorAssembler#setInputColsで渡したカラム集合が,VectorAssembler#setOutputColで指定したカラム(ここではfeatures)にVectorとしてまとめられます.

 

ここまでで変換したDataFrameをモデルに入力として渡すことによって,学習を行うことができます.

モデルに関しても簡単なモデルなら組み込みのモデルが用意されているので,ハイパーパラメータを指定するだけで簡単に構築できます.

例えばロジスティック回帰だと次のようになります.

import org.apache.spark.ml.classification.LogisticRegression

val model = new LogisticRegression()
   .setMaxIter(50)              
   .setFeaturesCol("features")
   .setLabelCol("label")
   .fit(assembledDF)

あとは学習したモデルを予測したいデータ集合に対して適用するだけです.

ただし,入力するデータ集合には当然ですが,学習時と同じ前処理を施す必要があります.

ここまでは個々にに処理を適用してきましたが,実用では一連の前処理を一つのPipelineとしてまとめておいた方が良いと思います.

今回だと,カテゴリカル変数のインデクシング,特徴量のベクトル化の二つを前処理として行ったので,次のようにこれを一つのパイプラインにまとめます.

val pipeline = new Pipeline().setStages(features.toArray :+ assembler)

これを次のテストデータに適用し,予測値を計算します.

+------+---+----------+
| name |age|occupation|
+------+---+----------+
|Edward| 27| student  |
| Fox  | 25| engineer |
| Gille| 25| engineer |
+------+---+----------+

val predictionInputDF = pipeline.fit(testSet).transform(testSet)
val resultDF = model.transform(predictionInputDF)
resultDF.show()

+------+---+----------+----------+---------+----------------+-------------+--------------------+--------------------+----------+
| name |age|occupation|name_index|age_index|occupation_index| features    | rawPrediction      | probability        |prediction|
+------+---+----------+----------+---------+----------------+-------------+--------------------+--------------------+----------+
|Edward| 27| student  | 1.0      | 1.0     | 1.0            |[1.0,1.0,1.0]|[17.8648754105089...|[0.99999998256655...| 0.0      |
| Fox  | 25| engineer | 0.0      | 0.0     | 0.0            | (3,[],[])   |[-18.311224465270...|[1.11567127880168...| 1.0      |
| Gille| 25| engineer | 2.0      | 0.0     | 0.0            |[2.0,0.0,0.0]|[-17.268510850550...|[3.16505319192869...| 1.0      |
+------+---+----------+----------+---------+----------------+-------------+--------------------+--------------------+----------+

ここまでで一通り学習,予測まで動作するところまではできていますが,実運用では,モデルを一度作ったらハイパーパラメータをチューニングを行うと思います.SparkMLではこれを楽に行えるようにグリッドサーチを行うAPIも提供されています.

LogisticRegressionモデルだと,パラメータ更新時のイテレーション回数,目的関数の正規化項がグリッドサーチで調整できます.

val bestModel = new CrossValidator()
  .setEstimator(new LogisticRegression())
  .setEvaluator(new BinaryClassificationEvaluator())
  .setEstimatorParamMaps(
    new ParamGridBuilder()
    .addGrid(model.regParam, Seq(1.0e-4, 1.0e-3, 1.0e-2, 1.0e-1)) // 正規化項のハイパーパラメータ
    .addGrid(model.maxIter, Seq(50, 100))                         // イテレーション回数
    .build()
  ).setNumFolds(3)
  .fit(df)
  .bestModel                                                              // 最良のモデルを選択

これにより指定したハイパーパラメータの組を網羅的に評価した上で最適なモデルを選択することができます.

選択したモデルを予測に利用する手順は前述のものと同様です.

 

作ったモデルは次のようにファイルに保存することができます.

bestModel.save("gs://samplebucket/sample")

この処理を行うとSparkらしくparquetで出力してくれます.ファイルの内容は各モデルクラスに実装されているので,モデルによって異なりますが,Logistic Regressionだと次のような内容でした.

message spark_schema {
  required int32 numClasses;
  required int32 numFeatures;
  optional group interceptVector {
    required int32 type (INT_8);
    optional int32 size;
    optional group indices (LIST) {
      repeated group list {
        required int32 element;
      }
    }
    optional group values (LIST) {
      repeated group list {
        required double element;
      }
    }
  }
  optional group coefficientMatrix {
    required int32 type (INT_8);
    required int32 numRows;
    required int32 numCols;
    optional group colPtrs (LIST) {
      repeated group list {
        required int32 element;
      }
    }
    optional group rowIndices (LIST) {
      repeated group list {
        required int32 element;
      }
    }
    optional group values (LIST) {
      repeated group list {
        required double element;
      }
    }
    required boolean isTransposed;
  }
  required boolean isMultinomial;
}

numClasses = 2
numFeatures = 3
interceptVector:
.type = 1
.values:
..list:
...element = 18.311224465270886
coefficientMatrix:
.type = 1
.numRows = 1
.numCols = 3
.values:
..list:
...element = -0.5213568073602765
..list:
...element = -35.96680562628096
..list:
...element = 0.3120625578614303
.isTransposed = true
isMultinomial = false

 

上述の通り,SparkMLは予測時にも入力としてDataFrameを取るので,複数の入力に対して一括で予測値を計算するというような用途には向いていると思いますが,Bidderのようにリクエストが来るたびに予測値を出さないといけないような状況でそのまま使うのは難しいと感じました.parquetのreaderは様々な言語でライブラリが提供されているので,それを使って自前でモデルの予測計算を実装する必要が出て来そうです.その際,予測側も学習時と同じ前処理を実装しないといけないので,注意しましょう.(この辺りはあまり詳しくないので,良い方法があれば教えてください)

 

これらの具体的なSparkMLの使い方の話の他にも,モデルの並列学習の方式(Data Parallel, Model Parallel)が,それぞれどういう基盤に向いているのかといった話や,最近の分散機械学習周りで著名なライブラリについても軽く触れていたりと,俯瞰するには良いチュートリアルだったと思います.興味のある方は,ぜひ一度資料の方にも目を通してみてください.また,SparkML周りのサンプルコードもこちらで提供されているので,ぜひご覧ください.

 

BigDLはカンファレンス全体としてもやや推されているという印象を受けたので,こちらも別の機会に調査してみたいと思いました.

]]>
[Strata Data Conference 2017 in New York] – Sessions Day1 https://cyberagent.ai/blog/tech/10203/ Fri, 27 Oct 2017 06:18:58 +0000 https://cyberagent.ai/techblog/?p=3041 アドテク本部の成尾と水野です。
 

まずは、セッションの1日目について
成尾から、初日に見たセッションの中で気になったものを取り上げます。

 

Geospatial big data analysis at Uber 

ご存知の方も多いと思いますが Uber という配車サービスの事例です。

ユーザーが出発地、目的地をWebやアプリから入力しドライバーが迎えにきて目的地まで連れていく、いわゆるタクシーですが
ドライバーは自家用車を利用したり、ユーザーとドライバーが双方に評価をつけたり、とタクシーとは違った特徴もあります。

2009年3月にアメリカで設立された Uber は、2013年11月に日本でもトライアルを実施し
2014年8月から都内全域でサービスをしている事からも利用したことがある人もいるのではないでしょうか。
現在は6大陸、73ヵ国、633都市でサービスを行っています。

このセッションでは、 Uber として重要な地理空間のビッグデータ分析についてでした。

地理空間のデータは
出発地、目的地を選択すると、その地点までの最適なルートを計算したり
都市の場所、旅行情報、イベント情報などビジネスの意思決定を改善する情報 etc
非常に重要で、分析基盤のアーキテクチャとしては

膨大なデータを、Kafka や RDB ( MySQL, Postgres ) から集め
ストリーム処理、リアルタイム分析( Flink, Pinot, MemSQL ) し HDFS にデータが貯める(データレイク)
そのHDFSからバッチ、ETL処理はHiveとSpark、リアルタイム処理に Presto を実行しているとの事でした。

Presto を採用した理由は、そのアーキテクチャと速度の面から
また、複数のデータソースからコネクタを利用して、複製なしにデータを利用できるという点との事です。
後述で、GeoSpatial Plugin for Presto の事にも触れていたので、ここら辺も決め手になっている気がします。

データモデルは
単純な緯度経度を使った、点で表すポイントいうデータ

POINT (77.3548351 28.6973627)

面で現すポリゴンというデータに分かれていて

POLYGON ((36.814155579
-1.3174386070000002, 36.814863682
-1.317545867, 36.814863682
-1.318221605, 36.813973188
-1.317910551, 36.814155579
-1.3174386070000002))

Uber ではポリゴン、マルチポリゴンからジオフェンスを作っているので
ジオフェンス内を移動しているユーザーに対してプロモーションを行う事もできます。

過去はポイントとジオフェンスをもとに
総当たりで Hive/MapReduce の計算を行いイベント回数を計算していたため膨大な時間がかかっていたところを
QuadTree (四分木) を使うことで最適化(この時点で最初の 15 倍高速)

さらに Presto を利用したり、クエリを最適化したり
GeoSpatial Plugin など使うことにより、最終的に 60 倍高速になったとのことです。

昨年の Starata でも Presto を使って高速化していますという事例や
Presto での最適化はこのような手法でしていますというセッションはいくつかあったのですが
今回は、地理空間データ(位置情報など)に対しても GeoSpatial Plugin というものをしれたり
実際に Hive で遅かった処理が 60 倍まで速くなる というところや、四分木アルゴリズムを使っているところは興味深かったです。

 

次に水野から, 興味深かったセッションのみ抜粋して紹介します.

 

Working smarter, not harder: Driving data engineering efficiency at Netflix 

サービスの運用コストを削減するためにデータプラットフォーム周りをどのように整備すべきかが紹介されていました.

  • Data Catalog周り
  • Data Qualityの検知
  • Configuration調整のサポート

等が作り込んであるようです.

DataCatalogはMetaCatを使って実現しているとのことでした.データの所在やLineage(データの出自情報)、テーブルへのアクセス状況を管理しており、データの重要度を計測してユーザにレコメンドしたりすることで利用効率を高めているようです.データセットの数が増えてくるとこういう取り組みも必要になってくるだろうなと思いました.

Data Quality周りはデータを取り込んだあとのパイプラインでデータの分布を集計してAnormaly detectionをにかけており異常があれば検知できる仕組みを作っているようです.

Configuration周りの話では、実行時間など設定を一緒に可視化しており、最適なConfigurationはどれだったのか、何故実行が遅いのかなど考察できるサポートをしているようでした.

 

Rethinking data marts in the cloud: Common architectural patterns for analytics

こちらは,コンピュートとストレージの分離周りのトレードオフにまつわるセッションでした.

簡単にまとめると,S3のような外部のオープンストレージにデータを配置する場合,

  1. ディスク領域確保の目的にクラスタノードをスケールさせる必要がない (純粋にコンピューティングリソースがたりない場合のみスケールすればよくなる)
  2. 様々なコンポーネントからアクセスでき,データのサイロ化が防げる

というメリットがある一方で,ローカルディスクに書き出す場合と比べるとどうしても書き出し性能は下がってしまうので,ここにはトレードオフの関係があるという話です.

ただ,以前QConでも類似した内容のセッションがあり,こちらの方が説明も丁寧でわかりやすいのでオススメです.

https://www.infoq.com/presentations/netflix-big-data-infrastructure

7:43から13:54ぐらいまでで,同様の内容に関して議論しています.データを処理しているパイプライン全てでS3に保存するのではなく,パイプラインの途中まではローカルのHDFSに書き出しておいて,処理が終わったデータのみS3に書き出すようにすることで,パフォーマンス上のデメリットはかなり緩和できると述べられています.確かに,自分もこれぐらいの使い方をしておくのががバランスがよいのかなと感じました.

上記の内容に加えてS3はオブジェクトのバージョニング機能も使えるので,データが安全に管理できるという話があったりと,他にも参考になる話がたくさん入っているので,こちらもぜひご覧ください.

 

End-to-end data discovery and lineage in a heterogeneous big data environment with Apache Atlas

こちらは,Apache Atrasを使ってリッチなデータカタログを構築したという内容のセッションでした.

ここでは,データの所在,出自,スキーマを一元管理しており,どこにどのようなデータがあるのか一目でわかるようになっているとのことです.

データのスキーマとして統一的にAvroのスキーマを利用しており,私の所属するグループで構築している基盤と類似したアプローチをとっていたので非常に親近感が湧きました.ちなみにAtlasにはもともとAvroのスキーマを管理する機能はないので,拡張機能として独自開発したそうです.

UIでスキーマの登録や閲覧時のドリルダウンをサポートしていたり,各フィールドに必ず注釈をつけるようにしているという部分は非常に参考になりました.

また,ユーザビリティと堅牢性のトレードオフに関する議論もあり、SSOT for metadata and lineage, MVOT for dataという考え方が紹介されていました.サイズが大きく、移動させるコストが大きいデータ本体は利便性を考慮して複数散らばっているような状態も許容する.ただし、そのメタデータはSingle Source of Truthとして一元管理すべきという方針で運用されているようです.

出展ブースにもいくつかデータカタログ周りの製品が展示されていたりと,データガバナンス周りの話は,カンファレンス全体を通してホットな分野になってきているなと感じました.

]]>
db tech showcase Tokyo 2016 でデータ分析基盤について話してきました https://cyberagent.ai/blog/tech/888/ Thu, 14 Jul 2016 01:00:28 +0000 https://cyberagent.ai/techblog/?p=888 アドテク本部の成尾です。
先日、db tech showcase Tokyo 2016 で サイバーエージェント アドテクスタジオの次世代データ分析基盤紹介 というタイトルで話をしてきました。

現在社内で進めている Ganesha Project という分析共通基盤を構築するプロジェクトがあるのですが
このプロジェクトがなぜ必要とされたのか、という過去の分析環境と問題点から始まり
実際にどういった視点で選び構築していったのか、今後さらにどういう機能拡張を予定しているのかという話をしてきました。

13619834_1042476639121045_7763337678816206913_n

これまで非公開の部分も多かったアドテク本部の分析基盤のサーバースペックについても
今回のスライドの中では数点含まれていますので、参考になれば幸いです。

また今後の動向についても、他の機会や本ブログで公開していく予定です。

]]>
CROSS 2016 で話してきました https://cyberagent.ai/blog/no_use/dwh/487/ Sat, 06 Feb 2016 01:00:07 +0000 https://cyberagent.ai/?p=487 CROSS 2016にてGoogle BigQuery × Amazon Redshiftについてパネルディスカッション形式で話をしてきました。

Bigquery, Redshift両方を使い分けしているアドテクスタジオとして
どちらも使った事ない方に向けた、使い方の事例や特徴を書いた内容ですので
既に使われている方からしたら内容が薄い部分もあるかと思いますが、少しでも参考になれば幸いです。

cross_2016_1

[slideshare id=61937603&doc=googlebigqueryamazonredshift-160512083752]

当日は、グーグル株式会社の佐藤さんからもBigQueryの突っ込んだお話や
実際にBigQueryを使われている中村さん、Redshiftを使われている佐長さんのお話など聞けて後半はパネラー同士の質問も飛び交っていました。

今後さらにブログ上ではDWHの話は詳しく取り上げていく予定です。

]]>