오늘은 새로운 맥북으로 처음 듣는 수업이었다. 그동안 커널이 터져버리던 무거운 작업들이 바로 돌아가는 것도 좋았지만 다른 사람들은 10분 걸리던 작업이 1분도 안 돼서 완료되는 성능이 당황스러울 정도로 좋았다.
| 모델명 | 제안 주체 | 발표연도 | 핵심 아이디어 | 주요 특징 |
|---|---|---|---|---|
| AlexNet | 토론토 대학 연구진 | 2012 | GPU 기반 CNN, ReLU | 이미지 인식 문제에서 CNN 성능을 입증한 최초 모델 |
| VGGNet | Visual Geometry Group | 2014 | 합성곱을 깊게 반복 | 구조가 단순하여 CNN 기본 구조 설명에 최적화된 모델 |
| Inception(GoogLeNet) | 2014 | 다중 스케일 합성곱 병렬 처리 | 다양한 크기의 여러 특징을 동시에 추출 가능한 모델 | |
| ResNet | Microsoft Research | 2015 | 잔차 연결(skip connection) | 아주 깊은 CNN 학습을 가능하게 한 모델 |
| EfficientNet | 2019 | 깊이, 너비, 해상도 균형 확장 | 성능과 효율을 함께 고려한 최신 모델 |
| 구분 | 의미 | 형태 | 예시 (MNIST) |
|---|---|---|---|
| 채널 | 이미지 또는 특성맵의 깊이 차원 | 1채널 | |
| 커널 | 합성곱 연산에 사용되는 작은 가중치 행렬 | ||
| 필터 | 입력 채널 전체를 동시에 참조하는 커널 집합 | ||
| 필터 수 | 합성곱 층에서 사용하는 필터의 개수 | 32개 | |
| 특성맵 | 필터 적용 결과로 생성되는 출력 채널 |
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(in_features=6272, out_features=256),
nn.ReLU(),
nn.Linear(in_features=256, out_features=10)
).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=0.001)
trainer = Trainer(
model=model,
criterion=criterion,
optimizer=optimizer,
train_loader=train_loader,
test_loader=test_loader,
flatten=False # 합성곱 신경망의 입력은 평탄화 하지 않음
)
history = trainer.fit(n_epochs=10)
y_prob, y_pred, y_true = trainer.predict_loader(data_loader=test_loader)
mis_index = torch.where(y_true.ne(y_pred))[0]
from torchcam.methods import GradCAM
model.eval()
target_layer = model[2]
gradcam = GradCAM(model=model, target_layer=target_layer)
index = mis_index[0]
image, label = test_mnist[index]
logits = model(image.unsqueeze(dim=0).to(device))
pred = logits.argmax(dim=1).item()
cam_pred = gradcam(class_idx=pred, scores=logits)
true_label = test_mnist.classes[label]
pred_label = test_mnist.classes[pred]
plt.imshow(X=image.squeeze(), cmap='gray')
plt.title(label=f'index : {index}, true : {true_label}', fontsize=8)
plt.axis('off')
plt.show()

plt.imshow(X=image.squeeze(), cmap='gray')
plt.imshow(X=cam_pred[0].squeeze().cpu().numpy(), cmap='jet', interpolation='bilinear', alpha=0.5)
plt.title(label=f'index : {index}, pred : {pred_label}', fontsize=8)
plt.axis('off')
plt.show()

logits = model(image.unsqueeze(dim=0).to(device))
cam_true = gradcam(class_idx=label, scores=logits)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))
axes[0].imshow(X=image.squeeze(), cmap='gray')
axes[0].imshow(X=cam_pred[0].squeeze().cpu().numpy(), cmap='jet', interpolation='bilinear', alpha=0.5)
axes[0].set_title(label=f'index : {index}, pred : {pred_label}', fontsize=8)
axes[0].axis('off')
axes[1].imshow(X=image.squeeze(), cmap='gray')
axes[1].imshow(X=cam_true[0].squeeze().cpu().numpy(), cmap='jet', interpolation='bilinear', alpha=0.5)
axes[1].set_title(label=f'index : {index}, true : {true_label}', fontsize=8)
axes[1].axis('off')
plt.show()

def plot_gradcam_sample(dataset, index, model, gradcam, ax=None, cmap='jet', interpolation='bilinear'):
image, label = dataset[index]
image = image.to(device)
logits = model(image.unsqueeze(dim=0))
pred = logits.argmax(dim=1).item()
cam_pred = gradcam(class_idx=pred, scores=logits)
true_label = dataset.classes[label]
pred_label = dataset.classes[pred]
if ax is None:
ax = plt.gca()
ax.imshow(X=image.squeeze().detach().cpu(), cmap='gray')
ax.imshow(X=cam_pred[0].squeeze().detach().cpu(), cmap=cmap, alpha=0.5, interpolation=interpolation)
ax.set_title(f'index : {index}\ntrue : {true_label} | pred : {pred_label}')
ax.axis('off')
fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(12, 6))
for ax, index in zip(axes.flatten(), mis_index[:10]): # axes 2차원 배열이기 때문에 flatten 필요
plot_gradcam_sample(dataset=test_mnist, index=index.item(), model=model, gradcam=gradcam, ax=ax)
plt.tight_layout()
plt.show()

target_layer._forward_hooks
# OrderedDict([(0,
# functools.partial(<bound method _CAM._hook_a of GradCAM(target_layer=['2'])>, idx=0)),
# (1,
# functools.partial(<bound method _GradCAM._hook_g of GradCAM(target_layer=['2'])>, idx=0))])
gradcam.remove_hooks()
target_layer._forward_hooks
# OrderedDict()
train_cifar10 = CIFAR10(root='../../data', train=True, transform=None, download=True)
type(train_cifar10.data)
# numpy.ndarray
train_cifar10.data.shape
# (50000, 32, 32, 3)
image = train_cifar10.data[0]
image.shape
# (32, 32, 3)
train_cifar10.targets[0]
# 6
train_cifar10.classes
# ['airplane',
# 'automobile',
# 'bird',
# 'cat',
# 'deer',
# 'dog',
# 'frog',
# 'horse',
# 'ship',
# 'truck']
train_cifar10.class_to_idx
# {'airplane': 0,
# 'automobile': 1,
# 'bird': 2,
# 'cat': 3,
# 'deer': 4,
# 'dog': 5,
# 'frog': 6,
# 'horse': 7,
# 'ship': 8,
# 'truck': 9}
for i in range(9):
plt.subplot(3, 3, i+1)
image = train_cifar10.data[i]
plt.imshow(X=image)
target = train_cifar10.targets[i]
plt.title(label=f'{train_cifar10.classes[target]}')
plt.axis('off')
plt.tight_layout()
plt.show()

pixels = torch.from_numpy(train_cifar10.data).float() / 255
pixels.shape
# torch.Size([50000, 32, 32, 3])
pixels.mean(dim=(0, 1, 2))
# tensor([0.4914, 0.4822, 0.4465])
pixels.std(dim=(0, 1, 2))
# tensor([0.2470, 0.2435, 0.2616])
cifar10_avg = [round(v.item(), 4) for v in pixels.mean(dim=(0, 1, 2))]
cifar10_std = [round(v.item(), 4) for v in pixels.std(dim=(0, 1, 2))]
transform = transforms.Compose(
transforms=[
transforms.ToTensor(),
transforms.Normalize(mean=cifar10_avg, std=cifar10_std)
]
)
train_cifar10 = CIFAR10(root='../../data', train=True, transform=transform)
image, label = train_cifar10[0]
type(image)
# torch.Tensor
image.shape
# torch.Size([3, 32, 32])
image = image * std + avg : 이미지 역정규화image = image.clamp(min=0, max=1) : 범위를 0~1 실수로 제한image = (image * 255).round().to(torch.uint8) : 255를 곱하고 정수 변환하면 원본에 가까워짐def denormalize_tensor(image, avg, std):
avg = torch.tensor(data=avg, device=image.device).reshape(3, 1, 1)
std = torch.tensor(data=std, device=image.device).reshape(3, 1, 1)
image = image * std + avg
image = image.clamp(min=0, max=1)
image = (image * 255).round().to(torch.uint8)
return image
image = denormalize_tensor(image=image, avg=cifar10_avg, std=cifar10_std)
image = image.permute(1, 2, 0)
image.shape
# torch.Size([32, 32, 3])
plt.imshow(X=image)
plt.axis('off');

test_cifar10 = CIFAR10(root='../../data', train=False, transform=transform)
코드를 실행하면서 수업을 들으니 확실히 더 재밌게 배우는 것 같다. 어려운 내용도 많지만 이미지 모델들을 다루는게 꽤나 재밌어서 괜찮은 것 같다.