열심히 코딩 하숭!

10. 튜닝 기법 | 파이토치 딥러닝 프로그래밍 본문

프로그래밍 책/파이토치 딥러닝 프로그래밍

10. 튜닝 기법 | 파이토치 딥러닝 프로그래밍

채숭이 2024. 4. 21. 20:20

10 튜닝 기법

10.1 이 장의 중요 개념

이미지 대상 딥러닝 분류 모델의 튜닝 기법

(1) 신경망의 다층화

(2) 최적화 함수를 개선

(3) 과학습에 관한 대책

 

10.3 최적화 함수

최적화 함수

  • 손실의 경사 값을 기반으로 어떤 알고리즘을 통해 파라미터를 수정해 나갈 것인지

SGD

  • Stochastic Gradient Descent
  • 경사에 일정한 학습률을 곱해서 파라미터를 수정해 나가는 방식
lr = 0.001 # 학습률

w -= lr * W.grad
B -= lr * B.grad

Momentum

  • SGD 알고리즘을 개선하기 위해 나온 방법
  • 모멘텀 인수를 설정하여, 과거에 계산했던 경사 값을 기억했다가, 그만큼 파라미터를 일정 비율 감소시킴 (SGD의 경우 현재 경사 값만 반영함)
optimizer = optim.SGD(net.parameters(), lr=lr, momentum = 0.9)

Adam

  • 복잡한 알고리즘
Adam(Adaptive Moment Estimation)은 경사 하강법(Gradient Descent)의 한 종류로,
두 가지 개념인 모멘텀(Momentum)과 RMSProp(Root Mean Square Propagation)을 결합한 최적화 알고리즘입니다.
Adam은 모멘텀과 RMSProp의 아이디어를 조합하여 경사 하강법의 속도와 안정성을 향상시키는 데 사용됩니다.


Adam은 다음과 같은 특징을 가지고 있습니다:


(1) 모멘텀(Momentum): Adam은 경사 하강법의 업데이트에 모멘텀을 사용합니다. 모멘텀은 이전 업데이트의 방향과 크기를 고려하여 새로운 업데이트를 수행함으로써 매개 변수 업데이트의 일관성과 안정성을 향상시킵니다.


(2) RMSProp: Adam은 RMSProp의 개념을 사용하여 각 매개 변수마다 학습률(learning rate)을 조정합니다. RMSProp은 각 매개 변수의 기울기(경사)의 제곱에 대한 이동 평균을 계산하여 매개 변수마다 다른 학습률을 적용하는 방식을 사용합니다. 이는 각 매개 변수마다 학습률을 자동으로 조정하여 학습률을 더 정확하게 설정하는 데 도움이 됩니다.


(3) 바이어스 보정(Bias Correction): Adam은 초기 학습 단계에서 편향(bias)을 보정하는 기능을 제공합니다. 이는 초기 학습 단계에서 편향이 심한 값을 조정하여 모멘텀과 RMSProp을 올바르게 작동시키는 데 도움이 됩니다.
optimize = optim.Adam(net.parameters())

 

10.4 과학습의 대응 방법

과학습

  • 경사하강법 → 훈련 데이터의 손실을 줄이기 위한 알고리즘
  • 우리의 goal → 학습 data가 아닌 검증 data에 대한 정확도 향상

드롭아웃(Dropout)

  • 2개의 레이어 함수 사이에 드롭아웃 함수 추가
  • 중간 텐서에서 랜덤하게 드롭아웃하여 해당 요소는 더이상 출력되지 않음

dropout = nn.Dropout(0.5) # 드롭아웃 함수 정의 (비율 설정)

# 흔련 페이즈에서의 거동
dropout.train()
outputs = dropout(inputs)

# 예측 페이즈에서의 거동
dropout.eval()
outputs = dropout(inputs)
  • 특이사항: 드롭아웃되지 않은 요소들은 1/(1-p)를 곱한 값이 반환된다 ⇒ 입력값 전체 평균이 드롭아웃 이전과 변하지 않게 하기 위함
📍드롭아웃 - 예측에서는?

예측(prediction) 시에는 일반적으로 드롭아웃을 적용하지 않는다. 
모든 뉴런을 포함하여 전체 신경망을 사용하여 입력에 대한 예측을 수행. 
이는 모델이 가능한 한 잘 일반화되고 더 정확한 예측을 하도록 하기 위한 것임.

학습과 예측에서 드롭아웃에 차이가 있어도 되나?
 학습 데이터에 오버피팅(overfitting)되는 것을 방지하는 용도로 드롭아웃을 사용하는 것이기 때문에 예측에서는 사용하지 않는 것이 오히려 일관성 측면에서 좋음
→ 일관성 측면? == 랜덤성이 부여될 경우, 예측 결과가 달라질 수 있음

 

배치 정규화 (Batch Normalization)

  • 신경망의 각 층에서 입력 데이터의 분포를 조절
  • 미니 배치 단위로 정규화를 거침
  • 특징
    • 배치정규화도 드롭 아웃처럼 훈련 페이즈와 예측 페이즈에서 거동이 서로 다르다
    • 학습 대상 파라미터인 weight와 bias를 가진다
    • 입력 데이터의 채널 수 혹은 차원수를 인수로 넣는다

데이터 증강 (Data Augmentation)

  • 학습 전 입력 데이터를 인위적으로 가공하여 데이터의 다양성을 증가시킨다
  • 모델 입장에서 학습을 반복할 때 다른 패턴의 데이터가 들어오기 때문에, 과학습이 일어나기 어려워진다
기능명 처리 요소 입력 출력
RandomApply 여러 개의 Transforms를 지정한 확률로 수행 PIL Image PIL Image
RandomChoice 여러 개의 Transforms 중 한 가지를 선택해서 수행 PIL Image PIL Image
RandomCrop 랜덤으로 이미지를 잘라냄 PIL Image PIL Image
RandomResizedCrop 랜덤으로 이미지를 잘라내서 리사이징함 PIL Image PIL Image
Color Jitter 밝기,대비, 채도, 색상을 랜덤으로 변화시킴 PIL Image PIL Image
RandomGrayscale 랜덤으로 그레이 스케일로 변환 PIL Image PIL Image
RandomHorizontalFlip 랜덤으로 좌, 우를 반전시킴 PIL Image PIL Image
RandomVerticalFlip 랜덤으로 상, 하를 반전시킴 PIL Image PIL Image
RandomAffine 랜덤으로 아핀 변환을 수행 PIL Image PIL Image
RandomPerspective 랜덤으로 사영 변환을 수행 PIL Image PIL Image
RandomRotation 랜덤으로 회전시킴 PIL Image PIL Image
RandomErasing 랜덤으로 사각형 영역을 삭제 텐서 텐서
transform_train = transforms.Compose([
	transforms.RandomHorizontalFlip(p=0.5),
	transforms.ToTensor(),
	transforms.Normalize(0.5, 0.5),
	transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)
])
  • p는 확률임!!! 확률에 따라 변형 되거나 그대로 유지되거나

 

10.6 층을 깊게 쌓은 모델 구현하기

모델 구현

class CNN_v2(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=(1,1)) # 패딩 설정! (더미 데이터로 채움)
        self.conv2 = nn.Conv2d(32, 32, 3, padding=(1,1))
        self.conv3 = nn.Conv2d(32, 64, 3, padding=(1,1))
        self.conv4 = nn.Conv2d(64, 64, 3, padding=(1,1))
        self.conv5 = nn.Conv2d(64, 128, 3, padding=(1,1))
        self.conv6 = nn.Conv2d(128, 128, 3, padding=(1,1))
        self.relu = nn.ReLU(inplace=True)
        self.flatten = nn.Flatten()
        self.maxpool = nn.MaxPool2d((2,2))
        self.l1 = nn.Linear(4*4*128, 128) # 입력 사이즈 주의
        self.l2 = nn.Linear(128, num_classes)

        self.features = nn.Sequential(
            self.conv1,
            self.relu,
            self.conv2,
            self.relu,
            self.maxpool,
            self.conv3,
            self.relu,
            self.conv4,
            self.relu,
            self.maxpool,
            self.conv5,
            self.relu,
            self.conv6,
            self.relu,
            self.maxpool,
            )

        self.classifier = nn.Sequential(
            self.l1,
            self.relu,
            self.l2
        )

    def forward(self, x):
        x1 = self.features(x)
        x2 = self.flatten(x1)
        x3 = self.classifier(x2)
        return x3

학습 결과

  • 초기 상태 정확도가 매우 낮음 → 모델이 깊어서 학습의 효과가 미치기 위해 더 많은 시간을 소요해야함
  • 약 5% 정도 성능 좋아짐

 

10.7 최적화 함수 선택

모멘텀, Adam 적용 시

# 모멘텀
optimizer = optim.SGD(net.parameters(), lr = lr, momentum=0.9)

# Adam
optimizer = optim.Adam(net.parameters())

  • 성능이 77% 정도로 5% 향상

 

10.8 드롭아웃

모델에 드롭아웃을 추가

class CNN_v3(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=(1,1))
        self.conv2 = nn.Conv2d(32, 32, 3, padding=(1,1))
        self.conv3 = nn.Conv2d(32, 64, 3, padding=(1,1))
        self.conv4 = nn.Conv2d(64, 64, 3, padding=(1,1))
        self.conv5 = nn.Conv2d(64, 128, 3, padding=(1,1))
        self.conv6 = nn.Conv2d(128, 128, 3, padding=(1,1))
        self.relu = nn.ReLU(inplace=True)
        self.flatten = nn.Flatten()
        self.maxpool = nn.MaxPool2d((2,2))
        self.l1 = nn.Linear(4*4*128, 128)
        self.l2 = nn.Linear(128, 10)
        self.dropout1 = nn.Dropout(0.2)
        self.dropout2 = nn.Dropout(0.3)
        self.dropout3 = nn.Dropout(0.4)

        self.features = nn.Sequential(
            self.conv1,
            self.relu,
            self.conv2,
            self.relu,
            self.maxpool,
            self.dropout1,
            self.conv3,
            self.relu,
            self.conv4,
            self.relu,
            self.maxpool,
            self.dropout2,
            self.conv5,
            self.relu,
            self.conv6,
            self.relu,
            self.maxpool,
            self.dropout3,
            )

        self.classifier = nn.Sequential(
            self.l1,
            self.relu,
            self.dropout3,
            self.l2
        )

    def forward(self, x):
        x1 = self.features(x)
        x2 = self.flatten(x1)
        x3 = self.classifier(x2)
        return x3

 

  • 정확도 83% (7% 정도 향상)

드롭아웃에서 고려할 점

  • 과학습에 강한 효과가 있지만, 학습에 소요되는 시간은 전보다 길어짐!!!

 

10.9 배치 정규화

모델에 배치 정규화 레이어 함수 추가

        self.bn1 = nn.BatchNorm2d(32)
        self.bn2 = nn.BatchNorm2d(32)
        self.bn3 = nn.BatchNorm2d(64)
        self.bn4 = nn.BatchNorm2d(64)
        self.bn5 = nn.BatchNorm2d(128)
        self.bn6 = nn.BatchNorm2d(128)

        self.features = nn.Sequential(
            self.conv1,
            self.bn1,
            self.relu,
            self.conv2,
            self.bn2,
            self.relu,
            self.maxpool,
            self.dropout1,
            self.conv3,
            self.bn3,
            self.relu,
            self.conv4,
            self.bn4,
            self.relu,
            self.maxpool,
            self.dropout2,
            self.conv5,
            self.bn5,
            self.relu,
            self.conv6,
            self.bn6,
            self.relu,
            self.maxpool,
            self.dropout3,
            )
  • 주의!
    • 배치 정규화 함수도 파라미터를 가지고 있음
    • 그러므로, 사용해야 할 곳마다 별개의 인스턴스를 정의해야 함!!

  • 정확도 87% (전보다 4% 향상)

 

10.10 데이터 증강 기법

# 훈련 데이터용: 정규화에 반전과 RandomErasing 추가
transform_train = transforms.Compose([
  transforms.RandomHorizontalFlip(p=0.5),
  transforms.ToTensor(),
  transforms.Normalize(0.5, 0.5), 
  transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)
])

# transfrom_train을 사용한 데이터셋 정의
train_set2 = datasets.CIFAR10(
    root = data_root, train = True, 
    download = True, transform = transform_train)

# traisform_train을 사용한 데이터로더 정의
batch_size = 100
train_loader2 = DataLoader(train_set2, batch_size=batch_size, shuffle=True)

# 새로운 훈련 데이터의 처음 50개 표시

# 난수 고정
torch_seed()

show_images_labels(train_loader2, classes, None, None)

데이터가 변형된 것을 확인할 수 있

  • 증강은 데이터가 많아져 좀 더 걸림
  • 정확도 89% (전보다 2% 향상)

모델의 출력 net에서 확률 값을 계산

  • 모델이 잘못 예측한 이미지 추출
# 잘못 예측한 38번째 데이터 추출
for images, labelsin test_loader:
	break
	image= images[37]
	label= labels[37]

# 이미지 확인
plt.figure(figsize=(3,3))
w= image.numpy().copy()
w2= np.transpose(w, (1, 2, 0))
w3= (w2+ 1)/2
plt.title(classes[label])
plt.imshow(w3)
plt.show()

# 예측 값 출력
image = image.view(1, 3, 32, 32)
image = image.to(device)
output = net(image)

# 라벨 별 확률 값 출력
probs = torch.softmax(output, dim=1)
probs_np = probs.data.to('cpu').numpy()[0]
values = np.frompyfunc(lambda x: f'{x:.04f}', 1, 1)(probs_np)
names = np.array(classes)
tbl = np.array([names, values]).T
print(tbl)