[翻訳] DatabricksにおけるYOLOv8を用いた物体検知

[Ben Hayes \- Object Detection with YOLOv8 on Databricks](https://benhay.es/posts/object_detection_yolov8/)の翻訳です。

:::note warn
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

![](https://benhay.es/img/yolov8_banner.png)

# イントロダクション

最新のAIハイプサイクルで人工知能(AI)が斜陽となっていく中(IBMのDeep BlueやWatsonを考えてください)、自信満々に"I'll be back"という声を聞いたと主張した人がいても許されることでしょう。実際、2024年にさしかかる頃に、AIの知性線の上に再び太陽が登りました。AIの研究者は、そのフロンティアを探索し続け、計算資源はよりパワフル、経済的、豊富になり、ビジネスではさらに多くのデータを取り込むようになりました。現在では、主にテキストを取り扱う大規模言語モデル(LLM)が最も注目を集めていますが、このAIの復活の中でコンピュータービジョンも前進しています。このAIの分派は業界とイノベーションを強化しています - 自動運転自動車、製造欠陥の検知などの分野で。

この記事では、コンピュータービジョン、特に物体検知にフォーカスし、最近のAIに関する記事シリーズ([Getting Started with OpenAI](https://benhay.es/posts/getting-started-openai/)、[Hands\-on with Edge AI](https://benhay.es/posts/hands-on-edge-ai/)、[Getting Started with Databricks](https://benhay.es/posts/getting-started-databricks/))の続きとなります。AIの広範で進化し続けるトピックに興味があるのであれば、これらの記事もチェックください！

この記事では、なぜ、YOLOv8とDatabricksが、物体検知ユースケースの開発に対してエレガントなソリューションを提供するのかをpythonのサンプルとともにディープダイブします。はじめに、YOLOv8モデルとDatabricksプラットフォームについて議論しましょう。

## YOLOv8について

YOLOv8は、以下を含む多数のビジョン関連のタスクを実行できる`ultralytics`ライブラリでメンテナンスされているパワフルなAIモデルです:

- 画像分類(二値とマルチクラス)
- 物体位検知
- 画像セグメンテーション
- 物体追跡
- 姿勢推定

![](https://raw.githubusercontent.com/ultralytics/assets/main/im/banner-tasks.png)
*GithubのUltralytics/YOLOv8でサポートされているモデル。ソース: https://github.com/ultralytics/ultralytics*

Ultralyticsは、スクラッチからのトレーニングであろうと、事前トレーニング済みであっても、数行のコードでスタートできるようにYOLOv8を使うためのインタフェースをシンプルにします。そして、同じように、Databricks上でYOLOv8、ハイパワーのGPU有効かクラスターを活用することができます。

## Databricksについて

![](https://upload.wikimedia.org/wikipedia/commons/6/63/Databricks_Logo.png)

以前の記事で議論したように、Databricksはレイクハウスのアーキテクチャ/コンセプトの発明者であり、[MIT Review](https://www.technologyreview.com/2023/10/05/1080618/laying-the-foundation-for-data-and-ai-led-growth/)によると、企業の74%が導入しているとのことです。また、[Spark](https://spark.apache.org/)、[Delta Lake](https://delta.io/)、[MLflow](https://mlflow.org/)を含む、モダンデータアーキテクチャに含まれるテクノロジーのクリエーターでもあります。

Databricksを活用することで、構造化、半構造化、画像を含む非構造化データセットを取り扱うためのデータパイプラインを容易に構築することができます。データパイプラインや大規模なデータボリュームはこの生地の本題ではありませんが、このソリューションをスケールさせるための能力は、プロダクションのデータデプロイメントにおける重要な検討事項となります。さらに、Databricksを活用することで、データエンジニアリングのフォーカスと機械学習のフォーカスの間でシームレスにプロジェクトを移行することができます。新たに到着するデータに対応する必要がある場合には、このような機能が有用となります。Databricksでは、データエンジニアリング、データアナリティクス、データサイエンス、機械学習、人工知能プロジェクトのための長大な機能のリストがありますが、この物体検知の例で特に使う機能は以下のものとなります:

- [GPU有効化クラスター](https://docs.databricks.com/ja/compute/gpu.html)(`ultralytics`や`torch`のように容易に外部からインポートできるライブラリを含む)
- モデルトラッキング、評価、サービングのための[MLflow](https://docs.databricks.com/ja/mlflow/index.html)
- Databricks[ボリューム](https://docs.databricks.com/ja/volumes/index.html)(モデルチェックポイントのようなバイナリー/非テーブルデータの格納)

# DatabricksにおけるYOLOv8を用いた物体検知

## 概要

YOLOv8とモデルやDatabricksプラットフォームを理解したので、画像内の物体を検知するためにこれらのツールを活用するサンプルに踏み出す準備ができました。我々は、解きたいと考える問題が何であるのか、どのようなオブジェクトを検知したいのか？という問いに答える必要があります。さらに、物体検知を活用する背景にある'理由'を理解することは、ソリューションが問題を確実に*解決*するためには重要です。価値が鍵となります。

我々のゴールは、太陽光パネルを検知するために、Databricksプラットフォーム、GPU有効化コンピュート、YOLOv8モデルを活用することです。それなしには、このタスクは手動で、時間を浪費し、コストがかかり、(専門家によるものだとしても)精度が低いものとなるために、これを達成したいと考えます。このタスクのパフォーマンスを少し改善するだけでも、ソーラーパネルフリートオーナー / 再生可能エネルギー供給者において数百万ドルの価値を解放することになります。次に、この問題に取り組むために利用できるデータセットを議論しましょう。

この例では、太陽光パネルの写真を取り扱います。我々は、

1. 写真内の太陽光パネルを検知
1. 太陽光パネルが故障しているか、していないかを特定

します。我々のトレーニングデータは、約200の太陽光パネルの画像から構成されています。さらに、メタデータは太陽光パネルの位置('境界ボックス')とそれぞれの太陽光パネルの状態をラベル付けしています。

参考までに、提供される画像の例は以下のようなものとなります:

![](https://benhay.es/img/object_detection_yolov8_files/solar_img_raw.png)
*太陽光パネルのセットを含む画像サンプル*

ダイブしましょう！

## セットアップ

我々のコードの最初のステップは、計算環境をセットアップすることです。幸運なことに、Databricksを用いることでこれはシンプルなものとなります。簡単に`ultralytics`ライブラリをインストールし、ノートブックにインポートすることができます。CUDA設定やデータのスキーマのようないくつかの設定を調整することで、GPUクラスターのパフォーマンスを最適化することができます。最後に、モデルとデータの操作を簡単にするために、`YOLOC`というシンプルな*class*を作成し、いくつかのパスを定義します。

```py
%pip install ultralytics==8.1.14 opencv-python #==4.8.0.74

import os
os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
```

```py
from ultralytics import YOLO

import mlflow
import torch.distributed as dist
from ultralytics import settings
from mlflow.types.schema import Schema, ColSpec
from mlflow.models.signature import ModelSignature

os.environ["OMP_NUM_THREADS"] = "4"  # Configure as needed

input_schema = Schema(
    [
        ColSpec("string", "image_source"),
    ]
)
output_schema = Schema([ColSpec("string","class_name"),
                        ColSpec("integer","class_num"),
                        ColSpec("double","confidence")]
                       )

signature = ModelSignature(inputs=input_schema, 
                           outputs=output_schema)

settings.update({"mlflow":False})


##############################################################################
## Create YOLOC class to capture model results and a predict() method ##
##############################################################################

class YOLOC(mlflow.pyfunc.PythonModel):
  def __init__(self, point_file):
    self.point_file=point_file

  def load_context(self, context):
    from ultralytics import YOLO
```

```py
# Config MLflow
mlflow.autolog(disable=False)
mlflow.end_run()

# Config project structure directory
project_location = '/Volumes/benhayes/demo/solar-data/dataset_1'
os.makedirs(f'{project_location}/training_runs/', exist_ok=True)
os.chdir(f'{project_location}/training_runs/')
```

## トレーニング

次に、8つのバッチと50エポックを実行するために、トレーニングプロセスを開始し、`mlflow.start_run()`を使います。事前トレーニング済みモデル('.pt'のサフィックス)としてYOLOモデルを定義し、トレーニング画像をポイントするYAMLを指定し、モデルチューニングをコントロールするパラメーターを定義します。これらのパラメーターの詳細については、[YOLO documentation](https://docs.ultralytics.com/)をご覧ください。

`log_params()`と`log_model()`を用いて、パラメーターとモデルを記録するMLflowの機能を活用します。これらを用いることで、MLflowのエクスペリメントにモデルの詳細をキャプチャし、すべてのトレーニングモデルを再現したい場合には再訪することができます。

```py
if not dist.is_initialized():
  dist.init_process_group("nccl")


# Kickoff the MLflow training process
# Specify the number of batches, epoches, etc. for this experiment

with mlflow.start_run():
  model = YOLO("yolov8l-seg.pt")
  model.train(
          batch=8,
          device=[0],
          data=f"{project_location}/training/data.yaml",
          epochs=50,
          project='/tmp/solar_panel_damage/',
          exist_ok=True,
          fliplr=1,
          flipud=1,
          perspective=0.001,
          degrees=.45
      )

  mlflow.log_params(vars(model.trainer.model.args))
  yolo_wrapper = YOLOC(model.trainer.best)
  mlflow.pyfunc.log_model(artifact_path = "model",
                          artifacts = {'model_path': str(model.trainer.save_dir), 
                                       "best_point": str(model.trainer.best)},
                          python_model = yolo_wrapper,
                          signature = signature
                          )
```

Databricksノートブック環境のインタラクティブな性質のおかげで、クラスターがモデルをトレーニングする際にリアルタイムでアウトプットを参照できます。ここでは、UltralyticsライブラリがYAML設定を処理し、トレーニングデータセットにアクセスし、トレーニングのイテレーションを開始していることを確認できます。エポック数、GPUメモリー使用量、ロスのメトリクス、進捗のようなアウトプットを確認できます。

```
Ultralytics YOLOv8.1.14 🚀 Python-3.10.12 torch-2.0.1+cu118 CUDA:0 (Tesla T4, 14931MiB)
Overriding model.yaml nc=80 with nc=2
Transferred 651/657 items from pretrained weights
TensorBoard: Start with 'tensorboard --logdir /tmp/solar_panel_damage/train', view at http://localhost:6006/
Freezing layer 'model.22.dfl.conv.weight'
AMP: running Automatic Mixed Precision (AMP) checks with YOLOv8n...
AMP: checks passed ✅
train: Scanning /Volumes/benhayes/demo/solar-data/dataset_1/training/datasets/train/labels.cache... 108 images, 0 backgrounds, 0 corrupt: 100%|██████████| 108/108 [00:00<?, ?it/s]
val: Scanning /Volumes/benhayes/demo/solar-data/dataset_1/training/datasets/val/labels.cache... 6 images, 0 backgrounds, 0 corrupt: 100%|██████████| 6/6 [00:00<?, ?it/s]
Plotting labels to /tmp/solar_panel_damage/train/labels.jpg... 
optimizer: 'optimizer=auto' found, ignoring 'lr0=0.01' and 'momentum=0.937' and determining best 'optimizer', 'lr0' and 'momentum' automatically... 
optimizer: AdamW(lr=0.001667, momentum=0.9) with parameter groups 106 weight(decay=0.0), 117 weight(decay=0.0005), 116 bias(decay=0.0)
TensorBoard: model graph visualization added ✅
Image sizes 640 train, 640 val
Using 8 dataloader workers
Logging results to /tmp/solar_panel_damage/train
Starting training for 50 epochs...

      Epoch    GPU_mem   box_loss   seg_loss   cls_loss   dfl_loss  Instances       Size
       1/50      10.3G        2.1      5.152      4.102      2.091        328        640: 100%|██████████| 14/14 [00:18<00:00,  1.29s/it]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95)     Mask(P          R      mAP50  mAP50-95): 100%|██████████| 1/1 [00:00<00:00,  4.26it/s]
                   all          6        195      0.994      0.785      0.917      0.442      0.994      0.785      0.917      0.425


      Epoch    GPU_mem   box_loss   seg_loss   cls_loss   dfl_loss  Instances       Size
       2/50      8.83G      0.921      2.043      1.096       1.09        297        640: 100%|██████████| 14/14 [00:10<00:00,  1.33it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95)     Mask(P          R      mAP50  mAP50-95): 100%|██████████| 1/1 [00:00<00:00,  4.11it/s]
                   all          6        195      0.168      0.903      0.207      0.093      0.162      0.872        0.2     0.0861

...

50 epochs completed in 0.255 hours.
Optimizer stripped from /tmp/solar_panel_damage/train/weights/last.pt, 92.3MB
Optimizer stripped from /tmp/solar_panel_damage/train/weights/best.pt, 92.3MB

Validating /tmp/solar_panel_damage/train/weights/best.pt...
Ultralytics YOLOv8.1.14 🚀 Python-3.10.12 torch-2.0.1+cu118 CUDA:0 (Tesla T4, 14931MiB)
YOLOv8l-seg summary (fused): 295 layers, 45913430 parameters, 0 gradients, 220.1 GFLOPs
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95)     Mask(P          R      mAP50  mAP50-95): 100%|██████████| 1/1 [00:00<00:00,  3.45it/s]
                   all          6        195      0.994      0.785      0.917      0.439      0.994      0.785      0.917      0.429
               working          6        195      0.994      0.785      0.917      0.439      0.994      0.785      0.917      0.429
Speed: 0.3ms preprocess, 26.7ms inference, 0.0ms loss, 4.4ms postprocess per image
Results saved to /tmp/solar_panel_damage/train
Downloading artifacts:   0%|          | 0/31 [00:00<?, ?it/s]
2024/02/17 01:59:06 INFO mlflow.store.artifact.artifact_repo: The progress bar can be disabled by setting the environment variable MLFLOW_ENABLE_ARTIFACTS_PROGRESS_BAR to false
Downloading artifacts:   0%|          | 0/1 [00:00<?, ?it/s]
Uploading artifacts:   0%|          | 0/37 [00:00<?, ?it/s]
2024/02/17 01:59:31 INFO mlflow.store.artifact.cloud_artifact_repo: The progress bar can be disabled by setting the environment variable MLFLOW_ENABLE_ARTIFACTS_PROGRESS_BAR to false
```

我々のデータセットを用いてモデルをトレーニングしました。プロセスをスムーズにするために、Databricksのボリュームによるクラウドストレージ、GPU有効化コンピュート、MLflowを活用することができました！

## 評価

モデルをトレーニングしたので、プログラムからモデルにアクセスすることができます。Databricksに組み込まれているMLflowのUIを通じてモデルにアクセスすることができますが、プログラムからモデルを操作することにします。しかし、はじめにモデルの結果を用意かつ繰り返しアクセスできるように、2つのヘルパー関数をセットアップします。コンピュータービジョンを取り扱うので、単にテーブルから読み込むのではなく、結果を**参照**したいとも考えます。

2つの関数を作成します: ベストモデルで推論を行い、予測した境界ボックスを持つ画像を表示するための`predict_on_image()`や`prepare_img_w_box()`となります。

```py
##########
# Function to predict bounding boxes given a model and an input image
##########

def predict_on_image(model, img):
  import pandas as pd
  import json, os

  # Use model to generate bounding box predictions for the loaded img
  results = {}
  result_list = []

  model_results = model.predict(img)
  label_names, boxes, confidence = model_results[0].names, model_results[0].boxes.cpu().numpy(), model_results[0].boxes.conf.tolist()
  label_result = [int(i) for i in model_results[0].boxes.cls.tolist()]
  named_results = [{'class_name': label_names[i], 
                    'class_num': i, 
                    'confidence': confidence[count],
                    'box': boxes[count].xyxy[0].astype(int).tolist()} for count, i in enumerate(label_result)
                  ]
  results['labels'] = named_results

  return results


##########
# Function to display an image with bounding box(es) around solar panel(s)
# given the confidence threshold is set
##########

def prepare_img_w_box(img_in, results, confidence_thresh):

  # Helper function to convert box points
  def _convert_box_yolo_cv2(boxpoints):
    xmin, ymin, xmax, ymax = boxpoints
    x, y, w, h = xmin, ymin, xmax-xmin, ymax-ymin
    return x,y,w,h
  
  # Loop through each solar panel result, place box accordingly if confidence condition met
  for solar_panel in results['labels']:

    if solar_panel['confidence'] > confidence_thresh:
      x,y,w,h = _convert_box_yolo_cv2(solar_panel['box'])
      cv2.rectangle(img_in, (x,y), (x+w,y+h), (0,255,0), 2)

  cv2_imshow(img_in)
```

```py
import cv2
from dbruntime.patches import cv2_imshow

# Load the image via opencv2
img_loc = f'{project_location}/images/014R.jpg'
img = cv2.imread(img_loc)

cv2_imshow(img)
```

これで、予測結果を生成し、`results`という変数に格納するために、ヘルパー関数を呼び出すことができます。

```py
results = predict_on_image(model, img)
results
```

こちらが`results`のコンテンツとなります。提供した画像のそれぞれで予測された太陽光パネルのクラス(workingとnot working)と自信度(0.0 - 1.0)を予測しました。この自信度は、ヘルパー関数で閾値レベルを設定するために使用します。

```py
0: 512x640 16 defects, 122 workings, 85.6ms
Speed: 2.6ms preprocess, 85.6ms inference, 6.8ms postprocess per image at shape (1, 3, 512, 640)
{'labels': [{'class_name': 'working',
   'class_num': 1,
   'confidence': 0.9999998807907104,
   'box': [26, 199, 87, 293]},
  {'class_name': 'working',
   'class_num': 1,
   'confidence': 0.9999992847442627,
   'box': [31, 287, 90, 384]},
  {'class_name': 'working',
   'class_num': 1,
   'confidence': 0.9999960660934448,
   'box': [412, 344, 470, 441]},
...
  {'class_name': 'working',
   'class_num': 1,
   'confidence': 0.25604432821273804,
   'box': [351, 128, 460, 262]}]}
```

```py
prepare_img_w_box(img, results, 0.98)
```

:::note info
**注意**
ここでは、画像の表示に`cv2_imshow()`を使っています。これは、Databricksノートブックにアウトプットをレンダリングするヘルパー関数です。ご自身の計算資源で標準的なjupyterノートブックを使っている場合には、標準的な`imshow()`を使うことができます。
:::

限定的なエポック数とバッチサイズにしては、我々のモデルは非常にリーズナブルな仕事をしています。最低限のチューニングで、偽陽性(サンプル画像の右手をご覧ください)と偽陰性(サンプル画像の左下)の発生を削減するためにモデルのパフォーマンスを改善することができます。
![](https://benhay.es/img/object_detection_yolov8_files/solar_img_labeled.png)
*ラベル付された太陽光パネルを含むサンプル画像*

# まとめと次のステップ

振り返りましょう。コンピュータービジョンの概念と特に物体検知の重要性を説明しました。タスクの目的を特定し、Databricksプラットフォームを用いてYOLOv8モデルをファインチューニングしました。数行で我々のモデルがリーズナブルで信頼できる予測結果を生成し、数百のラベル付きのサンプルのみを必要とすることを見てきました。このコンピュータビジョンとAI領域におけるエクササイズの意図は、太陽光パネルを検知する完璧なモデルを作り出すことではありません。専門家と同じほどに正確で、人間の能力以上にスケールするモデルを生成する能力を証明したかったのです。この点に関しては成功したと言えます。

さらに、このモデルを改善する機会が存在します:

- 設計: 他のモデルをテストする。同じMLflowエクスペリメントで別のモデルのアウトプットをキャプチャするためにDatabricksを活用。
- ラベリング: フィードバックループをクローズする助けになるAIを活用することができます。[Use DINOv2 to train a YOLOv8 Classification model](https://roboflow.com/train/dinov2-and-yolov8)をご覧ください。
- トレーニング: トレーニングスコープ(エポック、バッチ)の拡大、他のモデルパラメーターのチューニング、トレーニングデータの追加。
- デプロイ: モデルを本格運用し、後段のユーザーに民主化するために、Databricks Mosaic AIのようなツールを活用。

コンピュータービジョンで何をするのかを計画はありますか？AIテクノロジーにおけるこのような進歩をどのように活用しますか？YOLOv8やDatabricksのようなツールからの明確な価値を確認しており、AIに関する現在のハイプサイクルは保証されています。データドリブンの効率性、インフラストラクチャの強化、民主化された情報をさらに推し進めることができます - より良い未来を作り出しましょう。🦾🤖

## その他のリソース

- [Github: Ultralytics/YOLOv8](https://github.com/ultralytics/ultralytics)
- [Ultralytics Docs](https://docs.ultralytics.com/)
- [Databricksデータインテリジェンスプラットフォーム](https://www.databricks.com/jp/product/data-intelligence-platform)

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

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