├── README.md ├── make_submission.py ├── make_folds.py ├── models.py ├── dataset.py ├── transforms.py ├── utils.py ├── main.py └── imet-predict-final.ipynb /README.md: -------------------------------------------------------------------------------- 1 | # iMet Collection 2019 - FGVC6 2 | This is a PyTorch implementation of [iMet Collection 2019](https://www.kaggle.com/c/imet-2019-fgvc6/overview). 3 | 4 | This code is based on the [baseline code](https://github.com/lopuhin/kaggle-imet-2019). 5 | 6 | ## Usage 7 | 8 | ### Train 9 | Make folds 10 | ``` 11 | python make_folds.py --n-folds 40 12 | ``` 13 | 14 | Train se_resnext101 from fold 0 to 9: 15 | ``` 16 | python main.py train model_se101_{fold} --model se_resnext101_32x4d --fold {fold} --n-epochs 40 --batch-size 32 --workers 8 17 | ``` 18 | Train inceptionresnetv2 from fold 5 to 9: 19 | ``` 20 | python main.py train model_inres2_{fold} --model inceptionresnetv2 --fold {fold} --n-epochs 40 --batch-size 32 --workers 8 21 | ``` 22 | Train pnas models from fold 0 to 4: 23 | ``` 24 | python main.py train model_pnas_{fold} --model pnasnet5large --fold {fold} --n-epochs 40 --batch-size 24 --workers 8 25 | ``` 26 | ### Test 27 | The ensemble of these model is used to predict results in `imet-predict-final.ipynb`. 28 | -------------------------------------------------------------------------------- /make_submission.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import pandas as pd 4 | 5 | from utils import mean_df 6 | from dataset import DATA_ROOT 7 | from main import binarize_prediction 8 | 9 | 10 | def main(): 11 | parser = argparse.ArgumentParser() 12 | arg = parser.add_argument 13 | arg('predictions', nargs='+') 14 | arg('output') 15 | arg('--threshold', type=float, default=0.2) 16 | arg('--max-labels', type=int, default=10) 17 | args = parser.parse_args() 18 | sample_submission = pd.read_csv( 19 | DATA_ROOT / 'sample_submission.csv', index_col='id') 20 | dfs = [] 21 | for prediction in args.predictions: 22 | df = pd.read_hdf(prediction, index_col='id') 23 | df = df.reindex(sample_submission.index) 24 | dfs.append(df) 25 | df = pd.concat(dfs) 26 | df = mean_df(df) 27 | df[:] = binarize_prediction(df.values, threshold=args.threshold, max_labels=args.max_labels) 28 | df = df.apply(get_classes, axis=1) 29 | df.name = 'attribute_ids' 30 | df.to_csv(args.output, header=True) 31 | 32 | 33 | def get_classes(item): 34 | return ' '.join(cls for cls, is_present in item.items() if is_present) 35 | 36 | 37 | if __name__ == '__main__': 38 | main() 39 | -------------------------------------------------------------------------------- /make_folds.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from collections import defaultdict, Counter 3 | import random 4 | 5 | import pandas as pd 6 | import tqdm 7 | 8 | from dataset import DATA_ROOT 9 | 10 | 11 | def make_folds(n_folds: int, seed: int) -> pd.DataFrame: 12 | df = pd.read_csv(DATA_ROOT / 'train.csv') 13 | cls_counts = Counter(cls for classes in df['attribute_ids'].str.split() 14 | for cls in classes) 15 | fold_cls_counts = defaultdict(int) 16 | folds = [-1] * len(df) 17 | for item in tqdm.tqdm(df.sample(frac=1, random_state=seed).itertuples(), 18 | total=len(df)): 19 | cls = min(item.attribute_ids.split(), key=lambda cls: cls_counts[cls]) 20 | fold_counts = [(f, fold_cls_counts[f, cls]) for f in range(n_folds)] 21 | min_count = min([count for _, count in fold_counts]) 22 | random.seed(item.Index) 23 | fold = random.choice([f for f, count in fold_counts 24 | if count == min_count]) 25 | folds[item.Index] = fold 26 | for cls in item.attribute_ids.split(): 27 | fold_cls_counts[fold, cls] += 1 28 | df['fold'] = folds 29 | return df 30 | 31 | 32 | def main(): 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('--n-folds', type=int, default=5) 35 | parser.add_argument('--seed', type=int, default=42) 36 | args = parser.parse_args() 37 | df = make_folds(n_folds=args.n_folds, seed=args.seed) 38 | df.to_csv('folds.csv', index=None) 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | import torch 4 | from torch import nn 5 | from torch.nn import functional as F 6 | import torchvision.models as M 7 | 8 | from utils import ON_KAGGLE 9 | 10 | 11 | class AvgPool(nn.Module): 12 | def forward(self, x): 13 | return F.avg_pool2d(x, x.shape[2:]) 14 | 15 | 16 | def create_net(net_cls, pretrained: bool): 17 | if ON_KAGGLE and pretrained: 18 | net = net_cls() 19 | model_name = net_cls.__name__ 20 | weights_path = f'../input/{model_name}/{model_name}.pth' 21 | net.load_state_dict(torch.load(weights_path)) 22 | else: 23 | net = net_cls(pretrained=pretrained) 24 | return net 25 | 26 | 27 | class ResNet(nn.Module): 28 | def __init__(self, num_classes, 29 | pretrained=False, net_cls=M.resnet50, dropout=False): 30 | super().__init__() 31 | self.net = create_net(net_cls, pretrained=pretrained) 32 | self.net.avgpool = AvgPool() 33 | if dropout: 34 | self.net.fc = nn.Sequential( 35 | nn.Dropout(), 36 | nn.Linear(self.net.fc.in_features, num_classes), 37 | ) 38 | else: 39 | self.net.fc = nn.Linear(self.net.fc.in_features, num_classes) 40 | 41 | def fresh_params(self): 42 | return self.net.fc.parameters() 43 | 44 | def forward(self, x): 45 | return self.net(x) 46 | 47 | 48 | class DenseNet(nn.Module): 49 | def __init__(self, num_classes, 50 | pretrained=False, net_cls=M.densenet121): 51 | super().__init__() 52 | self.net = create_net(net_cls, pretrained=pretrained) 53 | self.avg_pool = AvgPool() 54 | self.net.classifier = nn.Linear( 55 | self.net.classifier.in_features, num_classes) 56 | 57 | def fresh_params(self): 58 | return self.net.classifier.parameters() 59 | 60 | def forward(self, x): 61 | out = self.net.features(x) 62 | out = F.relu(out, inplace=True) 63 | out = self.avg_pool(out).view(out.size(0), -1) 64 | out = self.net.classifier(out) 65 | return out 66 | 67 | 68 | resnet18 = partial(ResNet, net_cls=M.resnet18) 69 | resnet34 = partial(ResNet, net_cls=M.resnet34) 70 | resnet50 = partial(ResNet, net_cls=M.resnet50) 71 | resnet101 = partial(ResNet, net_cls=M.resnet101) 72 | resnet152 = partial(ResNet, net_cls=M.resnet152) 73 | 74 | densenet121 = partial(DenseNet, net_cls=M.densenet121) 75 | densenet169 = partial(DenseNet, net_cls=M.densenet169) 76 | densenet201 = partial(DenseNet, net_cls=M.densenet201) 77 | densenet161 = partial(DenseNet, net_cls=M.densenet161) 78 | -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Callable, List 3 | import os 4 | 5 | import cv2 6 | import pandas as pd 7 | from PIL import Image 8 | import torch 9 | from torch.utils.data import Dataset 10 | 11 | from transforms import tensor_transform, train_tensor_transform 12 | from utils import ON_KAGGLE 13 | 14 | 15 | N_CLASSES = 1103 16 | DATA_ROOT = Path('../input/imet-2019-fgvc6' if ON_KAGGLE else os.path.expanduser('~')+'/.kaggle/imet') 17 | 18 | cv2.setNumThreads(8) 19 | 20 | class TrainDataset(Dataset): 21 | def __init__(self, root: Path, df: pd.DataFrame, 22 | image_transform: Callable, debug: bool = True): 23 | super().__init__() 24 | self._root = root 25 | self._df = df 26 | self._image_transform = image_transform 27 | self._debug = debug 28 | 29 | def __len__(self): 30 | return len(self._df) 31 | 32 | def __getitem__(self, idx: int): 33 | item = self._df.iloc[idx] 34 | image = train_load_transform_image( 35 | item, self._root, self._image_transform, debug=self._debug) 36 | target = torch.zeros(N_CLASSES) 37 | for cls in item.attribute_ids.split(): 38 | target[int(cls)] = 1 39 | return image, target 40 | 41 | 42 | class TTADataset: 43 | def __init__(self, root: Path, df: pd.DataFrame, 44 | image_transform: Callable, tta: int): 45 | self._root = root 46 | self._df = df 47 | self._image_transform = image_transform 48 | self._tta = tta 49 | 50 | def __len__(self): 51 | return len(self._df) * self._tta 52 | 53 | def __getitem__(self, idx): 54 | item = self._df.iloc[idx % len(self._df)] 55 | image = load_transform_image(item, self._root, self._image_transform) 56 | return image, item.id 57 | 58 | 59 | def load_transform_image( 60 | item, root: Path, image_transform: Callable, debug: bool = False): 61 | image = load_image(item, root) 62 | image = image_transform(image) 63 | if debug: 64 | image.save('_debug.png') 65 | return tensor_transform(image) 66 | 67 | 68 | def train_load_transform_image( 69 | item, root: Path, image_transform: Callable, debug: bool = False): 70 | image = load_image(item, root) 71 | image = image_transform(image) 72 | if debug: 73 | image.save('_debug.png') 74 | return train_tensor_transform(image) 75 | 76 | 77 | def load_image(item, root: Path) -> Image.Image: 78 | image = cv2.imread(str(root / f'{item.id}.png')) 79 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 80 | return Image.fromarray(image) 81 | 82 | 83 | def get_ids(root: Path) -> List[str]: 84 | return sorted({p.name.split('_')[0] for p in root.glob('*.png')}) -------------------------------------------------------------------------------- /transforms.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | 4 | from PIL import Image 5 | from torchvision.transforms import ( 6 | ToTensor, Normalize, Compose, Resize, CenterCrop, RandomCrop, 7 | RandomHorizontalFlip) 8 | 9 | 10 | class RandomSizedCrop: 11 | """Random crop the given PIL.Image to a random size 12 | of the original size and and a random aspect ratio 13 | of the original aspect ratio. 14 | size: size of the smaller edge 15 | interpolation: Default: PIL.Image.BILINEAR 16 | """ 17 | 18 | def __init__(self, size, interpolation=Image.BILINEAR, 19 | min_aspect=4/5, max_aspect=5/4, 20 | min_area=0.25, max_area=1): 21 | self.size = size 22 | self.interpolation = interpolation 23 | self.min_aspect = min_aspect 24 | self.max_aspect = max_aspect 25 | self.min_area = min_area 26 | self.max_area = max_area 27 | 28 | def __call__(self, img): 29 | for attempt in range(10): 30 | area = img.size[0] * img.size[1] 31 | target_area = random.uniform(self.min_area, self.max_area) * area 32 | aspect_ratio = random.uniform(self.min_aspect, self.max_aspect) 33 | 34 | w = int(round(math.sqrt(target_area * aspect_ratio))) 35 | h = int(round(math.sqrt(target_area / aspect_ratio))) 36 | 37 | if random.random() < 0.5: 38 | w, h = h, w 39 | 40 | if w <= img.size[0] and h <= img.size[1]: 41 | x1 = random.randint(0, img.size[0] - w) 42 | y1 = random.randint(0, img.size[1] - h) 43 | 44 | img = img.crop((x1, y1, x1 + w, y1 + h)) 45 | assert(img.size == (w, h)) 46 | 47 | return img.resize((self.size, self.size), self.interpolation) 48 | 49 | # Fallback 50 | scale = Resize(self.size, interpolation=self.interpolation) 51 | crop = CenterCrop(self.size) 52 | return crop(scale(img)) 53 | 54 | class RandomErasing(object): 55 | def __init__(self, EPSILON = 0.5, sl = 0.02, sh = 0.4, r1 = 0.3, mean=[0.4914, 0.4822, 0.4465]): 56 | self.EPSILON = EPSILON 57 | self.mean = mean 58 | self.sl = sl 59 | self.sh = sh 60 | self.r1 = r1 61 | 62 | def __call__(self, img): 63 | 64 | if random.uniform(0, 1) > self.EPSILON: 65 | return img 66 | 67 | for attempt in range(100): 68 | area = img.size()[1] * img.size()[2] 69 | 70 | target_area = random.uniform(self.sl, self.sh) * area 71 | aspect_ratio = random.uniform(self.r1, 1/self.r1) 72 | 73 | h = int(round(math.sqrt(target_area * aspect_ratio))) 74 | w = int(round(math.sqrt(target_area / aspect_ratio))) 75 | 76 | if w < img.size()[2] and h < img.size()[1]: 77 | x1 = random.randint(0, img.size()[1] - h) 78 | y1 = random.randint(0, img.size()[2] - w) 79 | if img.size()[0] == 3: 80 | #img[0, x1:x1+h, y1:y1+w] = random.uniform(0, 1) 81 | #img[1, x1:x1+h, y1:y1+w] = random.uniform(0, 1) 82 | #img[2, x1:x1+h, y1:y1+w] = random.uniform(0, 1) 83 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 84 | img[1, x1:x1+h, y1:y1+w] = self.mean[1] 85 | img[2, x1:x1+h, y1:y1+w] = self.mean[2] 86 | #img[:, x1:x1+h, y1:y1+w] = torch.from_numpy(np.random.rand(3, h, w)) 87 | else: 88 | img[0, x1:x1+h, y1:y1+w] = self.mean[1] 89 | # img[0, x1:x1+h, y1:y1+w] = torch.from_numpy(np.random.rand(1, h, w)) 90 | return img 91 | 92 | return img 93 | 94 | train_transform = Compose([ 95 | RandomCrop(320, pad_if_needed=True), 96 | RandomHorizontalFlip(), 97 | # ImageNetPolicy(), 98 | ]) 99 | 100 | 101 | test_transform = Compose([ 102 | RandomCrop(320, pad_if_needed=True), 103 | RandomHorizontalFlip(), 104 | ]) 105 | 106 | 107 | tensor_transform = Compose([ 108 | ToTensor(), 109 | Normalize(mean=[0.5949, 0.5611, 0.5185], std=[0.2900, 0.2844, 0.2811]), 110 | ]) 111 | 112 | train_tensor_transform = Compose([ 113 | ToTensor(), 114 | Normalize(mean=[0.5949, 0.5611, 0.5185], std=[0.2900, 0.2844, 0.2811]), 115 | RandomErasing(), 116 | ]) -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | import glob 4 | import os 5 | from pathlib import Path 6 | from multiprocessing.pool import ThreadPool 7 | from typing import Dict 8 | 9 | import numpy as np 10 | import pandas as pd 11 | from scipy.stats.mstats import gmean 12 | import torch 13 | from torch import nn 14 | from torch.utils.data import DataLoader 15 | 16 | 17 | ON_KAGGLE: bool = 'KAGGLE_WORKING_DIR' in os.environ 18 | 19 | 20 | def gmean_df(df: pd.DataFrame) -> pd.DataFrame: 21 | return df.groupby(level=0).agg(lambda x: gmean(list(x))) 22 | 23 | 24 | def mean_df(df: pd.DataFrame) -> pd.DataFrame: 25 | return df.groupby(level=0).mean() 26 | 27 | 28 | def load_model(model: nn.Module, path: Path) -> Dict: 29 | state = torch.load(str(path)) 30 | model.load_state_dict(state['model']) 31 | print('Loaded model from epoch {epoch}, step {step:,}'.format(**state)) 32 | return state 33 | 34 | 35 | class ThreadingDataLoader_(DataLoader): 36 | def __iter__(self): 37 | sample_iter = iter(self.batch_sampler) 38 | if self.num_workers == 0: 39 | for indices in sample_iter: 40 | yield self.collate_fn([self._get_item(i) for i in indices]) 41 | else: 42 | prefetch = 1 43 | with ThreadPool(processes=self.num_workers) as pool: 44 | futures = [] 45 | for indices in sample_iter: 46 | futures.append([pool.apply_async(self._get_item, args=(i,)) 47 | for i in indices]) 48 | if len(futures) > prefetch: 49 | yield self.collate_fn([f.get() for f in futures.pop(0)]) 50 | # items = pool.map(lambda i: self.dataset[i], indices) 51 | # yield self.collate_fn(items) 52 | for batch_futures in futures: 53 | yield self.collate_fn([f.get() for f in batch_futures]) 54 | 55 | def _get_item(self, i): 56 | return self.dataset[i] 57 | 58 | class ThreadingDataLoader(DataLoader): 59 | 60 | def _get_item(self, i): 61 | return self.dataset[i] 62 | 63 | 64 | def write_event(log, step: int, **data): 65 | data['step'] = step 66 | data['dt'] = datetime.now().isoformat() 67 | log.write(json.dumps(data, sort_keys=True)) 68 | log.write('\n') 69 | log.flush() 70 | 71 | 72 | def plot(*args, ymin=None, ymax=None, xmin=None, xmax=None, params=False, 73 | max_points=200, legend=True, title=None, 74 | print_keys=False, print_paths=False, plt=None, newfigure=True, 75 | x_scale=1): 76 | """ 77 | Use in the notebook like this:: 78 | 79 | %matplotlib inline 80 | from imet.utils import plot 81 | plot('./runs/oc2', './runs/oc1', 'loss', 'valid_loss') 82 | 83 | """ 84 | import json_lines # no available on Kaggle 85 | 86 | if plt is None: 87 | from matplotlib import pyplot as plt 88 | paths, keys = [], [] 89 | for x in args: 90 | if x.startswith('.') or '/' in x: 91 | if '*' in x: 92 | paths.extend(glob.glob(x)) 93 | else: 94 | paths.append(x) 95 | else: 96 | keys.append(x) 97 | if print_paths: 98 | print('Found paths: {}'.format(' '.join(sorted(paths)))) 99 | if newfigure: 100 | plt.figure(figsize=(12, 8)) 101 | keys = keys or ['loss', 'valid_loss'] 102 | 103 | ylim_kw = {} 104 | if ymin is not None: 105 | ylim_kw['bottom'] = ymin 106 | if ymax is not None: 107 | ylim_kw['top'] = ymax 108 | if ylim_kw: 109 | plt.ylim(**ylim_kw) 110 | 111 | xlim_kw = {} 112 | if xmin is not None: 113 | xlim_kw['left'] = xmin 114 | if xmax is not None: 115 | xlim_kw['right'] = xmax 116 | if xlim_kw: 117 | plt.xlim(**xlim_kw) 118 | all_keys = set() 119 | for path in sorted(paths): 120 | path = Path(path) 121 | with json_lines.open(path / 'train.log', broken=True) as f: 122 | events = list(f) 123 | all_keys.update(k for e in events for k in e) 124 | for key in sorted(keys): 125 | xs, ys, ys_err = [], [], [] 126 | for e in events: 127 | if key in e: 128 | xs.append(e['step'] * x_scale) 129 | ys.append(e[key]) 130 | std_key = key + '_std' 131 | if std_key in e: 132 | ys_err.append(e[std_key]) 133 | if xs: 134 | if np.isnan(ys).any(): 135 | print('Warning: NaN {} for {}'.format(key, path)) 136 | if len(xs) > 2 * max_points: 137 | indices = (np.arange(0, len(xs) - 1, len(xs) / max_points) 138 | .astype(np.int32)) 139 | xs = np.array(xs)[indices[1:]] 140 | ys = _smooth(ys, indices) 141 | if ys_err: 142 | ys_err = _smooth(ys_err, indices) 143 | label = '{}: {}'.format(path, key) 144 | if label.startswith('_'): 145 | label = ' ' + label 146 | if ys_err: 147 | ys_err = 1.96 * np.array(ys_err) 148 | plt.errorbar(xs, ys, yerr=ys_err, 149 | fmt='-o', capsize=5, capthick=2, 150 | label=label) 151 | else: 152 | plt.plot(xs, ys, label=label) 153 | plt.legend() 154 | if newfigure: 155 | plt.grid() 156 | if legend: 157 | plt.legend() 158 | if title: 159 | plt.title(title) 160 | if print_keys: 161 | print('Found keys: {}' 162 | .format(', '.join(sorted(all_keys - {'step', 'dt'})))) 163 | 164 | 165 | def _smooth(ys, indices): 166 | return [np.mean(ys[idx: indices[i + 1]]) 167 | for i, idx in enumerate(indices[:-1])] 168 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from itertools import islice 3 | import json 4 | from pathlib import Path 5 | import shutil 6 | import warnings 7 | from typing import Dict 8 | 9 | import numpy as np 10 | import pandas as pd 11 | from sklearn.metrics import fbeta_score 12 | from sklearn.exceptions import UndefinedMetricWarning 13 | import torch 14 | from torch import nn, cuda 15 | from torch.nn import functional as F 16 | from torch.optim import Adam 17 | from torch.optim import SGD 18 | import tqdm 19 | 20 | import pretrainedmodels as models 21 | from dataset import TrainDataset, TTADataset, get_ids, N_CLASSES, DATA_ROOT 22 | from transforms import train_transform, test_transform 23 | from utils import ( 24 | write_event, load_model, mean_df, ThreadingDataLoader as DataLoader, 25 | ON_KAGGLE) 26 | from tensorboardX import SummaryWriter 27 | 28 | 29 | def main(): 30 | parser = argparse.ArgumentParser() 31 | arg = parser.add_argument 32 | arg('mode', choices=['train', 'validate', 'predict_valid', 'predict_test']) 33 | arg('run_root') 34 | arg('--model', default='se_resnet50') 35 | arg('--pretrained', type=int, default=1) 36 | arg('--batch-size', type=int, default=64) 37 | arg('--step', type=int, default=1) 38 | arg('--workers', type=int, default=2 if ON_KAGGLE else 4) 39 | arg('--lr', type=float, default=1e-4) 40 | arg('--patience', type=int, default=4) 41 | arg('--clean', action='store_true') 42 | arg('--n-epochs', type=int, default=100) 43 | arg('--epoch-size', type=int) 44 | arg('--tta', type=int, default=4) 45 | arg('--use-sample', action='store_true', help='use a sample of the dataset') 46 | arg('--debug', action='store_true') 47 | arg('--limit', type=int) 48 | arg('--fold', type=int, default=0) 49 | arg('--multi', type=int, default=0) 50 | arg('--half', type=int, default=0) 51 | args = parser.parse_args() 52 | 53 | run_root = Path(args.run_root) 54 | folds = pd.read_csv('folds.csv') 55 | train_root = DATA_ROOT / ('train_sample' if args.use_sample else 'train') 56 | if args.use_sample: 57 | folds = folds[folds['Id'].isin(set(get_ids(train_root)))] 58 | train_fold = folds[folds['fold'] != args.fold] 59 | valid_fold = folds[folds['fold'] == args.fold] 60 | if args.limit: 61 | train_fold = train_fold[:args.limit] 62 | valid_fold = valid_fold[:args.limit] 63 | 64 | def make_loader(df: pd.DataFrame, image_transform) -> DataLoader: 65 | return DataLoader( 66 | TrainDataset(train_root, df, image_transform, debug=args.debug), 67 | shuffle=True, 68 | batch_size=args.batch_size, 69 | num_workers=args.workers, 70 | ) 71 | criterion = nn.BCEWithLogitsLoss(reduction='none') 72 | # criterion = FocalLoss() 73 | model = getattr(models, args.model)() 74 | feature_dim = model.last_linear.in_features 75 | class AvgPool(nn.Module): 76 | def forward(self, x): 77 | # print (x.size()) 78 | return F.avg_pool2d(x, x.shape[2:]) 79 | model.avg_pool = AvgPool() 80 | model.avgpool = AvgPool() 81 | model.last_linear = nn.Linear(feature_dim, N_CLASSES) 82 | use_cuda = cuda.is_available() 83 | fresh_params = list(model.last_linear.parameters()) 84 | all_params = list(model.parameters()) 85 | if use_cuda: 86 | if args.multi: 87 | model = torch.nn.DataParallel(model).cuda() 88 | else: 89 | model = model.cuda() 90 | 91 | if args.mode == 'train': 92 | if run_root.exists() and args.clean: 93 | shutil.rmtree(run_root) 94 | run_root.mkdir(exist_ok=True, parents=True) 95 | (run_root / 'params.json').write_text( 96 | json.dumps(vars(args), indent=4, sort_keys=True)) 97 | writer = SummaryWriter(str(run_root/'tbx')) 98 | 99 | train_loader = make_loader(train_fold, train_transform) 100 | valid_loader = make_loader(valid_fold, test_transform) 101 | print(f'{len(train_loader.dataset):,} items in train, ' 102 | f'{len(valid_loader.dataset):,} in valid') 103 | 104 | train_kwargs = dict( 105 | args=args, 106 | model=model, 107 | criterion=criterion, 108 | train_loader=train_loader, 109 | valid_loader=valid_loader, 110 | patience=args.patience, 111 | init_optimizer=lambda params, lr: Adam(params, lr), 112 | use_cuda=use_cuda, 113 | ) 114 | 115 | if args.pretrained: 116 | if train(params=fresh_params, writer=writer, n_epochs=1, **train_kwargs): 117 | train(params=all_params, writer=writer, **train_kwargs) 118 | else: 119 | train(params=all_params, writer=writer, **train_kwargs) 120 | writer.export_scalars_to_json(str(run_root/'tbx/all_scalars.json')) 121 | writer.close() 122 | 123 | elif args.mode == 'validate': 124 | valid_loader = make_loader(valid_fold, test_transform) 125 | load_model(model, run_root / 'model.pt') 126 | validation(model, criterion, tqdm.tqdm(valid_loader, desc='Validation'), 127 | use_cuda=use_cuda) 128 | 129 | elif args.mode.startswith('predict'): 130 | load_model(model, run_root / 'best-model.pt') 131 | predict_kwargs = dict( 132 | batch_size=args.batch_size, 133 | tta=args.tta, 134 | use_cuda=use_cuda, 135 | workers=args.workers, 136 | ) 137 | if args.mode == 'predict_valid': 138 | predict(model, df=valid_fold, root=train_root, 139 | out_path=run_root / 'val.h5', 140 | **predict_kwargs) 141 | elif args.mode == 'predict_test': 142 | test_root = DATA_ROOT / ( 143 | 'test_sample' if args.use_sample else 'test') 144 | ss = pd.read_csv(DATA_ROOT / 'sample_submission.csv') 145 | if args.use_sample: 146 | ss = ss[ss['id'].isin(set(get_ids(test_root)))] 147 | if args.limit: 148 | ss = ss[:args.limit] 149 | predict(model, df=ss, root=test_root, 150 | out_path=run_root / 'test.h5', 151 | **predict_kwargs) 152 | 153 | 154 | def predict(model, root: Path, df: pd.DataFrame, out_path: Path, 155 | batch_size: int, tta: int, workers: int, use_cuda: bool): 156 | loader = DataLoader( 157 | dataset=TTADataset(root, df, test_transform, tta=tta), 158 | shuffle=False, 159 | batch_size=batch_size, 160 | num_workers=workers, 161 | ) 162 | model.eval() 163 | all_outputs, all_ids = [], [] 164 | with torch.no_grad(): 165 | for inputs, ids in tqdm.tqdm(loader, desc='Predict'): 166 | if use_cuda: 167 | inputs = inputs.cuda() 168 | outputs = torch.sigmoid(model(inputs)) 169 | all_outputs.append(outputs.data.cpu().numpy()) 170 | all_ids.extend(ids) 171 | df = pd.DataFrame( 172 | data=np.concatenate(all_outputs), 173 | index=all_ids, 174 | columns=map(str, range(N_CLASSES))) 175 | df = mean_df(df) 176 | df.to_hdf(out_path, 'prob', index_label='id') 177 | print(f'Saved predictions to {out_path}') 178 | 179 | 180 | def train(args, model: nn.Module, criterion, *, params, 181 | train_loader, valid_loader, init_optimizer, use_cuda, 182 | n_epochs=None, patience=2, max_lr_changes=2, writer=SummaryWriter()) -> bool: 183 | lr = args.lr 184 | n_epochs = n_epochs or args.n_epochs 185 | params = list(params) 186 | optimizer = init_optimizer(params, lr) 187 | # scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.5, patience=5) 188 | 189 | run_root = Path(args.run_root) 190 | model_path = run_root / 'model.pt' 191 | best_model_path = run_root / 'best-model.pt' 192 | if model_path.exists(): 193 | state = load_model(model, model_path) 194 | epoch = state['epoch'] 195 | step = state['step'] 196 | best_valid_loss = state['best_valid_loss'] 197 | else: 198 | epoch = 1 199 | step = 0 200 | best_valid_loss = float('inf') 201 | lr_changes = 0 202 | total_step = len(train_loader) 203 | 204 | save = lambda ep: torch.save({ 205 | 'model': model.state_dict(), 206 | 'epoch': ep, 207 | 'step': step, 208 | 'best_valid_loss': best_valid_loss 209 | }, str(model_path)) 210 | 211 | report_each = 10 212 | log = run_root.joinpath('train.log').open('at', encoding='utf8') 213 | valid_losses = [] 214 | lr_reset_epoch = epoch 215 | for epoch in range(epoch, n_epochs + 1): 216 | model.train() 217 | tq = tqdm.tqdm(total=(args.epoch_size or 218 | len(train_loader) * args.batch_size)) 219 | tq.set_description(f'Epoch {epoch}, lr {lr}') 220 | losses = [] 221 | tl = train_loader 222 | if args.epoch_size: 223 | tl = islice(tl, args.epoch_size // args.batch_size) 224 | try: 225 | mean_loss = 0 226 | for i, (inputs, targets) in enumerate(tl): 227 | # lr = adjust_learning_rate(optimizer, args.lr, step/total_step, n_epochs) 228 | if use_cuda: 229 | inputs, targets = inputs.cuda(), targets.cuda() 230 | ''' 231 | outputs = model(inputs) 232 | loss = _reduce_loss(criterion(outputs, targets)) 233 | ''' 234 | 235 | l = np.random.beta(0.2, 0.2) 236 | idx = torch.randperm(inputs.size(0)) 237 | input_a, input_b = inputs, inputs[idx] 238 | target_a, target_b = targets, targets[idx] 239 | 240 | mixed_input = l * input_a + (1 - l) * input_b 241 | 242 | output = model(mixed_input) 243 | 244 | loss = l * _reduce_loss(criterion(output, target_a)) + (1 - l) * _reduce_loss(criterion(output, target_b)) 245 | 246 | 247 | batch_size = inputs.size(0) 248 | 249 | (batch_size * loss).backward() 250 | 251 | # loss.backward() 252 | 253 | if (i + 1) % args.step == 0: 254 | optimizer.step() 255 | optimizer.zero_grad() 256 | step += 1 257 | tq.update(batch_size) 258 | losses.append(loss.item()) 259 | mean_loss = np.mean(losses[-report_each:]) 260 | tq.set_postfix(loss=f'{mean_loss:.3f}') 261 | if i and i % report_each == 0: 262 | write_event(log, step, loss=mean_loss) 263 | writer.add_scalar('data/train_loss', mean_loss, step) 264 | write_event(log, step, loss=mean_loss) 265 | tq.close() 266 | save(epoch + 1) 267 | valid_metrics = validation(model, criterion, valid_loader, use_cuda) 268 | write_event(log, step, **valid_metrics) 269 | valid_loss = valid_metrics['valid_loss'] 270 | valid_losses.append(valid_loss) 271 | if valid_loss < best_valid_loss: 272 | best_valid_loss = valid_loss 273 | shutil.copy(str(model_path), str(best_model_path)) 274 | 275 | elif (patience and epoch - lr_reset_epoch > patience and 276 | min(valid_losses[-patience:]) > best_valid_loss): 277 | # "patience" epochs without improvement 278 | lr_changes +=1 279 | if lr_changes > max_lr_changes: 280 | break 281 | lr /= 5 282 | print(f'lr updated to {lr}') 283 | lr_reset_epoch = epoch 284 | optimizer = init_optimizer(params, lr) 285 | 286 | # scheduler.step(valid_loss) 287 | # lr = optimizer.param_groups[0]['lr'] 288 | writer.add_scalar('data/valid_loss', valid_loss, step) 289 | writer.add_scalar('data/lr', lr, step) 290 | metrics = {} 291 | for k,v in valid_metrics.items(): 292 | if k == 'valid_loss': 293 | continue 294 | metrics[k] = v 295 | writer.add_scalars('data/metrics', metrics, step) 296 | 297 | except KeyboardInterrupt: 298 | tq.close() 299 | print('Ctrl+C, saving snapshot') 300 | save(epoch) 301 | print('done.') 302 | return False 303 | return True 304 | 305 | 306 | def validation( 307 | model: nn.Module, criterion, valid_loader, use_cuda, 308 | ) -> Dict[str, float]: 309 | model.eval() 310 | all_losses, all_predictions, all_targets = [], [], [] 311 | with torch.no_grad(): 312 | for inputs, targets in valid_loader: 313 | all_targets.append(targets.numpy().copy()) 314 | if use_cuda: 315 | inputs, targets = inputs.cuda(), targets.cuda() 316 | outputs = model(inputs) 317 | loss = criterion(outputs, targets) 318 | all_losses.append(_reduce_loss(loss).item()) 319 | predictions = torch.sigmoid(outputs) 320 | all_predictions.append(predictions.cpu().numpy()) 321 | all_predictions = np.concatenate(all_predictions) 322 | all_targets = np.concatenate(all_targets) 323 | 324 | def get_score(y_pred): 325 | with warnings.catch_warnings(): 326 | warnings.simplefilter('ignore', category=UndefinedMetricWarning) 327 | return fbeta_score( 328 | all_targets, y_pred, beta=2, average='samples') 329 | 330 | metrics = {} 331 | argsorted = all_predictions.argsort(axis=1) 332 | for threshold in [0.05, 0.10, 0.15, 0.20, 0.30, 0.35, 0.4, 0.45, 0.5]: 333 | metrics[f'valid_f2_th_{threshold:.2f}'] = get_score( 334 | binarize_prediction(all_predictions, threshold, argsorted)) 335 | metrics['valid_loss'] = np.mean(all_losses) 336 | print(' | '.join(f'{k} {v:.3f}' for k, v in sorted( 337 | metrics.items(), key=lambda kv: -kv[1]))) 338 | 339 | return metrics 340 | 341 | 342 | def adjust_learning_rate(optimizer, max_lr, epoch, lr_rampdown_epochs): 343 | # print (epoch) 344 | ''' 345 | if epoch <= 1: 346 | lr = np.interp([epoch], [0, 1], [0, max_lr])[0] 347 | else: 348 | ''' 349 | lr = max_lr 350 | lr *= cosine_rampdown(epoch, lr_rampdown_epochs) 351 | 352 | for param_group in optimizer.param_groups: 353 | param_group['lr'] = lr 354 | return lr 355 | 356 | def cosine_rampdown(current, rampdown_length): 357 | """Cosine rampdown from https://arxiv.org/abs/1608.03983""" 358 | assert 0 <= current <= rampdown_length 359 | return float(.5 * (np.cos(np.pi * current / rampdown_length) + 1)) 360 | 361 | 362 | def binarize_prediction(probabilities, threshold: float, argsorted=None, 363 | min_labels=1, max_labels=10): 364 | """ Return matrix of 0/1 predictions, same shape as probabilities. 365 | """ 366 | assert probabilities.shape[1] == N_CLASSES 367 | if argsorted is None: 368 | argsorted = probabilities.argsort(axis=1) 369 | max_mask = _make_mask(argsorted, max_labels) 370 | min_mask = _make_mask(argsorted, min_labels) 371 | prob_mask = probabilities > threshold 372 | return (max_mask & prob_mask) | min_mask 373 | 374 | 375 | def _make_mask(argsorted, top_n: int): 376 | mask = np.zeros_like(argsorted, dtype=np.uint8) 377 | col_indices = argsorted[:, -top_n:].reshape(-1) 378 | row_indices = [i // top_n for i in range(len(col_indices))] 379 | mask[row_indices, col_indices] = 1 380 | return mask 381 | 382 | 383 | def _reduce_loss(loss): 384 | return loss.sum() / loss.shape[0] 385 | # return loss 386 | 387 | class FocalLoss(nn.Module): 388 | def __init__(self, gamma=2): 389 | super().__init__() 390 | self.gamma = gamma 391 | 392 | def forward(self, logit, target): 393 | target = target.float() 394 | max_val = (-logit).clamp(min=0) 395 | loss = logit - logit * target + max_val + \ 396 | ((-max_val).exp() + (-logit - max_val).exp()).log() 397 | 398 | invprobs = F.logsigmoid(-logit * (target * 2.0 - 1.0)) 399 | loss = (invprobs * self.gamma).exp() * loss 400 | if len(loss.size())==2: 401 | loss = loss.sum(dim=1) 402 | return loss.mean() 403 | 404 | if __name__ == '__main__': 405 | main() -------------------------------------------------------------------------------- /imet-predict-final.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", 8 | "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5" 9 | }, 10 | "outputs": [ 11 | { 12 | "name": "stdout", 13 | "output_type": "stream", 14 | "text": [ 15 | "['06-01-results', '05-28-results', '06-02-results', 'imet-2019-fgvc6', '05-30-results']\n" 16 | ] 17 | } 18 | ], 19 | "source": [ 20 | "# This Python 3 environment comes with many helpful analytics libraries installed\n", 21 | "# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n", 22 | "# For example, here's several helpful packages to load in \n", 23 | "import glob\n", 24 | "import numpy as np # linear algebra\n", 25 | "import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n", 26 | "\n", 27 | "# Input data files are available in the \"../input/\" directory.\n", 28 | "# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n", 29 | "\n", 30 | "import os\n", 31 | "print(os.listdir(\"../input/\"))\n", 32 | "\n", 33 | "# Any results you write to the current directory are saved as output." 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "import argparse\n", 43 | "from itertools import islice\n", 44 | "import json\n", 45 | "from pathlib import Path\n", 46 | "import shutil\n", 47 | "import warnings\n", 48 | "from typing import Dict\n", 49 | "import os\n", 50 | "import sys\n", 51 | "from collections import OrderedDict\n", 52 | "import math\n", 53 | "import random\n", 54 | "from typing import Callable, List\n", 55 | "from datetime import datetime\n", 56 | "import json\n", 57 | "import glob\n", 58 | "from multiprocessing.pool import ThreadPool\n", 59 | "import gc\n", 60 | "\n", 61 | "import torch\n", 62 | "from torch import nn, cuda\n", 63 | "from torch.nn import functional as F\n", 64 | "import torch.utils.model_zoo as model_zoo\n", 65 | "from torch.utils.data import DataLoader\n", 66 | "from torch.utils.data import Dataset\n", 67 | "from torchvision.transforms import (\n", 68 | " ToTensor, Normalize, Compose, Resize, CenterCrop, RandomCrop,\n", 69 | " RandomHorizontalFlip)\n", 70 | "\n", 71 | "from tqdm import tqdm_notebook\n", 72 | "from PIL import Image\n", 73 | "import cv2\n", 74 | "cv2.setNumThreads(0)\n", 75 | "from pretrainedmodels import *" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 3, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "def seed_everything(seed=1234):\n", 85 | " random.seed(seed)\n", 86 | " os.environ['PYTHONHASHSEED'] = str(seed)\n", 87 | " np.random.seed(seed)\n", 88 | " torch.manual_seed(seed)\n", 89 | " torch.cuda.manual_seed(seed)\n", 90 | " torch.backends.cudnn.deterministic = True\n", 91 | " \n", 92 | "seed_everything(7)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 4, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "DATA_ROOT = Path('../input/imet-2019-fgvc6')\n", 102 | "N_CLASSES = 1103" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 7, 108 | "metadata": { 109 | "_kg_hide-input": true 110 | }, 111 | "outputs": [], 112 | "source": [ 113 | "test_transform = Compose([\n", 114 | " RandomCrop(320, pad_if_needed=True),\n", 115 | " RandomHorizontalFlip(),\n", 116 | "])\n", 117 | "\n", 118 | "\n", 119 | "tensor_transform = Compose([\n", 120 | " ToTensor(),\n", 121 | " Normalize(mean=[0.5949, 0.5611, 0.5185], std=[0.2900, 0.2844, 0.2811]),\n", 122 | "])\n", 123 | "\n", 124 | "\n", 125 | "\n", 126 | "class TTADataset:\n", 127 | " def __init__(self, root: Path, df: pd.DataFrame,\n", 128 | " image_transform: Callable, tta: int):\n", 129 | " self._root = root\n", 130 | " self._df = df\n", 131 | " self._image_transform = image_transform\n", 132 | " self._tta = tta\n", 133 | "\n", 134 | " def __len__(self):\n", 135 | " return len(self._df) * self._tta\n", 136 | "\n", 137 | " def __getitem__(self, idx):\n", 138 | " item = self._df.iloc[idx % len(self._df)]\n", 139 | " image = load_transform_image(item, self._root, self._image_transform)\n", 140 | " return image, item.id\n", 141 | "\n", 142 | "\n", 143 | "def load_transform_image(\n", 144 | " item, root: Path, image_transform: Callable, debug: bool = False):\n", 145 | " image = load_image(item, root)\n", 146 | " image = image_transform(image)\n", 147 | " if debug:\n", 148 | " image.save('_debug.png')\n", 149 | " return tensor_transform(image)\n", 150 | "\n", 151 | "\n", 152 | "def train_load_transform_image(\n", 153 | " item, root: Path, image_transform: Callable, debug: bool = False):\n", 154 | " image = load_image(item, root)\n", 155 | " image = image_transform(image)\n", 156 | " if debug:\n", 157 | " image.save('_debug.png')\n", 158 | " return train_tensor_transform(image)\n", 159 | "\n", 160 | "\n", 161 | "def load_image(item, root: Path) -> Image.Image:\n", 162 | " image = cv2.imread(str(root / f'{item.id}.png'))\n", 163 | " image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n", 164 | " return Image.fromarray(image)\n", 165 | "\n", 166 | "\n", 167 | "def get_ids(root: Path) -> List[str]:\n", 168 | " return sorted({p.name.split('_')[0] for p in root.glob('*.png')})\n", 169 | "\n", 170 | "def load_model(model: nn.Module, path: Path) -> Dict:\n", 171 | " state = torch.load(str(path))\n", 172 | " model.load_state_dict(state['model'])\n", 173 | " print('Loaded model from epoch {epoch}, step {step:,}'.format(**state))\n", 174 | " return state\n", 175 | "\n", 176 | "def mean_df(df: pd.DataFrame) -> pd.DataFrame:\n", 177 | " return df.groupby(level=0).mean()\n", 178 | "\n", 179 | "def get_classes(item):\n", 180 | " return ' '.join(cls for cls, is_present in item.items() if is_present)\n", 181 | "\n", 182 | "def binarize_prediction(probabilities, threshold: float, argsorted=None,\n", 183 | " min_labels=1, max_labels=10):\n", 184 | " \"\"\" Return matrix of 0/1 predictions, same shape as probabilities.\n", 185 | " \"\"\"\n", 186 | " assert probabilities.shape[1] == N_CLASSES\n", 187 | " if argsorted is None:\n", 188 | " argsorted = probabilities.argsort(axis=1)\n", 189 | " max_mask = _make_mask(argsorted, max_labels)\n", 190 | " min_mask = _make_mask(argsorted, min_labels)\n", 191 | " \n", 192 | " prob_mask = []\n", 193 | " for prob in probabilities:\n", 194 | " prob_mask.append(prob > prob.max()/7)\n", 195 | " \n", 196 | " prob_mask = np.array(prob_mask, dtype=np.int)\n", 197 | " \n", 198 | " return (max_mask & prob_mask) | min_mask\n", 199 | "\n", 200 | "\n", 201 | "def _make_mask(argsorted, top_n: int):\n", 202 | " mask = np.zeros_like(argsorted, dtype=np.uint8)\n", 203 | " col_indices = argsorted[:, -top_n:].reshape(-1)\n", 204 | " row_indices = [i // top_n for i in range(len(col_indices))]\n", 205 | " mask[row_indices, col_indices] = 1\n", 206 | " return mask" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 8, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "args = {\n", 216 | " 'batch_size':64,\n", 217 | " 'tta':2,\n", 218 | " 'use_cuda':1,\n", 219 | " 'workers':8,\n", 220 | " 'threshold':0.1,\n", 221 | " 'max_labels':10,\n", 222 | " 'output':'submission.csv',\n", 223 | "}" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 9, 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "def create_model(model):\n", 233 | " feature_dim = model.last_linear.in_features\n", 234 | " class AvgPool(nn.Module):\n", 235 | " def forward(self, x):\n", 236 | " # print (x.size())\n", 237 | " return F.avg_pool2d(x, x.shape[2:])\n", 238 | " model.avg_pool = AvgPool()\n", 239 | " model.avgpool = AvgPool()\n", 240 | " model.last_linear = nn.Linear(feature_dim, N_CLASSES)\n", 241 | " model = torch.nn.DataParallel(model)\n", 242 | " model = model.cuda()\n", 243 | " return model\n", 244 | " \n", 245 | "def test(model, loader, model_path, multi=False, half=False):\n", 246 | " load_model(model, model_path / 'best-model.pt')\n", 247 | " df = predict(model, loader, use_cuda=args['use_cuda'], half=half)\n", 248 | " return df\n", 249 | " \n", 250 | "def predict(model, loader, use_cuda: bool, half=False):\n", 251 | " model.eval()\n", 252 | " all_outputs, all_ids = [], []\n", 253 | " with torch.no_grad():\n", 254 | " for inputs, ids in tqdm_notebook(loader):\n", 255 | " inputs = inputs.cuda()\n", 256 | " outputs = torch.sigmoid(model(inputs))\n", 257 | " # outputs = model(inputs)\n", 258 | " all_outputs.append(outputs.detach().cpu().numpy())\n", 259 | " all_ids.extend(ids)\n", 260 | " df = pd.DataFrame(\n", 261 | " data=np.concatenate(all_outputs),\n", 262 | " index=all_ids,\n", 263 | " columns=map(str, range(N_CLASSES)))\n", 264 | " df = mean_df(df)\n", 265 | " return df" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 10, 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [ 274 | "import string\n", 275 | "def randomString2(stringLength=8):\n", 276 | " \"\"\"Generate a random string of fixed length \"\"\"\n", 277 | " letters= string.ascii_lowercase\n", 278 | " return ''.join(random.sample(letters,stringLength))\n", 279 | "\n", 280 | "test_root = DATA_ROOT /'test'\n", 281 | "test_df = pd.read_csv(DATA_ROOT / 'sample_submission.csv')\n", 282 | "# df = pd.concat([df]*5, ignore_index=True)\n", 283 | "# df['new_id'] = [randomString2() for i in range(len(df))]\n", 284 | "loader = DataLoader(\n", 285 | " dataset=TTADataset(test_root, test_df, test_transform, tta=args['tta']),\n", 286 | " shuffle=False,\n", 287 | " batch_size=args['batch_size'],\n", 288 | " num_workers=args['workers'],\n", 289 | " )" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 11, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "name": "stdout", 299 | "output_type": "stream", 300 | "text": [ 301 | "Loaded model from epoch 12, step 24,420\n" 302 | ] 303 | }, 304 | { 305 | "data": { 306 | "application/vnd.jupyter.widget-view+json": { 307 | "model_id": "de53ab967cf04d5abffb0c2c58323609", 308 | "version_major": 2, 309 | "version_minor": 0 310 | }, 311 | "text/plain": [ 312 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 313 | ] 314 | }, 315 | "metadata": {}, 316 | "output_type": "display_data" 317 | }, 318 | { 319 | "name": "stdout", 320 | "output_type": "stream", 321 | "text": [ 322 | "\n", 323 | "Loaded model from epoch 17, step 35,520\n" 324 | ] 325 | }, 326 | { 327 | "data": { 328 | "application/vnd.jupyter.widget-view+json": { 329 | "model_id": "73da1ce3c2fd4ab8be0f44c411fc1f1b", 330 | "version_major": 2, 331 | "version_minor": 0 332 | }, 333 | "text/plain": [ 334 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 335 | ] 336 | }, 337 | "metadata": {}, 338 | "output_type": "display_data" 339 | }, 340 | { 341 | "name": "stdout", 342 | "output_type": "stream", 343 | "text": [ 344 | "\n", 345 | "Loaded model from epoch 18, step 37,723\n" 346 | ] 347 | }, 348 | { 349 | "data": { 350 | "application/vnd.jupyter.widget-view+json": { 351 | "model_id": "8a76b5341ee04503a4978b3d151c8b53", 352 | "version_major": 2, 353 | "version_minor": 0 354 | }, 355 | "text/plain": [ 356 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 357 | ] 358 | }, 359 | "metadata": {}, 360 | "output_type": "display_data" 361 | }, 362 | { 363 | "name": "stdout", 364 | "output_type": "stream", 365 | "text": [ 366 | "\n", 367 | "Loaded model from epoch 17, step 35,520\n" 368 | ] 369 | }, 370 | { 371 | "data": { 372 | "application/vnd.jupyter.widget-view+json": { 373 | "model_id": "8f9a48e993ba400c96d2699b74db76b3", 374 | "version_major": 2, 375 | "version_minor": 0 376 | }, 377 | "text/plain": [ 378 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 379 | ] 380 | }, 381 | "metadata": {}, 382 | "output_type": "display_data" 383 | }, 384 | { 385 | "name": "stdout", 386 | "output_type": "stream", 387 | "text": [ 388 | "\n", 389 | "Loaded model from epoch 19, step 39,942\n" 390 | ] 391 | }, 392 | { 393 | "data": { 394 | "application/vnd.jupyter.widget-view+json": { 395 | "model_id": "670445fd518c477db06d1a79b31cd086", 396 | "version_major": 2, 397 | "version_minor": 0 398 | }, 399 | "text/plain": [ 400 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 401 | ] 402 | }, 403 | "metadata": {}, 404 | "output_type": "display_data" 405 | }, 406 | { 407 | "name": "stdout", 408 | "output_type": "stream", 409 | "text": [ 410 | "\n", 411 | "Loaded model from epoch 19, step 29,970\n" 412 | ] 413 | }, 414 | { 415 | "data": { 416 | "application/vnd.jupyter.widget-view+json": { 417 | "model_id": "5002a2275fcd44ff99aff4b59c6114ce", 418 | "version_major": 2, 419 | "version_minor": 0 420 | }, 421 | "text/plain": [ 422 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 423 | ] 424 | }, 425 | "metadata": {}, 426 | "output_type": "display_data" 427 | }, 428 | { 429 | "name": "stdout", 430 | "output_type": "stream", 431 | "text": [ 432 | "\n", 433 | "Loaded model from epoch 19, step 29,970\n" 434 | ] 435 | }, 436 | { 437 | "data": { 438 | "application/vnd.jupyter.widget-view+json": { 439 | "model_id": "67f90fec0ee341a3a2972e9c976da328", 440 | "version_major": 2, 441 | "version_minor": 0 442 | }, 443 | "text/plain": [ 444 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 445 | ] 446 | }, 447 | "metadata": {}, 448 | "output_type": "display_data" 449 | }, 450 | { 451 | "name": "stdout", 452 | "output_type": "stream", 453 | "text": [ 454 | "\n", 455 | "Loaded model from epoch 15, step 23,310\n" 456 | ] 457 | }, 458 | { 459 | "data": { 460 | "application/vnd.jupyter.widget-view+json": { 461 | "model_id": "b6ede1286ad7472e80a6d5e3e399363f", 462 | "version_major": 2, 463 | "version_minor": 0 464 | }, 465 | "text/plain": [ 466 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 467 | ] 468 | }, 469 | "metadata": {}, 470 | "output_type": "display_data" 471 | }, 472 | { 473 | "name": "stdout", 474 | "output_type": "stream", 475 | "text": [ 476 | "\n", 477 | "Loaded model from epoch 13, step 26,628\n" 478 | ] 479 | }, 480 | { 481 | "data": { 482 | "application/vnd.jupyter.widget-view+json": { 483 | "model_id": "f340787dab164f568119205db30e31e6", 484 | "version_major": 2, 485 | "version_minor": 0 486 | }, 487 | "text/plain": [ 488 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 489 | ] 490 | }, 491 | "metadata": {}, 492 | "output_type": "display_data" 493 | }, 494 | { 495 | "name": "stdout", 496 | "output_type": "stream", 497 | "text": [ 498 | "\n", 499 | "Loaded model from epoch 18, step 37,723\n" 500 | ] 501 | }, 502 | { 503 | "data": { 504 | "application/vnd.jupyter.widget-view+json": { 505 | "model_id": "fa9480e811ef420eaa223c496b07d5a4", 506 | "version_major": 2, 507 | "version_minor": 0 508 | }, 509 | "text/plain": [ 510 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 511 | ] 512 | }, 513 | "metadata": {}, 514 | "output_type": "display_data" 515 | }, 516 | { 517 | "name": "stdout", 518 | "output_type": "stream", 519 | "text": [ 520 | "\n", 521 | "Saved predictions to 05_28_se101.h5\n", 522 | "Loaded model from epoch 27, step 43,290\n" 523 | ] 524 | }, 525 | { 526 | "data": { 527 | "application/vnd.jupyter.widget-view+json": { 528 | "model_id": "23a51a1e1a9b461bbb82c8cb90cf53cc", 529 | "version_major": 2, 530 | "version_minor": 0 531 | }, 532 | "text/plain": [ 533 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 534 | ] 535 | }, 536 | "metadata": {}, 537 | "output_type": "display_data" 538 | }, 539 | { 540 | "name": "stdout", 541 | "output_type": "stream", 542 | "text": [ 543 | "\n", 544 | "Loaded model from epoch 24, step 51,037\n" 545 | ] 546 | }, 547 | { 548 | "data": { 549 | "application/vnd.jupyter.widget-view+json": { 550 | "model_id": "aa8ce37dd5254ba3904f5d4ca1c4b140", 551 | "version_major": 2, 552 | "version_minor": 0 553 | }, 554 | "text/plain": [ 555 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 556 | ] 557 | }, 558 | "metadata": {}, 559 | "output_type": "display_data" 560 | }, 561 | { 562 | "name": "stdout", 563 | "output_type": "stream", 564 | "text": [ 565 | "\n", 566 | "Loaded model from epoch 28, step 60,360\n" 567 | ] 568 | }, 569 | { 570 | "data": { 571 | "application/vnd.jupyter.widget-view+json": { 572 | "model_id": "0a3934739e314591874d65f822a8d13c", 573 | "version_major": 2, 574 | "version_minor": 0 575 | }, 576 | "text/plain": [ 577 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 578 | ] 579 | }, 580 | "metadata": {}, 581 | "output_type": "display_data" 582 | }, 583 | { 584 | "name": "stdout", 585 | "output_type": "stream", 586 | "text": [ 587 | "\n", 588 | "Loaded model from epoch 30, step 64,351\n" 589 | ] 590 | }, 591 | { 592 | "data": { 593 | "application/vnd.jupyter.widget-view+json": { 594 | "model_id": "b5015ee9d6454dc6bebff7dbb2934eb3", 595 | "version_major": 2, 596 | "version_minor": 0 597 | }, 598 | "text/plain": [ 599 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 600 | ] 601 | }, 602 | "metadata": {}, 603 | "output_type": "display_data" 604 | }, 605 | { 606 | "name": "stdout", 607 | "output_type": "stream", 608 | "text": [ 609 | "\n", 610 | "Loaded model from epoch 28, step 59,913\n" 611 | ] 612 | }, 613 | { 614 | "data": { 615 | "application/vnd.jupyter.widget-view+json": { 616 | "model_id": "63b04635e0f34a6baa80022a83b8f0e7", 617 | "version_major": 2, 618 | "version_minor": 0 619 | }, 620 | "text/plain": [ 621 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 622 | ] 623 | }, 624 | "metadata": {}, 625 | "output_type": "display_data" 626 | }, 627 | { 628 | "name": "stdout", 629 | "output_type": "stream", 630 | "text": [ 631 | "\n", 632 | "Saved predictions to 05_30_inres2.h5\n" 633 | ] 634 | } 635 | ], 636 | "source": [ 637 | "import gc\n", 638 | "\n", 639 | "model = se_resnext101_32x4d(pretrained=None)\n", 640 | "model = create_model(model)\n", 641 | "dfs = []\n", 642 | "for i in range(10):\n", 643 | " df = test(model, loader, Path(f'../input/05-28-results/05_28_results/05_28_results/model_05_28_se101_{i+1}/'), multi=True)\n", 644 | " gc.collect()\n", 645 | " dfs.append(df)\n", 646 | "df = pd.concat(dfs)\n", 647 | "df = mean_df(df)\n", 648 | "out_path = '05_28_se101.h5'\n", 649 | "df.to_hdf(out_path, 'prob', index_label='id')\n", 650 | "print(f'Saved predictions to {out_path}')\n", 651 | "\n", 652 | "dfs = []\n", 653 | "model = inceptionresnetv2(pretrained=False)\n", 654 | "model = create_model(model)\n", 655 | "for i in range(5,10):\n", 656 | " df = test(model, loader, Path(f'../input/05-30-results/05_30_results/05_30_results/model_05_30_inres2_{i+1}/'), multi=True)\n", 657 | " gc.collect()\n", 658 | " dfs.append(df)\n", 659 | "df = pd.concat(dfs)\n", 660 | "df = mean_df(df)\n", 661 | "out_path = '05_30_inres2.h5'\n", 662 | "df.to_hdf(out_path, 'prob', index_label='id')\n", 663 | "print(f'Saved predictions to {out_path}')\n" 664 | ] 665 | }, 666 | { 667 | "cell_type": "code", 668 | "execution_count": 12, 669 | "metadata": {}, 670 | "outputs": [ 671 | { 672 | "name": "stdout", 673 | "output_type": "stream", 674 | "text": [ 675 | "Loaded model from epoch 13, step 39,948\n" 676 | ] 677 | }, 678 | { 679 | "data": { 680 | "application/vnd.jupyter.widget-view+json": { 681 | "model_id": "0750d5d307bd49b895da73a5d03c8496", 682 | "version_major": 2, 683 | "version_minor": 0 684 | }, 685 | "text/plain": [ 686 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 687 | ] 688 | }, 689 | "metadata": {}, 690 | "output_type": "display_data" 691 | }, 692 | { 693 | "name": "stdout", 694 | "output_type": "stream", 695 | "text": [ 696 | "\n", 697 | "Loaded model from epoch 15, step 46,606\n" 698 | ] 699 | }, 700 | { 701 | "data": { 702 | "application/vnd.jupyter.widget-view+json": { 703 | "model_id": "a0a7b6ce801441ee848be66b403194cc", 704 | "version_major": 2, 705 | "version_minor": 0 706 | }, 707 | "text/plain": [ 708 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 709 | ] 710 | }, 711 | "metadata": {}, 712 | "output_type": "display_data" 713 | }, 714 | { 715 | "name": "stdout", 716 | "output_type": "stream", 717 | "text": [ 718 | "\n", 719 | "Loaded model from epoch 16, step 49,920\n" 720 | ] 721 | }, 722 | { 723 | "data": { 724 | "application/vnd.jupyter.widget-view+json": { 725 | "model_id": "1f31bb6ad3184c22aa715868d312540f", 726 | "version_major": 2, 727 | "version_minor": 0 728 | }, 729 | "text/plain": [ 730 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 731 | ] 732 | }, 733 | "metadata": {}, 734 | "output_type": "display_data" 735 | }, 736 | { 737 | "name": "stdout", 738 | "output_type": "stream", 739 | "text": [ 740 | "\n", 741 | "Loaded model from epoch 18, step 56,593\n" 742 | ] 743 | }, 744 | { 745 | "data": { 746 | "application/vnd.jupyter.widget-view+json": { 747 | "model_id": "6a71c617d15f4be7b634f1f434362ef4", 748 | "version_major": 2, 749 | "version_minor": 0 750 | }, 751 | "text/plain": [ 752 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 753 | ] 754 | }, 755 | "metadata": {}, 756 | "output_type": "display_data" 757 | }, 758 | { 759 | "name": "stdout", 760 | "output_type": "stream", 761 | "text": [ 762 | "\n", 763 | "Loaded model from epoch 15, step 46,592\n" 764 | ] 765 | }, 766 | { 767 | "data": { 768 | "application/vnd.jupyter.widget-view+json": { 769 | "model_id": "edfef22095ee45e3b7406dd683a59750", 770 | "version_major": 2, 771 | "version_minor": 0 772 | }, 773 | "text/plain": [ 774 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 775 | ] 776 | }, 777 | "metadata": {}, 778 | "output_type": "display_data" 779 | }, 780 | { 781 | "name": "stdout", 782 | "output_type": "stream", 783 | "text": [ 784 | "\n", 785 | "Saved predictions to 06_01_se154.h5\n" 786 | ] 787 | } 788 | ], 789 | "source": [ 790 | "dfs = []\n", 791 | "model = senet154(pretrained=None)\n", 792 | "model = create_model(model)\n", 793 | "for i in range(5):\n", 794 | " df = test(model, loader, Path(f'../input/06-01-results/06_01_results/06_01_results/model_06_01_se154_{i+1}/'), multi=True)\n", 795 | " gc.collect()\n", 796 | " dfs.append(df)\n", 797 | "df = pd.concat(dfs)\n", 798 | "df = mean_df(df)\n", 799 | "out_path = '06_01_se154.h5'\n", 800 | "df.to_hdf(out_path, 'prob', index_label='id')\n", 801 | "print(f'Saved predictions to {out_path}')" 802 | ] 803 | }, 804 | { 805 | "cell_type": "code", 806 | "execution_count": 13, 807 | "metadata": {}, 808 | "outputs": [ 809 | { 810 | "name": "stdout", 811 | "output_type": "stream", 812 | "text": [ 813 | "Loaded model from epoch 17, step 71,143\n" 814 | ] 815 | }, 816 | { 817 | "data": { 818 | "application/vnd.jupyter.widget-view+json": { 819 | "model_id": "6a884ad2ccc44d6fa366ec22694e12ec", 820 | "version_major": 2, 821 | "version_minor": 0 822 | }, 823 | "text/plain": [ 824 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 825 | ] 826 | }, 827 | "metadata": {}, 828 | "output_type": "display_data" 829 | }, 830 | { 831 | "name": "stdout", 832 | "output_type": "stream", 833 | "text": [ 834 | "\n", 835 | "Loaded model from epoch 18, step 75,616\n" 836 | ] 837 | }, 838 | { 839 | "data": { 840 | "application/vnd.jupyter.widget-view+json": { 841 | "model_id": "073dc6eb572046638d8ed71efdc2e8d4", 842 | "version_major": 2, 843 | "version_minor": 0 844 | }, 845 | "text/plain": [ 846 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 847 | ] 848 | }, 849 | "metadata": {}, 850 | "output_type": "display_data" 851 | }, 852 | { 853 | "name": "stdout", 854 | "output_type": "stream", 855 | "text": [ 856 | "\n", 857 | "Loaded model from epoch 20, step 84,303\n" 858 | ] 859 | }, 860 | { 861 | "data": { 862 | "application/vnd.jupyter.widget-view+json": { 863 | "model_id": "9758ad8361e045a4864e8f9de9be0d88", 864 | "version_major": 2, 865 | "version_minor": 0 866 | }, 867 | "text/plain": [ 868 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 869 | ] 870 | }, 871 | "metadata": {}, 872 | "output_type": "display_data" 873 | }, 874 | { 875 | "name": "stdout", 876 | "output_type": "stream", 877 | "text": [ 878 | "\n", 879 | "Loaded model from epoch 19, step 79,902\n" 880 | ] 881 | }, 882 | { 883 | "data": { 884 | "application/vnd.jupyter.widget-view+json": { 885 | "model_id": "6cd00bb22f534683b383abc67aa70234", 886 | "version_major": 2, 887 | "version_minor": 0 888 | }, 889 | "text/plain": [ 890 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 891 | ] 892 | }, 893 | "metadata": {}, 894 | "output_type": "display_data" 895 | }, 896 | { 897 | "name": "stdout", 898 | "output_type": "stream", 899 | "text": [ 900 | "\n", 901 | "Loaded model from epoch 28, step 119,826\n" 902 | ] 903 | }, 904 | { 905 | "data": { 906 | "application/vnd.jupyter.widget-view+json": { 907 | "model_id": "da96955830d547c38be51ed4ebfc8343", 908 | "version_major": 2, 909 | "version_minor": 0 910 | }, 911 | "text/plain": [ 912 | "HBox(children=(IntProgress(value=0, max=233), HTML(value='')))" 913 | ] 914 | }, 915 | "metadata": {}, 916 | "output_type": "display_data" 917 | }, 918 | { 919 | "name": "stdout", 920 | "output_type": "stream", 921 | "text": [ 922 | "\n", 923 | "Saved predictions to 06_02_pnas.h5\n" 924 | ] 925 | } 926 | ], 927 | "source": [ 928 | "dfs = []\n", 929 | "model = pnasnet5large(pretrained=False)\n", 930 | "model = create_model(model)\n", 931 | "for i in range(5):\n", 932 | " df = test(model, loader, Path(f'../input/06-02-results/06_02_results/06_02_results/model_06_02_pnas_{i+1}/'), multi=True)\n", 933 | " gc.collect()\n", 934 | " dfs.append(df)\n", 935 | "df = pd.concat(dfs)\n", 936 | "df = mean_df(df)\n", 937 | "out_path = '06_02_pnas.h5'\n", 938 | "df.to_hdf(out_path, 'prob', index_label='id')\n", 939 | "print(f'Saved predictions to {out_path}')" 940 | ] 941 | }, 942 | { 943 | "cell_type": "code", 944 | "execution_count": 14, 945 | "metadata": {}, 946 | "outputs": [], 947 | "source": [ 948 | "dfs = []\n", 949 | "results = [f for f in os.listdir('./') if f.endswith('.h5')]\n", 950 | "\n", 951 | "for prediction in results:\n", 952 | " df = pd.read_hdf(prediction, index_col='id')\n", 953 | " df = df.reindex(test_df.id)\n", 954 | " dfs.append(df)\n", 955 | "df = pd.concat(dfs)\n", 956 | "df = mean_df(df)\n", 957 | "pred = df.values\n", 958 | "# def sigmoid(x): \n", 959 | "# return 1.0 / (1.0 + np.exp(-x))\n", 960 | "# pred = sigmoid(pred)\n", 961 | "df[:] = binarize_prediction(pred, threshold=args['threshold'], max_labels=args['max_labels'])\n", 962 | "df = df.apply(get_classes, axis=1)\n", 963 | "df.name = 'attribute_ids'\n", 964 | "df.to_csv(args['output'], header=True)" 965 | ] 966 | }, 967 | { 968 | "cell_type": "code", 969 | "execution_count": 15, 970 | "metadata": {}, 971 | "outputs": [ 972 | { 973 | "name": "stdout", 974 | "output_type": "stream", 975 | "text": [ 976 | "id,attribute_ids\r\n", 977 | "10023b2cc4ed5f68,195 223 289 343 344 369 587 766 1059\r\n", 978 | "100fbe75ed8fd887,93 231 1039\r\n", 979 | "101b627524a04f19,79 784 1037\r\n", 980 | "10234480c41284c6,13 147 725 738 776 813 830 1046\r\n", 981 | "1023b0e2636dcea8,147 322 584 813 954 1046 1092\r\n", 982 | "1039cd6cf85845c,13 405 903 1092\r\n", 983 | "103a5b3f83fbe88,194 671 813 1020 1057 1092\r\n", 984 | "10413aaae8d6a9a2,147 616 698 813 1046 1092\r\n", 985 | "10423822b93a65ab,51 121 483 738 813 1039\r\n" 986 | ] 987 | } 988 | ], 989 | "source": [ 990 | "!head submission.csv" 991 | ] 992 | }, 993 | { 994 | "cell_type": "code", 995 | "execution_count": 16, 996 | "metadata": {}, 997 | "outputs": [], 998 | "source": [] 999 | } 1000 | ], 1001 | "metadata": { 1002 | "kernelspec": { 1003 | "display_name": "Python 3", 1004 | "language": "python", 1005 | "name": "python3" 1006 | }, 1007 | "language_info": { 1008 | "codemirror_mode": { 1009 | "name": "ipython", 1010 | "version": 3 1011 | }, 1012 | "file_extension": ".py", 1013 | "mimetype": "text/x-python", 1014 | "name": "python", 1015 | "nbconvert_exporter": "python", 1016 | "pygments_lexer": "ipython3", 1017 | "version": "3.6.3" 1018 | } 1019 | }, 1020 | "nbformat": 4, 1021 | "nbformat_minor": 1 1022 | } 1023 | --------------------------------------------------------------------------------