コンサルでデータサイエンティスト

仕事でPythonを書いてます。機械学習、Webマーケティングに興味があります。趣味は旅です。

Scala SparkのUDFとウィンドウ関数を使って緯度経度データから2点間の距離を求める

Sparkの練習として、スマホで計測した位置情報データから2点間の距離を求める処理をScala sparkで実装してみました。本記事は、SparkのDataFrame API または DataSet APIでUDFやウィンドウ関数を使ってみたい方を対象としています。この記事で紹介されるコードはScalaで書かれていますが、Pysparkで書きたい方もほとんど同じ実装で書くことができるため参考にしてみてください。

ウィンドウ関数で1レコード前のデータを読み込む

Sparkではウィンドウ関数(window functions)という機能を使うことができます。これはSQLなどにも実装されている機能で、対象レコード以外のレコードを参照することができるものです。

今回は、以下のようなデータがあったときに、対象レコードの前時刻の緯度経度と対象レコードの緯度経度の距離を求めます。

locData.show()
| timestamp      | lat       | lon        |
| 2018/2/1 10:00 | 35.680923 | 139.766051 |
| 2018/2/1 10:05 | 35.681236 | 139.764362 |
| 2018/2/1 10:07 | 35.680232 | 139.762944 |
| 2018/2/1 10:08 | 35.678161 | 139.762836 |
| 2018/2/1 10:08 | 35.680496 | 139.762686 |
| 2018/2/1 10:10 | 35.681301 | 139.762971 |

locDataには各時刻の緯度経度データしかないため、ここに前時刻の緯度経度のカラムを追加することで、レコードごとに前時刻との距離を求めることができます。

まずウィンドウ関数を次のように定義します。

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

val w = Window.orderby("timestamp")


今回は1人のユーザ分のデータを使うためtimestamp順にデータを並べるだけですが、複数ユーザがいる場合は以下のようにパーティションを切ってウィンドウを定義します。

val w = Window.partitionBy("userId").orderBy("timestamp")


上記で定義したウィンドウ関数を使って、前レコードの緯度経度カラムを次のように追加します。

val locDataWithPrev = locData.withColumn("prev_lat", lag($"lat", 1).over(w)).withColumn("prev_lon", lag($"lon", 1).over(w))
locDataWithPrev.show()
| timestamp      | lat       | lon        | prev_lat  | prev_lon   |
|----------------|-----------|------------|-----------|------------|
| 2018/2/1 10:00 | 35.680923 | 139.766051 | null      | null       |
| 2018/2/1 10:05 | 35.681236 | 139.764362 | 35.680923 | 139.766051 |
| 2018/2/1 10:07 | 35.680232 | 139.762944 | 35.681236 | 139.764362 |
| 2018/2/1 10:08 | 35.678161 | 139.762836 | 35.680232 | 139.762944 |
| 2018/2/1 10:08 | 35.680496 | 139.762686 | 35.678161 | 139.762836 |
| 2018/2/1 10:10 | 35.681301 | 139.762971 | 35.680496 | 139.762686 |

これでデータの下準備は終わりです。このデータにUDFを適用することで2点間の距離を求めていきます。

Spark UDFで2点間の距離を求める

SparkにはScala spark, PysparkともにUDF(User Defined Functions)という概念があります。Spark UDFとは、
Spark上でユーザが自由に定義することができる関数を表しています。ウィンドウ関数のlagはあらかじめ定義されていた関数であるのに対して、UDFでは"lat", "lon", "prev_lat", "prev_lon"の4カラムを入力として2点間の距離を出力するような複雑な関数を実装することができます。


まずは、2点の緯度経度を入力として距離を求めるScalaメソッドを次のように定義します。ここでは、haversine formulaという式を使って2点間の距離を求めます。(参考:Haversine formula - Wikipedia)

def getDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
  val r: Long = 6371
  val lat_dist = scala.math.toRadians(lat1) – scala. .math.toRadians(lat2)
  val lon_dist = scala.math.toRadians(lon1) – scala. .math.toRadians(lon2)
  val h = scala.math.pow(scala.math.sin(lat_dist/2), 2) + scala.math.cos(lat1) * scala.math.cos(lat2) * 
  scala.math.pow(scala.math.sin(lon_dist/2), 2)
  scala.math.asin(scala.math.sqrt(h)) * 2 * r * 1000) // メートル


次に、このメソッドをUDFとして次のように定義します。
このステップを行うことで、Sparkの処理内にUDFを適用することができます。

import org.apache.spark.sql.functions.udf
val getDistUdf = udf(getDistance _)


最後に各レコードに対して前時刻のレコードとの距離を求めた結果のカラムを追加することで、求めたかった2点間の距離を求めることができます。

val locDataWithDist = locDataWithPrev.withColumn(getDistance($"lat", $"lon", $"prev_lat", $"prev_lon"))
locDataWithDist.show()
| timestamp      | lat       | lon        | prev_lat  | prev_lon   | dist    |
|----------------|-----------|------------|-----------|------------|---------|
| 2018/2/1 10:00 | 35.680923 | 139.766051 | null      | null       | null    |
| 2018/2/1 10:05 | 35.681236 | 139.764362 | 35.680923 | 139.766051 | 156.472 |
| 2018/2/1 10:07 | 35.680232 | 139.762944 | 35.681236 | 139.764362 | 169.902 |
| 2018/2/1 10:08 | 35.678161 | 139.762836 | 35.680232 | 139.762944 | 230.471 |
| 2018/2/1 10:08 | 35.680496 | 139.762686 | 35.678161 | 139.762836 | 259.993 |
| 2018/2/1 10:10 | 35.681301 | 139.762971 | 35.680496 | 139.762686 | 93.14   |

まとめ

Scala SparkのUDFとウィンドウ関数を使って緯度経度データから2点間の距離を求めるコードを実装しました。SparkのUDFとウィンドウ関数の使い方について理解していただけたかと思います。ウィンドウ関数にはlag以外にもさまざまな関数が用意されているため、さまざまな応用ができます。パフォーマンスが求められるような場面では、処理が重くなってしまう可能性があるため注意が必要です。ウィンドウ関数については今後の記事でまとめたいと思います。

Scalaでパッケージをインポートする

Scalaでパッケージおよびライブラリをインポート(import) する方法についてご紹介します。
Scalaに入門したばかりの私が、次の入門書を参考にしながらまとめました。

実践Scala入門

実践Scala入門



パッケージのインポート

Scalaでは、Javaと同様にクラス、オブジェクト、トレイトなどは必ず何らかのパッケージに所属します。パッケージのインポートは他のプログラミング言語と同じく、プログラムの冒頭で行われます。
ドメイン名を逆順にしたものをパッケージ名とするのが一般的な慣習です。

例えば、SparkのWindow関数を使うためのクラスをインポートする方法は以下の通りです。

import org.apache.spark.sql.expressions.Window

この例では、Windowという名前で org.apache.spark.sql.expressions.Window を参照することができるようになります。


ここで、Windowという同じ名前のクラスがインポートされていて衝突してしまった場合にはどのようにすればいいでしょうか?
Scalaではインポート時の名前衝突を以下のように防ぐことができます。

import org.apache.spark.sql.expressions.{Window => SparkWindow}


また、特定のクラスだけでなく、あるパッケージ配下に所属するすべてのクラスやオブジェクトをインポートしたいときには以下のようなインポート文を書くことができます。

import org.apache.spark.sql.expressions._

このようなインポートはワイルドカードインポートなどと呼ばれます。

まとめ

Scalaでパッケージをインポートする方法についてまとめました。Javaに馴染みがある方にとっては特に違和感のない内容となっていますが、他の言語から始められた方はこの機会にインポートの仕方を確認してみてください。

【Scala入門】 ScalaでFizzBuzz問題を解く

Apache Sparkの学習の一環としてScalaに触れてみることにしました。
本記事では、Scalaの基本的な構文を確認した後にFizzBuzz問題を解いていきます。

SparkとScalaの関係性について詳しく知りたい方はこちら
hktech.hatenablog.com


FizzBuzz問題とは

FizzBuzz問題とは1から100までの数字について、3で割り切れる場合に”Fizz”、5で割り切れる場合に”Buzz”、両者で割り切れる場合に”FizzBuzz”、それ以外の場合はその数字を出力させるプログラミング問題です。

調べてみたらもともとは英語圏で遊ばれている言葉遊びらしいです。

プレイヤーは円状に座る。最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」(Bizz Buzzの場合は「Bizz」)、5で割り切れる場合は「Buzz」、両者で割り切れる場合(すなわち15で割り切れる場合)は「Fizz Buzz」(Bizz Buzzの場合は「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

ja.wikipedia.org


いや、ただの宴会ゲーム。難しそう。


Scalaのプログラミング構文

いきなりScalaの実装に入る前に、基本的なプログラミング構文を確認しておきます。

FizzBuzz問題で必要となるのは大きく3つです。

  • 1から100までの数字のリスト(配列)
  • リストをまわすためのfor文
  • 条件分岐するためのif-else文

リスト

まずはリストの構文について確認していきます。

1から3までのリストを作ります。

$ List(1, 2, 3)
 res: List[Int] = List(3, 1, 4)


直感的ですね。てかScalaって型推論あるのか、、、確かに型宣言はしてないですよね。

次に1から100のリストを作ります。さすがに1から100まですべて書き出すわけにはいかないですよね。
そこで、Rangeというものを使います。これはPythonと同じですね。

$ Range(1, 101)
 res: scala.collection.immutable.Range = Range 1 until 101


できてそうですね。これをリスト型にします。

$ Range(1, 101).toList
 res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)


こんな書き方もできます。

$ (1 to 100).toList
 res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)

for文

Scalaにおけるfor文の書き方についてみていきます。

こんな感じです。簡単です。

$ for (s <- Array("a", "b", "c")) println(s)
 a
 b
 c



それでは前のセクションで学んだリストを使い、1から100を出力してみましょう。

$ for (s <- (1 to 100)) println(s)
 1
 2
 3
 4
 ..........
 
 99
 100


できました。
あとは条件分岐にしたがって、”Fizz” や “Buzz” を出力するようにしていきましょう。

if-else文 / match文

Scalaにおけるif-else条件分岐の書き方について確認していきます。

if-else文は次のように書きます。

$ val i = 3
$ if (i % 2 == 0) print("even") else print("odd")
 odd

また、他の言語のswitch文にあたる構文として、Scalaにはmatch文があります。

var num = 1
num match{
    case 1 => println("one")
    case 2 => println("two)
    case _ => println("other")
}


ScalaFizzBuzz問題を解く

FizzBuzz問題に必要なScalaプログラミング構文を完全に理解したところで、早速実装をしてみました。

for (s <- (1 to 100))
    if (s % 15 == 0)
        println("FizzBuzz")
    else if (s % 3 == 0)
        println("Fizz")
    else if (s % 5 == 0)
        println("Buzz")
    else
        println(s)

無事にFizzBuzz問題が解けました!
コーディング面接でFizzBuzzを書く機会があれば間違いなくScalaで書くでしょう。

もっといい実装方法があるか
こちらの記事では、関数をつくって次のように実装しています。

def fizzBuzz(i: Int): String = {
    val fizz = i % 3 == 0
    val buzz = i % 5 == 0
 
    if (fizz && buzz) "FizzBuzz"
    else if (fizz) "Fizz"
    else if (buzz) "Buzz"
    else i.toString()
    }
   
for (i <- 1 to 99) println(fizzBuzz(i))


さらにScalaっぽく書くパターンとして、次のように処理をチェーンするような書き方があります。

def fizzBuzz(i: Int) = {
  1 to i
} map {
  case i if i % 15 == 0 => "FizzBuzz"
  case i if i % 3 == 0 => "Fizz"
  case i if i % 5 == 0 => "Buzz"
  case _ => i
} foreach {
  s => println(s)
}

fizzBuzz(100)

まずリストを作った上で、その要素をmapで加工し、最後に出力の処理を行います。


最後はmatch文を用いた実装です。こちらが最もシンプルでわかりやすいですね。

def fizzBuzz(i: Int) = i match {
  case i if i % 15 == 0 => "FizzBuzz"
  case i if i % 3 == 0 => "Fizz"
  case i if i % 5 == 0 => "Buzz"
  case _ => i.toString()
}



まとめ

ScalaFizzBuzz問題にチャレンジしてみました。Scalaの基本的なプログラミング構文を活用し、さまざまな実装パターンについて確認しました。Scalaは複雑なプログラミング言語であるというイメージがありましたが、意外にシンプルに書くことができました。引き続きScalaに関する記事を書いていきたいと思います。

Apache Spark: PythonとScalaのどっちを使うべきか比較する

データサイエンスプロジェクトで Spark を使う場合、必ず議論に上がるのがPythonScalaのどちらのプログラミング言語を採用すべきかということです。

Sparkは元来Scalaで書かれているため、Scalaで処理コードを書いていくのが直感的にも自然なことです。しかし、データサイエンティストの多くはPythonに親しみがあるため、Sparkから取得したデータに対してPythonで書いた機械学習モデルや処理コードをそのまま適用したいということがあります。

そこで、近年台頭してきたのがPySparkです。PySparkとは、PythonApache Sparkを実行するためのAPIです。PySparkの発展により、PythonによるSparkの扱いやすさは劇的に改善してきており、ScalaPythonのどちらを使うべきかという論争は大きな盛り上がりをみせています。

本記事では、PythonScalaのどちらでSparkを扱うべきかということについて、それぞれのメリットとデメリットを紹介しながら比較していきます。

apache_spark


Apache Spark とは

どのプログラミング言語を扱うべきかという比較に入る前に、Apache Sparkの概要について確認していきましょう。

Apache Sparkとは大量のデータに対して高速に分散処理を行うOSSフレームワークです。APIとしてはPython, Java, Scala, R などのプログラミング言語が用意されています。大規模データを扱う分散アプリケーションを開発する際にはSparkの利用が必ず検討されるといってもよいでしょう。

類似の分散処理フレームワークとしてHadoopがありますが、HadoopJavaで書かれているのに対して、SparkはScalaで書かれています。また、HadoopとSparkの大きな違いとして、HadoopではMapReduceにおける入出力のたびにストレージにアクセスする必要があったのに対して、Sparkではデータをメモリに保存することで高速化を図っています。これはインメモリ処理と呼ばれ、機械学習などのように入出力処理が頻繁に発生するようなアプリケーションでは実行性能が100倍程度改善することもあります。

www.graffe.jp


Apache SparkではSpark MLという機械学習ライブラリが備わっているのも特徴です。これによって、ユーザは複雑な分散処理を考えることなく、高速で動作する機械学習手法を実装することが可能です。

Python vs Scala

Sparkを扱う際に、PythonScalaのどちらを選択すればよいのでしょうか。それぞれのメリットとデメリットを比較していきます。

実行速度

まずは、PythonScalaを実行速度の観点で比較します。

平均的に、ScalaPythonより約10倍の速さで実行することが可能です。Scalaは実行時にJava Virtual Machine (JVM) を使用するのに対して、Pythonは動的型変換を伴うインタプリタ言語であるため、その実行速度の差は歴然です。さらにSparkのライブラリはすべてScalaで書かれているため、Pythonでこれらを使用しようとすれば、Scalaそのものでライブラリを扱うよりは実行速度が遅くなることは明らかです。

したがって、実行速度の観点ではScalaが圧倒的に優位です。

扱いやすさ

次にPythonScalaを扱いやすさの観点で比較していきます。

まずは、実装のしやすさという点では、Pythonに圧倒的な軍配が上がります。ご存知の通り、Pythonは学習コストが低いプログラミング言語のひとつとして知られており、データを扱う多くのエンジニアやデータサイエンティストはPythonに慣れているということがあります。一方で、ScalaをマスターするためにはJavaの基本的な理解が必要であるため、情報系出身者ではない多くのデータサイエンティストがここで脱落します。さらに厄介なことに、Javaに習熟しているものでもScalaの特殊な構文に慣れるには時間がかかるといわれています。このようなことから、チーム全体の実装スピード運用コストという点でもPythonのほうが優れているといえるでしょう。

Scalaの実装が簡単ではないということを説明しましたが、Scalaを扱えることができればSparkのフレームワークをより簡単に活用することができます。SparkのライブラリはScalaAPIコレクションを活用しているため、これを理解しておけば内部的な動作を把握することができるほか、用途に応じた修正をすることが可能です。また、動的型変換を行うPythonと比較すると、Scalaは静的に型が定義されるため、コンパイル時にエラーを発見することができるという点で安全性が高いです。

結論

実行速度では、ScalaのほうがPythonより早く、扱いやすさという点では、Pythonのほうがやや優勢であるということがわかりました。

結局、PythonScalaのどちらを使えばよいのでしょうか?

例えば、大規模データを処理してレポーティングをしたいという場合にはPythonが有効です。商用システムでない場合、実行速度は大きな問題ではないため、すばやく実装でき、可視化ライブラリなどが充実しているPythonを選ぶのがおすすめです。特に、機械学習系の案件であれば、データサイエンティストの多くはPythonに慣れているはずなので、こちらを使うのが無難でしょう。

商用のシステムでSparkを活用したい場合、PythonScalaのどちらを選ぶかは会社の形態やリソース状況によって変わると思います。

事業会社で、大規模データを扱うシステムまたはサービスを長期的に開発する場合はScalaがよいでしょう。学習コストがかかってしまうものの、Sparkを本質的に理解するためにはScalaの理解は欠かせないほか、パフォーマンス観点や最新のSparkライブラリにアクセスしやすさからもScalaを選択することが賢明です。

一方で、コンサルや短納期での実装が求められるような会社においてはPythonを選択するのがよいでしょう。特に、受注をするような場合は保守がしやすく、実装できる人材が多いプログラミング言語を選択することが一般的であり、Sparkにおいてもこれは当てまります。


まとめ

Apache Sparkを扱う際にPythonScala のどっちを使うべきかということについてまとめました。実行速度や扱いやすさの観点から、それぞれのメリットとデメリットがあることがわかりました。

Pythonコモディティ化してきている部分があるので個人的にはScalaに挑戦してみるのもよいかと思います。

コンサルでデータサイエンティストとして働く

コンサルティングファーム(会社)におけるデータサイエンティストの求人が近年増加しています。かつては経営戦略などに力を入れていた外資コンサルティングファームや、大規模なシステム開発などに強みがあった IT 系コンサルティング企業も、デジタル領域でのデータを活用したビジネスに大きな期待を寄せています。本記事ではコンサルタントとデータサイエンティストの役割の違いについて触れながら

  • コンサルでデータサイエンティストとして働くために必要なスキル
  • データサイエンティストが求められている理由

ということについて、実際にコンサルでデータサイエンティストとして働いている立場からご紹介します。コンサルにデータサイエンティストとして入社したい就活中の学生や、転職を考えている方にとって少しでも参考になれば幸いです。


目次

データサイエンティストとは

データサイエンティストの定義は個人や企業によって解釈が様々となっています。専門職としてのデータサイエンティストの役割は、統計学または機械学習 ×プログラミングによる課題解決をすることであると考えています。


Excelでピボットテーブルを組んでクロス集計をしたり、BIツールでユーザーの行動を解析するといった仕事もデータアナリティクスの重要な仕事ではあるものの、これらはコンサルティングファームコンサルタントであれば順当に持っているスキルであるため、このような仕事を専門に行う職種は本記事におけるデータサイエンティストのスコープ外とします。


data_scientist

データサイエンティストの分類

データサイエンティストとは大きくデータアナリスト機械学習エンジニアの2種類の職種に分けて考えることができます。

machine_learning_engineer

データサイエンティストと機械学習エンジニアは別物として解釈する場合もありますが、コンサルでははっきりと区別されていないことが多いです。そもそもコンサルティングファームにおいては、データアナリストの方が機械学習エンジニアよりもはるかに数が多いというのがその理由であるともいえます。しかし、人工知能を画像認識システムやチャットボットのサービスなどに実装していくような案件が増えつつある昨今のコンサル界隈では、後者の機械学習エンジニアへの需要も高まっています。ここでは、データアナリストと機械学習エンジニアの違いについて説明していきます。


データアナリストとは、データ分析で得た示唆を通してビジネス上の意思決定を支援する職種です。彼らはクライアントの業務やサービスを深く理解し、クライアントと密にコミュニケーションをしながら分析を企画、設計、実行していきます。場合によっては、データ分析で得た示唆を施策に落とし込んでいくサポート役を担うこともあります。

機械学習エンジニアとは、サービスやシステムに機械学習を用いた機能を実装していく職種です。彼らは機械学習モデルを商用のサービスやシステムに耐えうるレベルで実装を行います。人工知能やAIと言われるようなシステムの開発は彼らが担っていると考えてよいでしょう。


これらの2つの職種は、重複があるものの求められているスキルが微妙に異なります。データサイエンティストに必要なスキルにはどういったものがあるのでしょうか。


データサイエンティストに必要なスキル

データサイエンティストに必要なスキルは、一般的に以下の3軸で考えることができます。

  1. 機械学習・統計
  2. ビジネス(コンサルティングスキル・コミュニケーション力)
  3. エンジニアリング(プログラミング力・インフラの知識)


data_scientist_skill


優れたデータサイエンティストはこの三角形に示されたスキルを一定レベルで満たしていることが条件となります。機械学習・統計はデータアナリスト及び機械学習エンジニアの両方に必要な必須スキルであるものの、データアナリストは左側のビジネス寄り機械学習エンジニアは右側のエンジニアリング寄りといったように、多少のスキルの偏りがあることが一般的です。


機械学習・統計のスキル

機械学習・統計のスキルとは、データを適切に解釈し、様々な機械学習や統計的手法のなかから問題解決に結びつくものを取捨選択し、実装・分析を進めていく力です。こちらはデータサイエンティストとして当然持っておくべきスキルであり、これが欠けている場合は他のエンジニアやコンサルタントとの違いが無いと言えるでしょう。基本的な数学の知識から、統計学機械学習に関する知識に加え、最新の論文を読解するリサーチ力や特定の分野(自然言語処理、画像認識、異常検知、時系列データなど)に関する理解など、多くが求められています。しかし、データ分析のみで価値を生み出すことは年々難しくなっているため、近年では機械学習や統計のスキルが突き抜けているからといって、一流のデータサイエンティストを名乗れるような状況ではなくなってきています。

② エンジニアリングスキル

エンジニアリングスキルとは、プログラミングやサーバ・データベースなどのインフラに関する知識をはじめとしたスキルを指します。イメージとしては、情報工学科の授業で学ぶような内容に加え、実務で使用するWebフレームワークや、AWSGCPなどのクラウドサービス、ビッグデータを扱うソフトウェア(Spark, Hadoop, Hive)に関する知識が求められています。

③ ビジネススキル

ビジネススキルとは、データ分析を通して顧客に価値を提供するスキルを指します。事業会社であれば自社サービス、コンサルであればクライアントのサービスやドメインについて深く理解した上で、社内外とコミュニケーションをとりながらデータアナリティクスプロジェクトを円滑に進めることが求められています。また、データ分析を完璧に理解していないような方に対して、分析の方針や結果をわかりやすく説明するといった能力も必要です。




コンサルでデータサイエンティストとして働く場合、高いビジネススキルは当然のように求められることが多いです。機械学習・統計やエンジニアリングのスキルについては、関わる案件によって求められるレベルに大きな幅があります。案件ごとに技術を素早くキャッチアップできるような高い学習意欲や、手戻りが発生しないように分析を設計できる力が重要視されるといってよいでしょう。


コンサルでデータサイエンティストが求められる理由

データサイエンティストという職種は、幅広いスキルセットと深い知見が求められるということがわかりました。このような役割が、なぜいまコンサルティングファームで求められているのでしょうか。
コンサルでデータサイエンティストが必要とされている理由について考えていきます。


コンサルタントとは従来よりクライアントの課題に対して、深い知見と高いスキルによって解決策を提示し、変革を推し進めるという役割を担っています。コンサルにおけるデータサイエンティストはデータ分析という強力な武器をもったコンサルタントです。

データ分析が課題解決のひとつの手段として確立した背景には、冒頭でご紹介した通り、国内・国外のコンサルティングファームの売上の多くが経営・戦略コンサルティング事業から、デジタルやITといったドメインでのソリューション提供といった事業にシフトしているということがあります。そのような状況のなかで、昨今のデータ分析や人工知能ブームが火をつける形で、データ分析や機械学習システムの開発に関連する案件が急激に増えています。例えば、従来ではExcelなどで行っていた要因分析は、PythonやRによる多変量分析に取って代わられたり、多くのコンサルティングファームが得意としていたオペレーションの自動化は、機械学習を含むアルゴリズムによってより高度化されています。

また、より上流の意思決定に携わるコンサルティングにおいてもデータという武器は極めて有効です。クライアントの役員になにかを提案しようとするとき、その業界に関する知識では到底勝つことができません。しかし、データによる示唆を説明すれば新たな発見を与えることができます。客観的な情報であるデータとクライアントのドメイン知識を繋ぐ役割として、データサイエンティストが必要不可欠な存在となっています。

データサイエンティストとしてコンサルに就職する

コンサルティングファームでいかにデータサイエンティストが求められているかということが伝わったかと思います。読者の方の中には、データサイエンティストとして就職先を探している学生の方や、転職先の候補としてコンサルでデータサイエンティストを視野に入れている方もいるでしょう。


データサイエンティストは多くのスキルや知識を必要とする職種です。しかし、就職や転職をするタイミングですべてのスキルが必要であるというわけではありません。多くの業界でデータサイエンティストが不足している状況にあり、未経験に近い形で採用される方も会社によっては一定数いると聞きます。コンサルティングファームでは、多くの場合にプロジェクトにいきなりアサインされることが多いため、最低限のスキルとして以下は身につけておきたいです。


私自身は paiza というオンライン学習サイトを中心に python を学びました。現在では、UdemyAidemyといったデータ分析や機械学習プログラミングに特化したオンラインコースを提供するサイトも増えてきているので、一通りやってみるというのもよいかもしれません。上記スキルに加えて、学習意欲が最も大切です。これがあればコンサルティングファームでもやっていけるでしょう。

データサイエンティストとしてコンサルで働くメリット

データサイエンティストは多くの会社で求められていますが、コンサルでデータサイエンティストとして働くメリットとして、多様な業界のデータ分析プロジェクトに触れることができるということがあります。また、プロジェクトベースで動くため、自らが立ち上げメンバーとなってより実践的なスキルが身につけることができるという利点もあります。

一方で、多くのコンサルティングファームでは分析だけでプロジェクトが終わってしまい、実装に入れないケースも多々あります。先にも述べた通り、まだまだ機械学習のエンジニアリングが強くない会社も多く、開発は外注していることも多々あります。

したがって、現時点でデータサイエンティストとして機械学習エンジニア寄りのスキルを伸ばしていきたい方に、コンサルでデータサイエンティスト職に就くことは強くおすすめできません。そのような方は、ウェブ系のサービスを持ったメガベンチャーで働くほうが、エンジニアリングスキルを成長させていくことができるように思います。(あくまで、個人的な意見です)


まとめ

コンサルティングファームでデータサイエンティストとして働く立場から、データサイエンティストという職種の役割や、データサイエンティストが求められている背景についてご紹介しました。データサイエンスに必要なスキルは多岐にわたりますが、コンサルティングファームではプロジェクトごとに必要なスキルを身につけていくのが一般的です。本記事を読んで、データサイエンティストという職種に興味を持っていただける方がいれば幸いです。

pandasのTimestampで決まった範囲の時刻リストを作る

Python で日時関連のデータ操作をするときにdatetime モジュールを使用されている方も多いと思いますが、pandasを使ったTimestamp関連の操作が便利なので個人的にはおすすめです。本記事は時刻を含むデータを処理する際に、決まった範囲の時刻リストを作りたいというときに見返すためのメモ記事となります。

時刻リストとは次のようなイメージです。

['2018-12-19 08:00:00+00:00',
 '2018-12-19 08:01:00+00:00',
 '2018-12-19 08:02:00+00:00',
 '2018-12-19 08:03:00+00:00',
 '2018-12-19 08:04:00+00:00']


ある時刻範囲のループをまわして、なにかしらの処理をしたい場合などにご活用ください。

現在の時刻を取得

Python での実装をご紹介します。
まず、次のように現在の時刻を取得します。

$ time = pd.Timestamp.now(tz='GMT').floor(freq='T')



このとき、用途にあわせて時刻のタイムゾーンを指定することができます。

  • 世界標準時(GMT)で設定したい場合: tz='GMT'
  • 日本時刻で設定したい場合: tz=’Asia/Tokyo’

とします。

また .floor を指定することで細かい時刻の端数を丸めることができます。
ここでは、freq=’T’ としているので分単位に丸めています。(ちなみに H が1時間単位、Dが1日単位になります)

このように、GMTタイムゾーンで現在時刻を取得することができました。

$ time
Timestamp('2018-12-19 11:02:00+0000', tz='GMT')


時刻リストの取得

時刻リストを取得します。
今回は、現在の時刻の2分前から、現在の時刻の2分後までの時刻リストを取得したい場合を考えます。

ここではpandasの date_rangeメソッドを使って配列を作っていきます。

$ time_range = pd.date_range(pd.Timestamp(time, tz='GMT') - pd.offsets.Minute(2), pd.Timestamp(time, tz='GMT') + pd.offsets.Minute(2), freq='T')


pandasの date_rangeでは、開始時刻、終了時刻、周期を引数に指定します。
今回は分単位のリストがほしいので freq=’T’ としています。

また時刻の指定においては、pd.offsets を使用して時間単位で分数を足したり引いたりしています。datetimeモジュールなどと比較すると、かなり直感的に時刻を加算・減算することができます。

ちなみにdatetimeモジュールでは下記のように記述する必要がありました。

$ from dateutil.relativedelta import relativedelta
$ dt + relativedelta(minutes=1)



実際に取得したリストをみてみましょう。

$ time_range
 ['2018-12-19 11:00:00+00:00',
  '2018-12-19 11:01:00+00:00',
  '2018-12-19 11:02:00+00:00',
  '2018-12-19 11:03:00+00:00',
  '2018-12-19 11:04:00+00:00']


現在時刻の11:02から前後2分の時刻リストが取得できていることが確認できました。

Timestamp型から UNIX time に変換

pandas の Timestamp型データは簡単にUNIX timeにも変換することができます。
取得したリストを一気に変換したい場合は、リスト内包表記を使って次のように変換可能です。

$ [t.value // 1000000 for t in time_range]
   [1545217200000,
    1545217260000,
    1545217320000,
    1545217380000,
    1545217440000]


まとめ

pandas の Timestamp で時刻リストを作る方法について簡単に紹介しました。特定の時刻範囲のループをまわす処理を書きたいときには、本記事でご紹介したpandasのTimestampを利用してみてはいかがでしょうか。

【3D物体検出論文】Orthographic Feature Transform for Monocular 3D Object Detection をまとめた

3D object detection (3D 物体検出) に関する2018-2019期の最新の論文『Orthographic Feature Transform for Monocular 3D Object Detection』*1について読んでまとめました。

3D Object detection とは、自動運転などにおいて 3次元空間における物体の位置情報を画像データから予測する物体検出タスクです。本記事では、3D object detection の概念について通常の物体検出と比較しながら紹介した後に、提案された最新の3D物体検出手法について解説していきます。

3d_object_detection
目次

3D Object detection とは

3D Object detection (3D 物体検出) とはどういったものなのでしょうか。実際の予測画像を紹介しながら、ほかの一般的な画像認識タスクと比較をしていきます。


画像認識タスクには一般的に以下の4種類があります。

  • Classification(分類)
  • Classification + Localization(分類 + 位置推定)
  • Object Detection(物体検出)
  • Semantic segmentation(領域分割)


image_recognition_task



3D Object detection はこれらのいずれにも含まれない新たな画像認識タスクとして、近年注目され始めています。

通常の物体検出 (2D Object detection) は様々な用途におけるニーズを伸ばしており、多くのアプリケーションで実用化されています。しかし、自動運転などにおいては通常の物体検出では不足している部分があります。例えば、下図の左画像のような2次元の物体検出では画像内に自動車が映っていることはわかりますが、画像に映っている自動車までの距離情報を認識できないという問題があります。


3d_object_detection_compare


そこで、物体の奥行き情報も考慮した3D Object detection(3次元物体検出) が登場しました。3D Object detection とは、物体の3次元空間における位置情報(3次元の直方体の位置、大きさ、角度)を予測するタスクです。

自動運転などでは、従来 LiDARと呼ばれる光を用いたリモートセンシング技術を用いて周辺の車両の位置推定を行っていました。


lidar_object_detection
画像: https://medium.com/@jhkoh/object-detection-with-lidar-point-cloud-algorithm-94a241fd3f49

しかし、LiDARが精密機械ゆえに自動車搭載に不向きであることや、安いものでも50万円程度と高価であることから、カメラ画像から 3D 空間の物体検出を行いたいというニーズが高まっています。

このような理由から、カメラ画像による 3D Object detection タスクが誕生しました。

論文の要旨

3D Object detection の概念をお伝えしたところで、ここから論文『Orthographic Feature Transform for Monocular 3D Object Detection』の解説に入っていきたいと思います。


本論文の特徴は以下の通りです。

  • 1台のカメラで撮影した画像から 3D Object detection を行う
  • 2次元画像空間における特徴を現実の 3D 空間にマッピングすることで3次元空間の特徴を捉える
  • 3D 空間における特徴マップに対して Orthographic space(正投影空間)に変換する機構を加えることで計算量削減


本論文は、2018年11月に arxiv に登場した時点では 1台のカメラ画像による 3D 物体検出手法で State-of-the-art を達成しています。つまり、共通のデータセットで評価を比較した際には最も良い精度が出ているということになります。

3D 物体検出までの流れ

本論文の提案手法について、ネットワークの学習および推論までのステップについて解説していきます。

提案手法では、複数のネットワークを組み合わせた大きなネットワーク構成となっています。共通の損失関数からすべてのネットワークの重みを学習することができることから、本ネットワークは End-to-end network であるといえます。

提案手法のネットワークアーキテクチャは下図の通りです。

3d_object_detection_architecture


この図に書かれている各ネットワークの詳細について、ひとつずつ解説していきます。


ResNet による特徴抽出

はじめに、画像が入力される最初のネットワークについて説明いたします。
ここでは、一般的な画像認識タスクでも多く使われている ResNet という Convolutional Neural Network (CNN) に画像を入力し、入力画像の特徴マップを抽出します。

このとき、入力画像に対してマルチスケールな特徴マップを抽出することで、画像の大まかな特徴から細かな特徴といった多様な特徴を捉えた特徴マップを得ることができます。


画像特徴マップを 3D 空間へマッピング

提案手法では、現実空間の特徴をより捉えるために画像空間ではなく 3D 空間で物体検出を行います。ここでは1つ目の CNN で抽出された特徴マップを 3D 空間にマッピングしていきます。

画像の最小の解像度はピクセルで表されるように、筆者らは現実の 3D 空間の最小の解像度をボクセルで表しています。


orthographic_feature_transform


1つのボクセルを1辺の長さが r の立方体だとすると、カメラで撮影した立方体のボクセルは画像空間内では下図のような六角形に映ります。


object_detection_cube


ボクセルの中心座標を (x, y, z)、カメラの位置を( (c_u, c_v))、カメラの焦点距離fとすると、画像空間内に映るボクセルのバウンディングボックス座標は下式で求めることができます。


bounding_box_equation


ボクセルが画像空間内でどのような映るかがわかったところで、画像空間における特徴マップを 3D 空間にマッピングしていきます。

中心座標が (x, y, z) のボクセルの特徴マップ g(x, y, z) は、上記で求めたバウンディングボックスに含まれる範囲の特徴マップに対して Average pooling(平均プーリング)をかけることで求めることができます。


mapping_equation


特徴マップのテンソルを繰り返し足し合わせる処理は計算量増大の原因になってしまいます。そこで、提案手法では Integral images(積分画像)による高速化を図っています。ピクセル (u, v) の Integral map を F(u, v) は、再帰関数の利用により下式のように表すことができます。


recursive_integral_image


求めた積分画像を用いると、ボクセルの特徴マップ g(x, y, z) は下式のように書き換えることができます。

speedy_feature_mapping


積分画像については、こちらの方の記事を読んでみるといいかもしれません。

画像処理 Integral Image(積分画像) - のんびりしているエンジニアの日記


Orthographic Feature Transform(正投影空間への変換)

3次元空間に特徴をマッピングすることで 3D 空間の情報を抽出することができました。しかし、このまま 3次元空間の特徴マップを用いて物体検出をしようとすると計算量が膨大になってしまうことは明らかです。
そこで、提案手法では 3次元の特徴マップを高さ方向で圧縮して 2次元にマッピングしています。これを Orthographic Feature Transform と呼んでいます。

実空間にマッピングした特徴マップ (Voxel features) のy軸方向の高さを Hとすると、正投影空間における特徴マップ h(x, z) は下式で求めることができます。
orthographic_feature_transform_h


求めた正投影空間における特徴マップ h(x, z) を再び CNN に入力し、出力に対して Confidence map、位置、大きさ、向きに関する 4つの損失関数を最小化するようにネットワーク全体を学習していきます。


topdown_network

画像空間ではなく実空間に対応する特徴マップを入力するため、カメラから遠い物体をカメラから近い物体と同様に扱うことができるというのが提案手法の大きな特徴です。


3D 物体の位置推定

物体の位置推定ステップでは一般的な背景・物体の分類問題を解く手法ではなく、物体の中心座標の存在確率を表す Confidence map を回帰する手法を利用します。

confidence_map


下記の中心座標を持つ N 個の既知の物体(学習データ)があるとすると


training_data_equation

正投影空間における点(x, z) の ground truth Confidence score は次のガウス関数で表現することができます。

confidence_map_equation

ここで定義した ground truth Confidence map と予測した Confidence map の差を l1 損失関数で表し、学習時にはこの損失関数を最小化するように学習していく。


さらに、位置・大きさ・向きについても損失関数を用意することで精度向上を図ります。

f:id:hktech:20181217225719p:plain



評価

KITTI の 3D Object detection データセットを用いて学習・テストを実施し、評価を行っています。
KITTI データセットの中身については、次の方がまとめてくださっています。

kitti関連の覚え書き - 八谷大岳の覚え書きブログ

評価実験は下記の諸元で実施しています。

*Features
Front-end network ResNet-18
Feature map scales 1/8, 1/16, 1/32
3次元空間のグリッドサイズ 80m × 4m × 80m
グリッド解像度 r 0.5 m
Topdown network 16-layer ResNet
Data Number (Training, Validation) 3712, 3769
Data augmentation Random cropping, scaling, flipping, camera parameters
Training epochs 600

物体検出結果を画像で確認していきます。ご覧のとおり、画像上に正投影空間に投影した Confidence map が描画されているのが確認できると思います。また、bird-eye's-view(鳥瞰図)でみると、画像内の車両位置と対応するように、Confidence mapで描かれた円がプロットされていることがわかります。

object_detection_result



定量的に結果を確認していきます。ここでは、カメラを2台使った従来手法である
3DOP*2 と、1台のカメラ画像を使った従来手法であるMono3D*3を比較します。


evaluation_result


KITTI ベンチマークでは正解と予測の重複が 0.7 以上のものを予測成功と定義されています。
Mono3D と比較すると精度が改善していることがわかります。また、2台のカメラを使用する 3DOP に近い精度を達成しています。特に、物体の重複がある場合や物体が遠方なときの Hard カテゴリでは精度が大きく改善していることがわかります。これは、画像空間ではなく実空間に対応する特徴マップを入力するため、カメラから遠い物体をカメラから近い物体と同様に扱うことが可能だからです。

まとめ

3D Object detection の最新論文についてまとめました。
対象論文『Orthographic Feature Transform for Monocular 3D Object Detection』では単一のカメラで撮影した画像を使って3次元空間における物体の位置情報を検出する 3D 物体検出手法が提案され、2018年末時点での State-of-the-art を達成しました。
まだまだ実用化に向けて精度を改善していかなければいけないフェーズですが、カメラ画像を使った 3D Object detection は今後も注目され続ける分野だと思います。今後も画像認識に関連した新しい手法について調べてまとめていきたいので、ご期待ください。