Add semantic script, trainer (#16834)
* Add first draft * Improve script and README * Improve README * Apply suggestions from code review * Improve script, add link to resulting model * Add corresponding test * Adjust learning rate
This commit is contained in:
@@ -44,6 +44,7 @@ Coming soon!
|
|||||||
| [**`multi-lingual speech-recognition`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/speech-recognition) | Common Voice | ✅ | - |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/multi_lingual_speech_recognition.ipynb)
|
| [**`multi-lingual speech-recognition`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/speech-recognition) | Common Voice | ✅ | - |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/multi_lingual_speech_recognition.ipynb)
|
||||||
| [**`audio-classification`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/audio-classification) | SUPERB KS | ✅ | - |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/audio_classification.ipynb)
|
| [**`audio-classification`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/audio-classification) | SUPERB KS | ✅ | - |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/audio_classification.ipynb)
|
||||||
| [**`image-classification`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/image-classification) | CIFAR-10 | ✅ | ✅ |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/image_classification.ipynb)
|
| [**`image-classification`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/image-classification) | CIFAR-10 | ✅ | ✅ |✅ | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/image_classification.ipynb)
|
||||||
|
| [**`semantic-segmentation`**](https://github.com/huggingface/transformers/tree/main/examples/pytorch/semantic-segmentation) | SCENE_PARSE_150 | ✅ | ✅ |✅ | /
|
||||||
|
|
||||||
|
|
||||||
## Running quick tests
|
## Running quick tests
|
||||||
|
|||||||
@@ -14,13 +14,18 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Semantic segmentation example
|
# Semantic segmentation examples
|
||||||
|
|
||||||
This directory contains a script, `run_semantic_segmentation_no_trainer.py`, that showcases how to fine-tune any model supported by the [`AutoModelForSemanticSegmentation` API](https://huggingface.co/docs/transformers/main/en/model_doc/auto#transformers.AutoModelForSemanticSegmentation) (such as [SegFormer](https://huggingface.co/docs/transformers/main/en/model_doc/segformer), [BEiT](https://huggingface.co/docs/transformers/main/en/model_doc/beit), [DPT]((https://huggingface.co/docs/transformers/main/en/model_doc/dpt))) for semantic segmentation using PyTorch.
|
This directory contains 2 scripts that showcase how to fine-tune any model supported by the [`AutoModelForSemanticSegmentation` API](https://huggingface.co/docs/transformers/main/en/model_doc/auto#transformers.AutoModelForSemanticSegmentation) (such as [SegFormer](https://huggingface.co/docs/transformers/main/en/model_doc/segformer), [BEiT](https://huggingface.co/docs/transformers/main/en/model_doc/beit), [DPT](https://huggingface.co/docs/transformers/main/en/model_doc/dpt)) using PyTorch.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The script leverages [🤗 `Accelerate`](https://github.com/huggingface/accelerate), which allows to write your own training loop in PyTorch, but have it run instantly on any (distributed) environment, including CPU, multi-CPU, GPU, multi-GPU and TPU. It also supports mixed precision.
|
Content:
|
||||||
|
* [Note on custom data](#note-on-custom-data)
|
||||||
|
* [PyTorch version, Trainer](#pytorch-version-trainer)
|
||||||
|
* [PyTorch version, no Trainer](#pytorch-version-no-trainer)
|
||||||
|
* [Reload and perform inference](#reload-and-perform-inference)
|
||||||
|
* [Important notes](#important-notes)
|
||||||
|
|
||||||
## Note on custom data
|
## Note on custom data
|
||||||
|
|
||||||
@@ -33,7 +38,9 @@ The script assumes that you have a `DatasetDict` with 2 columns, "image" and "la
|
|||||||
```python
|
```python
|
||||||
from datasets import Dataset, DatasetDict, Image
|
from datasets import Dataset, DatasetDict, Image
|
||||||
|
|
||||||
image_paths_train = ["path/to/image_1.jpg/png", "path/to/image_2.jpg/png", ..., "path/to/image_n.jpg/png"]
|
# your images can of course have a different extension
|
||||||
|
# semantic segmentation maps are typically stored in the png format
|
||||||
|
image_paths_train = ["path/to/image_1.jpg/jpg", "path/to/image_2.jpg/jpg", ..., "path/to/image_n.jpg/jpg"]
|
||||||
label_paths_train = ["path/to/annotation_1.png", "path/to/annotation_2.png", ..., "path/to/annotation_n.png"]
|
label_paths_train = ["path/to/annotation_1.png", "path/to/annotation_2.png", ..., "path/to/annotation_n.png"]
|
||||||
|
|
||||||
# same for validation
|
# same for validation
|
||||||
@@ -63,17 +70,67 @@ dataset = DatasetDict({
|
|||||||
dataset.push_to_hub("name of repo on the hub")
|
dataset.push_to_hub("name of repo on the hub")
|
||||||
|
|
||||||
# optionally, you can push to a private repo on the hub
|
# optionally, you can push to a private repo on the hub
|
||||||
# dataset.push_to_hub("name of repo on the hub")
|
# dataset.push_to_hub("name of repo on the hub", private=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of such a dataset can be seen at [nielsr/ade20k-demo](https://huggingface.co/datasets/nielsr/ade20k-demo).
|
An example of such a dataset can be seen at [nielsr/ade20k-demo](https://huggingface.co/datasets/nielsr/ade20k-demo).
|
||||||
|
|
||||||
### Creating an id2label mapping
|
### Creating an id2label mapping
|
||||||
|
|
||||||
Besides that, the script also assumes the existence of an `id2label.json` file in the repo, containing a mapping from integers to actual class names.
|
Besides that, the script also assumes the existence of an `id2label.json` file in the repo, containing a mapping from integers to actual class names. An example of that can be seen [here](https://huggingface.co/datasets/nielsr/ade20k-demo/blob/main/id2label.json). This can be created in Python as follows:
|
||||||
An example of that can be seen [here](https://huggingface.co/datasets/nielsr/ade20k-demo/blob/main/id2label.json). You can easily upload this by clicking on "Add file" in the "Files and versions" tab of your repo on the hub.
|
|
||||||
|
|
||||||
## Running the script
|
```python
|
||||||
|
import json
|
||||||
|
# simple example
|
||||||
|
id2label = {0: 'cat', 1: 'dog'}
|
||||||
|
with open('id2label.json', 'w') as fp:
|
||||||
|
json.dump(id2label, fp)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can easily upload this by clicking on "Add file" in the "Files and versions" tab of your repo on the hub.
|
||||||
|
|
||||||
|
## PyTorch version, Trainer
|
||||||
|
|
||||||
|
Based on the script [`run_semantic_segmentation.py`](https://github.com/huggingface/transformers/blob/main/examples/pytorch/semantic-segmentation/run_semantic_segmentation.py).
|
||||||
|
|
||||||
|
The script leverages the [🤗 Trainer API](https://huggingface.co/docs/transformers/main_classes/trainer) to automatically take care of the training for you, running on distributed environments right away.
|
||||||
|
|
||||||
|
Here we show how to fine-tune a [SegFormer](https://huggingface.co/nvidia/mit-b0) model on the [segments/sidewalk-semantic](https://huggingface.co/datasets/segments/sidewalk-semantic) dataset:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python run_semantic_segmentation.py \
|
||||||
|
--model_name_or_path nvidia/mit-b0 \
|
||||||
|
--dataset_name segments/sidewalk-semantic \
|
||||||
|
--output_dir ./segformer_outputs/ \
|
||||||
|
--remove_unused_columns False \
|
||||||
|
--do_train \
|
||||||
|
--do_eval \
|
||||||
|
--evaluation_strategy steps \
|
||||||
|
--push_to_hub \
|
||||||
|
--push_to_hub_model_id segformer-finetuned-sidewalk-10k-steps \
|
||||||
|
--max_steps 10000 \
|
||||||
|
--learning_rate 0.00006 \
|
||||||
|
--lr_scheduler_type polynomial \
|
||||||
|
--per_device_train_batch_size 8 \
|
||||||
|
--per_device_eval_batch_size 8 \
|
||||||
|
--logging_strategy steps \
|
||||||
|
--logging_steps 100 \
|
||||||
|
--evaluation_strategy epoch \
|
||||||
|
--save_strategy epoch \
|
||||||
|
--seed 1337
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting model can be seen here: https://huggingface.co/nielsr/segformer-finetuned-sidewalk-10k-steps. The corresponding Weights and Biases report [here](https://wandb.ai/nielsrogge/huggingface/reports/SegFormer-fine-tuning--VmlldzoxODY5NTQ2). Note that it's always advised to check the original paper to know the details regarding training hyperparameters. E.g. from the SegFormer paper:
|
||||||
|
|
||||||
|
> We trained the models using AdamW optimizer for 160K iterations on ADE20K, Cityscapes, and 80K iterations on COCO-Stuff. (...) We used a batch size of 16 for ADE20K and COCO-Stuff, and a batch size of 8 for Cityscapes. The learning rate was set to an initial value of 0.00006 and then used a “poly” LR schedule with factor 1.0 by default.
|
||||||
|
|
||||||
|
Note that you can replace the model and dataset by simply setting the `model_name_or_path` and `dataset_name` arguments respectively, with any model or dataset from the [hub](https://huggingface.co/). For an overview of all possible arguments, we refer to the [docs](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments) of the `TrainingArguments`, which can be passed as flags.
|
||||||
|
|
||||||
|
## PyTorch version, no Trainer
|
||||||
|
|
||||||
|
Based on the script [`run_semantic_segmentation_no_trainer.py`](https://github.com/huggingface/transformers/blob/main/examples/pytorch/semantic-segmentation/run_semantic_segmentation.py).
|
||||||
|
|
||||||
|
The script leverages [🤗 `Accelerate`](https://github.com/huggingface/accelerate), which allows to write your own training loop in PyTorch, but have it run instantly on any (distributed) environment, including CPU, multi-CPU, GPU, multi-GPU and TPU. It also supports mixed precision.
|
||||||
|
|
||||||
First, run:
|
First, run:
|
||||||
|
|
||||||
@@ -90,14 +147,14 @@ accelerate test
|
|||||||
that will check everything is ready for training. Finally, you can launch training with
|
that will check everything is ready for training. Finally, you can launch training with
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
accelerate launch --output_dir segformer-finetuned-sidewalk --with_tracking --push_to_hub
|
accelerate launch run_semantic_segmentation_no_trainer.py --output_dir segformer-finetuned-sidewalk --with_tracking --push_to_hub
|
||||||
```
|
```
|
||||||
|
|
||||||
and boom, you're training, possibly on multiple GPUs, logging everything to all trackers found in your environment (like Weights and Biases, Tensorboard) and regularly pushing your model to the hub (with the repo name being equal to `args.output_dir` at your HF username) 🤗
|
and boom, you're training, possibly on multiple GPUs, logging everything to all trackers found in your environment (like Weights and Biases, Tensorboard) and regularly pushing your model to the hub (with the repo name being equal to `args.output_dir` at your HF username) 🤗
|
||||||
|
|
||||||
With the default settings, the script fine-tunes a [SegFormer]((https://huggingface.co/docs/transformers/main/en/model_doc/segformer)) model on the [segments/sidewalk-semantic](segments/sidewalk-semantic) dataset.
|
With the default settings, the script fine-tunes a [SegFormer]((https://huggingface.co/docs/transformers/main/en/model_doc/segformer)) model on the [segments/sidewalk-semantic](https://huggingface.co/datasets/segments/sidewalk-semantic) dataset.
|
||||||
|
|
||||||
The resulting model can be seen here: https://huggingface.co/nielsr/segformer-finetuned-sidewalk.
|
The resulting model can be seen here: https://huggingface.co/nielsr/segformer-finetuned-sidewalk. Note that the script usually requires quite a few epochs to achieve great results, e.g. the SegFormer authors fine-tuned their model for 160k steps (batches) on [`scene_parse_150`](https://huggingface.co/datasets/scene_parse_150).
|
||||||
|
|
||||||
## Reload and perform inference
|
## Reload and perform inference
|
||||||
|
|
||||||
@@ -142,6 +199,6 @@ For visualization of the segmentation maps, we refer to the [example notebook](h
|
|||||||
|
|
||||||
## Important notes
|
## Important notes
|
||||||
|
|
||||||
Some datasets, like [`scene_parse_150`](scene_parse_150), contain a "background" label that is not part of the classes. The Scene Parse 150 dataset for instance contains labels between 0 and 150, with 0 being the background class, and 1 to 150 being actual class names (like "tree", "person", etc.). For these kind of datasets, one replaces the background label (0) by 255, which is the `ignore_index` of the PyTorch model's loss function, and reduces all labels by 1. This way, the `labels` are PyTorch tensors containing values between 0 and 149, and 255 for all background/padding.
|
Some datasets, like [`scene_parse_150`](https://huggingface.co/datasets/scene_parse_150), contain a "background" label that is not part of the classes. The Scene Parse 150 dataset for instance contains labels between 0 and 150, with 0 being the background class, and 1 to 150 being actual class names (like "tree", "person", etc.). For these kind of datasets, one replaces the background label (0) by 255, which is the `ignore_index` of the PyTorch model's loss function, and reduces all labels by 1. This way, the `labels` are PyTorch tensors containing values between 0 and 149, and 255 for all background/padding.
|
||||||
|
|
||||||
In case you're training on such a dataset, make sure to set the ``reduce_labels`` flag, which will take care of this.
|
In case you're training on such a dataset, make sure to set the ``reduce_labels`` flag, which will take care of this.
|
||||||
@@ -0,0 +1,502 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2022 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import datasets
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
from datasets import load_dataset
|
||||||
|
from PIL import Image
|
||||||
|
from torch import nn
|
||||||
|
from torchvision import transforms
|
||||||
|
from torchvision.transforms import functional
|
||||||
|
|
||||||
|
import transformers
|
||||||
|
from huggingface_hub import hf_hub_download
|
||||||
|
from transformers import (
|
||||||
|
AutoConfig,
|
||||||
|
AutoFeatureExtractor,
|
||||||
|
AutoModelForSemanticSegmentation,
|
||||||
|
HfArgumentParser,
|
||||||
|
Trainer,
|
||||||
|
TrainingArguments,
|
||||||
|
default_data_collator,
|
||||||
|
)
|
||||||
|
from transformers.trainer_utils import get_last_checkpoint
|
||||||
|
from transformers.utils import check_min_version
|
||||||
|
from transformers.utils.versions import require_version
|
||||||
|
|
||||||
|
|
||||||
|
""" Finetuning any 🤗 Transformers model supported by AutoModelForSemanticSegmentation for semantic segmentation leveraging the Trainer API."""
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Will error if the minimal version of Transformers is not installed. Remove at your own risks.
|
||||||
|
check_min_version("4.19.0.dev0")
|
||||||
|
|
||||||
|
require_version("datasets>=2.0.0", "To fix: pip install -r examples/pytorch/semantic-segmentation/requirements.txt")
|
||||||
|
|
||||||
|
|
||||||
|
def pad_if_smaller(img, size, fill=0):
|
||||||
|
min_size = min(img.size)
|
||||||
|
if min_size < size:
|
||||||
|
original_width, original_height = img.size
|
||||||
|
pad_height = size - original_height if original_height < size else 0
|
||||||
|
pad_width = size - original_width if original_width < size else 0
|
||||||
|
img = functional.pad(img, (0, 0, pad_width, pad_height), fill=fill)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
class Compose:
|
||||||
|
def __init__(self, transforms):
|
||||||
|
self.transforms = transforms
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
for t in self.transforms:
|
||||||
|
image, target = t(image, target)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class Identity:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class Resize:
|
||||||
|
def __init__(self, size):
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
image = functional.resize(image, self.size)
|
||||||
|
target = functional.resize(target, self.size, interpolation=transforms.InterpolationMode.NEAREST)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class RandomResize:
|
||||||
|
def __init__(self, min_size, max_size=None):
|
||||||
|
self.min_size = min_size
|
||||||
|
if max_size is None:
|
||||||
|
max_size = min_size
|
||||||
|
self.max_size = max_size
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
size = random.randint(self.min_size, self.max_size)
|
||||||
|
image = functional.resize(image, size)
|
||||||
|
target = functional.resize(target, size, interpolation=transforms.InterpolationMode.NEAREST)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class RandomCrop:
|
||||||
|
def __init__(self, size):
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
image = pad_if_smaller(image, self.size)
|
||||||
|
target = pad_if_smaller(target, self.size, fill=255)
|
||||||
|
crop_params = transforms.RandomCrop.get_params(image, (self.size, self.size))
|
||||||
|
image = functional.crop(image, *crop_params)
|
||||||
|
target = functional.crop(target, *crop_params)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class RandomHorizontalFlip:
|
||||||
|
def __init__(self, flip_prob):
|
||||||
|
self.flip_prob = flip_prob
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
if random.random() < self.flip_prob:
|
||||||
|
image = functional.hflip(image)
|
||||||
|
target = functional.hflip(target)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class PILToTensor:
|
||||||
|
def __call__(self, image, target):
|
||||||
|
image = functional.pil_to_tensor(image)
|
||||||
|
target = torch.as_tensor(np.array(target), dtype=torch.int64)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class ConvertImageDtype:
|
||||||
|
def __init__(self, dtype):
|
||||||
|
self.dtype = dtype
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
image = functional.convert_image_dtype(image, self.dtype)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class Normalize:
|
||||||
|
def __init__(self, mean, std):
|
||||||
|
self.mean = mean
|
||||||
|
self.std = std
|
||||||
|
|
||||||
|
def __call__(self, image, target):
|
||||||
|
image = functional.normalize(image, mean=self.mean, std=self.std)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
class ReduceLabels:
|
||||||
|
def __call__(self, image, target):
|
||||||
|
if not isinstance(target, np.ndarray):
|
||||||
|
target = np.array(target).astype(np.uint8)
|
||||||
|
# avoid using underflow conversion
|
||||||
|
target[target == 0] = 255
|
||||||
|
target = target - 1
|
||||||
|
target[target == 254] = 255
|
||||||
|
|
||||||
|
target = Image.fromarray(target)
|
||||||
|
return image, target
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataTrainingArguments:
|
||||||
|
"""
|
||||||
|
Arguments pertaining to what data we are going to input our model for training and eval.
|
||||||
|
Using `HfArgumentParser` we can turn this class into argparse arguments to be able to specify
|
||||||
|
them on the command line.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dataset_name: Optional[str] = field(
|
||||||
|
default="segments/sidewalk-semantic",
|
||||||
|
metadata={
|
||||||
|
"help": "Name of a dataset from the hub (could be your own, possibly private dataset hosted on the hub)."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
dataset_config_name: Optional[str] = field(
|
||||||
|
default=None, metadata={"help": "The configuration name of the dataset to use (via the datasets library)."}
|
||||||
|
)
|
||||||
|
train_val_split: Optional[float] = field(
|
||||||
|
default=0.15, metadata={"help": "Percent to split off of train for validation."}
|
||||||
|
)
|
||||||
|
max_train_samples: Optional[int] = field(
|
||||||
|
default=None,
|
||||||
|
metadata={
|
||||||
|
"help": "For debugging purposes or quicker training, truncate the number of training examples to this "
|
||||||
|
"value if set."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
max_eval_samples: Optional[int] = field(
|
||||||
|
default=None,
|
||||||
|
metadata={
|
||||||
|
"help": "For debugging purposes or quicker training, truncate the number of evaluation examples to this "
|
||||||
|
"value if set."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
reduce_labels: Optional[bool] = field(
|
||||||
|
default=False,
|
||||||
|
metadata={"help": "Whether or not to reduce all labels by 1 and replace background by 255."},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.dataset_name is None and (self.train_dir is None and self.validation_dir is None):
|
||||||
|
raise ValueError(
|
||||||
|
"You must specify either a dataset name from the hub or a train and/or validation directory."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModelArguments:
|
||||||
|
"""
|
||||||
|
Arguments pertaining to which model/config/tokenizer we are going to fine-tune from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_name_or_path: str = field(
|
||||||
|
default="nvidia/mit-b0",
|
||||||
|
metadata={"help": "Path to pretrained model or model identifier from huggingface.co/models"},
|
||||||
|
)
|
||||||
|
config_name: Optional[str] = field(
|
||||||
|
default=None, metadata={"help": "Pretrained config name or path if not the same as model_name"}
|
||||||
|
)
|
||||||
|
cache_dir: Optional[str] = field(
|
||||||
|
default=None, metadata={"help": "Where do you want to store the pretrained models downloaded from s3"}
|
||||||
|
)
|
||||||
|
model_revision: str = field(
|
||||||
|
default="main",
|
||||||
|
metadata={"help": "The specific model version to use (can be a branch name, tag name or commit id)."},
|
||||||
|
)
|
||||||
|
feature_extractor_name: str = field(default=None, metadata={"help": "Name or path of preprocessor config."})
|
||||||
|
use_auth_token: bool = field(
|
||||||
|
default=False,
|
||||||
|
metadata={
|
||||||
|
"help": "Will use the token generated when running `transformers-cli login` (necessary to use this script "
|
||||||
|
"with private models)."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# See all possible arguments in src/transformers/training_args.py
|
||||||
|
# or by passing the --help flag to this script.
|
||||||
|
# We now keep distinct sets of args, for a cleaner separation of concerns.
|
||||||
|
|
||||||
|
parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments))
|
||||||
|
if len(sys.argv) == 2 and sys.argv[1].endswith(".json"):
|
||||||
|
# If we pass only one argument to the script and it's the path to a json file,
|
||||||
|
# let's parse it to get our arguments.
|
||||||
|
model_args, data_args, training_args = parser.parse_json_file(json_file=os.path.abspath(sys.argv[1]))
|
||||||
|
else:
|
||||||
|
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||||
|
datefmt="%m/%d/%Y %H:%M:%S",
|
||||||
|
handlers=[logging.StreamHandler(sys.stdout)],
|
||||||
|
)
|
||||||
|
|
||||||
|
log_level = training_args.get_process_log_level()
|
||||||
|
logger.setLevel(log_level)
|
||||||
|
transformers.utils.logging.set_verbosity(log_level)
|
||||||
|
transformers.utils.logging.enable_default_handler()
|
||||||
|
transformers.utils.logging.enable_explicit_format()
|
||||||
|
|
||||||
|
# Log on each process the small summary:
|
||||||
|
logger.warning(
|
||||||
|
f"Process rank: {training_args.local_rank}, device: {training_args.device}, n_gpu: {training_args.n_gpu}"
|
||||||
|
+ f"distributed training: {bool(training_args.local_rank != -1)}, 16-bits training: {training_args.fp16}"
|
||||||
|
)
|
||||||
|
logger.info(f"Training/evaluation parameters {training_args}")
|
||||||
|
|
||||||
|
# Detecting last checkpoint.
|
||||||
|
last_checkpoint = None
|
||||||
|
if os.path.isdir(training_args.output_dir) and training_args.do_train and not training_args.overwrite_output_dir:
|
||||||
|
last_checkpoint = get_last_checkpoint(training_args.output_dir)
|
||||||
|
if last_checkpoint is None and len(os.listdir(training_args.output_dir)) > 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Output directory ({training_args.output_dir}) already exists and is not empty. "
|
||||||
|
"Use --overwrite_output_dir to overcome."
|
||||||
|
)
|
||||||
|
elif last_checkpoint is not None and training_args.resume_from_checkpoint is None:
|
||||||
|
logger.info(
|
||||||
|
f"Checkpoint detected, resuming training at {last_checkpoint}. To avoid this behavior, change "
|
||||||
|
"the `--output_dir` or add `--overwrite_output_dir` to train from scratch."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load dataset
|
||||||
|
# In distributed training, the load_dataset function guarantees that only one local process can concurrently
|
||||||
|
# download the dataset.
|
||||||
|
# TODO support datasets from local folders
|
||||||
|
dataset = load_dataset(data_args.dataset_name, cache_dir=model_args.cache_dir)
|
||||||
|
|
||||||
|
# Rename column names to standardized names (only "image" and "label" need to be present)
|
||||||
|
if "pixel_values" in dataset["train"].column_names:
|
||||||
|
dataset = dataset.rename_columns({"pixel_values": "image"})
|
||||||
|
if "annotation" in dataset["train"].column_names:
|
||||||
|
dataset = dataset.rename_columns({"annotation": "label"})
|
||||||
|
|
||||||
|
# If we don't have a validation split, split off a percentage of train as validation.
|
||||||
|
data_args.train_val_split = None if "validation" in dataset.keys() else data_args.train_val_split
|
||||||
|
if isinstance(data_args.train_val_split, float) and data_args.train_val_split > 0.0:
|
||||||
|
split = dataset["train"].train_test_split(data_args.train_val_split)
|
||||||
|
dataset["train"] = split["train"]
|
||||||
|
dataset["validation"] = split["test"]
|
||||||
|
|
||||||
|
# Prepare label mappings.
|
||||||
|
# We'll include these in the model's config to get human readable labels in the Inference API.
|
||||||
|
if data_args.dataset_name == "scene_parse_150":
|
||||||
|
repo_id = "datasets/huggingface/label-files"
|
||||||
|
filename = "ade20k-id2label.json"
|
||||||
|
else:
|
||||||
|
repo_id = f"datasets/{data_args.dataset_name}"
|
||||||
|
filename = "id2label.json"
|
||||||
|
id2label = json.load(open(hf_hub_download(repo_id, filename), "r"))
|
||||||
|
id2label = {int(k): v for k, v in id2label.items()}
|
||||||
|
label2id = {v: str(k) for k, v in id2label.items()}
|
||||||
|
|
||||||
|
# Load the mean IoU metric from the datasets package
|
||||||
|
metric = datasets.load_metric("mean_iou")
|
||||||
|
|
||||||
|
# Define our compute_metrics function. It takes an `EvalPrediction` object (a namedtuple with a
|
||||||
|
# predictions and label_ids field) and has to return a dictionary string to float.
|
||||||
|
@torch.no_grad()
|
||||||
|
def compute_metrics(eval_pred):
|
||||||
|
logits, labels = eval_pred
|
||||||
|
logits_tensor = torch.from_numpy(logits)
|
||||||
|
# scale the logits to the size of the label
|
||||||
|
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()
|
||||||
|
metrics = metric.compute(
|
||||||
|
predictions=pred_labels,
|
||||||
|
references=labels,
|
||||||
|
num_labels=len(id2label),
|
||||||
|
ignore_index=0,
|
||||||
|
reduce_labels=feature_extractor.reduce_labels,
|
||||||
|
)
|
||||||
|
# add per category metrics as individual key-value pairs
|
||||||
|
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
|
||||||
|
|
||||||
|
config = AutoConfig.from_pretrained(
|
||||||
|
model_args.config_name or model_args.model_name_or_path,
|
||||||
|
label2id=label2id,
|
||||||
|
id2label=id2label,
|
||||||
|
cache_dir=model_args.cache_dir,
|
||||||
|
revision=model_args.model_revision,
|
||||||
|
use_auth_token=True if model_args.use_auth_token else None,
|
||||||
|
)
|
||||||
|
model = AutoModelForSemanticSegmentation.from_pretrained(
|
||||||
|
model_args.model_name_or_path,
|
||||||
|
from_tf=bool(".ckpt" in model_args.model_name_or_path),
|
||||||
|
config=config,
|
||||||
|
cache_dir=model_args.cache_dir,
|
||||||
|
revision=model_args.model_revision,
|
||||||
|
use_auth_token=True if model_args.use_auth_token else None,
|
||||||
|
)
|
||||||
|
feature_extractor = AutoFeatureExtractor.from_pretrained(
|
||||||
|
model_args.feature_extractor_name or model_args.model_name_or_path,
|
||||||
|
cache_dir=model_args.cache_dir,
|
||||||
|
revision=model_args.model_revision,
|
||||||
|
use_auth_token=True if model_args.use_auth_token else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define torchvision transforms to be applied to each image + target.
|
||||||
|
# Not that straightforward in torchvision: https://github.com/pytorch/vision/issues/9
|
||||||
|
# Currently based on official torchvision references: https://github.com/pytorch/vision/blob/main/references/segmentation/transforms.py
|
||||||
|
train_transforms = Compose(
|
||||||
|
[
|
||||||
|
ReduceLabels() if data_args.reduce_labels else Identity(),
|
||||||
|
RandomCrop(size=feature_extractor.size),
|
||||||
|
RandomHorizontalFlip(flip_prob=0.5),
|
||||||
|
PILToTensor(),
|
||||||
|
ConvertImageDtype(torch.float),
|
||||||
|
Normalize(mean=feature_extractor.image_mean, std=feature_extractor.image_std),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Define torchvision transform to be applied to each image.
|
||||||
|
# jitter = ColorJitter(brightness=0.25, contrast=0.25, saturation=0.25, hue=0.1)
|
||||||
|
val_transforms = Compose(
|
||||||
|
[
|
||||||
|
ReduceLabels() if data_args.reduce_labels else Identity(),
|
||||||
|
Resize(size=(feature_extractor.size, feature_extractor.size)),
|
||||||
|
PILToTensor(),
|
||||||
|
ConvertImageDtype(torch.float),
|
||||||
|
Normalize(mean=feature_extractor.image_mean, std=feature_extractor.image_std),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def preprocess_train(example_batch):
|
||||||
|
pixel_values = []
|
||||||
|
labels = []
|
||||||
|
for image, target in zip(example_batch["image"], example_batch["label"]):
|
||||||
|
image, target = train_transforms(image.convert("RGB"), target)
|
||||||
|
pixel_values.append(image)
|
||||||
|
labels.append(target)
|
||||||
|
|
||||||
|
encoding = dict()
|
||||||
|
encoding["pixel_values"] = torch.stack(pixel_values)
|
||||||
|
encoding["labels"] = torch.stack(labels)
|
||||||
|
|
||||||
|
return encoding
|
||||||
|
|
||||||
|
def preprocess_val(example_batch):
|
||||||
|
pixel_values = []
|
||||||
|
labels = []
|
||||||
|
for image, target in zip(example_batch["image"], example_batch["label"]):
|
||||||
|
image, target = val_transforms(image.convert("RGB"), target)
|
||||||
|
pixel_values.append(image)
|
||||||
|
labels.append(target)
|
||||||
|
|
||||||
|
encoding = dict()
|
||||||
|
encoding["pixel_values"] = torch.stack(pixel_values)
|
||||||
|
encoding["labels"] = torch.stack(labels)
|
||||||
|
|
||||||
|
return encoding
|
||||||
|
|
||||||
|
if training_args.do_train:
|
||||||
|
if "train" not in dataset:
|
||||||
|
raise ValueError("--do_train requires a train dataset")
|
||||||
|
if data_args.max_train_samples is not None:
|
||||||
|
dataset["train"] = (
|
||||||
|
dataset["train"].shuffle(seed=training_args.seed).select(range(data_args.max_train_samples))
|
||||||
|
)
|
||||||
|
# Set the training transforms
|
||||||
|
dataset["train"].set_transform(preprocess_train)
|
||||||
|
|
||||||
|
if training_args.do_eval:
|
||||||
|
if "validation" not in dataset:
|
||||||
|
raise ValueError("--do_eval requires a validation dataset")
|
||||||
|
if data_args.max_eval_samples is not None:
|
||||||
|
dataset["validation"] = (
|
||||||
|
dataset["validation"].shuffle(seed=training_args.seed).select(range(data_args.max_eval_samples))
|
||||||
|
)
|
||||||
|
# Set the validation transforms
|
||||||
|
dataset["validation"].set_transform(preprocess_val)
|
||||||
|
|
||||||
|
# Initalize our trainer
|
||||||
|
trainer = Trainer(
|
||||||
|
model=model,
|
||||||
|
args=training_args,
|
||||||
|
train_dataset=dataset["train"] if training_args.do_train else None,
|
||||||
|
eval_dataset=dataset["validation"] if training_args.do_eval else None,
|
||||||
|
compute_metrics=compute_metrics,
|
||||||
|
tokenizer=feature_extractor,
|
||||||
|
data_collator=default_data_collator,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Training
|
||||||
|
if training_args.do_train:
|
||||||
|
checkpoint = None
|
||||||
|
if training_args.resume_from_checkpoint is not None:
|
||||||
|
checkpoint = training_args.resume_from_checkpoint
|
||||||
|
elif last_checkpoint is not None:
|
||||||
|
checkpoint = last_checkpoint
|
||||||
|
train_result = trainer.train(resume_from_checkpoint=checkpoint)
|
||||||
|
trainer.save_model()
|
||||||
|
trainer.log_metrics("train", train_result.metrics)
|
||||||
|
trainer.save_metrics("train", train_result.metrics)
|
||||||
|
trainer.save_state()
|
||||||
|
|
||||||
|
# Evaluation
|
||||||
|
if training_args.do_eval:
|
||||||
|
metrics = trainer.evaluate()
|
||||||
|
trainer.log_metrics("eval", metrics)
|
||||||
|
trainer.save_metrics("eval", metrics)
|
||||||
|
|
||||||
|
# Write model card and (optionally) push to hub
|
||||||
|
kwargs = {
|
||||||
|
"finetuned_from": model_args.model_name_or_path,
|
||||||
|
"dataset": data_args.dataset_name,
|
||||||
|
"tags": ["image-segmentation", "vision"],
|
||||||
|
}
|
||||||
|
if training_args.push_to_hub:
|
||||||
|
trainer.push_to_hub(**kwargs)
|
||||||
|
else:
|
||||||
|
trainer.create_model_card(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -56,10 +56,10 @@ require_version("datasets>=2.0.0", "To fix: pip install -r examples/pytorch/sema
|
|||||||
def pad_if_smaller(img, size, fill=0):
|
def pad_if_smaller(img, size, fill=0):
|
||||||
min_size = min(img.size)
|
min_size = min(img.size)
|
||||||
if min_size < size:
|
if min_size < size:
|
||||||
ow, oh = img.size
|
original_width, original_height = img.size
|
||||||
padh = size - oh if oh < size else 0
|
pad_height = size - original_height if original_height < size else 0
|
||||||
padw = size - ow if ow < size else 0
|
pad_width = size - original_width if original_width < size else 0
|
||||||
img = functional.pad(img, (0, 0, padw, padh), fill=fill)
|
img = functional.pad(img, (0, 0, pad_width, pad_height), fill=fill)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -45,6 +44,7 @@ SRC_DIRS = [
|
|||||||
"audio-classification",
|
"audio-classification",
|
||||||
"speech-pretraining",
|
"speech-pretraining",
|
||||||
"image-pretraining",
|
"image-pretraining",
|
||||||
|
"semantic-segmentation",
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
sys.path.extend(SRC_DIRS)
|
sys.path.extend(SRC_DIRS)
|
||||||
@@ -60,6 +60,7 @@ if SRC_DIRS is not None:
|
|||||||
import run_mlm
|
import run_mlm
|
||||||
import run_ner
|
import run_ner
|
||||||
import run_qa as run_squad
|
import run_qa as run_squad
|
||||||
|
import run_semantic_segmentation
|
||||||
import run_seq2seq_qa as run_squad_seq2seq
|
import run_seq2seq_qa as run_squad_seq2seq
|
||||||
import run_speech_recognition_ctc
|
import run_speech_recognition_ctc
|
||||||
import run_speech_recognition_seq2seq
|
import run_speech_recognition_seq2seq
|
||||||
@@ -386,7 +387,6 @@ class ExamplesTests(TestCasePlus):
|
|||||||
result = get_results(tmp_dir)
|
result = get_results(tmp_dir)
|
||||||
self.assertGreaterEqual(result["eval_bleu"], 30)
|
self.assertGreaterEqual(result["eval_bleu"], 30)
|
||||||
|
|
||||||
@unittest.skip("This is currently broken.")
|
|
||||||
def test_run_image_classification(self):
|
def test_run_image_classification(self):
|
||||||
tmp_dir = self.get_auto_remove_tmp_dir()
|
tmp_dir = self.get_auto_remove_tmp_dir()
|
||||||
testargs = f"""
|
testargs = f"""
|
||||||
@@ -534,7 +534,6 @@ class ExamplesTests(TestCasePlus):
|
|||||||
model = Wav2Vec2ForPreTraining.from_pretrained(tmp_dir)
|
model = Wav2Vec2ForPreTraining.from_pretrained(tmp_dir)
|
||||||
self.assertIsNotNone(model)
|
self.assertIsNotNone(model)
|
||||||
|
|
||||||
@unittest.skip("This is currently broken.")
|
|
||||||
def test_run_vit_mae_pretraining(self):
|
def test_run_vit_mae_pretraining(self):
|
||||||
tmp_dir = self.get_auto_remove_tmp_dir()
|
tmp_dir = self.get_auto_remove_tmp_dir()
|
||||||
testargs = f"""
|
testargs = f"""
|
||||||
@@ -562,3 +561,28 @@ class ExamplesTests(TestCasePlus):
|
|||||||
run_mae.main()
|
run_mae.main()
|
||||||
model = ViTMAEForPreTraining.from_pretrained(tmp_dir)
|
model = ViTMAEForPreTraining.from_pretrained(tmp_dir)
|
||||||
self.assertIsNotNone(model)
|
self.assertIsNotNone(model)
|
||||||
|
|
||||||
|
def test_run_semantic_segmentation(self):
|
||||||
|
tmp_dir = self.get_auto_remove_tmp_dir()
|
||||||
|
testargs = f"""
|
||||||
|
run_semantic_segmentation.py
|
||||||
|
--output_dir {tmp_dir}
|
||||||
|
--dataset_name huggingface/semantic-segmentation-test-sample
|
||||||
|
--do_train
|
||||||
|
--do_eval
|
||||||
|
--remove_unused_columns False
|
||||||
|
--overwrite_output_dir True
|
||||||
|
--max_steps 10
|
||||||
|
--learning_rate=2e-4
|
||||||
|
--per_device_train_batch_size=2
|
||||||
|
--per_device_eval_batch_size=1
|
||||||
|
--seed 32
|
||||||
|
""".split()
|
||||||
|
|
||||||
|
if is_cuda_and_apex_available():
|
||||||
|
testargs.append("--fp16")
|
||||||
|
|
||||||
|
with patch.object(sys, "argv", testargs):
|
||||||
|
run_semantic_segmentation.main()
|
||||||
|
result = get_results(tmp_dir)
|
||||||
|
self.assertGreaterEqual(result["eval_overall_accuracy"], 0.1)
|
||||||
|
|||||||
Reference in New Issue
Block a user