-
pytorch-6 과적합 방지 방법딥러닝/pytorch 2023. 7. 10. 20:28
1. drop out
드롭아웃은 과대적합을 방지하기 위한 방법이다. 은닉층의 유닛의 일부분을 동작하지 않게 함으로써 랜덤 하게 일부 노드들을 사용하지 않는다. 드롭아웃을 진행하면 노드를 제거하기 때문에 특정 데이터에 대해서 과적합되는것을 막을 수 있다. 또한 일반적으로 미니배치로 학습하게 되면 학습할 때마다 조금씩 다른 가중치로 학습하기 때문에 앙상블 효과를 얻을 수 있다.
import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as datasets import torchvision.transforms as transforms class DropoutNet(nn.Module) : def __init__(self) : super(DropoutNet, self).__init__() self.fc1 = nn.Linear(784, 500) self.dropout = nn.Dropout(p=0.5) self.fc2 = nn.Linear(500, 10) def forward(self, x) : x = x.view(x.size(0), -1) x = torch.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x
droupout함수를 사용하여 droupout을 적용한 신경망을 구성한다.
class NonDropoutNet(nn.Module) : def __init__(self) : super(NonDropoutNet, self).__init__() self.fc1 = nn.Linear(784, 500) self.fc2 = nn.Linear(500, 10) def forward(self, x) : x = x.view(x.size(0), -1) # 입력 데이터 크기 조절 -> 1차원 으로 펼침 x = torch.relu(self.fc1(x)) # self.fc1(x) -> relu 적용 x = self.fc2(x) return x
droupout만 적용하지 않고 나머지 층의 구성은 동일하게 하여 비교할 수 있도록 nondropout 신경망을 구성한다.
train_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,),(0.3,)) ]) test_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,),(0.3,)) ]) train_dataset = datasets.MNIST(root='./data', train=True, download=False, transform=train_transform) test_dataset = datasets.MNIST(root="./data", train=False, download=False, transform=test_transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)
학습 데이터와 테스트 데이터의 transform을 텐서변환과 정규화로 지정해 주고 mnist 데이터를 로드하고 잘라준다.
dropout_model = DropoutNet() print(dropout_model) dropout_criterion = nn.CrossEntropyLoss() dropout_optimizer = optim.SGD(dropout_model.parameters(), lr=0.001) DropoutNet( (fc1): Linear(in_features=784, out_features=500, bias=True) (dropout): Dropout(p=0.5, inplace=False) (fc2): Linear(in_features=500, out_features=10, bias=True) )
drououtnet을 변수에 할당하고 출력해서 확인해 본다. 손실함수와 옵티마이저를 설정한다.
for epoch in range(10) : dropout_model.train() for images, labels in train_loader : dropout_optimizer.zero_grad() dropout_output = dropout_model(images) dropout_loss = dropout_criterion(dropout_output, labels) dropout_loss.backward() dropout_optimizer.step() dropout_model.eval() with torch.no_grad() : dropout_correct = 0 dropout_total = 0 for images, labels in test_loader : test_out = dropout_model(images) _, dropout_pre = torch.max(test_out.data, 1) dropout_total += images.size(0) dropout_correct += (dropout_pre == labels).sum().item() print("드롭아웃 적용 모델 정확도 >> {:.2f}%".format(100 * dropout_correct / dropout_total)) 드롭아웃 적용 모델 정확도 >> 91.11%
droupout 모델을 사용해서 10번을 반복하며 학습을 진행한다. 평가모델로 전환하여 정확도를 구한다.
non_dropout_model = NonDropoutNet() non_dropout_criterion = nn.CrossEntropyLoss() non_dropout_optimizer = optim.SGD(non_dropout_model.parameters(), lr=0.001) for epoch in range(10) : non_dropout_model.train() for images, labels in train_loader : non_dropout_optimizer.zero_grad() no_output = non_dropout_model(images) no_losss = non_dropout_criterion(no_output, labels) no_losss.backward() non_dropout_optimizer.step() non_dropout_model.eval() with torch.no_grad() : no_correct = 0 no_total = 0 for images, labels in test_loader : ouput = non_dropout_model(images) _, pred = torch.max(ouput.data, 1) no_total += labels.size(0) no_correct += (pred == labels).sum().item() print("드롭아웃 적용하지 않은 모델 정확도 >> {:.2f}%".format(100 * no_correct / no_total)) 드롭아웃 적용하지 않은 모델 정확도 >> 90.94%
이번에는 nondroupout 모델을 사용해서 10번을 반복하며 학습을 진행한다. 평가모델로 전환하여 정확도를 구한다. dropout을 적용한 모델의 정확도가 0.2프로 정도 높게 나타나는 것을 알 수 있다.
2. label smoothing
라벨 스무딩은 데이터를 정규화하는 방법으로 모델의 일반화 성능을 간단하게 높일 수 있다. 하나의 라벨이 하나의 클래스를 표현하는 원핫 벡터라벨의 일부 정보를 손실하면서 라벨을 부드럽게 만드는 방법이다.
일반적으로 원핫 벡터의 구조를 가지면 모델은 학습을 할 때 엄격하게 주어진 라벨에 맞추려는 경향이 생긴다.
예를 들어 [0,1,0,0]의 라벨값을 부분적으로 [0.025,0.0925,0.025,0.025]처럼 부드럽게 만들어주어서 오버피팅을 완화시키는 것이다.
class LabelSmothingLoss(nn.Module) : def __init__(self, num_classes, smothing=0.0) : super(LabelSmothingLoss, self).__init__() self.num_classes = num_classes self.smothing = smothing self.confidence = 1.0 - smothing def forward(self, pred, target) : one_hot = torch.zeros_like(pred).scatter(1, target.unsqueeze(1),1) smoth_label = one_hot * self.confidence + (1 - one_hot) * self.smothing / (self.num_classes - 1) loss = torch.sum(-smoth_label * torch.log_softmax(pred, dim=1), dim=1) return torch.mean(loss)
라벨 스무딩을 진행하는 클래스를 구성한다. 클래스의 개수와, 라벨 스무딩의 정도, 1-라벨 스무딩의 정도를 변수에 할당하고 forward 함수에서 예측값과 정답값을 인자로 받는다. target을 원핫벡터로 변환하기 위해 pred와 동일한 zeros 모양을 만들고 scatter로 target의 위치에 1을 할당한다.
one_hot값에 라벨 스무딩의 공식을 적용하여 타깃위치가 아닌 위치에 스무딩을 진행한다. pred에 log_softmax를 사용하고 스무딩 한 값에 곱하여 손실을 계산한다. mean을 사용하여 평균 손실로 반환한다.
class MyModel(nn.Module) : def __init__(self) : super(MyModel, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3) self.relu1 = nn.ReLU(inplace=True) self.pool1 = nn.MaxPool2d(kernel_size=2) self.conv2 = nn.Conv2d(32, 64, kernel_size=3) self.relu2 = nn.ReLU(inplace=True) self.pool2 = nn.MaxPool2d(kernel_size=2) self.flatten = nn.Flatten() self.fc1 = nn.Linear(64*5*5, 128) self.relu3 = nn.ReLU(inplace=True) self.fc2 = nn.Linear(128, 10) def forward(self, x) : x = self.conv1(x) x = self.relu1(x) x = self.pool1(x) x = self.conv2(x) x = self.relu2(x) x = self.pool2(x) x = self.flatten(x) x = self.fc1(x) x = self.relu3(x) x = self.fc2(x) return x
숫자 분류를 위한 모델을 구성하고 학습하기 위한 클래스를 구성한다.
train_dataset = torchvision.datasets.FashionMNIST(root="./F_data", train=True, transform=ToTensor(), download=True) train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
학습 데이터를 로드하고 잘라서 준비한다.
model = MyModel() print(model) MyModel( (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1)) (relu1): ReLU(inplace=True) (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1)) (relu2): ReLU(inplace=True) (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (flatten): Flatten(start_dim=1, end_dim=-1) (fc1): Linear(in_features=1600, out_features=128, bias=True) (relu3): ReLU(inplace=True) (fc2): Linear(in_features=128, out_features=10, bias=True) )
모델을 구성하고 출력해서 구성을 확인한다.
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) num_classes = 10 smoting = 0.2 no_criterion = LabelSmothingLoss(num_classes, smothing=0.0) criterion = LabelSmothingLoss(num_classes, smoting)
최적화 함수를 설정하고 분류하기 위한 클래스의 개수, 스무딩 정도, 스무딩을 진행하지 않은 손실 함수, 스무딩을 진행한 손실함수를 생성한다.
num_epochs = 20 train_losses_no_smothing = [] train_losses_smothing = [] for epoch in range(num_epochs) : train_losses_no_smothing_temp = 0.0 train_losses_smothing_temp = 0.0 for images, labels in train_dataloader : optimizer.zero_grad() outputs_no_smothing = model(images) loss_no_smothing = no_criterion(outputs_no_smothing, labels) loss_no_smothing.backward() optimizer.step() train_losses_no_smothing_temp += loss_no_smothing.item() outputs_smothing = model(images) loss_smothing = criterion(outputs_smothing, labels) loss_smothing.backward() optimizer.step() train_losses_smothing_temp += loss_smothing.item() train_losses_no_smothing.append(train_losses_no_smothing_temp / len(train_dataloader)) train_losses_smothing.append(train_losses_smothing_temp / len(train_dataloader))
20번을 반복하며 스무딩을 적용하지 않은 손실과 스무딩을 진행한 손실을 기록한 리스트를 구성한다. 트레인 데이터셋에서 데이터를 꺼내서 학습을 진행하며 loss_no_smoothing과 loss_smoothing을 구하고 학습 손실의 평균값을 계산하여 리스트에 추가한다.
epochs = range(1, num_epochs +1 ) plt.plot(epochs, train_losses_no_smothing, label='Without Label Smothing') plt.plot(epochs, train_losses_smothing, label='With Label Smothing') plt.xlabel('Epoch') plt.ylabel('loss') plt.legend() plt.show()
리스트에서 값을 하나씩 꺼내서 학습을 진행하면서 loss의 값을 확인할 수 있고 스무딩을 했을 때 더 적을 loss함수를 얻을 수 있다.
3. mixup
믹스업은 증강기법의 일종으로 두 데이터와 라벨을 섞어서 새로운 데이터를 만들어내는 방법으로 믹스업 등의 증강기법을 활용하여 이미지의 다양성을 얻는다면 과적합을 방지할 수 있다.
class MyModel(nn.Module) : def __init__(self) : super(MyModel, self).__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1) self.relu = nn.ReLU() self.flatten = nn.Flatten() self.fc1 = nn.Linear(16 * 32 * 32, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x) : x = self.conv1(x) x = self.relu(x) x = self.flatten(x) x = self.fc1(x) x = self.fc2(x) return x
믹스업을 적용하기 위한 모델을 구성한다.
train_transform = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.AutoAugment(), transforms.ToTensor(), transforms.Normalize((0.5,0.5,0.5), (0.3,0.3,0.3)) ]) train_dataset = datasets.CIFAR10(root='./CIFAR10_data', train=True, transform = train_transform, download=True) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
데이터 변환에 사용될 랜덤크롭, 수평, 수직 뒤집기, 자동증강, 텐서변환, 정규화를 정의하고 데이터를 불러와서 잘라준다.
model = MyModel() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001)
모델을 구성하고 손실함수와 옵티마이저를 설정한다.
def mixup_data(x, y, alpha=1.0) : batch_size = x.size(0) lam = torch.rand(batch_size, 1, 1, 1) lam = torch.max(lam, 1 - lam) mixed_x = lam * x + (1 - lam) * x.flip(dims=[0,2,3]) indices = torch.randperm(batch_size) mixed_y = lam.squeeze() * y + ( 1 - lam.squeeze()) * y[indices] mixed_y = mixed_y.type(torch.long) return mixed_x, mixed_y
믹스업을 구현한다. 이미지와 라벨을 받아오기 위해 x, y 변수를 설정하고 믹스된 결과를 반환한다.
0에서 1 사이의 랜덤 한 값을 가지는 lam을 생성한다. torch.rand로 배치 사이즈에 랜덤 한 값을 생성하고 max로 0.5 이상으로 조정한다. mixed_x에 입력 이미지 x와 x를 좌우 대칭으로 뒤집은 이미지를 섞어서 할당한다.
indicies는 랜덤한 순열을 저장하며 mixed_y에 정답 라벨 y와 랜덤한 라벨 y값을 섞어서 할당하고 long타입으로 변경한다.
def plot_images(images, labels, title) : fig, axes = plt.subplots(2, 5 , figsize=(12,6)) fig.suptitle(title, fontsize=16) labels = labels.numpy() for i, ax in enumerate(axes.flat) : image = images[i].squeeze() ax.imshow(image, cmap='gray') ax.set_title(f"Label:{labels[i]}") ax.axis('off') plt.show()
믹스 업된 이미지와 라벨을 시각화하기 위한 함수를 구성한다.
num_epochs = 10 train_losses_no_mixup = [] train_losses_with_mixup = [] end_idx = 0 for epoch in range(num_epochs) : train_loss_no = 0.0 train_loss = 0.0 for inputs, labels in train_loader : optimizer.zero_grad() images_temp, labels_tmep = inputs, labels inputs, labels = mixup_data(inputs, labels) mixed_images = inputs.cpu().numpy() mixed_images = np.transpose(mixed_images, (0,2,3,1)) mixed_images = np.squeeze(mixed_images) if end_idx == 0 : plot_images(mixed_images, labels.squeeze(), "Mixed Image with label smothing") end_idx = 1 outputs_no_mixup = model(images_temp) outputs_mixup = model(inputs) labels = torch.squeeze(labels) loss_no_mixup = criterion(outputs_no_mixup, labels) loss_mixup = criterion(outputs_mixup, labels) loss_no_mixup.backward() loss_mixup.backward() optimizer.step() train_loss_no += loss_no_mixup.item() train_loss += loss_mixup.item() train_losses_no_mixup.append(train_loss_no / len(train_loader)) train_losses_with_mixup.append(train_loss / len(train_loader))
믹스업을 적용하여 학습을 10번 진행한다. 믹스 업한 손실과 하지 않은 손실을 저장하기 위한 리스트를 만들고 데이터를 불러와 mixup_data 함수를 사용하여 믹스 업된 이미지와 라벨을 얻는다.
첫 번째 에포크에서 믹스 업된 라벨과 이미지를 시각화해서 확인할 수 있다. 믹스 업된 이미지와 원본 이미지를 넣어서 학습을 각각 진행하고 손실 평균값을 계산하여 리스트에 추가한다.
epochs = range(1, num_epochs +1 ) plt.plot(epochs, train_losses_no_mixup, label='No Mixed') plt.plot(epochs, train_losses_with_mixup, label='Mixed') plt.xlabel('Epoch') plt.ylabel('loss') plt.legend() plt.show()
믹스업 한 이미지와 하지 않은 이미지들을 에포크의 흐름에 따라 비교하면 믹스업을 했을 때 손실함수의 값이 낮다는 것을 알 수 있다.
'딥러닝 > pytorch' 카테고리의 다른 글
pytorch-8 앙상블 (0) 2023.07.11 pytorch-7 전이학습과 파인튜닝 (1) 2023.07.11 pytorch-5 CNN (0) 2023.07.09 pytorch-4 stride conv, dilated conv, 가중치 행렬 시각화 (0) 2023.07.09