This repository has been archived by the owner on Sep 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* TGS salt example * updates * updates
- Loading branch information
Showing
14 changed files
with
2,482 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
## 33rd place solution code for Kaggle [TGS Salt Identification Chanllenge](https://www.kaggle.com/c/tgs-salt-identification-challenge) | ||
|
||
This example shows how to enable AutoML for competition code by running it on NNI without any code change. | ||
To run this code on NNI, firstly you need to run it standalone, then configure the config.yml and: | ||
``` | ||
nnictl create --config config.yml | ||
``` | ||
|
||
This code can still run standalone, the code is for reference, it requires at least one week effort to reproduce the competition result. | ||
|
||
[Solution summary](https://www.kaggle.com/c/tgs-salt-identification-challenge/discussion/69593) | ||
|
||
Preparation: | ||
|
||
Download competition data, run preprocess.py to prepare training data. | ||
|
||
Stage 1: | ||
|
||
Train fold 0-3 for 100 epochs, for each fold, train 3 models: | ||
``` | ||
python3 train.py --ifolds 0 --epochs 100 --model_name UNetResNetV4 | ||
python3 train.py --ifolds 0 --epochs 100 --model_name UNetResNetV5 --layers 50 | ||
python3 train.py --ifolds 0 --epochs 100 --model_name UNetResNetV6 | ||
``` | ||
|
||
Stage 2: | ||
|
||
Fine tune stage 1 models for 300 epochs with cosine annealing lr scheduler: | ||
|
||
``` | ||
python3 train.py --ifolds 0 --epochs 300 --lrs cosine --lr 0.001 --min_lr 0.0001 --model_name UNetResNetV4 | ||
``` | ||
|
||
Stage 3: | ||
|
||
Fine tune Stage 2 models with depths channel: | ||
|
||
``` | ||
python3 train.py --ifolds 0 --epochs 300 --lrs cosine --lr 0.001 --min_lr 0.0001 --model_name UNetResNetV4 --depths | ||
``` | ||
|
||
Stage 4: | ||
|
||
Make prediction for each model, then ensemble the result to generate peasdo labels. | ||
|
||
Stage 5: | ||
|
||
Fine tune stage 3 models with pseudo labels | ||
|
||
``` | ||
python3 train.py --ifolds 0 --epochs 300 --lrs cosine --lr 0.001 --min_lr 0.0001 --model_name UNetResNetV4 --depths --pseudo | ||
``` | ||
|
||
Stage 6: | ||
Ensemble all stage 3 and stage 5 models. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
# Copyright (c) Microsoft Corporation | ||
# All rights reserved. | ||
# | ||
# MIT License | ||
# | ||
# Permission is hereby granted, free of charge, | ||
# to any person obtaining a copy of this software and associated | ||
# documentation files (the "Software"), | ||
# to deal in the Software without restriction, including without limitation | ||
# the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
# and/or sell copies of the Software, and | ||
# to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
# The above copyright notice and this permission notice shall be included | ||
# in all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
import os | ||
import cv2 | ||
import numpy as np | ||
import random | ||
import torchvision.transforms.functional as F | ||
from torchvision.transforms import RandomResizedCrop, ColorJitter, RandomAffine | ||
import PIL | ||
from PIL import Image | ||
import collections | ||
|
||
import settings | ||
|
||
|
||
class RandomHFlipWithMask(object): | ||
def __init__(self, p=0.5): | ||
self.p = p | ||
def __call__(self, *imgs): | ||
if random.random() < self.p: | ||
return map(F.hflip, imgs) | ||
else: | ||
return imgs | ||
|
||
class RandomVFlipWithMask(object): | ||
def __init__(self, p=0.5): | ||
self.p = p | ||
def __call__(self, *imgs): | ||
if random.random() < self.p: | ||
return map(F.vflip, imgs) | ||
else: | ||
return imgs | ||
|
||
class RandomResizedCropWithMask(RandomResizedCrop): | ||
def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Image.BILINEAR): | ||
super(RandomResizedCropWithMask, self).__init__(size, scale, ratio, interpolation) | ||
def __call__(self, *imgs): | ||
i, j, h, w = self.get_params(imgs[0], self.scale, self.ratio) | ||
#print(i,j,h,w) | ||
return map(lambda x: F.resized_crop(x, i, j, h, w, self.size, self.interpolation), imgs) | ||
|
||
class RandomAffineWithMask(RandomAffine): | ||
def __init__(self, degrees, translate=None, scale=None, shear=None, resample='edge'): | ||
super(RandomAffineWithMask, self).__init__(degrees, translate, scale, shear, resample) | ||
def __call__(self, *imgs): | ||
ret = self.get_params(self.degrees, self.translate, self.scale, self.shear, imgs[0].size) | ||
w, h = imgs[0].size | ||
imgs = map(lambda x: F.pad(x, w//2, 0, self.resample), imgs) | ||
imgs = map(lambda x: F.affine(x, *ret, resample=0), imgs) | ||
imgs = map(lambda x: F.center_crop(x, (w, h)), imgs) | ||
return imgs | ||
|
||
class RandomRotateWithMask(object): | ||
def __init__(self, degrees, pad_mode='reflect', expand=False, center=None): | ||
self.pad_mode = pad_mode | ||
self.expand = expand | ||
self.center = center | ||
self.degrees = degrees | ||
|
||
def __call__(self, *imgs): | ||
angle = self.get_angle() | ||
if angle == int(angle) and angle % 90 == 0: | ||
if angle == 0: | ||
return imgs | ||
else: | ||
#print(imgs) | ||
return map(lambda x: F.rotate(x, angle, False, False, None), imgs) | ||
else: | ||
return map(lambda x: self._pad_rotate(x, angle), imgs) | ||
|
||
def get_angle(self): | ||
if isinstance(self.degrees, collections.Sequence): | ||
index = int(random.random() * len(self.degrees)) | ||
return self.degrees[index] | ||
else: | ||
return random.uniform(-self.degrees, self.degrees) | ||
|
||
def _pad_rotate(self, img, angle): | ||
w, h = img.size | ||
img = F.pad(img, w//2, 0, self.pad_mode) | ||
img = F.rotate(img, angle, False, self.expand, self.center) | ||
img = F.center_crop(img, (w, h)) | ||
return img | ||
|
||
class CropWithMask(object): | ||
def __init__(self, i, j, h, w): | ||
self.i = i | ||
self.j = j | ||
self.h = h | ||
self.w = w | ||
def __call__(self, *imgs): | ||
return map(lambda x: F.crop(x, self.i, self.j, self.h, self.w), imgs) | ||
|
||
class PadWithMask(object): | ||
def __init__(self, padding, padding_mode): | ||
self.padding = padding | ||
self.padding_mode = padding_mode | ||
def __call__(self, *imgs): | ||
return map(lambda x: F.pad(x, self.padding, padding_mode=self.padding_mode), imgs) | ||
|
||
class Compose(object): | ||
def __init__(self, transforms): | ||
self.transforms = transforms | ||
|
||
def __call__(self, *imgs): | ||
for t in self.transforms: | ||
imgs = t(*imgs) | ||
return imgs | ||
def __repr__(self): | ||
format_string = self.__class__.__name__ + '(' | ||
for t in self.transforms: | ||
format_string += '\n' | ||
format_string += ' {0}'.format(t) | ||
format_string += '\n)' | ||
return format_string | ||
|
||
def get_img_mask_augments(train_mode, pad_mode): | ||
if pad_mode == 'resize': | ||
img_mask_aug_train = Compose([ | ||
RandomHFlipWithMask(), | ||
RandomAffineWithMask(10, translate=(0.1, 0.1), scale=(0.9, 1.1), shear=None) | ||
]) | ||
img_mask_aug_val = None | ||
else: | ||
img_mask_aug_train = Compose([ | ||
PadWithMask((28, 28), padding_mode=pad_mode), | ||
RandomHFlipWithMask(), | ||
RandomAffineWithMask(10, translate=(0.1, 0.1), scale=(0.9, 1.1), shear=None), | ||
RandomResizedCropWithMask(128, scale=(1., 1.), ratio=(1., 1.)) | ||
]) | ||
img_mask_aug_val = PadWithMask((13, 14), padding_mode=pad_mode) | ||
|
||
return img_mask_aug_train, img_mask_aug_val | ||
|
||
|
||
def test_transform(): | ||
img_id = '0b73b427d1.png' | ||
img = Image.open(os.path.join(settings.TRAIN_IMG_DIR, img_id)).convert('RGB') | ||
mask = Image.open(os.path.join(settings.TRAIN_MASK_DIR, img_id)).convert('L').point(lambda x: 0 if x < 128 else 1, 'L') | ||
|
||
img_id = '0a1ea1af4.jpg' | ||
img = Image.open(os.path.join(r'D:\data\ship\train_v2', img_id)).convert('RGB') | ||
mask = Image.open(os.path.join(r'D:\data\ship\train_masks', img_id)).convert('L').point(lambda x: 0 if x < 128 else 1, 'L') | ||
|
||
trans = Compose([ | ||
RandomHFlipWithMask(), | ||
RandomVFlipWithMask(), | ||
RandomRotateWithMask([0, 90, 180, 270]), | ||
#RandomRotateWithMask(15), | ||
RandomResizedCropWithMask(768, scale=(0.81, 1)) | ||
]) | ||
|
||
trans2 = RandomAffineWithMask(45, (0.2,0.2), (0.9, 1.1)) | ||
trans3, trans4 = get_img_mask_augments(True, 'edge') | ||
|
||
img, mask = trans4(img, mask) | ||
|
||
img.show() | ||
mask.point(lambda x: x*255).show() | ||
|
||
def test_color_trans(): | ||
img_id = '00abc623a.jpg' | ||
img = Image.open(os.path.join(settings.TRAIN_IMG_DIR, img_id)).convert('RGB') | ||
trans = ColorJitter(0.1, 0.1, 0.1, 0.1) | ||
|
||
img2 = trans(img) | ||
img.show() | ||
img2.show() | ||
|
||
|
||
class TTATransform(object): | ||
def __init__(self, index): | ||
self.index = index | ||
def __call__(self, img): | ||
trans = { | ||
0: lambda x: x, | ||
1: lambda x: F.hflip(x), | ||
2: lambda x: F.vflip(x), | ||
3: lambda x: F.vflip(F.hflip(x)), | ||
4: lambda x: F.rotate(x, 90, False, False), | ||
5: lambda x: F.hflip(F.rotate(x, 90, False, False)), | ||
6: lambda x: F.vflip(F.rotate(x, 90, False, False)), | ||
7: lambda x: F.vflip(F.hflip(F.rotate(x, 90, False, False))) | ||
} | ||
return trans[self.index](img) | ||
|
||
# i is tta index, 0: no change, 1: horizon flip, 2: vertical flip, 3: do both | ||
def tta_back_mask_np(img, index): | ||
print(img.shape) | ||
trans = { | ||
0: lambda x: x, | ||
1: lambda x: np.flip(x, 2), | ||
2: lambda x: np.flip(x, 1), | ||
3: lambda x: np.flip(np.flip(x, 2), 1), | ||
4: lambda x: np.rot90(x, 3, axes=(1,2)), | ||
5: lambda x: np.rot90(np.flip(x, 2), 3, axes=(1,2)), | ||
6: lambda x: np.rot90(np.flip(x, 1), 3, axes=(1,2)), | ||
7: lambda x: np.rot90(np.flip(np.flip(x,2), 1), 3, axes=(1,2)) | ||
} | ||
|
||
return trans[index](img) | ||
|
||
def test_tta(): | ||
img_f = os.path.join(settings.TEST_IMG_DIR, '0c2637aa9.jpg') | ||
img = Image.open(img_f) | ||
img = img.convert('RGB') | ||
|
||
tta_index = 7 | ||
trans1 = TTATransform(tta_index) | ||
img = trans1(img) | ||
#img.show() | ||
|
||
img_np = np.array(img) | ||
img_np = np.expand_dims(img_np, 0) | ||
print(img_np.shape) | ||
img_np = tta_back_mask_np(img_np, tta_index) | ||
img_np = np.reshape(img_np, (768, 768, 3)) | ||
img_back = F.to_pil_image(img_np) | ||
img_back.show() | ||
|
||
if __name__ == '__main__': | ||
test_transform() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
authorName: default | ||
experimentName: example_tgs | ||
trialConcurrency: 2 | ||
maxExecDuration: 10h | ||
maxTrialNum: 10 | ||
#choice: local, remote, pai | ||
trainingServicePlatform: local | ||
#choice: true, false | ||
useAnnotation: true | ||
tuner: | ||
#choice: TPE, Random, Anneal, Evolution, BatchTuner | ||
#SMAC (SMAC should be installed through nnictl) | ||
builtinTunerName: TPE | ||
classArgs: | ||
#choice: maximize, minimize | ||
optimize_mode: maximize | ||
trial: | ||
command: python3 train.py | ||
codeDir: . | ||
gpuNum: 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Copyright (c) Microsoft Corporation | ||
# All rights reserved. | ||
# | ||
# MIT License | ||
# | ||
# Permission is hereby granted, free of charge, | ||
# to any person obtaining a copy of this software and associated | ||
# documentation files (the "Software"), | ||
# to deal in the Software without restriction, including without limitation | ||
# the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
# and/or sell copies of the Software, and | ||
# to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
# The above copyright notice and this permission notice shall be included | ||
# in all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
import torch | ||
import torch.nn as nn | ||
import torch.nn.functional as F | ||
|
||
class FocalLoss2d(nn.Module): | ||
|
||
def __init__(self, gamma=2, size_average=True): | ||
super(FocalLoss2d, self).__init__() | ||
self.gamma = gamma | ||
self.size_average = size_average | ||
|
||
|
||
def forward(self, logit, target, class_weight=None, type='sigmoid'): | ||
target = target.view(-1, 1).long() | ||
|
||
if type=='sigmoid': | ||
if class_weight is None: | ||
class_weight = [1]*2 #[0.5, 0.5] | ||
|
||
prob = torch.sigmoid(logit) | ||
prob = prob.view(-1, 1) | ||
prob = torch.cat((1-prob, prob), 1) | ||
select = torch.FloatTensor(len(prob), 2).zero_().cuda() | ||
select.scatter_(1, target, 1.) | ||
|
||
elif type=='softmax': | ||
B,C,H,W = logit.size() | ||
if class_weight is None: | ||
class_weight =[1]*C #[1/C]*C | ||
|
||
logit = logit.permute(0, 2, 3, 1).contiguous().view(-1, C) | ||
prob = F.softmax(logit,1) | ||
select = torch.FloatTensor(len(prob), C).zero_().cuda() | ||
select.scatter_(1, target, 1.) | ||
|
||
class_weight = torch.FloatTensor(class_weight).cuda().view(-1,1) | ||
class_weight = torch.gather(class_weight, 0, target) | ||
|
||
prob = (prob*select).sum(1).view(-1,1) | ||
prob = torch.clamp(prob,1e-8,1-1e-8) | ||
batch_loss = - class_weight *(torch.pow((1-prob), self.gamma))*prob.log() | ||
|
||
if self.size_average: | ||
loss = batch_loss.mean() | ||
else: | ||
loss = batch_loss | ||
|
||
return loss | ||
|
||
|
||
if __name__ == '__main__': | ||
L = FocalLoss2d() | ||
out = torch.randn(2, 3, 3).cuda() | ||
target = (torch.sigmoid(out) > 0.5).float() | ||
loss = L(out, target) | ||
print(loss) |
Oops, something went wrong.