데이터 분석과 머신러닝 모델링을 하다 보면 수많은 특성(Feature)을 가진 고차원 데이터를 마주하게 됩니다. 하지만 차원이 높아질수록 모델 학습은 어려워지고, 데이터의 숨겨진 패턴을 시각적으로 파악하기는 불가능에 가까워집니다.
이러한 문제를 해결하기 위해 필수적으로 사용되는 기법이 바로 차원 축소(Dimensionality Reduction)입니다. 오늘은 차원 축소의 핵심 개념부터 대표적인 알고리즘(PCA, t-SNE, UMAP), 그리고 파이썬 실전 시각화 코드까지 꼼꼼하게 정리해 보겠습니다.
1. 차원 축소란
차원 축소는 수많은 차원(특성)을 가진 고차원 데이터에서 핵심 정보와 구조는 최대한 유지한 채 더 낮은 차원으로 압축하는 기법입니다.
불필요한 중복 정보나 노이즈를 줄여 모델 학습을 안정적으로 만들고 과적합(Overfitting)을 완화합니다. 또한 데이터를 2차원이나 3차원으로 줄여 시각화함으로써 데이터의 군집 구조나 관계를 직관적으로 파악할 수 있게 해줍니다.
차원의 저주 (Curse of Dimensionality)
데이터의 차원이 증가할수록 데이터를 표현하는 공간의 부피가 기하급수적으로 커집니다. 고차원 공간에서는 데이터가 뿔뿔이 흩어져 희소(Sparse)해지며, 거리 기반 알고리즘(KNN, 클러스터링 등)의 경우 모든 데이터 간의 거리가 비슷해져 변별력을 잃게 됩니다.
💡 예시로 이해하기
각 특성을 10단계(0~9)로 나눈다고 가정해 봅시다.
- 1차원: 10칸
- 2차원: 100칸
- 10차원: $10^{10}$ = 10,000,000,000칸
겨우 10차원만 되어도 학습 가능한 공간이 100억 칸으로 늘어납니다. 데이터가 수만 개 있더라도 대부분의 공간은 텅텅 비게 되는 심각한 '희소성' 문제가 발생합니다.
매니폴드 가정 (Manifold Hypothesis)
"고차원 데이터는 사실 훨씬 더 낮은 차원의 매끄러운 곡면(매니폴드) 위에 분포해 있다"는 가정입니다. 대표적인 예시가 Swiss Roll 데이터입니다. 3차원 공간에 둥글게 말려 있지만, 본질적으로는 2차원 평면을 구부려 놓은 것과 같습니다. 비선형 차원 축소 알고리즘은 이 엉킨 구조를 잘 파악해 평면으로 예쁘게 펼쳐내는 역할을 합니다.
2. 대표적인 차원 축소 알고리즘 3가지
2-1 PCA (주성분 분석, Principal Component Analysis)
- 특징: 원본 데이터의 분산(정보)을 최대한 보존하는 선형 변환 기법입니다.
- 원리: 데이터의 상관관계를 분석해 가장 분산이 큰 방향(주성분)을 찾고, 그 직교(Orthogonal)하는 축을 기준으로 데이터를 재투영합니다.
- 장단점: 계산 속도가 빠르고 노이즈 제거에 탁월하지만, 선형 기법이라 복잡하게 꼬인 비선형 데이터(매니폴드)를 펼치는 데는 한계가 있습니다.
2-2 t-SNE (t-Distributed Stochastic Neighbor Embedding)
- 특징: 고차원 데이터를 2~3차원으로 시각화하는 데 특화된 비선형 기법입니다.
- 원리: 고차원 공간에서 이웃한 점들의 확률 분포와 저차원 공간의 확률 분포가 비슷해지도록 최적화합니다.
- t-분포를 쓰는 이유: 정규분포(가우시안)는 꼬리가 얇아 멀리 떨어진 데이터 간의 거리를 뭉개는 경향이 있습니다. 반면 t-분포는 꼬리가 두꺼워 멀리 떨어진 점들을 확실하게 밀어내어 군집 간의 구분을 명확하게 해줍니다.
- 자유도(Degree of Freedom): $n-1$ (데이터 수에서 평균 계산 제약을 뺀 값). 자유도가 작을수록 꼬리가 두꺼워지고, 커질수록 정규분포에 가까워집니다.
2-3 UMAP (Uniform Manifold Approximation and Projection)
- 특징: t-SNE의 강력한 대안으로, 위상 공간 이론을 바탕으로 전역적 구조와 국소적 구조를 모두 잘 보존하는 비선형 기법입니다.
- 장점: t-SNE보다 연산 속도가 훨씬 빠르며, 군집 간의 거리와 밀도 정보까지 어느 정도 보존합니다. 시각화뿐만 아니라 머신러닝 전처리용으로도 훌륭합니다.
최신 트렌드: 초기값(Initialization)의 중요성
t-SNE나 UMAP을 사용할 때 랜덤 초기화보다 PCA를 기반으로 한 초기화(init='pca')를 사용하면, 전역적인 데이터 구조를 더 안정적으로 보존하고 재현성을 높일 수 있습니다.
3. 실전: MNIST & CIFAR-10 차원 축소 시각화
이제 파이썬 코드를 통해 고차원의 이미지 데이터를 2차원으로 줄여 어떻게 군집화되는지 확인해 보겠습니다.
import numpy as np
from umap import UMAP
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from torchvision.datasets import MNIST, CIFAR10
# 1. 데이터셋 다운로드
mnist = MNIST(root='.', train=True, download=True)
cifar10 = CIFAR10(root='.', train=True, download=True)
# 2. PyTorch 데이터셋을 Scikit-learn 형태로 변환
def convert_sklearn_dataset(pytorch_dataset):
X, y = [], []
for image, label in pytorch_dataset:
x = np.array(image) / 255.0 # 정규화(0~1)
X.append(x)
y.append(label)
X = np.array(X)
# MNIST: 28x28 = 784차원 / CIFAR10: 32x32x3 = 3072차원
X = X.reshape(len(X), -1) # 1차원 벡터로 Flatten
y = np.array(y)
return X, y
mnist_X, mnist_y = convert_sklearn_dataset(mnist)
cifar10_X, cifar10_y = convert_sklearn_dataset(cifar10)
# 3. 모델 정의 및 샘플링 (시간 단축을 위해 1000개 추출)
tsne_random = TSNE(n_components=2, perplexity=30, init='random', random_state=2026)
tsne_pca = TSNE(n_components=2, perplexity=30, init='pca', random_state=2026)
umap_model = UMAP(n_components=2, min_dist=0.05, n_neighbors=8, random_state=2026)
np.random.seed(2026)
mnist_idx = np.random.choice(len(mnist_X), 1000, replace=False)
cifar10_idx = np.random.choice(len(cifar10_X), 1000, replace=False)
# 4. 시각화 함수
def plot_embedding(model, X, y, idx, title):
X_subset = X[idx]
y_subset = y[idx]
# 2차원으로 차원 축소 (1000, 2)
X_embedded = model.fit_transform(X_subset)
class_names = np.unique(y_subset)
plt.figure(figsize=(8, 6))
for i, class_name in enumerate(class_names):
plt.scatter(
X_embedded[y_subset == class_name, 0],
X_embedded[y_subset == class_name, 1],
color=plt.cm.tab10(i),
label=f'Class {class_name}',
alpha=0.7
)
plt.title(title)
plt.xlabel('Component 0')
plt.ylabel('Component 1')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
# 실행 예시
plot_embedding(tsne_pca, mnist_X, mnist_y, mnist_idx, "MNIST - t-SNE (PCA Init)")
plot_embedding(umap_model, cifar10_X, cifar10_y, cifar10_idx, "CIFAR10 - UMAP")
4. 심화: CLIP 모델을 활용한 멀티모달 임베딩 시각화
단순한 이미지 픽셀값이 아닌, 사전 학습된 딥러닝 모델(CLIP)이 추출한 임베딩(특징 벡터)을 UMAP으로 시각화해 봅니다. 이 과정을 통해 텍스트와 이미지가 벡터 공간에서 어떻게 매칭되는지 눈으로 확인할 수 있습니다.
import torch
import umap
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from transformers import CLIPProcessor, CLIPModel
from datasets import load_dataset
# 1. CLIP 모델 및 프로세서 로드
model_id = 'openai/clip-vit-base-patch32'
model = CLIPModel.from_pretrained(model_id)
processor = CLIPProcessor.from_pretrained(model_id)
# 2. 임베딩 추출 함수
def get_clip_embeddings(images, texts):
inputs = processor(text=texts, images=images, return_tensors='pt', padding=True, truncation=True)
with torch.no_grad():
# 이미지 임베딩 추출
vision_out = model.vision_model(pixel_values=inputs['pixel_values'])
image_embeds = model.visual_projection(vision_out.pooler_output)
# 텍스트 임베딩 추출
text_out = model.text_model(input_ids=inputs['input_ids'], attention_mask=inputs['attention_mask'])
text_embeds = model.text_projection(text_out.pooler_output)
return image_embeds.cpu().numpy(), text_embeds.cpu().numpy()
# 3. 데이터 로드 및 샘플링
dataset = load_dataset('clip-benchmark/wds_flickr8k')
subset = dataset['test'].select(range(100))
images, captions = list(subset['jpg']), list(subset['txt'])
# 4. 임베딩 및 UMAP 차원 축소
image_embeds, text_embeds = get_clip_embeddings(images, captions)
all_embeds = np.concatenate([image_embeds, text_embeds], axis=0) # 이미지+텍스트 벡터 통합
umap_model = umap.UMAP(n_neighbors=15, min_dist=0.1, metric='cosine')
reduced_embeds = umap_model.fit_transform(all_embeds)
# 축소된 좌표 분리
image_coords = reduced_embeds[:100]
text_coords = reduced_embeds[100:]
# 5. 시각화 (이미지 썸네일 및 텍스트 매핑)
fig, ax = plt.subplots(figsize=(20, 20))
ax.scatter(text_coords[:, 0], text_coords[:, 1], color='red', label='Captions', alpha=0.5)
def plot_with_images(ax, coords, images, captions, is_text=False):
for i, (x, y) in enumerate(coords):
if is_text:
# 캡션은 첫 30자만 출력
ax.text(x, y, str(captions[i])[:30], fontsize=9, color='darkred', ha='right')
else:
# 이미지는 리사이즈하여 썸네일로 삽입
img = images[i].resize((64, 64))
imagebox = OffsetImage(img, zoom=1.0)
ab = AnnotationBbox(imagebox, (x, y), frameon=False)
ax.add_artist(ab)
plot_with_images(ax, image_coords, images, captions, is_text=False)
plot_with_images(ax, text_coords, images, captions, is_text=True)
# 여백 조정 및 출력
x_min, y_min = reduced_embeds.min(axis=0)
x_max, y_max = reduced_embeds.max(axis=0)
pad_x, pad_y = (x_max - x_min) * 0.05, (y_max - y_min) * 0.05
ax.set_xlim(x_min - pad_x, x_max + pad_x)
ax.set_ylim(y_min - pad_y, y_max + pad_y)
plt.legend()
plt.tight_layout()
plt.savefig('clip_umap_visualization.png', dpi=200)
plt.show()

'개념 정리 step2 > 멀티모달(Multi-modal)' 카테고리의 다른 글
| [멀티모달] BLIP & BLIP-2 핵심 구조 및 실습 코드 정리 (0) | 2026.02.17 |
|---|---|
| [비전 AI] 텍스트로 객체를 찾는 Zero-Shot Detection부터 GroundingDINO까지 (0) | 2026.02.14 |
| [Vision-Language] CLIP 모델 핵심 정리 및 유사도 히트맵 실습 (0) | 2026.02.11 |
| [멀티모달] Multimodal Learning 정리 (0) | 2026.02.10 |
| [생성형 AI] GAN와 DCGAN 개념 정리와 실습 (0) | 2026.01.31 |