Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions competition/City_CycleGAN/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CycleGAN TinyMS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @bravotty It is better to add supported hardware platforms to this file

> Improved CycleGAN implementation in TinyMS.

## TODOs
+ [x] Refactor the GAN report part.
+ [x] Refactor the data load part.
+ [x] Refactor the model training part.
+ [x] Use the semantic masks to guide the image translation.
+ [ ] EMA version generator.


## Others
1. https://tinyms.readthedocs.io/zh_CN/latest/design/concepts.html
2. https://www.mindspore.cn/tutorials/zh-CN/master/intermediate/image_and_video/dcgan.html
2 changes: 2 additions & 0 deletions competition/City_CycleGAN/archive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Archive
> This folder is used to save archived files, such as datasets and saved models.
9 changes: 9 additions & 0 deletions competition/City_CycleGAN/bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Bin Sub-folder
> This folder is used to save executable utils.

## template.py
This is a template script.

Run: `template.py --parameter template`

Commit: `4e45c9`
11 changes: 11 additions & 0 deletions competition/City_CycleGAN/bin/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import argparse


def main(args):
pass


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--parameter', type=str, required=True)
main(parser.parse_args())
136 changes: 136 additions & 0 deletions competition/City_CycleGAN/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import argparse
import json
import os
import random
import sys

import numpy as np
from munch import Munch
from tinyms import context

from utils.file import save_json, prepare_dirs
from utils.misc import get_datetime, str2bool, get_commit_hash


def setup_cfg(args):
np.random.seed(args.seed)
random.seed(args.seed)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

if args.graph_mode == "static":
graph_mode = context.PYNATIVE_MODE
else:
graph_mode = context.GRAPH_MODE

context.set_context(mode=graph_mode, device_target=args.device)

args.log_dir = os.path.join(args.exp_dir, args.exp_id, "logs")
args.sample_dir = os.path.join(args.exp_dir, args.exp_id, "samples")
args.model_dir = os.path.join(args.exp_dir, args.exp_id, "models")
args.eval_dir = os.path.join(args.exp_dir, args.exp_id, "eval")
prepare_dirs([args.log_dir, args.sample_dir, args.model_dir, args.eval_dir])
args.record_file = os.path.join(args.exp_dir, args.exp_id, "records.txt")
args.loss_file = os.path.join(args.exp_dir, args.exp_id, "losses.csv")


def validate_cfg(args):
pass


def load_cfg():
# There are two ways to load config, use a json file or command line arguments.
if len(sys.argv) >= 2 and sys.argv[1].endswith('.json'):
with open(sys.argv[1], 'r') as f:
cfg = json.load(f)
cfg = Munch(cfg)
if len(sys.argv) >= 3:
cfg.exp_id = sys.argv[2]
else:
print("Warning: using existing experiment dir.")
if not cfg.about:
cfg.about = f"Copied from: {sys.argv[1]}"
else:
cfg = parse_args()
cfg = Munch(cfg.__dict__)
if not cfg.hash:
cfg.hash = get_commit_hash()
current_hash = get_commit_hash()
if current_hash != cfg.hash:
print(f"Warning: unmatched git commit hash: `{current_hash}` & `{cfg.hash}`.")
return cfg


def save_cfg(cfg):
exp_path = os.path.join(cfg.exp_dir, cfg.exp_id)
os.makedirs(exp_path, exist_ok=True)
filename = cfg.mode
if cfg.mode == 'train' and cfg.start_epoch != 0:
filename = f"resume_{cfg.start_epoch}"
save_json(exp_path, cfg, filename)


def print_cfg(cfg):
print(json.dumps(cfg, indent=4))


def parse_args():
parser = argparse.ArgumentParser()

# About this experiment.
parser.add_argument('--about', type=str, default="")
parser.add_argument('--hash', type=str, required=False, help="Git commit hash for this experiment.")
parser.add_argument('--exp_id', type=str, default=get_datetime(), help='Folder name and id for this experiment.')
parser.add_argument('--exp_dir', type=str, default='expr')

# Meta arguments.
parser.add_argument('--mode', type=str, default='train', choices=['train', 'eval', 'sample'])
parser.add_argument('--device', type=str, default='GPU', choices=['GPU', 'CPU', 'Ascend'])
parser.add_argument('--graph_mode', type=str, default='dynamic', choices=['dynamic', 'static'])

# Model related arguments.
parser.add_argument('--img_size', type=int, default=128)
parser.add_argument('--g_arch', type=str, default='resnet', choices=["resnet", "unet"])

# Dataset related arguments.
parser.add_argument('--dataset', type=str, required=False)
parser.add_argument('--dataset_path', type=str, required=True)

# Training related arguments
parser.add_argument('--start_epoch', type=int, default=0)
parser.add_argument('--end_epoch', type=int, default=200, help='epoch size for training, default is 200.')
parser.add_argument('--initial_epoch', type=int, default=100,
help='number of epochs with the initial learning rate, default is 100')
parser.add_argument('--parameter_init', type=str, default='default', choices=['he', 'default'])

# Optimizing related arguments.
parser.add_argument('--lr', type=float, default=1e-4, help="Learning rate for generator.")
parser.add_argument('--d_lr', type=float, default=1e-4, help="Learning rate for discriminator.")
parser.add_argument('--beta1', type=float, default=0.5)
parser.add_argument('--beta2', type=float, default=0.999)
parser.add_argument('--weight_decay', type=float, default=1e-4)
parser.add_argument('--batch_size', type=int, default=8)
parser.add_argument('--ema_beta', type=float, default=0.999)

# Semantic loss related arguments.
parser.add_argument('--lambda_sem', type=float, default=0)
parser.add_argument('--sem_g_arch', type=str, default='resnet', choices=['resnet', 'unet', 'fpn'])

# Loss hyper arguments.
parser.add_argument('--lambda_adv', type=float, default=1)

# Step related arguments.
parser.add_argument('--log_every', type=int, default=10)
parser.add_argument('--sample_every', type=int, default=1000)
parser.add_argument('--save_every', type=int, default=5000)
parser.add_argument('--eval_every', type=int, default=5000)

# Log related arguments.
parser.add_argument('--use_tensorboard', type=str2bool, default=False)
parser.add_argument('--save_loss', type=str2bool, default=True)

# Others
parser.add_argument('--seed', type=int, default=0, help='Seed for random number generator.')
parser.add_argument('--keep_all_models', type=str2bool, default=True)

return parser.parse_args()
2 changes: 2 additions & 0 deletions competition/City_CycleGAN/data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Data
> This folder is used to save data process related scripts.
99 changes: 99 additions & 0 deletions competition/City_CycleGAN/data/dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import os
import random

import numpy as np
from tinyms.data import GeneratorDataset, GanImageFolderDataset, DistributedSampler, generate_image_list, load_img
from data.transform import cyclegan_transform


def create_dataset(dataset_path, batch_size=1, repeat_size=1, max_dataset_size=None,
shuffle=True, num_parallel_workers=1, phase='train', data_dir='testA', use_S=False):
""" create Mnist dataset for train or eval.
dataset_path: Data path
batch_size: The number of data records in each group
repeat_size: The number of replicated data records
num_parallel_workers: The number of parallel workers
"""
# define dataset and apply the transform func
if phase == 'train':
ds = UnalignedDataset(dataset_path, phase, max_dataset_size=max_dataset_size, shuffle=True, use_S=use_S)
column_names = ["image_A", "image_B"]
if use_S:
column_names.append('image_S')
device_num = 1
distributed_sampler = DistributedSampler(len(ds), num_replicas=device_num, rank=0, shuffle=shuffle)
gan_generator_ds = GeneratorDataset(ds, column_names=column_names, sampler=distributed_sampler,
num_parallel_workers=num_parallel_workers)
else:
data_dir = os.path.join(dataset_path, data_dir)
ds = GanImageFolderDataset(data_dir, max_dataset_size=max_dataset_size)
gan_generator_ds = GeneratorDataset(ds, column_names=["image", "image_name"],
num_parallel_workers=num_parallel_workers)

gan_generator_ds = cyclegan_transform.apply_ds(gan_generator_ds,
repeat_size=repeat_size,
batch_size=batch_size,
num_parallel_workers=num_parallel_workers,
shuffle=shuffle,
phase=phase,
use_S=use_S)
dataset_size = len(ds)
return gan_generator_ds, dataset_size


class UnalignedDataset:
"""
This dataset class can load unaligned/unpaired datasets.

Args:
dataset_path (str): The path of images (should have subfolders trainA, trainB, testA, testB, etc).
phase (str): Train or test. It requires two directories in dataset_path, like trainA and trainB to.
host training images from domain A '{dataset_path}/trainA' and from domain B '{dataset_path}/trainB'
respectively.
max_dataset_size (int): Maximum number of return image paths.

Returns:
Two domain image path list.
"""

def __init__(self, dataset_path, phase, max_dataset_size=float("inf"), shuffle=True, use_S=False):
self.use_S = use_S
self.dir_A = os.path.join(dataset_path, phase + 'A')
self.dir_B = os.path.join(dataset_path, phase + 'B')

self.A_paths = sorted(generate_image_list(self.dir_A,
max_dataset_size)) # load images from '/path/to/data/trainA'
self.B_paths = sorted(generate_image_list(self.dir_B,
max_dataset_size)) # load images from '/path/to/data/trainB'

self.A_size = len(self.A_paths) # get the size of dataset A
self.B_size = len(self.B_paths) # get the size of dataset B

if use_S:
self.dir_S = os.path.join(dataset_path, phase + 'S')
self.S_paths = sorted(generate_image_list(self.dir_S,
max_dataset_size)) # load images from '/path/to/data/trainS'
self.S_size = len(self.S_paths) # get the size of dataset S

self.shuffle = shuffle

def __getitem__(self, index):
index_B = index % self.B_size
if index % max(self.A_size, self.B_size) == 0 and self.shuffle:
random.shuffle(self.A_paths)
index_B = random.randint(0, self.B_size - 1)
A_path = self.A_paths[index % self.A_size]
B_path = self.B_paths[index_B]
A_img = np.array(load_img(A_path))
B_img = np.array(load_img(B_path))
if self.use_S:
S_path = self.S_paths[index_B]
S_img = np.array(load_img(S_path))
return A_img, B_img, S_img

return A_img, B_img

def __len__(self):
return max(self.A_size, self.B_size)


97 changes: 97 additions & 0 deletions competition/City_CycleGAN/data/transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import numpy as np
from PIL import Image
from tinyms.data import GeneratorDataset
from tinyms.vision._transform_ops import *


class CycleGanDatasetTransform:
r'''
CycleGan dataset transform class.

Inputs:
img (Union[numpy.ndarray, PIL.Image]): Image to be transformed in city_scape.

Outputs:
numpy.ndarray, transformed image.

Examples:
>>> from PIL import Image
>>> from tinyms.vision import CycleGanDatasetTransform
>>>
>>> cyclegan_transform = CycleGanDatasetTransform()
>>> img = Image.open('example.jpg')
>>> img = cyclegan_transform(img)
'''

def __init__(self):
self.random_resized_crop = RandomResizedCrop(256, scale=(0.5, 1.0), ratio=(0.75, 1.333))
self.random_horizontal_flip = RandomHorizontalFlip(prob=0.5)
self.resize = Resize((256, 256))
self.normalize = Normalize(mean=[0.5 * 255] * 3, std=[0.5 * 255] * 3)

def __call__(self, img):
if not isinstance(img, (np.ndarray, Image.Image)):
raise TypeError("Input type should be numpy.ndarray or PIL.Image, got {}.".format(type(img)))
img = self.resize(img)
img = self.normalize(img)
img = hwc2chw(img)

return img

def apply_ds(self, gan_generator_ds, repeat_size=1, batch_size=1,
num_parallel_workers=1, shuffle=True, phase='train',use_S=False):
r'''
Apply preprocess operation on GeneratorDataset instance.

Args:
gan_generator_ds (data.GeneratorDataset): GeneratorDataset instance.
repeat_size (int): The repeat size of dataset. Default: 1.
batch_size (int): Batch size. Default: 32.
num_parallel_workers (int): The number of concurrent workers. Default: 1.
shuffle (bool): Specifies if applying shuffle operation. Default: True.
phase (str): Specifies the current phase. Default: train.

Returns:
data.GeneratorDataset, the preprocessed GeneratorDataset instance.

Examples:
>>> from tinyms.vision import CycleGanDatasetTransform
>>>
>>> cyclegan_transform = CycleGanDatasetTransform()
>>> gan_generator_ds = cyclegan_transform.apply_ds(gan_generator_ds)

Raises:
TypeError: If `gan_generator_ds` is not instance of GeneratorDataset.
'''
if not isinstance(gan_generator_ds, GeneratorDataset):
raise TypeError("Input type should be GeneratorDataset, got {}.".format(type(gan_generator_ds)))

trans_func = []
if phase == 'train':
if shuffle:
trans_func += [self.random_resized_crop, self.random_horizontal_flip, self.normalize, hwc2chw]
else:
trans_func += [self.resize, self.normalize, hwc2chw]

# apply transform functions on gan_generator_ds dataset
gan_generator_ds = gan_generator_ds.map(operations=trans_func,
input_columns=["image_A"],
num_parallel_workers=num_parallel_workers)
gan_generator_ds = gan_generator_ds.map(operations=trans_func,
input_columns=["image_B"],
num_parallel_workers=num_parallel_workers)
if use_S:
gan_generator_ds = gan_generator_ds.map(operations=trans_func,
input_columns=["image_S"],
num_parallel_workers=num_parallel_workers)
else:
trans_func += [self.resize, self.normalize, hwc2chw]
gan_generator_ds = gan_generator_ds.map(operations=trans_func,
input_columns=["image"],
num_parallel_workers=num_parallel_workers)
gan_generator_ds = gan_generator_ds.batch(batch_size, drop_remainder=True)
gan_generator_ds = gan_generator_ds.repeat(repeat_size)
return gan_generator_ds


cyclegan_transform = CycleGanDatasetTransform()
Loading