|
|
24.08.27 20:30경 학습 시작. 24.08.28, 00:05종료 확인
이미지가 움직이며 흔들림을 생각하여 GaussianBlur를 조금 강하게 주기로 함.
ColorJitter 와 GaussianBlur를 같이 사용했을 때 결과가 안좋아 ColorJitter는 데이터 증식에서 제외
Class는 background, floor, blockage 3개로 진행.
| Epoch : 50까지 기록 if not use_v2: transforms += [T.RandomRotations(degrees=10)] #transforms += [T.ColorJitter(brightness=(1.0, 1.2), saturation=(0.5,1.5), hue=(-0.3, 0.5))] transforms += [T.RandomGrayscale(0.1)] transforms += [T.RandomErasing()] #GaussinaBlur와 ElasticTransform은 서로 겹치지 않게. transforms += [T.GaussianBlur(kernel_size=(5,10), sigma=(0.1,2.0))] #transforms += [T.ElasticTransform(alpha=100.0,sigma=5.0)] python my_train.py -j 16 --data-path D:\pytorch_cuda\segmentaion_data\dataset8 --lr 0.015 --dataset coco -b 4 --model deeplabv3_resnet101 --aux-loss --device cuda --epoch 50 --weights-backbone ResNet101_Weights.IMAGENET1K_V1 --output-dir D:\pytorch_cuda\segmentaion_data\dataset8\output Evaluate 단계 Test: Total time: 0:02:28 검증 데이터 acc 및 IoU [_background_, ceiling, floor, wall, blockage] global correct: 91.0 average row correct: ['89.6', '97.7', '85.5'] IoU: ['85.0', '88.4', '74.6'] mean IoU: 82.7 훈련 데이터 acc 및 IoU [_background_, ceiling, floor, wall, blockage] global correct: 91.5 average row correct: ['89.2', '95.2', '90.8'] IoU: ['83.9', '90.7', '78.4'] mean IoU: 84.3 Training time 3:30:51 |
마지막 Epoch에서도 Acc가 올라가고 loss값이 내려가는 경향이 있어 --resume 명령행인자를 사용 50 Epoch를 더 진행하기로 함.
이후 새로운 학습에서는 Gaussianblur가 모든 image에 적용되고 있어 50%확률로 제한할 예정 및 kernel_size도 더 키워볼 예정.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class GaussianBlur: def __init__(self, kernel_size=(5, 9), sigma=(0.1, 2.0)): self.kernel = kernel_size self.sigma_min = sigma[0] self.sigma_max = sigma[1] def get_params(self, min, max): return torch.empty(1).uniform_(min, max).item() def get_kernel(self, kernel_size): return random.randrange(kernel_size[0], kernel_size[1]) def __call__(self, image, target): if random.random() > 0.5: return image, target sigma = self.get_params(self.sigma_min, self.sigma_max) kernel_int = self.get_kernel(self.kernel) if kernel_int % 2 == 0: kernel_int += 1 image = F.gaussian_blur(image, kernel_int, [sigma, sigma]) return image, target | cs |
학습에 사용된 V1 Transfroms 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | import random import numpy as np import torch from torchvision import transforms as T from torchvision.transforms import functional as F from torchvision.transforms.functional import InterpolationMode import numpy as np import cv2 from scipy.ndimage.interpolation import map_coordinates from scipy.ndimage.filters import gaussian_filter import matplotlib.pyplot as plt def pad_if_smaller(img, size, fill=0): min_size = min(img.size) if min_size < size: ow, oh = img.size padh = size - oh if oh < size else 0 padw = size - ow if ow < size else 0 img = F.pad(img, (0, 0, padw, padh), fill=fill) return img class Compose: def __init__(self, transforms): self.transforms = transforms def __call__(self, image, target): for t in self.transforms: image, target = t(image, target) return image, target class RandomResize: def __init__(self, min_size, max_size=None): self.min_size = min_size if max_size is None: max_size = min_size self.max_size = max_size def __call__(self, image, target): size = random.randint(self.min_size, self.max_size) image = F.resize(image, size, antialias=True) target = F.resize(target, size, interpolation=T.InterpolationMode.NEAREST) return image, target class RandomHorizontalFlip: def __init__(self, flip_prob): self.flip_prob = flip_prob def __call__(self, image, target): if random.random() < self.flip_prob: image = F.hflip(image) target = F.hflip(target) return image, target class RandomCrop: def __init__(self, size): self.size = size def __call__(self, image, target): image = pad_if_smaller(image, self.size) target = pad_if_smaller(target, self.size, fill=255) crop_params = T.RandomCrop.get_params(image, (self.size, self.size)) image = F.crop(image, *crop_params) target = F.crop(target, *crop_params) return image, target class CenterCrop: def __init__(self, size): self.size = size def __call__(self, image, target): image = F.center_crop(image, self.size) target = F.center_crop(target, self.size) return image, target class PILToTensor: def __call__(self, image, target): image = F.pil_to_tensor(image) target = torch.as_tensor(np.array(target), dtype=torch.int64) return image, target class ToDtype: def __init__(self, dtype, scale=False): self.dtype = dtype self.scale = scale def __call__(self, image, target): if not self.scale: return image.to(dtype=self.dtype), target image = F.convert_image_dtype(image, self.dtype) return image, target class Normalize: def __init__(self, mean, std): self.mean = mean self.std = std def __call__(self, image, target): image = F.normalize(image, mean=self.mean, std=self.std) return image, target class RandomRotations: def __init__(self, degrees=10 , interpolation=InterpolationMode.NEAREST, expand=False, center=None, fill=0): self.degrees = degrees self.interpolation = interpolation self.expand = expand self.center = center self.fill = fill def __call__(self, image, target): array = random.random() degrees = None if array < 0.3: degrees = random.randint(2, self.degrees) else: degrees = self.degrees if random.random() < 0.5: degrees = -(degrees) if random.random() < 0.5: return image, target image = F.rotate(image, degrees, self.interpolation, self.expand, self.center, self.fill) target = F.rotate(target, degrees, self.interpolation, self.expand, self.center, self.fill) return image, target class ColorJitter: def __init__(self, brightness=(0.7, 1.1), hue=(-0.2, 0.2), contrast=(0.5, 1.0), saturation=(0.9, 1.0)): self.brightness = brightness self.hue = hue self.contrast = contrast self.saturation = saturation def chose_number(self, value): random_value = random.uniform(value[0], value[1]) rounded_value = round(random_value, 1) return rounded_value def __call__(self, image, target): brightness = self.chose_number(self.brightness) saturation = self.chose_number(self.saturation) hue = self.chose_number(self.hue) contrast = self.chose_number(self.contrast) image = F.adjust_brightness(image, brightness) image = F.adjust_saturation(image, saturation) image = F.adjust_hue(image, hue) image = F.adjust_contrast(image, contrast) return image, target class RandomGrayscale: def __init__(self, p=0.1): self.p = p def __call__(self, image, target): num_output_channels, _, _ = F.get_dimensions(image) if torch.rand(1) < self.p: return F.rgb_to_grayscale(image, num_output_channels=num_output_channels), target return image, target class RandomErasing: def __init__(self, p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0): self.p = p self.scale = scale self.ratio = ratio self.value = value def chose_number(self, value): random_value = random.uniform(value[0], value[1]) rounded_value = round(random_value, 2) return rounded_value def __call__(self, image, target): scale = self.chose_number(value=self.scale) ratio = self.chose_number(value=self.ratio) transform = T.ToTensor() tensor_image = transform(image) #tensor_target = transform(target) img_c, img_h, img_w = tensor_image.shape image_height = img_h image_width = img_w #height and width erase_height = image_height * scale erase_width = image_width * scale #start x and y erase_height = int(erase_height) erase_width = int(erase_height) if image_width > erase_width: x = random.randint(0, image_width - erase_width) else: x = 0 if image_height > erase_height: y = random.randint(0, image_height - erase_height) else: y = 0 if random.random() <= self.p: image = transform(image) target = transform(target) image = F.erase(image, x, y, erase_height, erase_width, self.value) target = F.erase(target, x, y, erase_height, erase_width, self.value) transform = T.ToPILImage() image = transform(image) target = transform(target) return image, target #kernel_size는 튜플로 입력할 것. 고정시킬 값이라면 (5,5)같이 할 것. class GaussianBlur: def __init__(self, kernel_size=(5, 9), sigma=(0.1, 2.0)): self.kernel = kernel_size self.sigma_min = sigma[0] self.sigma_max = sigma[1] def get_params(self, min, max): return torch.empty(1).uniform_(min, max).item() def get_kernel(self, kernel_size): return random.randrange(kernel_size[0], kernel_size[1]) def __call__(self, image, target): if random.random() > 0.5: return image, target sigma = self.get_params(self.sigma_min, self.sigma_max) kernel_int = self.get_kernel(self.kernel) if kernel_int % 2 == 0: kernel_int += 1 image = F.gaussian_blur(image, kernel_int, [sigma, sigma]) return image, target class ElasticTransform: def __init__(self, alpha=50.0, sigma=5.0, interpolation=InterpolationMode.BILINEAR, fill=0): self.alpha = alpha self.sigma = sigma self.interpolation = interpolation self.fill = fill def __call__(self, image, target): _, height, width = F.get_dimensions(image) _, m_height, m_width = F.get_dimensions(target) alpha = [float(self.alpha), float(self.alpha)] sigma = [float(self.sigma), float(self.sigma)] displacement = T.ElasticTransform.get_params(alpha, sigma, [height, width]) image = F.elastic_transform(image, displacement, self.interpolation, self.fill) m_displacement = T.ElasticTransform.get_params(alpha, sigma, [m_height, m_width]) target = F.elastic_transform(target, m_displacement, self.interpolation, self.fill) return image, target | cs |
ElasticTransform 증식 결과
학습 과정에서 Train과 Valid 데이터셋에 대해 각각의 loss값과 Acc 출력
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | def evaluate(model, data_loader, device, num_classes, epoch, cut_train): model.eval() confmat = utils.ConfusionMatrix(num_classes) metric_logger = utils.MetricLogger(delimiter=" ") #header가 Test로 들어가면 Metric_logger.log_every에서 header = f"Test: [{epoch}] cut : " + cut_train num_processed_samples = 0 with torch.inference_mode(): #100을 10으로 수정하면 loss값을 계산하긴 하는거같은데... for image, target in metric_logger.log_every(data_loader, 30, header): image, target = image.to(device), target.to(device) #if문으로 Data_loader가 Val일 때에만 진행해야 함. scaler = torch.cuda.amp.GradScaler() if args.amp else None model_without_ddp = model params_to_optimize = [ {"params": [p for p in model_without_ddp.backbone.parameters() if p.requires_grad]}, {"params": [p for p in model_without_ddp.classifier.parameters() if p.requires_grad]}, ] if args.aux_loss: params = [p for p in model_without_ddp.aux_classifier.parameters() if p.requires_grad] params_to_optimize.append({"params": params, "lr": args.lr * 10}) optimizer = torch.optim.SGD(params_to_optimize, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay) with torch.cuda.amp.autocast(enabled=scaler is not None): output = model(image) loss = criterion(output, target) metric_logger.update(loss=loss.item(), lr=optimizer.param_groups[0]["lr"]) output = model(image) output = output["out"] confmat.update(target.flatten(), output.argmax(1).flatten()) # FIXME need to take into account that the datasets # could have been padded in distributed setup num_processed_samples += image.shape[0] confmat.reduce_from_all_processes() num_processed_samples = utils.reduce_across_processes(num_processed_samples) if ( hasattr(data_loader.dataset, "__len__") and len(data_loader.dataset) != num_processed_samples and torch.distributed.get_rank() == 0 ): # See FIXME above warnings.warn( f"It looks like the dataset has {len(data_loader.dataset)} samples, but {num_processed_samples} " "samples were used for the validation, which might bias the results. " "Try adjusting the batch size and / or the world size. " "Setting the world size to 1 is always a safe bet." ) return confmat def train_one_epoch(model, criterion, optimizer, data_loader, lr_scheduler, device, epoch, print_freq, scaler=None): model.train() metric_logger = utils.MetricLogger(delimiter=" ") metric_logger.add_meter("lr", utils.SmoothedValue(window_size=1, fmt="{value}")) header = f"Epoch: [{epoch}]" #log_every에서 Epoch별 내용이 모두 출력된다. (log_every) for image, target in metric_logger.log_every(data_loader, print_freq, header): image, target = image.to(device), target.to(device) with torch.cuda.amp.autocast(enabled=scaler is not None): output = model(image) loss = criterion(output, target) optimizer.zero_grad() if scaler is not None: scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() else: loss.backward() optimizer.step() lr_scheduler.step() metric_logger.update(loss=loss.item(), lr=optimizer.param_groups[0]["lr"]) | cs |
train_one_epoch에서 Loss값을 계산하는 부분은
1 2 3 4 5 6 7 8 9 10 11 | with torch.cuda.amp.autocast(enabled=scaler is not None): output = model(image) loss = criterion(output, target) optimizer.zero_grad() if scaler is not None: scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() else: loss.backward() optimizer.step() | cs |
해당 영역이다.
하지만 evaluate 함수 원본에는 ACC값을 계산하는 코드만 있고 Loss값을 계산하는 코드가 없어 임의로 코드를 부여함.
다만 학습에 영향을 미치지 않게 backward나 update와 같은 함수는 진행하지 않음.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | for image, target in metric_logger.log_every(data_loader, 30, header): image, target = image.to(device), target.to(device) #if문으로 Data_loader가 Val일 때에만 진행해야 함. scaler = torch.cuda.amp.GradScaler() if args.amp else None model_without_ddp = model params_to_optimize = [ {"params": [p for p in model_without_ddp.backbone.parameters() if p.requires_grad]}, {"params": [p for p in model_without_ddp.classifier.parameters() if p.requires_grad]}, ] if args.aux_loss: params = [p for p in model_without_ddp.aux_classifier.parameters() if p.requires_grad] params_to_optimize.append({"params": params, "lr": args.lr * 10}) optimizer = torch.optim.SGD(params_to_optimize, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay) with torch.cuda.amp.autocast(enabled=scaler is not None): output = model(image) loss = criterion(output, target) metric_logger.update(loss=loss.item(), lr=optimizer.param_groups[0]["lr"]) output = model(image) output = output["out"] confmat.update(target.flatten(), output.argmax(1).flatten()) | cs |
train_one_epoch와는 다르게 evaluate에서는 criterion이나 optimizer와 같은 인자를 받지 않아 코드 내부에서 직접 선언해줌.
evaluate의 cut_train인자는 train_one_epoch와 evaluate 모두 MetricLogger에서 로스값을 출력하고 등록하는데 main함수 내부에서 총 3번 불러와져 Train데이터셋의 경우 evaluate 함수일 때 손실함수 값을 등록하지 않게 제한해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | class ConfusionMatrix: def __init__(self, num_classes): self.num_classes = num_classes self.mat = None def update(self, a, b): n = self.num_classes if self.mat is None: self.mat = torch.zeros((n, n), dtype=torch.int64, device=a.device) with torch.inference_mode(): k = (a >= 0) & (a < n) inds = n * a[k].to(torch.int64) + b[k] self.mat += torch.bincount(inds, minlength=n**2).reshape(n, n) def reset(self): self.mat.zero_() def compute(self): h = self.mat.float() acc_global = torch.diag(h).sum() / h.sum() acc = torch.diag(h) / h.sum(1) iu = torch.diag(h) / (h.sum(1) + h.sum(0) - torch.diag(h)) return acc_global, acc, iu def reduce_from_all_processes(self): self.mat = reduce_across_processes(self.mat).to(torch.int64) def __str__(self): #검증에서 실행. acc_global, acc, iu = self.compute() return ("global correct: {:.1f}\naverage row correct: {}\nIoU: {}\nmean IoU: {:.1f}").format( acc_global.item() * 100, [f"{i:.1f}" for i in (acc * 100).tolist()], [f"{i:.1f}" for i in (iu * 100).tolist()], iu.mean().item() * 100, ) class MetricLogger: def __init__(self, delimiter="\t"): #meters는 learning rate, Loss값을 포함. self.meters = defaultdict(SmoothedValue) self.delimiter = delimiter def update(self, **kwargs): for k, v in kwargs.items(): if isinstance(v, torch.Tensor): v = v.item() if not isinstance(v, (float, int)): raise TypeError( f"This method expects the value of the input arguments to be of type float or int, instead got {type(v)}" ) self.meters[k].update(v) def __getattr__(self, attr): if attr in self.meters: return self.meters[attr] if attr in self.__dict__: return self.__dict__[attr] raise AttributeError(f"'{type(self).__name__}' object has no attribute '{attr}'") def __str__(self): loss_str = [] for name, meter in self.meters.items(): loss_str.append(f"{name}: {str(meter)}") return self.delimiter.join(loss_str) def synchronize_between_processes(self): for meter in self.meters.values(): meter.synchronize_between_processes() def add_meter(self, name, meter): self.meters[name] = meter def log_every(self, iterable, print_freq, header=None): i = 0 if not header: header = "" start_time = time.time() end = time.time() iter_time = SmoothedValue(fmt="{avg:.4f}") data_time = SmoothedValue(fmt="{avg:.4f}") space_fmt = ":" + str(len(str(len(iterable)))) + "d" #train if문 if torch.cuda.is_available(): log_msg = self.delimiter.join( [ header, "[{0" + space_fmt + "}/{1}]", "eta: {eta}", "{meters}", "time: {time}", "data: {data}", "max mem: {memory:.0f}", ] ) else: log_msg = self.delimiter.join( [header, "[{0" + space_fmt + "}/{1}]", "eta: {eta}", "{meters}", "time: {time}", "data: {data}"] ) MB = 1024.0 * 1024.0 for obj in iterable: data_time.update(time.time() - end) yield obj iter_time.update(time.time() - end) #print_freq가 10이라서 10마다 출력 args.print-freq if i % print_freq == 0: eta_seconds = iter_time.global_avg * (len(iterable) - i) eta_string = str(datetime.timedelta(seconds=int(eta_seconds))) if torch.cuda.is_available(): if 'V' in header: print( log_msg.format( i, len(iterable), eta=eta_string, #str(self에서 learning rate와 loss값 모두 출력) meters=str(self), time=str(iter_time), data=str(data_time), memory=torch.cuda.max_memory_allocated() / MB, ) ) print("str : ", str(self)) else: print( log_msg.format( i, len(iterable), eta=eta_string, meters=str(self), time=str(iter_time), data=str(data_time) ) ) i += 1 end = time.time() #유저 추가 코드. Epoch = None import re Epoch = re.sub(r'[^0-9]', '', header) Epoch = int(Epoch) if 'Epoch' in header: print("Train Loss value : ", self.meters['loss']) print("Epoch : ", Epoch) loss_value = str(self.meters['loss']) loss_value = loss_value.split()[0] loss_value = float(loss_value) #Train loss tensorboard 기록 writer = SummaryWriter('./runs/train_loss') writer.add_scalar('Train Loss', loss_value, Epoch) writer.close() elif 'Test' in header: if 'V' in header: print("Valid Loss value : ", self.meters['loss']) print("Epoch : ", Epoch) loss_value = str(self.meters['loss']) loss_value = loss_value.split()[0] loss_value = float(loss_value) writer = SummaryWriter('./runs/Val_loss') writer.add_scalar('Val Loss', loss_value, Epoch) writer.close() total_time = time.time() - start_time total_time_str = str(datetime.timedelta(seconds=int(total_time))) print(f"{header} Total time: {total_time_str}") | cs |
Val Loss가 전체적으로 낮게 나오는 경향이 있지만 학습이 마무리될 때 값이 비슷해진다.

첫댓글 train acc와 val acc 그래프가 동일함 같은 데이터를 그린것 같음
코드 확인해보니 변수 받는 과정에서 동일한 데이터를 받은 것으로 확인했습니다. 현재 --resume 사용하여 150 Epoch으로 진행중인 학습에서는 분류해서 학습하고 있습니다.
T.ElasticTransform 이변환이 잘 적용되었는지 입력영상, 레이블 마스크영상 모두 확인했는지? 결과 첨부해줄것
ElasticTransform 오류가 발생해 수정 후 결과 첨부하였습니다.
로스, 정확도 출력하는 수정한 부분의 소스와 코드 설명 추가할것
3시 이내로 정리해서 올리겠습니다.
ADE20K 데이터셋으로 학습한 ViT기반 분할 모델이용해서 군산대 실내영상으로 전이학습을 해보도록 할것
ADE20K 데이터셋에는 실내 영상이 포함되어 이미 선행학습이 잘되어있을거고 군산대 영상을 사용해서 전이학습하면 더 잘하지 않을까
전이학습시 백본모델로 사용한 ViT의 가중치는 고정해야함 -> 작은데이터로도 좋은 성능을 얻을수 있을것임 대신 ADE20K 클래스기준으로 세분화해서 다시 레이블링해야함