chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# DiT for Image Classification
|
||||
|
||||
This folder contains the image classification running instructions on DiT for RVL-CDIP.
|
||||
|
||||
## Usage
|
||||
### Data Preparation
|
||||
|
||||
**RVL-CDIP**
|
||||
|
||||
Download the "rvl-cdip.tar.gz" from this [link](https://www.cs.ryerson.ca/~aharley/rvl-cdip/) (~37GB). Then extract it to `PATH-to-rvlcdip`.
|
||||
|
||||
### Evaluation
|
||||
Following commands provide example to evaluate the fine-tuned checkpoints.
|
||||
```bash
|
||||
python -m torch.distributed.launch --nproc_per_node=8 --master_port=47770 run_class_finetuning.py \
|
||||
--model beit_base_patch16_224 #beit_base_patch16_224 / beit_large_patch16_224
|
||||
--data_path "/path/to/rvlcdip"
|
||||
--eval_data_path "/path/to/rvlcdip"
|
||||
--enable_deepspeed
|
||||
--nb_classes 16
|
||||
--eval
|
||||
--data_set rvlcdip
|
||||
--finetune /path/to/model.pth
|
||||
--output_dir output_dir
|
||||
--log_dir output_dir/tf
|
||||
--batch_size 256
|
||||
--abs_pos_emb
|
||||
--disable_rel_pos_bias
|
||||
```
|
||||
|
||||
### Training
|
||||
Fine-tune DiT on RVL-CDIP:
|
||||
```bash
|
||||
exp_name=dit-base-exp
|
||||
|
||||
mkdir -p output/${exp_name}
|
||||
python -m torch.distributed.launch --nproc_per_node=8 run_class_finetuning.py
|
||||
--model beit_base_patch16_224 #beit_base_patch16_224 / beit_large_patch16_224
|
||||
--data_path "/path/to/rvlcdip"
|
||||
--eval_data_path "/path/to/rvlcdip"
|
||||
--nb_classes 16
|
||||
--data_set rvlcdip
|
||||
--finetune /path/to/model.pth
|
||||
--output_dir output/${exp_name}/
|
||||
--log_dir output/${exp_name}/tf
|
||||
--batch_size 64
|
||||
--lr 5e-4
|
||||
--update_freq 2
|
||||
--eval_freq 10
|
||||
--save_ckpt_freq 10
|
||||
--warmup_epochs 20
|
||||
--epochs 180
|
||||
--layer_scale_init_value 1e-5
|
||||
--layer_decay 0.75
|
||||
--drop_path 0.2
|
||||
--weight_decay 0.05
|
||||
--clip_grad 1.0
|
||||
--abs_pos_emb
|
||||
--disable_rel_pos_bias
|
||||
```
|
||||
|
||||
|
||||
## Citation
|
||||
|
||||
If you find this repository useful, please consider citing our work:
|
||||
```
|
||||
@misc{li2022dit,
|
||||
title={DiT: Self-supervised Pre-training for Document Image Transformer},
|
||||
author={Junlong Li and Yiheng Xu and Tengchao Lv and Lei Cui and Cha Zhang and Furu Wei},
|
||||
year={2022},
|
||||
eprint={2203.02378},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.CV}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Acknowledgment
|
||||
This part is built using the [timm](https://github.com/rwightman/pytorch-image-models) library, the [Beit](https://github.com/microsoft/unilm/tree/master/beit) repository, the [DeiT](https://github.com/facebookresearch/deit) repository and the [Dino](https://github.com/facebookresearch/dino) repository.
|
||||
@@ -0,0 +1,298 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Modified on torchvision code bases
|
||||
# https://github.com/pytorch/vision
|
||||
# --------------------------------------------------------'
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
from typing import Any, Callable, cast, Dict, List, Optional, Tuple
|
||||
|
||||
from PIL import Image
|
||||
from torchvision.datasets.vision import VisionDataset
|
||||
|
||||
|
||||
def has_file_allowed_extension(filename: str, extensions: Tuple[str, ...]) -> bool:
|
||||
"""Checks if a file is an allowed extension.
|
||||
|
||||
Args:
|
||||
filename (string): path to a file
|
||||
extensions (tuple of strings): extensions to consider (lowercase)
|
||||
|
||||
Returns:
|
||||
bool: True if the filename ends with one of given extensions
|
||||
"""
|
||||
return filename.lower().endswith(extensions)
|
||||
|
||||
|
||||
def is_image_file(filename: str) -> bool:
|
||||
"""Checks if a file is an allowed image extension.
|
||||
|
||||
Args:
|
||||
filename (string): path to a file
|
||||
|
||||
Returns:
|
||||
bool: True if the filename ends with a known image extension
|
||||
"""
|
||||
return has_file_allowed_extension(filename, IMG_EXTENSIONS)
|
||||
|
||||
|
||||
def make_dataset(
|
||||
directory: str,
|
||||
class_to_idx: Dict[str, int],
|
||||
extensions: Optional[Tuple[str, ...]] = None,
|
||||
is_valid_file: Optional[Callable[[str], bool]] = None,
|
||||
) -> List[Tuple[str, int]]:
|
||||
instances = []
|
||||
directory = os.path.expanduser(directory)
|
||||
both_none = extensions is None and is_valid_file is None
|
||||
both_something = extensions is not None and is_valid_file is not None
|
||||
if both_none or both_something:
|
||||
raise ValueError("Both extensions and is_valid_file cannot be None or not None at the same time")
|
||||
if extensions is not None:
|
||||
def is_valid_file(x: str) -> bool:
|
||||
return has_file_allowed_extension(x, cast(Tuple[str, ...], extensions))
|
||||
is_valid_file = cast(Callable[[str], bool], is_valid_file)
|
||||
for target_class in sorted(class_to_idx.keys()):
|
||||
class_index = class_to_idx[target_class]
|
||||
target_dir = os.path.join(directory, target_class)
|
||||
if not os.path.isdir(target_dir):
|
||||
continue
|
||||
for root, _, fnames in sorted(os.walk(target_dir, followlinks=True)):
|
||||
for fname in sorted(fnames):
|
||||
path = os.path.join(root, fname)
|
||||
if is_valid_file(path):
|
||||
item = path, class_index
|
||||
instances.append(item)
|
||||
return instances
|
||||
|
||||
|
||||
class DatasetFolder(VisionDataset):
|
||||
"""A generic data loader where the samples are arranged in this way: ::
|
||||
|
||||
root/class_x/xxx.ext
|
||||
root/class_x/xxy.ext
|
||||
root/class_x/xxz.ext
|
||||
|
||||
root/class_y/123.ext
|
||||
root/class_y/nsdf3.ext
|
||||
root/class_y/asd932_.ext
|
||||
|
||||
Args:
|
||||
root (string): Root directory path.
|
||||
loader (callable): A function to load a sample given its path.
|
||||
extensions (tuple[string]): A list of allowed extensions.
|
||||
both extensions and is_valid_file should not be passed.
|
||||
transform (callable, optional): A function/transform that takes in
|
||||
a sample and returns a transformed version.
|
||||
E.g, ``transforms.RandomCrop`` for images.
|
||||
target_transform (callable, optional): A function/transform that takes
|
||||
in the target and transforms it.
|
||||
is_valid_file (callable, optional): A function that takes path of a file
|
||||
and check if the file is a valid file (used to check of corrupt files)
|
||||
both extensions and is_valid_file should not be passed.
|
||||
|
||||
Attributes:
|
||||
classes (list): List of the class names sorted alphabetically.
|
||||
class_to_idx (dict): Dict with items (class_name, class_index).
|
||||
samples (list): List of (sample path, class_index) tuples
|
||||
targets (list): The class_index value for each image in the dataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
loader: Callable[[str], Any],
|
||||
extensions: Optional[Tuple[str, ...]] = None,
|
||||
transform: Optional[Callable] = None,
|
||||
target_transform: Optional[Callable] = None,
|
||||
is_valid_file: Optional[Callable[[str], bool]] = None,
|
||||
) -> None:
|
||||
super(DatasetFolder, self).__init__(root, transform=transform,
|
||||
target_transform=target_transform)
|
||||
classes, class_to_idx = self._find_classes(self.root)
|
||||
samples = make_dataset(self.root, class_to_idx, extensions, is_valid_file)
|
||||
if len(samples) == 0:
|
||||
msg = "Found 0 files in subfolders of: {}\n".format(self.root)
|
||||
if extensions is not None:
|
||||
msg += "Supported extensions are: {}".format(",".join(extensions))
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.loader = loader
|
||||
self.extensions = extensions
|
||||
|
||||
self.classes = classes
|
||||
self.class_to_idx = class_to_idx
|
||||
self.samples = samples
|
||||
self.targets = [s[1] for s in samples]
|
||||
|
||||
def _find_classes(self, dir: str) -> Tuple[List[str], Dict[str, int]]:
|
||||
"""
|
||||
Finds the class folders in a dataset.
|
||||
|
||||
Args:
|
||||
dir (string): Root directory path.
|
||||
|
||||
Returns:
|
||||
tuple: (classes, class_to_idx) where classes are relative to (dir), and class_to_idx is a dictionary.
|
||||
|
||||
Ensures:
|
||||
No class is a subdirectory of another.
|
||||
"""
|
||||
classes = [d.name for d in os.scandir(dir) if d.is_dir()]
|
||||
classes.sort()
|
||||
class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}
|
||||
return classes, class_to_idx
|
||||
|
||||
def __getitem__(self, index: int) -> Tuple[Any, Any]:
|
||||
"""
|
||||
Args:
|
||||
index (int): Index
|
||||
|
||||
Returns:
|
||||
tuple: (sample, target) where target is class_index of the target class.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
path, target = self.samples[index]
|
||||
sample = self.loader(path)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
index = random.randint(0, len(self.samples) - 1)
|
||||
|
||||
if self.transform is not None:
|
||||
sample = self.transform(sample)
|
||||
if self.target_transform is not None:
|
||||
target = self.target_transform(target)
|
||||
|
||||
return sample, target
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.samples)
|
||||
|
||||
|
||||
IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp')
|
||||
|
||||
|
||||
def pil_loader(path: str) -> Image.Image:
|
||||
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
|
||||
with open(path, 'rb') as f:
|
||||
img = Image.open(f)
|
||||
return img.convert('RGB')
|
||||
|
||||
|
||||
# TODO: specify the return type
|
||||
def accimage_loader(path: str) -> Any:
|
||||
import accimage
|
||||
try:
|
||||
return accimage.Image(path)
|
||||
except IOError:
|
||||
# Potentially a decoding problem, fall back to PIL.Image
|
||||
return pil_loader(path)
|
||||
|
||||
|
||||
def default_loader(path: str) -> Any:
|
||||
from torchvision import get_image_backend
|
||||
if get_image_backend() == 'accimage':
|
||||
return accimage_loader(path)
|
||||
else:
|
||||
return pil_loader(path)
|
||||
|
||||
|
||||
class RvlcdipDatasetFolder(VisionDataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
loader: Callable[[str], Any],
|
||||
extensions: Optional[Tuple[str, ...]] = None,
|
||||
transform: Optional[Callable] = None,
|
||||
target_transform: Optional[Callable] = None,
|
||||
split: str = None,
|
||||
dataset_size: Optional[int] = None
|
||||
) -> None:
|
||||
super().__init__(root, transform=transform, target_transform=target_transform)
|
||||
self.dataset_size = int(dataset_size) if dataset_size is not None else 42948004
|
||||
classes = ["letter",
|
||||
"form",
|
||||
"email",
|
||||
"handwritten",
|
||||
"advertisement",
|
||||
"scientific report",
|
||||
"scientific publication",
|
||||
"specification",
|
||||
"file folder",
|
||||
"news article",
|
||||
"budget",
|
||||
"invoice",
|
||||
"presentation",
|
||||
"questionnaire",
|
||||
"resume",
|
||||
"memo"]
|
||||
class_to_idx = {c: i for i, c in enumerate(classes)}
|
||||
with open(os.path.join(self.root, "labels", split + ".txt"), "r") as f:
|
||||
labels = f.read().splitlines()
|
||||
samples = [(line.split()[0], int(line.split()[1])) for line in labels]
|
||||
try:
|
||||
assert len(samples) > 0 and os.path.exists(os.path.join(self.root, "images", samples[0][0]))
|
||||
except:
|
||||
msg = "Found 0 files in subfolders of: {}\n".format(self.root)
|
||||
msg += "Expected first file: {}".format(os.path.join(self.root, "images", samples[0][0]))
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.loader = loader
|
||||
self.extensions = extensions
|
||||
|
||||
self.classes = classes
|
||||
self.class_to_idx = class_to_idx
|
||||
self.samples = samples
|
||||
self.targets = [s[1] for s in samples]
|
||||
|
||||
def __getitem__(self, index: int) -> Tuple[Any, Any]:
|
||||
"""
|
||||
Args:
|
||||
index (int): Index
|
||||
Returns:
|
||||
tuple: (sample, target) where target is class_index of the target class.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
path, target = self.samples[index]
|
||||
sample = self.loader(os.path.join(self.root, "images", path))
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
index = random.randint(0, len(self.samples) - 1)
|
||||
|
||||
if self.transform is not None:
|
||||
sample = self.transform(sample)
|
||||
if self.target_transform is not None:
|
||||
target = self.target_transform(target)
|
||||
|
||||
return sample, target
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.samples)
|
||||
|
||||
|
||||
class RvlcdipImageFolder(RvlcdipDatasetFolder):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
transform: Optional[Callable] = None,
|
||||
target_transform: Optional[Callable] = None,
|
||||
loader: Callable[[str], Any] = default_loader,
|
||||
split: str = None,
|
||||
dataset_size: Optional[int] = None
|
||||
):
|
||||
super().__init__(root, loader, IMG_EXTENSIONS if split is None else None,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
split=split,
|
||||
dataset_size=dataset_size)
|
||||
self.imgs = self.samples
|
||||
@@ -0,0 +1,92 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm, DINO and DeiT code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# https://github.com/facebookresearch/deit/
|
||||
# https://github.com/facebookresearch/dino
|
||||
# --------------------------------------------------------'
|
||||
from timm.data import create_transform
|
||||
from timm.data.constants import \
|
||||
IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD, IMAGENET_INCEPTION_MEAN, IMAGENET_INCEPTION_STD
|
||||
from timm.data.transforms import str_to_interp_mode
|
||||
from torchvision import transforms
|
||||
|
||||
from dataset_folder import RvlcdipImageFolder
|
||||
|
||||
|
||||
def build_dataset(is_train, args):
|
||||
transform = build_transform(is_train, args)
|
||||
|
||||
print("Transform = ")
|
||||
if isinstance(transform, tuple):
|
||||
for trans in transform:
|
||||
print(" - - - - - - - - - - ")
|
||||
for t in trans.transforms:
|
||||
print(t)
|
||||
else:
|
||||
for t in transform.transforms:
|
||||
print(t)
|
||||
print("---------------------------")
|
||||
|
||||
if args.data_set == 'rvlcdip':
|
||||
root = args.data_path if is_train else args.eval_data_path
|
||||
split = "train" if is_train else "test"
|
||||
dataset = RvlcdipImageFolder(root, split=split, transform=transform)
|
||||
nb_classes = args.nb_classes
|
||||
assert len(dataset.class_to_idx) == nb_classes
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
assert nb_classes == args.nb_classes
|
||||
print("Number of the class = %d" % args.nb_classes)
|
||||
|
||||
return dataset, nb_classes
|
||||
|
||||
|
||||
def build_transform(is_train, args):
|
||||
resize_im = args.input_size > 32
|
||||
imagenet_default_mean_and_std = args.imagenet_default_mean_and_std
|
||||
mean = IMAGENET_INCEPTION_MEAN if not imagenet_default_mean_and_std else IMAGENET_DEFAULT_MEAN
|
||||
std = IMAGENET_INCEPTION_STD if not imagenet_default_mean_and_std else IMAGENET_DEFAULT_STD
|
||||
|
||||
if is_train:
|
||||
# this should always dispatch to transforms_imagenet_train
|
||||
transform = create_transform(
|
||||
input_size=args.input_size,
|
||||
is_training=True,
|
||||
color_jitter=args.color_jitter,
|
||||
auto_augment=args.aa,
|
||||
interpolation=args.train_interpolation,
|
||||
re_prob=args.reprob,
|
||||
re_mode=args.remode,
|
||||
re_count=args.recount,
|
||||
mean=mean,
|
||||
std=std,
|
||||
)
|
||||
if not resize_im:
|
||||
# replace RandomResizedCropAndInterpolation with
|
||||
# RandomCrop
|
||||
transform.transforms[0] = transforms.RandomCrop(
|
||||
args.input_size, padding=4)
|
||||
return transform
|
||||
|
||||
t = []
|
||||
if resize_im:
|
||||
if args.crop_pct is None:
|
||||
if args.input_size < 384:
|
||||
args.crop_pct = 224 / 256
|
||||
else:
|
||||
args.crop_pct = 1.0
|
||||
size = int(args.input_size / args.crop_pct)
|
||||
t.append(
|
||||
transforms.Resize(size, interpolation=str_to_interp_mode("bicubic")),
|
||||
# to maintain same ratio w.r.t. 224 images
|
||||
)
|
||||
t.append(transforms.CenterCrop(args.input_size))
|
||||
|
||||
t.append(transforms.ToTensor())
|
||||
t.append(transforms.Normalize(mean, std))
|
||||
return transforms.Compose(t)
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"train_batch_size": 2048,
|
||||
"train_micro_batch_size_per_gpu": 32,
|
||||
"steps_per_print": 1000,
|
||||
"optimizer": {
|
||||
"type": "Adam",
|
||||
"adam_w_mode": true,
|
||||
"params": {
|
||||
"lr": 0.001,
|
||||
"weight_decay": 0.05,
|
||||
"bias_correction": true,
|
||||
"betas": [
|
||||
0.9,
|
||||
0.999
|
||||
]
|
||||
}
|
||||
},
|
||||
"gradient_clipping": 1,
|
||||
"fp16": {
|
||||
"enabled": true,
|
||||
"loss_scale": 0,
|
||||
"initial_scale_power": 7,
|
||||
"loss_scale_window": 128
|
||||
},
|
||||
"zero_optimization": {
|
||||
"stage": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm, DINO and DeiT code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# https://github.com/facebookresearch/deit/
|
||||
# https://github.com/facebookresearch/dino
|
||||
# --------------------------------------------------------'
|
||||
import math
|
||||
import sys
|
||||
from typing import Iterable, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from timm.data import Mixup
|
||||
from timm.utils import accuracy, ModelEma
|
||||
|
||||
import utils
|
||||
|
||||
|
||||
def train_class_batch(model, samples, target, criterion):
|
||||
outputs = model(samples)
|
||||
if not isinstance(outputs, torch.Tensor):
|
||||
# assume that the model outputs a tuple of [outputs, outputs_kd]
|
||||
outputs, outputs_kd = outputs
|
||||
loss = criterion(outputs, target)
|
||||
return loss, outputs
|
||||
|
||||
|
||||
def get_loss_scale_for_deepspeed(model):
|
||||
optimizer = model.optimizer
|
||||
return optimizer.loss_scale if hasattr(optimizer, "loss_scale") else optimizer.cur_scale
|
||||
|
||||
|
||||
def train_one_epoch(model: torch.nn.Module, criterion: torch.nn.Module,
|
||||
data_loader: Iterable, optimizer: torch.optim.Optimizer,
|
||||
device: torch.device, epoch: int, loss_scaler, max_norm: float = 0,
|
||||
model_ema: Optional[ModelEma] = None, mixup_fn: Optional[Mixup] = None, log_writer=None,
|
||||
start_steps=None, lr_schedule_values=None, wd_schedule_values=None,
|
||||
num_training_steps_per_epoch=None, update_freq=None):
|
||||
model.train(True)
|
||||
metric_logger = utils.MetricLogger(delimiter=" ")
|
||||
metric_logger.add_meter('lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
|
||||
metric_logger.add_meter('min_lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
|
||||
header = 'Epoch: [{}]'.format(epoch)
|
||||
print_freq = 10
|
||||
|
||||
if loss_scaler is None:
|
||||
model.zero_grad()
|
||||
model.micro_steps = 0
|
||||
else:
|
||||
optimizer.zero_grad()
|
||||
|
||||
for data_iter_step, (samples, targets) in enumerate(metric_logger.log_every(data_loader, print_freq, header)):
|
||||
step = data_iter_step // update_freq
|
||||
if step >= num_training_steps_per_epoch:
|
||||
continue
|
||||
it = start_steps + step # global training iteration
|
||||
# Update LR & WD for the first acc
|
||||
if lr_schedule_values is not None or wd_schedule_values is not None and data_iter_step % update_freq == 0:
|
||||
for i, param_group in enumerate(optimizer.param_groups):
|
||||
if lr_schedule_values is not None:
|
||||
param_group["lr"] = lr_schedule_values[it] * param_group["lr_scale"]
|
||||
if wd_schedule_values is not None and param_group["weight_decay"] > 0:
|
||||
param_group["weight_decay"] = wd_schedule_values[it]
|
||||
|
||||
samples = samples.to(device, non_blocking=True)
|
||||
targets = targets.to(device, non_blocking=True)
|
||||
|
||||
if mixup_fn is not None:
|
||||
samples, targets = mixup_fn(samples, targets)
|
||||
|
||||
if loss_scaler is None:
|
||||
samples = samples.half()
|
||||
loss, output = train_class_batch(
|
||||
model, samples, targets, criterion)
|
||||
else:
|
||||
with torch.cuda.amp.autocast():
|
||||
loss, output = train_class_batch(
|
||||
model, samples, targets, criterion)
|
||||
|
||||
loss_value = loss.item()
|
||||
|
||||
if not math.isfinite(loss_value):
|
||||
print("Loss is {}, stopping training".format(loss_value))
|
||||
sys.exit(1)
|
||||
|
||||
if loss_scaler is None:
|
||||
loss /= update_freq
|
||||
model.backward(loss)
|
||||
model.step()
|
||||
|
||||
if (data_iter_step + 1) % update_freq == 0:
|
||||
# model.zero_grad()
|
||||
# Deepspeed will call step() & model.zero_grad() automatic
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
grad_norm = None
|
||||
loss_scale_value = get_loss_scale_for_deepspeed(model)
|
||||
else:
|
||||
# this attribute is added by timm on one optimizer (adahessian)
|
||||
is_second_order = hasattr(optimizer, 'is_second_order') and optimizer.is_second_order
|
||||
loss /= update_freq
|
||||
grad_norm = loss_scaler(loss, optimizer, clip_grad=max_norm,
|
||||
parameters=model.parameters(), create_graph=is_second_order,
|
||||
update_grad=(data_iter_step + 1) % update_freq == 0)
|
||||
if (data_iter_step + 1) % update_freq == 0:
|
||||
optimizer.zero_grad()
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
loss_scale_value = loss_scaler.state_dict()["scale"]
|
||||
|
||||
torch.cuda.synchronize()
|
||||
|
||||
if mixup_fn is None:
|
||||
class_acc = (output.max(-1)[-1] == targets).float().mean()
|
||||
else:
|
||||
class_acc = None
|
||||
metric_logger.update(loss=loss_value)
|
||||
metric_logger.update(class_acc=class_acc)
|
||||
metric_logger.update(loss_scale=loss_scale_value)
|
||||
min_lr = 10.
|
||||
max_lr = 0.
|
||||
for group in optimizer.param_groups:
|
||||
min_lr = min(min_lr, group["lr"])
|
||||
max_lr = max(max_lr, group["lr"])
|
||||
|
||||
metric_logger.update(lr=max_lr)
|
||||
metric_logger.update(min_lr=min_lr)
|
||||
weight_decay_value = None
|
||||
for group in optimizer.param_groups:
|
||||
if group["weight_decay"] > 0:
|
||||
weight_decay_value = group["weight_decay"]
|
||||
metric_logger.update(weight_decay=weight_decay_value)
|
||||
metric_logger.update(grad_norm=grad_norm)
|
||||
|
||||
if log_writer is not None:
|
||||
log_writer.update(loss=loss_value, head="loss")
|
||||
log_writer.update(class_acc=class_acc, head="loss")
|
||||
log_writer.update(loss_scale=loss_scale_value, head="opt")
|
||||
log_writer.update(lr=max_lr, head="opt")
|
||||
log_writer.update(min_lr=min_lr, head="opt")
|
||||
log_writer.update(weight_decay=weight_decay_value, head="opt")
|
||||
log_writer.update(grad_norm=grad_norm, head="opt")
|
||||
|
||||
log_writer.set_step()
|
||||
|
||||
# gather the stats from all processes
|
||||
metric_logger.synchronize_between_processes()
|
||||
print("Averaged stats:", metric_logger)
|
||||
return {k: meter.global_avg for k, meter in metric_logger.meters.items()}
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def evaluate(data_loader, model, device):
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
|
||||
metric_logger = utils.MetricLogger(delimiter=" ")
|
||||
header = 'Test:'
|
||||
|
||||
# switch to evaluation mode
|
||||
model.eval()
|
||||
|
||||
for batch in metric_logger.log_every(data_loader, 10, header):
|
||||
images = batch[0]
|
||||
target = batch[-1]
|
||||
images = images.to(device, non_blocking=True)
|
||||
target = target.to(device, non_blocking=True)
|
||||
|
||||
# compute output
|
||||
with torch.cuda.amp.autocast():
|
||||
output = model(images)
|
||||
loss = criterion(output, target)
|
||||
|
||||
acc1, acc5 = accuracy(output, target, topk=(1, 5))
|
||||
|
||||
batch_size = images.shape[0]
|
||||
metric_logger.update(loss=loss.item())
|
||||
metric_logger.meters['acc1'].update(acc1.item(), n=batch_size)
|
||||
metric_logger.meters['acc5'].update(acc5.item(), n=batch_size)
|
||||
# gather the stats from all processes
|
||||
metric_logger.synchronize_between_processes()
|
||||
print('* Acc@1 {top1.global_avg:.3f} Acc@5 {top5.global_avg:.3f} loss {losses.global_avg:.3f}'
|
||||
.format(top1=metric_logger.acc1, top5=metric_logger.acc5, losses=metric_logger.loss))
|
||||
|
||||
return {k: meter.global_avg for k, meter in metric_logger.meters.items()}
|
||||
@@ -0,0 +1,409 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm and DeiT code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# https://github.com/facebookresearch/deit/
|
||||
# https://github.com/facebookresearch/dino
|
||||
# --------------------------------------------------------'
|
||||
import math
|
||||
from functools import partial
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from timm.models.layers import drop_path, to_2tuple, trunc_normal_
|
||||
from timm.models.registry import register_model
|
||||
|
||||
|
||||
def _cfg(url='', **kwargs):
|
||||
return {
|
||||
'url': url,
|
||||
'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': None,
|
||||
'crop_pct': .9, 'interpolation': 'bicubic',
|
||||
'mean': (0.5, 0.5, 0.5), 'std': (0.5, 0.5, 0.5),
|
||||
**kwargs
|
||||
}
|
||||
|
||||
|
||||
class DropPath(nn.Module):
|
||||
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
||||
"""
|
||||
def __init__(self, drop_prob=None):
|
||||
super(DropPath, self).__init__()
|
||||
self.drop_prob = drop_prob
|
||||
|
||||
def forward(self, x):
|
||||
return drop_path(x, self.drop_prob, self.training)
|
||||
|
||||
def extra_repr(self) -> str:
|
||||
return 'p={}'.format(self.drop_prob)
|
||||
|
||||
|
||||
class Mlp(nn.Module):
|
||||
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = act_layer()
|
||||
self.fc2 = nn.Linear(hidden_features, out_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
# x = self.drop(x)
|
||||
# commit this for the orignal BERT implement
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class Attention(nn.Module):
|
||||
def __init__(
|
||||
self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0.,
|
||||
proj_drop=0., window_size=None, attn_head_dim=None):
|
||||
super().__init__()
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
|
||||
self.qkv = nn.Linear(dim, all_head_dim * 3, bias=False)
|
||||
if qkv_bias:
|
||||
self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
else:
|
||||
self.q_bias = None
|
||||
self.v_bias = None
|
||||
|
||||
if window_size:
|
||||
self.window_size = window_size
|
||||
self.num_relative_distance = (2 * window_size[0] - 1) * (2 * window_size[1] - 1) + 3
|
||||
self.relative_position_bias_table = nn.Parameter(
|
||||
torch.zeros(self.num_relative_distance, num_heads)) # 2*Wh-1 * 2*Ww-1, nH
|
||||
# cls to token & token 2 cls & cls to cls
|
||||
|
||||
# get pair-wise relative position index for each token inside the window
|
||||
coords_h = torch.arange(window_size[0])
|
||||
coords_w = torch.arange(window_size[1])
|
||||
coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
|
||||
coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
|
||||
relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
|
||||
relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
|
||||
relative_coords[:, :, 0] += window_size[0] - 1 # shift to start from 0
|
||||
relative_coords[:, :, 1] += window_size[1] - 1
|
||||
relative_coords[:, :, 0] *= 2 * window_size[1] - 1
|
||||
relative_position_index = \
|
||||
torch.zeros(size=(window_size[0] * window_size[1] + 1, ) * 2, dtype=relative_coords.dtype)
|
||||
relative_position_index[1:, 1:] = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
|
||||
relative_position_index[0, 0:] = self.num_relative_distance - 3
|
||||
relative_position_index[0:, 0] = self.num_relative_distance - 2
|
||||
relative_position_index[0, 0] = self.num_relative_distance - 1
|
||||
|
||||
self.register_buffer("relative_position_index", relative_position_index)
|
||||
else:
|
||||
self.window_size = None
|
||||
self.relative_position_bias_table = None
|
||||
self.relative_position_index = None
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x, rel_pos_bias=None):
|
||||
B, N, C = x.shape
|
||||
qkv_bias = None
|
||||
if self.q_bias is not None:
|
||||
qkv_bias = torch.cat((self.q_bias, torch.zeros_like(self.v_bias, requires_grad=False), self.v_bias))
|
||||
# qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
|
||||
qkv = F.linear(input=x, weight=self.qkv.weight, bias=qkv_bias)
|
||||
qkv = qkv.reshape(B, N, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
|
||||
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1))
|
||||
|
||||
if self.relative_position_bias_table is not None:
|
||||
relative_position_bias = \
|
||||
self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
|
||||
self.window_size[0] * self.window_size[1] + 1,
|
||||
self.window_size[0] * self.window_size[1] + 1, -1) # Wh*Ww,Wh*Ww,nH
|
||||
relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
|
||||
attn = attn + relative_position_bias.unsqueeze(0)
|
||||
|
||||
if rel_pos_bias is not None:
|
||||
attn = attn + rel_pos_bias
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class Block(nn.Module):
|
||||
|
||||
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
|
||||
drop_path=0., init_values=None, act_layer=nn.GELU, norm_layer=nn.LayerNorm,
|
||||
window_size=None, attn_head_dim=None):
|
||||
super().__init__()
|
||||
self.norm1 = norm_layer(dim)
|
||||
self.attn = Attention(
|
||||
dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
attn_drop=attn_drop, proj_drop=drop, window_size=window_size, attn_head_dim=attn_head_dim)
|
||||
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
||||
self.norm2 = norm_layer(dim)
|
||||
mlp_hidden_dim = int(dim * mlp_ratio)
|
||||
self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
|
||||
|
||||
if init_values > 0:
|
||||
self.gamma_1 = nn.Parameter(init_values * torch.ones((dim)),requires_grad=True)
|
||||
self.gamma_2 = nn.Parameter(init_values * torch.ones((dim)),requires_grad=True)
|
||||
else:
|
||||
self.gamma_1, self.gamma_2 = None, None
|
||||
|
||||
def forward(self, x, rel_pos_bias=None):
|
||||
if self.gamma_1 is None:
|
||||
x = x + self.drop_path(self.attn(self.norm1(x), rel_pos_bias=rel_pos_bias))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
||||
else:
|
||||
x = x + self.drop_path(self.gamma_1 * self.attn(self.norm1(x), rel_pos_bias=rel_pos_bias))
|
||||
x = x + self.drop_path(self.gamma_2 * self.mlp(self.norm2(x)))
|
||||
return x
|
||||
|
||||
|
||||
class PatchEmbed(nn.Module):
|
||||
""" Image to Patch Embedding
|
||||
"""
|
||||
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
|
||||
super().__init__()
|
||||
img_size = to_2tuple(img_size)
|
||||
patch_size = to_2tuple(patch_size)
|
||||
num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])
|
||||
self.patch_shape = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
|
||||
self.img_size = img_size
|
||||
self.patch_size = patch_size
|
||||
self.num_patches = num_patches
|
||||
|
||||
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
|
||||
|
||||
def forward(self, x, **kwargs):
|
||||
B, C, H, W = x.shape
|
||||
# FIXME look at relaxing size constraints
|
||||
assert H == self.img_size[0] and W == self.img_size[1], \
|
||||
f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
|
||||
x = self.proj(x).flatten(2).transpose(1, 2)
|
||||
return x
|
||||
|
||||
|
||||
class RelativePositionBias(nn.Module):
|
||||
|
||||
def __init__(self, window_size, num_heads):
|
||||
super().__init__()
|
||||
self.window_size = window_size
|
||||
self.num_relative_distance = (2 * window_size[0] - 1) * (2 * window_size[1] - 1) + 3
|
||||
self.relative_position_bias_table = nn.Parameter(
|
||||
torch.zeros(self.num_relative_distance, num_heads)) # 2*Wh-1 * 2*Ww-1, nH
|
||||
# cls to token & token 2 cls & cls to cls
|
||||
|
||||
# get pair-wise relative position index for each token inside the window
|
||||
coords_h = torch.arange(window_size[0])
|
||||
coords_w = torch.arange(window_size[1])
|
||||
coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
|
||||
coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
|
||||
relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
|
||||
relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
|
||||
relative_coords[:, :, 0] += window_size[0] - 1 # shift to start from 0
|
||||
relative_coords[:, :, 1] += window_size[1] - 1
|
||||
relative_coords[:, :, 0] *= 2 * window_size[1] - 1
|
||||
relative_position_index = \
|
||||
torch.zeros(size=(window_size[0] * window_size[1] + 1,) * 2, dtype=relative_coords.dtype)
|
||||
relative_position_index[1:, 1:] = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
|
||||
relative_position_index[0, 0:] = self.num_relative_distance - 3
|
||||
relative_position_index[0:, 0] = self.num_relative_distance - 2
|
||||
relative_position_index[0, 0] = self.num_relative_distance - 1
|
||||
|
||||
self.register_buffer("relative_position_index", relative_position_index)
|
||||
|
||||
# trunc_normal_(self.relative_position_bias_table, std=.02)
|
||||
|
||||
def forward(self):
|
||||
relative_position_bias = \
|
||||
self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
|
||||
self.window_size[0] * self.window_size[1] + 1,
|
||||
self.window_size[0] * self.window_size[1] + 1, -1) # Wh*Ww,Wh*Ww,nH
|
||||
return relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
|
||||
|
||||
|
||||
class VisionTransformer(nn.Module):
|
||||
""" Vision Transformer with support for patch or hybrid CNN input stage
|
||||
"""
|
||||
def __init__(self, img_size=224, patch_size=16, in_chans=3, num_classes=1000, embed_dim=768, depth=12,
|
||||
num_heads=12, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop_rate=0., attn_drop_rate=0.,
|
||||
drop_path_rate=0., norm_layer=nn.LayerNorm, init_values=None,
|
||||
use_abs_pos_emb=True, use_rel_pos_bias=False, use_shared_rel_pos_bias=False,
|
||||
use_mean_pooling=True, init_scale=0.001):
|
||||
super().__init__()
|
||||
self.num_classes = num_classes
|
||||
self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
|
||||
|
||||
self.patch_embed = PatchEmbed(
|
||||
img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)
|
||||
num_patches = self.patch_embed.num_patches
|
||||
|
||||
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
||||
# self.mask_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
||||
if use_abs_pos_emb:
|
||||
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
|
||||
else:
|
||||
self.pos_embed = None
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
if use_shared_rel_pos_bias:
|
||||
self.rel_pos_bias = RelativePositionBias(window_size=self.patch_embed.patch_shape, num_heads=num_heads)
|
||||
else:
|
||||
self.rel_pos_bias = None
|
||||
|
||||
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
|
||||
self.use_rel_pos_bias = use_rel_pos_bias
|
||||
self.blocks = nn.ModuleList([
|
||||
Block(
|
||||
dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer,
|
||||
init_values=init_values, window_size=self.patch_embed.patch_shape if use_rel_pos_bias else None)
|
||||
for i in range(depth)])
|
||||
self.norm = nn.Identity() if use_mean_pooling else norm_layer(embed_dim)
|
||||
self.fc_norm = norm_layer(embed_dim) if use_mean_pooling else None
|
||||
self.head = nn.Linear(embed_dim, num_classes) if num_classes > 0 else nn.Identity()
|
||||
|
||||
if self.pos_embed is not None:
|
||||
trunc_normal_(self.pos_embed, std=.02)
|
||||
trunc_normal_(self.cls_token, std=.02)
|
||||
# trunc_normal_(self.mask_token, std=.02)
|
||||
trunc_normal_(self.head.weight, std=.02)
|
||||
self.apply(self._init_weights)
|
||||
self.fix_init_weight()
|
||||
|
||||
self.head.weight.data.mul_(init_scale)
|
||||
self.head.bias.data.mul_(init_scale)
|
||||
|
||||
def fix_init_weight(self):
|
||||
def rescale(param, layer_id):
|
||||
param.div_(math.sqrt(2.0 * layer_id))
|
||||
|
||||
for layer_id, layer in enumerate(self.blocks):
|
||||
rescale(layer.attn.proj.weight.data, layer_id + 1)
|
||||
rescale(layer.mlp.fc2.weight.data, layer_id + 1)
|
||||
|
||||
def _init_weights(self, m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
def get_num_layers(self):
|
||||
return len(self.blocks)
|
||||
|
||||
@torch.jit.ignore
|
||||
def no_weight_decay(self):
|
||||
return {'pos_embed', 'cls_token'}
|
||||
|
||||
def get_classifier(self):
|
||||
return self.head
|
||||
|
||||
def reset_classifier(self, num_classes, global_pool=''):
|
||||
self.num_classes = num_classes
|
||||
self.head = nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity()
|
||||
|
||||
def forward_features(self, x):
|
||||
x = self.patch_embed(x)
|
||||
batch_size, seq_len, _ = x.size()
|
||||
|
||||
cls_tokens = self.cls_token.expand(batch_size, -1, -1) # stole cls_tokens impl from Phil Wang, thanks
|
||||
x = torch.cat((cls_tokens, x), dim=1)
|
||||
if self.pos_embed is not None:
|
||||
x = x + self.pos_embed
|
||||
x = self.pos_drop(x)
|
||||
|
||||
rel_pos_bias = self.rel_pos_bias() if self.rel_pos_bias is not None else None
|
||||
for blk in self.blocks:
|
||||
x = blk(x, rel_pos_bias=rel_pos_bias)
|
||||
|
||||
x = self.norm(x)
|
||||
if self.fc_norm is not None:
|
||||
t = x[:, 1:, :]
|
||||
return self.fc_norm(t.mean(1))
|
||||
else:
|
||||
return x[:, 0]
|
||||
|
||||
def forward(self, x):
|
||||
x = self.forward_features(x)
|
||||
x = self.head(x)
|
||||
return x
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_small_patch16_224(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
patch_size=16, embed_dim=384, depth=12, num_heads=8, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_base_patch16_224(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
patch_size=16, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_base_patch16_384(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
img_size=384, patch_size=16, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_large_patch16_224(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
patch_size=16, embed_dim=1024, depth=24, num_heads=16, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_large_patch16_384(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
img_size=384, patch_size=16, embed_dim=1024, depth=24, num_heads=16, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
|
||||
|
||||
@register_model
|
||||
def beit_large_patch16_512(pretrained=False, **kwargs):
|
||||
model = VisionTransformer(
|
||||
img_size=512, patch_size=16, embed_dim=1024, depth=24, num_heads=16, mlp_ratio=4, qkv_bias=True,
|
||||
norm_layer=partial(nn.LayerNorm, eps=1e-6), **kwargs)
|
||||
model.default_cfg = _cfg()
|
||||
return model
|
||||
@@ -0,0 +1,179 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# --------------------------------------------------------'
|
||||
import torch
|
||||
from torch import optim as optim
|
||||
|
||||
from timm.optim.adafactor import Adafactor
|
||||
from timm.optim.adahessian import Adahessian
|
||||
from timm.optim.adamp import AdamP
|
||||
from timm.optim.lookahead import Lookahead
|
||||
from timm.optim.nadam import Nadam
|
||||
from timm.optim.nvnovograd import NvNovoGrad
|
||||
from timm.optim.radam import RAdam
|
||||
from timm.optim.rmsprop_tf import RMSpropTF
|
||||
from timm.optim.sgdp import SGDP
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
from apex.optimizers import FusedNovoGrad, FusedAdam, FusedLAMB, FusedSGD
|
||||
has_apex = True
|
||||
except ImportError:
|
||||
has_apex = False
|
||||
|
||||
|
||||
def get_num_layer_for_vit(var_name, num_max_layer):
|
||||
if var_name in ("cls_token", "mask_token", "pos_embed"):
|
||||
return 0
|
||||
elif var_name.startswith("patch_embed"):
|
||||
return 0
|
||||
elif var_name.startswith("rel_pos_bias"):
|
||||
return num_max_layer - 1
|
||||
elif var_name.startswith("blocks"):
|
||||
layer_id = int(var_name.split('.')[1])
|
||||
return layer_id + 1
|
||||
else:
|
||||
return num_max_layer - 1
|
||||
|
||||
|
||||
class LayerDecayValueAssigner(object):
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
|
||||
def get_scale(self, layer_id):
|
||||
return self.values[layer_id]
|
||||
|
||||
def get_layer_id(self, var_name):
|
||||
return get_num_layer_for_vit(var_name, len(self.values))
|
||||
|
||||
|
||||
def get_parameter_groups(model, weight_decay=1e-5, skip_list=(), get_num_layer=None, get_layer_scale=None):
|
||||
parameter_group_names = {}
|
||||
parameter_group_vars = {}
|
||||
|
||||
for name, param in model.named_parameters():
|
||||
if not param.requires_grad:
|
||||
continue # frozen weights
|
||||
if len(param.shape) == 1 or name.endswith(".bias") or name in skip_list:
|
||||
group_name = "no_decay"
|
||||
this_weight_decay = 0.
|
||||
else:
|
||||
group_name = "decay"
|
||||
this_weight_decay = weight_decay
|
||||
if get_num_layer is not None:
|
||||
layer_id = get_num_layer(name)
|
||||
group_name = "layer_%d_%s" % (layer_id, group_name)
|
||||
else:
|
||||
layer_id = None
|
||||
|
||||
if group_name not in parameter_group_names:
|
||||
if get_layer_scale is not None:
|
||||
scale = get_layer_scale(layer_id)
|
||||
else:
|
||||
scale = 1.
|
||||
|
||||
parameter_group_names[group_name] = {
|
||||
"weight_decay": this_weight_decay,
|
||||
"params": [],
|
||||
"lr_scale": scale
|
||||
}
|
||||
parameter_group_vars[group_name] = {
|
||||
"weight_decay": this_weight_decay,
|
||||
"params": [],
|
||||
"lr_scale": scale
|
||||
}
|
||||
|
||||
parameter_group_vars[group_name]["params"].append(param)
|
||||
parameter_group_names[group_name]["params"].append(name)
|
||||
print("Param groups = %s" % json.dumps(parameter_group_names, indent=2))
|
||||
return list(parameter_group_vars.values())
|
||||
|
||||
|
||||
def create_optimizer(args, model, get_num_layer=None, get_layer_scale=None, filter_bias_and_bn=True, skip_list=None):
|
||||
opt_lower = args.opt.lower()
|
||||
weight_decay = args.weight_decay
|
||||
if weight_decay and filter_bias_and_bn:
|
||||
skip = {}
|
||||
if skip_list is not None:
|
||||
skip = skip_list
|
||||
elif hasattr(model, 'no_weight_decay'):
|
||||
skip = model.no_weight_decay()
|
||||
parameters = get_parameter_groups(model, weight_decay, skip, get_num_layer, get_layer_scale)
|
||||
weight_decay = 0.
|
||||
else:
|
||||
parameters = model.parameters()
|
||||
|
||||
if 'fused' in opt_lower:
|
||||
assert has_apex and torch.cuda.is_available(), 'APEX and CUDA required for fused optimizers'
|
||||
|
||||
opt_args = dict(lr=args.lr, weight_decay=weight_decay)
|
||||
if hasattr(args, 'opt_eps') and args.opt_eps is not None:
|
||||
opt_args['eps'] = args.opt_eps
|
||||
if hasattr(args, 'opt_betas') and args.opt_betas is not None:
|
||||
opt_args['betas'] = args.opt_betas
|
||||
|
||||
opt_split = opt_lower.split('_')
|
||||
opt_lower = opt_split[-1]
|
||||
if opt_lower == 'sgd' or opt_lower == 'nesterov':
|
||||
opt_args.pop('eps', None)
|
||||
optimizer = optim.SGD(parameters, momentum=args.momentum, nesterov=True, **opt_args)
|
||||
elif opt_lower == 'momentum':
|
||||
opt_args.pop('eps', None)
|
||||
optimizer = optim.SGD(parameters, momentum=args.momentum, nesterov=False, **opt_args)
|
||||
elif opt_lower == 'adam':
|
||||
optimizer = optim.Adam(parameters, **opt_args)
|
||||
elif opt_lower == 'adamw':
|
||||
optimizer = optim.AdamW(parameters, **opt_args)
|
||||
elif opt_lower == 'nadam':
|
||||
optimizer = Nadam(parameters, **opt_args)
|
||||
elif opt_lower == 'radam':
|
||||
optimizer = RAdam(parameters, **opt_args)
|
||||
elif opt_lower == 'adamp':
|
||||
optimizer = AdamP(parameters, wd_ratio=0.01, nesterov=True, **opt_args)
|
||||
elif opt_lower == 'sgdp':
|
||||
optimizer = SGDP(parameters, momentum=args.momentum, nesterov=True, **opt_args)
|
||||
elif opt_lower == 'adadelta':
|
||||
optimizer = optim.Adadelta(parameters, **opt_args)
|
||||
elif opt_lower == 'adafactor':
|
||||
if not args.lr:
|
||||
opt_args['lr'] = None
|
||||
optimizer = Adafactor(parameters, **opt_args)
|
||||
elif opt_lower == 'adahessian':
|
||||
optimizer = Adahessian(parameters, **opt_args)
|
||||
elif opt_lower == 'rmsprop':
|
||||
optimizer = optim.RMSprop(parameters, alpha=0.9, momentum=args.momentum, **opt_args)
|
||||
elif opt_lower == 'rmsproptf':
|
||||
optimizer = RMSpropTF(parameters, alpha=0.9, momentum=args.momentum, **opt_args)
|
||||
elif opt_lower == 'novograd' or opt_lower == 'nvnovograd':
|
||||
optimizer = NvNovoGrad(parameters, **opt_args)
|
||||
elif opt_lower == 'fusedsgd':
|
||||
opt_args.pop('eps', None)
|
||||
optimizer = FusedSGD(parameters, momentum=args.momentum, nesterov=True, **opt_args)
|
||||
elif opt_lower == 'fusedmomentum':
|
||||
opt_args.pop('eps', None)
|
||||
optimizer = FusedSGD(parameters, momentum=args.momentum, nesterov=False, **opt_args)
|
||||
elif opt_lower == 'fusedadam':
|
||||
optimizer = FusedAdam(parameters, adam_w_mode=False, **opt_args)
|
||||
elif opt_lower == 'fusedadamw':
|
||||
optimizer = FusedAdam(parameters, adam_w_mode=True, **opt_args)
|
||||
elif opt_lower == 'fusedlamb':
|
||||
optimizer = FusedLAMB(parameters, **opt_args)
|
||||
elif opt_lower == 'fusednovograd':
|
||||
opt_args.setdefault('betas', (0.95, 0.98))
|
||||
optimizer = FusedNovoGrad(parameters, **opt_args)
|
||||
else:
|
||||
assert False and "Invalid optimizer"
|
||||
raise ValueError
|
||||
|
||||
if len(opt_split) > 1:
|
||||
if opt_split[0] == 'lookahead':
|
||||
optimizer = Lookahead(optimizer)
|
||||
|
||||
return optimizer
|
||||
@@ -0,0 +1,16 @@
|
||||
# timm==0.4.12
|
||||
git+https://github.com/rwightman/pytorch-image-models.git
|
||||
taming-transformers-rom1504
|
||||
deepspeed==0.4.0
|
||||
Pillow
|
||||
numpy
|
||||
requests
|
||||
einops
|
||||
tensorboard
|
||||
scipy
|
||||
attrs
|
||||
pybase64
|
||||
pyyaml
|
||||
webdataset
|
||||
omegaconf==2.0.0
|
||||
pytorch-lightning==1.6.0
|
||||
@@ -0,0 +1,615 @@
|
||||
# --------------------------------------------------------
|
||||
# DIT: SELF-SUPERVISED PRE-TRAINING FOR DOCUMENT IMAGE TRANSFORMER
|
||||
# Based on Beit
|
||||
# --------------------------------------------------------'
|
||||
import argparse
|
||||
import datetime
|
||||
import numpy as np
|
||||
import time
|
||||
import torch
|
||||
import torch.backends.cudnn as cudnn
|
||||
import json
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from timm.data.mixup import Mixup
|
||||
from timm.models import create_model
|
||||
from timm.loss import LabelSmoothingCrossEntropy, SoftTargetCrossEntropy
|
||||
from timm.utils import ModelEma
|
||||
from optim_factory import create_optimizer, get_parameter_groups, LayerDecayValueAssigner
|
||||
import webdataset as wds
|
||||
from datasets import build_dataset
|
||||
from engine_for_finetuning import train_one_epoch, evaluate
|
||||
from utils import NativeScalerWithGradNormCount as NativeScaler
|
||||
import utils
|
||||
from scipy import interpolate
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser('BEiT fine-tuning and evaluation script for image classification', add_help=False)
|
||||
parser.add_argument('--batch_size', default=64, type=int)
|
||||
parser.add_argument('--epochs', default=30, type=int)
|
||||
parser.add_argument('--update_freq', default=1, type=int)
|
||||
parser.add_argument('--save_ckpt_freq', default=5, type=int)
|
||||
parser.add_argument('--eval_freq', default=5, type=int)
|
||||
|
||||
# Model parameters
|
||||
parser.add_argument('--model', default='deit_base_patch16_224', type=str, metavar='MODEL',
|
||||
help='Name of model to train')
|
||||
parser.add_argument('--rel_pos_bias', action='store_true')
|
||||
parser.add_argument('--disable_rel_pos_bias', action='store_false', dest='rel_pos_bias')
|
||||
parser.set_defaults(rel_pos_bias=True)
|
||||
parser.add_argument('--abs_pos_emb', action='store_true')
|
||||
parser.add_argument('--qkv_bias', action='store_true')
|
||||
parser.add_argument('--layer_scale_init_value', default=0.1, type=float,
|
||||
help="0.1 for base, 1e-5 for large. set 0 to disable layer scale")
|
||||
|
||||
parser.add_argument('--input_size', default=224, type=int,
|
||||
help='images input size')
|
||||
|
||||
parser.add_argument('--drop', type=float, default=0.0, metavar='PCT',
|
||||
help='Dropout rate (default: 0.)')
|
||||
parser.add_argument('--attn_drop_rate', type=float, default=0.0, metavar='PCT',
|
||||
help='Attention dropout rate (default: 0.)')
|
||||
parser.add_argument('--drop_path', type=float, default=0.1, metavar='PCT',
|
||||
help='Drop path rate (default: 0.1)')
|
||||
|
||||
parser.add_argument('--disable_eval_during_finetuning', action='store_true', default=False)
|
||||
|
||||
parser.add_argument('--model_ema', action='store_true', default=False)
|
||||
parser.add_argument('--model_ema_decay', type=float, default=0.9999, help='')
|
||||
parser.add_argument('--model_ema_force_cpu', action='store_true', default=False, help='')
|
||||
|
||||
# Optimizer parameters
|
||||
parser.add_argument('--opt', default='adamw', type=str, metavar='OPTIMIZER',
|
||||
help='Optimizer (default: "adamw"')
|
||||
parser.add_argument('--opt_eps', default=1e-8, type=float, metavar='EPSILON',
|
||||
help='Optimizer Epsilon (default: 1e-8)')
|
||||
parser.add_argument('--opt_betas', default=None, type=float, nargs='+', metavar='BETA',
|
||||
help='Optimizer Betas (default: None, use opt default)')
|
||||
parser.add_argument('--clip_grad', type=float, default=None, metavar='NORM',
|
||||
help='Clip gradient norm (default: None, no clipping)')
|
||||
parser.add_argument('--momentum', type=float, default=0.9, metavar='M',
|
||||
help='SGD momentum (default: 0.9)')
|
||||
parser.add_argument('--weight_decay', type=float, default=0.05,
|
||||
help='weight decay (default: 0.05)')
|
||||
parser.add_argument('--weight_decay_end', type=float, default=None, help="""Final value of the
|
||||
weight decay. We use a cosine schedule for WD and using a larger decay by
|
||||
the end of training improves performance for ViTs.""")
|
||||
|
||||
parser.add_argument('--lr', type=float, default=5e-4, metavar='LR',
|
||||
help='learning rate (default: 5e-4)')
|
||||
parser.add_argument('--layer_decay', type=float, default=0.9)
|
||||
|
||||
parser.add_argument('--warmup_lr', type=float, default=1e-6, metavar='LR',
|
||||
help='warmup learning rate (default: 1e-6)')
|
||||
parser.add_argument('--min_lr', type=float, default=1e-6, metavar='LR',
|
||||
help='lower lr bound for cyclic schedulers that hit 0 (1e-5)')
|
||||
|
||||
parser.add_argument('--warmup_epochs', type=int, default=5, metavar='N',
|
||||
help='epochs to warmup LR, if scheduler supports')
|
||||
parser.add_argument('--warmup_steps', type=int, default=-1, metavar='N',
|
||||
help='num of steps to warmup LR, will overload warmup_epochs if set > 0')
|
||||
|
||||
# Augmentation parameters
|
||||
parser.add_argument('--color_jitter', type=float, default=0.4, metavar='PCT',
|
||||
help='Color jitter factor (default: 0.4)')
|
||||
parser.add_argument('--aa', type=str, default='rand-m9-mstd0.5-inc1', metavar='NAME',
|
||||
help='Use AutoAugment policy. "v0" or "original". " + "(default: rand-m9-mstd0.5-inc1)'),
|
||||
parser.add_argument('--smoothing', type=float, default=0.1,
|
||||
help='Label smoothing (default: 0.1)')
|
||||
parser.add_argument('--train_interpolation', type=str, default='bicubic',
|
||||
help='Training interpolation (random, bilinear, bicubic default: "bicubic")')
|
||||
|
||||
# Evaluation parameters
|
||||
parser.add_argument('--crop_pct', type=float, default=None)
|
||||
|
||||
# * Random Erase params
|
||||
parser.add_argument('--reprob', type=float, default=0.25, metavar='PCT',
|
||||
help='Random erase prob (default: 0.25)')
|
||||
parser.add_argument('--remode', type=str, default='pixel',
|
||||
help='Random erase mode (default: "pixel")')
|
||||
parser.add_argument('--recount', type=int, default=1,
|
||||
help='Random erase count (default: 1)')
|
||||
parser.add_argument('--resplit', action='store_true', default=False,
|
||||
help='Do not random erase first (clean) augmentation split')
|
||||
|
||||
# * Mixup params
|
||||
parser.add_argument('--mixup', type=float, default=0,
|
||||
help='mixup alpha, mixup enabled if > 0.')
|
||||
parser.add_argument('--cutmix', type=float, default=0,
|
||||
help='cutmix alpha, cutmix enabled if > 0.')
|
||||
parser.add_argument('--cutmix_minmax', type=float, nargs='+', default=None,
|
||||
help='cutmix min/max ratio, overrides alpha and enables cutmix if set (default: None)')
|
||||
parser.add_argument('--mixup_prob', type=float, default=1.0,
|
||||
help='Probability of performing mixup or cutmix when either/both is enabled')
|
||||
parser.add_argument('--mixup_switch_prob', type=float, default=0.5,
|
||||
help='Probability of switching to cutmix when both mixup and cutmix enabled')
|
||||
parser.add_argument('--mixup_mode', type=str, default='batch',
|
||||
help='How to apply mixup/cutmix params. Per "batch", "pair", or "elem"')
|
||||
|
||||
# * Finetuning params
|
||||
parser.add_argument('--finetune', default='',
|
||||
help='finetune from checkpoint')
|
||||
parser.add_argument('--model_key', default='model|module', type=str)
|
||||
parser.add_argument('--model_prefix', default='', type=str)
|
||||
parser.add_argument('--init_scale', default=0.001, type=float)
|
||||
parser.add_argument('--use_mean_pooling', action='store_true')
|
||||
parser.set_defaults(use_mean_pooling=True)
|
||||
parser.add_argument('--use_cls', action='store_false', dest='use_mean_pooling')
|
||||
parser.add_argument('--disable_weight_decay_on_rel_pos_bias', action='store_true', default=False)
|
||||
|
||||
# Dataset parameters
|
||||
parser.add_argument('--data_path', default='/datasets01/imagenet_full_size/061417/', type=str,
|
||||
help='dataset path')
|
||||
parser.add_argument('--eval_data_path', default=None, type=str,
|
||||
help='dataset path for evaluation')
|
||||
parser.add_argument('--nb_classes', default=0, type=int,
|
||||
help='number of the classification types')
|
||||
parser.add_argument('--imagenet_default_mean_and_std', default=False, action='store_true')
|
||||
|
||||
parser.add_argument('--data_set', default='IMNET', choices=['CIFAR', 'IMNET', 'image_folder', "rvlcdip", "rvlcdip_wds"],
|
||||
type=str, help='ImageNet dataset path')
|
||||
parser.add_argument('--output_dir', default='',
|
||||
help='path where to save, empty for no saving')
|
||||
parser.add_argument('--log_dir', default=None,
|
||||
help='path where to tensorboard log')
|
||||
parser.add_argument('--device', default='cuda',
|
||||
help='device to use for training / testing')
|
||||
parser.add_argument('--seed', default=0, type=int)
|
||||
parser.add_argument('--resume', default='',
|
||||
help='resume from checkpoint')
|
||||
parser.add_argument('--auto_resume', action='store_true')
|
||||
parser.add_argument('--no_auto_resume', action='store_false', dest='auto_resume')
|
||||
parser.set_defaults(auto_resume=True)
|
||||
|
||||
parser.add_argument('--save_ckpt', action='store_true')
|
||||
parser.add_argument('--no_save_ckpt', action='store_false', dest='save_ckpt')
|
||||
parser.set_defaults(save_ckpt=True)
|
||||
|
||||
parser.add_argument('--start_epoch', default=0, type=int, metavar='N',
|
||||
help='start epoch')
|
||||
parser.add_argument('--eval', action='store_true',
|
||||
help='Perform evaluation only')
|
||||
parser.add_argument('--dist_eval', action='store_true', default=False,
|
||||
help='Enabling distributed evaluation')
|
||||
parser.add_argument('--num_workers', default=10, type=int)
|
||||
parser.add_argument('--pin_mem', action='store_true',
|
||||
help='Pin CPU memory in DataLoader for more efficient (sometimes) transfer to GPU.')
|
||||
parser.add_argument('--no_pin_mem', action='store_false', dest='pin_mem')
|
||||
parser.set_defaults(pin_mem=True)
|
||||
|
||||
# distributed training parameters
|
||||
parser.add_argument('--world_size', default=1, type=int,
|
||||
help='number of distributed processes')
|
||||
parser.add_argument('--local_rank', default=-1, type=int)
|
||||
parser.add_argument('--dist_on_itp', action='store_true')
|
||||
parser.add_argument('--dist_url', default='env://',
|
||||
help='url used to set up distributed training')
|
||||
|
||||
parser.add_argument('--enable_deepspeed', action='store_true', default=False)
|
||||
parser.add_argument('--zero_stage', default=0, type=int,
|
||||
help='ZeRO optimizer stage (default: 0)')
|
||||
|
||||
known_args, _ = parser.parse_known_args()
|
||||
|
||||
if known_args.enable_deepspeed:
|
||||
try:
|
||||
import deepspeed
|
||||
from deepspeed import DeepSpeedConfig
|
||||
parser = deepspeed.add_config_arguments(parser)
|
||||
ds_init = deepspeed.initialize
|
||||
except:
|
||||
print("Please 'pip install deepspeed==0.4.0'")
|
||||
exit(0)
|
||||
else:
|
||||
ds_init = None
|
||||
|
||||
return parser.parse_args(), ds_init
|
||||
|
||||
|
||||
def main(args, ds_init):
|
||||
utils.init_distributed_mode(args)
|
||||
|
||||
if ds_init is not None:
|
||||
utils.create_ds_config(args)
|
||||
|
||||
print(args)
|
||||
|
||||
device = torch.device(args.device)
|
||||
|
||||
# fix the seed for reproducibility
|
||||
seed = args.seed + utils.get_rank()
|
||||
torch.manual_seed(seed)
|
||||
np.random.seed(seed)
|
||||
# random.seed(seed)
|
||||
|
||||
cudnn.benchmark = True
|
||||
|
||||
dataset_train, args.nb_classes = build_dataset(is_train=True, args=args)
|
||||
if args.disable_eval_during_finetuning:
|
||||
dataset_val = None
|
||||
else:
|
||||
dataset_val, _ = build_dataset(is_train=False, args=args)
|
||||
|
||||
if True: # args.distributed:
|
||||
num_tasks = utils.get_world_size()
|
||||
global_rank = utils.get_rank()
|
||||
if not isinstance(dataset_train, torch.utils.data.IterableDataset):
|
||||
sampler_train = torch.utils.data.DistributedSampler(
|
||||
dataset_train, num_replicas=num_tasks, rank=global_rank, shuffle=True
|
||||
)
|
||||
print("Sampler_train = %s" % str(sampler_train))
|
||||
if args.dist_eval:
|
||||
if len(dataset_val) % num_tasks != 0:
|
||||
print('Warning: Enabling distributed evaluation with an eval dataset not divisible by process number. '
|
||||
'This will slightly alter validation results as extra duplicate entries are added to achieve '
|
||||
'equal num of samples per-process.')
|
||||
sampler_val = torch.utils.data.DistributedSampler(
|
||||
dataset_val, num_replicas=num_tasks, rank=global_rank, shuffle=False)
|
||||
else:
|
||||
sampler_val = torch.utils.data.SequentialSampler(dataset_val)
|
||||
else:
|
||||
sampler_train = torch.utils.data.RandomSampler(dataset_train)
|
||||
sampler_val = torch.utils.data.SequentialSampler(dataset_val)
|
||||
|
||||
if 'AMLT_OUTPUT_DIR' in os.environ:
|
||||
args.log_dir = os.environ['AMLT_OUTPUT_DIR']
|
||||
print(f'update log_dir to {args.log_dir}')
|
||||
if global_rank == 0 and args.log_dir is not None:
|
||||
os.makedirs(args.log_dir, exist_ok=True)
|
||||
log_writer = utils.TensorboardLogger(log_dir=args.log_dir)
|
||||
else:
|
||||
log_writer = None
|
||||
|
||||
dataset_size_train = len(dataset_train)
|
||||
if isinstance(dataset_train, torch.utils.data.IterableDataset):
|
||||
dataset_train = dataset_train.batched(args.batch_size, partial=False)
|
||||
data_loader_train = wds.WebLoader(
|
||||
dataset_train, num_workers=args.num_workers, batch_size=None, shuffle=False, )
|
||||
data_loader_train = data_loader_train.ddp_equalize(dataset_size_train // args.batch_size, with_length=True)
|
||||
else:
|
||||
data_loader_train = torch.utils.data.DataLoader(
|
||||
dataset_train, sampler=sampler_train,
|
||||
batch_size=args.batch_size,
|
||||
num_workers=args.num_workers,
|
||||
pin_memory=args.pin_mem,
|
||||
drop_last=True,
|
||||
)
|
||||
|
||||
|
||||
if dataset_val is not None:
|
||||
dataset_size_val = len(dataset_val)
|
||||
if not isinstance(dataset_val, torch.utils.data.IterableDataset):
|
||||
data_loader_val = torch.utils.data.DataLoader(
|
||||
dataset_val, sampler=sampler_val,
|
||||
batch_size=int(1.5 * args.batch_size),
|
||||
num_workers=args.num_workers,
|
||||
pin_memory=args.pin_mem,
|
||||
drop_last=False
|
||||
)
|
||||
else:
|
||||
dataset_val = dataset_val.batched(args.batch_size, partial=False)
|
||||
data_loader_val = wds.WebLoader(
|
||||
dataset_val, num_workers=args.num_workers, batch_size=None, shuffle=False, )
|
||||
data_loader_val = data_loader_val.ddp_equalize(dataset_size_val // args.batch_size, with_length=True)
|
||||
else:
|
||||
data_loader_val = None
|
||||
|
||||
mixup_fn = None
|
||||
mixup_active = args.mixup > 0 or args.cutmix > 0. or args.cutmix_minmax is not None
|
||||
if mixup_active:
|
||||
print("Mixup is activated!")
|
||||
mixup_fn = Mixup(
|
||||
mixup_alpha=args.mixup, cutmix_alpha=args.cutmix, cutmix_minmax=args.cutmix_minmax,
|
||||
prob=args.mixup_prob, switch_prob=args.mixup_switch_prob, mode=args.mixup_mode,
|
||||
label_smoothing=args.smoothing, num_classes=args.nb_classes)
|
||||
|
||||
if "beit" not in args.model:
|
||||
model = create_model(args.model, pretrained=False, num_classes=args.nb_classes, distilled=False)
|
||||
else:
|
||||
model = create_model(
|
||||
args.model,
|
||||
pretrained=False,
|
||||
num_classes=args.nb_classes,
|
||||
drop_rate=args.drop,
|
||||
drop_path_rate=args.drop_path,
|
||||
attn_drop_rate=args.attn_drop_rate,
|
||||
drop_block_rate=None,
|
||||
use_mean_pooling=args.use_mean_pooling,
|
||||
init_scale=args.init_scale,
|
||||
use_rel_pos_bias=args.rel_pos_bias,
|
||||
use_abs_pos_emb=args.abs_pos_emb,
|
||||
init_values=args.layer_scale_init_value,
|
||||
)
|
||||
|
||||
patch_size = model.patch_embed.patch_size
|
||||
print("Patch size = %s" % str(patch_size))
|
||||
args.window_size = (args.input_size // patch_size[0], args.input_size // patch_size[1])
|
||||
args.patch_size = patch_size
|
||||
|
||||
if args.finetune:
|
||||
if args.finetune.startswith('https'):
|
||||
checkpoint = torch.hub.load_state_dict_from_url(
|
||||
args.finetune, map_location='cpu', check_hash=False)
|
||||
else:
|
||||
checkpoint = torch.load(args.finetune, map_location='cpu')
|
||||
|
||||
print("Load ckpt from %s" % args.finetune)
|
||||
checkpoint_model = None
|
||||
for model_key in args.model_key.split('|'):
|
||||
if model_key in checkpoint:
|
||||
checkpoint_model = checkpoint[model_key]
|
||||
print("Load state_dict by model_key = %s" % model_key)
|
||||
break
|
||||
if checkpoint_model is None:
|
||||
checkpoint_model = checkpoint
|
||||
state_dict = model.state_dict()
|
||||
for k in ['head.weight', 'head.bias']:
|
||||
if k in checkpoint_model and checkpoint_model[k].shape != state_dict[k].shape:
|
||||
print(f"Removing key {k} from pretrained checkpoint")
|
||||
del checkpoint_model[k]
|
||||
|
||||
if getattr(model, "use_rel_pos_bias", False) and "rel_pos_bias.relative_position_bias_table" in checkpoint_model:
|
||||
print("Expand the shared relative position embedding to each transformer block. ")
|
||||
num_layers = model.get_num_layers()
|
||||
rel_pos_bias = checkpoint_model["rel_pos_bias.relative_position_bias_table"]
|
||||
for i in range(num_layers):
|
||||
checkpoint_model["blocks.%d.attn.relative_position_bias_table" % i] = rel_pos_bias.clone()
|
||||
|
||||
checkpoint_model.pop("rel_pos_bias.relative_position_bias_table")
|
||||
|
||||
all_keys = list(checkpoint_model.keys())
|
||||
for key in all_keys:
|
||||
if "relative_position_index" in key:
|
||||
checkpoint_model.pop(key)
|
||||
|
||||
if "relative_position_bias_table" in key:
|
||||
rel_pos_bias = checkpoint_model[key]
|
||||
src_num_pos, num_attn_heads = rel_pos_bias.size()
|
||||
dst_num_pos, _ = model.state_dict()[key].size()
|
||||
dst_patch_shape = model.patch_embed.patch_shape
|
||||
if dst_patch_shape[0] != dst_patch_shape[1]:
|
||||
raise NotImplementedError()
|
||||
num_extra_tokens = dst_num_pos - (dst_patch_shape[0] * 2 - 1) * (dst_patch_shape[1] * 2 - 1)
|
||||
src_size = int((src_num_pos - num_extra_tokens) ** 0.5)
|
||||
dst_size = int((dst_num_pos - num_extra_tokens) ** 0.5)
|
||||
if src_size != dst_size:
|
||||
print("Position interpolate for %s from %dx%d to %dx%d" % (
|
||||
key, src_size, src_size, dst_size, dst_size))
|
||||
extra_tokens = rel_pos_bias[-num_extra_tokens:, :]
|
||||
rel_pos_bias = rel_pos_bias[:-num_extra_tokens, :]
|
||||
|
||||
def geometric_progression(a, r, n):
|
||||
return a * (1.0 - r ** n) / (1.0 - r)
|
||||
|
||||
left, right = 1.01, 1.5
|
||||
while right - left > 1e-6:
|
||||
q = (left + right) / 2.0
|
||||
gp = geometric_progression(1, q, src_size // 2)
|
||||
if gp > dst_size // 2:
|
||||
right = q
|
||||
else:
|
||||
left = q
|
||||
|
||||
# if q > 1.090307:
|
||||
# q = 1.090307
|
||||
|
||||
dis = []
|
||||
cur = 1
|
||||
for i in range(src_size // 2):
|
||||
dis.append(cur)
|
||||
cur += q ** (i + 1)
|
||||
|
||||
r_ids = [-_ for _ in reversed(dis)]
|
||||
|
||||
x = r_ids + [0] + dis
|
||||
y = r_ids + [0] + dis
|
||||
|
||||
t = dst_size // 2.0
|
||||
dx = np.arange(-t, t + 0.1, 1.0)
|
||||
dy = np.arange(-t, t + 0.1, 1.0)
|
||||
|
||||
print("Original positions = %s" % str(x))
|
||||
print("Target positions = %s" % str(dx))
|
||||
|
||||
all_rel_pos_bias = []
|
||||
|
||||
for i in range(num_attn_heads):
|
||||
z = rel_pos_bias[:, i].view(src_size, src_size).float().numpy()
|
||||
f = interpolate.interp2d(x, y, z, kind='cubic')
|
||||
all_rel_pos_bias.append(
|
||||
torch.Tensor(f(dx, dy)).contiguous().view(-1, 1).to(rel_pos_bias.device))
|
||||
|
||||
rel_pos_bias = torch.cat(all_rel_pos_bias, dim=-1)
|
||||
|
||||
new_rel_pos_bias = torch.cat((rel_pos_bias, extra_tokens), dim=0)
|
||||
checkpoint_model[key] = new_rel_pos_bias
|
||||
|
||||
# interpolate position embedding
|
||||
if 'pos_embed' in checkpoint_model:
|
||||
pos_embed_checkpoint = checkpoint_model['pos_embed']
|
||||
embedding_size = pos_embed_checkpoint.shape[-1]
|
||||
num_patches = model.patch_embed.num_patches
|
||||
num_extra_tokens = model.pos_embed.shape[-2] - num_patches
|
||||
# height (== width) for the checkpoint position embedding
|
||||
orig_size = int((pos_embed_checkpoint.shape[-2] - num_extra_tokens) ** 0.5)
|
||||
# height (== width) for the new position embedding
|
||||
new_size = int(num_patches ** 0.5)
|
||||
# class_token and dist_token are kept unchanged
|
||||
if orig_size != new_size:
|
||||
print("Position interpolate from %dx%d to %dx%d" % (orig_size, orig_size, new_size, new_size))
|
||||
extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens]
|
||||
# only the position tokens are interpolated
|
||||
pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:]
|
||||
pos_tokens = pos_tokens.reshape(-1, orig_size, orig_size, embedding_size).permute(0, 3, 1, 2)
|
||||
pos_tokens = torch.nn.functional.interpolate(
|
||||
pos_tokens, size=(new_size, new_size), mode='bicubic', align_corners=False)
|
||||
pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)
|
||||
new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1)
|
||||
checkpoint_model['pos_embed'] = new_pos_embed
|
||||
|
||||
utils.load_state_dict(model, checkpoint_model, prefix=args.model_prefix)
|
||||
# model.load_state_dict(checkpoint_model, strict=False)
|
||||
|
||||
model.to(device)
|
||||
|
||||
model_ema = None
|
||||
if args.model_ema:
|
||||
# Important to create EMA model after cuda(), DP wrapper, and AMP but before SyncBN and DDP wrapper
|
||||
model_ema = ModelEma(
|
||||
model,
|
||||
decay=args.model_ema_decay,
|
||||
device='cpu' if args.model_ema_force_cpu else '',
|
||||
resume='')
|
||||
print("Using EMA with decay = %.8f" % args.model_ema_decay)
|
||||
|
||||
model_without_ddp = model
|
||||
n_parameters = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
||||
|
||||
print("Model = %s" % str(model_without_ddp))
|
||||
print('number of params:', n_parameters)
|
||||
|
||||
total_batch_size = args.batch_size * args.update_freq * utils.get_world_size()
|
||||
num_training_steps_per_epoch = dataset_size_train // total_batch_size
|
||||
print("LR = %.8f" % args.lr)
|
||||
print("Batch size = %d" % total_batch_size)
|
||||
print("Update frequent = %d" % args.update_freq)
|
||||
print("Number of training examples = %d" % dataset_size_train)
|
||||
print("Number of training training per epoch = %d" % num_training_steps_per_epoch)
|
||||
|
||||
# num_layers = model_without_ddp.get_num_layers()
|
||||
num_layers = len(model_without_ddp.blocks)
|
||||
if args.layer_decay < 1.0:
|
||||
assigner = LayerDecayValueAssigner(list(args.layer_decay ** (num_layers + 1 - i) for i in range(num_layers + 2)))
|
||||
else:
|
||||
assigner = None
|
||||
|
||||
if assigner is not None:
|
||||
print("Assigned values = %s" % str(assigner.values))
|
||||
|
||||
skip_weight_decay_list = model.no_weight_decay()
|
||||
if args.disable_weight_decay_on_rel_pos_bias:
|
||||
for i in range(num_layers):
|
||||
skip_weight_decay_list.add("blocks.%d.attn.relative_position_bias_table" % i)
|
||||
|
||||
if args.distributed:
|
||||
torch.distributed.barrier()
|
||||
if args.enable_deepspeed:
|
||||
loss_scaler = None
|
||||
optimizer_params = get_parameter_groups(
|
||||
model, args.weight_decay, skip_weight_decay_list,
|
||||
assigner.get_layer_id if assigner is not None else None,
|
||||
assigner.get_scale if assigner is not None else None)
|
||||
model, optimizer, _, _ = ds_init(
|
||||
args=args, model=model, model_parameters=optimizer_params,
|
||||
dist_init_required=not args.distributed,
|
||||
)
|
||||
|
||||
print("model.gradient_accumulation_steps() = %d" % model.gradient_accumulation_steps())
|
||||
assert model.gradient_accumulation_steps() == args.update_freq
|
||||
else:
|
||||
if args.distributed:
|
||||
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu], find_unused_parameters=True)
|
||||
model_without_ddp = model.module
|
||||
|
||||
optimizer = create_optimizer(
|
||||
args, model_without_ddp, skip_list=skip_weight_decay_list,
|
||||
get_num_layer=assigner.get_layer_id if assigner is not None else None,
|
||||
get_layer_scale=assigner.get_scale if assigner is not None else None)
|
||||
loss_scaler = NativeScaler()
|
||||
|
||||
print("Use step level LR scheduler!")
|
||||
lr_schedule_values = utils.cosine_scheduler(
|
||||
args.lr, args.min_lr, args.epochs, num_training_steps_per_epoch,
|
||||
warmup_epochs=args.warmup_epochs, warmup_steps=args.warmup_steps,
|
||||
)
|
||||
if args.weight_decay_end is None:
|
||||
args.weight_decay_end = args.weight_decay
|
||||
wd_schedule_values = utils.cosine_scheduler(
|
||||
args.weight_decay, args.weight_decay_end, args.epochs, num_training_steps_per_epoch)
|
||||
print("Max WD = %.7f, Min WD = %.7f" % (max(wd_schedule_values), min(wd_schedule_values)))
|
||||
|
||||
if mixup_fn is not None:
|
||||
# smoothing is handled with mixup label transform
|
||||
criterion = SoftTargetCrossEntropy()
|
||||
elif args.smoothing > 0.:
|
||||
criterion = LabelSmoothingCrossEntropy(smoothing=args.smoothing)
|
||||
else:
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
|
||||
print("criterion = %s" % str(criterion))
|
||||
|
||||
utils.auto_load_model(
|
||||
args=args, model=model, model_without_ddp=model_without_ddp,
|
||||
optimizer=optimizer, loss_scaler=loss_scaler, model_ema=model_ema)
|
||||
|
||||
if args.eval:
|
||||
test_stats = evaluate(data_loader_val, model, device)
|
||||
print(f"Accuracy of the network on the {dataset_size_val} test images: {test_stats['acc1']:.1f}%")
|
||||
exit(0)
|
||||
|
||||
print(f"Start training for {args.epochs} epochs")
|
||||
start_time = time.time()
|
||||
max_accuracy = 0.0
|
||||
for epoch in range(args.start_epoch, args.epochs):
|
||||
if args.distributed:
|
||||
sampler = getattr(data_loader_train, "sampler", None)
|
||||
if sampler is not None and hasattr(sampler, "set_epoch"):
|
||||
sampler.set_epoch(epoch)
|
||||
if log_writer is not None:
|
||||
log_writer.set_step(epoch * num_training_steps_per_epoch * args.update_freq)
|
||||
train_stats = train_one_epoch(
|
||||
model, criterion, data_loader_train, optimizer,
|
||||
device, epoch, loss_scaler, args.clip_grad, model_ema, mixup_fn,
|
||||
log_writer=log_writer, start_steps=epoch * num_training_steps_per_epoch,
|
||||
lr_schedule_values=lr_schedule_values, wd_schedule_values=wd_schedule_values,
|
||||
num_training_steps_per_epoch=num_training_steps_per_epoch, update_freq=args.update_freq,
|
||||
)
|
||||
if args.output_dir and args.save_ckpt:
|
||||
if (epoch + 1) % args.save_ckpt_freq == 0 or epoch + 1 == args.epochs:
|
||||
utils.save_model(
|
||||
args=args, model=model, model_without_ddp=model_without_ddp, optimizer=optimizer,
|
||||
loss_scaler=loss_scaler, epoch=epoch, model_ema=model_ema)
|
||||
if data_loader_val is not None and ((epoch + 1) % args.eval_freq == 0 or epoch + 1 == args.epochs):
|
||||
test_stats = evaluate(data_loader_val, model, device)
|
||||
print(f"Accuracy of the network on the {dataset_size_val} test images: {test_stats['acc1']:.1f}%")
|
||||
if max_accuracy < test_stats["acc1"]:
|
||||
max_accuracy = test_stats["acc1"]
|
||||
if args.output_dir and args.save_ckpt:
|
||||
utils.save_model(
|
||||
args=args, model=model, model_without_ddp=model_without_ddp, optimizer=optimizer,
|
||||
loss_scaler=loss_scaler, epoch="best", model_ema=model_ema)
|
||||
|
||||
print(f'Max accuracy: {max_accuracy:.2f}%')
|
||||
if log_writer is not None:
|
||||
log_writer.update(test_acc1=test_stats['acc1'], head="perf", step=epoch)
|
||||
log_writer.update(test_acc5=test_stats['acc5'], head="perf", step=epoch)
|
||||
log_writer.update(test_loss=test_stats['loss'], head="perf", step=epoch)
|
||||
|
||||
log_stats = {**{f'train_{k}': v for k, v in train_stats.items()},
|
||||
**{f'test_{k}': v for k, v in test_stats.items()},
|
||||
'epoch': epoch,
|
||||
'n_parameters': n_parameters}
|
||||
else:
|
||||
log_stats = {**{f'train_{k}': v for k, v in train_stats.items()},
|
||||
# **{f'test_{k}': v for k, v in test_stats.items()},
|
||||
'epoch': epoch,
|
||||
'n_parameters': n_parameters}
|
||||
|
||||
if args.output_dir and utils.is_main_process():
|
||||
if log_writer is not None:
|
||||
log_writer.flush()
|
||||
with open(os.path.join(args.output_dir, "log.txt"), mode="a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(log_stats) + "\n")
|
||||
|
||||
total_time = time.time() - start_time
|
||||
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
||||
print('Training time {}'.format(total_time_str))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
opts, ds_init = get_args()
|
||||
if opts.output_dir:
|
||||
Path(opts.output_dir).mkdir(parents=True, exist_ok=True)
|
||||
main(opts, ds_init)
|
||||
@@ -0,0 +1,132 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# --------------------------------------------------------'
|
||||
import math
|
||||
import random
|
||||
import warnings
|
||||
|
||||
import torchvision.transforms.functional as F
|
||||
from timm.data.transforms import interp_mode_to_str, _RANDOM_INTERPOLATION, str_to_interp_mode
|
||||
|
||||
|
||||
class RandomResizedCropAndInterpolationWithTwoPic:
|
||||
"""Crop the given PIL Image to random size and aspect ratio with random interpolation.
|
||||
|
||||
A crop of random size (default: of 0.08 to 1.0) of the original size and a random
|
||||
aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop
|
||||
is finally resized to given size.
|
||||
This is popularly used to train the Inception networks.
|
||||
|
||||
Args:
|
||||
size: expected output size of each edge
|
||||
scale: range of size of the origin size cropped
|
||||
ratio: range of aspect ratio of the origin aspect ratio cropped
|
||||
interpolation: Default: PIL.Image.BILINEAR
|
||||
"""
|
||||
|
||||
def __init__(self, size, second_size=None, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.),
|
||||
interpolation='bilinear', second_interpolation='lanczos'):
|
||||
if isinstance(size, tuple):
|
||||
self.size = size
|
||||
else:
|
||||
self.size = (size, size)
|
||||
if second_size is not None:
|
||||
if isinstance(second_size, tuple):
|
||||
self.second_size = second_size
|
||||
else:
|
||||
self.second_size = (second_size, second_size)
|
||||
else:
|
||||
self.second_size = None
|
||||
if (scale[0] > scale[1]) or (ratio[0] > ratio[1]):
|
||||
warnings.warn("range should be of kind (min, max)")
|
||||
|
||||
if interpolation == 'random':
|
||||
self.interpolation = _RANDOM_INTERPOLATION
|
||||
else:
|
||||
self.interpolation = str_to_interp_mode(interpolation)
|
||||
self.second_interpolation = str_to_interp_mode(second_interpolation)
|
||||
self.scale = scale
|
||||
self.ratio = ratio
|
||||
|
||||
@staticmethod
|
||||
def get_params(img, scale, ratio):
|
||||
"""Get parameters for ``crop`` for a random sized crop.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be cropped.
|
||||
scale (tuple): range of size of the origin size cropped
|
||||
ratio (tuple): range of aspect ratio of the origin aspect ratio cropped
|
||||
|
||||
Returns:
|
||||
tuple: params (i, j, h, w) to be passed to ``crop`` for a random
|
||||
sized crop.
|
||||
"""
|
||||
area = img.size[0] * img.size[1]
|
||||
|
||||
for attempt in range(10):
|
||||
target_area = random.uniform(*scale) * area
|
||||
log_ratio = (math.log(ratio[0]), math.log(ratio[1]))
|
||||
aspect_ratio = math.exp(random.uniform(*log_ratio))
|
||||
|
||||
w = int(round(math.sqrt(target_area * aspect_ratio)))
|
||||
h = int(round(math.sqrt(target_area / aspect_ratio)))
|
||||
|
||||
if w <= img.size[0] and h <= img.size[1]:
|
||||
i = random.randint(0, img.size[1] - h)
|
||||
j = random.randint(0, img.size[0] - w)
|
||||
return i, j, h, w
|
||||
|
||||
# Fallback to central crop
|
||||
in_ratio = img.size[0] / img.size[1]
|
||||
if in_ratio < min(ratio):
|
||||
w = img.size[0]
|
||||
h = int(round(w / min(ratio)))
|
||||
elif in_ratio > max(ratio):
|
||||
h = img.size[1]
|
||||
w = int(round(h * max(ratio)))
|
||||
else: # whole image
|
||||
w = img.size[0]
|
||||
h = img.size[1]
|
||||
i = (img.size[1] - h) // 2
|
||||
j = (img.size[0] - w) // 2
|
||||
return i, j, h, w
|
||||
|
||||
def __call__(self, img):
|
||||
"""
|
||||
Args:
|
||||
img (PIL Image): Image to be cropped and resized.
|
||||
|
||||
Returns:
|
||||
PIL Image: Randomly cropped and resized image.
|
||||
"""
|
||||
i, j, h, w = self.get_params(img, self.scale, self.ratio)
|
||||
if isinstance(self.interpolation, (tuple, list)):
|
||||
interpolation = random.choice(self.interpolation)
|
||||
else:
|
||||
interpolation = self.interpolation
|
||||
if self.second_size is None:
|
||||
return F.resized_crop(img, i, j, h, w, self.size, interpolation)
|
||||
else:
|
||||
return F.resized_crop(img, i, j, h, w, self.size, interpolation), \
|
||||
F.resized_crop(img, i, j, h, w, self.second_size, self.second_interpolation)
|
||||
|
||||
def __repr__(self):
|
||||
if isinstance(self.interpolation, (tuple, list)):
|
||||
interpolate_str = ' '.join([interp_mode_to_str(x) for x in self.interpolation])
|
||||
else:
|
||||
interpolate_str = interp_mode_to_str(self.interpolation)
|
||||
format_string = self.__class__.__name__ + '(size={0}'.format(self.size)
|
||||
format_string += ', scale={0}'.format(tuple(round(s, 4) for s in self.scale))
|
||||
format_string += ', ratio={0}'.format(tuple(round(r, 4) for r in self.ratio))
|
||||
format_string += ', interpolation={0}'.format(interpolate_str)
|
||||
if self.second_size is not None:
|
||||
format_string += ', second_size={0}'.format(self.second_size)
|
||||
format_string += ', second_interpolation={0}'.format(interp_mode_to_str(self.second_interpolation))
|
||||
format_string += ')'
|
||||
return format_string
|
||||
@@ -0,0 +1,523 @@
|
||||
# --------------------------------------------------------
|
||||
# BEIT: BERT Pre-Training of Image Transformers (https://arxiv.org/abs/2106.08254)
|
||||
# Github source: https://github.com/microsoft/unilm/tree/master/beit
|
||||
# Copyright (c) 2021 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# By Hangbo Bao
|
||||
# Based on timm, DINO and DeiT code bases
|
||||
# https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
# https://github.com/facebookresearch/deit
|
||||
# https://github.com/facebookresearch/dino
|
||||
# --------------------------------------------------------'
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
import json
|
||||
from collections import defaultdict, deque
|
||||
import datetime
|
||||
import numpy as np
|
||||
from timm.utils import get_state_dict
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from torch._six import inf
|
||||
# from modeling_discrete_vae import Dalle_VAE, DiscreteVAE, DiscreteVAE2, VQGanVAE, DiscreteVAEforBEiT
|
||||
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
class SmoothedValue(object):
|
||||
"""Track a series of values and provide access to smoothed values over a
|
||||
window or the global series average.
|
||||
"""
|
||||
|
||||
def __init__(self, window_size=20, fmt=None):
|
||||
if fmt is None:
|
||||
fmt = "{median:.4f} ({global_avg:.4f})"
|
||||
self.deque = deque(maxlen=window_size)
|
||||
self.total = 0.0
|
||||
self.count = 0
|
||||
self.fmt = fmt
|
||||
|
||||
def update(self, value, n=1):
|
||||
self.deque.append(value)
|
||||
self.count += n
|
||||
self.total += value * n
|
||||
|
||||
def synchronize_between_processes(self):
|
||||
"""
|
||||
Warning: does not synchronize the deque!
|
||||
"""
|
||||
if not is_dist_avail_and_initialized():
|
||||
return
|
||||
t = torch.tensor([self.count, self.total], dtype=torch.float64, device='cuda')
|
||||
dist.barrier()
|
||||
dist.all_reduce(t)
|
||||
t = t.tolist()
|
||||
self.count = int(t[0])
|
||||
self.total = t[1]
|
||||
|
||||
@property
|
||||
def median(self):
|
||||
d = torch.tensor(list(self.deque))
|
||||
return d.median().item()
|
||||
|
||||
@property
|
||||
def avg(self):
|
||||
d = torch.tensor(list(self.deque), dtype=torch.float32)
|
||||
return d.mean().item()
|
||||
|
||||
@property
|
||||
def global_avg(self):
|
||||
return self.total / self.count
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
return max(self.deque)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.deque[-1]
|
||||
|
||||
def __str__(self):
|
||||
return self.fmt.format(
|
||||
median=self.median,
|
||||
avg=self.avg,
|
||||
global_avg=self.global_avg,
|
||||
max=self.max,
|
||||
value=self.value)
|
||||
|
||||
|
||||
class MetricLogger(object):
|
||||
def __init__(self, delimiter="\t"):
|
||||
self.meters = defaultdict(SmoothedValue)
|
||||
self.delimiter = delimiter
|
||||
|
||||
def update(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, torch.Tensor):
|
||||
v = v.item()
|
||||
assert isinstance(v, (float, int))
|
||||
self.meters[k].update(v)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.meters:
|
||||
return self.meters[attr]
|
||||
if attr in self.__dict__:
|
||||
return self.__dict__[attr]
|
||||
raise AttributeError("'{}' object has no attribute '{}'".format(
|
||||
type(self).__name__, attr))
|
||||
|
||||
def __str__(self):
|
||||
loss_str = []
|
||||
for name, meter in self.meters.items():
|
||||
loss_str.append(
|
||||
"{}: {}".format(name, str(meter))
|
||||
)
|
||||
return self.delimiter.join(loss_str)
|
||||
|
||||
def synchronize_between_processes(self):
|
||||
for meter in self.meters.values():
|
||||
meter.synchronize_between_processes()
|
||||
|
||||
def add_meter(self, name, meter):
|
||||
self.meters[name] = meter
|
||||
|
||||
def log_every(self, iterable, print_freq, header=None):
|
||||
i = 0
|
||||
if not header:
|
||||
header = ''
|
||||
start_time = time.time()
|
||||
end = time.time()
|
||||
iter_time = SmoothedValue(fmt='{avg:.4f}')
|
||||
data_time = SmoothedValue(fmt='{avg:.4f}')
|
||||
space_fmt = ':' + str(len(str(len(iterable)))) + 'd'
|
||||
log_msg = [
|
||||
header,
|
||||
'[{0' + space_fmt + '}/{1}]',
|
||||
'eta: {eta}',
|
||||
'{meters}',
|
||||
'time: {time}',
|
||||
'data: {data}'
|
||||
]
|
||||
if torch.cuda.is_available():
|
||||
log_msg.append('max mem: {memory:.0f}')
|
||||
log_msg = self.delimiter.join(log_msg)
|
||||
MB = 1024.0 * 1024.0
|
||||
for obj in iterable:
|
||||
data_time.update(time.time() - end)
|
||||
yield obj
|
||||
iter_time.update(time.time() - end)
|
||||
if i % print_freq == 0 or i == len(iterable) - 1:
|
||||
eta_seconds = iter_time.global_avg * (len(iterable) - i)
|
||||
eta_string = str(datetime.timedelta(seconds=int(eta_seconds)))
|
||||
if torch.cuda.is_available():
|
||||
print(log_msg.format(
|
||||
i, len(iterable), eta=eta_string,
|
||||
meters=str(self),
|
||||
time=str(iter_time), data=str(data_time),
|
||||
memory=torch.cuda.max_memory_allocated() / MB))
|
||||
else:
|
||||
print(log_msg.format(
|
||||
i, len(iterable), eta=eta_string,
|
||||
meters=str(self),
|
||||
time=str(iter_time), data=str(data_time)))
|
||||
i += 1
|
||||
end = time.time()
|
||||
total_time = time.time() - start_time
|
||||
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
||||
print('{} Total time: {} ({:.4f} s / it)'.format(
|
||||
header, total_time_str, total_time / len(iterable)))
|
||||
|
||||
|
||||
class TensorboardLogger(object):
|
||||
def __init__(self, log_dir):
|
||||
self.writer = SummaryWriter(log_dir=log_dir)
|
||||
self.step = 0
|
||||
|
||||
def set_step(self, step=None):
|
||||
if step is not None:
|
||||
self.step = step
|
||||
else:
|
||||
self.step += 1
|
||||
|
||||
def update(self, head='scalar', step=None, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, torch.Tensor):
|
||||
v = v.item()
|
||||
assert isinstance(v, (float, int))
|
||||
self.writer.add_scalar(head + "/" + k, v, self.step if step is None else step)
|
||||
|
||||
def flush(self):
|
||||
self.writer.flush()
|
||||
|
||||
|
||||
def _load_checkpoint_for_ema(model_ema, checkpoint):
|
||||
"""
|
||||
Workaround for ModelEma._load_checkpoint to accept an already-loaded object
|
||||
"""
|
||||
mem_file = io.BytesIO()
|
||||
torch.save(checkpoint, mem_file)
|
||||
mem_file.seek(0)
|
||||
model_ema._load_checkpoint(mem_file)
|
||||
|
||||
|
||||
def setup_for_distributed(is_master):
|
||||
"""
|
||||
This function disables printing when not in master process
|
||||
"""
|
||||
import builtins as __builtin__
|
||||
builtin_print = __builtin__.print
|
||||
|
||||
def print(*args, **kwargs):
|
||||
force = kwargs.pop('force', False)
|
||||
if is_master or force:
|
||||
builtin_print(*args, **kwargs)
|
||||
|
||||
__builtin__.print = print
|
||||
|
||||
|
||||
def is_dist_avail_and_initialized():
|
||||
if not dist.is_available():
|
||||
return False
|
||||
if not dist.is_initialized():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_world_size():
|
||||
if not is_dist_avail_and_initialized():
|
||||
return 1
|
||||
return dist.get_world_size()
|
||||
|
||||
|
||||
def get_rank():
|
||||
if not is_dist_avail_and_initialized():
|
||||
return 0
|
||||
return dist.get_rank()
|
||||
|
||||
|
||||
def is_main_process():
|
||||
return get_rank() == 0
|
||||
|
||||
|
||||
def save_on_master(*args, **kwargs):
|
||||
if is_main_process():
|
||||
torch.save(*args, **kwargs)
|
||||
|
||||
|
||||
def init_distributed_mode(args):
|
||||
if args.dist_on_itp:
|
||||
args.rank = int(os.environ['OMPI_COMM_WORLD_RANK'])
|
||||
args.world_size = int(os.environ['OMPI_COMM_WORLD_SIZE'])
|
||||
args.gpu = int(os.environ['OMPI_COMM_WORLD_LOCAL_RANK'])
|
||||
args.dist_url = "tcp://%s:%s" % (os.environ['MASTER_ADDR'], os.environ['MASTER_PORT'])
|
||||
os.environ['LOCAL_RANK'] = str(args.gpu)
|
||||
os.environ['RANK'] = str(args.rank)
|
||||
os.environ['WORLD_SIZE'] = str(args.world_size)
|
||||
# ["RANK", "WORLD_SIZE", "MASTER_ADDR", "MASTER_PORT", "LOCAL_RANK"]
|
||||
elif 'RANK' in os.environ and 'WORLD_SIZE' in os.environ:
|
||||
args.rank = int(os.environ["RANK"])
|
||||
args.world_size = int(os.environ['WORLD_SIZE'])
|
||||
args.gpu = int(os.environ['LOCAL_RANK'])
|
||||
elif 'SLURM_PROCID' in os.environ:
|
||||
args.rank = int(os.environ['SLURM_PROCID'])
|
||||
args.gpu = args.rank % torch.cuda.device_count()
|
||||
else:
|
||||
print('Not using distributed mode')
|
||||
args.distributed = False
|
||||
return
|
||||
|
||||
args.distributed = True
|
||||
|
||||
torch.cuda.set_device(args.gpu)
|
||||
args.dist_backend = 'nccl'
|
||||
print('| distributed init (rank {}): {}, gpu {}'.format(
|
||||
args.rank, args.dist_url, args.gpu), flush=True)
|
||||
torch.distributed.init_process_group(
|
||||
backend=args.dist_backend, init_method=args.dist_url,
|
||||
world_size=args.world_size, rank=args.rank,
|
||||
timeout=datetime.timedelta(0, 7200)
|
||||
)
|
||||
torch.distributed.barrier()
|
||||
setup_for_distributed(args.rank == 0)
|
||||
|
||||
|
||||
def load_state_dict(model, state_dict, prefix='', ignore_missing="relative_position_index"):
|
||||
missing_keys = []
|
||||
unexpected_keys = []
|
||||
error_msgs = []
|
||||
# copy state_dict so _load_from_state_dict can modify it
|
||||
metadata = getattr(state_dict, '_metadata', None)
|
||||
state_dict = state_dict.copy()
|
||||
if metadata is not None:
|
||||
state_dict._metadata = metadata
|
||||
|
||||
def load(module, prefix=''):
|
||||
local_metadata = {} if metadata is None else metadata.get(
|
||||
prefix[:-1], {})
|
||||
module._load_from_state_dict(
|
||||
state_dict, prefix, local_metadata, True, missing_keys, unexpected_keys, error_msgs)
|
||||
for name, child in module._modules.items():
|
||||
if child is not None:
|
||||
load(child, prefix + name + '.')
|
||||
|
||||
load(model, prefix=prefix)
|
||||
|
||||
warn_missing_keys = []
|
||||
ignore_missing_keys = []
|
||||
for key in missing_keys:
|
||||
keep_flag = True
|
||||
for ignore_key in ignore_missing.split('|'):
|
||||
if ignore_key in key:
|
||||
keep_flag = False
|
||||
break
|
||||
if keep_flag:
|
||||
warn_missing_keys.append(key)
|
||||
else:
|
||||
ignore_missing_keys.append(key)
|
||||
|
||||
missing_keys = warn_missing_keys
|
||||
|
||||
if len(missing_keys) > 0:
|
||||
print("Weights of {} not initialized from pretrained model: {}".format(
|
||||
model.__class__.__name__, missing_keys))
|
||||
if len(unexpected_keys) > 0:
|
||||
print("Weights from pretrained model not used in {}: {}".format(
|
||||
model.__class__.__name__, unexpected_keys))
|
||||
if len(ignore_missing_keys) > 0:
|
||||
print("Ignored weights of {} not initialized from pretrained model: {}".format(
|
||||
model.__class__.__name__, ignore_missing_keys))
|
||||
if len(error_msgs) > 0:
|
||||
print('\n'.join(error_msgs))
|
||||
|
||||
|
||||
class NativeScalerWithGradNormCount:
|
||||
state_dict_key = "amp_scaler"
|
||||
|
||||
def __init__(self):
|
||||
self._scaler = torch.cuda.amp.GradScaler()
|
||||
|
||||
def __call__(self, loss, optimizer, clip_grad=None, parameters=None, create_graph=False, update_grad=True):
|
||||
self._scaler.scale(loss).backward(create_graph=create_graph)
|
||||
if update_grad:
|
||||
if clip_grad is not None:
|
||||
assert parameters is not None
|
||||
self._scaler.unscale_(optimizer) # unscale the gradients of optimizer's assigned params in-place
|
||||
norm = torch.nn.utils.clip_grad_norm_(parameters, clip_grad)
|
||||
else:
|
||||
self._scaler.unscale_(optimizer)
|
||||
norm = get_grad_norm_(parameters)
|
||||
self._scaler.step(optimizer)
|
||||
self._scaler.update()
|
||||
else:
|
||||
norm = None
|
||||
return norm
|
||||
|
||||
def state_dict(self):
|
||||
return self._scaler.state_dict()
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self._scaler.load_state_dict(state_dict)
|
||||
|
||||
|
||||
def get_grad_norm_(parameters, norm_type: float = 2.0) -> torch.Tensor:
|
||||
if isinstance(parameters, torch.Tensor):
|
||||
parameters = [parameters]
|
||||
parameters = [p for p in parameters if p.grad is not None]
|
||||
norm_type = float(norm_type)
|
||||
if len(parameters) == 0:
|
||||
return torch.tensor(0.)
|
||||
device = parameters[0].grad.device
|
||||
if norm_type == inf:
|
||||
total_norm = max(p.grad.detach().abs().max().to(device) for p in parameters)
|
||||
else:
|
||||
total_norm = torch.norm(torch.stack([torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]), norm_type)
|
||||
return total_norm
|
||||
|
||||
|
||||
def cosine_scheduler(base_value, final_value, epochs, niter_per_ep, warmup_epochs=0,
|
||||
start_warmup_value=0, warmup_steps=-1):
|
||||
warmup_schedule = np.array([])
|
||||
warmup_iters = warmup_epochs * niter_per_ep
|
||||
if warmup_steps > 0:
|
||||
warmup_iters = warmup_steps
|
||||
print("Set warmup steps = %d" % warmup_iters)
|
||||
if warmup_epochs > 0:
|
||||
warmup_schedule = np.linspace(start_warmup_value, base_value, warmup_iters)
|
||||
|
||||
iters = np.arange(epochs * niter_per_ep - warmup_iters)
|
||||
schedule = np.array(
|
||||
[final_value + 0.5 * (base_value - final_value) * (1 + math.cos(math.pi * i / (len(iters)))) for i in iters])
|
||||
|
||||
schedule = np.concatenate((warmup_schedule, schedule))
|
||||
|
||||
# assert len(schedule) == epochs * niter_per_ep
|
||||
return schedule
|
||||
|
||||
|
||||
def save_model(args, epoch, model, model_without_ddp, optimizer, loss_scaler, model_ema=None):
|
||||
output_dir = Path(args.output_dir)
|
||||
epoch_name = str(epoch)
|
||||
if loss_scaler is not None:
|
||||
checkpoint_paths = [output_dir / ('checkpoint-%s.pth' % epoch_name)]
|
||||
for checkpoint_path in checkpoint_paths:
|
||||
to_save = {
|
||||
'model': model_without_ddp.state_dict(),
|
||||
'optimizer': optimizer.state_dict(),
|
||||
'epoch': epoch,
|
||||
'scaler': loss_scaler.state_dict(),
|
||||
'args': args,
|
||||
}
|
||||
|
||||
if model_ema is not None:
|
||||
to_save['model_ema'] = get_state_dict(model_ema)
|
||||
|
||||
save_on_master(to_save, checkpoint_path)
|
||||
else:
|
||||
client_state = {'epoch': epoch}
|
||||
if model_ema is not None:
|
||||
client_state['model_ema'] = get_state_dict(model_ema)
|
||||
model.save_checkpoint(save_dir=args.output_dir, tag="checkpoint-%s" % epoch_name, client_state=client_state)
|
||||
|
||||
|
||||
def auto_load_model(args, model, model_without_ddp, optimizer, loss_scaler, model_ema=None):
|
||||
output_dir = Path(args.output_dir)
|
||||
if loss_scaler is not None:
|
||||
# torch.amp
|
||||
if args.auto_resume and len(args.resume) == 0:
|
||||
import glob
|
||||
all_checkpoints = glob.glob(os.path.join(output_dir, 'checkpoint-*.pth'))
|
||||
latest_ckpt = -1
|
||||
for ckpt in all_checkpoints:
|
||||
t = ckpt.split('-')[-1].split('.')[0]
|
||||
if t.isdigit():
|
||||
latest_ckpt = max(int(t), latest_ckpt)
|
||||
if latest_ckpt >= 0:
|
||||
args.resume = os.path.join(output_dir, 'checkpoint-%d.pth' % latest_ckpt)
|
||||
print("Auto resume checkpoint: %s" % args.resume)
|
||||
|
||||
if args.resume:
|
||||
if args.resume.startswith('https'):
|
||||
checkpoint = torch.hub.load_state_dict_from_url(
|
||||
args.resume, map_location='cpu', check_hash=True)
|
||||
else:
|
||||
checkpoint = torch.load(args.resume, map_location='cpu')
|
||||
model_without_ddp.load_state_dict(checkpoint['model'])
|
||||
print("Resume checkpoint %s" % args.resume)
|
||||
if 'optimizer' in checkpoint and 'epoch' in checkpoint:
|
||||
optimizer.load_state_dict(checkpoint['optimizer'])
|
||||
args.start_epoch = checkpoint['epoch'] + 1
|
||||
if hasattr(args, 'model_ema') and args.model_ema:
|
||||
_load_checkpoint_for_ema(model_ema, checkpoint['model_ema'])
|
||||
if 'scaler' in checkpoint:
|
||||
loss_scaler.load_state_dict(checkpoint['scaler'])
|
||||
print("With optim & sched!")
|
||||
else:
|
||||
# deepspeed, only support '--auto_resume'.
|
||||
if args.auto_resume:
|
||||
import glob
|
||||
all_checkpoints = glob.glob(os.path.join(output_dir, 'checkpoint-*'))
|
||||
latest_ckpt = -1
|
||||
for ckpt in all_checkpoints:
|
||||
t = ckpt.split('-')[-1].split('.')[0]
|
||||
if t.isdigit():
|
||||
latest_ckpt = max(int(t), latest_ckpt)
|
||||
if latest_ckpt >= 0:
|
||||
args.resume = os.path.join(output_dir, 'checkpoint-%d' % latest_ckpt)
|
||||
print("Auto resume checkpoint: %d" % latest_ckpt)
|
||||
_, client_states = model.load_checkpoint(args.output_dir, tag='checkpoint-%d' % latest_ckpt)
|
||||
args.start_epoch = client_states['epoch'] + 1
|
||||
if model_ema is not None:
|
||||
if args.model_ema:
|
||||
_load_checkpoint_for_ema(model_ema, client_states['model_ema'])
|
||||
|
||||
def create_ds_config(args):
|
||||
args.deepspeed_config = os.path.join(args.output_dir, "deepspeed_config.json")
|
||||
with open(args.deepspeed_config, mode="w") as writer:
|
||||
ds_config = {
|
||||
"train_batch_size": args.batch_size * args.update_freq * get_world_size(),
|
||||
"train_micro_batch_size_per_gpu": args.batch_size,
|
||||
"steps_per_print": 1000,
|
||||
"optimizer": {
|
||||
"type": "Adam",
|
||||
"adam_w_mode": True,
|
||||
"params": {
|
||||
"lr": args.lr,
|
||||
"weight_decay": args.weight_decay,
|
||||
"bias_correction": True,
|
||||
"betas": [
|
||||
0.9,
|
||||
0.999
|
||||
],
|
||||
"eps": 1e-8
|
||||
}
|
||||
},
|
||||
"fp16": {
|
||||
"enabled": True,
|
||||
"loss_scale": 0,
|
||||
"initial_scale_power": 16,
|
||||
"loss_scale_window": 1000,
|
||||
"hysteresis": 2,
|
||||
"min_loss_scale": 1
|
||||
},
|
||||
"zero_optimization": {
|
||||
"stage": args.zero_stage
|
||||
},
|
||||
"amp": {
|
||||
"enabled": False,
|
||||
"opt_level": "O2"
|
||||
}
|
||||
}
|
||||
|
||||
if args.clip_grad is not None:
|
||||
ds_config.update({'gradient_clipping': args.clip_grad})
|
||||
|
||||
writer.write(json.dumps(ds_config, indent=2))
|
||||
Reference in New Issue
Block a user