전이학습 기초
전이학습이란?
전이학습
- 특정 데이터로 훈련된 모델을 다른 데이터 형태로 학습
전이학습의 목적
- 훈련할 데이터가 충분치 않다.
- 학습된 모델을 사용하자
전이학습 모델 구성
- head: classification(분류)
- backbone: Filtering(필터링)
전이학습 방법
제한사항
- 입력이미지 크기와 출력레이블이 모델과 맞지 않는다.
전이학습 문제
- 입력 이미지가 바뀔때, conv층은 영향 받지 않으나, 완전연결층의 첫번째 층은 영향 받음
- 출력레이블이 바뀔 때, 나머지 층은 영향받지 않으나, 완전연결층의 마지막 층만 영향 받음.
전이학습 해결책
- 입력이미지 크기와 레이블 수를 학습 데이터에 맞추던지
- 입력이미지 크기는 맞출 수 있지만, 레이블 수 맞추기는 불가능
- 모델의 완전연결층 부분을 재설계 하던지
- 모델의 완전연결층(head, 분류) 부분 재설계
- 입력이미지 크기를 바꾸면 backbone(필터링)은 그대로,
head(분류)는 재설계 해야한다.
전이학습 과정
- head만: Mydata로 head만 학습
- fine-tuning: Mydata로 모델 전체 재학습
Pytorch CNN 전이학습
dir(torchvision.models) # 참고사항: 모델 리스트 조회
- 모델: resnet50, googlenet
- 데이터셋: CIFAR-10
전처리
# 데이터 전처리 및 증강
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(), #증강
transforms.Resize(224), # ResNet과 GoogLeNet의 입력 크기에 맞춤
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
# 전처리
transform_test = transforms.Compose([
transforms.Resize(224), #레즈넷 사이즈에 맞춤
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
데이터셋 로드
# CIFAR-10 데이터셋 로드 - [ (데이터, 레이블) * batch_size ]
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform_train)
trainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform_test)
testloader = DataLoader(testset, batch_size=64, shuffle=False, num_workers=2)
데이터셋 레이블
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
DataLoader
trainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)
# image 64개, Label 64개 쌍으로 묶어준다.
모델의 특징
ResNet50 모델의 특징
- 입력이미지: 224, 224, 3
- head
- in_features: 2048
- avgpool 이 있으므로 바꿀 필요 없다.
- out_features: 1000
GoogLeNet 모델의 특징
- 입력이미지: 224, 224, 3
- head
- in_features: 1024
- avgpool 이 있으므로 바꿀 필요 없다.
- out_features: 1000
모델 정의
# resnet50
resnet_model = resnet50(pretrained=True) # imagenet으로 사전학습된 상태 가져오기
num_ftrs = resnet_model.fc.in_features # 2048 그대로 사용
resnet_model.fc = nn.Linear(num_ftrs, 10) # 2048, 10으로 선언
# googlenet
googlenet_model = googlenet(pretrained=True)
num_ftrs = googlenet_model.fc.in_features # 1024 그대로 사용
googlenet_model.fc = nn.Linear(num_ftrs, 10) # 1024, 10으로 선언
학습 함수
def train_model(model, criterion, optimizer, num_epochs=5):
# criterion(오차함수), optimizer(기울기 최적화)
best_acc = 0.0
train_losses = [] # 시각화를 그리기 위해 모아줌
train_accuracies = []
for epoch in range(num_epochs): # epoch 반복
model.train() # 트레인모드 변경: 모드에 따라 모델에서 on/off 하는 부분이 있다.
running_loss = 0.0
for i, (inputs, labels) in enumerate(trainloader): # batch 반복
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad() # 기울기 초기화
outputs = model(inputs) # 예측
loss = criterion(outputs, labels) # 오차확인 예측값과 레이블비교
loss.backward() # 기울기 계산
optimizer.step() # weight 수정
running_loss += loss.item()
if i % 100 == 99:
# 1 epoch, 100 번째 batch 평균 오차...
# 1 epoch, 800 번째 batch 평균 오차
# 2 epoch....
print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f}')
train_losses.append(running_loss / 100) # 훈련 평균 수집
running_loss = 0.0
# train 정확도 계산
model.eval() #평가모드 변경
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in trainloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1) # value(=확률값), index(=추론한 label)
total += labels.size(0) # 1개 batch의 갯수만큼 더함. 전체 데이터 갯수
correct += (predicted == labels).sum().item() # 참이면 1 이므로, 맞춘 갯수를 더한다.
acc = 100 * correct / total # 정확도 = 맞춘데이터/전체데이터 비율
train_accuracies.append(acc) # 훈련 정확도 수집
print(f'Accuracy on test images: {acc}%')
return train_losses, train_accuracies
학습
# ResNet 학습
criterion = nn.CrossEntropyLoss() # 오차(cost)함수
optimizer = optim.SGD(resnet_model.parameters(), lr=0.001, momentum=0.9) #최적화 알고리즘, momentum(이전기울기 누적)
resnet_losses, resnet_accuracies = train_model(resnet_model.to(device), criterion, optimizer)
# GoogLeNet 학습
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(googlenet_model.parameters(), lr=0.001, momentum=0.9)
googlenet_losses, googlenet_accuracies = train_model(googlenet_model.to(device), criterion, optimizer)
결과 시각화
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(resnet_losses, label='ResNet')
plt.plot(googlenet_losses, label='GoogLeNet')
plt.title('Training Loss')
plt.xlabel('Iterations (x100)')
plt.ylabel('Loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(resnet_accuracies, label='ResNet')
plt.plot(googlenet_accuracies, label='GoogLeNet')
plt.title('Train Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.tight_layout()
plt.show()
기본 평가지표
Accuracy (정확도)
- = ( 맞춘갯수 / 전체갯수 ) * 100
Accuracy가 높다고 성능이 좋은걸까?
- 데이터의 레이블이 불균형(embalanced) 하다면, Accuracy의 신뢰도가 떨어진다.
- 암환자 1, 정상 99명일 경우, 모두 정상이라고 판정하면 accuracy는 99점이기 때문이다.
- 데이터 레이블이 불균형(embalanced)하면, 다른 평가지표를 사용하자.
Pasitive 와 Negative
- Pasitive: 밝혀야 하는 것, 찾아야 하는 것.
- Negative: 나머지
- 채점 - 예측
- TP: True - Pasitive
- TN: True - Negative
- FP: False - Pasitive
- FN: False - Negative
Precision (예측 중에 맞춘 것)
- Predict Pasitive 중에 맞춘 갯수
- P = TP / (TP + FP)
- 예측을 틀리면 안될 때, P를 조절한다면?
- FP를 줄이기 위해 P를 줄인다.
- FP가 줄어들지만, FN이 많아진다.
Recall (실제 중에 맞춘 것)
- Real Pasitive 중에 맞춘 갯수
- R = TP / ( TP + FN )
- 실제를 틀리면 안될 때, P를 조절한다면?
- FN을 줄이기 위해 P를 늘린다.
- FN은 줄어들지만, FP가 많아진다.
- 예시1)
- 암판정 봇: 의심환자 명단 의사에게 전달
- 의사는 100명 다 확인 -> 의심환자 50명만 확인 하고 싶다.
- 의심P 50명(P54/N1), 정상N(P1/N49)
- 의심하지 않은 것(정상N) 중에서 암환자가 나오면 큰 일이다.
F1 Score
- Precision 과 Recall의 조화평균
- 2 * (서로의 곱) / (서로의 합)
- F1 = 2 * ( Precision * Recall ) / ( Precision + Recall )
합리적인 모델은 무엇일까?
- 인간이 사용해서 합리적이라고 느끼는 것.
- 인간의 정성적인 평가.
- 인간의 정성적인 평가에 가까운 정량적인 평가지표를 바꾸는 것이 해결 과제다.
- gpt의 평가지표(합리적이라 느끼는 정성적 평가)는 무엇일까?
ROC_AUC ( = TPR / FPR )
- TPR: 실제 Pasitive 대비 TP의 비율 (=Recall, Sensitibity)
- FPR: 실제 Negative 대비 FP의 비율 (= 1-특이도)
- ROC_AUC 차트
- FPR은 낮을 수록 좋고, TPR은 높을 수록 좋다.
- X=FPR, Y=TPR
- TNR (특이도)
- TNR: 실제 Negative 대비 TN의 비율 (= 1-FPR)
Tags:
AI개발_교육