2025. 5. 22. 13:58ㆍ개발/👽 졸업 논문
논문만 읽다가 너무 지쳐버려숴...
차라리 코드를 좀 짜보자는 생각으로 Zoobot Finetuning에 도전해보았다
🛠 Fine-Tuning
파인튜닝 파인튜닝 많이 들어보긴 했는데
정확히 어떤 걸 말하는지, 어떤 구조로 이루어지는지 잘 몰랐어서
한번 조사해보았다
Zoobot과 같은 딥러닝 모델들은
여러 블록으로 이루어진 인코더 + classification head로 이루어져 있는데
이때 파인튜닝은 크게 아래와 같은 두가지 방식이 있다고 한다
1 ) 기존의 모델에서 classification head를 떼고 custom한 classification head를 붙이는 것
2 ) 앞단의 인코더 block부터 재학습시키는 것
보통 파인튜닝에 사용할 새로운 데이터가 많거나 매우 중요하면 2번의 방식을
데이터양이 적고 기존 모델의 임베딩이 탄탄하다면 1번의 방식을 사용한다고 한다
1번을 택할지, 2번을 택할지는 추후에 우리가 새롭게 받게 될 TNG 데이터의 양이 Zoobot을 모델링할 때 사용했던 데이터양의 몇 퍼센트 정도 되는지 확인해보고 판단해도 좋을 듯하다
우선 지금은 얼마 전에 읽었던 Zoobot 파인튜닝 논문에서 1번의 방식을 사용했기 때문에... 나도 1번 방식을 사용해보도록 하겠다
🛠 Zoobot Fine-Tuning
Zoobot Fine-Tuning Flow
새롭게 학습시킬 이미지 데이터 -> Zoobot Encoder -> 임베딩 추출
|-- 결합 -> 학습 -> classification head
해당 이미지 데이터의 수치 데이터 -> MLP -> 2D 피처 맵 추출
Data 구조
지금 아직 TNG 데이터세트가 준비가 안 돼서 임의로 아래와 같이 데이터 구성했다
(어차피 파인튜닝 코드 한번 써보려고 한 거라서ㅎㅎ)
1. 은하 병합 여부(True/False) 라벨링된 이미지 데이터
Kaggle Galaxy Zoo Dataset
https://www.kaggle.com/competitions/galaxy-zoo-the-galaxy-challenge/data
- images_training_rev1 : GalaxyID로 저장되어 있는 은하 이미지
- training_solutions_rev1 : GalaxyID별 GalaxyZoo 설문 응답
위 두 압축 파일을 사용했다
2. 수치 데이터
그냥 각 항목별 range 알아내서 range 내에서 랜덤으로 값 생성했움...
- SubhaloSFR
- SubhaloVelDisp
- SubhaloMass
- SubhaloGasMetallicity
- SubhaloHalfmassRad
약 10000개 row로 구성함
Zoobot Import
!git clone https://github.com/mwalmsley/zoobot.git
!pip install zoobot[pytorch-colab] -q
!pip install albumentations==1.4.24
Zoobot GitHub에서 Quick Start 들어가보면 Colab Notebook 잘 작성되어 있움
Zoobot_v2_Finetune_Classifier.ipynb
Colab notebook
colab.research.google.com
Labeling
is_merger = df["Class8.4"] > 0.5
df["label"] = is_merger.astype(int)
print(df["label"].value_counts())
GalaxyID별 GalaxyZoo 설문 응답에서
8번 질문이 Q8. What is the odd feature? 였고 이에 대한 4번 답변이 Merge였다
따라서 "Class8.4" 칼럼의 값이 True인 애들을 병합 중인 은하로 라벨링했다
Tabular Data Standardization
# 정규화
scaler = StandardScaler()
scaled_array = scaler.fit_transform(tabular_col)
# numpy 배열을 다시 DataFrame으로 변환할 때 컬럼명 설정 가능
tabular_col_scaled = pd.DataFrame(scaled_array, columns=tabular_col.columns)
Pytorch Dataset 정의
# zoobot 추상 클래스 import
from zoobot.pytorch.training.finetune import FinetuneableZoobotAbstract
# ===============================
# 1. 커스텀 PyTorch Dataset 정의
# ===============================
class MultimodalDataset(Dataset):
# 클래스 생성자
'''
image_paths : 이미지 경로
tabular_data : 수치 데이터
labels : 라벨
trasform : 이미지 전처리 도구 - 크기 맞추기 등
'''
def __init__(self, image_paths, tabular_data, labels, transform=None):
self.image_paths = image_paths
self.tabular_data = tabular_data
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
# 이미지 데이터 RGB로 변환
image = Image.open(self.image_paths[idx]).convert("RGB")
# 이미지 전처리
if self.transform:
image = self.transform(image)
tabular_row = self.tabular_data.iloc[idx].values if isinstance(self.tabular_data, pd.DataFrame) else self.tabular_data[idx]
label_value = self.labels.iloc[idx] if isinstance(self.labels, pd.Series) else self.labels[idx]
# 수치 데이터 tensor 구조로 변환
tabular = torch.tensor(tabular_row, dtype=torch.float32)
# 라벨 tensor 구조로 변환
label = torch.tensor(label_value, dtype=torch.long)
return image, tabular, label
PyTorch Lightning DataModule 정의
# ===============================
# 2. PyTorch Lightning DataModule 정의
# ===============================
class MultimodalDataModule(pl.LightningDataModule):
"""
학습 및 검증용 데이터 로더 제공
"""
def __init__(self, train_dataset, val_dataset, batch_size=32):
super().__init__()
self.train_dataset = train_dataset
self.val_dataset = val_dataset
self.batch_size = batch_size
def train_dataloader(self):
return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True)
def val_dataloader(self):
return DataLoader(self.val_dataset, batch_size=self.batch_size)
Zoobot Model Architecture 생성
class MultimodalZoobotModel(FinetuneableZoobotAbstract):
"""
Zoobot 이미지 인코더 + 수치형 MLP + 새로운 classification head
"""
def __init__(self, name, n_blocks, learning_rate, lr_decay, tabular_input_dim, tabular_embed_dim, num_classes):
super().__init__(name=name, n_blocks=n_blocks, learning_rate=learning_rate, lr_decay=lr_decay)
# 수치형 데이터 처리 MLP
self.tabular_branch = nn.Sequential(
nn.Linear(tabular_input_dim, 64),
nn.ReLU(),
nn.Linear(64, tabular_embed_dim)
)
# 정확한 이미지 임베딩 차원
image_embed_dim = 640 # ConvNeXt Nano 기준 (Zoobot)
# 이미지 + 수치 임베딩을 결합하여 분류하는 head
self.head = nn.Sequential(
nn.Linear(image_embed_dim + tabular_embed_dim, 128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, num_classes)
)
self.loss_fn = nn.CrossEntropyLoss()
def forward(self, image, tabular):
image_embed = self.encoder(image) # shape: (B, 640)
tabular_embed = self.tabular_branch(tabular) # shape: (B, tabular_embed_dim)
combined = torch.cat([image_embed, tabular_embed], dim=1)
print(f"combined shape: {combined.shape}") # 디버깅 출력
return self.head(combined)
def training_step(self, batch, batch_idx):
image, tabular, label = batch
logits = self(image, tabular)
loss = self.loss_fn(logits, label)
self.log("train_loss", loss)
return loss
def validation_step(self, batch, batch_idx):
image, tabular, label = batch
logits = self(image, tabular)
loss = self.loss_fn(logits, label)
acc = (logits.argmax(dim=1) == label).float().mean()
self.log("val_loss", loss)
self.log("val_acc", acc)
return {"loss": loss, "acc": acc}
Dataset Setting
# ===============================
# 4. 데이터셋 준비
# ===============================
# 실제 이미지 폴더 경로 지정
image_dir = "/content/drive/MyDrive/Zoobot/images_training_rev1"
image_paths = [os.path.join(image_dir, f"{gal_id}.jpg") for gal_id in id_col]
# 수치형 데이터 및 라벨 예시 (리스트 or numpy)
tabular_data = tabular_col
labels = label_col
# 이미지 전처리 정의 (Zoobot은 보통 224x224 사용)
transform = T.Compose([
T.Resize((224, 224)),
T.ToTensor(),
T.Normalize(mean=[0.5]*3, std=[0.5]*3)
])
# 학습/검증 데이터셋 분리
split_idx = int(0.8 * len(image_paths))
train_dataset = MultimodalDataset(image_paths[:split_idx], tabular_data[:split_idx], labels[:split_idx], transform)
val_dataset = MultimodalDataset(image_paths[split_idx:], tabular_data[split_idx:], labels[split_idx:], transform)
for i in range(3): # 처음 3개 샘플 확인
image, tabular, label = train_dataset[i]
print(f"[샘플 {i}]")
print(f" - 이미지 텐서 shape: {image.shape}")
print(f" - 수치형 데이터 텐서: {tabular}")
print(f" - 라벨: {label}")
print("="*40)
Model Training
# ===============================
# 5. 학습 실행
# ===============================
# Lightning DataModule 생성
datamodule = MultimodalDataModule(train_dataset, val_dataset, batch_size=32)
# 모델 인스턴스 생성
model = MultimodalZoobotModel(
name="hf_hub:mwalmsley/zoobot-encoder-convnext_nano", # Zoobot 이미지 인코더 이름
n_blocks=0, # encoder에서 freeze할 블록 수
learning_rate=1e-5,
lr_decay=0.5,
tabular_input_dim=5, # 수치형 feature 수
tabular_embed_dim=2, # 수치 feature에서 추출할 임베딩 차원
num_classes=2 # 분류 클래스 수
)
# Trainer 정의 및 학습 시작
trainer = pl.Trainer(max_epochs=10, accelerator="gpu" if torch.cuda.is_available() else "cpu")
trainer.fit(model, datamodule=datamodule)