From 4ef2812dbb9ba6d6a0c0c6f086749940695b8d16 Mon Sep 17 00:00:00 2001 From: dIronmanb Date: Sun, 3 Apr 2022 11:08:15 +0000 Subject: [PATCH] chapter05 --- .gitignore | 4 + Chapter05/README.md | 27 ++++ Chapter05/config/config.yaml | 39 ++++++ Chapter05/src/dataloader/dataloader.py | 66 +++++++++ Chapter05/src/dataloader/preprocessing.py | 31 +++++ Chapter05/src/metrics/metrics.py | 41 ++++++ Chapter05/src/models/model.py | 153 +++++++++++++++++++++ Chapter05/src/optimizers/loss_function.py | 13 ++ Chapter05/src/optimizers/optimizer.py | 19 +++ Chapter05/src/optimizers/scheduler.py | 29 ++++ Chapter05/train.py | 159 ++++++++++++++++++++++ 11 files changed, 581 insertions(+) create mode 100644 Chapter05/README.md create mode 100644 Chapter05/config/config.yaml create mode 100644 Chapter05/src/dataloader/dataloader.py create mode 100644 Chapter05/src/dataloader/preprocessing.py create mode 100644 Chapter05/src/metrics/metrics.py create mode 100644 Chapter05/src/models/model.py create mode 100644 Chapter05/src/optimizers/loss_function.py create mode 100644 Chapter05/src/optimizers/optimizer.py create mode 100644 Chapter05/src/optimizers/scheduler.py create mode 100644 Chapter05/train.py diff --git a/.gitignore b/.gitignore index afed1e7..faafd0e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,8 @@ Chapter04/dataset/ Chapter04/final_result/ Chapter04/runs/ Chapter04/save/ + +Chapter05/save/ +Chapter05/runs/ + *.pyc \ No newline at end of file diff --git a/Chapter05/README.md b/Chapter05/README.md new file mode 100644 index 0000000..1e79d64 --- /dev/null +++ b/Chapter05/README.md @@ -0,0 +1,27 @@ +# Convolution 기법들 + +## 그전에... +2차원 컨볼루션 세 가지 문제점이 존재한다. +- Expensive Cost +- Dead Channels +- Low Correlation between channels + +또한 영상 내의 객체에 대한 정확한 판단을 하기 위해서는 **Contextual Information**이 중요하다. Object Detection이나 Object Segment에서는 충분한 Contextual Information을 확보하기 위해 상대적으로 넓은 Receptive Field를 고려할 필요가 있다. + +단순히 더 많은 convolution layer를 많이 쌓거나 kernel size를 확장하는 방법은 연산량을 크게 증가하므로 적절하지 않다. + +**연산량을 경량화하면서 정보 손실이 일어나지 않게끔 유의미한 정보만을 추출하기 위해 다양한 convolution 기법들이 등장하였다.** + +## Convolution 기법들 +### 1. Convlolution +### 2. Dilated Convolutions +### 3. Transpose Convolutions +### 4. Separable Convolution +### 5. Depthwise Convolution +### 6. Depthwise Separable Convolution +### 7. Pointwise Convolution +### 8. Grouped Convolution +### 9. Deformable Convolution + + + diff --git a/Chapter05/config/config.yaml b/Chapter05/config/config.yaml new file mode 100644 index 0000000..e99a905 --- /dev/null +++ b/Chapter05/config/config.yaml @@ -0,0 +1,39 @@ + + + +--- + use_cuda: true + epoch: 100 + train_batch_size: 64 + test_batch_size: 64 + learning_rate: 0.001 + dataset_name: "CIFAR10" + is_trained: false + + # train_dataset + # test_dataset + # 은 CIFAR10 Dataloader에서만 작업할 것이므로 pass + + num_workers: 0 + train_dataset_shuffle: True + test_dataset_shuffle: False + data_loader_name: 'data_load_normalizing_and_agumentation' + + model: "VGG11_Dilated" + loss: "CrossEntropyLoss" + optimizer: "Adam" + scheduler: 'ExponentialLR' + momentum: 0.9 + weight_decay : 0.01 + metrics: "accuracy_score" + + + VGG_types: { + 'VGG11' : [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], + 'VGG11_Dilated': ['D64','D128', 'D256', 'D256', 'D512', 'D512', 'D512', 'D512'], + + 'VGG13' : [64,64, 'M', 128, 128, 'M', 256, 256, 'M', 512,512, 'M', 512,512,'M'], + 'VGG16' : [64,64, 'M', 128, 128, 'M', 256, 256,256, 'M', 512,512,512, 'M',512,512,512,'M'], + 'VGG19' : [64,64, 'M', 128, 128, 'M', 256, 256,256,256, 'M', 512,512,512,512, 'M',512,512,512,512,'M'] + + } \ No newline at end of file diff --git a/Chapter05/src/dataloader/dataloader.py b/Chapter05/src/dataloader/dataloader.py new file mode 100644 index 0000000..a0bb2c8 --- /dev/null +++ b/Chapter05/src/dataloader/dataloader.py @@ -0,0 +1,66 @@ +from torch.utils.data import DataLoader +from torchvision import transforms, datasets +import numpy as np + +# datasets.ImageFolder + +def data_load(config): + name = config['data_loader_name'] + if name == 'data_load_only_normalizing': + data_transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean = [0.4913, 0.4821, 0.4465], + std = [0.2470, 0.2434, 0.2615]) + ]) + elif name == 'data_load_normalizing_and_agumentation': + + data_transform = transforms.Compose([ + transforms.RandomHorizontalFlip(p=0.5), # 이미지를 좌우반전 + transforms.RandomRotation(10), + # Image (가로, 세로, 채널) + transforms.ToTensor(), # (채널, 세로, 가로) + transforms.Normalize(mean = [0.4913, 0.4821, 0.4465], std = [0.2470, 0.2434, 0.2615]) # tensor의 데이터 수치를 정규화한다. + ]) + + elif name == 'data_load_rainbow': + data_transform = transforms.Compose([ + transforms.RandomHorizontalFlip(p=0.5), # 이미지를 좌우반전 + transforms.RandomRotation(10), + transforms.ToTensor(), + transforms.Normalize(mean = [0.4913, 0.4821, 0.4465], std = [0.2470, 0.2434, 0.2615]) # tensor의 데이터 수치를 정규화한다. + # transforms.Normalize((0.5), (0.5))) -> -1 ~ 1 사이의 값으로 normalized # output[channel] = (input[channel] - mean[channel]) / std[channel] + ]) + + else: + print("There was no name in DataLoader_Name") + + + train_set = datasets.CIFAR10(root = '/data/Github_Management/StartDeepLearningWithPytorch/Chapter03/cifar10', + train = True, + download = True, # If true, downloads the dataset from the internet and puts it in root directory. If dataset is already downloaded, it is not downloaded again. + transform = data_transform) + + test_set = datasets.CIFAR10(root = '/data/Github_Management/StartDeepLearningWithPytorch/Chapter03/cifar10', + train = False, + download = True, + transform = data_transform + ) + + train_loader = DataLoader(train_set, + batch_size= 64, #['train_batch_size'], + num_workers = config['num_workers'], + shuffle = True)#config['train_dataset_shuffle']) + + test_loader = DataLoader(test_set, + batch_size = 64, #config['test_batch_size'], + num_workers = config['num_workers'], + shuffle = False) #config['test_dataset_shuffle']) + + + classes = ('plane', 'car', 'bird', 'cat', 'deer', + 'dog', 'frog', 'horse', 'ship', 'truck') + + return train_loader, test_loader, classes # train + + + \ No newline at end of file diff --git a/Chapter05/src/dataloader/preprocessing.py b/Chapter05/src/dataloader/preprocessing.py new file mode 100644 index 0000000..29bc939 --- /dev/null +++ b/Chapter05/src/dataloader/preprocessing.py @@ -0,0 +1,31 @@ +import os +from torchvision.datasets import ImageFolder +from torchvision import transforms + +train_dir = '/data/Github_Management/StartDeepLearningWithPytorch/Chapter04/dataset/train/' +val_dir = '/data/Github_Management/StartDeepLearningWithPytorch/Chapter04/dataset/validation' +test_dir = '/data/Github_Management/StartDeepLearningWithPytorch/Chapter04/dataset/test' + +# 과일 이름을 담은 리스트 +classes = os.listdir(train_dir) +# print(classes) + +train_transform = transforms.Compose([ + transforms.RandomRotation(10), # +/- 10 degrees + transforms.RandomHorizontalFlip(), # reverse 50% of images -> 위아로 filp은 X + transforms.Resize(40), # (40, 40) + transforms.CenterCrop(40), #(40, 40) + transforms.ToTensor(), # 텐서로 변환 + transforms.Normalize(mean = [0.5, 0.5, 0.5], \ + std = [0.5, 0.5, 0.5]) # mu와 std는 나중에 구해보기 + ]) + +train_set = ImageFolder(train_dir, transform = train_transform) +valid_set = ImageFolder(val_dir, transform = train_transform) +test_set = ImageFolder(test_dir, transform = train_transform) + +# Train, Valid, Test +num_data = [len(train_set), len(valid_set), len(test_set)] +print(num_data) +print(type(train_set)) +print(type(valid_set)) diff --git a/Chapter05/src/metrics/metrics.py b/Chapter05/src/metrics/metrics.py new file mode 100644 index 0000000..0d425c3 --- /dev/null +++ b/Chapter05/src/metrics/metrics.py @@ -0,0 +1,41 @@ +''' + 정확도 평가 지표 정보 + accuracy: + recall: + precision: + f1: + confusion_matrix: + + + prdicted set과 target set에 대해 판별(인자는 list..???) +''' + + +from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, confusion_matrix + + +def get_metrics(targets, predicted, config): + name = config['metrics'] + if name == "accuracy_score": + return accuracy_score(targets, predicted) + elif name == "recall_score": + return recall_score(targets, predicted) + elif name == "precision_score": + return precision_score(targets, predicted) + elif name == "f1_score": + return f1_score(targets, predicted) + elif name == "confusion_matrix": + return confusion_matrix + else: + print("There is no metrics in metrics_name") + + +def get_confusion_metric(targets, predicted): + return confusion_matrix(targets, predicted) + +def get_recall_score(targets, predicted): + return recall_score(targets, predicted) + +def get_precision_score(targets, predicted): + return precision_score(targets, predicted) + diff --git a/Chapter05/src/models/model.py b/Chapter05/src/models/model.py new file mode 100644 index 0000000..e39943a --- /dev/null +++ b/Chapter05/src/models/model.py @@ -0,0 +1,153 @@ +''' + config file 수정하기 + + model은 VGG11를 기반으로 제작 + convolution 자리에 + 1. Convolution + 2. Dliated Convolution + 3. Transposed Convolution + 4. Separable Convolution + 5. Depthwise Convolution + 6. Depthwise Separable Convolution + 7. Pointwise Convolution + 8. Grouped Convolution + 9. Deformable Convolution + +''' + +import torch +import torch.nn as nn +import yaml + +def get_VGG(config): + name = config['model'] + model_list = config['VGG_types'] + + if name == 'VGG11': + return VGGnet(model_list[name]) + elif name == 'VGG13': + return VGGnet(model_list[name]) + elif name == 'VGG16': + return VGGnet(model_list[name]) + elif name == 'VGG19': + return VGGnet(model_list[name]) + elif name == 'VGG11_Dilated': + return VGGnet(model_list[name]) + # ㅇㄷ + + + else: + print("There is no name in models") + + + +class VGGnet(nn.Module): + + def __init__(self, model, in_channels = 3, num_classes = 10, init_weights = True): + super(VGGnet, self).__init__() + self.in_channels = in_channels + + self.conv_layers = self.create_conv_layers(model) + + self.fcs = nn.Sequential( + + nn.Linear( 512 * 16 * 16, 4096), + + nn.ReLU(), + nn.Dropout(p = 0.5), + nn.Linear(4096, 4096), + nn.ReLU(), + nn.Dropout(p = 0.5), + nn.Linear(4096, num_classes) + ) + + if init_weights: + self._initialize_weights() + + + def forward(self, x): + + x = self.conv_layers(x) +# x = x.flatten() + x = x.view(-1, 512 * 16 * 16) + + x = self.fcs(x) + return x + + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode = 'fan_out', nonlinearity='relu') + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.constant_(m.bias, 0) + + def create_conv_layers(self, architecture): + layers = [] + in_channels = self.in_channels + + for x in architecture: + if type(x) == int: + out_channels = x + layers += [nn.Conv2d(in_channels = in_channels, out_channels = out_channels, + kernel_size = (3,3), stride = (1,1), padding = (1,1)), + nn.BatchNorm2d(x), + nn.ReLU()] + in_channels = x + + # if type(x) == int: 대신에 if name == "Convolution" 또는 name == "Deliated Convolution" + + elif 'D' in x: # Dilated 실행 + num = int(x[1:]) + out_channels = num + layers += [nn.Conv2d(in_channels = in_channels, out_channels = out_channels, + kernel_size = (3,3), stride = (1,1), padding = (1,1), dilation = 2), + nn.BatchNorm2d(num), + nn.ReLU()] + in_channels = num + + elif x == 'M': + layers += [nn.MaxPool2d(kernel_size = (2,2), stride = (2,2))] + + + return nn.Sequential(*layers) + + + + + + + + + + +# Open config file -> quick test +def open_config_file(): + with open("/data/Github_Management/StartDeepLearningWithPytorch/Chapter05/config/config.yaml", 'r', encoding = 'utf-8') as stream: + try: + config = yaml.safe_load(stream) # return into Dict + except yaml.YAMLError as exc: + print(exc) + return config['VGG_types'] + + +if __name__ == '__main__': + print('Quick Test...') + + models = open_config_file() + model = VGGnet(models['VGG11_Dilated']) + print(model) + + input = torch.zeros([1,3,32,32], dtype = torch.float32) + # model = VGG_19(32, 3) + output = model(input) + + print('input_shape: {}, output_size: {}' + .format(input.shape, output.shape)) + \ No newline at end of file diff --git a/Chapter05/src/optimizers/loss_function.py b/Chapter05/src/optimizers/loss_function.py new file mode 100644 index 0000000..53518ba --- /dev/null +++ b/Chapter05/src/optimizers/loss_function.py @@ -0,0 +1,13 @@ +import torch.nn as nn + +def get_loss(config, params = None): + name = config['loss'] + if name == 'MSELoss': + return nn.MSELoss() + elif name == 'CrossEntropyLoss': + return nn.CrossEntropyLoss() + elif name == 'Softmax': + return nn.Softmax() + else: + print("There is no name in loss") + diff --git a/Chapter05/src/optimizers/optimizer.py b/Chapter05/src/optimizers/optimizer.py new file mode 100644 index 0000000..cc9a44a --- /dev/null +++ b/Chapter05/src/optimizers/optimizer.py @@ -0,0 +1,19 @@ +import torch.optim as optimizer + + +def get_optimizer(model_paramter, config): + name = config['optimizer'] + + if name == 'Adam': + return optimizer.Adam(params = model_paramter, + lr = config['learning_rate'], + weight_decay = config['weight_decay']) + + elif name == 'SGD': + return optimizer.SGD(params = model_paramter, + lr = config['learning_rate'], + momentum = config['momentum'], + weight_decay = config['weight_decay']) + + else: + print("There is no name in optimizer") \ No newline at end of file diff --git a/Chapter05/src/optimizers/scheduler.py b/Chapter05/src/optimizers/scheduler.py new file mode 100644 index 0000000..c768824 --- /dev/null +++ b/Chapter05/src/optimizers/scheduler.py @@ -0,0 +1,29 @@ +import torch.optim.lr_scheduler as scheduler + +def get_scheduler(optimizer, config): + name = config['scheduler'] + + if name == 'LamdbaLR': + return scheduler.LambdaLR(optimizer = optimizer, + lr_lambda = lambda epoch : 0.95 ** epoch ) + elif name == 'StepLR': + return scheduler.StepLR(optimizer = optimizer, + step_size = 10, + gamma = 0.5) + elif name == 'MultiStepLR': + return scheduler.MultiStepLR(optimizer = optimizer, + milestones = [30, 80], + gamma = 0.5) + elif name == 'ExponentialLR': + return scheduler.ExponentialLR(optimizer = optimizer, + gamma = 0.5) + elif name == 'CosineAnnealingLR': + return scheduler.CosineAnnealingWarmRestarts(optimizer = optimizer, + T_max = 50, + eta_min = 0) + else: + print("There was no name in scheduler") + + + + \ No newline at end of file diff --git a/Chapter05/train.py b/Chapter05/train.py new file mode 100644 index 0000000..3d474cb --- /dev/null +++ b/Chapter05/train.py @@ -0,0 +1,159 @@ +import os +import yaml +import torch.nn.functional as F +import torch +import numpy as np +import datetime +from pytz import timezone + +from torch.utils.tensorboard import SummaryWriter +import torchvision + +import src.optimizers.loss_function as loss_function +import src.optimizers.scheduler as scheduler +import src.optimizers.optimizer as optim +import src.dataloader.dataloader as dataloader +import src.models.model as model +import src.metrics.metrics as metric # 정확도 판단 +import test + + + +current_time = datetime.datetime.now(timezone('Asia/Seoul')).strftime('%Y-%m-%d_%H:%M:%S') + +# Open config file +with open("config/config.yaml", 'r', encoding = 'utf-8') as stream: + try: + config = yaml.safe_load(stream) # return into Dict + except yaml.YAMLError as exc: + print(exc) + +print(config) + +# Load data +train_data, test_data, classes = dataloader.data_load(config) +print(classes) + + +# Use cuda +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +# make model +vgg = model.get_VGG(config).to(DEVICE) +if(config['is_trained']): + vgg.load_state_dict(torch.load('save/VGG11/2022-03-24_11:02:16/saved_weights_198')) + +# print model +print(vgg) + +# set epoch +epoch = config["epoch"] + +# set loss and optimizer +criterion = loss_function.get_loss(config) +optimizer = optim.get_optimizer(vgg.parameters(), config) +schedule = scheduler.get_scheduler(optimizer, config) + + +# convert vgg to train_mode +vgg.train() + +# show images in tensorboard (as batch size) +writer = SummaryWriter('runs/' + config['model'] + '_' + current_time) +dataiter = iter(train_data) +images, labels = dataiter.next() +img_grid = torchvision.utils.make_grid(images) +writer.add_image('64_CIFAR_images', img_grid) + + +# train model +running_loss_history = [] +running_correct_history = [] +validation_running_loss_history = [] +validation_running_correct_history = [] + +for i in range(1, epoch + 1): + running_loss = 0.0 + running_correct = 0.0 + validation_running_loss = 0.0 + validation_running_correct = 0.0 + + total_loss = 0.0 # 배치에서의 loss + total_length = 0.0 # 현재 길이 + for batch_idx, (data, targets) in enumerate(train_data): + + data = data.to(DEVICE) + targets = targets.to(DEVICE, dtype = torch.int64) + outputs = vgg(data) + los = criterion(outputs, targets) + + optimizer.zero_grad() + los.backward() + optimizer.step() + + _ , preds = torch.max(outputs, 1) + + running_correct += torch.sum(preds == targets.data) + running_loss += los.item() + + total_loss += los.item() * len(data) + total_length += len(data) + + if batch_idx % 100 == 0: + writer.add_scalar("Loss/train_step",los.item(), batch_idx + len(data) * (i)) + print("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}".format( + i + 1, batch_idx * len(data), len(train_data.dataset), 100. * batch_idx / len(train_data), los.item() + )) + + else: + + with torch.no_grad(): + + for val_input, val_label in test_data: + + val_input = val_input.to(DEVICE) + val_label = val_label.to(DEVICE) + val_outputs = vgg(val_input) + val_loss = criterion(val_outputs, val_label) + + _ , val_preds = torch.max(val_outputs, 1) + validation_running_loss += val_loss.item() + validation_running_correct += torch.sum(val_preds == val_label.data) + + + epoch_loss = running_loss / len(train_data) + epoch_acc = running_correct.float() / len(train_data) + running_loss_history.append(epoch_loss) + running_correct_history.append(epoch_acc) + + val_epoch_loss = validation_running_loss / len(test_data) + val_epoch_acc = validation_running_correct.float() / len(test_data) + validation_running_loss_history.append(val_epoch_loss) + validation_running_correct_history.append(val_epoch_acc) + + + print("===================================================") + print("epoch: ", i + 1) + print("training loss: {:.5f}, acc: {:5f}".format(epoch_loss, epoch_acc)) + print("test loss: {:.5f}, acc: {:5f}".format(val_epoch_loss, val_epoch_acc)) + + writer.add_scalar("Loss/train_epoch", epoch_loss, i) + writer.add_scalar("Accuracy/train_epcoh", epoch_acc, i) + writer.add_scalar("Loss/test_epoch", val_epoch_loss , i) + writer.add_scalar("Accuracy/test_epoch",val_epoch_acc, i) + + if i % 10 == 0: + if os.path.exists('save/' + config['model'] + '/' + current_time): + pass + else: + os.makedirs('save/' + config['model'] + '/' + current_time) + + torch.save(vgg.state_dict(), 'save/' + config['model'] + '/' + current_time + '/saved_weights_' + str(i)) + + + +# test and metric +# accuracy = test.predict(vgg, test_data, config) +# print('The test accuracy: {0:.3f}%'.format(accuracy)) + +