머신러닝 & 딥러닝/딥러닝

[AI 기초 다지기] 스탠포드 대학 딥러닝 기초(4) - Convolutional Neural Networks

Haru_29 2024. 11. 8. 20:06
 
 

합성곱 신경망(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의 핵심 특징

  1. 입력이 이미지인 경우에 최적화
  2. 3차원 뉴런 배열 (너비, 높이, 깊이)
  3. 지역적 연결성
  4. 파라미터 공유

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)

주요 하이퍼파라미터

  1. 필터 개수 (K)
  2. 필터 크기 (F)
  3. 스트라이드 (S)
  4. 패딩 (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)

특징

  1. 공간 크기 감소
  2. 파라미터 없음
  3. 깊이 차원 유지

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 레이어 크기 설계 원칙

  1. 입력 레이어
    • 2의 거듭제곱 크기 선호 (32, 64, 224 등)
  2. 합성곱 레이어
    • 작은 필터 크기 (3x3 또는 5x5)
    • 스트라이드 1
    • 패딩으로 입력 크기 유지
  3. 풀링 레이어
    • 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 기타 주요 아키텍처

  1. LeNet
  2. AlexNet
  3. GoogLeNet
  4. 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 설계 지침

  1. 작은 필터 사용 (3x3)
  2. 더 깊은 네트워크 선호
  3. 배치 정규화 사용

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 주요 메모리 소비원

  1. 중간 볼륨 크기
  2. 파라미터 크기
  3. 기타 메모리 (배치 데이터 등)

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')

주의사항

  1. 죽은 필터(Dead filters) 감지
  2. 희소성(Sparsity) 확인
  3. 활성화 패턴의 지역성 관찰

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 체크리스트

  1. 데이터셋 분석
  2. 적절한 전이 학습 전략 선택
  3. 레이어별 학습률 설정
  4. 정규화 기법 적용
  5. 성능 모니터링

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"
            }
        }