합성곱 신경망(CNN) 가이드: 구조와 구현
1. CNN의 기본 개념
1.1 일반 신경망과의 차이점
# 일반 신경망
class RegularNeuralNet:
def __init__(self, input_size):
self.weights = np.random.randn(input_size, hidden_size)
# 모든 입력과 연결: 32x32x3 이미지의 경우 3072개 가중치
# CNN
class ConvNet:
def __init__(self, filter_size):
self.filter = np.random.randn(filter_size, filter_size, 3)
# 지역적 연결: 5x5x3 필터의 경우 75개 가중치
1.2 CNN의 핵심 특징
- 입력이 이미지인 경우에 최적화
- 3차원 뉴런 배열 (너비, 높이, 깊이)
- 지역적 연결성
- 파라미터 공유
2. CNN의 주요 레이어 구성
2.1 합성곱 레이어 (Convolutional Layer)
class ConvLayer:
def __init__(self, num_filters, filter_size, stride, padding):
self.num_filters = num_filters
self.filter_size = filter_size
self.stride = stride
self.padding = padding
def forward(self, input_volume):
W = input_volume.shape[0] # 입력 너비
H = input_volume.shape[1] # 입력 높이
D = input_volume.shape[2] # 입력 깊이
# 출력 볼륨 크기 계산
out_W = (W - self.filter_size + 2*self.padding) // self.stride + 1
out_H = (H - self.filter_size + 2*self.padding) // self.stride + 1
return (out_W, out_H, self.num_filters)
주요 하이퍼파라미터
- 필터 개수 (K)
- 필터 크기 (F)
- 스트라이드 (S)
- 패딩 (P)
2.2 풀링 레이어 (Pooling Layer)
class MaxPoolLayer:
def __init__(self, pool_size=2, stride=2):
self.pool_size = pool_size
self.stride = stride
def forward(self, input_volume):
W = input_volume.shape[0]
H = input_volume.shape[1]
D = input_volume.shape[2]
out_W = (W - self.pool_size) // self.stride + 1
out_H = (H - self.pool_size) // self.stride + 1
return (out_W, out_H, D)
특징
- 공간 크기 감소
- 파라미터 없음
- 깊이 차원 유지
2.3 완전 연결 레이어 (Fully Connected Layer)
class FCLayer:
def __init__(self, input_size, output_size):
self.weights = np.random.randn(input_size, output_size) * 0.01
self.biases = np.zeros(output_size)
3. CNN 아키텍처 설계
3.1 일반적인 패턴
class ConvNet:
def __init__(self):
self.layers = [
ConvLayer(filters=64, size=3, stride=1, pad=1),
ReLU(),
MaxPoolLayer(size=2, stride=2),
ConvLayer(filters=128, size=3, stride=1, pad=1),
ReLU(),
MaxPoolLayer(size=2, stride=2),
FCLayer(input_size=128*7*7, output_size=1000)
]
3.2 레이어 크기 설계 원칙
- 입력 레이어
- 2의 거듭제곱 크기 선호 (32, 64, 224 등)
- 합성곱 레이어
- 작은 필터 크기 (3x3 또는 5x5)
- 스트라이드 1
- 패딩으로 입력 크기 유지
- 풀링 레이어
- 2x2 필터, 스트라이드 2가 표준
- 75% 활성화 감소
4. 유명한 CNN 아키텍처
4.1 VGGNet 상세 구조
class VGGNet:
def __init__(self):
# 메모리와 파라미터 계산
self.config = [
# 레이어: [크기, 채널수, 메모리, 파라미터]
['INPUT', [224, 224, 3], 224*224*3, 0],
['CONV3-64', [224, 224, 64], 224*224*64, 3*3*3*64],
['CONV3-64', [224, 224, 64], 224*224*64, 3*3*64*64],
['POOL2', [112, 112, 64], 112*112*64, 0],
# ... 추가 레이어
]
4.2 기타 주요 아키텍처
- LeNet
- AlexNet
- GoogLeNet
- ResNet
5. 구현 최적화
5.1 메모리 관리
def calculate_memory_usage(batch_size, input_size, num_layers):
# 활성화 메모리
activation_memory = 0
# 파라미터 메모리
parameter_memory = 0
for layer in layers:
activation_memory += layer.output_size * batch_size * 4 # float32
parameter_memory += layer.num_parameters * 4 # float32
return activation_memory + parameter_memory
5.2 계산 최적화
class OptimizedConvLayer:
def __init__(self):
self.col_weights = None # im2col 용
def im2col(self, input_data):
# 입력을 행렬 곱셈에 적합한 형태로 변환
pass
def forward(self, input_data):
# 최적화된 행렬 곱셈 사용
col = self.im2col(input_data)
out = np.dot(col, self.col_weights)
return out.reshape(self.output_shape)
6. 실전 구현 팁
6.1 설계 지침
- 작은 필터 사용 (3x3)
- 더 깊은 네트워크 선호
- 배치 정규화 사용
6.2 하이퍼파라미터 설정
class CNNHyperparameters:
def __init__(self):
self.learning_rate = 0.001
self.batch_size = 64
self.num_epochs = 100
self.weight_decay = 0.0005
self.momentum = 0.9
6.3 성능 모니터링
class CNNMonitor:
def __init__(self):
self.train_loss = []
self.val_accuracy = []
def update(self, epoch, loss, accuracy):
self.train_loss.append(loss)
self.val_accuracy.append(accuracy)
def visualize(self):
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(self.train_loss)
plt.title('Training Loss')
plt.subplot(1, 2, 2)
plt.plot(self.val_accuracy)
plt.title('Validation Accuracy')
7. 메모리 고려사항
7.1 주요 메모리 소비원
- 중간 볼륨 크기
- 파라미터 크기
- 기타 메모리 (배치 데이터 등)
7.2 메모리 최적화 전략
def optimize_memory(network):
# 1. 배치 크기 조정
optimal_batch_size = find_optimal_batch_size()
# 2. 중간 활성화 메모리 관리
activation_checkpointing()
# 3. 파라미터 공유
parameter_sharing()
8. 성능 향상을 위한 추가 기법
8.1 데이터 증강
class DataAugmentation:
def __init__(self):
self.transforms = [
('rotate', [-10, 10]),
('translate', [-0.1, 0.1]),
('scale', [0.9, 1.1]),
('flip', True)
]
def apply(self, image):
for transform_type, params in self.transforms:
if random.random() > 0.5:
image = self.apply_transform(image, transform_type, params)
return image
8.2 앙상블 기법
class CNNEnsemble:
def __init__(self, models):
self.models = models
def predict(self, input_data):
predictions = []
for model in self.models:
pred = model.predict(input_data)
predictions.append(pred)
return np.mean(predictions, axis=0)
8.3 학습률 스케줄링
class LearningRateScheduler:
def __init__(self, initial_lr=0.1):
self.initial_lr = initial_lr
def step_decay(self, epoch):
drop_rate = 0.5
epochs_drop = 10
return self.initial_lr * math.pow(drop_rate, math.floor(epoch/epochs_drop))
CNN의 시각화 기법과 해석 가이드
1. 활성화와 가중치 시각화
1.1 레이어 활성화 시각화
class ActivationVisualizer:
def __init__(self, model):
self.model = model
def get_activations(self, input_image, layer_name):
# 특정 레이어의 활성화 맵 추출
activations = {}
def hook(model, input, output):
activations['output'] = output.detach()
handle = self.model._modules[layer_name].register_forward_hook(hook)
self.model(input_image)
handle.remove()
return activations['output']
def visualize_activations(self, activations):
# 활성화 맵 시각화
plt.figure(figsize=(15, 15))
for i in range(min(64, activations.shape[1])):
plt.subplot(8, 8, i+1)
plt.imshow(activations[0, i].cpu(), cmap='viridis')
plt.axis('off')
주의사항
- 죽은 필터(Dead filters) 감지
- 희소성(Sparsity) 확인
- 활성화 패턴의 지역성 관찰
1.2 필터 가중치 시각화
class FilterVisualizer:
def __init__(self, model):
self.model = model
def visualize_filters(self, layer_name):
# 필터 가중치 추출
filters = self.model._modules[layer_name].weight.data.cpu()
# 정규화
min_val = filters.min()
max_val = filters.max()
filters = (filters - min_val) / (max_val - min_val)
# 시각화
plt.figure(figsize=(15, 15))
for i in range(min(64, filters.shape[0])):
plt.subplot(8, 8, i+1)
plt.imshow(filters[i, 0], cmap='gray')
plt.axis('off')
2. 뉴런 활성화 최대화
2.1 이미지 검색 기반 접근
class NeuronActivationMaximizer:
def __init__(self, model, dataset):
self.model = model
self.dataset = dataset
def find_max_activating_images(self, layer_name, neuron_idx, top_k=9):
activations = []
image_indices = []
for i, image in enumerate(self.dataset):
# 특정 뉴런의 활성화값 계산
activation = self.get_neuron_activation(image, layer_name, neuron_idx)
activations.append(activation)
image_indices.append(i)
# 상위 k개 이미지 선택
top_k_indices = np.argsort(activations)[-top_k:]
return [self.dataset[i] for i in top_k_indices]
def visualize_max_activations(self, images):
plt.figure(figsize=(10, 10))
for i, img in enumerate(images):
plt.subplot(3, 3, i+1)
plt.imshow(img)
plt.axis('off')
2.2 최적화 기반 접근
class ActivationOptimizer:
def __init__(self, model):
self.model = model
def generate_optimal_input(self, layer_name, neuron_idx, num_iterations=100):
# 랜덤 이미지로 시작
input_image = torch.randn(1, 3, 224, 224, requires_grad=True)
optimizer = optim.Adam([input_image], lr=0.1)
for i in range(num_iterations):
optimizer.zero_grad()
# 특정 뉴런의 활성화 최대화
activation = self.get_neuron_activation(input_image, layer_name, neuron_idx)
loss = -activation # 활성화 최대화를 위해 음수
loss.backward()
optimizer.step()
return input_image
3. t-SNE를 사용한 특징 임베딩
3.1 CNN 코드 추출
class CNNCodeExtractor:
def __init__(self, model):
self.model = model
def extract_codes(self, dataset):
codes = []
for image in dataset:
# 이미지를 CNN에 통과시켜 특징 추출
features = self.get_features(image)
codes.append(features)
return np.array(codes)
3.2 t-SNE 시각화
class TSNEVisualizer:
def __init__(self, perplexity=30, n_iter=1000):
self.tsne = TSNE(perplexity=perplexity, n_iter=n_iter)
def visualize_embeddings(self, codes, images):
# t-SNE 수행
embeddings = self.tsne.fit_transform(codes)
# 그리드에 이미지 배치
plt.figure(figsize=(20, 20))
for i in range(len(images)):
plt.subplot(50, 50, i+1)
plt.imshow(images[i])
plt.axis('off')
4. 이미지 가림 분석
4.1 슬라이딩 윈도우 기반 분석
class OcclusionAnalyzer:
def __init__(self, model, window_size=35, stride=5):
self.model = model
self.window_size = window_size
self.stride = stride
def analyze_image(self, image, target_class):
height, width = image.shape[:2]
heatmap = np.zeros((height, width))
for y in range(0, height-self.window_size, self.stride):
for x in range(0, width-self.window_size, self.stride):
# 이미지의 특정 영역을 가림
occluded_image = self.occlude_region(image, x, y)
# 가려진 이미지에 대한 예측 확률
prob = self.get_class_probability(occluded_image, target_class)
heatmap[y:y+self.window_size, x:x+self.window_size] = prob
return heatmap
def visualize_heatmap(self, image, heatmap):
plt.figure(figsize=(12, 4))
plt.subplot(131)
plt.imshow(image)
plt.title('Original Image')
plt.subplot(132)
plt.imshow(heatmap, cmap='hot')
plt.title('Occlusion Sensitivity')
plt.subplot(133)
plt.imshow(image)
plt.imshow(heatmap, alpha=0.5, cmap='hot')
plt.title('Overlay')
5. 고급 시각화 기법
5.1 Gradient-weighted Class Activation Mapping (Grad-CAM)
class GradCAM:
def __init__(self, model):
self.model = model
self.gradients = None
self.activations = None
def get_gradcam(self, input_image, target_class):
# 훅 설정
def save_gradient(grad):
self.gradients = grad.detach()
def save_activation(module, input, output):
self.activations = output.detach()
# 특정 레이어에 훅 등록
target_layer = self.model.features[-1]
target_layer.register_forward_hook(save_activation)
target_layer.register_backward_hook(save_gradient)
# 순전파
output = self.model(input_image)
# 역전파
self.model.zero_grad()
class_loss = output[0, target_class]
class_loss.backward()
# Grad-CAM 계산
pooled_gradients = torch.mean(self.gradients, dim=[2, 3])
for i in range(self.activations.shape[1]):
self.activations[:, i, :, :] *= pooled_gradients[i]
heatmap = torch.mean(self.activations, dim=1).squeeze()
heatmap = F.relu(heatmap)
heatmap /= torch.max(heatmap)
return heatmap
5.2 Deep Dream
class DeepDream:
def __init__(self, model):
self.model = model
def dream(self, image, layer_name, iterations=20, lr=0.01):
input_image = Variable(torch.FloatTensor(image).unsqueeze(0), requires_grad=True)
for i in range(iterations):
# 순전파
out = self.model(input_image)
loss = -torch.mean(out) # 활성화 최대화
# 역전파
loss.backward()
# 경사 상승법
gradient = input_image.grad.data
input_image.data += lr * gradient
# 그래디언트 초기화
input_image.grad.data.zero_()
return input_image.data.squeeze()
6. 시각화 결과 분석
6.1 해석 도구
class InterpretabilityTools:
def __init__(self):
self.visualizers = {
'activations': ActivationVisualizer(),
'filters': FilterVisualizer(),
'gradcam': GradCAM(),
'occlusion': OcclusionAnalyzer()
}
def analyze_model_decisions(self, model, image, target_class):
results = {}
# 다양한 시각화 기법 적용
for name, visualizer in self.visualizers.items():
results[name] = visualizer.analyze(model, image, target_class)
return results
def generate_report(self, results):
# 종합 보고서 생성
plt.figure(figsize=(20, 10))
for i, (name, result) in enumerate(results.items()):
plt.subplot(2, 2, i+1)
plt.title(name)
plt.imshow(result)
plt.tight_layout()
6.2 결과 저장 및 문서화
class VisualizationLogger:
def __init__(self, save_dir='visualizations'):
self.save_dir = save_dir
os.makedirs(save_dir, exist_ok=True)
def save_visualization(self, name, visualization, metadata=None):
# 시각화 결과 저장
plt.imsave(f'{self.save_dir}/{name}.png', visualization)
# 메타데이터 저장
if metadata:
with open(f'{self.save_dir}/{name}_metadata.json', 'w') as f:
json.dump(metadata, f)
전이 학습(Transfer Learning)의 완벽 가이드
1. 기본 개념과 시나리오
1.1 전이 학습의 종류
class TransferLearningType:
def __init__(self):
self.types = {
'feature_extractor': {
'description': 'CNN을 고정된 특징 추출기로 사용',
'modify_layers': ['fc'],
'train_layers': ['fc']
},
'fine_tuning': {
'description': '사전 학습된 가중치로 미세 조정',
'modify_layers': ['fc'],
'train_layers': ['conv5', 'fc']
},
'full_training': {
'description': '전체 네트워크 재학습',
'modify_layers': ['fc'],
'train_layers': ['all']
}
}
1.2 특징 추출기로서의 CNN
class CNNFeatureExtractor:
def __init__(self, base_model, layer_name='fc7'):
self.base_model = base_model
self.layer_name = layer_name
def extract_features(self, images):
features = []
def hook(module, input, output):
features.append(output.detach().cpu().numpy())
# 특정 레이어에 훅 등록
handle = self.base_model._modules[self.layer_name].register_forward_hook(hook)
# 특징 추출
with torch.no_grad():
_ = self.base_model(images)
handle.remove()
return np.concatenate(features)
2. 미세 조정 (Fine-tuning)
2.1 레이어 동결
class ModelFinetuner:
def __init__(self, model, trainable_layers):
self.model = model
self.trainable_layers = trainable_layers
def freeze_layers(self):
"""지정된 레이어를 제외한 모든 레이어 동결"""
for name, param in self.model.named_parameters():
if not any(layer in name for layer in self.trainable_layers):
param.requires_grad = False
def get_optimizer(self, lr_base=0.001, lr_new=0.01):
"""레이어별 학습률 설정"""
params = []
# 기존 레이어
params.append({
'params': [p for n, p in self.model.named_parameters()
if p.requires_grad and not 'fc' in n],
'lr': lr_base
})
# 새로운 레이어
params.append({
'params': [p for n, p in self.model.named_parameters()
if p.requires_grad and 'fc' in n],
'lr': lr_new
})
return optim.Adam(params)
2.2 학습률 스케줄링
class FineTuningScheduler:
def __init__(self, optimizer, patience=3):
self.optimizer = optimizer
self.patience = patience
self.scheduler = ReduceLROnPlateau(
optimizer,
mode='min',
patience=patience,
factor=0.1
)
def step(self, val_loss):
self.scheduler.step(val_loss)
def get_lr(self):
return [group['lr'] for group in self.optimizer.param_groups]
3. 데이터셋 크기와 유사성에 따른 전략
3.1 데이터셋 분석기
class DatasetAnalyzer:
def __init__(self, new_dataset, original_dataset):
self.new_dataset = new_dataset
self.original_dataset = original_dataset
def analyze_similarity(self):
"""데이터셋 간 유사성 분석"""
new_features = self.extract_features(self.new_dataset)
orig_features = self.extract_features(self.original_dataset)
similarity = cosine_similarity(new_features, orig_features)
return float(similarity.mean())
def get_strategy(self):
"""전이 학습 전략 추천"""
size = len(self.new_dataset)
similarity = self.analyze_similarity()
if size < 1000:
if similarity > 0.7:
return "feature_extractor"
else:
return "partial_fine_tuning"
else:
if similarity > 0.7:
return "full_fine_tuning"
else:
return "train_from_scratch"
4. 실전 구현
4.1 전이 학습 파이프라인
class TransferLearningPipeline:
def __init__(self, base_model, num_classes, strategy='feature_extractor'):
self.base_model = base_model
self.num_classes = num_classes
self.strategy = strategy
def modify_model(self):
"""모델 수정"""
if self.strategy == 'feature_extractor':
self.freeze_all_layers()
elif self.strategy == 'fine_tuning':
self.freeze_early_layers()
# 분류기 수정
num_features = self.base_model.fc.in_features
self.base_model.fc = nn.Linear(num_features, self.num_classes)
def train(self, train_loader, val_loader, epochs=10):
"""모델 학습"""
optimizer = self.get_optimizer()
scheduler = self.get_scheduler(optimizer)
criterion = nn.CrossEntropyLoss()
best_val_acc = 0
for epoch in range(epochs):
train_loss = self.train_epoch(train_loader, optimizer, criterion)
val_loss, val_acc = self.validate(val_loader, criterion)
scheduler.step(val_loss)
if val_acc > best_val_acc:
best_val_acc = val_acc
self.save_checkpoint()
4.2 모델 평가 및 모니터링
class TransferLearningMonitor:
def __init__(self, model_name):
self.model_name = model_name
self.history = defaultdict(list)
def update(self, metrics):
"""학습 지표 업데이트"""
for key, value in metrics.items():
self.history[key].append(value)
def plot_metrics(self):
"""학습 곡선 시각화"""
plt.figure(figsize=(15, 5))
# 손실 함수 그래프
plt.subplot(121)
plt.plot(self.history['train_loss'], label='Train Loss')
plt.plot(self.history['val_loss'], label='Val Loss')
plt.title('Loss Curves')
plt.legend()
# 정확도 그래프
plt.subplot(122)
plt.plot(self.history['train_acc'], label='Train Acc')
plt.plot(self.history['val_acc'], label='Val Acc')
plt.title('Accuracy Curves')
plt.legend()
5. 고급 기법
5.1 점진적 미세 조정
class ProgressiveFineTuner:
def __init__(self, model):
self.model = model
self.layers = self.get_layer_groups()
def get_layer_groups(self):
"""레이어를 그룹으로 나누기"""
return [
['conv1', 'bn1'],
['layer1'],
['layer2'],
['layer3'],
['layer4'],
['fc']
]
def progressive_unfreeze(self, epoch):
"""에폭에 따라 점진적으로 레이어 동결 해제"""
n_groups = len(self.layers)
layers_to_train = self.layers[-(epoch//5)-1:]
for name, param in self.model.named_parameters():
param.requires_grad = any(layer in name for layers in layers_to_train
for layer in layers)
5.2 지식 증류
class KnowledgeDistillation:
def __init__(self, teacher_model, student_model, temperature=3.0):
self.teacher_model = teacher_model
self.student_model = student_model
self.temperature = temperature
def distillation_loss(self, student_logits, teacher_logits, labels, alpha=0.5):
"""지식 증류 손실 계산"""
distillation_loss = nn.KLDivLoss()(
F.log_softmax(student_logits/self.temperature, dim=1),
F.softmax(teacher_logits/self.temperature, dim=1)
) * (self.temperature ** 2)
student_loss = F.cross_entropy(student_logits, labels)
return alpha * student_loss + (1-alpha) * distillation_loss
6. 모범 사례와 팁
6.1 체크리스트
- 데이터셋 분석
- 적절한 전이 학습 전략 선택
- 레이어별 학습률 설정
- 정규화 기법 적용
- 성능 모니터링
6.2 최적의 실천 방법
class TransferLearningBestPractices:
@staticmethod
def get_recommendations():
return {
"small_dataset": {
"strategy": "feature_extractor",
"learning_rate": 0.001,
"regularization": "high",
"augmentation": "extensive"
},
"medium_dataset": {
"strategy": "partial_fine_tuning",
"learning_rate": {"base": 0.0001, "new": 0.001},
"regularization": "medium",
"augmentation": "moderate"
},
"large_dataset": {
"strategy": "full_fine_tuning",
"learning_rate": {"base": 0.00001, "new": 0.0001},
"regularization": "low",
"augmentation": "minimal"
}
}