UNet 아키텍처와 얼굴 합성(Face Fusion)의 결합
딥러닝 기반의 이미지 생성 및 편집 작업에서 UNet은 강력한 인코딩-디코딩 성능으로 인해 널리 사용됩니다. 특히 얼굴 합성(Face Fusion) 시스템에서는 원본 이미지의 정체성(Identity)을 유지하면서 목표 이미지의 자세, 조명, 배경과 자연스럽게 융합하는 조건부 이미지 간 변환(Conditional Image-to-Image Translation) 역할을 수행합니다. 이는 Pix2Pix나 CycleGAN의 생성기 구조와 유사한 맥락에서 작동합니다.
하지만 이러한 시스템은 종종 '블랙박스'로 간주되어, 모델 내부에서 피처가 어떻게 추출되고 재구성되는지 파악하기 어렵습니다. 본 글에서는 UNet의 각 계층별 피처 맵(Feature Map)이 얼굴 합성 과정에서 수행하는 구체적인 역할을 분석하고, 모델의 해석 가능성(Interpretability)을 높이는 방안을 다룹니다.
인코더 단계: 픽셀에서 고수준 의미론적 피처로
UNet의 인코더는 다운샘플링을 통해 공간 해상도를 낮추고 채널 수를 늘리며 계층적 피처를 추출합니다.
초기 계층 (C1 ~ C2): 저수준 시각적 정보
- C1 (64 채널, 약 H/2 × W/2 해상도): 원본 이미지에 가장 가까운 계층으로, 수평/수직/대각선 엣지, 명암 경계, 초기 질감 등을 감지합니다. 얼굴 이미지에서는 눈썹, 입술, 코 등의 윤곽선이 강조됩니다.
- C2 (128 채널, 약 H/4 × W/4 해상도): 풀링을 통해 수용 영역(Receptive Field)이 확대됩니다. 눈이나 입과 같은 로컬 파츠의 구조, 피부와 비피부 영역의 구분, 대략적인 조명 방향 등을 포착하기 시작합니다.
심층 계층 (C3 ~ C5): 고수준 정체성 및 의미론
| 계층 | 해상도 | 주요 추출 정보 |
|---|---|---|
| C3 | H/8 | 얼굴 기관의 상대적 위치, 대략적인 표정 분류 |
| C4 | H/16 | 얼굴형 윤곽, 헤어스타일의 거시적 분포 |
| C5 | H/32 | 고수준 정체성 임베딩(Face Identity Embedding), 전체적인 자세 추정 |
C4와 C5 계층의 피처 맵은 인간의 눈으로 '얼굴'을 식별하기 어려울 정도로 추상화되어 있지만, '누구인가'를 결정하는 핵심적인 정체성 정보를 인코딩하고 있습니다. 이는 교차 이미지 얼굴 합성이 성립할 수 있는 기반이 됩니다.
스킵 커넥션과 디코더의 재구성 메커니즘
스킵 커넥션(Skip Connection)은 단순한 데이터 복사가 아닌, 다중 스케일 정보의 융합을 담당합니다. 저수준 피처(C1-C2)는 융합 후 이목구비의 정확한 공간적 위치를 보장하며, 고수준 피처(C4-C5)는 '어떻게 보여야 하는지'에 대한 의미론적 가이드를 제공합니다.
디코더는 업샘플링을 통해 해상도를 복원하고, 인코더의 대응하는 피처 맵과 채널 차원에서 결합(Concatenate)합니다.
# 디코더의 피처 융합 의사 코드
upsampled_tensor = F.interpolate(decoder_input, scale_factor=2, mode='bilinear')
skip_connection_data = encoder_outputs['layer_3']
merged_features = torch.cat([upsampled_tensor, skip_connection_data], dim=1)
refined_output = convolutional_block(merged_features)
최종 출력층은 일반적으로 단일 컨볼루션 커널과 Tanh 활성화 함수를 사용하여 피처를 [-1, 1] 범위의 RGB 이미지 공간으로 매핑합니다.
PyTorch Hook을 활용한 피처 맵 추출 및 시각화
모델의 내부 동작을 분석하기 위해 PyTorch의 Hook 메커니즘을 사용하여 순전파(Forward Pass) 중간의 활성화 값을 추출할 수 있습니다. 다음은 재구성된 피처 수집기 구현 예시입니다.
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
class ActivationCollector:
def __init__(self, network):
self.network = network
self.activations = {}
self._hook_handles = []
self._attach_hooks()
def _attach_hooks(self):
for layer_name, module in self.network.named_modules():
if isinstance(module, nn.Conv2d):
handle = module.register_forward_hook(self._create_hook_fn(layer_name))
self._hook_handles.append(handle)
def _create_hook_fn(self, layer_name):
def hook_fn(module, inp, out):
self.activations[layer_name] = out.detach().cpu()
return hook_fn
def clear_hooks(self):
for handle in self._hook_handles:
handle.remove()
# 사용 예시
face_fusion_net = load_pretrained_unet()
collector = ActivationCollector(face_fusion_net)
input_batch = preprocess_input_image("target_face.jpg")
with torch.no_grad():
_ = face_fusion_net(input_batch)
# 특정 계층의 활성화 맵 확인
target_layer_activation = collector.activations['encoder.stage1.conv']
print(f"Activation shape: {target_layer_activation.shape}")
# 채널별 시각화
fig, axes = plt.subplots(2, 3, figsize=(12, 6))
for idx, ax in enumerate(axes.flatten()):
ax.imshow(target_layer_activation[0, idx, :, :], cmap='viridis')
ax.set_title(f"Channel {idx}")
ax.axis('off')
plt.tight_layout()
plt.show()
collector.clear_hooks()
해석 가능성을 통한 모델 디버깅 및 최적화
각 계층의 피처 맵 역할을 이해하면, 얼굴 합성 시스템의 2차 개발 및 문제 해결에 실질적인 이점을 얻을 수 있습니다.
결함 진단 및 품질 개선
| 문제 현상 | 예상되는 피처 이상 원인 |
|---|---|
| 융합된 이목구비의 위치 어긋남 | 저수준 피처(C1-C2)의 공간 정렬 실패 |
| 얼굴 디테일 뭉개짐 및 흐림 | 고수준 피처(C4-C5) 표현력 부족 또는 스킵 커넥션 손실 |
| 피부 톤 및 색상 불일치 | 디코더 후단부의 색상 채널 재구성 오류 |
| 부자연스러운 경계선 및 합성 흔적 | 중간 계층의 매끄러운 전환(Smooth Transition) 메커니즘 부재 |
모델 경량화 및 커스텀 손실 함수 설계
엣지 디바이스 배포를 위해 모델 경량화가 필요한 경우, 피처의 중요도에 따라 불필요한 채널을 가지치기(Pruning)할 수 있습니다. 예를 들어, 기본 구조와 정체성 표현을 위해 C1, C2, C5와 해당 스킵 커넥션은 유지하면서, 중간 컨볼루션 채널 수를 줄여 추론 속도를 높일 수 있습니다.
또한, 계층별 의미론을 활용하여 다중 스케일 감독 손실(Multi-scale Supervised Loss)을 설계하면 합성의 자연스러움을 크게 향상시킬 수 있습니다.
# 중간 계층 L1 손실(지각적 일관성) 추가 예시
intermediate_loss = nn.L1Loss()(
collector.activations['decoder.stage2'],
target_network_activations['decoder.stage2']
)
total_objective = primary_loss + 0.25 * intermediate_loss