열심히 코딩 하숭!
10. 튜닝 기법 | 파이토치 딥러닝 프로그래밍 본문
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)
'프로그래밍 책 > 파이토치 딥러닝 프로그래밍' 카테고리의 다른 글
12. 사용자 정의 데이터를 활용한 이미지 분류 | 파이토치 딥러닝 프로그래밍 (1) | 2024.06.06 |
---|---|
11. 사전 학습 모델 이용하기 | 파이토치 딥러닝 프로그래밍 (1) | 2024.05.16 |
9. CNN을 활용한 이미지 인식 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.21 |
8. MNIST를 활용한 숫자 인식 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.21 |
4. 예측 함수 정의하기 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.17 |