분류(Classification)는 이미지나 비디오에서 객체나 패턴을 식별하고, 이를 미리 정의된 카테고리 중 하나로 분류하는 작업입니다. 예를 들어, 이미지에 나타난 고양이와 개를 구별하거나, 특정 물체가 무엇인지를 분류하는 것입니다. 이 과정은 이미지 데이터를 입력으로 받아, 딥러닝 모델이나 머신러닝 알고리즘을 통해 각 이미지가 어떤 클래스에 속하는지 예측하는 방식으로 진행됩니다. 이때 사용되는 대표적인 모델로는 Convolutional Neural Network(CNN)가 있으며, 학습된 모델은 새로운 이미지에 대해서도 빠르고 정확하게 분류할 수 있습니다. 분류는 크게 두 가지로 나눌 수 있습니다. 이진 분류와 다중 클래스 분류입니다. 이진 분류는 두 개의 클래스 중 하나로 데이터를 분류하는 작업으로, 예를 들어 이메일을 '스팸'과 '비스팸'으로 구분하는 경우입니다. 반면, 다중 클래스 분류는 세 개 이상의 클래스로 데이터를 분류하는 작업으로, 예를 들어 이미지를 '고양이', '개', '새'와 같은 여러 카테고리로 분류하는 경우입니다. 또한, 다중 라벨 분류라는 유형도 존재하는데, 이는 하나의 입력에 대해 여러 개의 클래스를 동시에 예측하는 작업으로, 예를 들어 하나의 이미지에 여러 개의 물체가 있을 때 각 물체를 개별적으로 분류하는 경우에 해당합니다.
Surface Crack Detection 데이터셋은 콘크리트 표면에서 균열을 자동으로 탐지하기 위한 학습 데이터를 제공합니다. 이 데이터셋은 주로 다양한 콘크리트 표면 이미지를 포함하고 있으며, 각 이미지는 균열이 포함된 부분과 그렇지 않은 부분으로 구분됩니다. 이러한 데이터셋은 컴퓨터 비전 모델, 특히 Convolutional Neural Network(CNN) 등의 딥러닝 모델을 훈련시키는 데 사용되며, 균열 탐지 및 구조물의 상태 평가에 중요한 역할을 합니다. 다양한 환경과 조건을 반영한 이미지를 제공하여, 실제 적용에서 발생할 수 있는 다양한 상황에 대한 모델의 일반화 능력을 높이는 데 도움을 줍니다.
!kaggle datasets download arunrk7/surface-crack-detection
!unzip -q surface-crack-detection.zip
import os
import shutil
import random
import torch
import glob
import numpy as np
from torch.utils.data import Subset
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from torchvision.datasets import ImageFolder
from torchvision.models import vgg19, VGG19_Weights
from torchvision import datasets, models, transforms
from torch.utils.data import DataLoader
from PIL import Image
from torch.utils.data import SubsetRandomSampler
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)
# train, val, test 폴더 및 하위 폴더 생성
dataset_structure = ['train/negative', 'train/positive',
'val/negative', 'val/positive',
'test/negative', 'test/positive']
for folder in dataset_structure:
os.makedirs(folder, exist_ok=True)
# 데이터 분할 및 복사
categories = ['Negative', 'Positive']
for category in categories:
files = os.listdir(category)
random.shuffle(files)
num_files = len(files)
train_split = int(num_files * 0.6)
val_split = int(num_files * 0.2)
train_files = files[:train_split]
val_files = files[train_split:train_split + val_split]
test_files = files[train_split + val_split:]
target_category = category.lower()
for file in train_files:
shutil.copy(os.path.join(category, file), f'train/{target_category}/')
for file in val_files:
shutil.copy(os.path.join(category, file), f'val/{target_category}/')
for file in test_files:
shutil.copy(os.path.join(category, file), f'test/{target_category}/')
print("데이터 분할 및 복사 완료!")
# 폴더 경로 설정
train_positive_dir = "train/positive"
train_negative_dir = "train/negative"
# 각 폴더에서 4개씩 샘플링
positive_files = random.sample(os.listdir(train_positive_dir), 4)
negative_files = random.sample(os.listdir(train_negative_dir), 4)
# 파일 경로 리스트 생성
positive_paths = [os.path.join(train_positive_dir, file) for file in positive_files]
negative_paths = [os.path.join(train_negative_dir, file) for file in negative_files]
# 이미지 시각화
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
fig.suptitle("Train Dataset Samples", fontsize=16)
for i, file_path in enumerate(positive_paths):
image = Image.open(file_path)
axes[0, i].imshow(image)
axes[0, i].axis("off")
axes[0, i].set_title("Positive")
for i, file_path in enumerate(negative_paths):
image = Image.open(file_path)
axes[1, i].imshow(image)
axes[1, i].axis("off")
axes[1, i].set_title("Negative")
plt.show()
# 데이터 전처리 정의
transform = transforms.Compose([
transforms.Resize((224, 224)), # 이미지 크기 조정
transforms.ToTensor(), # 이미지를 텐서로 변환
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 정규화
])
# negative 폴더가 0, positive 폴더가 1로 자동 라벨링
# 데이터셋 로드
train_dataset = ImageFolder('/content/train', transform=transform)
val_dataset = ImageFolder('/content/val', transform=transform)
print(len(train_dataset))
print(len(val_dataset))
# 사용할 데이터셋의 비율 (예: 10%만 사용)
subset_ratio = 0.1
# 전체 데이터셋 크기 확인
total_train_size = len(train_dataset)
total_val_size = len(val_dataset)
# 선택할 샘플 수 계산
train_subset_size = int(total_train_size * subset_ratio)
val_subset_size = int(total_val_size * subset_ratio)
# 랜덤하게 샘플 선택
train_indices = np.random.choice(total_train_size, train_subset_size, replace=False)
val_indices = np.random.choice(total_val_size, val_subset_size, replace=False)
# Subset을 사용하여 데이터셋 크기 줄이기
train_dataset = Subset(train_dataset, train_indices)
val_dataset = Subset(val_dataset, val_indices)
# 데이터셋 크기 출력
print(f"Train dataset size after reduction: {len(train_dataset)}")
print(f"Validation dataset size after reduction: {len(val_dataset)}")
# DataLoader 생성
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4, pin_memory=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4, pin_memory=True)
VGG19는 2014년 Visual Geometry Group(VGG)에서 개발한 합성곱 신경망(CNN) 모델로, ImageNet 대회(ILSVRC-2014)에서 우수한 성능을 보이며 널리 알려졌습니다. 총 19개 층(16개의 합성곱 층 + 3개의 완전연결 층)**으로 구성되었으며, 모든 합성곱 층에서 3x3 필터를 사용하고, 최대 풀링을 통한 다운샘플링을 적용하여 깊은 계층에서도 효과적으로 특징을 학습할 수 있도록 설계되었습니다. VGG19는 깊은 구조를 통해 고해상도 이미지의 복잡한 패턴을 학습하는 데 강점이 있으며, 이미지 분류, 객체 검출, 스타일 트랜스퍼 등 다양한 컴퓨터 비전 작업에서 널리 활용됩니다.
# VGG 모델 로드 및 네트워크 구조 확인
net = vgg19(weights=VGG19_Weights.IMAGENET1K_V1)
net
# 모델의 모든 파라미터를 고정
for param in net.parameters():
param.requires_grad = False
# classifier의 마지막 레이어를 Binary Classification Task에 맞게 교체하고, 이 레이어의 파라미터는 학습 가능하도록 설정
net.classifier[6] = nn.Linear(4096, 2)
net.classifier[6].requires_grad = True
# 손실 함수
criterion = nn.CrossEntropyLoss()
import torch.optim as optim
def train_model(optimizer_name, net, train_loader, val_loader, criterion, num_epochs=20):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net.to(device)
# optimizer 설정
if optimizer_name == 'SGD':
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
elif optimizer_name == 'Adam':
optimizer = optim.Adam(net.parameters(), lr=0.001, betas=(0.9, 0.999))
elif optimizer_name == 'RAdam':
optimizer = optim.RAdam(net.parameters(), lr=0.001, betas=(0.9, 0.999))
else:
raise ValueError(f"Unsupported optimizer: {optimizer_name}")
# 학습/검증 손실과 검증 정확도를 저장할 리스트
train_losses = []
val_losses = []
val_accuracies = []
for epoch in range(num_epochs):
net.train() # 모델을 학습 모드로 설정
running_loss = 0.0
for i, data in enumerate(train_loader):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
# 매 에포크마다 평균 학습 손실 계산
train_loss = running_loss / len(train_loader)
train_losses.append(train_loss)
# 검증 손실 계산
val_loss = 0.0
net.eval() # 모델을 평가 모드로 설정
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
loss = criterion(outputs, labels)
val_loss += loss.item()
val_loss /= len(val_loader)
val_losses.append(val_loss)
val_accuracy = 100 * correct / total
val_accuracies.append(val_accuracy)
print(f'[{optimizer_name}] Epoch {epoch + 1}, Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}, Validation Accuracy: {val_accuracy:.2f}%', flush=True)
return train_losses, val_losses, val_accuracies
train_losses_SGD, val_losses_SGD, val_accuracies_SGD = train_model('SGD', net, train_loader, val_loader, criterion)
# 초기화
net = models.vgg19(pretrained=True)
for param in net.parameters():
param.requires_grad = False
net.classifier[6] = nn.Linear(4096, 2)
net.classifier[6].requires_grad = True
train_losses_Adam, val_losses_Adam, val_accuracies_Adam = train_model('Adam', net, train_loader, val_loader, criterion)
# 초기화
net = models.vgg19(pretrained=True)
for param in net.parameters():
param.requires_grad = False
net.classifier[6] = nn.Linear(4096, 2)
net.classifier[6].requires_grad = True
train_losses_RAdam, val_losses_RAdam, val_accuracies_RAdam = train_model('RAdam', net, train_loader, val_loader, criterion)
언제 Adam과 RAdam을 사용할까?
즉, Adam을 먼저 사용하고, 학습이 불안정하면 RAdam을 고려하는 것이 일반적인 전략입니다.
# 학습 손실과 검증 정확도 그래프 그리기
plt.figure(figsize=(15, 10))
# 학습 손실 그래프
plt.subplot(3, 1, 1) # 3행 1열의 첫 번째 위치
plt.plot(train_losses_SGD, label='SGD')
plt.plot(train_losses_Adam, label='Adam')
plt.plot(train_losses_RAdam, label='RAdam')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Over Epochs')
plt.legend()
# 검증 손실 그래프
plt.subplot(3, 1, 2) # 3행 1열의 두 번째 위치
plt.plot(val_losses_SGD, label='SGD')
plt.plot(val_losses_Adam, label='Adam')
plt.plot(val_losses_RAdam, label='RAdam')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Validation Loss Over Epochs')
plt.legend()
# 검증 정확도 그래프
plt.subplot(3, 1, 3) # 3행 1열의 세 번째 위치
plt.plot(val_accuracies_SGD, label='SGD', color='blue')
plt.plot(val_accuracies_Adam, label='Adam', color='green')
plt.plot(val_accuracies_RAdam, label='RAdam', color='orange')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('Validation Accuracy Over Epochs')
plt.legend()
plt.tight_layout()
plt.show()
# Image load 및 tensor로 변환
def load_and_transform_image(image_path, transform):
image = Image.open(image_path).convert('RGB')
return transform(image).unsqueeze(0) # 이미지를 모델에 맞게 변환하고 배치 차원 추가
# 클래스별 폴더 경로
class_folders = {
'crack': '/content/test/positive',
'normal': '/content/test/negative'
}
# 디바이스 설정 (GPU 사용 가능 여부 확인)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
plt.figure(figsize=(20, 8))
# subplot 인덱스를 위한 카운터
counter = 1
# 각 클래스별로 5장의 이미지 추론 및 시각화
for class_name, folder_path in class_folders.items():
# 해당 클래스의 이미지 경로 가져오기
image_paths = glob.glob(os.path.join(folder_path, '*'))
selected_paths = image_paths[:5] # 첫 5장 선택
for image_path in selected_paths:
image = load_and_transform_image(image_path, transform)
# ✅ 입력 데이터도 GPU로 이동
image = image.to(device)
net.eval() # 모델을 평가 모드로 설정
# 모델을 사용한 추론
with torch.no_grad():
outputs = net(image)
_, predicted = torch.max(outputs, 1)
prediction = 'normal' if predicted.item() == 0 else 'crack'
# 결과 시각화
plt.subplot(2, 5, counter)
plt.imshow(Image.open(image_path))
plt.title(f'True: {class_name}, Pred: {prediction}')
plt.axis('off')
counter += 1 # subplot 인덱스 업데이트
plt.tight_layout()
plt.show()
Object Detection (0) | 2025.02.25 |
---|---|
OCR (0) | 2025.02.20 |
OpenCV (0) | 2025.02.18 |
포켓몬 분류 데이터셋 (0) | 2025.02.17 |
컴퓨터 비전 (1) | 2025.02.13 |