출처: 차근차근 실습하며 배우는 파이토치 딥러닝 프로그래밍
사전학습 모델은 다음과 같이 나타낼 수 있다
from torchvision import models
net= models.resnet18(pretrained=True)
파이토치 홈페이지에서 사용가능한 사전학습 모델들이 나와있다.
사전학습 모델을 이용해서 효율적으로 학습하는 방법으로 파인튜닝과 전이학습이 있다.
파인튜닝-> 사전학습 모델의 파라미터를 초깃값으로 사용하지만, 모든 레이어 함수를 사용해 학습한다.
전이 학습 -> 사전 학습 모델의 파라미터 중에서 입력에 가까운 부분의 레이어 함수는 모두 고정, 출력에 가까운 부분만을 학습
적응형 평균 풀링 함수(nn.AdaptiveAvgPool2d) ->
적응형 평균 풀링 함수는 이미지의 화소수에 의존하지 않고도 이미지를 입력받을 수 있는 모델을 만들기 위함이다.
p = nn.AdaptiveAvgPool2d((1,1))
print(p)
l1 = nn.Linear(32, 10)
print(l1)
풀링 함수는 파라미터는 커널 사이즈(필터 사이즈)를 지정한다. 만약 nn.AdaptiveAvgPool2d((2,2))로 작성된 풀링 함수를 통과시키면 가로세로 해상도는 모두절반이 된다. nn.AdaptiveXXXPool2d에서 지정하는 파라미터는 변환 후의 화소 수가 된다. n.AdaptiveAvgPool2d((1,1))은 모든 채널의 결과가 1x1 화소를 갖는다. XXX는 avg이므로 평균값을 취하는 처리를 수행하게 된다.
사전학습 모델 시뮬레이션
inputs = torch.randn(100,32,16,16)
m1 = p(inputs)
m2 = m1.view(m1.shape[0],-1)
m3 = l1(m2)
print(m1.shape)
print(m2.shape)
print(m3.shape)
adaptiveavgpool2d를 거친후에 화소수가 16x16에서 1x1로 한개의 화소만 가지는 형태로 변하였다. 이후 view 함수를 거쳐 선형함수에 입력하기 위한 shape으로 변환된다.
이전 CNN 모델을 작성할 때, 첫번째 선형 함수의 입력 차원수를 어떤 방식으로든지 계산할 필요가 있다라고 하였다. 이 문제를 마지막단계에 적응형 풀링 합성 함수를 사용하면 해결된다. 사전학습 모델이 입력 화소수에 상관없이 사용할 수 있는 것이다.
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 분류 클래스 수는 10
n_output = len(classes)
# 학습 데이터용 : 정규화에 반전과 RandomErasing 추가
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)
])
resize(112)는 원본 이미지를 지정된 화소수로 확대 또는 축소 변환하는 기능이다. 데이터를 확대하면 모델의 정확도가 높아진다. 112라는 값은 사전학습 모델을 학습할 때 사용한 입력 이미지의 화소수인 224의 절반이다.
# 배치 사이즈 지정
batch_size = 50
# 데이터로더
# 훈련용 데이터로더
# 훈련용이므로 셔플을 True로 설정함
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
# 검증용 데이터로더
# 검증용은 셔플이 필요하지 않음
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)
배치 사이즈를 50으로 하여 구글 코랩 환경에서 학습이 실행될 수 있게 한다.
# 라이브러리 임포트
from torchvision import models
# 사전 학습 모델 불러오기
# pretraind = True로 학습을 마친 파라미터를 동시에 불러오기
net = models.resnet18(pretrained = True)
사전학습 모델인 resnet-18을 불러온다. pretrained=True 로 설정함
모델의개요 마지막 부분을 보면 적응형 풀링 함수가 사용되었고, 선형함수는 fc라는 이름으로 한군데만 정의된 것을 할 수 있다.
# 모델 개요 표시 2
net = net.to(device)
summary(net,(100,3,112,112))
끝에 [100,512,4,4] 였던 shape이 adaptiveAvgpool2d 함수 호출 이후 [100,512,1,1]로 변하였다. summary(net,(100,3,224,224))로 설정해도 출력은 똑같이 [100,512,1,1]가 출력된다. 위의 사전학습 모델이 입력 화소수에 상관없이 사용할 수 있는 것을 설명하는 증거가 된다.
print(net.fc)
print(net.fc.in_features)
fc는 nn.Linear이며 입력은 512, 출력은 1000이다.
최종 레이어 함수 교체하기
파인튜닝 전이학습 둘다 마지막 단을 목적에 맞는 선형 함수로 교체해야한다. 이를 최종 레이어 함수교체라고 함
# 난수 고정
torch_seed()
# 최종 레이어 함수의 입력 차원수 확인
fc_in_features = net.fc.in_features
# 최종 레이어 함수 교체
net.fc = nn.Linear(fc_in_features, n_output)
in_features는 앞에서와 같은값, out_features는 n_output값(10)을 지정해서 생성하고 이를 net.fc에 대입하여 레이어 함수를 교체하였다.
학습 결과 평가
# 난수 고정
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 이라고 하였지만, 사전학습 모델에서는 각 레이어 함수의 파라미터는 어느정도 최적화가 이루어진 상태이다. 이런 조건에서 학습할때는 복잡한 최적화 함수보다 비교적 간단한 알고리즘을 사용하는 것이 좋다.
모멘텀 옵션을 추가한 optim.SGD 함수를 최적화 함수로 사용한다.
# 학습
num_epochs = 5
history = fit(net, optimizer, criterion, num_epochs,train_loader, test_loader, device, history)
# 결과 요약
evaluate_history(history)
코랩으로 돌릴때 한 에폭당 학습시간이 40분이나 걸려서 깃허브의 결과를 참고하였다. 사전학습 모델사용만으로 정확도를 94%나 얻을 수 있었다.
VGG-19-BN을 이용한 파인튜닝
# 사전 학습 모델 불러오기
from torchvision import models
net = models.vgg19_bn(pretrained = True)
print(net)
모델 구조의 마지막 출력 결과를 보면 선형함수가 총 3개 있다.
print(net.classifier[6])
Linear(in_features=4096, out_features=1000, bias=True)
가장 마지막 선형 함수는 classifier[6]로 참조하여 나타낸다. input은 4096, output은 1000이다.
# 난수 고정
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()
가장 밑 두줄 Maxpool2d와 AdaptiveAvgpool2d를 제거하였다.
+ 범용적인 사전학습 모델을 작성하는 법
다른 플랫폼에서 사용가능한 모델의 표준 형식은 ONNX가 있다. 파이토치 본연의 VGG-19-BN 모델을 ONNX로 익스포트 하면 에러가 발생한다. 왜냐하면 레이어함수인 AdaptiveAvgpool2d 함수가 ONNX로 익스포트 되지 않기 때문이다. 그러므로 AdaptiveAvgPool2d 함수를 삭제하여 모델을 익스포트한다.
# 원본 데이터 사이즈의 경우(배치사이즈 100)
net = net.to(device)
summary(net, (100, 3, 224, 224))
VGG-19-BN의 중간 텐서를 확인하면 다음과 같다.
데이터 shape에는 변화가 없다. AdaptiveAvgPool2d 레이어 함수가 (3,224,224) 사이즈를 가진 입력 텐서의 경우 아무것도 처리하지 않음을 의미한다.
# 실습용 데이터 사이즈의 경우(배치사이즈 100)
summary(net, (100, 3, 112, 112))
위에서 실습으로 사용한 입력데이터로 (3,112,112) 사이즈를 이용하면 위와같은 결과가 나온다.
ReLU52를 거칠때 이미 [100,512,7,7] 사이즈 텐서로 변했는데, 그다음 MaxPool2d-53 에서 [100,512,3,3] 사이즈로 변경했다가 적응형 풀링함수에서 [100,512,7,7] 로 사이즈가 돌아왔다. Maxpool2dd-53과 이어지는 AdaptiveAvgPool2d를 동시에 없애고, ReLU52의 출력을 직접 Linear55로 이을수가 있다면 이전에 설명한 첫번째 선형함수 사이즈 문제에 영향을 끼치지 않으면서, AdaptiveAvgPool2d 함수가 없는 사전학습 모델을 사용할 수 있게 되는것이다.
# 난수 고정
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]
print(net.features)
features의 마지막 요소 MaxPool2d를 제거해준다.
본래 존재하던 MaxPool2d-53 함수가 제거되었다.
다음으로 AdaptiveAvgPool2d-54를 제거해준다.
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
이 부분은 본래 forward 함수 내부에서 avgpool 함수를 호출 한 것으로 예상되며, 이 부분은 변경할 수 없다. 따라서 AdaptiveAvgPool2d가 위치한 부분을 아무것도 하지않는 함수(nn.Identity)로 치환한다.
# avgpool에 위치한AdaptiveAvgPool2d을 아무것도 하지 않는 함수(nn.Identity)로 치환
net.avgpool = nn.Identity()
# 실습용 데이터 사이즈로 중간 텐서 확인(배치사이즈 100)
net = net.to(device)
summary(net,(100, 3, 112, 112))
ReLU의 출력인 [100,512,7,7] 사이즈의 텐서가 아무것도 하지 않는 Identity:1-2 함수를 거쳐 Linear53 함수로 직접 입력되고 있다.
'Machine Learning' 카테고리의 다른 글
MNIST 활용한 숫자 인식 (1) | 2024.04.15 |
---|---|
다중분류 with pytorch (0) | 2024.04.12 |
이진 분류 with pytorch (0) | 2024.04.11 |
선형회귀 with Pytorch (0) | 2024.04.08 |
예측 함수 정의 with pytorch (1) | 2024.04.06 |