カスタムデータセットでセマンティックセグメンテーションモデルを微調整する

'Customデータセットでセマンティックセグメンテーションモデルを微調整する'

このガイドでは、最先端のセマンティックセグメンテーションモデルであるSegformerのファインチューニング方法を紹介します。私たちの目標は、ピザ配達ロボットのためのモデルを構築することで、それによってロボットがどこに進むべきかを見ることができ、障害物を認識できるようにすることです 🍕🤖。最初に、Segments.aiで一連の歩道の画像にラベルを付けます。次に、🤗 transformersというオープンソースのライブラリを使用して、事前学習済みのSegFormerモデルをファインチューニングします。このライブラリは、最先端のモデルの簡単な実装を提供しています。このプロセスで、最大のオープンソースのモデルとデータセットのカタログであるHugging Face Hubの使用方法も学びます。

セマンティックセグメンテーションは、画像内の各ピクセルを分類するタスクです。これはより正確な画像の分類方法と見なすことができます。医療画像や自動運転など、さまざまな分野で幅広い用途があります。例えば、ピザ配達ロボットの場合、画像内の歩道がどこにあるか正確に知ることが重要です。

セマンティックセグメンテーションは分類の一種であるため、画像分類とセマンティックセグメンテーションに使用されるネットワークアーキテクチャは非常に似ています。2014年、Longらによる画像セグメンテーションのための異彩を放つ論文では、畳み込みニューラルネットワークが使用されています。最近では、画像分類にTransformers(例:ViT)が使用されており、最新のセマンティックセグメンテーションにも使用されており、最先端の技術をさらに押し上げています。

SegFormerは、2021年にXieらによって提案されたセマンティックセグメンテーションのモデルです。ポジションエンコーディングを使用しない階層的なトランスフォーマーエンコーダと、単純な多層パーセプトロンデコーダを持っています。SegFormerは、複数の一般的なデータセットで最先端の性能を実現しています。さあ、ピザ配達ロボットが歩道の画像でどのようにパフォーマンスを発揮するか見てみましょう。

必要な依存関係をインストールして始めましょう。データセットとモデルをHugging Face Hubにプッシュするために、Git LFSをインストールし、Hugging Faceにログインする必要があります。

git-lfsのインストール方法は、お使いのシステムによって異なる場合があります。Google ColabにはGit LFSが事前にインストールされていることに注意してください。

pip install -q transformers datasets evaluate segments-ai
apt-get install git-lfs
git lfs install
huggingface-cli login

MLプロジェクトの最初のステップは、良いデータセットの準備です。セマンティックセグメンテーションモデルをトレーニングするためには、セマンティックセグメンテーションのラベル付きデータセットが必要です。Hugging Face Hubから既存のデータセット(ADE20kなど)を使用するか、独自のデータセットを作成することができます。

ピザ配達ロボットの場合、CityScapesやBDD100Kなどの既存の自動運転データセットを使用することもできます。ただし、これらのデータセットは道路上を走る車によってキャプチャされたものです。配達ロボットは歩道を走行するため、これらのデータセットの画像とロボットが実際の世界で見るデータとの間に不一致が生じます。

混乱を避けるために、歩道でキャプチャされた独自のセマンティックセグメンテーションデータセットを作成します。次のステップでは、キャプチャした画像にどのようにラベルを付けるかを示します。完成したラベル付きデータセットを使用したい場合は、「独自のデータセットを作成する」セクションをスキップし、「Hubからデータセットを使用する」から続けることができます。

独自のデータセットを作成する

セマンティックセグメンテーションデータセットを作成するには、2つの要素が必要です:

  1. モデルが実世界で遭遇する状況をカバーする画像
  2. セグメンテーションラベル(各ピクセルがクラス/カテゴリを表す画像)

私たちは、ベルギーの歩道の画像を1000枚キャプチャしました。このようなデータセットの収集とラベリングには時間がかかる場合があるため、モデルのパフォーマンスが十分でない場合は、より小さなデータセットから始めて拡張することができます。

歩道データセットのいくつかの例。

セグメンテーションラベルを取得するには、これらの画像のすべての領域/オブジェクトのクラスを示す必要があります。これは時間のかかる作業ですが、適切なツールを使用すれば作業を大幅に加速することができます。セグメンテーションには、Segments.aiを使用します。Segments.aiには、画像セグメンテーションのためのスマートなラベリングツールと使いやすいPython SDKがあります。

Segments.aiでラベリングタスクを設定する

まず、https://segments.ai/join でアカウントを作成してください。次に、新しいデータセットを作成し、画像をアップロードします。WebインターフェースまたはPython SDKを使用してこれを行うことができます(ノートブックを参照してください)。

画像にラベルを付ける

生データが読み込まれたので、segments.ai/homeに移動して新しく作成したデータセットを開きます。”Start labeling”をクリックしてセグメンテーションマスクを作成します。より高速にラベルを付けるために、MLパワードのスーパーピクセルとオートセグメントツールを使用することができます。

ヒント:スーパーピクセルツールを使用する場合、スーパーピクセルサイズを変更するためにスクロールし、セグメントを選択するためにクリックしてドラッグします。

結果をHugging Face Hubにプッシュする

ラベリングが完了したら、ラベル付きデータを含む新しいデータセットリリースを作成します。これは、Segments.aiのリリースタブで行うか、ノートブックで示されているようにSDKを使用してプログラム的に行うことができます。

リリースの作成には数秒かかる場合があります。リリースがまだ作成中かどうかは、Segments.aiのリリースタブで確認することができます。

次に、作成したリリースをSegments.ai Python SDKを介してHugging Faceデータセットに変換します。Segments Pythonクライアントの設定をまだ行っていない場合は、ノートブックの「Segments.aiでラベリングタスクをセットアップする」セクションの手順に従ってください。

データセットのサイズに応じて、変換には時間がかかる場合がありますので、ご注意ください。

from segments.huggingface import release2dataset

release = segments_client.get_release(dataset_identifier, release_name)
hf_dataset = release2dataset(release)

新しいデータセットの特徴を調べると、画像列と対応するラベルが表示されます。ラベルは、アノテーションのリストとセグメンテーションビットマップから構成されます。アノテーションは画像内の異なるオブジェクトに対応しています。各オブジェクトについて、アノテーションにはidcategory_idが含まれています。セグメンテーションビットマップは、各ピクセルにオブジェクトのidが含まれる画像です。詳細は関連ドキュメントを参照してください。

セマンティックセグメンテーションでは、各ピクセルにcategory_idが含まれるセマンティックビットマップが必要です。ビットマップをセマンティックビットマップに変換するために、Segments.ai SDKのget_semantic_bitmap関数を使用します。データセットのすべての行にこの関数を適用するために、dataset.mapを使用します。

from segments.utils import get_semantic_bitmap

def convert_segmentation_bitmap(example):
    return {
        "label.segmentation_bitmap":
            get_semantic_bitmap(
                example["label.segmentation_bitmap"],
                example["label.annotations"],
                id_increment=0,
            )
    }


semantic_dataset = hf_dataset.map(
    convert_segmentation_bitmap,
)

convert_segmentation_bitmap関数をバッチを使用して書き直し、batched=Truedataset.mapに渡すこともできます。これにより、マッピングが大幅に高速化されますが、プロセスがメモリ不足にならないようにbatch_sizeを調整する必要があります。

後で微調整するSegFormerモデルでは、特定の名前の特徴を使用する必要があります。便宜上、この形式に合わせて名前を変更します。したがって、imageの特徴をpixel_valuesに、label.segmentation_bitmaplabelに変更し、その他の特徴を削除します。

semantic_dataset = semantic_dataset.rename_column('image', 'pixel_values')
semantic_dataset = semantic_dataset.rename_column('label.segmentation_bitmap', 'label')
semantic_dataset = semantic_dataset.remove_columns(['name', 'uuid', 'status', 'label.annotations'])

変換されたデータセットをHugging Face Hubにプッシュできるようになりました。これにより、チームとHugging Faceコミュニティがそれを活用することができます。次のセクションでは、Hubからデータセットをロードする方法を見ていきます。

hf_dataset_identifier = f"{hf_username}/{dataset_name}"

semantic_dataset.push_to_hub(hf_dataset_identifier)

Hubからデータセットを使用する

独自のデータセットを作成したくない場合、Hugging Face Hubで使用するための適切なデータセットを見つけた場合は、ここで識別子を定義できます。

たとえば、完全なラベル付き歩道データセットを使用することができます。直接ブラウザで例を確認することもできます。

hf_dataset_identifier = "segments/sidewalk-semantic"

新しいデータセットを作成し、Hugging Face Hubにプッシュしたので、1行でデータセットを読み込むことができます。

from datasets import load_dataset

ds = load_dataset(hf_dataset_identifier)

データセットをシャッフルし、トレーニングセットとテストセットに分割しましょう。

ds = ds.shuffle(seed=1)
ds = ds["train"].train_test_split(test_size=0.2)
train_ds = ds["train"]
test_ds = ds["test"]

ラベルの数と読みやすいIDを抽出して、後でセグメンテーションモデルを正しく設定できるようにします。

import json
from huggingface_hub import hf_hub_download

repo_id = f"datasets/{hf_dataset_identifier}"
filename = "id2label.json"
id2label = json.load(open(hf_hub_download(repo_id=hf_dataset_identifier, filename=filename, repo_type="dataset"), "r"))
id2label = {int(k): v for k, v in id2label.items()}
label2id = {v: k for k, v in id2label.items()}

num_labels = len(id2label)

特徴抽出器とデータ拡張

SegFormerモデルは、入力が特定の形状であることを期待しています。トレーニングデータを期待される形状に変換するために、SegFormerFeatureExtractorを使用することができます。トレーニングデータセット全体に特徴抽出器を事前に適用するために、ds.map関数を使用することもできますが、これには多くのディスク容量が必要です。代わりに、使用されるデータごとにデータバッチを準備するトランスフォームを使用します(オンザフライ)。これにより、データの前処理を待つことなくトレーニングを開始することができます。

トランスフォームでは、モデルをさまざまな照明条件に対して強靭にするために、データ拡張も定義します。バッチ内の画像の明るさ、コントラスト、彩度、色相をランダムに変更するために、torchvisionColorJitter関数を使用します。

from torchvision.transforms import ColorJitter
from transformers import SegformerFeatureExtractor

feature_extractor = SegformerFeatureExtractor()
jitter = ColorJitter(brightness=0.25, contrast=0.25, saturation=0.25, hue=0.1) 

def train_transforms(example_batch):
    images = [jitter(x) for x in example_batch['pixel_values']]
    labels = [x for x in example_batch['label']]
    inputs = feature_extractor(images, labels)
    return inputs


def val_transforms(example_batch):
    images = [x for x in example_batch['pixel_values']]
    labels = [x for x in example_batch['label']]
    inputs = feature_extractor(images, labels)
    return inputs


# トランスフォームを設定
train_ds.set_transform(train_transforms)
test_ds.set_transform(val_transforms)

ファインチューニングのためにモデルをロードする

SegFormerの著者は、サイズが大きくなるにつれて5つのモデル(B0からB5)を定義しています。以下のチャート(元の論文から引用)は、これらの異なるモデルがADE20Kデータセット上でどのようにパフォーマンスを発揮するかを示しています。

ソース

ここでは、最小のSegFormerモデル(B0)をロードし、ImageNet-1kで事前学習されたものを使用します。サイズはわずか14MBです!小さなモデルを使用することで、ピザの配達ロボットでスムーズにモデルを実行できるようになります。

from transformers import SegformerForSemanticSegmentation

pretrained_model_name = "nvidia/mit-b0" 
model = SegformerForSemanticSegmentation.from_pretrained(
    pretrained_model_name,
    id2label=id2label,
    label2id=label2id
)

トレーナーの設定

データ上でモデルをファインチューニングするために、Hugging FaceのTrainer APIを使用します。トレーニング構成と評価メトリックを設定する必要があります。

まず、TrainingArgumentsを設定します。これには学習率やエポック数、モデルを保存する頻度など、すべてのトレーニングハイパーパラメータが定義されています。また、トレーニング後にモデルをハブにプッシュするように指定します(push_to_hub=True)。

from transformers import TrainingArguments

epochs = 50
lr = 0.00006
batch_size = 2

hub_model_id = "segformer-b0-finetuned-segments-sidewalk-2"

training_args = TrainingArguments(
    "segformer-b0-finetuned-segments-sidewalk-outputs",
    learning_rate=lr,
    num_train_epochs=epochs,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    save_total_limit=3,
    evaluation_strategy="steps",
    save_strategy="steps",
    save_steps=20,
    eval_steps=20,
    logging_steps=1,
    eval_accumulation_steps=5,
    load_best_model_at_end=True,
    push_to_hub=True,
    hub_model_id=hub_model_id,
    hub_strategy="end",
)

次に、求めたい評価メトリクスを計算する関数を定義します。セマンティックセグメンテーションを行っているため、平均IoU(Intersection over Union)を使用します。IoUはセグメンテーションマスクの重なりを表します。平均IoUはすべてのクラスについてのIoUの平均です。画像セグメンテーションの評価メトリクスの概要については、このブログポストをご覧ください。

モデルの出力は高さ/4と幅/4の次元を持っているため、mIoUを計算する前に拡大する必要があります。

import torch
from torch import nn
import evaluate

metric = evaluate.load("mean_iou")

def compute_metrics(eval_pred):
  with torch.no_grad():
    logits, labels = eval_pred
    logits_tensor = torch.from_numpy(logits)
    # ロジットをラベルのサイズにスケーリングする
    logits_tensor = nn.functional.interpolate(
        logits_tensor,
        size=labels.shape[-2:],
        mode="bilinear",
        align_corners=False,
    ).argmax(dim=1)

    pred_labels = logits_tensor.detach().cpu().numpy()
    # computeではなく_computeを使用している
    # 詳細については、このissueを参照してください:https://github.com/huggingface/evaluate/pull/328#issuecomment-1286866576
    metrics = metric._compute(
            predictions=pred_labels,
            references=labels,
            num_labels=len(id2label),
            ignore_index=0,
            reduce_labels=feature_extractor.do_reduce_labels,
        )
    
    # カテゴリごとのメトリクスを個別のキーと値のペアとして追加する
    per_category_accuracy = metrics.pop("per_category_accuracy").tolist()
    per_category_iou = metrics.pop("per_category_iou").tolist()

    metrics.update({f"accuracy_{id2label[i]}": v for i, v in enumerate(per_category_accuracy)})
    metrics.update({f"iou_{id2label[i]}": v for i, v in enumerate(per_category_iou)})
    
    return metrics

最後に、Trainerオブジェクトをインスタンス化します。

from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_ds,
    eval_dataset=test_ds,
    compute_metrics=compute_metrics,
)

トレーナーの設定が完了したので、トレーニングはtrain関数を呼び出すだけで簡単です。GPUの管理について心配する必要はありません、トレーナーがそれを処理してくれます。

trainer.train()

トレーニングが終了したら、微調整されたモデルと特徴抽出器をHubにプッシュすることができます。

これにより、結果を含むモデルカードが自動的に作成されます。モデルカードをより完全にするために、kwargsにいくつかの追加情報を提供します。

kwargs = {
    "tags": ["vision", "image-segmentation"],
    "finetuned_from": pretrained_model_name,
    "dataset": hf_dataset_identifier,
}

feature_extractor.push_to_hub(hub_model_id)
trainer.push_to_hub(**kwargs)

さあ、楽しみな部分です、微調整されたモデルを使用する方法です!このセクションでは、Hubからモデルをロードし、推論に使用する方法を示します。

ただし、ホステッド推論APIによって提供されるクールなウィジェットのおかげで、ハブ上で直接モデルを試すこともできます。前のステップでモデルをHubにプッシュした場合、モデルページに推論ウィジェットが表示されるはずです。モデルカードで例となる画像のURLを定義することで、ウィジェットにデフォルトの例を追加することができます。このモデルカードを参考にしてください。

Hubからモデルを使用する

まず、SegformerForSemanticSegmentation.from_pretrained() を使って、ハブからモデルをロードします。

from transformers import SegformerFeatureExtractor, SegformerForSemanticSegmentation

feature_extractor = SegformerFeatureExtractor.from_pretrained("nvidia/segformer-b0-finetuned-ade-512-512")
model = SegformerForSemanticSegmentation.from_pretrained(f"{hf_username}/{hub_model_id}")

次に、テストデータセットから画像を読み込みます。

image = test_ds[0]['pixel_values']
gt_seg = test_ds[0]['label']
image

このテスト画像をセグメンテーションするためには、まず特徴抽出器を使用して画像を準備する必要があります。次に、モデルを通して転送します。

また、出力のロジットを元の画像サイズに拡大する必要もあります。実際のカテゴリ予測を得るためには、ロジットにargmaxを適用するだけです。

from torch import nn

inputs = feature_extractor(images=image, return_tensors="pt")
outputs = model(**inputs)
logits = outputs.logits  # shape (batch_size, num_labels, height/4, width/4)

# まず、ロジットを元の画像サイズにスケーリングします
upsampled_logits = nn.functional.interpolate(
    logits,
    size=image.size[::-1], # (height, width)
    mode='bilinear',
    align_corners=False
)

# 次に、クラス次元でargmaxを適用します
pred_seg = upsampled_logits.argmax(dim=1)[0]

さて、結果を表示する時が来ました。結果をグラウンドトゥルースのマスクの隣に表示します。

どう思いますか?このセグメンテーション情報を使って、ピザ配達ロボットを道路に送りますか?

結果はまだ完璧ではないかもしれませんが、データセットを拡張してモデルをより堅牢にすることができます。また、より大きなSegFormerモデルをトレーニングして、どのように比較されるかを見てみることもできます。

以上です!自分自身の画像セグメンテーションデータセットを作成し、それを用いてセマンティックセグメンテーションモデルを微調整する方法を学びました。

途中でいくつか便利なツールを紹介しました:

  • Segments.aiはデータのラベリングに役立ちます
  • 🤗 datasetsはデータセットの作成と共有に役立ちます
  • 🤗 transformersは最新のセグメンテーションモデルを簡単に微調整することができます
  • Hugging Face Hubはデータセットとモデルの共有、およびモデルの推論ウィジェットの作成に役立ちます

この記事がお楽しみいただけたら嬉しいです。Twitter(@TobiasCornille、@NielsRogge、@huggingface)で、各自のモデルを共有していただければ幸いです。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more