๋๋๋งฅ ํ๋ง ํ์ฐฉ์ฆ(Aortic Stenosis) ์ง๋จ์ ์ํ ํ์ด๋ธ๋ฆฌ๋ ์๋ฃ ์์ ๋ถ์ ์์คํ
Radiomics ํน์ง ์ถ์ถ๊ณผ ๋ฅ๋ฌ๋ ์๋ฒ ๋ฉ์ ๊ฒฐํฉํ์ฌ 3D ์ฌ์ฅ ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๊ณ AS ์ค์ฆ๋๋ฅผ ๋ถ๋ฅํ๋ ํ๋ก์ ํธ์ ๋๋ค.
- ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ๋ฒ: Handcrafted Radiomics + ๋ฅ๋ฌ๋ ์๋ฒ ๋ฉ ๊ฒฐํฉ
- Multi-fold DL ์๋ฒ ๋ฉ: fold ๋ณ DL ๊ฐ์ค์น๋ฅผ ๋ฐ๊ฟ 5ํ ๋ฐ๋ณต ํ๊ฐ (๋ฐ์ดํฐ ๋ถํ ์ ๊ณ ์ hold-out โ ๊ต์ฐจ๊ฒ์ฆ ์๋)
- ์ด์ค ๋ถ๋ฅ ๋ชจ๋: Binary (nonsevere/severe) ๋ฐ Multi-class (normal/nonsevere/severe) ๋ถ๋ฅ ์ง์
- nnUNet ํตํฉ: ์ฌ์ ํ๋ จ๋ nnUNet ์ธ์ฝ๋ ํ์ฉ ๊ฐ๋ฅ
- Gated Fusion: Learnable gate๋ฅผ ํตํ adaptive feature fusion
- Soft Voting Ensemble: DL + ML ๋ชจ๋ธ ์์๋ธ
- ์ ์ฐํ ํน์ง ์ ํ: LASSO, RFE, Univariate, Mutual Info, Random Forest ์ง์
# Multi-class ๋ถ๋ฅ ๋ชจ๋๋ก ์ ์ฒด ํ์ดํ๋ผ์ธ ์คํ
python main.py# nnUNet ์ธ์ฝ๋ ์ฌ์ฉ (๊ถ์ฅ) โ ๊ฒฝ๋ก๊ฐ cwd ์๋์ด๋ฏ๋ก ์ ์ฅ์ ๋ฃจํธ์์ ์คํ
python DL_Classification/dl_cls_train.py --model_type nnunet --img_size "(32, 384, 320)"
# Custom ResNet50 ์ฌ์ฉ
python DL_Classification/dl_cls_train.py --model_type custom --img_size "(56, 448, 448)"# Grad-CAM ์๊ฐํ ํฌํจ ํ
์คํธ โ ์ ์ฅ์ ๋ฃจํธ์์ ์คํ
python DL_Classification/dl_cls_test.py --model_type nnunet --img_size "(32, 384, 320)" --enable_camCLASSIFICATION_MODE = 'multi' # 'binary' ๋๋ 'multi'ENABLE_DL_EMBEDDING = False # DL embedding ์ฌ์ฉ ์ฌ๋ถ (๊ธฐ๋ณธ๊ฐ False; ์ฌ์ฉํ๋ ค๋ฉด True)
DL_MODEL_TYPE = 'nnunet' # 'nnunet' ๋๋ 'custom'
DL_IMG_SIZE = (32, 384, 320) # nnUNet ๊ถ์ฅ: (32, 384, 320)USE_GATED_FUSION = False # True: Gated Fusion, False: ์ผ๋ฐ Concat
USE_ENSEMBLE = False # Soft Voting Ensemble ์ฌ์ฉ ์ฌ๋ถFEATURE_SELECTION_METHOD = 'lasso' # 'lasso', 'rfe', 'univariate', 'mutual_info', 'random_forest', 'none'DATA_SPLIT_MODE = 'fix' # 'random' ๋๋ 'fix' (๋๋ ํ ๋ฆฌ ๊ธฐ๋ฐ ๊ณ ์ ๋ถํ )
TEST_SIZE_RATIO = 0.2 # random ๋ชจ๋์์๋ง ์ฌ์ฉ
DATA_SPLIT_RANDOM_STATE = 42 # random ๋ชจ๋์์๋ง ์ฌ์ฉAS_Radiomics/
โโโ config.py # ์ ์ญ ์ค์ ๊ด๋ฆฌ
โโโ main.py # ๋ฉ์ธ ํ์ดํ๋ผ์ธ
โโโ data/ # ๋ฐ์ดํฐ ๋ก๋ฉยท์ ์ฒ๋ฆฌ + ๋ฐ์ดํฐ์
์ค์ฒด
โ โโโ loader.py
โ โโโ preprocessor.py
โ โโโ AS_CRF.csv # ํ์ ๋ ์ด๋ธ ํ์ผ
โ โโโ dataprep/ # ๋ฐ์ดํฐ์
๊ตฌ์ถ ์คํฌ๋ฆฝํธ (1ํ์ฑ, ํ์ดํ๋ผ์ธ์์ ํธ์ถ ์ ํจ)
โ โโโ datasets/ # Dataset00* ์์ยท๋ง์คํฌ (git ์ ์ธ)
โ โโโ datasets_raw/ # ์๋ณธ DICOM (git ์ ์ธ)
โโโ trainer/ # ํน์ง ์ถ์ถ ๋ฐ ๋ชจ๋ธ ํ์ต
โ โโโ features_extractor.py
โ โโโ dl_embedding_extractor.py
โ โโโ feature_selector.py
โ โโโ model_factory.py
โ โโโ train.py
โโโ DL_Classification/ # ๋ฅ๋ฌ๋ ๋ถ๋ฅ ๋ชจ๋
โ โโโ dl_cls_train.py # DL ๋ชจ๋ธ ํ์ต
โ โโโ dl_cls_test.py # DL ๋ชจ๋ธ ํ
์คํธ
โ โโโ dl_cls_cam.py # Grad-CAM ์๊ฐํ
โ โโโ dl_cls_model.py # 3D CNN ๋ชจ๋ธ ์ ์
โ โโโ dl_cls_dataset.py # ๋ฐ์ดํฐ๋ก๋
โ โโโ dl_cls_config.py # ์ค์ ๋ฐ ํ์ฑ
โ โโโ dl_cls_valid.py # ์ฑ๋ฅ ํ๊ฐ
โ โโโ nnUNet/ # nnUNet ์ค์ ํ์ผ
โโโ gated_models/ # Gated Fusion ๋ชจ๋ธ
โ โโโ gated_model.py # Gated Fusion ๋ ์ด์ด ๋ฐ ๋ถ๋ฅ๊ธฐ
โ โโโ gated_trainer.py # ํ์ต ์คํฌ๋ฆฝํธ
โ โโโ gated_feature_extractor.py # ํน์ง ์ถ์ถ๊ธฐ
โ โโโ gated_pipeline.py # ํ์ดํ๋ผ์ธ
โ โโโ README.md # Gated Fusion ์์ธ ๋ฌธ์
โโโ utils/ # ์ ํธ๋ฆฌํฐ ๋ชจ๋
โ โโโ plotter.py # ๊ฒฐ๊ณผ ์๊ฐํ
โ โโโ file_handler.py # ํ์ผ ์ ์ฅ ๋ฐ ๊ด๋ฆฌ
โ โโโ logger.py # ๋ก๊น
์์คํ
โ โโโ data_splitter.py # ๋ฐ์ดํฐ ๋ถํ
โ โโโ ensemble.py # Soft Voting Ensemble
โโโ docs/ # ์์ธ ๋ฌธ์ (๋ฐ์ดํฐ์
/์ํคํ
์ฒ/์ํฌํ๋ก์ฐ/์ค์ /๊ฒฐ๊ณผ)
โโโ CLAUDE.md # ์ฝ๋ฉ ์์ด์ ํธ์ฉ ํ๋ ์ปจ์คํธ๋ ์ธํธ
โโโ radiomics_analysis_results/ # ๋ถ์ ๊ฒฐ๊ณผ ์ ์ฅ
- PyRadiomics๋ฅผ ์ฌ์ฉํ์ฌ handcrafted ํน์ง ์ถ์ถ
- ์ถ์ถ ์
[0.3828125, 0.3828125, 3.0] mm๋ก ๋ฆฌ์ํ๋ง (Config.RESAMPLED_SPACING,None์ด๋ฉด ์๋ณธ spacing) - imagesTr๊ณผ imagesVal ๋๋ ํ ๋ฆฌ์์ ๋ ๋ฆฝ์ ์ผ๋ก ์ถ์ถ ํ ๋ณํฉ
- Dilation ์ต์ ์ง์
- Fold๋ณ ์ฌ์ ํ๋ จ๋ DL ๋ชจ๋ธ์์ ๊ณ ์ฐจ์ ํน์ง ์ถ์ถ
- nnUNet ๋๋ Custom ResNet50 ๋ชจ๋ธ ์ง์
- Radiomics + DL features๋ฅผ ๋จ์ concatenation
- Learnable gate๋ฅผ ํตํ adaptive fusion
- Two-stage learning:
- Stage 1: Gated Fusion Layer + MLP Classifier ํ์ต โ imagesVal(test set)๋ก MLP ์ต์ข ์ฑ๋ฅ ํ๊ฐ
- Stage 2: Fused features ์ถ์ถ โ ์ ํต์ ML ๋ถ๋ฅ๊ธฐ ํ์ต
(
fusion_dim๋ฏธ์ง์ ์ radiomics + DL ์ฐจ์ ํฉ. nnUNet ๊ธฐ๋ณธ ์ค์ ์์๋ 107 + 320 = 427)
- Gated Fusion ์ด ์ผ์ ธ ์์ผ๋ฉด Ensemble ์ ์คํ๋์ง ์๋๋ค (
main.py:210-212์์ ์กฐ๊ธฐ ๋ฐํ)
- LASSO: L1 ์ ๊ทํ ๊ธฐ๋ฐ (ํฌ์์ฑ ์ ๋)
- RFE: Recursive Feature Elimination
- Univariate: F-test ๊ธฐ๋ฐ ๋จ๋ณ๋ ๊ฒ์
- Mutual Info: ์ํธ ์ ๋ณด๋ ๊ธฐ๋ฐ
- Random Forest: ํน์ง ์ค์๋ ๊ธฐ๋ฐ
- ์ ํต์ ML ๋ถ๋ฅ๊ธฐ: LR, SVM, RF, GB, KNN, NB (๊ธฐ๋ณธ๊ฐ์
CLASSIFICATION_MODELS = ['LR', 'SVM', 'RF']) - ํ์ต/ํ๊ฐ๋ train/test hold-out 1ํ.
CV_FOLDS = 5๋ LassoCV ๋ฑ ํน์ง ์ ํ ๋ด๋ถ CV ์๋ง ์ฐ์ธ๋ค - ์ฑ๋ฅ ๋ฉํธ๋ฆญ: Accuracy, F1-Score, AUC, AP
- DL ๋ชจ๋ธ๊ณผ ML ๋ชจ๋ธ๋ค์ ํ๋ฅ ๊ฐ ๊ฒฐํฉ
- DL+LR, DL+RF, DL+SVM ์กฐํฉ
- Macro-average AUC, AP ๊ณ์ฐ
ํด๋์ค ๋ถ๊ท ํ ๋ฌธ์ ๋ฅผ ์๋์ผ๋ก ํด๊ฒฐ:
# Cross Entropy Loss์ ์๋ ์ ์ฉ
weights = total_samples / (num_classes * class_counts)์ผ๊ด๋ ํด๋์ค ์์ ๋ณด์ฅ:
- Multi-class: ['normal', 'nonsevere', 'severe'] (0, 1, 2)
- Binary: ['nonsevere', 'severe'] (0, 1)
Multi-class ๋ถ๋ฅ์์ One-vs-Rest ๋ฐฉ์ ์ฌ์ฉ:
# ๊ฐ ํด๋์ค๋ฅผ ์ด์ง ๋ถ๋ฅ ๋ฌธ์ ๋ก ๋ณํ
y_true_bin = label_binarize(y_true, classes=range(n_classes))
auc_score = roc_auc_score(y_true_bin, y_proba, average='macro', multi_class='ovr')๋ชจ๋ธ ํด์ ๊ฐ๋ฅ์ฑ ํฅ์ (--enable_cam ์ง์ ์, ์ต๋ 20 ์ํ ร 3์ฅ):
{sample}_key_slices.png: CAM ๋ฐ์์ด ํฐ ์์ 12๊ฐ ์ฌ๋ผ์ด์ค{sample}_all_slices.png: ์ ์ฒด ์ฌ๋ผ์ด์ค ๊ทธ๋ฆฌ๋{sample}_3d_projection.png: Axial/Coronal/Sagittal 3๋ฐฉํฅ ์ต๋ ๊ฐ๋ ํฌ์(MIP)- ์ ์ฅ ์์น:
DL_Classification/results/{writer_comment}/cam_visualization/fold_{n}/
์ด 120๊ฐ ์ปฌ๋ผ์ด์ง๋ง ์ฝ๋๊ฐ ์ฝ๋ ๊ฒ์ ์๋ 3๊ฐ๋ฟ์ด๋ค (data/loader.py:38).
1์ฐจ๋
๋์ฐ๊ตฌ๋ฒํธ,...,AV_binaryclassification,AS ,...
patient001,...,nonsevere,none,...
patient002,...,severe,severe,...
patient003,...,nonsevere,mild,...AS๋ ์ปฌ๋ผ๋ช ๋์ ๊ณต๋ฐฑ์ด ํ๋ ๋ถ์ด ์๊ณ ,1์ฐจ๋ ๋์ฐ๊ตฌ๋ฒํธ๋ ํ์ผ ์ ๋์ BOM ์ด ์๋ค. ์ปฌ๋ผ๋ช ๋น๊ต ์ ์ฃผ์.
- ์ด๋ฏธ์ง ํ์ผ:
{patient_id}_{sequence}_0000.nii.gz - ๋ ์ด๋ธ ํ์ผ:
{patient_id}_{sequence}.nii.gz
Binary ๋ชจ๋:
# AV_binaryclassification ์ปฌ๋ผ ์ฌ์ฉ
'nonsevere' โ 0
'severe' โ 1Multi-class ๋ชจ๋:
# AS ์ปฌ๋ผ ๋ณํ
'none', 'no' โ 'normal' (0)
'mild', 'moderate', 'pseudosevere' โ 'nonsevere' (1)
'severe', 'very severe' โ 'severe' (2)radiomics_analysis_results/
โโโ total/
โโโ lasso/
โโโ multi/
โโโ dlnnunet_32_384_320_gated_20250930_123456/ # USE_GATED_FUSION = True
โ โโโ log.txt # ์คํ ๋ก๊ทธ (์ต์๋จ์๋ง ์์ฑ)
โ โโโ fold_1/
โ โ โโโ fold_1_best_model.pth # Gated Fusion ๋ชจ๋ธ
โ โ โโโ gated_training.log # Gated ํ์ต ๋ก๊ทธ
โ โ โโโ gated_fusion_predictions_fold_1.csv # MLP ์์ธก ๊ฒฐ๊ณผ
โ โ โโโ gated_fused_features_all.csv # fused features (+ _train/_test)
โ โ โโโ model_validation_summary.csv # MLP/LR/RF/SVM ์ฑ๋ฅ ์์ฝ
โ โ โโโ {model}_confusion_matrix.png # Confusion Matrix
โ โ โโโ test_cases_prediction_results.csv # ์์ธก ๊ฒฐ๊ณผ
โ โ โโโ lasso_feature_analysis.csv # LASSO ๋ถ์
โ โโโ fold_2/
โ โโโ ...
โโโ dlnnunet_32_384_320_ensemble_20250930_123456/ # USE_ENSEMBLE = True (Gated ์ ๋ฐฐํ)
โโโ fold_1/
โโโ radiomics_features_all.csv # (+ _train/_test)
โโโ model_validation_summary.csv
โโโ test_cases_prediction_results.csv
โโโ ensemble/ # Ensemble ๊ฒฐ๊ณผ
โโโ ensemble_results_fold_1.csv
โโโ ensemble_model_validation_summary.csv
Radiomics์ DL features๋ฅผ adaptiveํ๊ฒ ์ตํฉ:
h = tanh(W_h [Radiomics; Deep Learning] + b_h)
g = ฯ(w_g [Radiomics; Deep Learning] + b_g)
F_fused = g โ h
์์ธํ ๋ด์ฉ์ gated_models/README.md ์ฐธ์กฐ
DL๊ณผ ML ๋ชจ๋ธ์ ํ๋ฅ ๊ฐ์ ํ๊ท ํ์ฌ ์ต์ข ์์ธก:
# DL+LR ์์๋ธ ์์
ensemble_proba = (DL_proba + LR_proba) / 2
predicted_class = argmax(ensemble_proba)requirements.txt / pyproject.toml ์ ์๋ค. ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ง์ ์ค์นํด์ผ ํ๋ค.
torch,tensorboard: ๋ฅ๋ฌ๋ ํ๋ ์์ํฌ ๋ฐ ํ์ต ๋ก๊นmonai: ์๋ฃ ์์ ๋ฅ๋ฌ๋ (Custom ResNet50 ๋ฐฑ๋ณธ)nnunetv2: nnUNet encoder ๋ก๋ฉpyradiomics,nibabel,scipy: Radiomics ํน์ง ์ถ์ถ ๋ฐ ๋ง์คํฌ Dilationscikit-learn: ์ ํต์ ML ๋ฐ ํ๊ฐpandas,numpy: ๋ฐ์ดํฐ ์ฒ๋ฆฌmatplotlib,seaborn: ์๊ฐํtqdm,natsort: ์งํ ํ์ ๋ฐ ํ์ผ ์ ๋ ฌ
- Accuracy: ์ ์ฒด ์ ํ๋
- F1-Score: Macro-average (๋ชจ๋ ํด๋์ค ๋๋ฑ)
- AUC: One-vs-Rest Macro-average
- AP: Average Precision (Macro-average)
- Accuracy: ์ ์ฒด ์ ํ๋
- F1-Score: Binary ๋ฐฉ์
- AUC: ์์ฑ ํด๋์ค(severe) ๊ธฐ์ค
- AP: ์์ฑ ํด๋์ค(severe) ๊ธฐ์ค
# config.py์ get_dl_model_paths()๊ฐ {fold: ๊ฒฝ๋ก} ๋์
๋๋ฆฌ๋ฅผ ๋ฐํํ๋ค.
# ํฌ๋งท: ./DL_Classification/weights/{DL_COMMENT_WRITER}/{fold}/best_model.pth
Config.get_dl_model_paths()# Gated Fusion: gated_models/gated_pipeline.py ์ train_config ์์ ๋ฐฐ์น ํฌ๊ธฐ ๊ฐ์
train_config = {
'batch_size': 8, # 16 โ 8๋ก ๊ฐ์
...
}DL Classification ํ์ต์ CLI ๋ก ์กฐ์ ํ๋ค: --batch_size 1 (๊ธฐ๋ณธ๊ฐ 2).
# DL Embedding์ด ํ์ฑํ๋์ด์ผ ํจ
ENABLE_DL_EMBEDDING = True
USE_ENSEMBLE = True # ๋๋ USE_GATED_FUSION = True- ์์ธ ๋ฌธ์ ์ธ๋ฑ์ค: docs/README.md
- ๋ฐ์ดํฐ์ ์นดํ๋ก๊ทธยทํด๋์ค ๋ถํฌ: docs/DATASET.md
- ๋ชจ๋ ์์กด์ฑยท๋ฐ์ดํฐ ํ๋ฆ: docs/ARCHITECTURE.md
- ์คํ ์๋๋ฆฌ์คยทํธ๋ฌ๋ธ์ํ : docs/WORKFLOWS.md
Config์ ์ ๋ ํผ๋ฐ์ค: docs/CONFIG_REFERENCE.md- ๊ฒฐ๊ณผ ๋๋ ํ ๋ฆฌ/CSV ์ฌ์: docs/RESULTS_LAYOUT.md
- ์คํ ์ค๊ณ ๊ฒํ ๋
ธํธ (ํญ๋ชฉ๋ณ ์งํ ์ํ๋ ๊ฐ ๋ฌธ์์
[์๋ฃ]ํ์ ์ฐธ์กฐ): docs/notes/
- ์ฝ๋ฉ ์์ด์ ํธ์ฉ ๊ท์ฝ: CLAUDE.md
- Gated Fusion ์์ธ: gated_models/README.md
- DL Classification:
DL_Classification/๋๋ ํ ๋ฆฌ