カスタムデータセットでセマンティックセグメンテーションモデルを微調整する
'Customデータセットでセマンティックセグメンテーションモデルを微調整する'
このガイドでは、最先端のセマンティックセグメンテーションモデルであるSegformerのファインチューニング方法を紹介します。私たちの目標は、ピザ配達ロボットのためのモデルを構築することで、それによってロボットがどこに進むべきかを見ることができ、障害物を認識できるようにすることです 🍕🤖。最初に、Segments.aiで一連の歩道の画像にラベルを付けます。次に、🤗 transformers
というオープンソースのライブラリを使用して、事前学習済みのSegFormerモデルをファインチューニングします。このライブラリは、最先端のモデルの簡単な実装を提供しています。このプロセスで、最大のオープンソースのモデルとデータセットのカタログであるHugging Face Hubの使用方法も学びます。
セマンティックセグメンテーションは、画像内の各ピクセルを分類するタスクです。これはより正確な画像の分類方法と見なすことができます。医療画像や自動運転など、さまざまな分野で幅広い用途があります。例えば、ピザ配達ロボットの場合、画像内の歩道がどこにあるか正確に知ることが重要です。
セマンティックセグメンテーションは分類の一種であるため、画像分類とセマンティックセグメンテーションに使用されるネットワークアーキテクチャは非常に似ています。2014年、Longらによる画像セグメンテーションのための異彩を放つ論文では、畳み込みニューラルネットワークが使用されています。最近では、画像分類にTransformers(例:ViT)が使用されており、最新のセマンティックセグメンテーションにも使用されており、最先端の技術をさらに押し上げています。
- PyTorch完全にシャーディングされたデータパラレルを使用して、大規模モデルのトレーニングを加速する
- 実際のデータなしで効率的なテーブルの事前学習:TAPEXへの導入
- 敵対的なデータを使用してモデルを動的にトレーニングする方法
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つの要素が必要です:
- モデルが実世界で遭遇する状況をカバーする画像
- セグメンテーションラベル(各ピクセルがクラス/カテゴリを表す画像)
私たちは、ベルギーの歩道の画像を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)
新しいデータセットの特徴を調べると、画像列と対応するラベルが表示されます。ラベルは、アノテーションのリストとセグメンテーションビットマップから構成されます。アノテーションは画像内の異なるオブジェクトに対応しています。各オブジェクトについて、アノテーションにはid
とcategory_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=True
をdataset.map
に渡すこともできます。これにより、マッピングが大幅に高速化されますが、プロセスがメモリ不足にならないようにbatch_size
を調整する必要があります。
後で微調整するSegFormerモデルでは、特定の名前の特徴を使用する必要があります。便宜上、この形式に合わせて名前を変更します。したがって、image
の特徴をpixel_values
に、label.segmentation_bitmap
をlabel
に変更し、その他の特徴を削除します。
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
関数を使用することもできますが、これには多くのディスク容量が必要です。代わりに、使用されるデータごとにデータバッチを準備するトランスフォームを使用します(オンザフライ)。これにより、データの前処理を待つことなくトレーニングを開始することができます。
トランスフォームでは、モデルをさまざまな照明条件に対して強靭にするために、データ拡張も定義します。バッチ内の画像の明るさ、コントラスト、彩度、色相をランダムに変更するために、torchvision
のColorJitter
関数を使用します。
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!
Was this article helpful?
93 out of 132 found this helpful
Related articles