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 pydicomimport 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, ImageFilterSEED = 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ộtDataFramechứ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 ảnhvalid: 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 trongcoormàfilenamenằm trong danh sáchtrain. Tương tự, lọc lại các dòng cófilenamenằm trong danh sáchvalid.
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– chocosine_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– chocosine_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ằngfor 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=Truegiú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_losskhông cải thiện saupatienceepoch.
- Dừng sớm nếu
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
- Vì
CosineAnnealingLRvàcosine_with_warmupcầ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
DetectModulechứ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ừ checkpointload_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 sangcudanếu đang dùng GPU.
- 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.
- 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.datatrả về: ảnh batch, label dạng dict, và list các đường dẫn ảnh.
- Dự đoán & lưu kết quả
- Đưa ảnh vào GPU và truyền qua model để dự đoán.
predslà dictionary{level: tensor(batch_size, 2)}.
- 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)