t-SNE
t-Distributed Stochastic Neighbor Embedding
고차원 데이터 시각화 기법. 비선형 차원 축소로 데이터 클러스터를 효과적으로 시각화.
t-Distributed Stochastic Neighbor Embedding
고차원 데이터 시각화 기법. 비선형 차원 축소로 데이터 클러스터를 효과적으로 시각화.
t-SNE(t-Distributed Stochastic Neighbor Embedding)는 고차원 데이터를 2D 또는 3D로 시각화하기 위한 비선형 차원 축소 기법입니다. 원본 데이터의 국소적 구조(가까운 점들 간의 관계)를 저차원 공간에서도 최대한 보존하여, 데이터의 군집 구조를 직관적으로 파악할 수 있게 해줍니다.
t-SNE는 2008년 Laurens van der Maaten과 Geoffrey Hinton이 제안했으며, 기존 SNE 알고리즘을 개선한 것입니다. 핵심 아이디어는 고차원에서의 유사도를 가우시안 분포로, 저차원에서의 유사도를 t-분포(Student's t-distribution)로 모델링하는 것입니다. t-분포의 두꺼운 꼬리가 "크라우딩 문제"를 해결하여 더 선명한 클러스터를 만들어냅니다.
t-SNE의 핵심 하이퍼파라미터는 perplexity입니다. 이는 각 점이 고려하는 유효 이웃 수와 관련되며, 보통 5~50 사이 값을 사용합니다. 작은 perplexity는 지역 구조를, 큰 perplexity는 전역 구조를 더 잘 포착합니다. 또한 learning_rate와 n_iter(반복 횟수)도 결과에 영향을 미치므로 적절한 튜닝이 필요합니다.
실무에서 t-SNE는 워드 임베딩 시각화, 이미지 특징 분석, 클러스터링 결과 검증 등에 널리 사용됩니다. 다만 계산 비용이 높아 대규모 데이터셋에서는 UMAP이 더 적합할 수 있습니다. 또한 t-SNE 결과의 클러스터 크기나 거리는 해석에 주의가 필요합니다. 이는 시각화 용도이지, 클러스터링 알고리즘 자체는 아닙니다.
# t-SNE 완전 가이드: 시각화, 최적화, 비교
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits, fetch_20newsgroups
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import time
# 1. 기본 t-SNE 시각화
def basic_tsne_visualization():
"""MNIST 숫자 데이터 t-SNE 시각화"""
# 데이터 로드
digits = load_digits()
X, y = digits.data, digits.target
print(f"데이터 형태: {X.shape}") # (1797, 64)
# t-SNE 적용
tsne = TSNE(
n_components=2, # 2D로 축소
perplexity=30, # 유효 이웃 수
learning_rate=200, # 학습률
n_iter=1000, # 반복 횟수
random_state=42
)
start_time = time.time()
X_tsne = tsne.fit_transform(X)
elapsed = time.time() - start_time
print(f"t-SNE 시간: {elapsed:.2f}초")
# 시각화
plt.figure(figsize=(12, 10))
scatter = plt.scatter(
X_tsne[:, 0], X_tsne[:, 1],
c=y,
cmap='tab10',
alpha=0.7,
s=20
)
plt.colorbar(scatter, label='숫자 클래스')
plt.title('MNIST Digits t-SNE Visualization')
plt.xlabel('t-SNE 1')
plt.ylabel('t-SNE 2')
plt.savefig('tsne_mnist.png', dpi=150)
plt.close()
return X_tsne
# 2. Perplexity 값에 따른 결과 비교
def compare_perplexity():
"""다양한 perplexity로 t-SNE 결과 비교"""
digits = load_digits()
X, y = digits.data, digits.target
perplexities = [5, 15, 30, 50, 100]
fig, axes = plt.subplots(1, 5, figsize=(25, 5))
for ax, perp in zip(axes, perplexities):
tsne = TSNE(
n_components=2,
perplexity=perp,
random_state=42
)
X_tsne = tsne.fit_transform(X)
ax.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
ax.set_title(f'Perplexity = {perp}')
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.savefig('tsne_perplexity_comparison.png', dpi=150)
plt.close()
# 3. 대용량 데이터: PCA 전처리 + t-SNE
def optimized_tsne_pipeline():
"""대용량 데이터를 위한 PCA + t-SNE 파이프라인"""
# 가상의 고차원 데이터 (10000 샘플, 784 차원)
np.random.seed(42)
n_samples = 10000
n_features = 784
# 3개 클러스터 생성
X1 = np.random.randn(n_samples // 3, n_features) + np.array([5] * n_features)
X2 = np.random.randn(n_samples // 3, n_features) + np.array([-5] * n_features)
X3 = np.random.randn(n_samples // 3, n_features)
X = np.vstack([X1, X2, X3])
y = np.array([0] * (n_samples // 3) + [1] * (n_samples // 3) + [2] * (n_samples // 3))
print(f"원본 데이터: {X.shape}")
# Step 1: 스케일링
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Step 2: PCA로 먼저 차원 축소 (t-SNE 속도 향상)
pca = PCA(n_components=50)
X_pca = pca.fit_transform(X_scaled)
print(f"PCA 후: {X_pca.shape}, 설명 분산: {pca.explained_variance_ratio_.sum():.2%}")
# Step 3: t-SNE 적용
start = time.time()
tsne = TSNE(
n_components=2,
perplexity=30,
n_iter=500,
init='pca', # PCA 초기화 (더 안정적)
random_state=42
)
X_tsne = tsne.fit_transform(X_pca)
print(f"t-SNE 시간: {time.time() - start:.2f}초")
# 시각화
plt.figure(figsize=(10, 8))
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=5, alpha=0.5)
plt.title('PCA + t-SNE Pipeline')
plt.savefig('tsne_optimized.png', dpi=150)
plt.close()
return X_tsne
# 4. t-SNE vs UMAP 비교
def tsne_vs_umap():
"""t-SNE와 UMAP 성능 및 결과 비교"""
try:
import umap
digits = load_digits()
X, y = digits.data, digits.target
# t-SNE
start = time.time()
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X)
tsne_time = time.time() - start
# UMAP
start = time.time()
umap_model = umap.UMAP(n_components=2, random_state=42)
X_umap = umap_model.fit_transform(X)
umap_time = time.time() - start
print(f"t-SNE 시간: {tsne_time:.2f}초")
print(f"UMAP 시간: {umap_time:.2f}초")
print(f"UMAP이 {tsne_time/umap_time:.1f}배 빠름")
# 시각화
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
axes[0].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
axes[0].set_title(f't-SNE ({tsne_time:.1f}s)')
axes[1].scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
axes[1].set_title(f'UMAP ({umap_time:.1f}s)')
plt.tight_layout()
plt.savefig('tsne_vs_umap.png', dpi=150)
plt.close()
except ImportError:
print("UMAP이 설치되지 않았습니다: pip install umap-learn")
# 5. 워드 임베딩 시각화
def visualize_word_embeddings():
"""워드 임베딩 t-SNE 시각화"""
try:
from gensim.models import KeyedVectors
# 사전 학습된 Word2Vec (예시용 작은 모델)
# 실제로는 'word2vec-google-news-300' 등 사용
words = ['king', 'queen', 'man', 'woman', 'dog', 'cat', 'car', 'bike']
# 가상의 임베딩 생성 (실제로는 Word2Vec에서 로드)
np.random.seed(42)
embeddings = np.random.randn(len(words), 100)
# t-SNE 적용
tsne = TSNE(n_components=2, perplexity=min(5, len(words)-1), random_state=42)
embeddings_2d = tsne.fit_transform(embeddings)
# 시각화
plt.figure(figsize=(10, 8))
for i, word in enumerate(words):
plt.scatter(embeddings_2d[i, 0], embeddings_2d[i, 1], s=100)
plt.annotate(word, (embeddings_2d[i, 0] + 0.1, embeddings_2d[i, 1]))
plt.title('Word Embeddings t-SNE')
plt.savefig('tsne_word_embeddings.png', dpi=150)
plt.close()
except ImportError:
print("gensim이 설치되지 않았습니다: pip install gensim")
# 6. 인터랙티브 시각화
def interactive_tsne():
"""Plotly를 사용한 인터랙티브 t-SNE"""
try:
import plotly.express as px
import pandas as pd
digits = load_digits()
X, y = digits.data, digits.target
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X)
df = pd.DataFrame({
't-SNE 1': X_tsne[:, 0],
't-SNE 2': X_tsne[:, 1],
'digit': y.astype(str)
})
fig = px.scatter(
df, x='t-SNE 1', y='t-SNE 2',
color='digit',
title='Interactive t-SNE Visualization',
hover_data={'digit': True}
)
fig.write_html('tsne_interactive.html')
print("인터랙티브 시각화: tsne_interactive.html 저장됨")
except ImportError:
print("plotly가 설치되지 않았습니다: pip install plotly")
# 실행
if __name__ == "__main__":
print("=== 기본 t-SNE ===")
basic_tsne_visualization()
print("\n=== Perplexity 비교 ===")
compare_perplexity()
print("\n=== 최적화 파이프라인 ===")
optimized_tsne_pipeline()
print("\n=== t-SNE vs UMAP ===")
tsne_vs_umap()
"임베딩 모델이 제대로 학습되었는지 확인하고 싶어요." - "t-SNE로 시각화해보면 어떨까요? 같은 클래스의 데이터가 잘 군집화되면 임베딩이 의미 있는 표현을 학습한 거예요. perplexity 30 정도로 시작해서, 군집이 잘 안 보이면 값을 조절해보세요. 단, 클러스터 크기나 거리는 해석에 주의가 필요해요."
"t-SNE와 PCA의 차이를 설명해주세요." - "PCA는 선형 변환으로 전역 분산을 최대화하지만, t-SNE는 비선형이고 국소 구조를 보존해요. PCA는 빠르고 역변환이 가능하지만 복잡한 구조를 못 잡고, t-SNE는 클러스터를 잘 분리하지만 느리고 새 데이터 변환이 안 돼요. 보통 대용량 데이터에서는 PCA로 50차원 정도 먼저 줄이고, t-SNE를 적용하는 게 효율적입니다."
"t-SNE가 느린데 대안이 있나요?" - "UMAP을 추천해요. t-SNE보다 10배 이상 빠르고, 전역 구조도 더 잘 보존합니다. 또한 새 데이터를 기존 임베딩에 추가할 수 있어서 프로덕션에서도 활용 가능해요. 단, 처음 분석할 때는 t-SNE로 여러 perplexity를 시험해보고, 최종 시각화는 UMAP으로 하는 것도 방법입니다."
t-SNE 결과에서 클러스터 크기가 크다고 그 클래스가 더 분산되어 있다거나, 두 클러스터가 가깝다고 실제로 유사하다고 해석하면 안 됩니다. t-SNE는 국소 구조만 보존합니다.
t-SNE는 확률적 알고리즘이라 random_state에 따라 결과가 달라집니다. 또한 perplexity와 learning_rate에 민감하므로, 여러 설정으로 실험해보고 일관된 패턴을 찾아야 합니다.
고차원 데이터는 PCA로 먼저 50~100차원으로 줄인 후 t-SNE를 적용하세요. 여러 perplexity(5, 30, 50)로 시험하고, 결과를 보조 분석 도구로만 활용하세요. 클러스터링에는 K-means나 DBSCAN을 별도로 사용하는 것이 좋습니다.