👽 졸업 논문 ( 17 ) - Zoobot 분석

2025. 5. 13. 12:19개발/👽 졸업 논문

 

 

Zoobot 개요

 

📌  목적

Zoobot은 Galaxy Zoo 프로젝트에서 구축한 은하 이미지 분류 모델

 

  • 은하의 모양 분류 (나선 vs 타원, 바의 유무, 회전 방향 등)
  • 구조적 특징 분석
  • Galaxy Zoo 설문 결과 예측 : 사용자 투표 기반
1.  은하의 전반적인 형태는 무엇인가요?   
Smooth / Features or Disk / Star or Artifact

2.  은하에 중심 막대 구조가 있나요?
Yes / No

3.  은하에 나선 구조가 있나요?
Yes / No

4.  나선팔이 몇 개 있나요?
1개 / 2개 / 3개 / 4개 / 5개 이상 / 확실하지 않음

5. 은하의 나선팔이 시계 방향으로 감기나요, 반시계 방향으로 감기나요?
시계 방향 / 반시계 방향 / 확실하지 않음

6.  은하에 중심 팽대부가 있나요?
Yes / No

7.  은하가 다른 은하와 상호작용하거나 병합 중인가요?
Yes / No / 확실하지 않음

8.  은하에 링 구조가 있나요?
Yes / No / 확실하지 않음

9.  은하에 불규칙한 구조나 이상한 특징이 있나요?
Yes / No / 확실하지 않음

 

📌  구조

Encoder + Classification Head

Encoder : 이미지 임베딩( ex - 모양, 색, 모서리 등 이미지에서 추출한 특징 ) 추출기

Classification Head : Galaxy Zoo의 설문 항목들에 대한 분류 결과를 뱉어냄

 


 

📍 우리 모델 구조

 

Zoobot에서 Classification Head 떼어내고 Encoder만 가져올 예정

Encoder(이미지 임베딩) + 수치 데이터 벡터 -> Custom Classification Head

이미지 인코더: zoobot의 pretrained encoder
(ex - ConvNeXt, EfficientNet 등 => timm 라이브러리로 여러 encoder test 가능)

수치 데이터: 시뮬레이션에서 얻은 물리량 (ex. 질량, 속도분산 등)

라벨: 병합 여부

학습 방식: 이미지 인코더 출력 + 수치 데이터 → 융합 → fully-connected head → 지도 학습

 

코드 구조

import torch
import torch.nn as nn
import timm

# 1. Zoobot 이미지 인코더 불러오기 (ConvNeXt Nano 기반)
#    - Hugging Face Hub에서 pretrained 모델을 로드
#    - num_classes=0 → classification head 제거하고 feature extractor로만 사용
encoder = timm.create_model(
    'hf_hub:mwalmsley/zoobot-encoder-convnext_nano',
    pretrained=True,
    num_classes=0
)

# 2. 이미지 + 수치 데이터를 입력받아 병합 여부를 예측하는 멀티모달 분류 모델 정의
class MergerClassifier(nn.Module):
    def __init__(self, encoder, num_numerical_features):
        """
        Args:
            encoder (nn.Module): pretrained image encoder (Zoobot에서 가져온 ConvNeXt 등)
            num_numerical_features (int): 수치 데이터의 feature 수 (예: 질량, 속도, 금속도 등)
        """
        super().__init__()
        
        # 이미지 인코더 (Zoobot에서 가져온 pretrained 모델)
        self.encoder = encoder
        
        # 이미지 인코더의 출력 차원 수 (예: EfficientNet-B0은 1280, ConvNeXt Nano는 768)
        self.encoder_out_dim = encoder.num_features

        # 이미지 임베딩 + 수치 feature → MLP로 이진 분류
        self.mlp = nn.Sequential(
            nn.Linear(self.encoder_out_dim + num_numerical_features, 128),  # feature 합쳐서 128차원으로 축소
            nn.ReLU(),                                                       # 비선형 활성화 함수
            nn.Linear(128, 1)                                                # 출력층: 1개의 값 (시그모이드를 통과해 확률로 해석)
        )

    def forward(self, image, numerical):
        """
        Forward pass (순전파)

        Args:
            image (Tensor): 이미지 배치 (예: [B, 3, 224, 224])
            numerical (Tensor): 수치 데이터 배치 (예: [B, N])

        Returns:
            Tensor: 병합 확률 (예: [B, 1])
        """
        # 1. 이미지 → 인코더 → 임베딩 벡터 (예: [B, 768])
        img_features = self.encoder(image)

        # 2. 이미지 임베딩 + 수치 feature를 채널 차원(axis=1)으로 concat
        #    → [B, encoder_out_dim + num_numerical_features]
        combined = torch.cat([img_features, numerical], dim=1)

        # 3. 결합된 feature를 MLP에 통과시켜 병합 확률 예측
        return self.mlp(combined)