대표적인 CNN모델 : VGG, ResNet, Inception
torchvision.models.vgg16()
torchvision.models.resnet18()
torchvision.models.inception_v3()
# ❶ CIFAR10 데이터셋을 불러옴
training_data = CIFAR10(
root="./", # CIFAR-10 데이터를 내려받을 경로, "./" 는 현재 디렉토리를 의미함.
train=True, # 학습용 데이터 : True // 평가용 데이터 : False
download=True,
transform=ToTensor()) # 이미지를 파이토치 텐서로 변환.
test_data = CIFAR10(
root="./",
train=False,
download=True,
transform=ToTensor())
import matplotlib.pyplot as plt
import torchvision.transforms as T
from torchvision.datasets.cifar import CIFAR10
from torchvision.transforms import Compose
from torchvision.transforms import RandomHorizontalFlip, RandomCrop
transforms = Compose([ # ❶ 데이터 전처리 함수들, Compose 객체 안에 차례대로 넣어주면 순서대로 이미지에 적용됨.
T.ToPILImage(),
RandomCrop((32, 32), padding=4), # ➋ 랜덤으로 이미지 일부 제거 후 패딩. (32, 32)는 최종 출력 크기를 의미,
RandomHorizontalFlip(p=0.5), # ➌ y축을 기준으로 대칭, p는 이미지가 대칭될 확률을 의미
])
Compose([tf])
: 전처리 함수 tf를 입력받아 차례대로 실행
RandomCrop(size, padding = )
: padding크기만큼 0으로 패딩 후 size 크기만큼 이미지의 일부를 랜덤으로 뽑아냄.
RandomHorizontalFlip(p)
: p확률로 이미지를 좌우대칭
import matplotlib.pyplot as plt
import torchvision.transforms as T
from torchvision.datasets.cifar import CIFAR10
from torchvision.transforms import Compose
from torchvision.transforms import RandomHorizontalFlip, RandomCrop, Normalize
transforms = Compose([
T.ToPILImage(),
RandomCrop((32, 32), padding=4),
RandomHorizontalFlip(p=0.5),
T.ToTensor(),
# ➊ 데이터 정규화
Normalize(mean=(0.4914, 0.4822, 0.4465), std=(0.247, 0.243, 0.261)),
T.ToPILImage()
])
training_data = CIFAR10(
root="./",
train=True,
download=True,
transform=transforms)
test_data = CIFAR10(
root="./",
train=False,
download=True,
transform=transforms)
Normalize(mean, std)
: 평균 mean, 표준편차 std를 갖는 정규분포가 되도록 정규화
❓그렇다면 평균과 표준편차는 어떻게 구할까❓
import torch
training_data = CIFAR10(
root="./",
train=True,
download=True,
transform=ToTensor())
# item[0]은 이미지, item[1]은 정답 레이블
imgs = [item[0] for item in training_data]
# imgs는 여러 이미지를 담고 있는 리스트. 파이토치에서 사용하려면 리스트를 텐서로 바꿔야함.
# --> 이미지가 여러개 담긴 '리스트' --> 이미지 여러개를 담는 '텐서'로 변환해야함.
# ❶imgs를 하나로 합침
imgs = torch.stack(imgs, dim=0).numpy()
# rgb 각각의 평균
mean_r = imgs[:,0,:,:].mean()
mean_g = imgs[:,1,:,:].mean()
mean_b = imgs[:,2,:,:].mean()
print(mean_r,mean_g,mean_b)
# rgb 각각의 표준편차
std_r = imgs[:,0,:,:].std()
std_g = imgs[:,1,:,:].std()
std_b = imgs[:,2,:,:].std()
print(std_r,std_g,std_b)
stack(tensor, dim)
: tensor를 dim 방향으로 합쳐줌.
ex) (224, 224)크기의 텐서들을 dim = 0 방향으로 텐서 세개를 합치면 (3, 224, 224)모양의 텐서가 됨.
MaxPool2d(kernel, stride)
: 최대 풀링. kernel은 커널 크기, stride는 커널이 이동하는 거리
Conv2d(in, out, kernel, stride)
: 합성곱을 계산. in : 입력 채널 개수, out : 출력 채널 개수, kernel : 커널 크기
import torch
import torch.nn as nn
class BasicBlock(nn.Module): # ❶ 기본 블록을 정의합니다.
# __init__ : 모듈의 초기화를 담당, __init__()안에 기본블록을 구성하는 모든 층을 정의함.
def __init__(self, in_channels, out_channels, hidden_dim):
# ❷ nn.Module 클래스의 요소 상속
super(BasicBlock, self).__init__()
# ❸ 합성곱층 정의
# in_channels : 입력 채널 개수 // out_channels : 출력 채널 개수 // hidden_dim : 은닉 채널 개수
self.conv1 = nn.Conv2d(in_channels, hidden_dim,
kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(hidden_dim, out_channels,
kernel_size=3, padding=1)
self.relu = nn.ReLU()
# stride는 커널의 이동 거리를 의미합니다.
self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 이미지 크기가 절반으로 줄어듦.
def forward(self, x): # ➍ 기본블록의 순전파를 계산하는 함수.
x = self.conv1(x)
x = self.relu(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool(x)
return x
flatten(A, start_dim)
: 텐서 A를 1차원으로 풀어준다. start_dim 파라미터를 이용해 몇번째 차원부터 풀어줄지 결정할 수 있다.class CNN(nn.Module):
def __init__(self, num_classes): # num_classes는 클래스의 개수를 의미합니다
super(CNN, self).__init__()
# ❶ 합성곱 기본 블록의 정의
self.block1 = BasicBlock(in_channels=3, out_channels=32, hidden_dim=16) # 이미지는 3개의 채널(R, G, B)로 이루어졌기 때문에 처음 입력 채널은 3이다.
self.block2 = BasicBlock(in_channels=32, out_channels=128, hidden_dim=64)
self.block3 = BasicBlock(in_channels=128, out_channels=256,
hidden_dim=128)
# ❷ 분류기 정의
self.fc1 = nn.Linear(in_features=4096, out_features=2048)
self.fc2 = nn.Linear(in_features=2048, out_features=256)
self.fc3 = nn.Linear(in_features=256, out_features=num_classes)
# ❸ 분류기의 활성화 함수
self.relu = nn.ReLU()
def forward(self, x):
x = self.block1(x)
x = self.block2(x)
x = self.block3(x) # 출력 모양: (-1, 256, 4, 4)
x = torch.flatten(x, start_dim=1) # ➍ 2차원 특징맵을 1차원으로
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
return x
from torch.utils.data.dataloader import DataLoader
from torch.optim.adam import Adam
# 데이터 전처리
transforms = Compose([
RandomCrop((32, 32), padding=4), # ❶ 랜덤 크롭핑
RandomHorizontalFlip(p=0.5), # ❷ y축으로 뒤집기
ToTensor(), # ❸ 텐서로 변환
# ❹ 이미지 정규화
Normalize(mean=(0.4914, 0.4822, 0.4465), std=(0.247, 0.243, 0.261))
])
# ❶ 학습 데이터와 평가 데이터 불러오기
training_data = CIFAR10(root="./", train=True, download=True, transform=transforms)
test_data = CIFAR10(root="./", train=False, download=True, transform=transforms)
# ❷ 데이터로더 정의
train_loader = DataLoader(training_data, batch_size=32, shuffle=True)
test_loader = DataLoader(test_data, batch_size=32, shuffle=False)
# ❸ 학습을 진행할 프로세서 설정
device = "cuda" if torch.cuda.is_available() else "cpu"
# ➍ CNN 모델 정의
model = CNN(num_classes=10)
# ➎ 모델을 device로 보냄
model.to(device)
파이토치의 to(device)
메서드는 device로 데이터, 텐서, 모델을 보내주는 역할을 함. 모델의 device와 데이터의 device가 일치하지 않으면 오류가 발생하므로 유의하자!
# ❶ 학습률 정의
lr = 1e-3
# ❷ 최적화 기법 정의
optim = Adam(model.parameters(), lr=lr)
# 학습 루프 정의
for epoch in range(100):
for data, label in train_loader: # ➌ 데이터 호출
optim.zero_grad() # ➍ 기울기 초기화
preds = model(data.to(device)) # ➎ 모델의 예측
# ➏ 예측값을 이용해 손실을 계산하고 오차를 역전파한 뒤 최적화 진행.
loss = nn.CrossEntropyLoss()(preds, label.to(device))
loss.backward()
optim.step()
if epoch==0 or epoch%10==9: # 10번마다 손실 출력
print(f"epoch{epoch+1} loss:{loss.item()}")
# 모델 저장
torch.save(model.state_dict(), "CIFAR.pth")
optim.zero_grad()
: 기울기 0으로 초기화. 초기화 해주지 않으면 기울기가 쌓여 무한대로 발산하기 때문에 반드시 초기화해야함.
CrossEntropyLoss(A, B)
: A와 B의 크로스엔트로피 계산
# 모델 불러오기
model.load_state_dict(torch.load("CIFAR.pth", map_location=device))
num_corr = 0
with torch.no_grad():
for data, label in test_loader:
output = model(data.to(device))
preds = output.data.max(1)[1]
corr = preds.eq(label.to(device).data).sum().item()
num_corr += corr
print(f"Accuracy:{num_corr/len(test_data)}")
약 83%의 정확도
model.load_state_dict(torch.load("MNIST.pth", map_location=device))
: 모델파일을 불러온다.no_grad()
: 기울기 계산 X, 학습할 때는 가중치를 업데이트하는데 기울기가 필요하지만, 평가는 가중치를 바꿀 필요 없으므로 기울기 계산 Xmax(1)[1]
: 가장 높은 값을 갖는 위치 반환.eq()
: 값이 같으면 1, 다르면 0을 반환.전이학습(Trnasfer learning) : 다른 데이터를 이용해 학습된 모델을 갖고 내가 가진 데이터에 최적화시키는 학습방법. 보통 대용량 데이터로 사전 학습된 모델을 소규모 데이터에 최적화시킨다.
VGG16
import torch
import torch.nn as nn
from torchvision.models.vgg import vgg16
device = "cuda" if torch.cuda.is_available() else "cpu"
model = vgg16(pretrained=True) # ❶ vgg16 모델 객체 생성
fc = nn.Sequential( # ❷ 분류층의 정의
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(),
nn.Dropout(), #❷ 드롭아웃층 정의
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(),
nn.Linear(4096, 10),
)
model.classifier = fc # ➍ VGG의 classifier를 수정한 코드로 덮어씀
model.to(device)
Dropout(p)
: p 확률로 뉴런 값을 의도적으로 0으로 바꾼다. 기본값은 0.5로 50% 확률로 가중치가 사라진다. 오버피팅을 방지하고자 사용데이터 전처리와 증강
import tqdm
from torchvision.datasets.cifar import CIFAR10
from torchvision.transforms import Compose, ToTensor, Resize
from torchvision.transforms import RandomHorizontalFlip, RandomCrop, Normalize
from torch.utils.data.dataloader import DataLoader
from torch.optim.adam import Adam
transforms = Compose([
Resize(224),
RandomCrop((224, 224), padding=4),
RandomHorizontalFlip(p=0.5),
ToTensor(),
Normalize(mean=(0.4914, 0.4822, 0.4465), std=(0.247, 0.243, 0.261))
])
데이터로더 정의
training_data = CIFAR10(root="./", train=True, download=True, transform=transforms)
test_data = CIFAR10(root="./", train=False, download=True, transform=transforms)
train_loader = DataLoader(training_data, batch_size=32, shuffle=True)
test_loader = DataLoader(test_data, batch_size=32, shuffle=False)
학습루프 정의
lr = 1e-4
optim = Adam(model.parameters(), lr=lr)
for epoch in range(5):
iterator = tqdm.tqdm(train_loader) # ➊ 학습 로그 출력
for data, label in iterator:
optim.zero_grad()
preds = model(data.to(device)) # 모델의 예측값 출력
loss = nn.CrossEntropyLoss()(preds, label.to(device))
loss.backward()
optim.step()
# ❷ tqdm이 출력할 문자열 : 현재 epoch와 loss를 출력함.
iterator.set_description(f"epoch:{epoch+1} loss:{loss.item()}")
torch.save(model.state_dict(), "CIFAR_pretrained.pth") # 모델 저장
tqdm(iterable)
: 학습 로그를 출력하는 함수. iterable에 train_loader 변수를 인수로 넣어주고 반복문 안에 들어갈 객체로 넣어주면 반복문이 돌아가면서 iterable 객체에 대한 진행 상황을 프로그레스바를 이용하여 보여준다. 학습 도중에 손실을 확인하는 목적으로 사용됨.
model.load_state_dict(torch.load("CIFAR_pretrained.pth", map_location=device))
num_corr = 0
with torch.no_grad():
for data, label in test_loader:
output = model(data.to(device))
preds = output.data.max(1)[1]
corr = preds.eq(label.to(device).data).sum().item()
num_corr += corr
print(f"Accuracy:{num_corr/len(test_data)}")
--> Accuracy : 0.9274
사전학습된 vgg16모델을 사용한 결과 정확도는 92%로 앞선 예제(CNN)에서 보인 정확도보다 훨씬 높음을 알 수 있다.
전체코드