HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
장지원 페이지/
📕
2024 UGRP
/
Member Page
Member Page
/
장지원
장지원
/
#35. ToDo

#35. ToDo

태그
연구
날짜
Nov 26, 2024
상태
완료
tsne
Silhouette Score TISTORYTISTORY[머신러닝] 클러스터링 평가지표 - 실루엣 계수 (1)
[머신러닝] 클러스터링 평가지표 - 실루엣 계수 (1)

[머신러닝] 클러스터링 평가지표 - 실루엣 계수 (1)

실루엣 계수(Silhouette Coefficient) : 각 데이터 포인트와 주위 데이터 포인트들과의 거리 계산을 통해 값을 구하며, 군집 안에 있는 데이터들은 잘 모여있는지, 군집끼리는 서로 잘 구분되는지 클러스터링을 평가하는 척도로 활용된다. * 참고한 논문의 표현을 빌리자면, 군집 내 비유사성('within' dissimilarities)은 작고, 군집 간 비유사성('between' dissimilarities)은 커야 생성된 클러스터의 품질이 좋다고 할 수 있다. 이번 포스팅에서는 실루엣 계수를 구하는 방법과 평가 지표로써의 장단점에 대해 알아보고자 한다. 실루엣 계수 구하는 방법 왼쪽 그림처럼 어떠한 클러스터링 기법에 의해 총 10개의 데이터 포인트들이 3개의 군집으로 나눠졌다고 하자. 클러스..

TISTORYTISTORY
코드 (일반)
from transformers import CLIPProcessor, CLIPModel from datasets import load_dataset from torch.utils.data import DataLoader import torch from transformers import AdamW from tqdm import tqdm from sklearn.manifold import TSNE from sklearn.metrics import silhouette_score from sklearn.cluster import KMeans import matplotlib.pyplot as plt import numpy as np # 1. 모델 및 프로세서 로드 model_name = "openai/clip-vit-base-patch32" model = CLIPModel.from_pretrained(model_name) processor = CLIPProcessor.from_pretrained(model_name) # 2. 데이터셋 로드 및 분리 dataset = load_dataset("JANGJIWON/UGRP_sketchset_textbook") split_dataset = dataset["train"].train_test_split(test_size=0.2, seed=42) # 80% train, 20% test train_dataset = split_dataset["train"] test_dataset = split_dataset["test"] # 3. 레이블을 감정 텍스트로 매핑 possible_labels = ["Happiness", "Sadness", "Disgust", "Fear", "Anger", "Surprise"] # 4. 데이터셋 처리 함수 정의 def collate_fn(samples): images = [s['image'] for s in samples] labels = [s['label'] for s in samples] inputs = processor(images=images, text=possible_labels, return_tensors="pt", padding=True) inputs['labels'] = torch.tensor(labels) return inputs # DataLoader 설정 train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, collate_fn=collate_fn) test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False, collate_fn=collate_fn) # 5. 학습 설정 optimizer = AdamW(model.parameters(), lr=5e-5) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) num_epochs = 5 # 6. 학습 및 평가 함수 def evaluate(model, loader): model.eval() correct = 0 total = 0 with torch.no_grad(): for batch in loader: inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} labels = batch['labels'].to(device) outputs = model(**inputs) logits = outputs.logits_per_image # 이미지-텍스트 유사도 preds = logits.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return 100 * correct / total # 7. 학습 루프 for epoch in range(1, num_epochs + 1): model.train() epoch_loss = 0 for batch in tqdm(train_loader, desc=f"Epoch {epoch}/{num_epochs}"): inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} labels = batch['labels'].to(device) outputs = model(**inputs) loss = torch.nn.functional.cross_entropy(outputs.logits_per_image, labels) loss.backward() optimizer.step() optimizer.zero_grad() epoch_loss += loss.item() print(f"Epoch {epoch} Loss: {epoch_loss / len(train_loader):.4f}") # Train 정확도 train_acc = evaluate(model, train_loader) print(f"Train Accuracy: {train_acc:.2f}%") # Test 정확도 test_acc = evaluate(model, test_loader) print(f"Test Accuracy: {test_acc:.2f}%") # 8. t-SNE 시각화를 위한 임베딩 추출 def extract_embeddings(model, loader): model.eval() image_embeddings = [] labels = [] with torch.no_grad(): for batch in loader: inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} batch_labels = batch['labels'].numpy() outputs = model(**inputs) image_embeds = outputs.image_embeds.cpu().numpy() image_embeddings.append(image_embeds) labels.extend(batch_labels) image_embeddings = np.vstack(image_embeddings) return image_embeddings, np.array(labels) # Silhouette Score 계산 함수 def calculate_silhouette_score(tsne_embeddings, labels, n_clusters): # KMeans를 사용해 클러스터링 수행 kmeans = KMeans(n_clusters=n_clusters, random_state=42) cluster_labels = kmeans.fit_predict(tsne_embeddings) # Silhouette Score 계산 score = silhouette_score(tsne_embeddings, cluster_labels) return score # Train 데이터에서 임베딩 추출 image_embeds, embed_labels = extract_embeddings(model, train_loader) # t-SNE 적용 tsne = TSNE(n_components=2, random_state=42) image_tsne = tsne.fit_transform(image_embeds) # Train 데이터 Silhouette Score 계산 train_silhouette_score = calculate_silhouette_score(image_tsne, embed_labels, n_clusters=len(possible_labels)) print(f"Train Data Silhouette Score: {train_silhouette_score:.4f}") # 시각화 plt.figure(figsize=(10, 7)) scatter = plt.scatter(image_tsne[:, 0], image_tsne[:, 1], c=embed_labels, cmap='viridis', alpha=0.7) plt.colorbar(scatter, ticks=range(len(possible_labels)), label='Emotion Labels') plt.title('t-SNE Visualization of Train Image Embeddings') plt.xlabel('t-SNE 1') plt.ylabel('t-SNE 2') plt.show() # Test 데이터에서 임베딩 추출 test_image_embeds, test_embed_labels = extract_embeddings(model, test_loader) # t-SNE 적용 perplexity = min(30, len(test_image_embeds) - 1) # perplexity는 샘플 수보다 작아야 함 tsne = TSNE(n_components=2, random_state=42, perplexity=perplexity) # Test 데이터에 t-SNE 적용 test_image_tsne = tsne.fit_transform(test_image_embeds) # Test 데이터 Silhouette Score 계산 test_silhouette_score = calculate_silhouette_score(test_image_tsne, test_embed_labels, n_clusters=len(possible_labels)) print(f"Test Data Silhouette Score: {test_silhouette_score:.4f}") # Test 데이터 시각화 plt.figure(figsize=(10, 7)) scatter = plt.scatter(test_image_tsne[:, 0], test_image_tsne[:, 1], c=test_embed_labels, cmap='viridis', alpha=0.7) plt.colorbar(scatter, ticks=range(len(possible_labels)), label='Emotion Labels') plt.title('t-SNE Visualization of Test Image Embeddings') plt.xlabel('t-SNE 1') plt.ylabel('t-SNE 2') plt.show()
코드 (모네)
from huggingface_hub import hf_hub_download from transformers import CLIPProcessor, CLIPModel from datasets import load_dataset from torch.utils.data import DataLoader import torch from transformers import AdamW from tqdm import tqdm from sklearn.manifold import TSNE from sklearn.metrics import silhouette_score from sklearn.cluster import KMeans import matplotlib.pyplot as plt import numpy as np # 1. 모델 및 프로세서 로드 model_name = "openai/clip-vit-base-patch32" model = CLIPModel.from_pretrained(model_name) processor = CLIPProcessor.from_pretrained(model_name) model_weights_path = hf_hub_download(repo_id="JANGJIWON/EmoSet118K_MonetStyle_CLIP_student", filename="CLIPEmoset118k_mone.pth") model.load_state_dict(torch.load(model_weights_path, map_location='cpu'), strict=False) # 2. 데이터셋 로드 및 분리 dataset = load_dataset("JANGJIWON/UGRP_sketchset_textbook") split_dataset = dataset["train"].train_test_split(test_size=0.2, seed=42) # 80% train, 20% test train_dataset = split_dataset["train"] test_dataset = split_dataset["test"] # 3. 레이블을 감정 텍스트로 매핑 possible_labels = ["Happiness", "Sadness", "Disgust", "Fear", "Anger", "Surprise"] # 4. 데이터셋 처리 함수 정의 def collate_fn(samples): images = [s['image'] for s in samples] labels = [s['label'] for s in samples] inputs = processor(images=images, text=possible_labels, return_tensors="pt", padding=True) inputs['labels'] = torch.tensor(labels) return inputs # DataLoader 설정 train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, collate_fn=collate_fn) test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False, collate_fn=collate_fn) # 5. 학습 설정 optimizer = AdamW(model.parameters(), lr=5e-5) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) num_epochs = 5 # 6. 학습 및 평가 함수 def evaluate(model, loader): model.eval() correct = 0 total = 0 with torch.no_grad(): for batch in loader: inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} labels = batch['labels'].to(device) outputs = model(**inputs) logits = outputs.logits_per_image # 이미지-텍스트 유사도 preds = logits.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return 100 * correct / total # 7. 학습 루프 for epoch in range(1, num_epochs + 1): model.train() epoch_loss = 0 for batch in tqdm(train_loader, desc=f"Epoch {epoch}/{num_epochs}"): inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} labels = batch['labels'].to(device) outputs = model(**inputs) loss = torch.nn.functional.cross_entropy(outputs.logits_per_image, labels) loss.backward() optimizer.step() optimizer.zero_grad() epoch_loss += loss.item() print(f"Epoch {epoch} Loss: {epoch_loss / len(train_loader):.4f}") # Train 정확도 train_acc = evaluate(model, train_loader) print(f"Train Accuracy: {train_acc:.2f}%") # Test 정확도 test_acc = evaluate(model, test_loader) print(f"Test Accuracy: {test_acc:.2f}%") # 8. t-SNE 시각화를 위한 임베딩 추출 def extract_embeddings(model, loader): model.eval() image_embeddings = [] labels = [] with torch.no_grad(): for batch in loader: inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"} batch_labels = batch['labels'].numpy() outputs = model(**inputs) image_embeds = outputs.image_embeds.cpu().numpy() image_embeddings.append(image_embeds) labels.extend(batch_labels) image_embeddings = np.vstack(image_embeddings) return image_embeddings, np.array(labels) # Silhouette Score 계산 함수 def calculate_silhouette_score(tsne_embeddings, labels, n_clusters): # KMeans를 사용해 클러스터링 수행 kmeans = KMeans(n_clusters=n_clusters, random_state=42) cluster_labels = kmeans.fit_predict(tsne_embeddings) # Silhouette Score 계산 score = silhouette_score(tsne_embeddings, cluster_labels) return score # Train 데이터에서 임베딩 추출 image_embeds, embed_labels = extract_embeddings(model, train_loader) # t-SNE 적용 tsne = TSNE(n_components=2, random_state=42) image_tsne = tsne.fit_transform(image_embeds) # Train 데이터 Silhouette Score 계산 train_silhouette_score = calculate_silhouette_score(image_tsne, embed_labels, n_clusters=len(possible_labels)) print(f"Train Data Silhouette Score: {train_silhouette_score:.4f}") # 시각화 plt.figure(figsize=(10, 7)) scatter = plt.scatter(image_tsne[:, 0], image_tsne[:, 1], c=embed_labels, cmap='viridis', alpha=0.7) plt.colorbar(scatter, ticks=range(len(possible_labels)), label='Emotion Labels') plt.title('t-SNE Visualization of Train Image Embeddings') plt.xlabel('t-SNE 1') plt.ylabel('t-SNE 2') plt.show() # Test 데이터에서 임베딩 추출 test_image_embeds, test_embed_labels = extract_embeddings(model, test_loader) # t-SNE 적용 perplexity = min(30, len(test_image_embeds) - 1) # perplexity는 샘플 수보다 작아야 함 tsne = TSNE(n_components=2, random_state=42, perplexity=perplexity) # Test 데이터에 t-SNE 적용 test_image_tsne = tsne.fit_transform(test_image_embeds) # Test 데이터 Silhouette Score 계산 test_silhouette_score = calculate_silhouette_score(test_image_tsne, test_embed_labels, n_clusters=len(possible_labels)) print(f"Test Data Silhouette Score: {test_silhouette_score:.4f}") # Test 데이터 시각화 plt.figure(figsize=(10, 7)) scatter = plt.scatter(test_image_tsne[:, 0], test_image_tsne[:, 1], c=test_embed_labels, cmap='viridis', alpha=0.7) plt.colorbar(scatter, ticks=range(len(possible_labels)), label='Emotion Labels') plt.title('t-SNE Visualization of Test Image Embeddings') plt.xlabel('t-SNE 1') plt.ylabel('t-SNE 2') plt.show()
결과 비교
결과 비교

Accuracy

Epoch 1/5: 100%|██████████| 5/5 [00:31<00:00, 6.21s/it] Epoch 1 Loss: 2.3623 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 2/5: 100%|██████████| 5/5 [00:28<00:00, 5.62s/it] Epoch 2 Loss: 1.5584 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 3/5: 100%|██████████| 5/5 [00:28<00:00, 5.70s/it] Epoch 3 Loss: 1.4939 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 4/5: 100%|██████████| 5/5 [00:28<00:00, 5.63s/it] Epoch 4 Loss: 1.2970 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 5/5: 100%|██████████| 5/5 [00:28<00:00, 5.64s/it] Epoch 5 Loss: 1.2707 Train Accuracy: 63.16% Test Accuracy: 60.00%
Epoch 1/5: 100%|██████████| 5/5 [00:03<00:00, 1.36it/s] Epoch 1 Loss: 2.6947 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 2/5: 100%|██████████| 5/5 [00:03<00:00, 1.59it/s] Epoch 2 Loss: 1.4273 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 3/5: 100%|██████████| 5/5 [00:03<00:00, 1.62it/s] Epoch 3 Loss: 1.3362 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 4/5: 100%|██████████| 5/5 [00:03<00:00, 1.64it/s] Epoch 4 Loss: 1.2291 Train Accuracy: 60.53% Test Accuracy: 60.00% Epoch 5/5: 100%|██████████| 5/5 [00:03<00:00, 1.63it/s] Epoch 5 Loss: 1.3526 Train Accuracy: 60.53% Test Accuracy: 60.00%

t-sne

일반
train
Train Data Silhouette Score: 0.6272
notion image
Test Data Silhouette Score: -0.0217
notion image
모네
train
Train Data Silhouette Score: 0.5273
notion image
Test Data Silhouette Score: -0.0063
notion image
 
모네가 미세하게 Silhouette score가 더 높다
⇒ train은 일반이 0에 더 가깝고, test는 모네가 0에 더 가깝다. overfitting 관점에서 설명해도 좋을듯!?
  • 실루엣 계수의 평균값이 1에 가까울수록 군집화가 잘 되었다고 생각할 수 있다.
  • 각 클러스터 내의 데이터 포인트들의 실루엣 계수 평균값을 구하여, 각 클러스터별 평균값도 구할 수 있다. 1에 가까운 평균값을 가지는 클러스터는 'clear-cut' 클러스터, 0에 가까운 값을 가지는 클러스터는 'weak' 클러스터로 표현된다.

추가적으로 실루엣 계수에 대해 조금 더 살펴보면,
  • s(i)가 0에 가까운 경우는, 두 군집 간 거리가 거의 비슷한 경우를 의미하며 잘 구분되지 않은 상태이다.
    • s(i)
  • s(i)가 -1에 가까운 경우는, 데이터 포인트 i가 오히려 이웃 클러스터에 더 가까운 경우를 의미하며 아예 잘못 할당된 상태라고 볼 수 있다. 그래서 이러한 경우는 실제로는 거의 나타나지 않는다.
    • s(i)
 
  1. 프롬프트는 따로 넣어주기 → clear
  1. train 코드 작성하기 → clear
  1. test data 일정하게 넣어주기 → not clear