열심히 코딩 하숭!
11. 사전 학습 모델 이용하기 | 파이토치 딥러닝 프로그래밍 본문
11 사전 학습 모델 활용하기
11.2 사전 학습 모델
사전 학습 모델
- 실제 실무에서는 학습을 밑바닥부터 시작하는 경우는 오히려 드물다
- 미리 학습이 되어있는 사전 학습 모델을 활용하여 정확도가 더욱 높은 모델을 만든다
파이토치에서 이미지 분류 모델로 사용 가능한 사전 학습 모델
- 아래의 모델들은 모두 224 x 224 화소의 이미지 데이터로 학습되었음
- 클래스명: 파이토치에서 실제 모델을 읽어올 때 사용하는 클래스명
- Top-1 에러: ILSVRC에서 과제를 위해 공개한 ImageNet이라는 1000개의 클래스를 분류하는 문제에서 검증에 대한 에러 비율
- Top-5에러: 정답이 모델의 예측 결과의 Top-5 안에 들지 못한 비율
- 사전 학습 모델 호출 예시
from torchvision import models
net = models.resnet18(pretrained = True)
11.3 파인 튜닝과 전이 학습
파인튜닝 (Fine tuning)
- 사전 학습 모델의 파라미터를 초깃값으로 사용
- 모든 레이어 함수를 사용해 학습하는 방법
- 학습 데이터를 대량으로 확보하고 있을 때 유리함
전이 학습 (Transfer Learning)
- 사전 학습 모델의 파라미터 중에 입력에 가까운 부분의 레이어 함수는 모두 고정
- 출력에 가까운 부분만 학습하는 방법
- 학습 데이터가 소량일 경우 유리함
11.4 적응형 평균 풀링 함수 (nn.AdaptiveAvgPool2d)
nn.AdaptiveAvgPool2d
- 이미지 화소 수에 의존하지 않고 이미지를 입력받을 수 있도록 한다
- Avg이므로 평균 값을 취함
- 아래의 그림과 같이
- 보통 풀링 함수) 파라미터로 ‘커널 사이즈’를 지정해줌
- Adaptive 함수) 파라미터로 ‘변환 후의 화소 수’를 지정해줌
- 모델 구현 코드
# nn.AdaptiveAvgPool2d 함수 정의
p = nn.AdaptiveAvgPool2d**((1,1))**
print(p)
# 선형 함수 정의
l1 = nn.Linear(32,10)
print(l1)
## 결과 ##
AdaptiveAvgPool2d(output_size=(1,1))
Linear(in_features=32, out_features=10, bias=True)
# 사전 학습 모델 시뮬레이션
inputs = torch.randn(100, 32, 16, 16)
m1 = p(inputs) # Adaptive 함수
m2 = m1.view(m1.shape[0], -1) # view 함수
m3 = l1(m2) # 선형 함수
# shape 확인
print(m1.shape)
print(m2.shape)
print(m3.shape)
## 결과 ##
torch.Size([100, 32, 1, 1])
torch.Size([100, 32])
torch.Size([100, 10])
- [100,32,16,16]라는 4계 텐서가 AdaptiveAvgPool2d((1,1)) 함수로 인해 [100,32,1,1]로 변환됨
- [100,32,1,1]라는 4계 텐서에 view(100, -1) 함수를 적용하면 (데이터 개수: 100 x 32개) [100, 32]라는 2계 텐서로 변환됨
- AdaptiveAvgPool2d에 의해 inputs의 shape이 아래와 같더라도 동일한 선형 함수(Linear(32, 10))를 사용할 수 있다
- [100, 32, 8, 8]
- [100, 32, 4, 4]
- 이를 통해 모델에서 첫번째 선형함수의 입력 차원수를 꼭 계산해야 하는 문제를 해결할 수 있다
- 사전 학습 모델의 입력 화소 수에 상관없이 사용할 수 있게 된다
11.5 데이터 준비
Transforms 정의
- 학습용과 테스트용의 Tranforms 정의가 다르다는 것 주의
- Resize(112): 원본 이미지를 해당 사이즈로 확대 또는 축소 변환하는 기능
- 32 x 32 사이즈인 CIFAR10에 대해 데이터를 확대하면 모델의 정확도가 높아진다는 사실 때문에 Resize를 구현한 것임
# 학습 데이터용
transform_train = transforms.Compose([
**transforms.Resize(112),**
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)
])
# 검증 데이터용
transform = transforms.Compose([
**transforms.Resize(112),**
transforms.ToTensor(),
transforms.Normalize(0.5, 0.5),
])
데이터로더 정의
- batch_size = 50 : 이전보다 거대한 신경망이기 때문에, GPU 메모리가 부족해지는 문제 발생
- 구글 코랩의 경우 배치 사이즈를 작게 설정하여 문제를 해결할 수 있다
# 배치사이즈 지정
batch_size = 50
# 훈련용 데이터로더
# 훈련용이기 때문에 셔플 = True
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
# 검증용 데이터로더
# 검증용이기 때문에 셔플은 굳이 필요하지 않음 = False
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)
11.6 ResNet-18 불러오기
ResNet-18 불러오기
- pretrained=True : 학습을 마친 파라미터도 동시에 불러오기
from torchvision import models
# 사전 학습 모델 불러오기
net = models.resnet18(pretrained=True) # pretrained=True: 학습을 마친 파라미터도 동시에 불러오기
모델 개요 1
print(net)
결과
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=1000, bias=True)
)
- avgpool / fc 함수가 정의되어 있는 것을 확인할 수 있다
📍 개요에 대한 해석 * layer 1, 2, 3, 4 … → Residual Block (조사 필요) * (0) (1) (2) … → Sequential 컨테이너 내의 모듈의 위치 |
모델 개요 2 - summary
net = net.to(device)
summary(net, (100, 3, 112, 112))
결과
==========================================================================================
Layer (type:depth-idx) **Output Shape** Param #
==========================================================================================
ResNet [100, 1000] --
├─Conv2d: 1-1 [100, 64, 56, 56] 9,408
├─BatchNorm2d: 1-2 [100, 64, 56, 56] 128
├─ReLU: 1-3 [100, 64, 56, 56] --
├─MaxPool2d: 1-4 [100, 64, 28, 28] --
├─Sequential: 1-5 [100, 64, 28, 28] --
│ └─BasicBlock: 2-1 [100, 64, 28, 28] --
│ │ └─Conv2d: 3-1 [100, 64, 28, 28] 36,864
│ │ └─BatchNorm2d: 3-2 [100, 64, 28, 28] 128
│ │ └─ReLU: 3-3 [100, 64, 28, 28] --
│ │ └─Conv2d: 3-4 [100, 64, 28, 28] 36,864
│ │ └─BatchNorm2d: 3-5 [100, 64, 28, 28] 128
│ │ └─ReLU: 3-6 [100, 64, 28, 28] --
│ └─BasicBlock: 2-2 [100, 64, 28, 28] --
│ │ └─Conv2d: 3-7 [100, 64, 28, 28] 36,864
│ │ └─BatchNorm2d: 3-8 [100, 64, 28, 28] 128
│ │ └─ReLU: 3-9 [100, 64, 28, 28] --
│ │ └─Conv2d: 3-10 [100, 64, 28, 28] 36,864
│ │ └─BatchNorm2d: 3-11 [100, 64, 28, 28] 128
│ │ └─ReLU: 3-12 [100, 64, 28, 28] --
├─Sequential: 1-6 [100, 128, 14, 14] --
│ └─BasicBlock: 2-3 [100, 128, 14, 14] --
│ │ └─Conv2d: 3-13 [100, 128, 14, 14] 73,728
│ │ └─BatchNorm2d: 3-14 [100, 128, 14, 14] 256
│ │ └─ReLU: 3-15 [100, 128, 14, 14] --
│ │ └─Conv2d: 3-16 [100, 128, 14, 14] 147,456
│ │ └─BatchNorm2d: 3-17 [100, 128, 14, 14] 256
│ │ └─Sequential: 3-18 [100, 128, 14, 14] 8,448
│ │ └─ReLU: 3-19 [100, 128, 14, 14] --
│ └─BasicBlock: 2-4 [100, 128, 14, 14] --
│ │ └─Conv2d: 3-20 [100, 128, 14, 14] 147,456
│ │ └─BatchNorm2d: 3-21 [100, 128, 14, 14] 256
│ │ └─ReLU: 3-22 [100, 128, 14, 14] --
│ │ └─Conv2d: 3-23 [100, 128, 14, 14] 147,456
│ │ └─BatchNorm2d: 3-24 [100, 128, 14, 14] 256
│ │ └─ReLU: 3-25 [100, 128, 14, 14] --
├─Sequential: 1-7 [100, 256, 7, 7] --
│ └─BasicBlock: 2-5 [100, 256, 7, 7] --
│ │ └─Conv2d: 3-26 [100, 256, 7, 7] 294,912
│ │ └─BatchNorm2d: 3-27 [100, 256, 7, 7] 512
│ │ └─ReLU: 3-28 [100, 256, 7, 7] --
│ │ └─Conv2d: 3-29 [100, 256, 7, 7] 589,824
│ │ └─BatchNorm2d: 3-30 [100, 256, 7, 7] 512
│ │ └─Sequential: 3-31 [100, 256, 7, 7] 33,280
│ │ └─ReLU: 3-32 [100, 256, 7, 7] --
│ └─BasicBlock: 2-6 [100, 256, 7, 7] --
│ │ └─Conv2d: 3-33 [100, 256, 7, 7] 589,824
│ │ └─BatchNorm2d: 3-34 [100, 256, 7, 7] 512
│ │ └─ReLU: 3-35 [100, 256, 7, 7] --
│ │ └─Conv2d: 3-36 [100, 256, 7, 7] 589,824
│ │ └─BatchNorm2d: 3-37 [100, 256, 7, 7] 512
│ │ └─ReLU: 3-38 [100, 256, 7, 7] --
├─Sequential: 1-8 [100, 512, 4, 4] --
│ └─BasicBlock: 2-7 [100, 512, 4, 4] --
│ │ └─Conv2d: 3-39 [100, 512, 4, 4] 1,179,648
│ │ └─BatchNorm2d: 3-40 [100, 512, 4, 4] 1,024
│ │ └─ReLU: 3-41 [100, 512, 4, 4] --
│ │ └─Conv2d: 3-42 [100, 512, 4, 4] 2,359,296
│ │ └─BatchNorm2d: 3-43 [100, 512, 4, 4] 1,024
│ │ └─Sequential: 3-44 [100, 512, 4, 4] 132,096
│ │ └─ReLU: 3-45 [100, 512, 4, 4] --
│ └─BasicBlock: 2-8 [100, 512, 4, 4] --
│ │ └─Conv2d: 3-46 [100, 512, 4, 4] 2,359,296
│ │ └─BatchNorm2d: 3-47 [100, 512, 4, 4] 1,024
│ │ └─ReLU: 3-48 [100, 512, 4, 4] --
│ │ └─Conv2d: 3-49 [100, 512, 4, 4] 2,359,296
│ │ └─BatchNorm2d: 3-50 [100, 512, 4, 4] 1,024
│ │ └─ReLU: 3-51 [100, 512, 4, 4] --
├─AdaptiveAvgPool2d: 1-9 **[100, 512, 1, 1]** --
├─Linear: 1-10 **[100, 1000]** 513,000
==========================================================================================
Total params: 11,689,512
Trainable params: 11,689,512
Non-trainable params: 0
Total mult-adds (G): 48.54
==========================================================================================
Input size (MB): 15.05
Forward/backward pass size (MB): 1009.64
Params size (MB): 46.76
Estimated Total Size (MB): 1071.46
==========================================================================================
- AdaptiveAvgPool2d 함수로 인해, [100, 512, 4, 4] 였던 shape이 [100, 512, 1, 1]로 변했다는 것을 알 수 있음
선형 함수 fc 확인
print(net.fc)
print(net.fc.in_features)
Linear(in_features=512, out_features=1000, bias=True)
512
- 입력: 512 / 출력 1000
11.7 최종 레이어 함수 교체하기
최종 레이어 함수 교체
- 파인 튜닝과 전이 학습 모두, 불러온 사전 학습 모델의 마지막 단을 목적에 맞는 선형 함수로 교체할 필요가 있음
- 현재 사전 학습 모델의 마지막 단의 선형 함수 출력은 1000차원으로 설정돼 있음 → 10차원으로 교체
torch_seed() # 난수 고정
# 최종 레이어 함수의 입력 차원수 확인
fc_in_features = net.fc.in_features
# 최종 레이어 교체 함수
**net.fc = nn.Linear(fc_in_features, n_output)**
- fc_in_features → 원래의 선형 함수 입력 차원수
- n_output → 10으로 지정하고 대입
# 최종으로 작성된 모델을 사용해 손실 정의하고 그래프 시각화
criterion = nn.CrossEntropyLoss()
loss = eval_loss(test_loader, device, net, criterion)
g = make_dot(loss, params=dict(net.named_parameters())
display(g)
- 의도한대로 클래스 수가 10으로 설정됨
11.8 학습과 결과 평가
초기 설정
# 난수 고정
torch_seed()
# 사전 학습 모델 불러오기
# pretraind = True로 **학습을 마친 파라미터도 함께 불러오기**
net = models.resnet18(pretrained = True)
# 최종 레이어 함수 입력 차원수 확인
fc_in_features = net.fc.in_features
# 최종 레이어 함수 교체
net.fc = nn.Linear(fc_in_features, n_output)
# GPU 사용
net = net.to(device)
# 학습률
lr = 0.001
# 손실 함수 정의
criterion = nn.CrossEntropyLoss()
# 최적화 함수 정의
optimizer = optim.SGD(net.parameters(), lr=lr, momentum=0.9)
# history 파일 초기화
history = np.zeros((0, 5))
- 사전 학습 모델을 사용한다면, 각 레이어 함수의 파라미터는 어느정도 자리 잡은 상태임
⇒ 이런 조건에서는, Adam같이 복잡한 최적화 함수보다는 비교적 간단한 알고리즘을 사용하는 것이 좋다
- 그래서 이번 코드에서는 momentum 옵션이 추가된 optim.SGD 함수를 최적화에 사용!
학습
- 반복 횟수는 5회로 설정
# 학습
num_epochs = 5
history = fit(net, optimizer, criterion, num_epochs,
train_loader, test_loader, device, history)
평가
# 결과 요약
evaluate_history(history)
- 훈련 - 파란색 / 검증 - 검은색
- 5회 반복만으로 94%의 정확도를 달성
11.9 VGG-19-BN 활용하기
위에 있는 코드랑 거의 비슷
모델 불러오기
from torchvision import models
net = models.vvg19_bn(pretrained=True)
모델 구조
... 생략 ...
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
(2): Dropout(p=0.5, inplace=False)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace=True)
(5): Dropout(p=0.5, inplace=False)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
- 선형 함수는 총 3개
print(net.classifier[6])
- 맨 마지막 선형 함수임
최종 레이어 함수 교체
# 난수 고정
torch_seed()
# 최종 레이어 함수 교체
in_features = net.classifier[6].in_features
**net.classifier[6] = nn.Linear(in_features, n_output)**
# features 마지막의 MaxPool2d 제거
net.features = net.features[:-1]
# AdaptiveAvgPool2d 제거
net.avgpool = nn.Identity()
=>
features 마지막의 MaxPool2d 제거:
net.features = net.features[:-1]는 모델의 특성 추출 부분(features)에서
***마지막 MaxPool2d 레이어를 제거합니다.***
이는 모델의 아키텍처를 조정하여 특성 맵의 크기를 조절하거나,
다른 특성 처리 방식을 적용하고자 할 때 유용합니다.
AdaptiveAvgPool2d 제거:
net.avgpool = nn.Identity()는 평균 풀링(AdaptiveAvgPool2d) 레이어를
신원 함수(Identity)로 교체합니다.
nn.Identity 레이어는 입력을 그대로 출력으로 전달합니다.
***이 변경은 모델에서 평균 풀링 단계를 건너뛰고 싶을 때 사용됩니다.***
평균 풀링을 제거함으로써, 모델은 풀링에 의해 감소되기 전의 더 높은 차원의
특성 맵을 유지할 수 있습니다.
학습 및 평가
num_epochs = 5
history = fit(net, optimizer, criterion, num_epochs,
train_loader, test_loader, device, history)
# 결과 요약
evaluate_history(history)
- 정확도 95.7%
# 이미지와 정답, 예측 결과를 함께 표시
show_images_labels(test_loader, classes, net, device)
'프로그래밍 책 > 파이토치 딥러닝 프로그래밍' 카테고리의 다른 글
12. 사용자 정의 데이터를 활용한 이미지 분류 | 파이토치 딥러닝 프로그래밍 (1) | 2024.06.06 |
---|---|
10. 튜닝 기법 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.21 |
9. CNN을 활용한 이미지 인식 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.21 |
8. MNIST를 활용한 숫자 인식 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.21 |
4. 예측 함수 정의하기 | 파이토치 딥러닝 프로그래밍 (1) | 2024.04.17 |