Giới thiệu

Table of Content


Coordinate pretrained models

1. Set up

!pip install kaggle
from google.colab import files
uploaded = files.upload()
for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))
# Then move kaggle.json into the folder where the API expects to find it.
!mkdir -p ~/.kaggle/ && mv kaggle.json ~/.kaggle/ && chmod 600 ~/.kaggle/kaggle.json
from google.colab import drive
drive.mount('./gdrive')
!kaggle datasets download -d brendanartley/lumbar-coordinate-pretraining-dataset
!unzip -qq "/content/lumbar-coordinate-pretraining-dataset.zip"
!pip install -q pytorch-lightning & pip install -q -U albumentations & pip install -q iterative-stratification
!pip install -q timm & pip install -q einops & pip install -q pytorch-lightning wandb & pip install torch-ema
!git clone https://github.com/mlpc-ucsd/CoaT
!pip install -q pydicom
import wandb
from pytorch_lightning.loggers import WandbLogger
import os
import yaml
import sys
import cv2
import random
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import torch
from glob import glob
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from torch.optim import AdamW, Adam
from torch.optim.lr_scheduler import CosineAnnealingLR, StepLR, ReduceLROnPlateau
import torch.nn as nn
import pytorch_lightning as pl
from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping, TQDMProgressBar
import torchvision.transforms as T
import albumentations as A
import pandas.api.types
import sklearn.metrics
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold, StratifiedGroupKFold
from iterstrat.ml_stratifiers import MultilabelStratifiedKFold
\#from torchvision.models.maxvit import MaxVit
from timm.models.maxxvit import MaxxVit
import timm
import scipy
import albumentations as A
from torchvision.transforms import v2
from torchvision import models
from einops import repeat
from einops.layers.torch import Rearrange
from tqdm.auto import tqdm
sys.path.append('/content/CoaT')
from CoaT.src.models.coat import coat_lite_medium
from joblib import Parallel, delayed
from torch.utils.data import default_collate
from torch_ema import ExponentialMovingAverage
import pydicom as dcm
import transformers
from PIL import Image, ImageFilter
SEED = 126 # friend's birthday
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def seed_everything(seed):
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True # Fix the network according to random seed
    print('Finish seeding with seed {}'.format(seed))
seed_everything(SEED)
print('Training on device {}'.format(device))
time
import warnings
warnings.filterwarnings("ignore")
#############\#COORDINATE DETECT#########################
print(T_MAX)
\#for i in [5]:
for i in [0]:
    dataset_train = CoorDetectDataset(train_coor)
    dataset_validation = CoorDetectDataset(valid_coor)
    data_loader_train = DataLoader(
        dataset_train,
        batch_size=4,
        shuffle=True,
        num_workers=8,
        pin_memory=True,
    )
    data_loader_validation = DataLoader(
        dataset_validation,
        batch_size=8,
        shuffle=False,
        num_workers=8,
        pin_memory=True
    )
    checkpoint_callback = ModelCheckpoint(
        save_weights_only=True,
        monitor="val_loss",
        dirpath="/content/gdrive/MyDrive/RSNA_SPINE/pretrained_models",
        mode='min',
        filename=f"scs_detect_pretrained_efficientnetv2l_1e-4",
        save_top_k=config["save_topk"],
        verbose=1,
    )
    progress_bar_callback = TQDMProgressBar(
        refresh_rate=config["progress_bar_refresh_rate"]
    )
    early_stop_callback = EarlyStopping(**config["early_stop"])
    _c, _k = config['task']['condition'], config['task']['kind']
    wandb_logger = WandbLogger(project=f'rsna_spine_coor_pretrained', # group runs in "MNIST" project
                            log_model=False) # log all new checkpoints during training
    trainer = pl.Trainer(
        logger=wandb_logger,
        callbacks=[checkpoint_callback, early_stop_callback, progress_bar_callback],
        **config["trainer"],
    )
    config["model"]["scheduler"]["params"]["CosineAnnealingLR"]["T_max"] = T_MAX*len(data_loader_train)/config["trainer"]["devices"]
    config["model"]["scheduler"]["params"]["cosine_with_warmup"]["num_training_steps"] = int(num_training_steps*len(data_loader_train)/config["trainer"]["devices"])
    config["model"]["scheduler"]["params"]["cosine_with_warmup"]["num_warmup_steps"] = int(num_warmup_steps*len(data_loader_train)/config["trainer"]["devices"])
    model = DetectModule(config=config)
    trainer.fit(model, data_loader_train, data_loader_validation)
    wandb.finish()

Run history: epoch ▁▁▁▁▂▂▂▂▂▂▃▃▃▃▃▄▄▄▄▅▅▅▅▅▅▆▆▆▆▆▇▇▇▇▇▇▇███ lr ███████▇▇▇▇▇▆▆▆▆▅▅▅▅▄▄▄▄▃▃▃▃▂▂▂▂▂▁▁▁▁▁▁▁ train_loss_epoch █▄▃▃▃▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ train_loss_step█▆▅▃▃▃▂▅▃▂▃▂▃▃▂▂▂▂▂▂▂▂▂▂▂▂▁▂▁▂▁▁▁▁▁▂▁▁▁▁ trainer/global_step ▁▁▁▁▂▂▂▂▂▃▃▃▃▃▃▄▄▄▄▄▅▅▅▅▅▅▆▆▆▆▆▇▇▇▇▇▇███ val_loss █▅▆▄▃▃▂▂▂▂▂▁▂▁▂▁▁▁▁▁▁▁▁▁▁

Run summary: epoch 24
lr 5e-05
train_loss_epoch 0.0015
train_loss_step 0.00166
trainer/global_step 27999
val_loss 0.00516 train, valid = train_test_split(coor.filename.unique(), test_size=0.2, random_state=42)

  • coor: là một DataFrame chứa thông tin label tọa độ cho các ảnh (có thể nhiều dòng ứng với nhiều đốt sống trong cùng 1 ảnh).
  • coor.filename.unique(): lấy danh sách các tên file ảnh không trùng lặp.
  • train_test_split(...): chia danh sách ảnh thành 2 phần:
    • train: 80% file ảnh
    • valid: 20% file ảnh
  • random_state=42: cố định seed để chia dữ liệu lặp lại được. Sau đó lọc lại toàn bộ các dòng trong coorfilename nằm trong danh sách train. Tương tự, lọc lại các dòng có filename nằm trong danh sách valid.

T_MAX – cho CosineAnnealingLR

  • T_max: Số bước (hoặc epochs) cần để giảm learning rate theo hình cosine từ giá trị khởi đầu xuống **eta_min**.

  • Trong CosineAnnealingLR, công thức:

  • Thường đặt T_max = num_epochs để learning rate về gần 0 ở cuối training. num_training_steps – cho cosine_with_warmup

  • Tổng số bước huấn luyện (tức tổng batch update).

  • Dùng cho scheduler của HuggingFace Transformers: get_cosine_schedule_with_warmup.

  • Phần sau warm-up, learning rate sẽ giảm dần theo hình cosine. num_warmup_steps – cho cosine_with_warmup

  • Số bước đầu tiên (warm-up phase) learning rate sẽ tăng tuyến tính từ 0 → lr ban đầu.

  • Sau đó mới bắt đầu giảm theo cosine.

Giai đoạn huấn luyện: %%time & warnings

  • Đo thời gian chạy toàn bộ cell (Jupyter).
  • Bỏ qua cảnh báo (cho sạch log khi huấn luyện). Vòng lặp for i in [0]:
  • chỉ chạy 1 fold duy nhất (i = 0) để test, có thể thay bằng for i in [5] Tạo dataset & DataLoader:
  • Tạo 2 dataset từ dataframe chứa annotation tọa độ.
  • Dataloader cho train và validation. pin_memory=True giúp tăng tốc nếu dùng GPU.

Cài đặt callback:

  • ModelCheckpoint:
    • Lưu model tốt nhất (val_loss thấp nhất).
    • File lưu tại đường dẫn.
  • Progress bar:
    • Hiển thị tiến trình với tốc độ cập nhật theo config.
  • EarlyStopping:
    • Dừng sớm nếu val_loss không cải thiện sau patience epoch.

Logging với Weights & Biases:

  • Dùng Wandb để log kết quả huấn luyện.
  • log_model=False: không lưu model vào Wandb.

Tạo Trainer

  • Trainer Lightning với logger + callbacks + config (max_epochs, devices, …).

Điều chỉnh lại scheduler theo batch

  • CosineAnnealingLRcosine_with_warmup cần biết tổng số bước cập nhật, chứ không chỉ epoch.
  • len(data_loader_train) = số batch / epoch.
  • Chia cho số GPU (devices) nếu dùng multi-GPU (Lightning tự xử lý).

Huấn luyện mô hình

  • Tạo module DetectModule chứa mô hình + loss + optimizer.
  • Huấn luyện với .fit().
  • Kết thúc Wandb run: wandb.finish()

7. Validation

dataset_validation = CoorDetectDataset(valid_coor)
data_loader = DataLoader(
        dataset_validation,
        batch_size=8,
        shuffle=True,
        num_workers=8,
        pin_memory=True,
    )
# prediction
model = DetectModule.load_from_checkpoint(checkpoint_path='/content/gdrive/MyDrive/RSNA_SPINE/pretrained_models/scs_detect_pretrained_convnext-base.ckpt', config=config)
model.eval()
model.zero_grad()
model.to(device)
coor_predict = {'L1/L2': [], 'L2/L3': [], 'L3/L4': [], 'L4/L5': [], 'L5/S1': []}
coor_label = {'L1/L2': [], 'L2/L3': [], 'L3/L4': [], 'L4/L5': [], 'L5/S1': []}
path_list = []
with torch.no_grad():
    for data in tqdm(data_loader, total=len(data_loader)):
        images, label, path = data
        path_list.append(path)
        images = images.to(device)
        preds = model.forward(images)
        \#print(preds)
        for k, v in preds.items():
            coor_predict[k].append(v.to('cpu').detach().numpy())
        for k, v in label.items():
            coor_label[k].append(v.detach().numpy())

Post processing:

for k, v in coor_predict.items():
    coor_predict[k] = np.concatenate(v)
for k, v in coor_label.items():
    coor_label[k] = np.concatenate(v)
path_list = np.concatenate(path_list)
coor_label['L5/S1'][0]

Load mô hình đã huấn luyện và thực hiện dự đoán (inference) trên tập validation, rồi lưu lại kết quả tọa độ dự đoán và ground truth cho từng đốt sống. 1. Tạo dataset & dataloader cho validation

  • valid_coor: là DataFrame chứa ground truth của tập validation.
  • CoorDetectDataset: trả về tuple (image_tensor, coor_dict, path) cho mỗi ảnh.
  • DataLoader: load ảnh theo batch (ở đây batch_size = 8). 2. Load mô hình đã huấn luyện từ checkpoint
  • load_from_checkpoint(...): khôi phục lại mô hình đã được huấn luyện với tham số cấu hình.
  • eval(): chuyển sang chế độ evaluation (vô hiệu hóa dropout, batchnorm…).
  • zero_grad(): xóa gradient (cẩn thận, không cần thiết ở inference).
  • to(device): chuyển model sang cuda nếu đang dùng GPU.
  1. Khởi tạo dictionary để lưu kết quả
  • Mỗi đốt sống sẽ lưu lại danh sách các dự đoán (predict) và ground truth (label).
  • path_list: lưu đường dẫn ảnh tương ứng để so sánh trực quan sau này.
  1. Vòng lặp dự đoán
  • torch.no_grad(): tắt gradient để tiết kiệm bộ nhớ.
  • tqdm: hiển thị progress bar.
  • data trả về: ảnh batch, label dạng dict, và list các đường dẫn ảnh.
  1. Dự đoán & lưu kết quả
  • Đưa ảnh vào GPU và truyền qua model để dự đoán.
  • preds là dictionary {level: tensor(batch_size, 2)}.
  1. Lưu dự đoán và ground truth vào dict
  • Với mỗi level (đốt sống), tách giá trị tensor thành numpy array rồi append vào list.
  • .to('cpu') để chuyển về CPU trước khi convert sang numpy.
  • detach() ngắt khỏi computational graph. → array([0.47265625, 0.73046875], dtype=float32)