1CNN(Convolutional Neural Network) 기초
CNN은 이미지 인식에 특화된 딥러닝 아키텍처로, 제조 비전 AI의 핵심 기술입니다. 이미지의 공간적 특징을 계층적으로 학습하여 복잡한 결함 패턴도 인식할 수 있습니다.
CNN Architecture
1. Convolution Layer (합성곱)
작은 필터(3x3, 5x5)로 이미지를 스캔하며 특징 추출. 에지, 텍스처, 패턴 등 시각적 특징 학습
2. Pooling Layer (풀링)
특징맵 크기 축소 (Max Pooling, Average Pooling). 위치 변화에 대한 강건성 확보
3. Fully Connected Layer
추출된 특징을 조합하여 최종 분류. 출력: 클래스별 확률 (Softmax)
제조 비전에서 CNN의 장점: (1) 결함 패턴을 자동으로 학습 - 규칙 프로그래밍 불필요, (2) 다양한 변형에 강건 - 조명, 각도, 위치 변화 대응, (3) 새로운 결함 유형 학습 가능 - 데이터만 추가하면 됨
주요 CNN 아키텍처:
| 모델 | 연도 | 파라미터 | 특징 | 제조 적용 |
| ResNet-50 | 2015 | 25M | Skip Connection, 깊은 네트워크 | 범용 결함 분류 |
| EfficientNet | 2019 | 5-66M | 효율적 스케일링, 높은 정확도 | 엣지 디바이스 |
| ConvNeXt | 2022 | 28-350M | 최신 설계, ViT 수준 성능 | 고성능 검사 |
| MobileNetV3 | 2019 | 2-5M | 경량화, 모바일 최적화 | 실시간 검사 |
2Transfer Learning (전이 학습)
Transfer Learning은 대규모 데이터셋(ImageNet)으로 사전 학습된 모델을 제조 결함 검출에 재활용하는 기법입니다. 소량의 데이터로도 높은 성능을 달성할 수 있어 제조 AI에서 필수적입니다.
Transfer Learning 전략
방법 1: Feature Extraction (특징 추출기)
Pre-trained CNN
Conv Layers
(동결, 학습X)
ImageNet 가중치
→
새로운 분류기
FC Layer
(학습O)
제조 결함용
적용: 데이터 1,000장 미만, 빠른 구축 필요
방법 2: Fine-tuning (미세 조정)
적용: 데이터 5,000장 이상, 높은 정확도 필요
PyTorch 구현 - 결함 분류 모델:
import torch
import torch.nn as nn
from torchvision import models, transforms
class DefectClassifier(nn.Module):
"""제조 결함 분류기 (Transfer Learning)"""
def __init__(self, num_classes: int, freeze_backbone: bool = True):
super().__init__()
# EfficientNet-B0 백본 (ImageNet 사전학습)
self.backbone = models.efficientnet_b0(pretrained=True)
# 백본 동결 (Feature Extraction 모드)
if freeze_backbone:
for param in self.backbone.parameters():
param.requires_grad = False
# 분류기 교체 (제조 결함용)
in_features = self.backbone.classifier[1].in_features
self.backbone.classifier = nn.Sequential(
nn.Dropout(p=0.3),
nn.Linear(in_features, 256),
nn.ReLU(),
nn.Dropout(p=0.2),
nn.Linear(256, num_classes)
)
def forward(self, x):
return self.backbone(x)
# 데이터 전처리 파이프라인
train_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(15),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 사용 예시
model = DefectClassifier(num_classes=5) # OK, Scratch, Dent, Stain, Crack
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
3성능 평가 지표
제조 품질 검사에서는 단순 정확도(Accuracy)보다 재현율(Recall)과 정밀도(Precision)가 더 중요합니다. 미검출(Escape)과 허위 불량(False Call)의 비즈니스 영향이 다르기 때문입니다.
제조 품질 검사 성능 지표 (Confusion Matrix)
|
|
실제 |
|
|
불량 |
정상 |
|
| 예측 |
불량 |
TP 정상 검출 |
FP 허위 불량 |
False Call (과잉 검출) |
| 정상 |
FN 미검출 |
TN 정상 통과 |
Escape (치명적!) |
1. Recall (재현율)
TP / (TP + FN)
실제 불량 중 검출한 비율
목표: 99% 이상
2. Precision (정밀도)
TP / (TP + FP)
불량 예측 중 실제 불량 비율
목표: 95% 이상
3. Escape Rate
FN / (TP + FN)
불량 중 미검출 비율
목표: 0.1% 이하
실무 주의: 정확도(Accuracy)가 99%여도 불량 검출률이 낮을 수 있습니다. 클래스 불균형이 심한 제조 데이터에서는 Recall과 Precision을 함께 확인해야 합니다.
4클래스 불균형 처리
제조 데이터에서 불량 샘플은 전체의 0.1~5%에 불과합니다. 이러한 극심한 클래스 불균형 문제를 해결하는 방법을 알아봅니다.
1 가중 손실함수
희소 클래스에 높은 가중치 부여. CrossEntropyLoss의 weight 파라미터 활용
2 오버샘플링
불량 샘플 복제 또는 합성(SMOTE). WeightedRandomSampler 활용
3 Focal Loss
쉬운 샘플 가중치 감소, 어려운 샘플에 집중. 객체 탐지에 효과적
4 데이터 증강
불량 샘플에 더 많은 증강 적용. 회전, 플립, 색상 변환 등
import torch.nn as nn
# 가중 CrossEntropy
def get_weighted_loss(class_counts):
"""클래스별 빈도 역수로 가중치 계산"""
total = sum(class_counts)
weights = [total / c for c in class_counts]
weights = torch.FloatTensor(weights)
weights = weights / weights.sum() * len(weights)
return nn.CrossEntropyLoss(weight=weights)
# 예: OK 9000장, Scratch 500장, Dent 300장
class_counts = [9000, 500, 300, 150, 50]
criterion = get_weighted_loss(class_counts)
5모델 학습 파이프라인
결함 분류 모델의 학습 파이프라인을 구현합니다. 데이터 로딩, 학습 루프, 검증, 체크포인트 저장까지 포함합니다.
from torch.utils.data import DataLoader
from tqdm import tqdm
def train_epoch(model, dataloader, criterion, optimizer, device):
"""1 에포크 학습"""
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in tqdm(dataloader, desc="Training"):
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return running_loss / len(dataloader), correct / total
def validate(model, dataloader, criterion, device):
"""검증"""
model.eval()
running_loss = 0.0
all_preds, all_labels = [], []
with torch.no_grad():
for images, labels in dataloader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = outputs.max(1)
all_preds.extend(predicted.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
accuracy = np.mean(np.array(all_preds) == np.array(all_labels))
return running_loss / len(dataloader), accuracy
학습 팁: (1) 불량 클래스 불균형 → 가중 손실함수 적용, (2) 과적합 방지 → 데이터 증강, 드롭아웃, Early Stopping, (3) 학습률 → Cosine Annealing 사용 권장
6추론 및 배포
학습된 모델을 실제 검사 라인에 적용하기 위한 추론 코드입니다.
class DefectInferenceEngine:
"""결함 분류 추론 엔진"""
def __init__(self, model_path: str, class_names: list, device='cuda'):
self.device = torch.device(device if torch.cuda.is_available() else 'cpu')
self.class_names = class_names
# 모델 로드
self.model = DefectClassifier(num_classes=len(class_names))
checkpoint = torch.load(model_path, map_location=self.device)
self.model.load_state_dict(checkpoint['model_state_dict'])
self.model.to(self.device)
self.model.eval()
def predict(self, image_path: str):
"""단일 이미지 추론"""
image = Image.open(image_path).convert('RGB')
input_tensor = self.transform(image).unsqueeze(0).to(self.device)
with torch.no_grad():
outputs = self.model(input_tensor)
probabilities = torch.softmax(outputs, dim=1)[0]
predicted_idx = probabilities.argmax().item()
return {
'predicted_class': self.class_names[predicted_idx],
'confidence': probabilities[predicted_idx].item(),
'is_defect': predicted_idx != 0 # OK가 0번 클래스
}
# 사용 예시
engine = DefectInferenceEngine(
model_path='best_model.pth',
class_names=['OK', 'Scratch', 'Dent', 'Stain', 'Crack']
)
result = engine.predict('test_image.jpg')
print(f"결과: {result['predicted_class']} ({result['confidence']*100:.1f}%)")