Databricks機械学習ランタイムを用いた動画における不審な振る舞いの検知

[Identify Suspicious Behavior in Video with Databricks Runtime for Machine Learning \- The Databricks Blog](https://databricks.com/blog/2018/09/13/identify-suspicious-behavior-in-video-with-databricks-runtime-for-machine-learning.html)の翻訳です。

> Databricksで[一連のノートブック](https://s3.us-east-2.amazonaws.com/databricks-dennylee/notebooks/Identifying+Suspicious+Behavior+in+Video.dbc)を試す。

カメラと動画の指数関数的な増加に伴い、動画の識別と分類のプロセスの運用、自動化が非常に重要になってきています。適切な猫の識別から、オブジェクトの見た目に基づく分類に至る様々なアプリケーションがより一般的になってきています。世界中の数百万のユーザーが、1日当たり数十億分の動画を生成、消費しているため、これらの膨大な規模のデータを取り扱うことのできるインフラストラクチャが必要となります。
![db-video-pipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/952d9245-fcb2-8841-2020-a48f079f1c99.png)

急激にスケールできるインフラストラクチャの複雑性、複数の機械学習、ディープラーニングパッケージの管理、高パフォーマンスな計算処理によって、動画処理は複雑かつわかりにくいものになりえます。このミッションが与えられたデータサイエンティストとデータエンジニアは、継続的に数多くのアーキテクチャ上の疑問に直面します。

1. 構築するインフラストラクチャはどのようにスケールし、どこまでスケーラブルであるべきか？
1. 処理の重いデータサイエンス部分に対して、Apache Sparkのインフラストラクチャに加え、どのように様々な機械学習、ディープラーニングパッケージを連携、維持できるのか？
1. データエンジニア、データアナリスト、データサイエンティスト、そして、ビジネス上のステークホルダーはどのように連携したらいいのか？

この問題に対する我々のソリューションが、企業内の異なるペルソナの人たちが協働できるようにするためのDatabricksノートブック、コラボレーション、ワークスペース機能を提供するDatabricksの[レイクハウスプラットフォーム](https://databricks.com/jp/product/data-lakehouse)です。Databricksには、XGBoost、scikit-learn、TensorFlow、Keras、Horovodなどの機械学習フレームワークが設定済みで、最適化されている[Databricks機械学習ランタイム](https://databricks.com/blog/2018/06/05/announcing-databricks-runtime-for-machine-learning.html)が含まれています。Databricksは[AWS](https://docs.databricks.com/user-guide/clusters/gpu.html)、[Azure](https://docs.microsoft.com/en-us/azure/databricks/clusters/gpu)の両方で、[コスト削減のために最適化されたオートスケーリング機能](https://databricks.com/blog/2018/05/02/introducing-databricks-optimized-auto-scaling.html)やGPUサポートを提供しています。

この記事では、不審な動画を分類、識別するために、[Databricks機械学習ランタイム](https://databricks.com/blog/2018/06/05/distributed-deep-learning-made-simple.html)を用いて、どのようにApache Sparkの分散処理機能とディプラーニングパイプラインを組み合わせるのかをご説明します。

# 不審な動画の分類

我々のシナリオにおいては、[EC Funded CAVIAR project/IST 2001 37540](http://groups.inf.ed.ac.uk/vision/CAVIAR/CAVIARDATA1/)データセットから得られる一連の動画を使用します。CAVIARチームメンバによって演じられた6つの基本的なシナリオのクリップを使用します。

- 歩く
- 眺める
- 休んでいる、バタンと倒れる、フラフラする
- バッグを置いていく
- 人々、グループが集まっている、一緒に歩いている、別れる
- 2人が戦っている

この記事と関連する[Databricksノートブック](https://s3.us-east-2.amazonaws.com/databricks-dennylee/notebooks/Identifying+Suspicious+Behavior+in+Video.dbc)では、前処理、画像特徴量の抽出、動画に対する機械学習モデルの適用を行います。
![image002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/0357ffc9-812e-ca15-103a-08d016d6f48b.jpeg)
*ソース: CAVIARメンバーによる戦闘シーンの再現 - EC Funded CAVIAR project/IST 2001 37540 http://groups.inf.ed.ac.uk/vision/CAVIAR/CAVIARDATA1/*

例えば、トレーニング用のビデオデータセットから抽出した異なる画像セットに対してトレーニングした機械学習モデルを適用することで、テスト用データセットから以下のような不審な画像を識別します。
![Fight_OneManDownframe0014.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4bea74f2-88a5-0647-0a0a-b17378e152f5.jpeg)

# ハイレベルのデータフロー

以下の画像では、ロジスティック回帰モデルをトレーニング、テストするためにソース動画を処理するためのハイレベルのデータフローを示しています。
![mnt_raela_video_splash.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d3404088-3989-629e-2981-5caea3431412.png)

ハイレベルのデータフローにおいては以下の処理を行います。

- **ビデオ:** トレーニング、テストデータセット(トレーニング用のビデオ、テスト用のビデオ)として[EC Funded CAVIAR project/IST 2001 37540](http://groups.inf.ed.ac.uk/vision/CAVIAR/CAVIARDATA1/)によるINRIA(最初のビデオ)のクリップを活用します。
- **前処理:** トレーニング用の画像、テスト用の画像を生成するために動画から画像を抽出します。
- **DeepImageFeaturizer:** SparkのディープラーニングパイプラインのDeepImageFeaturizerを用いて、トレーニング用、テスト用の画像の特徴量セットを生成します。
- **ロジスティック回帰:** 不審な振る舞いか、不審ではない画像の特徴量(最終的には動画のセグメント)を分類するためにロジスティック回帰モデルのトレーニング、フィッティングを行います。

この処理を実行するのに必要なライブラリは以下の通りとなります。

- h5py
- TensorFlow
- Keras
- Spark Deep Learning Pipelines
- TensorFrames
- OpenCV

Databricks機械学習ランタイムを用いると、Keras、TensorFlow、Spark Deep Learning pipelinesでディープラーニングパイプラインを実行できるように、OpenCV以外は事前インストール、設定された状態で利用できます。Databricksを用いることで、オートスケールするクラスター、複数のタイプのクラスター、そして、コラボレーション、複数言語のサポートするワークスペースを利用でき、エンドツーエンドの分析要件に応えるDatabricksのレイクハウスプラットフォーム活用できるメリットを享受できます。

# ソース動画

![videos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f9483178-3986-a79b-f3cb-bf2a901f5968.png)

動画処理をすぐにスタートできるように、CAVIAR Clips from INRIA (1st Set) videos [[EC Funded CAVIAR project/IST 2001 37540](http://groups.inf.ed.ac.uk/vision/CAVIAR/CAVIARDATA1/)]を`/databricks-datasets`にコピーしています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/TMyqwGpdIRI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

- トレーニング用動画 (`srcVideoPath`): `/databricks-datasets/cctvVideos/train/`
- テスト用動画 (`srcTestVideoPath`): `/databricks-datasets/cctvVideos/test/`
- ラベル付きデータ (`labeledDataPath`): `/databricks-datasets/cctvVideos/labels/cctvFrames_train_labels.csv`

# 前処理

![preprocessing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/de59bc80-9bed-0962-09d0-fe614f91aaff.png)

最終的には、動画に含まれる個々の画像の特徴量に対して、機械学習モデル(ロジスティック回帰)を実行します。最初のステップ(前処理)は動画から個々の画像を抽出します。アプローチの一つは、以下のコードスニペットにあるように、1秒あたりの画像を抽出するためにOpenCVを用いるというものです。

```py:Python
## Extract one video frame per second and save frame as JPG
def extractImages(pathIn):
   count = 0
   srcVideos = "/dbfs" + src + "(.*).mpg"
   p = re.compile(srcVideos)
   vidName = str(p.search(pathIn).group(1))
   vidcap = cv2.VideoCapture(pathIn)
   success,image = vidcap.read()
   success = True
   while success:
      vidcap.set(cv2.CAP_PROP_POS_MSEC,(count*1000))
      success,image = vidcap.read()
      print ('Read a new frame: ', success)
      cv2.imwrite("/dbfs" + tgt + vidName + "frame%04d.jpg" % count, image)     # save frame as JPEG file
      count = count + 1
      print ('Wrote a new frame')    
```

この場合、DBFS(Databricksファイルシステム)に保存されている動画を抽出し、OpenCVの[VideoCaptureメソッド](https://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture)を用いて、画像フレーム(1000msごと)を作成し、これらのDBFSに保存します。こちらの[ノートブック](https://s3.us-east-2.amazonaws.com/databricks-dennylee/notebooks/Identifying+Suspicious+Behavior+in+Video.dbc)で完全なコードサンプルにアクセスすることができます。

画像を抽出した後で、以下のコードスニペットを用いて抽出画像を読み込み、参照することができます。

```py:Python
from pyspark.ml.image import ImageSchema

trainImages = ImageSchema.readImages(targetImgPath)
display(trainImages)
```

アウトプットは以下のようになります。
![view-images.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4da3ec20-fb7d-faac-f36f-a31f86b047f2.png)

このタスクをトレーニング用、テスト用の動画に対して実行します。

# DeepImageFeaturizer

![deep-image-featurizer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/8b38295e-b3bf-72d5-203d-32b125cc43a5.png)

[A Gentle Introduction to Transfer Learning for Deep Learning](https://machinelearningmastery.com/transfer-learning-for-deep-learning/)に記されているように、転移学習は、あるタスク(例:車の画像識別)に対してトレーニングされたモデルを別のタスク(例:トラックの画像識別)に適用するテクノロジーです。我々のシナリオでは、[画像に対する転移学習](https://github.com/databricks/spark-deep-learning#working-with-images-in-spark)を実行するためにSparkのDeep Learning Pipelinesを使用します。
![inception_v3_architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/da6ba215-ce7a-90f5-9a9e-e13a5ed9d717.png)
*ソース: TensorFlowのインセプション*

以下のコードスニペットにあるように、これらの画像を数値の特徴量に変換するために、事前学習済みニューラルネットワークの最終例やを自動で抽出するために`DeepImageFeaturizer `の中で[Inception V3 model](https://arxiv.org/abs/1512.00567)([TensorFlowにおけるインセプション](https://github.com/tensorflow/models/tree/master/research/slim))を使用します。

```py:Python
# Build featurizer using DeepImageFeaturizer and InceptionV3 
featurizer = DeepImageFeaturizer( \
    inputCol="image", \
    outputCol="features", \ 
    modelName="InceptionV3" \
)

# Transform images to pull out 
#   - image (origin, height, width, nChannels, mode, data) 
#   - and features (udt)
features = featurizer.transform(images)

# Push feature information into Parquet file format
features.select( \
  "Image.origin", "features" \
).coalesce(2).write.mode("overwrite").parquet(filePath)
```

画像のトレーニング用データセット、テスト用データセットの両方は、DeepImageFeaturizerを用いて処理され、最終的にはParquetファイルに`features`として保存されます。

# ロジスティック回帰

![logistic-regression.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/c242bf6e-c24c-a81a-01c8-cfb2f7dd6e0d.png)

以前のステップでは、OpenCVとSpark Deep Learning PipelinesのDeepImageFeaturizer(および、Inception V3)を用いてソースのトレーニング用動画、テスト用動画を画像に変換し、Parquetフォーマットとして特徴量を保存するプロセスをウォークスルーしました。この時点で、MLモデルをフィッティング、テストする一連の数値の特徴量を手に入れたことになります。我々はトレーニングデータセット、テストデータセットを持っており、画像(および、関連づけられている動画)が不審な行動を示しているのかどうかを分類しようとしているので、ロジスティック回帰を試すことができる典型的な教師あり分類問題に帰着することができます。

ソースデータセットにはラベル付きデータのCSVファイル(画像フレームと不審フラグのマッピング)を含む`labeledDataPath`があるため、このユースケースでは教師あり学習となります。以下のコードスニペットで、手動でラベル付けがなされたデータ(`labels_df`)を読み込み、トレーニングデータセットを作成するために、これとトレーニング用の特徴量Parquetファイル(`featureDF`)を結合します。

```py:Python
# Read in hand-labeled data 
from pyspark.sql.functions import expr
labels = spark.read.csv( \
    labeledDataPath, header=True, inferSchema=True \
)
labels_df = labels.withColumn(
    "filePath", expr("concat('" + prefix + "', ImageName)") \
).drop('ImageName')

# Read in features data (saved in Parquet format)
featureDF = spark.read.parquet(imgFeaturesPath)

# Create train-ing dataset by joining labels and features
train = featureDF.join( \
   labels_df, featureDF.origin == labels_df.filePath \
).select("features", "label", featureDF.origin)
```

これで、以下のコードスニペットにあるようにデータセットに対して、ロジスティック回帰モデル(`lrModel`)をフィットできるようになります。

```py:Python
from pyspark.ml.classification import LogisticRegression

# Fit LogisticRegression Model
lr = LogisticRegression( \
   maxIter=20, regParam=0.05, elasticNetParam=0.3, labelCol="label")
lrModel = lr.fit(train)
```

モデルをトレーニングした後で、テストデータセットに対して予測を生成することができます。すなわち、我々のLRモデルに、どのテスト用動画が不審であるかどうかを予測させます。以下のコードスニペットにあるように、Parquetからテストデータ(`featuresTestDF`)をロードし、上でトレーニングしたモデル(`lrModel`)を用いて、テストデータに対する予測(`result`)を行います。

```py:Python
from pyspark.ml.classification import LogisticRegression, LogisticRegressionModel

# Load Test Data
featuresTestDF = spark.read.parquet(imgFeaturesTestPath)

# Generate predictions on test data
result = lrModel.transform(featuresTestDF)
result.createOrReplaceTempView("result")
```

これで、テストランによって`result`を得られるので、ソートできるように`probability`ベクトルの2つ目の要素(`prob2`)を抽出できます。

```py:Python
from pyspark.sql.functions import udf
from pyspark.sql.types import FloatType

# Extract first and second elements of the StructType
firstelement=udf(lambda v:float(v[0]),FloatType())
secondelement=udf(lambda v:float(v[1]),FloatType())

# Second element is what we need for probability
predictions = result.withColumn("prob2", secondelement('probability'))
predictions.createOrReplaceTempView("predictions")
```

> 我々のサンプルにおいては、`predictions`データフレームの最初の行は画像を`prediction = 0`で不審ではないと分類しています。ここでは、ロジスティック回帰を使用しているので、probabilityの`(firstelement, secondelement)`のStructTypeは、 `(prediction = 0の確率, prediction = 1の確率)`を意味しています。ここでは、不審な画像をレビューすることにフォーカスしていますので、2つ目の要素(`prob2`)でソートします。

不審な画像を確認するために、`(where prediction = 1)`を`prob2`で並び替える以下のSpark SQLクエリーを実行します。

```py:Python
%sql
select origin, probability, prob2, prediction from predictions where prediction = 1  order by prob2 desc
```

![suspicious-activity-in-video.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/35ec3874-19ea-1366-616e-e72a310fcbd3.png)

上の結果に基づき、不審であると判定されたトップ3のフレームを参照することができます。

```py:Python
displayImg("dbfs:/mnt/tardis/videos/cctvFrames/test/Fight_OneManDownframe0024.jpg")
```

![Fight_OneManDownframe0024.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/424172ab-152f-9548-dafe-365c25793496.jpeg)

```py:Python
displayImg("dbfs:/mnt/tardis/videos/cctvFrames/test/Fight_OneManDownframe0014.jpg")
```

![Fight_OneManDownframe0014.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/2e683df6-3cdf-2a30-3279-df49cb80d2cd.jpeg)

```py:Python
displayImg("dbfs:/mnt/tardis/videos/cctvFrames/test/Fight_OneManDownframe0017.jpg")
```

![Fight_OneManDownframe0017.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/eb4425ef-0651-f910-98b3-c902f127b5cb.jpeg)

結果に基づいて、以下のようにクイックに動画を特定することができます。

```py:Python
displayDbfsVid("databricks-datasets/cctvVideos/mp4/test/Fight_OneManDown.mp4")
```

![image002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/727300c9-4e8a-d0cf-995f-df1ffe67a489.jpeg)
*ソース: CAVIARメンバーによる戦闘シーンの再現 - EC Funded CAVIAR project/IST 2001 37540 http://groups.inf.ed.ac.uk/vision/CAVIAR/CAVIARDATA1/*

# まとめ

ここまでで、コラボレーションとMLモデル、動画、抽出画像の可視化を可能にするDatabrikcsワークスペース、ライブラリのメンテナンスをシンプルにするために、Keras、TensorFlow、TensorFramesや他の機械学習、ディープラーニングライブラリが事前設定されているDatabricks機械学習ランタイム、ハイパフォーマンス数値計算をスケールアップ、スケールアウトするためにGPUサポートも備えた最適化オートスケーリングクラスターを提供するDatabricksの[レイクハウスプラットフォーム](https://databricks.com/jp/product/data-lakehouse)を用いて、どのように不審な動画を分類、識別するのかをデモンストレーションしました。これらのコンポーネントを統合することで、あなたと他のデータ実践者が取り組む動画分類のデータフローと管理をシンプルなものにします。ぜひ、[Databricks機械学習ランタイム](https://databricks.com/blog/2018/06/05/distributed-deep-learning-made-simple.html)で[サンプルノートブック](https://s3.us-east-2.amazonaws.com/databricks-dennylee/notebooks/Identifying+Suspicious+Behavior+in+Video.dbc)を試してみてください。

翻訳したノートブックはこちらからダウンロードできます。

https://github.com/taka-yayoi/public_repo/tree/main/suspicious_behavior_detection

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)