Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- fMRI-foundation-model-old/fMRI-MAE-mamba/__init__.py +0 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/accel.slurm +65 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/config.yaml +53 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/dataloader.py +772 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/mamba.ipynb +0 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/mindeye_config.yaml +30 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/mindeye_models.py +276 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/models.py +504 -0
- fMRI-foundation-model-old/fMRI-MAE-mamba/utils.py +267 -0
- fMRI-foundation-model-old/fMRI-MAE/.ipynb_checkpoints/main-checkpoint.ipynb +1350 -0
- fMRI-foundation-model-old/fMRI-MAE/accel.slurm +65 -0
- fMRI-foundation-model-old/fMRI-MAE/dataloader.py +772 -0
- fMRI-foundation-model-old/fMRI-MAE/downstream.ipynb +1162 -0
- fMRI-foundation-model-old/fMRI-MAE/downstream.py +731 -0
- fMRI-foundation-model-old/fMRI-MAE/downstream.slurm +29 -0
- fMRI-foundation-model-old/fMRI-MAE/mindeye_models.py +276 -0
- fMRI-foundation-model-old/fMRI-MAE/models.py +423 -0
- fMRI-foundation-model-old/fMRI-MAE/rope.py +111 -0
- fMRI-foundation-model-old/fMRI-MAE/utils.py +412 -0
- fMRI-foundation-model-old/flat/.ipynb_checkpoints/main-checkpoint.py +593 -0
- fMRI-foundation-model-old/flat/.ipynb_checkpoints/main2-checkpoint.ipynb +6 -0
- fMRI-foundation-model-old/flat/__pycache__/flat_models.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/__pycache__/utils.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/accel.slurm +44 -0
- fMRI-foundation-model-old/flat/config.yaml +58 -0
- fMRI-foundation-model-old/flat/flat_models.py +784 -0
- fMRI-foundation-model-old/flat/main.ipynb +0 -0
- fMRI-foundation-model-old/flat/main.py +593 -0
- fMRI-foundation-model-old/flat/main2.ipynb +450 -0
- fMRI-foundation-model-old/flat/parquet_probe.ipynb +407 -0
- fMRI-foundation-model-old/flat/probe.ipynb +0 -0
- fMRI-foundation-model-old/flat/probe_prepper.ipynb +587 -0
- fMRI-foundation-model-old/flat/unique_tasks.npy +3 -0
- fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/hcp_flat-checkpoint.py +120 -0
- fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/misc-checkpoint.py +508 -0
- fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/visualize-checkpoint.py +123 -0
- fMRI-foundation-model-old/flat/util/__pycache__/hcp_flat.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/util/__pycache__/logging.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/util/__pycache__/losses.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/util/__pycache__/video_vit.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/util/__pycache__/visualize.cpython-310.pyc +0 -0
- fMRI-foundation-model-old/flat/util/hcp_flat.py +121 -0
- fMRI-foundation-model-old/flat/util/logging.py +117 -0
- fMRI-foundation-model-old/flat/util/losses.py +132 -0
- fMRI-foundation-model-old/flat/util/lr_decay.py +83 -0
- fMRI-foundation-model-old/flat/util/lr_sched.py +28 -0
- fMRI-foundation-model-old/flat/util/meters.py +234 -0
- fMRI-foundation-model-old/flat/util/misc.py +508 -0
- fMRI-foundation-model-old/flat/util/pos_embed.py +53 -0
- fMRI-foundation-model-old/flat/util/video_vit.py +173 -0
fMRI-foundation-model-old/fMRI-MAE-mamba/__init__.py
ADDED
|
File without changes
|
fMRI-foundation-model-old/fMRI-MAE-mamba/accel.slurm
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH --account=fmri
|
| 3 |
+
#SBATCH --partition=a40
|
| 4 |
+
#SBATCH --job-name=fmrimamba
|
| 5 |
+
#SBATCH --ntasks-per-node=1
|
| 6 |
+
#SBATCH --nodes=1
|
| 7 |
+
#SBATCH --gres=gpu:1
|
| 8 |
+
#SBATCH --gpus-per-task=1 # Set to equal gres=gpu:#!
|
| 9 |
+
#SBATCH --time=20:00:00 # total run time limit (HH:MM:SS)
|
| 10 |
+
#SBATCH -e slurms/%j.err # first create a "slurms" folder in current directory to store logs
|
| 11 |
+
#SBATCH -o slurms/%j.out
|
| 12 |
+
#SBATCH --comment=medarc
|
| 13 |
+
#SBATCH --no-requeue
|
| 14 |
+
#SBATCH --exclusive
|
| 15 |
+
|
| 16 |
+
export NUM_GPUS=1 # Set to equal gres=gpu:#!
|
| 17 |
+
echo NUM_GPUS=$NUM_GPUS
|
| 18 |
+
|
| 19 |
+
source ~/.bashrc
|
| 20 |
+
|
| 21 |
+
cd /weka/proj-fmri/paulscotti/fMRI-foundation-model/fMRI-MAE/
|
| 22 |
+
jupyter nbconvert mamba.ipynb --to python
|
| 23 |
+
if [ $? -ne 0 ]; then
|
| 24 |
+
echo "Error: Conversion of ipynb to Python failed. Exiting."
|
| 25 |
+
exit 1
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
# Make sure another job doesnt use same port, here using random number
|
| 29 |
+
export MASTER_PORT=$((RANDOM % (19000 - 11000 + 1) + 11000))
|
| 30 |
+
export HOSTNAMES=$(scontrol show hostnames "$SLURM_JOB_NODELIST")
|
| 31 |
+
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
|
| 32 |
+
echo MASTER_ADDR=${MASTER_ADDR}
|
| 33 |
+
echo MASTER_PORT=${MASTER_PORT}
|
| 34 |
+
echo WORLD_SIZE=$((${SLURM_NNODES} * ${NUM_GPUS}))
|
| 35 |
+
|
| 36 |
+
# Other settings relevant for multi-node
|
| 37 |
+
export NCCL_DEBUG=WARN #INFO
|
| 38 |
+
export NCCL_PROTO=simple
|
| 39 |
+
export FI_EFA_USE_DEVICE_RDMA=1
|
| 40 |
+
export FI_EFA_FORK_SAFE=1
|
| 41 |
+
export FI_LOG_LEVEL=1
|
| 42 |
+
export PYTHONFAULTHANDLER=1
|
| 43 |
+
export CUDA_LAUNCH_BLOCKING=0
|
| 44 |
+
export OMPI_MCA_mtl_base_verbose=1
|
| 45 |
+
export FI_EFA_ENABLE_SHM_TRANSFER=0
|
| 46 |
+
export FI_PROVIDER=efa
|
| 47 |
+
export FI_EFA_TX_MIN_CREDITS=64
|
| 48 |
+
export NCCL_TREE_THRESHOLD=0
|
| 49 |
+
export TORCH_DISTRIBUTED_DEBUG=DETAIL
|
| 50 |
+
export NCCL_P2P_DISABLE=1
|
| 51 |
+
module load openmpi cuda/12.1
|
| 52 |
+
|
| 53 |
+
srun torchrun \
|
| 54 |
+
--nproc_per_node=$NUM_GPUS \
|
| 55 |
+
--nnodes=$SLURM_NNODES \
|
| 56 |
+
--rdzv_id=$SLURM_JOBID \
|
| 57 |
+
--rdzv_backend=c10d \
|
| 58 |
+
--rdzv_endpoint=${MASTER_ADDR}:$MASTER_PORT \
|
| 59 |
+
--rdzv_conf=timeout=90 \
|
| 60 |
+
mamba.py
|
| 61 |
+
|
| 62 |
+
if [ $? -ne 0 ]; then
|
| 63 |
+
echo "Error: srun command failed. Please check the logs for more details."
|
| 64 |
+
exit 1
|
| 65 |
+
fi
|
fMRI-foundation-model-old/fMRI-MAE-mamba/config.yaml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Config
|
| 2 |
+
model_name: "framepatchsize4_bs8_300ep"
|
| 3 |
+
use_cls_token: False
|
| 4 |
+
use_contrastive_loss: False
|
| 5 |
+
constrastive_loss_weight: 0.01
|
| 6 |
+
|
| 7 |
+
# Training Configs
|
| 8 |
+
batch_size: 8
|
| 9 |
+
num_workers: 10
|
| 10 |
+
num_epochs: 300
|
| 11 |
+
seed: 42
|
| 12 |
+
max_lr: 3.0e-6 # Keep the x.0 else will be converted to string
|
| 13 |
+
num_samples_per_epoch: 1024
|
| 14 |
+
|
| 15 |
+
# Saving progress
|
| 16 |
+
ckpt_saving: True
|
| 17 |
+
ckpt_interval: 50
|
| 18 |
+
resume_from_ckpt: True
|
| 19 |
+
wandb_log: True
|
| 20 |
+
|
| 21 |
+
# MAE Config
|
| 22 |
+
tube_start_masking_ratio: 0.95
|
| 23 |
+
tube_end_masking_ratio: 0.95
|
| 24 |
+
decoder_mask_ratio: 0.85
|
| 25 |
+
|
| 26 |
+
# Model Config
|
| 27 |
+
patch_size: 8
|
| 28 |
+
frame_patch_size: 4
|
| 29 |
+
use_rope_emb: False
|
| 30 |
+
masking_strategy: "MNI"
|
| 31 |
+
|
| 32 |
+
# ViT Config
|
| 33 |
+
encoder_model: "vit_base"
|
| 34 |
+
decoder_model: "vit_small"
|
| 35 |
+
|
| 36 |
+
# Mamba config
|
| 37 |
+
#embed_dim: 512
|
| 38 |
+
#depth: 32
|
| 39 |
+
|
| 40 |
+
# Data Config
|
| 41 |
+
img_size: [88, 104, 72] # Image Size
|
| 42 |
+
num_frames: 4
|
| 43 |
+
is_s3: False
|
| 44 |
+
# 000495.tar is missing??
|
| 45 |
+
train_urls: ["/weka/proj-fmri/shared/NSD_MNI_wds/{000000..000738}.tar"]
|
| 46 |
+
# train_urls: ["s3://proj-fmri/fmri_foundation_datasets/NSD_MNI_wds/{000000..000494}.tar","s3://proj-fmri/fmri_foundation_datasets/NSD_MNI_wds/{000496..000740}.tar"]
|
| 47 |
+
|
| 48 |
+
#"/weka/proj-fmri/paulscotti/old_fMRI-foundation-model/dataset_creation/wds_creation/wds/000000.tar"
|
| 49 |
+
#"s3://proj-fmri/fmri_foundation_datasets/NSD_MNI_wds/{000000..000740}.tar"
|
| 50 |
+
# test_urls: "s3://proj-fmri/fmri_foundation_datasets/openneuro_MNI_wds/000000.tar"
|
| 51 |
+
# train_urls: "/weka/proj-fmri/paulscotti/old_fMRI-foundation-model/dataset_creation/wds_creation/wds/{000001..000240}.tar"
|
| 52 |
+
# train_urls: "/weka/proj-fmri/paulscotti/fMRI-foundation-model/dataset_creation/wds_creation/wds/{000000..000382}.tar"
|
| 53 |
+
# test_urls: "/weka/proj-fmri/paulscotti/old_fMRI-foundation-model/dataset_creation/wds_creation/wds/000000.tar"
|
fMRI-foundation-model-old/fMRI-MAE-mamba/dataloader.py
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from torch import Tensor, stack
|
| 3 |
+
from torch.utils.data import IterDataPipe, DataLoader
|
| 4 |
+
from torch.utils.data.datapipes._decorator import functional_datapipe
|
| 5 |
+
from torch.utils.data.datapipes.iter.callable import MapperIterDataPipe
|
| 6 |
+
from torch.utils.data.datapipes.utils.common import StreamWrapper, match_masks
|
| 7 |
+
from torchdata.datapipes.iter import IterableWrapper, IterDataPipe, TarArchiveLoader
|
| 8 |
+
from torchdata.datapipes.iter.load.s3io import S3FileLoaderIterDataPipe
|
| 9 |
+
from torchdata.datapipes.utils.common import validate_pathname_binary_tuple
|
| 10 |
+
import warnings
|
| 11 |
+
import subprocess
|
| 12 |
+
import tarfile
|
| 13 |
+
import tempfile
|
| 14 |
+
import time
|
| 15 |
+
import random
|
| 16 |
+
from omegaconf import DictConfig, ListConfig
|
| 17 |
+
import webdataset as wds
|
| 18 |
+
import re
|
| 19 |
+
import os
|
| 20 |
+
import copy
|
| 21 |
+
from io import BufferedIOBase, BytesIO, RawIOBase
|
| 22 |
+
from typing import (
|
| 23 |
+
IO,
|
| 24 |
+
Any,
|
| 25 |
+
Callable,
|
| 26 |
+
Dict,
|
| 27 |
+
Iterable,
|
| 28 |
+
Iterator,
|
| 29 |
+
List,
|
| 30 |
+
Optional,
|
| 31 |
+
Sequence,
|
| 32 |
+
Tuple,
|
| 33 |
+
TypeVar,
|
| 34 |
+
Union,
|
| 35 |
+
cast,
|
| 36 |
+
)
|
| 37 |
+
T_co = TypeVar("T_co", covariant=True)
|
| 38 |
+
|
| 39 |
+
class ShuffledListDataPipe(IterDataPipe[T_co]):
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
source_list: List[T_co],
|
| 43 |
+
*,
|
| 44 |
+
shuffle: bool = True,
|
| 45 |
+
cycle: Union[bool, int] = True,
|
| 46 |
+
):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.source = source_list
|
| 49 |
+
self._enabled = shuffle
|
| 50 |
+
self._seed = None
|
| 51 |
+
self._rng = random.Random()
|
| 52 |
+
if isinstance(cycle, bool):
|
| 53 |
+
if cycle:
|
| 54 |
+
self._cycle = -1
|
| 55 |
+
else:
|
| 56 |
+
self._cycle = 1
|
| 57 |
+
else:
|
| 58 |
+
assert isinstance(cycle, int)
|
| 59 |
+
self._cycle = cycle
|
| 60 |
+
|
| 61 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 62 |
+
source = copy.copy(self.source)
|
| 63 |
+
cycle = self._cycle
|
| 64 |
+
epochs = 0
|
| 65 |
+
while cycle == -1 or epochs < cycle:
|
| 66 |
+
if self._enabled:
|
| 67 |
+
self._rng.shuffle(source)
|
| 68 |
+
yield from source
|
| 69 |
+
epochs += 1
|
| 70 |
+
|
| 71 |
+
def __len__(self):
|
| 72 |
+
if self.count == -1:
|
| 73 |
+
raise TypeError(
|
| 74 |
+
f"This {type(self).__name__} instance cycles forever, and "
|
| 75 |
+
f"therefore doesn't have valid length."
|
| 76 |
+
)
|
| 77 |
+
else:
|
| 78 |
+
return self.count * len(self.source)
|
| 79 |
+
|
| 80 |
+
def __getstate__(self):
|
| 81 |
+
state = (
|
| 82 |
+
self.source,
|
| 83 |
+
self._enabled,
|
| 84 |
+
self._seed,
|
| 85 |
+
self._rng.getstate(),
|
| 86 |
+
self._valid_iterator_id,
|
| 87 |
+
self._number_of_samples_yielded,
|
| 88 |
+
)
|
| 89 |
+
if IterDataPipe.getstate_hook is not None:
|
| 90 |
+
return IterDataPipe.getstate_hook(state)
|
| 91 |
+
return state
|
| 92 |
+
|
| 93 |
+
def __setstate__(self, state):
|
| 94 |
+
(
|
| 95 |
+
self.source,
|
| 96 |
+
self._enabled,
|
| 97 |
+
self._seed,
|
| 98 |
+
rng_state,
|
| 99 |
+
self._valid_iterator_id,
|
| 100 |
+
self._number_of_samples_yielded,
|
| 101 |
+
) = state
|
| 102 |
+
self._rng = random.Random()
|
| 103 |
+
self._rng.setstate(rng_state)
|
| 104 |
+
|
| 105 |
+
def get_job_temp_dir(dl_root: str) -> str:
|
| 106 |
+
try:
|
| 107 |
+
job_or_array_id = (
|
| 108 |
+
os.environ.get("SLURM_ARRAY_JOB_ID", "") or os.environ["SLURM_JOB_ID"]
|
| 109 |
+
)
|
| 110 |
+
task_id = os.environ.get("SLURM_ARRAY_TASK_ID", "") or "0"
|
| 111 |
+
return os.path.join(dl_root, f"JOB_{job_or_array_id}/TASK_{task_id}")
|
| 112 |
+
except KeyError as e:
|
| 113 |
+
print("SLURM_JOB_ID env var not set: You sure this job is healthy?")
|
| 114 |
+
raise e
|
| 115 |
+
|
| 116 |
+
def _shard_expand(s: str) -> List[str]:
|
| 117 |
+
expansion = r"\{[0-9]+\.\.[0-9]+\}"
|
| 118 |
+
m = re.search(expansion, s)
|
| 119 |
+
if not m:
|
| 120 |
+
return [s]
|
| 121 |
+
prefix = s[: m.start()]
|
| 122 |
+
rest = _shard_expand(s[m.end() :])
|
| 123 |
+
rng = s[m.start() + 1 : m.end() - 1]
|
| 124 |
+
lohi = rng.split("..")
|
| 125 |
+
if len(lohi[0]) == len(lohi[1]) and lohi[0].startswith("0"):
|
| 126 |
+
fmt = "{prefix}{i:0>{l}d}{r}"
|
| 127 |
+
elif len(lohi[0]) <= len(lohi[1]):
|
| 128 |
+
if lohi[0].startswith("0") and lohi[0] != "0":
|
| 129 |
+
raise ValueError(
|
| 130 |
+
"shard_expand: low bound must not start with 0 if low bound is shorter"
|
| 131 |
+
)
|
| 132 |
+
fmt = "{prefix}{i}{r}"
|
| 133 |
+
else:
|
| 134 |
+
raise ValueError("shard_expand: low bound must be shorter than high bound")
|
| 135 |
+
lo, hi = (int(x) for x in lohi)
|
| 136 |
+
if lo >= hi:
|
| 137 |
+
raise ValueError(f"shard_expand: bad range in in shard spec {s}.")
|
| 138 |
+
result = []
|
| 139 |
+
for i in range(lo, hi + 1):
|
| 140 |
+
for r in rest:
|
| 141 |
+
expanded: str = fmt.format(prefix=prefix, i=i, r=r, l=len(lohi[1]))
|
| 142 |
+
result.append(expanded)
|
| 143 |
+
return result
|
| 144 |
+
|
| 145 |
+
@functional_datapipe("custom_shard_expand")
|
| 146 |
+
class CustomShardExpanderIterDataPipe(IterDataPipe[str]):
|
| 147 |
+
def __init__(self, source_datapipe: IterDataPipe[str]) -> None:
|
| 148 |
+
super().__init__()
|
| 149 |
+
self.source_datapipe: IterDataPipe[str] = source_datapipe
|
| 150 |
+
|
| 151 |
+
def __iter__(self) -> Iterator[str]:
|
| 152 |
+
for path in self.source_datapipe:
|
| 153 |
+
yield from _shard_expand(path)
|
| 154 |
+
|
| 155 |
+
def warn_and_continue(exn):
|
| 156 |
+
"""Call in an exception handler to ignore any exception, issue a warning, and continue."""
|
| 157 |
+
print(exn)
|
| 158 |
+
warnings.warn(repr(exn))
|
| 159 |
+
time.sleep(0.05)
|
| 160 |
+
return True
|
| 161 |
+
error_handler = warn_and_continue
|
| 162 |
+
|
| 163 |
+
def is_stream_handle(data):
|
| 164 |
+
obj_to_check = data.file_obj if isinstance(data, StreamWrapper) else data
|
| 165 |
+
return isinstance(obj_to_check, (BufferedIOBase, RawIOBase))
|
| 166 |
+
|
| 167 |
+
def read_stream_handles(data):
|
| 168 |
+
if not is_stream_handle(data):
|
| 169 |
+
return data
|
| 170 |
+
else:
|
| 171 |
+
ds = data
|
| 172 |
+
data = b"".join(data)
|
| 173 |
+
ds.close()
|
| 174 |
+
del ds
|
| 175 |
+
return data
|
| 176 |
+
|
| 177 |
+
def stream_reader(sample):
|
| 178 |
+
return {k: read_stream_handles(v) for k, v in sample.items()}
|
| 179 |
+
|
| 180 |
+
def to_our_format(sample):
|
| 181 |
+
wds_key = sample.pop("__key__")
|
| 182 |
+
sample = {k.lstrip("."): v for k, v in sample.items()}
|
| 183 |
+
sample["__key__"] = wds_key.split("/")[-1]
|
| 184 |
+
sample["__url__"] = "/".join(wds_key.split("/")[:-1])
|
| 185 |
+
return sample
|
| 186 |
+
|
| 187 |
+
def add_processors(
|
| 188 |
+
datapipeline,
|
| 189 |
+
processors: Optional[ListConfig],
|
| 190 |
+
description: str,
|
| 191 |
+
error_handler: Callable = warn_and_continue,
|
| 192 |
+
):
|
| 193 |
+
if not processors:
|
| 194 |
+
return datapipeline
|
| 195 |
+
else:
|
| 196 |
+
for i, processor_config in enumerate(processors):
|
| 197 |
+
processor = instantiate(processor_config)
|
| 198 |
+
if isinstance(processor, AbstractFilter):
|
| 199 |
+
print(
|
| 200 |
+
f"Adding filter {processor.__class__.__name__} as {description} #{i} "
|
| 201 |
+
f"to the datapipeline"
|
| 202 |
+
)
|
| 203 |
+
datapipeline = datapipeline.filter(processor.filter)
|
| 204 |
+
elif isinstance(processor, AbstractMapper):
|
| 205 |
+
print(
|
| 206 |
+
f"Adding mapper {processor.__class__.__name__} as {description} #{i} "
|
| 207 |
+
f"to the datapipeline"
|
| 208 |
+
)
|
| 209 |
+
datapipeline = datapipeline.map_with_handler(
|
| 210 |
+
processor.map,
|
| 211 |
+
handler=error_handler,
|
| 212 |
+
called_cls_name=processor.__class__.__name__,
|
| 213 |
+
)
|
| 214 |
+
else:
|
| 215 |
+
raise TypeError(
|
| 216 |
+
f"chosen {description} {processor.__class__.__name__} should be either subclass"
|
| 217 |
+
"AbstractMapper or AbstractFilter"
|
| 218 |
+
)
|
| 219 |
+
return datapipeline
|
| 220 |
+
|
| 221 |
+
@functional_datapipe("map_with_handler")
|
| 222 |
+
class MapperWithErrorHandlingIterDataPipe(MapperIterDataPipe):
|
| 223 |
+
def __init__(
|
| 224 |
+
self,
|
| 225 |
+
datapipe: IterDataPipe,
|
| 226 |
+
fn: Callable,
|
| 227 |
+
handler: Callable = wds.reraise_exception,
|
| 228 |
+
input_col: Optional[Union[str, int]] = None,
|
| 229 |
+
output_col: Optional[Union[str, int]] = None,
|
| 230 |
+
called_cls_name: Optional[str] = None,
|
| 231 |
+
):
|
| 232 |
+
# for now, disbable input and output col since this is never used anyways
|
| 233 |
+
if input_col is not None:
|
| 234 |
+
raise NotImplementedError("`input_col` argument currently not supported")
|
| 235 |
+
|
| 236 |
+
if output_col is not None:
|
| 237 |
+
raise NotImplementedError("`output_col` argument currently not supported")
|
| 238 |
+
|
| 239 |
+
super().__init__(datapipe, fn)
|
| 240 |
+
self.handler = handler
|
| 241 |
+
self._apply_fn_ = self._apply_fn
|
| 242 |
+
|
| 243 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 244 |
+
for data in self.datapipe:
|
| 245 |
+
try:
|
| 246 |
+
res = self._apply_fn_(data)
|
| 247 |
+
if res is None:
|
| 248 |
+
continue
|
| 249 |
+
yield res
|
| 250 |
+
except Exception as e:
|
| 251 |
+
if self.handler(e):
|
| 252 |
+
continue
|
| 253 |
+
else:
|
| 254 |
+
raise e
|
| 255 |
+
|
| 256 |
+
__S3_TOOLS__ = {
|
| 257 |
+
"s3": ["/usr/local/bin/aws", "s3"],
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
def is_tar(x: str) -> bool:
|
| 261 |
+
return x.endswith(".tar")
|
| 262 |
+
|
| 263 |
+
def ls_aws(
|
| 264 |
+
path: str,
|
| 265 |
+
tool: str = "s3",
|
| 266 |
+
recursive: bool = True,
|
| 267 |
+
raise_errors: bool = True,
|
| 268 |
+
skip_files: bool = True,
|
| 269 |
+
):
|
| 270 |
+
assert path.startswith("s3://"), path
|
| 271 |
+
|
| 272 |
+
# in case we have a file, set recursive to false
|
| 273 |
+
isfile = bool(os.path.splitext(path)[1])
|
| 274 |
+
if isfile and skip_files:
|
| 275 |
+
return [path]
|
| 276 |
+
|
| 277 |
+
if not path.endswith("/") and not isfile:
|
| 278 |
+
path = path + "/"
|
| 279 |
+
cmd = [*__S3_TOOLS__[tool], "ls", f"{path}"]
|
| 280 |
+
|
| 281 |
+
if recursive and not isfile:
|
| 282 |
+
cmd += ["--recursive"]
|
| 283 |
+
try:
|
| 284 |
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 285 |
+
result.check_returncode()
|
| 286 |
+
|
| 287 |
+
stdout = result.stdout.decode().split("\n")
|
| 288 |
+
# return empty list in case path is not a real file/directory
|
| 289 |
+
if not stdout[0]:
|
| 290 |
+
return []
|
| 291 |
+
|
| 292 |
+
# return path if it is an exisiting file
|
| 293 |
+
if isfile:
|
| 294 |
+
return [path]
|
| 295 |
+
|
| 296 |
+
# strip timestamp and object size
|
| 297 |
+
out = [line.split(" ")[-1] for line in stdout if line]
|
| 298 |
+
if recursive:
|
| 299 |
+
# aws s3 ls returns prefix+filename
|
| 300 |
+
bucket = path[: path.find("/", len("s3://"))] # s3://<bucket>/...
|
| 301 |
+
out = [os.path.join(bucket, o) for o in out]
|
| 302 |
+
else:
|
| 303 |
+
# aws s3 ls returns filename only
|
| 304 |
+
out = [os.path.join(path, o) for i in out]
|
| 305 |
+
|
| 306 |
+
return out
|
| 307 |
+
except subprocess.CalledProcessError as e:
|
| 308 |
+
print(f"Got exception while trying to load data! {e.__class__.__name__}: {e}")
|
| 309 |
+
if raise_errors:
|
| 310 |
+
raise e
|
| 311 |
+
else:
|
| 312 |
+
return []
|
| 313 |
+
|
| 314 |
+
@functional_datapipe("wrapped_load_files_by_s3")
|
| 315 |
+
class WrappedS3FileLoaderIterDataPipe(S3FileLoaderIterDataPipe):
|
| 316 |
+
def __init__(
|
| 317 |
+
self,
|
| 318 |
+
*args,
|
| 319 |
+
ignore_missing_files: bool = False,
|
| 320 |
+
**kwargs,
|
| 321 |
+
):
|
| 322 |
+
super().__init__(*args, **kwargs)
|
| 323 |
+
self.ignore_missing_files = ignore_missing_files
|
| 324 |
+
|
| 325 |
+
def __iter__(self) -> Iterator[Tuple[str, StreamWrapper]]:
|
| 326 |
+
# same as parent but maybe ignoring errors
|
| 327 |
+
for url in self.source_datapipe:
|
| 328 |
+
try:
|
| 329 |
+
yield url, StreamWrapper(BytesIO(self.handler.s3_read(url)))
|
| 330 |
+
except ValueError as e:
|
| 331 |
+
if not self.ignore_missing_files:
|
| 332 |
+
raise FileNotFoundError(url)
|
| 333 |
+
else:
|
| 334 |
+
print(f"Warning: Could not download {url}")
|
| 335 |
+
|
| 336 |
+
@functional_datapipe("list_files_with_aws_cli")
|
| 337 |
+
class S3CLIFileListerIterDataPipe(IterDataPipe[T_co]):
|
| 338 |
+
def __init__(
|
| 339 |
+
self,
|
| 340 |
+
source_datapipe: Union[str, Sequence[str], IterDataPipe],
|
| 341 |
+
s3_tool: str = "s3",
|
| 342 |
+
n_retries: int = 10,
|
| 343 |
+
sleep_interval: float = 0.01,
|
| 344 |
+
):
|
| 345 |
+
if isinstance(source_datapipe, str):
|
| 346 |
+
source_datapipe = [
|
| 347 |
+
source_datapipe,
|
| 348 |
+
]
|
| 349 |
+
if not isinstance(source_datapipe, IterDataPipe):
|
| 350 |
+
self.datapipe: IterDataPipe = IterableWrapper(source_datapipe) # type: ignore[assignment]
|
| 351 |
+
else:
|
| 352 |
+
self.datapipe = source_datapipe
|
| 353 |
+
|
| 354 |
+
assert s3_tool in __S3_TOOLS__, f"`s3_tool` has to be in {list(__S3_TOOLS__)}"
|
| 355 |
+
|
| 356 |
+
self.s3_tool = s3_tool
|
| 357 |
+
|
| 358 |
+
self.n_retries = n_retries
|
| 359 |
+
self.sleep = sleep_interval
|
| 360 |
+
|
| 361 |
+
def __iter__(self) -> Iterator[str]:
|
| 362 |
+
for root in self.datapipe:
|
| 363 |
+
files = None
|
| 364 |
+
for _ in range(self.n_retries):
|
| 365 |
+
try:
|
| 366 |
+
files = ls_aws(
|
| 367 |
+
path=root, tool=self.s3_tool, recursive=True, raise_errors=True
|
| 368 |
+
)
|
| 369 |
+
break
|
| 370 |
+
except subprocess.CalledProcessError:
|
| 371 |
+
time.sleep(self.sleep)
|
| 372 |
+
|
| 373 |
+
if files is None:
|
| 374 |
+
print(
|
| 375 |
+
f"Could not ls data expected under {root} in {self.n_retries} tries. Not yielding ..."
|
| 376 |
+
)
|
| 377 |
+
continue
|
| 378 |
+
|
| 379 |
+
for file in files:
|
| 380 |
+
yield file
|
| 381 |
+
|
| 382 |
+
def _download_tar(url, scratch, verbose=False, n_retries=100):
|
| 383 |
+
start = time.perf_counter()
|
| 384 |
+
if verbose:
|
| 385 |
+
_log(f"downloading {url}")
|
| 386 |
+
uid = "".join(filter(str.isalnum, os.path.splitext(url)[0]))
|
| 387 |
+
idx = 0
|
| 388 |
+
path = os.path.join(scratch, f"{os.getpid()}.{uid}.{idx}.tar")
|
| 389 |
+
while os.path.exists(path):
|
| 390 |
+
idx += 1
|
| 391 |
+
path = os.path.join(scratch, f"{os.getpid()}.{uid}.{idx}.tar")
|
| 392 |
+
|
| 393 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 394 |
+
for i_try in range(n_retries):
|
| 395 |
+
p = subprocess.call(
|
| 396 |
+
[
|
| 397 |
+
"/usr/local/bin/aws",
|
| 398 |
+
"s3",
|
| 399 |
+
"cp",
|
| 400 |
+
url,
|
| 401 |
+
path,
|
| 402 |
+
"--quiet",
|
| 403 |
+
]
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
if p != 0:
|
| 407 |
+
# mostly to work around
|
| 408 |
+
# Error when retrieving credentials from Ec2InstanceMetadata: No
|
| 409 |
+
# credentials found in credential_source referenced in profile
|
| 410 |
+
# default
|
| 411 |
+
if i_try == 0:
|
| 412 |
+
# check if the requested file actually exists
|
| 413 |
+
if not ls_aws(url, skip_files=False, raise_errors=False):
|
| 414 |
+
raise FileNotFoundError(url)
|
| 415 |
+
|
| 416 |
+
if i_try + 1 < n_retries:
|
| 417 |
+
time.sleep(random.uniform(0.0, 0.1))
|
| 418 |
+
continue
|
| 419 |
+
else:
|
| 420 |
+
raise Exception(
|
| 421 |
+
f"File {url} should exist but failed to download after trying {i_try+1} times."
|
| 422 |
+
)
|
| 423 |
+
break
|
| 424 |
+
|
| 425 |
+
if verbose:
|
| 426 |
+
_log(
|
| 427 |
+
f"wrote {path} in {time.perf_counter() - start} secs after {i_try+1} tries."
|
| 428 |
+
)
|
| 429 |
+
return path
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
@functional_datapipe("download_with_s3_cli")
|
| 433 |
+
class S3CLITarDownloader(IterDataPipe[T_co]):
|
| 434 |
+
def __init__(
|
| 435 |
+
self,
|
| 436 |
+
source_datapipe,
|
| 437 |
+
n_retries: int = 10,
|
| 438 |
+
verbose: bool = False,
|
| 439 |
+
dl_root: str = "/scratch",
|
| 440 |
+
mode: str = "r:*",
|
| 441 |
+
aws_kwargs: Optional[dict] = None,
|
| 442 |
+
ignore_missing_files: bool = False,
|
| 443 |
+
):
|
| 444 |
+
self.source_datapipe = source_datapipe
|
| 445 |
+
assert os.path.isdir(dl_root), f"`dl_root` {dl_root} is not a valid directory"
|
| 446 |
+
# get slurm job id and create a subdir in `dl_root` to download all the tars to
|
| 447 |
+
dl_root = get_job_temp_dir(dl_root)
|
| 448 |
+
os.makedirs(dl_root, exist_ok=True)
|
| 449 |
+
self.dl_root = dl_root
|
| 450 |
+
self.mode = mode
|
| 451 |
+
self.n_retries = n_retries
|
| 452 |
+
self.ignore_missing_files = ignore_missing_files
|
| 453 |
+
|
| 454 |
+
self.verbose = verbose
|
| 455 |
+
|
| 456 |
+
def _yield_next(self, url: str, local_path: str):
|
| 457 |
+
if self.verbose:
|
| 458 |
+
_log("popping queue")
|
| 459 |
+
|
| 460 |
+
if self.verbose:
|
| 461 |
+
_log(f"loading {local_path}")
|
| 462 |
+
tarstream = tarfile.open(local_path, self.mode)
|
| 463 |
+
|
| 464 |
+
if self.verbose:
|
| 465 |
+
_log(f"yielding {url}")
|
| 466 |
+
|
| 467 |
+
yield (url, StreamWrapper(tarstream))
|
| 468 |
+
if self.verbose:
|
| 469 |
+
_log("new tar request")
|
| 470 |
+
|
| 471 |
+
if self.verbose:
|
| 472 |
+
_log(f"removing previous tar at {local_path}")
|
| 473 |
+
try:
|
| 474 |
+
os.remove(local_path)
|
| 475 |
+
except FileNotFoundError:
|
| 476 |
+
_log(
|
| 477 |
+
f"WARNING: Could not find previous tar for deletion. Unless a clean-up was triggered this is unexpected. The location was {local_path}"
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
def __iter__(self):
|
| 481 |
+
with tempfile.TemporaryDirectory(dir=self.dl_root) as scratch:
|
| 482 |
+
for url in self.source_datapipe:
|
| 483 |
+
try:
|
| 484 |
+
local_path = _download_tar(
|
| 485 |
+
url, scratch, verbose=self.verbose, n_retries=self.n_retries
|
| 486 |
+
)
|
| 487 |
+
except FileNotFoundError as e:
|
| 488 |
+
if not self.ignore_missing_files:
|
| 489 |
+
raise e
|
| 490 |
+
else:
|
| 491 |
+
yield from self._yield_next(url, local_path)
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
@functional_datapipe("load_from_tar_and_handle_error")
|
| 495 |
+
class TarArchiveLoaderWithErrorHandlingIterDataPipe(TarArchiveLoader):
|
| 496 |
+
def __init__(
|
| 497 |
+
self,
|
| 498 |
+
datapipe: Iterable[Tuple[str, BufferedIOBase]],
|
| 499 |
+
mode: str = "r:*",
|
| 500 |
+
length: int = -1,
|
| 501 |
+
handler: Callable = wds.reraise_exception,
|
| 502 |
+
):
|
| 503 |
+
super().__init__(datapipe=datapipe, mode=mode, length=length)
|
| 504 |
+
self.handler = handler
|
| 505 |
+
|
| 506 |
+
def __iter__(self) -> Iterator[Tuple[str, BufferedIOBase]]:
|
| 507 |
+
for data in self.datapipe:
|
| 508 |
+
validate_pathname_binary_tuple(data)
|
| 509 |
+
pathname, data_stream = data
|
| 510 |
+
try:
|
| 511 |
+
if isinstance(data_stream, StreamWrapper) and isinstance(
|
| 512 |
+
data_stream.file_obj, tarfile.TarFile
|
| 513 |
+
):
|
| 514 |
+
tar = data_stream.file_obj
|
| 515 |
+
else:
|
| 516 |
+
reading_mode = (
|
| 517 |
+
self.mode
|
| 518 |
+
if hasattr(data_stream, "seekable") and data_stream.seekable()
|
| 519 |
+
else self.mode.replace(":", "|")
|
| 520 |
+
)
|
| 521 |
+
# typing.cast is used here to silence mypy's type checker
|
| 522 |
+
tar = tarfile.open(
|
| 523 |
+
fileobj=cast(Optional[IO[bytes]], data_stream),
|
| 524 |
+
mode=reading_mode,
|
| 525 |
+
)
|
| 526 |
+
for tarinfo in tar:
|
| 527 |
+
if not tarinfo.isfile():
|
| 528 |
+
continue
|
| 529 |
+
extracted_fobj = tar.extractfile(tarinfo)
|
| 530 |
+
if extracted_fobj is None:
|
| 531 |
+
warnings.warn(
|
| 532 |
+
f"failed to extract file {tarinfo.name} from source tarfile {pathname}"
|
| 533 |
+
)
|
| 534 |
+
raise tarfile.ExtractError
|
| 535 |
+
inner_pathname = os.path.normpath(
|
| 536 |
+
os.path.join(pathname, tarinfo.name)
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
yield inner_pathname, StreamWrapper(extracted_fobj, data_stream, name=inner_pathname) # type: ignore[misc]
|
| 540 |
+
except Exception as e:
|
| 541 |
+
warnings.warn(
|
| 542 |
+
f"Unable to extract files from corrupted tarfile stream {pathname} due to: {e}, abort!"
|
| 543 |
+
)
|
| 544 |
+
if self.handler(e):
|
| 545 |
+
if hasattr(e, "args") and len(e.args) > 0:
|
| 546 |
+
e.args = (e.args[0] + " @ " + str(pathname),) + e.args[1:]
|
| 547 |
+
finally:
|
| 548 |
+
if isinstance(data_stream, StreamWrapper):
|
| 549 |
+
data_stream.autoclose()
|
| 550 |
+
|
| 551 |
+
class DictCollator:
|
| 552 |
+
def __init__(
|
| 553 |
+
self,
|
| 554 |
+
combine_tensors: bool = True,
|
| 555 |
+
combine_scalars: bool = True,
|
| 556 |
+
timeout=None,
|
| 557 |
+
):
|
| 558 |
+
self.combine_tensors = combine_tensors
|
| 559 |
+
self.combine_scalars = combine_scalars
|
| 560 |
+
|
| 561 |
+
collate = self._collate
|
| 562 |
+
if timeout is not None:
|
| 563 |
+
collate = timeout_wrapper(collate, timeout=timeout)
|
| 564 |
+
self.collate = collate
|
| 565 |
+
|
| 566 |
+
def __call__(self, samples):
|
| 567 |
+
return self.collate(samples)
|
| 568 |
+
|
| 569 |
+
def _collate(self, samples):
|
| 570 |
+
keys = set.intersection(*[set(sample.keys()) for sample in samples])
|
| 571 |
+
batched = {key: [] for key in keys}
|
| 572 |
+
|
| 573 |
+
for s in samples:
|
| 574 |
+
[batched[key].append(s[key]) for key in batched]
|
| 575 |
+
|
| 576 |
+
result = {}
|
| 577 |
+
for key in batched:
|
| 578 |
+
if isinstance(batched[key][0], (int, float)):
|
| 579 |
+
if self.combine_scalars:
|
| 580 |
+
result[key] = np.array(list(batched[key]))
|
| 581 |
+
elif isinstance(batched[key][0], Tensor):
|
| 582 |
+
if self.combine_tensors:
|
| 583 |
+
result[key] = stack(list(batched[key]))
|
| 584 |
+
elif isinstance(batched[key][0], np.ndarray):
|
| 585 |
+
if self.combine_tensors:
|
| 586 |
+
result[key] = np.array(list(batched[key]))
|
| 587 |
+
else:
|
| 588 |
+
result[key] = list(batched[key])
|
| 589 |
+
|
| 590 |
+
del samples
|
| 591 |
+
del batched
|
| 592 |
+
return result
|
| 593 |
+
|
| 594 |
+
def timeout_wrapper(func: Callable, timeout: Optional[float] = None) -> Callable:
|
| 595 |
+
if timeout is None or timeout <= 0.0:
|
| 596 |
+
return func
|
| 597 |
+
|
| 598 |
+
@functools.wraps(func)
|
| 599 |
+
def wrapper(*args, **kwargs):
|
| 600 |
+
result = [None]
|
| 601 |
+
exception = [None]
|
| 602 |
+
event = threading.Event()
|
| 603 |
+
|
| 604 |
+
def wrapped_func():
|
| 605 |
+
try:
|
| 606 |
+
result[0] = func(*args, **kwargs)
|
| 607 |
+
except Exception as e:
|
| 608 |
+
exception[0] = e
|
| 609 |
+
finally:
|
| 610 |
+
event.set()
|
| 611 |
+
|
| 612 |
+
thread = threading.Thread(target=wrapped_func)
|
| 613 |
+
thread.start()
|
| 614 |
+
event.wait(timeout)
|
| 615 |
+
|
| 616 |
+
if not event.is_set():
|
| 617 |
+
raise TimeoutError(f"Function call timed out (longer than {timeout} secs).")
|
| 618 |
+
|
| 619 |
+
thread.join()
|
| 620 |
+
|
| 621 |
+
if exception[0] is not None:
|
| 622 |
+
err = exception[0]
|
| 623 |
+
del exception
|
| 624 |
+
raise err
|
| 625 |
+
|
| 626 |
+
del thread
|
| 627 |
+
del exception
|
| 628 |
+
del wrapped_func
|
| 629 |
+
del event
|
| 630 |
+
del args
|
| 631 |
+
del kwargs
|
| 632 |
+
|
| 633 |
+
ret = result[0]
|
| 634 |
+
del result
|
| 635 |
+
return ret
|
| 636 |
+
|
| 637 |
+
return wrapper
|
| 638 |
+
|
| 639 |
+
class DecoderWithTimeout(wds.Decoder):
|
| 640 |
+
"""Decode samples using a list of handlers.
|
| 641 |
+
|
| 642 |
+
For each key/data item, this iterates through the list of
|
| 643 |
+
handlers until some handler returns something other than None.
|
| 644 |
+
"""
|
| 645 |
+
|
| 646 |
+
def __init__(self, *args, timeout: Optional[float] = None, **kwargs):
|
| 647 |
+
super().__init__(*args, **kwargs)
|
| 648 |
+
|
| 649 |
+
if timeout is not None:
|
| 650 |
+
self.decode = timeout_wrapper(self.decode, timeout)
|
| 651 |
+
|
| 652 |
+
def setup_decoder(
|
| 653 |
+
decoders,
|
| 654 |
+
additional_decoder_kwargs: Optional[Union[DictConfig, Dict]] = None,
|
| 655 |
+
):
|
| 656 |
+
if not additional_decoder_kwargs:
|
| 657 |
+
additional_decoder_kwargs = {}
|
| 658 |
+
|
| 659 |
+
if not isinstance(decoders, (List, ListConfig)):
|
| 660 |
+
decoders = [decoders]
|
| 661 |
+
handlers = []
|
| 662 |
+
for decoder_spec in decoders:
|
| 663 |
+
if isinstance(decoder_spec, (Dict, DictConfig)):
|
| 664 |
+
decoder = instantiate(decoder_spec)
|
| 665 |
+
elif isinstance(decoder_spec, str):
|
| 666 |
+
decoder = wds.autodecode.ImageHandler(decoder_spec)
|
| 667 |
+
else:
|
| 668 |
+
raise TypeError(f"{decoder_spec} not a thing for decoders.")
|
| 669 |
+
handlers.append(decoder)
|
| 670 |
+
print(f"Adding decoder {decoder.__class__.__name__} to decoders.")
|
| 671 |
+
|
| 672 |
+
decoder = DecoderWithTimeout(
|
| 673 |
+
handlers,
|
| 674 |
+
partial=additional_decoder_kwargs.pop("partial", True),
|
| 675 |
+
**additional_decoder_kwargs, # todo
|
| 676 |
+
)
|
| 677 |
+
return decoder
|
| 678 |
+
|
| 679 |
+
def create_dataset(urls, is_s3=False, prefetch=None,
|
| 680 |
+
s3_buffer_size=None, s3_ignore_missing_files=True,
|
| 681 |
+
sample_shuffle=1, shard_shuffle=1000, cycle=True,
|
| 682 |
+
split_workers_along_tars=True, decoders="torch"):
|
| 683 |
+
if isinstance(urls, str):
|
| 684 |
+
urls = [urls]
|
| 685 |
+
dp = IterableWrapper(urls)
|
| 686 |
+
dp = dp.custom_shard_expand()
|
| 687 |
+
if is_s3:
|
| 688 |
+
dp = dp.list_files_with_aws_cli().filter(is_tar)
|
| 689 |
+
else:
|
| 690 |
+
dp = dp.list_files(masks="*.tar", recursive=True)
|
| 691 |
+
|
| 692 |
+
dp = ShuffledListDataPipe(list(dp), shuffle=shard_shuffle > 1, cycle=cycle)
|
| 693 |
+
|
| 694 |
+
if split_workers_along_tars:
|
| 695 |
+
dp = dp.sharding_filter()
|
| 696 |
+
|
| 697 |
+
if is_s3:
|
| 698 |
+
# if prefetch is None:
|
| 699 |
+
# s3_buffer_size = int(s3_buffer_size) if s3_buffer_size is not None else None
|
| 700 |
+
# dp = dp.wrapped_load_files_by_s3(
|
| 701 |
+
# buffer_size=s3_buffer_size,
|
| 702 |
+
# ignore_missing_files=s3_ignore_missing_files,
|
| 703 |
+
# )
|
| 704 |
+
dp = dp.download_with_s3_cli(ignore_missing_files=s3_ignore_missing_files)
|
| 705 |
+
else:
|
| 706 |
+
dp = dp.open_files(mode="b")
|
| 707 |
+
|
| 708 |
+
if prefetch is not None:
|
| 709 |
+
dp = dp.custom_prefetch(buffer_size=prefetch)
|
| 710 |
+
|
| 711 |
+
dp = dp.load_from_tar_and_handle_error(handler=error_handler)
|
| 712 |
+
dp = dp.webdataset()
|
| 713 |
+
|
| 714 |
+
if not split_workers_along_tars:
|
| 715 |
+
dp = dp.sharding_filter()
|
| 716 |
+
|
| 717 |
+
dp = dp.map_with_handler(
|
| 718 |
+
stream_reader,
|
| 719 |
+
handler=error_handler,
|
| 720 |
+
called_cls_name="StreamReader",
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
dp = dp.shuffle(buffer_size=sample_shuffle) if sample_shuffle > 1 else dp
|
| 724 |
+
|
| 725 |
+
dp = dp.map_with_handler(
|
| 726 |
+
to_our_format,
|
| 727 |
+
handler=error_handler,
|
| 728 |
+
called_cls_name="ToOurFormat",
|
| 729 |
+
)
|
| 730 |
+
|
| 731 |
+
decoder: DecoderWithTimeout = setup_decoder(decoders) # ,additional_decoder_kwargs)
|
| 732 |
+
|
| 733 |
+
dp = dp.map_with_handler(
|
| 734 |
+
decoder.decode,
|
| 735 |
+
handler=error_handler,
|
| 736 |
+
called_cls_name="Decoder",
|
| 737 |
+
)
|
| 738 |
+
|
| 739 |
+
return dp
|
| 740 |
+
|
| 741 |
+
def create_loader(
|
| 742 |
+
datapipeline: IterDataPipe,
|
| 743 |
+
batch_size: int,
|
| 744 |
+
num_workers: int,
|
| 745 |
+
partial: bool = False,
|
| 746 |
+
collation_fn: Optional[Union[Callable, Dict, DictConfig]] = DictCollator(),
|
| 747 |
+
batched_transforms: Optional[ListConfig] = None,
|
| 748 |
+
loader_kwargs: Optional[Union[Dict, DictConfig]] = None,
|
| 749 |
+
) -> DataLoader:
|
| 750 |
+
if not loader_kwargs:
|
| 751 |
+
loader_kwargs = {}
|
| 752 |
+
|
| 753 |
+
loader_kwargs.pop("shuffle", None)
|
| 754 |
+
if not batched_transforms:
|
| 755 |
+
batched_transforms = []
|
| 756 |
+
|
| 757 |
+
print("#" * 100)
|
| 758 |
+
print("Building dataloader with the following parameters")
|
| 759 |
+
print(f"batch_size: {batch_size}, num_workers: {num_workers}")
|
| 760 |
+
for key in loader_kwargs:
|
| 761 |
+
print(key, ": ", loader_kwargs[key])
|
| 762 |
+
print("#" * 100)
|
| 763 |
+
|
| 764 |
+
datapipeline = datapipeline.batch(batch_size, drop_last=not partial)
|
| 765 |
+
|
| 766 |
+
if isinstance(collation_fn, (Dict, DictConfig)):
|
| 767 |
+
collation_fn = instantiate(collation_fn)
|
| 768 |
+
datapipeline = datapipeline.collate(collate_fn=collation_fn)
|
| 769 |
+
loader = DataLoader(
|
| 770 |
+
datapipeline, batch_size=None, num_workers=num_workers, **loader_kwargs
|
| 771 |
+
)
|
| 772 |
+
return loader
|
fMRI-foundation-model-old/fMRI-MAE-mamba/mamba.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
fMRI-foundation-model-old/fMRI-MAE-mamba/mindeye_config.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Config
|
| 2 |
+
model_name: "pretrain_framepatchsize4"
|
| 3 |
+
mae_model_name: "framepatchsize4_bs8_300ep"
|
| 4 |
+
|
| 5 |
+
# Training Configs
|
| 6 |
+
global_batch_size: 32
|
| 7 |
+
mixed_precision: "fp16"
|
| 8 |
+
num_epochs: 12
|
| 9 |
+
seed: 42
|
| 10 |
+
max_lr: 3.0e-4 # Keep the x.0 else will be converted to string
|
| 11 |
+
multi_subject: False
|
| 12 |
+
multisubject_ckpt: None
|
| 13 |
+
|
| 14 |
+
# Saving progress
|
| 15 |
+
ckpt_saving: False
|
| 16 |
+
ckpt_interval: 99
|
| 17 |
+
resume_from_ckpt: False
|
| 18 |
+
wandb_log: True
|
| 19 |
+
|
| 20 |
+
# Model Config
|
| 21 |
+
in_dim: 370176
|
| 22 |
+
hidden_dim: 512
|
| 23 |
+
drop: .15
|
| 24 |
+
mixup_pct: 0.33
|
| 25 |
+
|
| 26 |
+
# Data Config
|
| 27 |
+
nsd_wds_path: "/weka/proj-fmri/shared/mindeyev2_dataset/wds"
|
| 28 |
+
nsd_raw_path: "/weka/proj-fmri/shared/mindeyev2_dataset"
|
| 29 |
+
nsd_image_path: "/weka/proj-fmri/shared/mindeyev2_dataset"
|
| 30 |
+
num_sessions: 40
|
fMRI-foundation-model-old/fMRI-MAE-mamba/mindeye_models.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
class MindEyeModule(nn.Module):
|
| 7 |
+
def __init__(self):
|
| 8 |
+
super().__init__()
|
| 9 |
+
def forward(self, x):
|
| 10 |
+
return x
|
| 11 |
+
class RidgeRegression(nn.Module):
|
| 12 |
+
# make sure to add weight_decay when initializing optimizer
|
| 13 |
+
def __init__(self, input_sizes, out_features, seq_len=1):
|
| 14 |
+
super().__init__()
|
| 15 |
+
self.out_features = out_features
|
| 16 |
+
self.linears = torch.nn.ModuleList([
|
| 17 |
+
torch.nn.Linear(input_size, out_features) for input_size in input_sizes
|
| 18 |
+
])
|
| 19 |
+
self.seq_len = seq_len
|
| 20 |
+
def forward(self, x, subj_idx):
|
| 21 |
+
out = torch.cat([self.linears[subj_idx](x[:,seq]).unsqueeze(1) for seq in range(self.seq_len)], dim=1)
|
| 22 |
+
return out
|
| 23 |
+
class BrainNetwork(nn.Module):
|
| 24 |
+
def __init__(self, h=1024, in_dim=15724, out_dim=768, seq_len=1, n_blocks=4, drop=.15, clip_size=768, clip_scale=1):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.seq_len = seq_len
|
| 27 |
+
self.h = h
|
| 28 |
+
self.clip_size = clip_size
|
| 29 |
+
self.clip_scale = clip_scale
|
| 30 |
+
self.mixer_blocks1 = nn.ModuleList([
|
| 31 |
+
self.mixer_block1(h, drop) for _ in range(n_blocks)
|
| 32 |
+
])
|
| 33 |
+
self.mixer_blocks2 = nn.ModuleList([
|
| 34 |
+
self.mixer_block2(seq_len, drop) for _ in range(n_blocks)
|
| 35 |
+
])
|
| 36 |
+
|
| 37 |
+
# Output linear layer
|
| 38 |
+
self.backbone_linear = nn.Linear(h * seq_len, out_dim, bias=True)
|
| 39 |
+
self.clip_proj = self.projector(clip_size, clip_size, h=clip_size)
|
| 40 |
+
|
| 41 |
+
def projector(self, in_dim, out_dim, h=2048):
|
| 42 |
+
return nn.Sequential(
|
| 43 |
+
nn.LayerNorm(in_dim),
|
| 44 |
+
nn.GELU(),
|
| 45 |
+
nn.Linear(in_dim, h),
|
| 46 |
+
nn.LayerNorm(h),
|
| 47 |
+
nn.GELU(),
|
| 48 |
+
nn.Linear(h, h),
|
| 49 |
+
nn.LayerNorm(h),
|
| 50 |
+
nn.GELU(),
|
| 51 |
+
nn.Linear(h, out_dim)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def mlp(self, in_dim, out_dim, drop):
|
| 55 |
+
return nn.Sequential(
|
| 56 |
+
nn.Linear(in_dim, out_dim),
|
| 57 |
+
nn.GELU(),
|
| 58 |
+
nn.Dropout(drop),
|
| 59 |
+
nn.Linear(out_dim, out_dim),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
def mixer_block1(self, h, drop):
|
| 63 |
+
return nn.Sequential(
|
| 64 |
+
nn.LayerNorm(h),
|
| 65 |
+
self.mlp(h, h, drop), # Token mixing
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
def mixer_block2(self, seq_len, drop):
|
| 69 |
+
return nn.Sequential(
|
| 70 |
+
nn.LayerNorm(seq_len),
|
| 71 |
+
self.mlp(seq_len, seq_len, drop) # Channel mixing
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def forward(self, x):
|
| 75 |
+
# make empty tensors
|
| 76 |
+
c = torch.Tensor([0.])
|
| 77 |
+
|
| 78 |
+
# Mixer blocks
|
| 79 |
+
residual1 = x
|
| 80 |
+
residual2 = x.permute(0,2,1)
|
| 81 |
+
for block1, block2 in zip(self.mixer_blocks1,self.mixer_blocks2):
|
| 82 |
+
x = block1(x) + residual1
|
| 83 |
+
residual1 = x
|
| 84 |
+
x = x.permute(0,2,1)
|
| 85 |
+
|
| 86 |
+
x = block2(x) + residual2
|
| 87 |
+
residual2 = x
|
| 88 |
+
x = x.permute(0,2,1)
|
| 89 |
+
|
| 90 |
+
x = x.reshape(x.size(0), -1)
|
| 91 |
+
backbone = self.backbone_linear(x).reshape(len(x), -1, self.clip_size)
|
| 92 |
+
if self.clip_scale>0:
|
| 93 |
+
c = self.clip_proj(backbone)
|
| 94 |
+
|
| 95 |
+
return backbone, c
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
import open_clip
|
| 99 |
+
import kornia
|
| 100 |
+
class FrozenOpenCLIPImageEmbedder(nn.Module):
|
| 101 |
+
"""
|
| 102 |
+
Uses the OpenCLIP vision transformer encoder for images
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
def __init__(
|
| 106 |
+
self,
|
| 107 |
+
arch="ViT-H-14",
|
| 108 |
+
version="laion2b_s32b_b79k",
|
| 109 |
+
device="cuda",
|
| 110 |
+
init_device="cpu",
|
| 111 |
+
max_length=77,
|
| 112 |
+
freeze=True,
|
| 113 |
+
antialias=True,
|
| 114 |
+
ucg_rate=0.0,
|
| 115 |
+
unsqueeze_dim=False,
|
| 116 |
+
repeat_to_max_len=False,
|
| 117 |
+
num_image_crops=0,
|
| 118 |
+
output_tokens=False,
|
| 119 |
+
l2_norm_tokens=False,
|
| 120 |
+
only_tokens=False,
|
| 121 |
+
cache_dir: Optional[str] = None,
|
| 122 |
+
):
|
| 123 |
+
super().__init__()
|
| 124 |
+
model, _, _ = open_clip.create_model_and_transforms(
|
| 125 |
+
arch,
|
| 126 |
+
device=torch.device(init_device),
|
| 127 |
+
pretrained=version,
|
| 128 |
+
cache_dir=cache_dir,
|
| 129 |
+
)
|
| 130 |
+
del model.transformer
|
| 131 |
+
self.model = model
|
| 132 |
+
self.max_crops = num_image_crops
|
| 133 |
+
self.pad_to_max_len = self.max_crops > 0
|
| 134 |
+
self.repeat_to_max_len = repeat_to_max_len and (not self.pad_to_max_len)
|
| 135 |
+
self.device = device
|
| 136 |
+
self.max_length = max_length
|
| 137 |
+
if freeze:
|
| 138 |
+
self.freeze()
|
| 139 |
+
|
| 140 |
+
self.antialias = antialias
|
| 141 |
+
|
| 142 |
+
self.register_buffer(
|
| 143 |
+
"mean", torch.Tensor([0.48145466, 0.4578275, 0.40821073]), persistent=False
|
| 144 |
+
)
|
| 145 |
+
self.register_buffer(
|
| 146 |
+
"std", torch.Tensor([0.26862954, 0.26130258, 0.27577711]), persistent=False
|
| 147 |
+
)
|
| 148 |
+
self.ucg_rate = ucg_rate
|
| 149 |
+
self.unsqueeze_dim = unsqueeze_dim
|
| 150 |
+
self.stored_batch = None
|
| 151 |
+
self.model.visual.output_tokens = output_tokens
|
| 152 |
+
self.output_tokens = output_tokens
|
| 153 |
+
if only_tokens:
|
| 154 |
+
assert output_tokens
|
| 155 |
+
self.only_tokens = only_tokens
|
| 156 |
+
self.l2_norm_tokens = l2_norm_tokens
|
| 157 |
+
if l2_norm_tokens:
|
| 158 |
+
assert output_tokens
|
| 159 |
+
|
| 160 |
+
def preprocess(self, x):
|
| 161 |
+
# normalize to [0,1]
|
| 162 |
+
x = kornia.geometry.resize(
|
| 163 |
+
x,
|
| 164 |
+
(224, 224),
|
| 165 |
+
interpolation="bicubic",
|
| 166 |
+
align_corners=True,
|
| 167 |
+
antialias=self.antialias,
|
| 168 |
+
)
|
| 169 |
+
x = (x + 1.0) / 2.0
|
| 170 |
+
# renormalize according to clip
|
| 171 |
+
x = kornia.enhance.normalize(x, self.mean, self.std)
|
| 172 |
+
return x
|
| 173 |
+
|
| 174 |
+
def freeze(self):
|
| 175 |
+
self.model = self.model.eval()
|
| 176 |
+
for param in self.parameters():
|
| 177 |
+
param.requires_grad = False
|
| 178 |
+
|
| 179 |
+
def forward(self, image, no_dropout=False):
|
| 180 |
+
z = self.encode_with_vision_transformer(image)
|
| 181 |
+
tokens = None
|
| 182 |
+
if self.output_tokens:
|
| 183 |
+
z, tokens = z[0], z[1]
|
| 184 |
+
z = z.to(image.dtype)
|
| 185 |
+
if self.ucg_rate > 0.0 and not no_dropout and not (self.max_crops > 0):
|
| 186 |
+
z = (
|
| 187 |
+
torch.bernoulli(
|
| 188 |
+
(1.0 - self.ucg_rate) * torch.ones(z.shape[0], device=z.device)
|
| 189 |
+
)[:, None]
|
| 190 |
+
* z
|
| 191 |
+
)
|
| 192 |
+
if tokens is not None:
|
| 193 |
+
tokens = (
|
| 194 |
+
expand_dims_like(
|
| 195 |
+
torch.bernoulli(
|
| 196 |
+
(1.0 - self.ucg_rate)
|
| 197 |
+
* torch.ones(tokens.shape[0], device=tokens.device)
|
| 198 |
+
),
|
| 199 |
+
tokens,
|
| 200 |
+
)
|
| 201 |
+
* tokens
|
| 202 |
+
)
|
| 203 |
+
if self.unsqueeze_dim:
|
| 204 |
+
z = z[:, None, :]
|
| 205 |
+
if self.output_tokens:
|
| 206 |
+
assert not self.repeat_to_max_len
|
| 207 |
+
assert not self.pad_to_max_len
|
| 208 |
+
if self.only_tokens:
|
| 209 |
+
return tokens
|
| 210 |
+
return tokens, z
|
| 211 |
+
if self.repeat_to_max_len:
|
| 212 |
+
if z.dim() == 2:
|
| 213 |
+
z_ = z[:, None, :]
|
| 214 |
+
else:
|
| 215 |
+
z_ = z
|
| 216 |
+
return repeat(z_, "b 1 d -> b n d", n=self.max_length), z
|
| 217 |
+
elif self.pad_to_max_len:
|
| 218 |
+
assert z.dim() == 3
|
| 219 |
+
z_pad = torch.cat(
|
| 220 |
+
(
|
| 221 |
+
z,
|
| 222 |
+
torch.zeros(
|
| 223 |
+
z.shape[0],
|
| 224 |
+
self.max_length - z.shape[1],
|
| 225 |
+
z.shape[2],
|
| 226 |
+
device=z.device,
|
| 227 |
+
),
|
| 228 |
+
),
|
| 229 |
+
1,
|
| 230 |
+
)
|
| 231 |
+
return z_pad, z_pad[:, 0, ...]
|
| 232 |
+
return z
|
| 233 |
+
|
| 234 |
+
def encode_with_vision_transformer(self, img):
|
| 235 |
+
# if self.max_crops > 0:
|
| 236 |
+
# img = self.preprocess_by_cropping(img)
|
| 237 |
+
if img.dim() == 5:
|
| 238 |
+
assert self.max_crops == img.shape[1]
|
| 239 |
+
img = rearrange(img, "b n c h w -> (b n) c h w")
|
| 240 |
+
img = self.preprocess(img)
|
| 241 |
+
if not self.output_tokens:
|
| 242 |
+
assert not self.model.visual.output_tokens
|
| 243 |
+
x = self.model.visual(img)
|
| 244 |
+
tokens = None
|
| 245 |
+
else:
|
| 246 |
+
assert self.model.visual.output_tokens
|
| 247 |
+
x, tokens = self.model.visual(img)
|
| 248 |
+
if self.l2_norm_tokens:
|
| 249 |
+
token_shape = tokens.shape
|
| 250 |
+
tokens = tokens.flatten(1)
|
| 251 |
+
tokens = torch.nn.functional.normalize(tokens, dim=-1)
|
| 252 |
+
tokens = (tokens - .0002) / .0015
|
| 253 |
+
tokens = tokens.view(token_shape)
|
| 254 |
+
tokens = (tokens * 1.0957) + .1598
|
| 255 |
+
if self.max_crops > 0:
|
| 256 |
+
x = rearrange(x, "(b n) d -> b n d", n=self.max_crops)
|
| 257 |
+
# drop out between 0 and all along the sequence axis
|
| 258 |
+
x = (
|
| 259 |
+
torch.bernoulli(
|
| 260 |
+
(1.0 - self.ucg_rate)
|
| 261 |
+
* torch.ones(x.shape[0], x.shape[1], 1, device=x.device)
|
| 262 |
+
)
|
| 263 |
+
* x
|
| 264 |
+
)
|
| 265 |
+
if tokens is not None:
|
| 266 |
+
tokens = rearrange(tokens, "(b n) t d -> b t (n d)", n=self.max_crops)
|
| 267 |
+
logpy.warning(
|
| 268 |
+
f"You are running very experimental token-concat in {self.__class__.__name__}. "
|
| 269 |
+
f"Check what you are doing, and then remove this message."
|
| 270 |
+
)
|
| 271 |
+
if self.output_tokens:
|
| 272 |
+
return x, tokens
|
| 273 |
+
return x
|
| 274 |
+
|
| 275 |
+
def encode(self, text):
|
| 276 |
+
return self(text)
|
fMRI-foundation-model-old/fMRI-MAE-mamba/models.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2015-present, Facebook, Inc.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from functools import partial
|
| 9 |
+
from torch import Tensor
|
| 10 |
+
from typing import Optional
|
| 11 |
+
import torch.utils.checkpoint as checkpoint
|
| 12 |
+
|
| 13 |
+
from einops import rearrange
|
| 14 |
+
from einops.layers.torch import Rearrange
|
| 15 |
+
import numpy as np
|
| 16 |
+
from timm.models.vision_transformer import _cfg
|
| 17 |
+
from timm.models.layers import trunc_normal_
|
| 18 |
+
|
| 19 |
+
from timm.models.layers import DropPath, to_2tuple
|
| 20 |
+
from timm.models.registry import register_model
|
| 21 |
+
from timm.models.vision_transformer import _load_weights
|
| 22 |
+
|
| 23 |
+
import math
|
| 24 |
+
|
| 25 |
+
from mamba_ssm.modules.mamba_simple import Mamba
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
from mamba_ssm.ops.triton.layernorm import RMSNorm, layer_norm_fn, rms_norm_fn
|
| 29 |
+
except ImportError:
|
| 30 |
+
RMSNorm, layer_norm_fn, rms_norm_fn = None, None, None
|
| 31 |
+
|
| 32 |
+
def posemb_sincos_4d(patches, temperature=10000, dtype=torch.float32):
|
| 33 |
+
_, f, d, h, w, dim, device, dtype = (*patches.shape, patches.device, patches.dtype)
|
| 34 |
+
|
| 35 |
+
z, y, x, t = torch.meshgrid(
|
| 36 |
+
torch.arange(f, device=device),
|
| 37 |
+
torch.arange(d, device=device),
|
| 38 |
+
torch.arange(h, device=device),
|
| 39 |
+
torch.arange(w, device=device),
|
| 40 |
+
indexing="ij",
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
fourier_dim = dim // 8
|
| 44 |
+
|
| 45 |
+
omega = torch.arange(fourier_dim, device=device) / (fourier_dim - 1)
|
| 46 |
+
omega = 1.0 / (temperature**omega)
|
| 47 |
+
|
| 48 |
+
z, y, x, t = [v.flatten()[:, None] * omega[None, :] for v in [z, y, x, t]]
|
| 49 |
+
|
| 50 |
+
pe = torch.cat(
|
| 51 |
+
(z.sin(), z.cos(), y.sin(), y.cos(), x.sin(), x.cos(), t.sin(), t.cos()), dim=1
|
| 52 |
+
)
|
| 53 |
+
pe = F.pad(pe, (0, dim - (fourier_dim * 8)))
|
| 54 |
+
return pe.type(dtype)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class Block(nn.Module):
|
| 59 |
+
def __init__(
|
| 60 |
+
self, dim, mixer_cls, norm_cls=nn.LayerNorm, fused_add_norm=False, residual_in_fp32=False,drop_path=0.,
|
| 61 |
+
):
|
| 62 |
+
"""
|
| 63 |
+
Simple block wrapping a mixer class with LayerNorm/RMSNorm and residual connection"
|
| 64 |
+
|
| 65 |
+
This Block has a slightly different structure compared to a regular
|
| 66 |
+
prenorm Transformer block.
|
| 67 |
+
The standard block is: LN -> MHA/MLP -> Add.
|
| 68 |
+
[Ref: https://arxiv.org/abs/2002.04745]
|
| 69 |
+
Here we have: Add -> LN -> Mixer, returning both
|
| 70 |
+
the hidden_states (output of the mixer) and the residual.
|
| 71 |
+
This is purely for performance reasons, as we can fuse add and LayerNorm.
|
| 72 |
+
The residual needs to be provided (except for the very first block).
|
| 73 |
+
"""
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.residual_in_fp32 = residual_in_fp32
|
| 76 |
+
self.fused_add_norm = fused_add_norm
|
| 77 |
+
self.mixer = mixer_cls(dim)
|
| 78 |
+
self.norm = norm_cls(dim)
|
| 79 |
+
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
| 80 |
+
if self.fused_add_norm:
|
| 81 |
+
assert RMSNorm is not None, "RMSNorm import fails"
|
| 82 |
+
assert isinstance(
|
| 83 |
+
self.norm, (nn.LayerNorm, RMSNorm)
|
| 84 |
+
), "Only LayerNorm and RMSNorm are supported for fused_add_norm"
|
| 85 |
+
|
| 86 |
+
def forward(
|
| 87 |
+
self, hidden_states: Tensor, residual: Optional[Tensor] = None, inference_params=None,
|
| 88 |
+
use_checkpoint=False
|
| 89 |
+
):
|
| 90 |
+
r"""Pass the input through the encoder layer.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
hidden_states: the sequence to the encoder layer (required).
|
| 94 |
+
residual: hidden_states = Mixer(LN(residual))
|
| 95 |
+
"""
|
| 96 |
+
if not self.fused_add_norm:
|
| 97 |
+
residual = (residual + self.drop_path(hidden_states)) if residual is not None else hidden_states
|
| 98 |
+
hidden_states = self.norm(residual.to(dtype=self.norm.weight.dtype))
|
| 99 |
+
if self.residual_in_fp32:
|
| 100 |
+
residual = residual.to(torch.float32)
|
| 101 |
+
else:
|
| 102 |
+
fused_add_norm_fn = rms_norm_fn if isinstance(self.norm, RMSNorm) else layer_norm_fn
|
| 103 |
+
hidden_states, residual = fused_add_norm_fn(
|
| 104 |
+
hidden_states if residual is None else self.drop_path(hidden_states),
|
| 105 |
+
self.norm.weight,
|
| 106 |
+
self.norm.bias,
|
| 107 |
+
residual=residual,
|
| 108 |
+
prenorm=True,
|
| 109 |
+
residual_in_fp32=self.residual_in_fp32,
|
| 110 |
+
eps=self.norm.eps,
|
| 111 |
+
)
|
| 112 |
+
if use_checkpoint:
|
| 113 |
+
hidden_states = checkpoint.checkpoint(self.mixer, hidden_states, inference_params)
|
| 114 |
+
else:
|
| 115 |
+
hidden_states = self.mixer(hidden_states, inference_params=inference_params)
|
| 116 |
+
return hidden_states, residual
|
| 117 |
+
|
| 118 |
+
def allocate_inference_cache(self, batch_size, max_seqlen, dtype=None, **kwargs):
|
| 119 |
+
return self.mixer.allocate_inference_cache(batch_size, max_seqlen, dtype=dtype, **kwargs)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def create_block(
|
| 123 |
+
d_model,
|
| 124 |
+
ssm_cfg=None,
|
| 125 |
+
norm_epsilon=1e-5,
|
| 126 |
+
drop_path=0.,
|
| 127 |
+
rms_norm=True,
|
| 128 |
+
residual_in_fp32=True,
|
| 129 |
+
fused_add_norm=True,
|
| 130 |
+
layer_idx=None,
|
| 131 |
+
bimamba=True,
|
| 132 |
+
device=None,
|
| 133 |
+
dtype=None,
|
| 134 |
+
):
|
| 135 |
+
import inspect
|
| 136 |
+
inspect.getsourcefile(Mamba)
|
| 137 |
+
factory_kwargs = {"device": device, "dtype": dtype}
|
| 138 |
+
if ssm_cfg is None:
|
| 139 |
+
ssm_cfg = {}
|
| 140 |
+
mixer_cls = partial(Mamba, layer_idx=layer_idx, bimamba=bimamba, **ssm_cfg, **factory_kwargs)
|
| 141 |
+
norm_cls = partial(nn.LayerNorm if not rms_norm else RMSNorm, eps=norm_epsilon)
|
| 142 |
+
block = Block(
|
| 143 |
+
d_model,
|
| 144 |
+
mixer_cls,
|
| 145 |
+
norm_cls=norm_cls,
|
| 146 |
+
drop_path=drop_path,
|
| 147 |
+
fused_add_norm=fused_add_norm,
|
| 148 |
+
residual_in_fp32=residual_in_fp32,
|
| 149 |
+
)
|
| 150 |
+
block.layer_idx = layer_idx
|
| 151 |
+
return block
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# https://github.com/huggingface/transformers/blob/c28d04e9e252a1a099944e325685f14d242ecdcd/src/transformers/models/gpt2/modeling_gpt2.py#L454
|
| 155 |
+
def _init_weights(
|
| 156 |
+
module,
|
| 157 |
+
n_layer,
|
| 158 |
+
initializer_range=0.02, # Now only used for embedding layer.
|
| 159 |
+
rescale_prenorm_residual=True,
|
| 160 |
+
n_residuals_per_layer=1, # Change to 2 if we have MLP
|
| 161 |
+
):
|
| 162 |
+
if isinstance(module, nn.Linear):
|
| 163 |
+
if module.bias is not None:
|
| 164 |
+
if not getattr(module.bias, "_no_reinit", False):
|
| 165 |
+
nn.init.zeros_(module.bias)
|
| 166 |
+
elif isinstance(module, nn.Embedding):
|
| 167 |
+
nn.init.normal_(module.weight, std=initializer_range)
|
| 168 |
+
|
| 169 |
+
if rescale_prenorm_residual:
|
| 170 |
+
# Reinitialize selected weights subject to the OpenAI GPT-2 Paper Scheme:
|
| 171 |
+
# > A modified initialization which accounts for the accumulation on the residual path with model depth. Scale
|
| 172 |
+
# > the weights of residual layers at initialization by a factor of 1/√N where N is the # of residual layers.
|
| 173 |
+
# > -- GPT-2 :: https://openai.com/blog/better-language-models/
|
| 174 |
+
#
|
| 175 |
+
# Reference (Megatron-LM): https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/model/gpt_model.py
|
| 176 |
+
for name, p in module.named_parameters():
|
| 177 |
+
if name in ["out_proj.weight", "fc2.weight"]:
|
| 178 |
+
# Special Scaled Initialization --> There are 2 Layer Norms per Transformer Block
|
| 179 |
+
# Following Pytorch init, except scale by 1/sqrt(2 * n_layer)
|
| 180 |
+
# We need to reinit p since this code could be called multiple times
|
| 181 |
+
# Having just p *= scale would repeatedly scale it down
|
| 182 |
+
nn.init.kaiming_uniform_(p, a=math.sqrt(5))
|
| 183 |
+
with torch.no_grad():
|
| 184 |
+
p /= math.sqrt(n_residuals_per_layer * n_layer)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def segm_init_weights(m):
|
| 188 |
+
if isinstance(m, nn.Linear):
|
| 189 |
+
trunc_normal_(m.weight, std=0.02)
|
| 190 |
+
if isinstance(m, nn.Linear) and m.bias is not None:
|
| 191 |
+
nn.init.constant_(m.bias, 0)
|
| 192 |
+
elif isinstance(m, nn.LayerNorm):
|
| 193 |
+
nn.init.constant_(m.bias, 0)
|
| 194 |
+
nn.init.constant_(m.weight, 1.0)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
class PatchEmbed(nn.Module):
|
| 198 |
+
""" Image to Patch Embedding
|
| 199 |
+
"""
|
| 200 |
+
def __init__(self, img_size=224, patch_size=16, kernel_size=1, in_chans=3, embed_dim=768):
|
| 201 |
+
super().__init__()
|
| 202 |
+
img_size = to_2tuple(img_size)
|
| 203 |
+
patch_size = to_2tuple(patch_size)
|
| 204 |
+
num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])
|
| 205 |
+
self.img_size = img_size
|
| 206 |
+
self.patch_size = patch_size
|
| 207 |
+
self.num_patches = num_patches
|
| 208 |
+
self.tubelet_size = kernel_size
|
| 209 |
+
|
| 210 |
+
self.proj = nn.Conv3d(
|
| 211 |
+
in_chans, embed_dim,
|
| 212 |
+
kernel_size=(kernel_size, patch_size[0], patch_size[1]),
|
| 213 |
+
stride=(kernel_size, patch_size[0], patch_size[1])
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
def forward(self, x):
|
| 217 |
+
x = self.proj(x)
|
| 218 |
+
return x
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
# sin-cos position encoding
|
| 222 |
+
# https://github.com/jadore801120/attention-is-all-you-need-pytorch/blob/master/transformer/Models.py#L31
|
| 223 |
+
def get_sinusoid_encoding_table(n_position, d_hid):
|
| 224 |
+
''' Sinusoid position encoding table '''
|
| 225 |
+
# TODO: make it with torch instead of numpy
|
| 226 |
+
def get_position_angle_vec(position):
|
| 227 |
+
return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]
|
| 228 |
+
|
| 229 |
+
sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
|
| 230 |
+
sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i
|
| 231 |
+
sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1
|
| 232 |
+
|
| 233 |
+
return torch.tensor(sinusoid_table, dtype=torch.float, requires_grad=False).unsqueeze(0)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
class VisionMamba(nn.Module):
|
| 237 |
+
def __init__(
|
| 238 |
+
self,
|
| 239 |
+
img_size=224,
|
| 240 |
+
patch_size=16,
|
| 241 |
+
frame_patch_size=4,
|
| 242 |
+
depth=24,
|
| 243 |
+
embed_dim=192,
|
| 244 |
+
channels=3,
|
| 245 |
+
drop_rate=0.,
|
| 246 |
+
drop_path_rate=0.1,
|
| 247 |
+
ssm_cfg=None,
|
| 248 |
+
norm_epsilon=1e-5,
|
| 249 |
+
initializer_cfg=None,
|
| 250 |
+
fused_add_norm=True,
|
| 251 |
+
rms_norm=True,
|
| 252 |
+
residual_in_fp32=True,
|
| 253 |
+
bimamba=True,
|
| 254 |
+
# video
|
| 255 |
+
kernel_size=1,
|
| 256 |
+
num_frames=8,
|
| 257 |
+
use_rope_emb=False,
|
| 258 |
+
device=None,
|
| 259 |
+
dtype=None,
|
| 260 |
+
# checkpoint
|
| 261 |
+
use_checkpoint=False,
|
| 262 |
+
checkpoint_num=0,
|
| 263 |
+
):
|
| 264 |
+
factory_kwargs = {"device": device, "dtype": dtype} # follow MambaLMHeadModel
|
| 265 |
+
super().__init__()
|
| 266 |
+
self.residual_in_fp32 = residual_in_fp32
|
| 267 |
+
self.fused_add_norm = fused_add_norm
|
| 268 |
+
self.use_checkpoint = use_checkpoint
|
| 269 |
+
self.checkpoint_num = checkpoint_num
|
| 270 |
+
print(f'Use checkpoint: {use_checkpoint}')
|
| 271 |
+
print(f'Checkpoint number: {checkpoint_num}')
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# pretrain parameters
|
| 275 |
+
self.d_model = self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
|
| 276 |
+
patch_depth, patch_height, patch_width = patch_size
|
| 277 |
+
self.patchify = Rearrange(
|
| 278 |
+
"b c (f pf) (d pd) (h ph) (w pw) -> b f d h w (pd ph pw pf c)",
|
| 279 |
+
pd=patch_depth,
|
| 280 |
+
ph=patch_height,
|
| 281 |
+
pw=patch_width,
|
| 282 |
+
pf=frame_patch_size,
|
| 283 |
+
)
|
| 284 |
+
# self.patch_embed = PatchEmbed(
|
| 285 |
+
# img_size=img_size, patch_size=patch_size,
|
| 286 |
+
# kernel_size=kernel_size,
|
| 287 |
+
# in_chans=channels, embed_dim=embed_dim
|
| 288 |
+
# )
|
| 289 |
+
self.patch_dim = channels * patch_depth * patch_height * patch_width * frame_patch_size
|
| 290 |
+
self.patch_to_emb = nn.Sequential(
|
| 291 |
+
nn.LayerNorm(self.patch_dim),
|
| 292 |
+
nn.Linear(self.patch_dim, self.embed_dim),
|
| 293 |
+
nn.LayerNorm(self.embed_dim),
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
self.encoder_proj = nn.Sequential(
|
| 297 |
+
nn.LayerNorm(self.embed_dim),
|
| 298 |
+
nn.GELU(),
|
| 299 |
+
nn.Linear(self.embed_dim, self.embed_dim),
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
self.decoder_proj = nn.Sequential(
|
| 303 |
+
nn.LayerNorm(self.embed_dim),
|
| 304 |
+
nn.GELU(),
|
| 305 |
+
nn.Linear(self.embed_dim, self.patch_dim),
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
self.cls_token = nn.Parameter(torch.randn(1, 1, self.embed_dim))
|
| 309 |
+
image_depth, image_height, image_width = img_size
|
| 310 |
+
self.use_rope_emb = use_rope_emb
|
| 311 |
+
self.posemb_sincos_4d = posemb_sincos_4d(
|
| 312 |
+
torch.zeros(
|
| 313 |
+
1,
|
| 314 |
+
num_frames,
|
| 315 |
+
image_depth // patch_depth,
|
| 316 |
+
image_height // patch_height,
|
| 317 |
+
image_width // patch_width,
|
| 318 |
+
self.embed_dim,
|
| 319 |
+
)
|
| 320 |
+
).to(device)
|
| 321 |
+
print ("posemb_sincos_4d", self.posemb_sincos_4d.shape)
|
| 322 |
+
self.encoder_to_decoder = nn.Linear(self.embed_dim, self.embed_dim, bias=False)
|
| 323 |
+
self.mask_token = nn.Parameter(torch.zeros(1, 1, self.embed_dim))
|
| 324 |
+
|
| 325 |
+
# self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, self.embed_dim))
|
| 326 |
+
# self.temporal_pos_embedding = nn.Parameter(torch.zeros(1, num_frames // kernel_size, embed_dim))
|
| 327 |
+
self.pos_drop = nn.Dropout(p=drop_rate)
|
| 328 |
+
|
| 329 |
+
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
|
| 330 |
+
inter_dpr = [0.0] + dpr
|
| 331 |
+
self.drop_path = DropPath(drop_path_rate) if drop_path_rate > 0. else nn.Identity()
|
| 332 |
+
# mamba blocks
|
| 333 |
+
print ("depth", depth, "d_model", embed_dim, "rms_norm", rms_norm, "residual_in_fp32", residual_in_fp32, "fused_add_norm", fused_add_norm, "bimamba", bimamba, "ssm_cfg", ssm_cfg)
|
| 334 |
+
self.encoder_layers = nn.ModuleList(
|
| 335 |
+
[
|
| 336 |
+
create_block(
|
| 337 |
+
embed_dim,
|
| 338 |
+
ssm_cfg=ssm_cfg,
|
| 339 |
+
norm_epsilon=norm_epsilon,
|
| 340 |
+
rms_norm=rms_norm,
|
| 341 |
+
residual_in_fp32=residual_in_fp32,
|
| 342 |
+
fused_add_norm=fused_add_norm,
|
| 343 |
+
layer_idx=i,
|
| 344 |
+
bimamba=bimamba,
|
| 345 |
+
drop_path=inter_dpr[i],
|
| 346 |
+
**factory_kwargs,
|
| 347 |
+
)
|
| 348 |
+
for i in range(depth)
|
| 349 |
+
]
|
| 350 |
+
)
|
| 351 |
+
self.decoder_layers = nn.ModuleList(
|
| 352 |
+
[
|
| 353 |
+
create_block(
|
| 354 |
+
embed_dim,
|
| 355 |
+
ssm_cfg=ssm_cfg,
|
| 356 |
+
norm_epsilon=norm_epsilon,
|
| 357 |
+
rms_norm=rms_norm,
|
| 358 |
+
residual_in_fp32=residual_in_fp32,
|
| 359 |
+
fused_add_norm=fused_add_norm,
|
| 360 |
+
layer_idx=i,
|
| 361 |
+
bimamba=bimamba,
|
| 362 |
+
drop_path=inter_dpr[i],
|
| 363 |
+
**factory_kwargs,
|
| 364 |
+
)
|
| 365 |
+
for i in range(depth)
|
| 366 |
+
]
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
# output head
|
| 370 |
+
self.norm_f = (nn.LayerNorm if not rms_norm else RMSNorm)(embed_dim, eps=norm_epsilon, **factory_kwargs)
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
# original init
|
| 374 |
+
self.apply(segm_init_weights)
|
| 375 |
+
# trunc_normal_(self.pos_embed, std=.02)
|
| 376 |
+
|
| 377 |
+
# mamba init
|
| 378 |
+
self.apply(
|
| 379 |
+
partial(
|
| 380 |
+
_init_weights,
|
| 381 |
+
n_layer=depth,
|
| 382 |
+
**(initializer_cfg if initializer_cfg is not None else {}),
|
| 383 |
+
)
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
def allocate_inference_cache(self, batch_size, max_seqlen, dtype=None, **kwargs):
|
| 387 |
+
return {
|
| 388 |
+
i: layer.allocate_inference_cache(batch_size, max_seqlen, dtype=dtype, **kwargs)
|
| 389 |
+
for i, layer in enumerate(self.layers)
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
@torch.jit.ignore
|
| 393 |
+
def no_weight_decay(self):
|
| 394 |
+
return {"pos_embed", "cls_token", "temporal_pos_embedding"}
|
| 395 |
+
|
| 396 |
+
def get_num_layers(self):
|
| 397 |
+
return (len(self.encoder_layers), len(self.decoder_layers))
|
| 398 |
+
|
| 399 |
+
@torch.jit.ignore()
|
| 400 |
+
def load_pretrained(self, checkpoint_path, prefix=""):
|
| 401 |
+
_load_weights(self, checkpoint_path, prefix)
|
| 402 |
+
|
| 403 |
+
def forward(self, x, inference_params=None, encoder_mask=None, decoder_mask=None, verbose=False):
|
| 404 |
+
# x = self.forward_features(x, inference_params, encoder_mask, decoder_mask, verbose)
|
| 405 |
+
# # x = self.head(self.head_drop(x))
|
| 406 |
+
|
| 407 |
+
# ENCODER
|
| 408 |
+
if decoder_mask is None:
|
| 409 |
+
if verbose: print(x.shape)
|
| 410 |
+
x = self.patchify(x)
|
| 411 |
+
if verbose: print("patched", x.shape)
|
| 412 |
+
x = self.patch_to_emb(x)
|
| 413 |
+
if verbose: print("patched_emb", x.shape)
|
| 414 |
+
x = rearrange(x, "b ... d -> b (...) d")
|
| 415 |
+
if verbose: print("reshaped", x.shape)
|
| 416 |
+
if not self.use_rope_emb:
|
| 417 |
+
if verbose: print("pe", self.posemb_sincos_4d.shape)
|
| 418 |
+
x = x + self.posemb_sincos_4d
|
| 419 |
+
if verbose: print("x", x.shape)
|
| 420 |
+
x = x[:, encoder_mask]
|
| 421 |
+
# if self.use_cls_token:
|
| 422 |
+
cls_tokens = self.cls_token.expand(len(x), -1, -1)
|
| 423 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 424 |
+
if verbose: print("masked", x.shape)
|
| 425 |
+
|
| 426 |
+
# Mamba below
|
| 427 |
+
residual = None
|
| 428 |
+
hidden_states = x
|
| 429 |
+
for idx, layer in enumerate(self.encoder_layers):
|
| 430 |
+
if self.use_checkpoint and idx < self.checkpoint_num:
|
| 431 |
+
hidden_states, residual = layer(
|
| 432 |
+
hidden_states, residual, inference_params=inference_params,
|
| 433 |
+
use_checkpoint=True
|
| 434 |
+
)
|
| 435 |
+
else:
|
| 436 |
+
hidden_states, residual = layer(
|
| 437 |
+
hidden_states, residual, inference_params=inference_params
|
| 438 |
+
)
|
| 439 |
+
# assuming fused_add_norm is True
|
| 440 |
+
fused_add_norm_fn = rms_norm_fn if isinstance(self.norm_f, RMSNorm) else layer_norm_fn
|
| 441 |
+
hidden_states = fused_add_norm_fn(
|
| 442 |
+
self.drop_path(hidden_states),
|
| 443 |
+
self.norm_f.weight,
|
| 444 |
+
self.norm_f.bias,
|
| 445 |
+
eps=self.norm_f.eps,
|
| 446 |
+
residual=residual,
|
| 447 |
+
prenorm=False, # Set prenorm=False here since we don't need the residual
|
| 448 |
+
residual_in_fp32=self.residual_in_fp32,
|
| 449 |
+
)
|
| 450 |
+
else: # DECODER
|
| 451 |
+
if verbose: print(x.shape)
|
| 452 |
+
x = self.encoder_to_decoder(x)
|
| 453 |
+
B, _, _ = x.shape
|
| 454 |
+
N = decoder_mask.sum()
|
| 455 |
+
mask = None
|
| 456 |
+
if not self.use_rope_emb:
|
| 457 |
+
pos_embed = self.posemb_sincos_4d
|
| 458 |
+
if verbose: print("pe", pos_embed.shape)
|
| 459 |
+
pos_emd_encoder = pos_embed[encoder_mask]
|
| 460 |
+
pos_emd_decoder = pos_embed[decoder_mask]
|
| 461 |
+
if verbose: print("pos_emd_encoder", pos_emd_encoder.shape)
|
| 462 |
+
if verbose: print("pos_emd_decoder", pos_emd_decoder.shape)
|
| 463 |
+
# if self.use_cls_token:
|
| 464 |
+
cls_tokens = x[:,:1,:]
|
| 465 |
+
x = x[:,1:,:]
|
| 466 |
+
x = torch.cat([x + pos_emd_encoder,
|
| 467 |
+
self.mask_token.repeat(B, N, 1) + pos_emd_decoder],
|
| 468 |
+
dim=1)
|
| 469 |
+
# if self.use_cls_token:
|
| 470 |
+
x = torch.cat([cls_tokens, x], dim=1)
|
| 471 |
+
else:
|
| 472 |
+
mask = torch.cat((torch.where(encoder_mask)[0], torch.where(decoder_mask)[0]))
|
| 473 |
+
# No abs positional embeddings for RoPE
|
| 474 |
+
x = torch.cat([x,self.mask_token.repeat(B, N-1, 1)],dim=1) # N-1 due to cls token
|
| 475 |
+
|
| 476 |
+
# Mamba below
|
| 477 |
+
residual = None
|
| 478 |
+
hidden_states = x
|
| 479 |
+
for idx, layer in enumerate(self.decoder_layers):
|
| 480 |
+
if self.use_checkpoint and idx < self.checkpoint_num:
|
| 481 |
+
hidden_states, residual = layer(
|
| 482 |
+
hidden_states, residual, inference_params=inference_params,
|
| 483 |
+
use_checkpoint=True
|
| 484 |
+
)
|
| 485 |
+
else:
|
| 486 |
+
hidden_states, residual = layer(
|
| 487 |
+
hidden_states, residual, inference_params=inference_params
|
| 488 |
+
)
|
| 489 |
+
# assuming fused_add_norm is True
|
| 490 |
+
fused_add_norm_fn = rms_norm_fn if isinstance(self.norm_f, RMSNorm) else layer_norm_fn
|
| 491 |
+
hidden_states = fused_add_norm_fn(
|
| 492 |
+
self.drop_path(hidden_states),
|
| 493 |
+
self.norm_f.weight,
|
| 494 |
+
self.norm_f.bias,
|
| 495 |
+
eps=self.norm_f.eps,
|
| 496 |
+
residual=residual,
|
| 497 |
+
prenorm=False, # Set prenorm=False here since we don't need the residual
|
| 498 |
+
residual_in_fp32=self.residual_in_fp32,
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
if decoder_mask is None:
|
| 502 |
+
return self.encoder_proj(hidden_states)
|
| 503 |
+
else:
|
| 504 |
+
return self.decoder_proj(hidden_states)
|
fMRI-foundation-model-old/fMRI-MAE-mamba/utils.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from io import BytesIO
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
from nilearn import plotting
|
| 8 |
+
from PIL import Image
|
| 9 |
+
from skimage import filters
|
| 10 |
+
from torchvision import transforms
|
| 11 |
+
import nibabel as nib
|
| 12 |
+
from nilearn import plotting
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
import re
|
| 15 |
+
|
| 16 |
+
def my_split_by_node(urls): return urls
|
| 17 |
+
|
| 18 |
+
def is_interactive():
|
| 19 |
+
import __main__ as main
|
| 20 |
+
|
| 21 |
+
return not hasattr(main, "__file__")
|
| 22 |
+
|
| 23 |
+
def my_split_by_node(urls): return urls
|
| 24 |
+
|
| 25 |
+
def seed_everything(seed=0, cudnn_deterministic=True):
|
| 26 |
+
random.seed(seed)
|
| 27 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 28 |
+
np.random.seed(seed)
|
| 29 |
+
torch.manual_seed(seed)
|
| 30 |
+
torch.cuda.manual_seed(seed)
|
| 31 |
+
torch.cuda.manual_seed_all(seed)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def grayscale_decoder(image_data):
|
| 35 |
+
return np.array(Image.open(BytesIO(image_data))).astype(np.float32) / 65535
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def numpy_decoder(npy_data):
|
| 39 |
+
return np.load(BytesIO(npy_data))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def reshape_to_2d(tensor):
|
| 43 |
+
if tensor.ndim == 5:
|
| 44 |
+
tensor = tensor[0]
|
| 45 |
+
assert tensor.ndim == 4
|
| 46 |
+
return rearrange(tensor, "b h w c -> (b h) (c w)")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def reshape_to_original(tensor_2d, h=64, w=64, c=48):
|
| 50 |
+
# print(tensor_2d.shape) # torch.Size([1, 256, 3072])
|
| 51 |
+
return rearrange(tensor_2d, "(tr h) (c w) -> tr h w c", h=h, w=w, c=c)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def plot_numpy_nii(image):
|
| 55 |
+
while image.ndim > 3:
|
| 56 |
+
image = image[0]
|
| 57 |
+
nii = nib.Nifti1Image(image.astype(np.float32), np.eye(4)) # noqa
|
| 58 |
+
plotting.plot_epi(nii, cmap="gray")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def threshold_based_masking(org_images):
|
| 62 |
+
thresholds = filters.threshold_multiotsu(org_images.numpy(), classes=3)
|
| 63 |
+
brain_segmentation = org_images > thresholds.min()
|
| 64 |
+
return brain_segmentation
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_brain_pos_patches(
|
| 68 |
+
func,
|
| 69 |
+
patch_depth=8,
|
| 70 |
+
patch_height=8,
|
| 71 |
+
patch_width=8,
|
| 72 |
+
frame_patch_size=1,
|
| 73 |
+
masking_strategy="conservative",
|
| 74 |
+
):
|
| 75 |
+
_, _, depth = func.shape
|
| 76 |
+
if masking_strategy == "conservative":
|
| 77 |
+
func = func.sum(axis=(-1), keepdim=True).repeat(1, 1, depth)
|
| 78 |
+
else:
|
| 79 |
+
raise Exception("Not implemented other masking strategies than conservative.")
|
| 80 |
+
|
| 81 |
+
return func
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class DataPrepper:
|
| 85 |
+
def __init__(
|
| 86 |
+
self,
|
| 87 |
+
num_frames=4,
|
| 88 |
+
masking_strategy="MNI",
|
| 89 |
+
patch_depth=8,
|
| 90 |
+
patch_height=8,
|
| 91 |
+
patch_width=8,
|
| 92 |
+
frame_patch_size=1,
|
| 93 |
+
):
|
| 94 |
+
self.num_frames = num_frames
|
| 95 |
+
self.masking_strategy = masking_strategy
|
| 96 |
+
self.patch_depth = 8
|
| 97 |
+
self.patch_height = 8
|
| 98 |
+
self.patch_width = 8
|
| 99 |
+
self.frame_patch_size = 1
|
| 100 |
+
|
| 101 |
+
def __call__(self, func):
|
| 102 |
+
start_timepoint = np.random.choice(np.arange(func.shape[1] - self.num_frames))
|
| 103 |
+
timepoints = np.arange(start_timepoint, start_timepoint + self.num_frames)
|
| 104 |
+
|
| 105 |
+
func = func[:,timepoints]
|
| 106 |
+
|
| 107 |
+
if self.masking_strategy=="MNI":
|
| 108 |
+
return func, None
|
| 109 |
+
|
| 110 |
+
brain_segmentation = threshold_based_masking(func.mean(1))
|
| 111 |
+
pos_patches = None
|
| 112 |
+
for brain in brain_segmentation:
|
| 113 |
+
output = get_brain_pos_patches(
|
| 114 |
+
brain,
|
| 115 |
+
patch_depth=self.patch_depth,
|
| 116 |
+
patch_height=self.patch_height,
|
| 117 |
+
patch_width=self.patch_width,
|
| 118 |
+
frame_patch_size=self.frame_patch_size,
|
| 119 |
+
masking_strategy=self.masking_strategy,
|
| 120 |
+
)
|
| 121 |
+
if pos_patches is None:
|
| 122 |
+
pos_patches = output[None]
|
| 123 |
+
else:
|
| 124 |
+
pos_patches = torch.vstack((pos_patches, output[None]))
|
| 125 |
+
return func, pos_patches
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def plot_slices(unpatches):
|
| 129 |
+
if unpatches.ndim == 5:
|
| 130 |
+
unpatches = unpatches[0]
|
| 131 |
+
return transforms.ToPILImage()(reshape_to_2d(unpatches))
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def check_loss(loss):
|
| 135 |
+
if loss.isnan().any():
|
| 136 |
+
raise ValueError('NaN loss')
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def count_params(model):
|
| 140 |
+
total = sum(p.numel() for p in model.parameters())
|
| 141 |
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 142 |
+
print("param counts:\n{:,} total\n{:,} trainable".format(total, trainable))
|
| 143 |
+
return trainable
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def contrastive_loss(
|
| 147 |
+
cls_token1: torch.Tensor, cls_token2: torch.Tensor, temperature: torch.Tensor
|
| 148 |
+
):
|
| 149 |
+
feat1 = cls_token1 / cls_token1.norm(dim=1, keepdim=True)
|
| 150 |
+
feat2 = cls_token2 / cls_token2.norm(dim=1, keepdim=True)
|
| 151 |
+
|
| 152 |
+
cosine_sim = feat1 @ feat2.T
|
| 153 |
+
logit_scale = temperature.exp() # log scale, learned during training
|
| 154 |
+
feat1 = cosine_sim * logit_scale
|
| 155 |
+
feat2 = feat1.T
|
| 156 |
+
|
| 157 |
+
labels = torch.arange(feat1.shape[0]).to(feat1.device)
|
| 158 |
+
loss = (
|
| 159 |
+
torch.nn.functional.cross_entropy(feat1, labels)
|
| 160 |
+
+ torch.nn.functional.cross_entropy(feat2, labels)
|
| 161 |
+
) / 2
|
| 162 |
+
return loss
|
| 163 |
+
|
| 164 |
+
### MindEye functions ###
|
| 165 |
+
|
| 166 |
+
def soft_clip_loss(preds, targs, temp=0.006):
|
| 167 |
+
clip_clip = (targs @ targs.T)/temp
|
| 168 |
+
brain_clip = (preds @ targs.T)/temp
|
| 169 |
+
loss1 = -(brain_clip.log_softmax(-1) * clip_clip.softmax(-1)).sum(-1).mean()
|
| 170 |
+
loss2 = -(brain_clip.T.log_softmax(-1) * clip_clip.softmax(-1)).sum(-1).mean()
|
| 171 |
+
|
| 172 |
+
loss = (loss1 + loss2)/2
|
| 173 |
+
return loss
|
| 174 |
+
|
| 175 |
+
def cosine_anneal(start, end, steps):
|
| 176 |
+
return end + (start - end)/2 * (1 + torch.cos(torch.pi*torch.arange(steps)/(steps-1)))
|
| 177 |
+
|
| 178 |
+
def mixco(voxels, beta=0.15, s_thresh=0.5, perm=None, betas=None, select=None):
|
| 179 |
+
if perm is None:
|
| 180 |
+
perm = torch.randperm(voxels.shape[0])
|
| 181 |
+
voxels_shuffle = voxels[perm].to(voxels.device,dtype=voxels.dtype)
|
| 182 |
+
if betas is None:
|
| 183 |
+
betas = torch.distributions.Beta(beta, beta).sample([voxels.shape[0]]).to(voxels.device,dtype=voxels.dtype)
|
| 184 |
+
if select is None:
|
| 185 |
+
select = (torch.rand(voxels.shape[0]) <= s_thresh).to(voxels.device)
|
| 186 |
+
betas_shape = [-1] + [1]*(len(voxels.shape)-1)
|
| 187 |
+
voxels[select] = voxels[select] * betas[select].reshape(*betas_shape) + \
|
| 188 |
+
voxels_shuffle[select] * (1 - betas[select]).reshape(*betas_shape)
|
| 189 |
+
betas[~select] = 1
|
| 190 |
+
return voxels, perm, betas, select
|
| 191 |
+
|
| 192 |
+
def mixco_nce(preds, targs, temp=0.1, perm=None, betas=None, select=None, distributed=False,
|
| 193 |
+
accelerator=None, local_rank=None, bidirectional=True):
|
| 194 |
+
brain_clip = (preds @ targs.T)/temp
|
| 195 |
+
|
| 196 |
+
if perm is not None and betas is not None and select is not None:
|
| 197 |
+
probs = torch.diag(betas)
|
| 198 |
+
probs[torch.arange(preds.shape[0]).to(preds.device), perm] = 1 - betas
|
| 199 |
+
|
| 200 |
+
loss = -(brain_clip.log_softmax(-1) * probs).sum(-1).mean()
|
| 201 |
+
if bidirectional:
|
| 202 |
+
loss2 = -(brain_clip.T.log_softmax(-1) * probs.T).sum(-1).mean()
|
| 203 |
+
loss = (loss + loss2)/2
|
| 204 |
+
return loss
|
| 205 |
+
else:
|
| 206 |
+
loss = F.cross_entropy(brain_clip, torch.arange(brain_clip.shape[0]).to(brain_clip.device))
|
| 207 |
+
if bidirectional:
|
| 208 |
+
loss2 = F.cross_entropy(brain_clip.T, torch.arange(brain_clip.shape[0]).to(brain_clip.device))
|
| 209 |
+
loss = (loss + loss2)/2
|
| 210 |
+
return loss
|
| 211 |
+
|
| 212 |
+
def topk(similarities,labels,k=5):
|
| 213 |
+
if k > similarities.shape[0]:
|
| 214 |
+
k = similarities.shape[0]
|
| 215 |
+
topsum=0
|
| 216 |
+
for i in range(k):
|
| 217 |
+
topsum += torch.sum(torch.argsort(similarities,axis=1)[:,-(i+1)] == labels)/len(labels)
|
| 218 |
+
return topsum
|
| 219 |
+
|
| 220 |
+
def batchwise_cosine_similarity(Z,B):
|
| 221 |
+
Z = Z.flatten(1)
|
| 222 |
+
B = B.flatten(1).T
|
| 223 |
+
Z_norm = torch.linalg.norm(Z, dim=1, keepdim=True) # Size (n, 1).
|
| 224 |
+
B_norm = torch.linalg.norm(B, dim=0, keepdim=True) # Size (1, b).
|
| 225 |
+
cosine_similarity = ((Z @ B) / (Z_norm @ B_norm)).T
|
| 226 |
+
return cosine_similarity
|
| 227 |
+
|
| 228 |
+
def prenormed_batchwise_cosine_similarity(Z,B):
|
| 229 |
+
return (Z @ B.T).T
|
| 230 |
+
|
| 231 |
+
def torch_to_Image(x):
|
| 232 |
+
if x.ndim==4:
|
| 233 |
+
x=x[0]
|
| 234 |
+
return transforms.ToPILImage()(x)
|
| 235 |
+
|
| 236 |
+
def get_masking_ratio(current_epoch, total_epochs, start_masking_ratio, end_masking_ratio):
|
| 237 |
+
"""Returns the masking ratio for the current epochs. Linearly increase the masking ratio over the span of the training"""
|
| 238 |
+
return start_masking_ratio + (end_masking_ratio-start_masking_ratio) * ((current_epoch+1)/total_epochs)
|
| 239 |
+
|
| 240 |
+
def view_brain(data):
|
| 241 |
+
if torch.is_tensor(data):
|
| 242 |
+
data = data.numpy()
|
| 243 |
+
if data.ndim==5:
|
| 244 |
+
new_nii = nib.Nifti1Image((data[0,0].astype(np.float32)-.5)*2, np.eye(4))
|
| 245 |
+
elif data.ndim==4:
|
| 246 |
+
new_nii = nib.Nifti1Image((data[0].astype(np.float32)-.5)*2, np.eye(4))
|
| 247 |
+
elif data.ndim==3:
|
| 248 |
+
new_nii = nib.Nifti1Image((data.astype(np.float32)-.5)*2, np.eye(4))
|
| 249 |
+
else:
|
| 250 |
+
raise Exception("Check dimensionality of your brain data")
|
| 251 |
+
return plotting.view_img(new_nii, bg_img=None, vmax=1, cmap=plt.cm.gray, threshold=None)
|
| 252 |
+
|
| 253 |
+
def get_first_tar(train_urls):
|
| 254 |
+
if isinstance(train_urls, list):
|
| 255 |
+
# If train_urls is a list, get the first element
|
| 256 |
+
url = train_urls[0]
|
| 257 |
+
else:
|
| 258 |
+
# If train_urls is a string, treat it as the only element
|
| 259 |
+
url = train_urls
|
| 260 |
+
|
| 261 |
+
# Extract the first tar file using regular expression
|
| 262 |
+
match = re.search(r'\{(\d+)\.\.', url)
|
| 263 |
+
if match:
|
| 264 |
+
first_tar = match.group(1)
|
| 265 |
+
return f"/scratch/fmri_foundation_datasets/NSD_MNI_wds/{first_tar}.tar"
|
| 266 |
+
else:
|
| 267 |
+
return None
|
fMRI-foundation-model-old/fMRI-MAE/.ipynb_checkpoints/main-checkpoint.ipynb
ADDED
|
@@ -0,0 +1,1350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "b8e236f1-385a-4d93-bb39-bea3ee384d76",
|
| 7 |
+
"metadata": {
|
| 8 |
+
"tags": []
|
| 9 |
+
},
|
| 10 |
+
"outputs": [
|
| 11 |
+
{
|
| 12 |
+
"name": "stdout",
|
| 13 |
+
"output_type": "stream",
|
| 14 |
+
"text": [
|
| 15 |
+
"Number of available CUDA devices: 1\n",
|
| 16 |
+
"LOCAL RANK=0\n",
|
| 17 |
+
"NUM GPUS=1\n",
|
| 18 |
+
"NODE=0\n",
|
| 19 |
+
"GLOBAL RANK=0\n",
|
| 20 |
+
"WORLD_SIZE=1\n",
|
| 21 |
+
"PID of this process = 1339581\n",
|
| 22 |
+
"device = cuda distributed = False num_devices = 1 local rank = 0 world size = 1 data_type = torch.float16\n"
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
],
|
| 26 |
+
"source": [
|
| 27 |
+
"# Import packages and setup gpu configuration.\n",
|
| 28 |
+
"# This code block shouldnt need to be adjusted!\n",
|
| 29 |
+
"import os\n",
|
| 30 |
+
"import sys\n",
|
| 31 |
+
"import json\n",
|
| 32 |
+
"import yaml\n",
|
| 33 |
+
"import numpy as np\n",
|
| 34 |
+
"import pandas as pd\n",
|
| 35 |
+
"import copy\n",
|
| 36 |
+
"import math\n",
|
| 37 |
+
"from einops import rearrange\n",
|
| 38 |
+
"from einops.layers.torch import Rearrange\n",
|
| 39 |
+
"import time\n",
|
| 40 |
+
"import random\n",
|
| 41 |
+
"import h5py\n",
|
| 42 |
+
"import webdataset as wds\n",
|
| 43 |
+
"import gc\n",
|
| 44 |
+
"import matplotlib.pyplot as plt\n",
|
| 45 |
+
"\n",
|
| 46 |
+
"import torch\n",
|
| 47 |
+
"import torch.nn as nn\n",
|
| 48 |
+
"import torch.nn.functional as F\n",
|
| 49 |
+
"from torchvision import transforms\n",
|
| 50 |
+
"import utils\n",
|
| 51 |
+
"from models import *\n",
|
| 52 |
+
"import nibabel as nib\n",
|
| 53 |
+
"from nilearn import plotting\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"import schedulefree\n",
|
| 56 |
+
"\n",
|
| 57 |
+
"# tf32 data type is faster than standard float32\n",
|
| 58 |
+
"torch.backends.cuda.matmul.allow_tf32 = True\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"### Multi-GPU config ###\n",
|
| 61 |
+
"device_count = torch.cuda.device_count()\n",
|
| 62 |
+
"print(f\"Number of available CUDA devices: {device_count}\")\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"local_rank = os.getenv('LOCAL_RANK')\n",
|
| 65 |
+
"if local_rank is None: \n",
|
| 66 |
+
" local_rank = 0\n",
|
| 67 |
+
"else:\n",
|
| 68 |
+
" local_rank = int(local_rank)\n",
|
| 69 |
+
"print(f\"LOCAL RANK={local_rank}\")\n",
|
| 70 |
+
"\n",
|
| 71 |
+
"num_devices = os.getenv('NUM_GPUS')\n",
|
| 72 |
+
"if num_devices is None: \n",
|
| 73 |
+
" num_devices = 1\n",
|
| 74 |
+
"else:\n",
|
| 75 |
+
" num_devices = int(num_devices)\n",
|
| 76 |
+
"print(f\"NUM GPUS={num_devices}\")\n",
|
| 77 |
+
"distributed = True if num_devices>1 else False\n",
|
| 78 |
+
"if distributed: assert device_count==num_devices\n",
|
| 79 |
+
"\n",
|
| 80 |
+
"node = os.getenv('SLURM_NODEID')\n",
|
| 81 |
+
"if node is None:\n",
|
| 82 |
+
" node = 0\n",
|
| 83 |
+
"else:\n",
|
| 84 |
+
" node = int(node)\n",
|
| 85 |
+
"print(f\"NODE={node}\")\n",
|
| 86 |
+
"\n",
|
| 87 |
+
"global_rank = os.getenv('RANK')\n",
|
| 88 |
+
"if global_rank is None:\n",
|
| 89 |
+
" global_rank = 0\n",
|
| 90 |
+
"else:\n",
|
| 91 |
+
" global_rank = int(global_rank)\n",
|
| 92 |
+
"print(f\"GLOBAL RANK={global_rank}\")\n",
|
| 93 |
+
"\n",
|
| 94 |
+
"world_size = os.getenv('WORLD_SIZE')\n",
|
| 95 |
+
"if world_size is None: \n",
|
| 96 |
+
" world_size = 1\n",
|
| 97 |
+
"else:\n",
|
| 98 |
+
" world_size = int(world_size)\n",
|
| 99 |
+
"print(f\"WORLD_SIZE={world_size}\")\n",
|
| 100 |
+
"\n",
|
| 101 |
+
"if utils.is_interactive():\n",
|
| 102 |
+
" # Following allows you to change functions in models.py or utils.py and \n",
|
| 103 |
+
" # have this notebook automatically update with your revisions\n",
|
| 104 |
+
" %load_ext autoreload\n",
|
| 105 |
+
" %autoreload 2\n",
|
| 106 |
+
" from tqdm.notebook import tqdm\n",
|
| 107 |
+
"else:\n",
|
| 108 |
+
" from tqdm import tqdm\n",
|
| 109 |
+
"\n",
|
| 110 |
+
"# Load parameters from yaml config\n",
|
| 111 |
+
"config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"# create global variables from the config\n",
|
| 114 |
+
"for attribute_name in config.keys():\n",
|
| 115 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 116 |
+
"\n",
|
| 117 |
+
"data_type = torch.float16 # change depending on your mixed_precision\n",
|
| 118 |
+
"# batch_size = global_batch_size // num_devices\n",
|
| 119 |
+
"global_batch_size = batch_size * world_size\n",
|
| 120 |
+
"\n",
|
| 121 |
+
"# FSDP Setup\n",
|
| 122 |
+
"if distributed:\n",
|
| 123 |
+
" import torch.distributed as dist\n",
|
| 124 |
+
" import torch.multiprocessing as mp\n",
|
| 125 |
+
" from torch.distributed.fsdp import FullyShardedDataParallel as FSDP\n",
|
| 126 |
+
" from torch.distributed.fsdp.api import BackwardPrefetch, CPUOffload, ShardingStrategy\n",
|
| 127 |
+
" import functools\n",
|
| 128 |
+
" from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy\n",
|
| 129 |
+
" print(\"starting init_process_group...\")\n",
|
| 130 |
+
" dist.init_process_group(\"nccl\", rank=global_rank, world_size=world_size)\n",
|
| 131 |
+
" print(f\"setting device to cuda:{local_rank}\")\n",
|
| 132 |
+
" try:\n",
|
| 133 |
+
" torch.cuda.set_device(local_rank)\n",
|
| 134 |
+
" device = torch.device('cuda',local_rank)\n",
|
| 135 |
+
" print(f\"\\nSuccessfully set cuda:{local_rank} | global_rank{global_rank} | node{node}\")\n",
|
| 136 |
+
" except Exception as error: \n",
|
| 137 |
+
" print(f\"\\nFAILED TO SET DEVICE cuda:{local_rank} | global_rank{global_rank} | node{node}\")\n",
|
| 138 |
+
" print(\"An exception occurred:\", error)\n",
|
| 139 |
+
" \n",
|
| 140 |
+
"else:\n",
|
| 141 |
+
" device = torch.device('cuda')\n",
|
| 142 |
+
"\n",
|
| 143 |
+
"print(\"PID of this process =\",os.getpid())\n",
|
| 144 |
+
"print(\"device =\", device, \"distributed =\",distributed, \"num_devices =\", num_devices, \"local rank =\", local_rank, \"world size =\", world_size, \"data_type =\", data_type)"
|
| 145 |
+
]
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"cell_type": "markdown",
|
| 149 |
+
"id": "d08179db-9c6a-4bc6-a245-79fae6884ca2",
|
| 150 |
+
"metadata": {},
|
| 151 |
+
"source": [
|
| 152 |
+
"# Configuration"
|
| 153 |
+
]
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"cell_type": "code",
|
| 157 |
+
"execution_count": 2,
|
| 158 |
+
"id": "e6b1c3fe-28ab-40b7-8906-c6a9c8070d00",
|
| 159 |
+
"metadata": {
|
| 160 |
+
"tags": []
|
| 161 |
+
},
|
| 162 |
+
"outputs": [
|
| 163 |
+
{
|
| 164 |
+
"name": "stdout",
|
| 165 |
+
"output_type": "stream",
|
| 166 |
+
"text": [
|
| 167 |
+
"{'model_name': 'mini_nomask_logitsCLS_downstream_40ep_l', 'use_cls_token': True, 'use_contrastive_loss': True, 'contrastive_loss_weight': 0.1, 'batch_size': 256, 'num_workers': 10, 'num_epochs': 20, 'seed': 42, 'max_lr': 3e-06, 'num_samples_per_epoch': 1024, 'test_num_samples_per_epoch': 384, 'ckpt_saving': True, 'ckpt_interval': 50, 'resume_from_ckpt': True, 'wandb_log': True, 'tube_start_masking_ratio': 0.75, 'tube_end_masking_ratio': 0.75, 'decoder_mask_ratio': 0.75, 'patch_size': [8, 8, 8], 'frame_patch_size': 4, 'use_rope_emb': False, 'masking_strategy': 'None', 'encoder_model': 'vit_mini', 'decoder_model': 'vit_mini', 'img_size': [88, 104, 72], 'num_frames': 4, 'is_s3': False, 'train_urls': ['/weka/proj-fmri/shared/NSD_MNI_wds/{000000..000699}.tar'], 'test_urls': ['/weka/proj-fmri/shared/NSD_MNI_wds/{000700..000738}.tar']}\n",
|
| 168 |
+
"outdir /weka/proj-fmri/paulscotti/fMRI-foundation-model/ckpts/mini_nomask_logitsCLS_downstream_40ep_l\n",
|
| 169 |
+
"global_batch_size 256\n",
|
| 170 |
+
"use_cls_token True\n",
|
| 171 |
+
"num_patches 5148\n",
|
| 172 |
+
"num_patches_per_timepoint 1287\n",
|
| 173 |
+
"num_encoder_patches 321\n",
|
| 174 |
+
"num_decoder_patches 321\n"
|
| 175 |
+
]
|
| 176 |
+
}
|
| 177 |
+
],
|
| 178 |
+
"source": [
|
| 179 |
+
"print(config)\n",
|
| 180 |
+
"\n",
|
| 181 |
+
"# seed all random functions\n",
|
| 182 |
+
"utils.seed_everything(seed)\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"outdir = os.path.abspath(f'../ckpts/{model_name}')\n",
|
| 185 |
+
"os.makedirs(outdir,exist_ok=True)\n",
|
| 186 |
+
"print(\"outdir\", outdir)\n",
|
| 187 |
+
"print(\"global_batch_size\", global_batch_size)\n",
|
| 188 |
+
"print(\"use_cls_token\", use_cls_token)\n",
|
| 189 |
+
"\n",
|
| 190 |
+
"if type(patch_size) == int:\n",
|
| 191 |
+
" patch_size = [patch_size,patch_size,patch_size]\n",
|
| 192 |
+
"patch_depth = patch_size[0]\n",
|
| 193 |
+
"patch_height = patch_size[1]\n",
|
| 194 |
+
"patch_width = patch_size[2]\n",
|
| 195 |
+
"\n",
|
| 196 |
+
"num_patches = int(\n",
|
| 197 |
+
" (img_size[0] / patch_depth)\n",
|
| 198 |
+
" * (img_size[1] / patch_height)\n",
|
| 199 |
+
" * (img_size[2] / patch_width)\n",
|
| 200 |
+
" * num_frames\n",
|
| 201 |
+
")\n",
|
| 202 |
+
"num_patches_per_timepoint = num_patches // frame_patch_size\n",
|
| 203 |
+
"num_encoder_patches = int(np.floor((num_patches_per_timepoint * num_frames // frame_patch_size) * (1 - tube_start_masking_ratio)))\n",
|
| 204 |
+
"num_decoder_patches = int(np.floor((num_patches_per_timepoint * num_frames // frame_patch_size) * (1 - decoder_mask_ratio)))\n",
|
| 205 |
+
"print(\"num_patches\", num_patches)\n",
|
| 206 |
+
"print(\"num_patches_per_timepoint\", num_patches_per_timepoint)\n",
|
| 207 |
+
"print(\"num_encoder_patches\", num_encoder_patches)\n",
|
| 208 |
+
"print(\"num_decoder_patches\", num_decoder_patches)"
|
| 209 |
+
]
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"cell_type": "markdown",
|
| 213 |
+
"id": "1ae8419d-988f-42c6-acb6-b258e0694eee",
|
| 214 |
+
"metadata": {},
|
| 215 |
+
"source": [
|
| 216 |
+
"# Prep models"
|
| 217 |
+
]
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"cell_type": "code",
|
| 221 |
+
"execution_count": 3,
|
| 222 |
+
"id": "5e37b6b9-5b91-4c4a-af85-ac2af69704e7",
|
| 223 |
+
"metadata": {
|
| 224 |
+
"tags": []
|
| 225 |
+
},
|
| 226 |
+
"outputs": [
|
| 227 |
+
{
|
| 228 |
+
"name": "stdout",
|
| 229 |
+
"output_type": "stream",
|
| 230 |
+
"text": [
|
| 231 |
+
"param counts:\n",
|
| 232 |
+
"1,693,008 total\n",
|
| 233 |
+
"1,693,008 trainable\n",
|
| 234 |
+
"\n",
|
| 235 |
+
"encoder\n",
|
| 236 |
+
"torch.Size([256, 1, 4, 88, 104, 72])\n",
|
| 237 |
+
"patched torch.Size([256, 1, 11, 13, 9, 2048])\n",
|
| 238 |
+
"reshaped torch.Size([256, 1287, 2048])\n",
|
| 239 |
+
"masked torch.Size([256, 321, 2048])\n",
|
| 240 |
+
"patched_emb torch.Size([256, 321, 48])\n",
|
| 241 |
+
"pe torch.Size([1287, 48])\n",
|
| 242 |
+
"masked torch.Size([256, 322, 48])\n",
|
| 243 |
+
"torch.Size([256, 322, 48])\n",
|
| 244 |
+
"\n",
|
| 245 |
+
"decoder\n",
|
| 246 |
+
"torch.Size([256, 322, 48])\n",
|
| 247 |
+
"pe torch.Size([1287, 48])\n",
|
| 248 |
+
"pos_emd_encoder torch.Size([321, 48])\n",
|
| 249 |
+
"pos_emd_decoder torch.Size([321, 48])\n",
|
| 250 |
+
"x_concat torch.Size([256, 643, 48])\n",
|
| 251 |
+
"torch.Size([256, 643, 48])\n",
|
| 252 |
+
"proj torch.Size([256, 643, 2048])\n",
|
| 253 |
+
"\n",
|
| 254 |
+
"enc_cls_token torch.Size([256, 1, 48])\n",
|
| 255 |
+
"encoder_patches torch.Size([256, 321, 48])\n",
|
| 256 |
+
"dec_cls_token torch.Size([256, 1, 2048])\n",
|
| 257 |
+
"decoder_patches torch.Size([256, 642, 2048])\n"
|
| 258 |
+
]
|
| 259 |
+
}
|
| 260 |
+
],
|
| 261 |
+
"source": [
|
| 262 |
+
"vit_size = {\n",
|
| 263 |
+
" \"encoder\": encoder_model,\n",
|
| 264 |
+
" \"decoder\": decoder_model\n",
|
| 265 |
+
"}\n",
|
| 266 |
+
" \n",
|
| 267 |
+
"model = get_vit(\n",
|
| 268 |
+
" size=vit_size,\n",
|
| 269 |
+
" image_size=img_size, # depth, height, width\n",
|
| 270 |
+
" image_patch_size=(patch_depth,patch_height,patch_width), # depth, height, width patch size\n",
|
| 271 |
+
" frames=num_frames,\n",
|
| 272 |
+
" frame_patch_size=frame_patch_size,\n",
|
| 273 |
+
" channels=1,\n",
|
| 274 |
+
" use_rope_emb=use_rope_emb,\n",
|
| 275 |
+
" use_cls_token=use_cls_token,\n",
|
| 276 |
+
")\n",
|
| 277 |
+
"utils.count_params(model)\n",
|
| 278 |
+
"\n",
|
| 279 |
+
"# function to select random num_frames from sample and obtain brain-positive patches\n",
|
| 280 |
+
"aug_transform = utils.DataPrepper(\n",
|
| 281 |
+
" num_frames=num_frames*2,\n",
|
| 282 |
+
" masking_strategy=masking_strategy,\n",
|
| 283 |
+
" patch_depth=patch_depth,\n",
|
| 284 |
+
" patch_height=patch_height,\n",
|
| 285 |
+
" patch_width=patch_width,\n",
|
| 286 |
+
" frame_patch_size=frame_patch_size,\n",
|
| 287 |
+
")\n",
|
| 288 |
+
"\n",
|
| 289 |
+
"# test that the model works without error\n",
|
| 290 |
+
"model = model.to(device)\n",
|
| 291 |
+
"encoder_mask = torch.zeros(num_patches_per_timepoint).to(torch.bool)\n",
|
| 292 |
+
"encoder_mask[:num_encoder_patches] = True\n",
|
| 293 |
+
"decoder_mask = torch.zeros(num_patches_per_timepoint).to(torch.bool)\n",
|
| 294 |
+
"decoder_mask[-num_decoder_patches:] = True\n",
|
| 295 |
+
"decoder_mask[encoder_mask] = False\n",
|
| 296 |
+
"with torch.no_grad():\n",
|
| 297 |
+
" print(\"\\nencoder\")\n",
|
| 298 |
+
" encoder_out = model(\n",
|
| 299 |
+
" torch.randn(batch_size, 1, num_frames, img_size[0], img_size[1], img_size[2]).to(device),\n",
|
| 300 |
+
" encoder_mask=encoder_mask,\n",
|
| 301 |
+
" verbose=True)\n",
|
| 302 |
+
" print(\"\\ndecoder\")\n",
|
| 303 |
+
" decoder_out = model(\n",
|
| 304 |
+
" encoder_out, \n",
|
| 305 |
+
" encoder_mask=encoder_mask, \n",
|
| 306 |
+
" decoder_mask=decoder_mask, \n",
|
| 307 |
+
" verbose=True)\n",
|
| 308 |
+
" if use_cls_token:\n",
|
| 309 |
+
" enc_cls_token = encoder_out[:, :1, :]\n",
|
| 310 |
+
" encoder_patches = encoder_out[:, 1:, :]\n",
|
| 311 |
+
" dec_cls_token = decoder_out[:, :1, :]\n",
|
| 312 |
+
" decoder_patches = decoder_out[:, 1:, :]\n",
|
| 313 |
+
" print(\"\\nenc_cls_token\", enc_cls_token.shape)\n",
|
| 314 |
+
" print(\"encoder_patches\", encoder_patches.shape)\n",
|
| 315 |
+
" print(\"dec_cls_token\", dec_cls_token.shape)\n",
|
| 316 |
+
" print(\"decoder_patches\", decoder_patches.shape)"
|
| 317 |
+
]
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
"cell_type": "markdown",
|
| 321 |
+
"id": "414cb007-5552-4c5a-8fbd-9b158565770c",
|
| 322 |
+
"metadata": {
|
| 323 |
+
"tags": []
|
| 324 |
+
},
|
| 325 |
+
"source": [
|
| 326 |
+
"### Add \"linear\" probe"
|
| 327 |
+
]
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"cell_type": "code",
|
| 331 |
+
"execution_count": 4,
|
| 332 |
+
"id": "bb3df855-81c4-4d2c-aff2-42a19dd0e67e",
|
| 333 |
+
"metadata": {
|
| 334 |
+
"tags": []
|
| 335 |
+
},
|
| 336 |
+
"outputs": [],
|
| 337 |
+
"source": [
|
| 338 |
+
"class LinearProbe(nn.Module):\n",
|
| 339 |
+
" def __init__(self, input_dim, h=256, num_classes=8):\n",
|
| 340 |
+
" super(LinearProbe, self).__init__()\n",
|
| 341 |
+
" # self.classifier = nn.Linear(input_dim, num_classes)\n",
|
| 342 |
+
" self.classifier = nn.Sequential(\n",
|
| 343 |
+
" nn.LayerNorm(input_dim),\n",
|
| 344 |
+
" nn.GELU(),\n",
|
| 345 |
+
" nn.Linear(input_dim, h),\n",
|
| 346 |
+
" nn.LayerNorm(h),\n",
|
| 347 |
+
" nn.GELU(),\n",
|
| 348 |
+
" nn.Linear(h, h),\n",
|
| 349 |
+
" nn.LayerNorm(h),\n",
|
| 350 |
+
" nn.GELU(),\n",
|
| 351 |
+
" nn.Linear(h, num_classes)\n",
|
| 352 |
+
" )\n",
|
| 353 |
+
" def forward(self, x):\n",
|
| 354 |
+
" x = self.classifier(x)\n",
|
| 355 |
+
" return x"
|
| 356 |
+
]
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"cell_type": "code",
|
| 360 |
+
"execution_count": 5,
|
| 361 |
+
"id": "2e6a865b-8348-486e-ab88-040a87fa2ee5",
|
| 362 |
+
"metadata": {},
|
| 363 |
+
"outputs": [],
|
| 364 |
+
"source": [
|
| 365 |
+
"# if use_cls_token:\n",
|
| 366 |
+
"# model.cont = LinearProbe((num_encoder_patches+1)*model.encoder_embed_dim,h=768,num_classes=768)\n",
|
| 367 |
+
"# else:\n",
|
| 368 |
+
"# model.cont = LinearProbe(model.encoder_embed_dim,h=256,num_classes=256)\n",
|
| 369 |
+
"# model = model.to(device)"
|
| 370 |
+
]
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"cell_type": "markdown",
|
| 374 |
+
"id": "3dd51ddf-fb71-48f4-bdbd-88753b44d2aa",
|
| 375 |
+
"metadata": {},
|
| 376 |
+
"source": [
|
| 377 |
+
"## Create dataset and data loaders"
|
| 378 |
+
]
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"cell_type": "code",
|
| 382 |
+
"execution_count": 6,
|
| 383 |
+
"id": "75dd0398-0eb8-464e-8bb8-13e7ce1a8480",
|
| 384 |
+
"metadata": {
|
| 385 |
+
"tags": []
|
| 386 |
+
},
|
| 387 |
+
"outputs": [],
|
| 388 |
+
"source": [
|
| 389 |
+
"# from dataloader import create_dataset, create_loader\n",
|
| 390 |
+
"# train_urls = train_urls[0]\n",
|
| 391 |
+
"# print(train_urls)\n",
|
| 392 |
+
"\n",
|
| 393 |
+
"# train_dp = create_dataset(train_urls, \n",
|
| 394 |
+
"# is_s3=train_urls[:2]==\"s3\", \n",
|
| 395 |
+
"# sample_shuffle=100, shard_shuffle=100)\n",
|
| 396 |
+
"# train_dl = create_loader(train_dp, batch_size=batch_size, num_workers=num_workers)"
|
| 397 |
+
]
|
| 398 |
+
},
|
| 399 |
+
{
|
| 400 |
+
"cell_type": "code",
|
| 401 |
+
"execution_count": 7,
|
| 402 |
+
"id": "cadb1eb6-dc41-416b-ace5-70bd7c7d887c",
|
| 403 |
+
"metadata": {
|
| 404 |
+
"tags": []
|
| 405 |
+
},
|
| 406 |
+
"outputs": [
|
| 407 |
+
{
|
| 408 |
+
"name": "stdout",
|
| 409 |
+
"output_type": "stream",
|
| 410 |
+
"text": [
|
| 411 |
+
"['/weka/proj-fmri/shared/NSD_MNI_wds/{000000..000699}.tar']\n",
|
| 412 |
+
"['/weka/proj-fmri/shared/NSD_MNI_wds/{000700..000738}.tar']\n"
|
| 413 |
+
]
|
| 414 |
+
}
|
| 415 |
+
],
|
| 416 |
+
"source": [
|
| 417 |
+
"def log_and_continue(exn):\n",
|
| 418 |
+
" \"\"\"Call in an exception handler to ignore any exception, issue a warning, and continue.\"\"\"\n",
|
| 419 |
+
" print(f'Handling webdataset error ({repr(exn)}). Ignoring.')\n",
|
| 420 |
+
" return True\n",
|
| 421 |
+
"\n",
|
| 422 |
+
"def filter_corrupted_images(sample):\n",
|
| 423 |
+
" \"\"\"If all the required files are not present don't use them.\"\"\"\n",
|
| 424 |
+
" correct_data = (\"func.npy\" in sample)\n",
|
| 425 |
+
" return correct_data\n",
|
| 426 |
+
"\n",
|
| 427 |
+
"### ================ Train Dataset and DataLoader ====================\n",
|
| 428 |
+
"from braceexpand import braceexpand\n",
|
| 429 |
+
"print(train_urls)\n",
|
| 430 |
+
"if is_s3:\n",
|
| 431 |
+
" expanded_urls = [f\"pipe:aws s3 cp {url} -\" for pattern in train_urls for url in braceexpand(pattern)]\n",
|
| 432 |
+
"else:\n",
|
| 433 |
+
" expanded_urls = [str(url) for pattern in train_urls for url in braceexpand(pattern)]\n",
|
| 434 |
+
"\n",
|
| 435 |
+
"train_data = (\n",
|
| 436 |
+
" wds.WebDataset(expanded_urls, resampled=True, nodesplitter=wds.split_by_node, handler=log_and_continue)\n",
|
| 437 |
+
" .shuffle(100, initial=100, rng=random.Random(seed))\n",
|
| 438 |
+
" .select(filter_corrupted_images)\n",
|
| 439 |
+
" .decode(\"torch\")\n",
|
| 440 |
+
")\n",
|
| 441 |
+
"train_dl = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=False, drop_last=True, pin_memory=True)\n",
|
| 442 |
+
"\n",
|
| 443 |
+
"### ================ Test Dataset and DataLoader ====================\n",
|
| 444 |
+
"print(test_urls)\n",
|
| 445 |
+
"if is_s3:\n",
|
| 446 |
+
" expanded_urls = [f\"pipe:aws s3 cp {url} -\" for pattern in test_urls for url in braceexpand(pattern)]\n",
|
| 447 |
+
"else:\n",
|
| 448 |
+
" expanded_urls = [str(url) for pattern in train_urls for url in braceexpand(pattern)]\n",
|
| 449 |
+
"\n",
|
| 450 |
+
"test_data = (\n",
|
| 451 |
+
" wds.WebDataset(expanded_urls, resampled=True, nodesplitter=wds.split_by_node, handler=log_and_continue)\n",
|
| 452 |
+
" .shuffle(100, initial=100, rng=random.Random(seed))\n",
|
| 453 |
+
" .select(filter_corrupted_images)\n",
|
| 454 |
+
" .decode(\"torch\")\n",
|
| 455 |
+
")\n",
|
| 456 |
+
"test_dl = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False, drop_last=True, pin_memory=True)"
|
| 457 |
+
]
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"cell_type": "markdown",
|
| 461 |
+
"id": "15d05a32-12eb-494a-82df-dce4c7e9924c",
|
| 462 |
+
"metadata": {},
|
| 463 |
+
"source": [
|
| 464 |
+
"### Check data loaders work"
|
| 465 |
+
]
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"cell_type": "code",
|
| 469 |
+
"execution_count": 8,
|
| 470 |
+
"id": "480e7c0b-f58e-4c35-80fe-1284ee0e3966",
|
| 471 |
+
"metadata": {
|
| 472 |
+
"tags": []
|
| 473 |
+
},
|
| 474 |
+
"outputs": [],
|
| 475 |
+
"source": [
|
| 476 |
+
"# if utils.is_interactive():\n",
|
| 477 |
+
"# start_time = time.time() \n",
|
| 478 |
+
"# num_it = 2\n",
|
| 479 |
+
"# print(f\"Yielding {num_it} batches\")\n",
|
| 480 |
+
" \n",
|
| 481 |
+
"# for i, batch in enumerate(test_dl):\n",
|
| 482 |
+
"# print(\"iter\",i)\n",
|
| 483 |
+
"# input_func = batch['func.npy']\n",
|
| 484 |
+
"# subject_id = batch['subject_id.txt']\n",
|
| 485 |
+
"# subject_id = [int(subject[-2:]) for subject in subject_id]\n",
|
| 486 |
+
"# # session_id = batch['session_id.txt']\n",
|
| 487 |
+
"# # session_id = [int(session[-2:]) for session in session_id]\n",
|
| 488 |
+
"# func, brain_pos_pats = aug_transform(input_func)\n",
|
| 489 |
+
"# if i >= (num_it-1):\n",
|
| 490 |
+
"# break\n",
|
| 491 |
+
" \n",
|
| 492 |
+
"# print(\"Done!\")\n",
|
| 493 |
+
"# print(\"input_func\", input_func.shape)\n",
|
| 494 |
+
"# print(\"func\", func.shape)\n",
|
| 495 |
+
"# print(\"subject_id\", subject_id)\n",
|
| 496 |
+
"\n",
|
| 497 |
+
"# end_time = time.time() \n",
|
| 498 |
+
"# execution_time = end_time - start_time \n",
|
| 499 |
+
"# print(f\"Execution time: {execution_time} seconds\")"
|
| 500 |
+
]
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"cell_type": "markdown",
|
| 504 |
+
"id": "349c6fba-5494-4964-b03d-e01c3afe47db",
|
| 505 |
+
"metadata": {},
|
| 506 |
+
"source": [
|
| 507 |
+
"### Playing with the data, visualization of patching + masking"
|
| 508 |
+
]
|
| 509 |
+
},
|
| 510 |
+
{
|
| 511 |
+
"cell_type": "code",
|
| 512 |
+
"execution_count": 9,
|
| 513 |
+
"id": "fea69a0b-f0a0-4309-b689-553b911a8da5",
|
| 514 |
+
"metadata": {
|
| 515 |
+
"tags": []
|
| 516 |
+
},
|
| 517 |
+
"outputs": [],
|
| 518 |
+
"source": [
|
| 519 |
+
"# if utils.is_interactive():\n",
|
| 520 |
+
"# func, brain_pos_pats = aug_transform(input_func)\n",
|
| 521 |
+
"# print(func.shape)\n",
|
| 522 |
+
"# display(utils.view_brain(func,cut_coords=(44,44,44)))\n",
|
| 523 |
+
"# # plt.hist(func[0,0].flatten().clamp(.25,3),bins=100)"
|
| 524 |
+
]
|
| 525 |
+
},
|
| 526 |
+
{
|
| 527 |
+
"cell_type": "markdown",
|
| 528 |
+
"id": "2b8e6baa-4b1c-4f38-b078-70b2b092d14d",
|
| 529 |
+
"metadata": {},
|
| 530 |
+
"source": [
|
| 531 |
+
"# Set up optimizer and saving functions"
|
| 532 |
+
]
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"cell_type": "code",
|
| 536 |
+
"execution_count": 10,
|
| 537 |
+
"id": "9553ad47-e76d-4ee1-a238-aad13a4be043",
|
| 538 |
+
"metadata": {},
|
| 539 |
+
"outputs": [],
|
| 540 |
+
"source": [
|
| 541 |
+
"if distributed: \n",
|
| 542 |
+
" # my_auto_wrap_policy = functools.partial(\n",
|
| 543 |
+
" # size_based_auto_wrap_policy, min_num_params=200000\n",
|
| 544 |
+
" # )\n",
|
| 545 |
+
" my_auto_wrap_policy = functools.partial(\n",
|
| 546 |
+
" transformer_auto_wrap_policy, \n",
|
| 547 |
+
" transformer_layer_cls={\n",
|
| 548 |
+
" Attention, # <--- Your Transformer layer class\n",
|
| 549 |
+
" },\n",
|
| 550 |
+
" )\n",
|
| 551 |
+
" print(f\"\\nPrepping FSDP on {global_rank} {node}...\\n\")\n",
|
| 552 |
+
" model = model.to(device)\n",
|
| 553 |
+
" model = FSDP(\n",
|
| 554 |
+
" model,\n",
|
| 555 |
+
" sharding_strategy=ShardingStrategy.HYBRID_SHARD,\n",
|
| 556 |
+
" auto_wrap_policy=my_auto_wrap_policy,\n",
|
| 557 |
+
" use_orig_params=False,\n",
|
| 558 |
+
" cpu_offload=None, #CPUOffload(offload_params=True)\n",
|
| 559 |
+
" sync_module_states=True,\n",
|
| 560 |
+
" limit_all_gathers=True, # See https://github.com/pytorch/pytorch/issues/91165\n",
|
| 561 |
+
" device_id=device,\n",
|
| 562 |
+
" )\n",
|
| 563 |
+
" print(f\"\\nSuccessfully loaded FSDP model to device on global_rank {global_rank}\\n\")\n",
|
| 564 |
+
" dist.barrier()\n",
|
| 565 |
+
" print(f\"\\nSuccessfully loaded FSDP model to device on global_rank {global_rank}\\n\")"
|
| 566 |
+
]
|
| 567 |
+
},
|
| 568 |
+
{
|
| 569 |
+
"cell_type": "code",
|
| 570 |
+
"execution_count": 11,
|
| 571 |
+
"id": "4da73c08-ca61-48ef-9e63-b70db6f07a59",
|
| 572 |
+
"metadata": {
|
| 573 |
+
"tags": []
|
| 574 |
+
},
|
| 575 |
+
"outputs": [
|
| 576 |
+
{
|
| 577 |
+
"name": "stdout",
|
| 578 |
+
"output_type": "stream",
|
| 579 |
+
"text": [
|
| 580 |
+
"num_iterations_per_epoch 4\n",
|
| 581 |
+
"probe_num_iterations_per_epoch 1\n",
|
| 582 |
+
"total_steps 80\n",
|
| 583 |
+
"\n",
|
| 584 |
+
"Done with model preparations!\n",
|
| 585 |
+
"param counts:\n",
|
| 586 |
+
"1,838,256 total\n",
|
| 587 |
+
"1,838,256 trainable\n"
|
| 588 |
+
]
|
| 589 |
+
}
|
| 590 |
+
],
|
| 591 |
+
"source": [
|
| 592 |
+
"no_decay = [\"bias\", \"LayerNorm.bias\", \"LayerNorm.weight\"]\n",
|
| 593 |
+
"opt_grouped_parameters = [\n",
|
| 594 |
+
" {'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 1e-2},\n",
|
| 595 |
+
" {'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},\n",
|
| 596 |
+
"]\n",
|
| 597 |
+
"\n",
|
| 598 |
+
"if distributed:\n",
|
| 599 |
+
" max_lr = max_lr * global_batch_size\n",
|
| 600 |
+
" print(f\"multiply lr {max_lr} by global batch size: max_lr={max_lr}\")\n",
|
| 601 |
+
"\n",
|
| 602 |
+
"# optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=max_lr)\n",
|
| 603 |
+
"optimizer = schedulefree.AdamWScheduleFree(opt_grouped_parameters, lr=max_lr)\n",
|
| 604 |
+
"\n",
|
| 605 |
+
"num_iterations_per_epoch = num_samples_per_epoch // global_batch_size\n",
|
| 606 |
+
"print(\"num_iterations_per_epoch\", num_iterations_per_epoch)\n",
|
| 607 |
+
"\n",
|
| 608 |
+
"probe_num_iterations_per_epoch = test_num_samples_per_epoch // global_batch_size\n",
|
| 609 |
+
"print(\"probe_num_iterations_per_epoch\", probe_num_iterations_per_epoch)\n",
|
| 610 |
+
"\n",
|
| 611 |
+
"total_steps = num_epochs * num_iterations_per_epoch * num_devices\n",
|
| 612 |
+
"print(\"total_steps\", total_steps)\n",
|
| 613 |
+
"\n",
|
| 614 |
+
"# lr_scheduler = torch.optim.lr_scheduler.OneCycleLR(\n",
|
| 615 |
+
"# optimizer,\n",
|
| 616 |
+
"# max_lr=max_lr,\n",
|
| 617 |
+
"# total_steps=total_steps,\n",
|
| 618 |
+
"# )\n",
|
| 619 |
+
"\n",
|
| 620 |
+
"print(\"\\nDone with model preparations!\")\n",
|
| 621 |
+
"num_params = utils.count_params(model)"
|
| 622 |
+
]
|
| 623 |
+
},
|
| 624 |
+
{
|
| 625 |
+
"cell_type": "code",
|
| 626 |
+
"execution_count": 12,
|
| 627 |
+
"id": "f51f674d-7cd4-49d1-bd8d-c697d9614f65",
|
| 628 |
+
"metadata": {
|
| 629 |
+
"tags": []
|
| 630 |
+
},
|
| 631 |
+
"outputs": [],
|
| 632 |
+
"source": [
|
| 633 |
+
"def save_ckpt(model,tag=\"last\"):\n",
|
| 634 |
+
" if distributed: dist.barrier()\n",
|
| 635 |
+
" model_states = model.state_dict()\n",
|
| 636 |
+
" if global_rank == 0:\n",
|
| 637 |
+
" os.makedirs(outdir,exist_ok=True)\n",
|
| 638 |
+
" ckpt_path = outdir+f'/{tag}.pth'\n",
|
| 639 |
+
" torch.save({\n",
|
| 640 |
+
" 'epoch': epoch,\n",
|
| 641 |
+
" 'model_state_dict': model_states,\n",
|
| 642 |
+
" 'optimizer_state_dict': optimizer.state_dict(),\n",
|
| 643 |
+
" }, ckpt_path)\n",
|
| 644 |
+
" print(f\"\\n---saved {ckpt_path}!---\\n\")"
|
| 645 |
+
]
|
| 646 |
+
},
|
| 647 |
+
{
|
| 648 |
+
"cell_type": "markdown",
|
| 649 |
+
"id": "ef9b4262-435b-4872-ab4a-424cb9dfd37a",
|
| 650 |
+
"metadata": {},
|
| 651 |
+
"source": [
|
| 652 |
+
"# Start wandb (if enabled)"
|
| 653 |
+
]
|
| 654 |
+
},
|
| 655 |
+
{
|
| 656 |
+
"cell_type": "code",
|
| 657 |
+
"execution_count": 13,
|
| 658 |
+
"id": "56431733-4fb5-4072-840e-22536608f9f1",
|
| 659 |
+
"metadata": {
|
| 660 |
+
"tags": []
|
| 661 |
+
},
|
| 662 |
+
"outputs": [
|
| 663 |
+
{
|
| 664 |
+
"name": "stdout",
|
| 665 |
+
"output_type": "stream",
|
| 666 |
+
"text": [
|
| 667 |
+
"wandb found run mini_nomask_logitsCLS_downstream_40ep_l\n",
|
| 668 |
+
"wandb_config:\n",
|
| 669 |
+
" {'model_name': 'mini_nomask_logitsCLS_downstream_40ep_l', 'global_batch_size': 256, 'batch_size': 256, 'num_epochs': 20, 'num_samples_per_epoch': 1024, 'test_num_samples_per_epoch': 384, 'num_iterations_per_epoch': 4, 'encoder_model': 'vit_mini', 'decoder_model': 'vit_mini', 'tube_start_masking_ratio': 0.75, 'tube_end_masking_ratio': 0.75, 'decoder_mask_ratio': 0.75, 'num_frames': 4, 'patch_size': [8, 8, 8], 'frame_patch_size': 4, 'use_contrastive_loss': True, 'use_cls_token': True, 'contrastive_loss_weight': 0.1, 'num_params': 1838256, 'max_lr': 3e-06, 'ckpt_interval': 50, 'ckpt_saving': False, 'seed': 42, 'distributed': False, 'num_devices': 1, 'world_size': 1, 'train_urls': ['/weka/proj-fmri/shared/NSD_MNI_wds/{000000..000699}.tar']}\n",
|
| 670 |
+
"wandb_id: mini_nomask_logitsCLS_downstream_40ep_l\n"
|
| 671 |
+
]
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"name": "stderr",
|
| 675 |
+
"output_type": "stream",
|
| 676 |
+
"text": [
|
| 677 |
+
"\u001b[34m\u001b[1mwandb\u001b[0m: Currently logged in as: \u001b[33mpaul-scotti\u001b[0m. Use \u001b[1m`wandb login --relogin`\u001b[0m to force relogin\n"
|
| 678 |
+
]
|
| 679 |
+
},
|
| 680 |
+
{
|
| 681 |
+
"data": {
|
| 682 |
+
"text/html": [
|
| 683 |
+
"wandb version 0.16.6 is available! To upgrade, please run:\n",
|
| 684 |
+
" $ pip install wandb --upgrade"
|
| 685 |
+
],
|
| 686 |
+
"text/plain": [
|
| 687 |
+
"<IPython.core.display.HTML object>"
|
| 688 |
+
]
|
| 689 |
+
},
|
| 690 |
+
"metadata": {},
|
| 691 |
+
"output_type": "display_data"
|
| 692 |
+
},
|
| 693 |
+
{
|
| 694 |
+
"data": {
|
| 695 |
+
"text/html": [
|
| 696 |
+
"Tracking run with wandb version 0.16.4"
|
| 697 |
+
],
|
| 698 |
+
"text/plain": [
|
| 699 |
+
"<IPython.core.display.HTML object>"
|
| 700 |
+
]
|
| 701 |
+
},
|
| 702 |
+
"metadata": {},
|
| 703 |
+
"output_type": "display_data"
|
| 704 |
+
},
|
| 705 |
+
{
|
| 706 |
+
"data": {
|
| 707 |
+
"text/html": [
|
| 708 |
+
"Run data is saved locally in <code>/weka/proj-fmri/paulscotti/MindEyeV2/wandb/run-20240421_203216-mini_nomask_logitsCLS_downstream_40ep_l</code>"
|
| 709 |
+
],
|
| 710 |
+
"text/plain": [
|
| 711 |
+
"<IPython.core.display.HTML object>"
|
| 712 |
+
]
|
| 713 |
+
},
|
| 714 |
+
"metadata": {},
|
| 715 |
+
"output_type": "display_data"
|
| 716 |
+
},
|
| 717 |
+
{
|
| 718 |
+
"data": {
|
| 719 |
+
"text/html": [
|
| 720 |
+
"Syncing run <strong><a href='https://wandb.ai/paul-scotti/found/runs/mini_nomask_logitsCLS_downstream_40ep_l' target=\"_blank\">mini_nomask_logitsCLS_downstream_40ep_l</a></strong> to <a href='https://wandb.ai/paul-scotti/found' target=\"_blank\">Weights & Biases</a> (<a href='https://wandb.me/run' target=\"_blank\">docs</a>)<br/>"
|
| 721 |
+
],
|
| 722 |
+
"text/plain": [
|
| 723 |
+
"<IPython.core.display.HTML object>"
|
| 724 |
+
]
|
| 725 |
+
},
|
| 726 |
+
"metadata": {},
|
| 727 |
+
"output_type": "display_data"
|
| 728 |
+
},
|
| 729 |
+
{
|
| 730 |
+
"data": {
|
| 731 |
+
"text/html": [
|
| 732 |
+
" View project at <a href='https://wandb.ai/paul-scotti/found' target=\"_blank\">https://wandb.ai/paul-scotti/found</a>"
|
| 733 |
+
],
|
| 734 |
+
"text/plain": [
|
| 735 |
+
"<IPython.core.display.HTML object>"
|
| 736 |
+
]
|
| 737 |
+
},
|
| 738 |
+
"metadata": {},
|
| 739 |
+
"output_type": "display_data"
|
| 740 |
+
},
|
| 741 |
+
{
|
| 742 |
+
"data": {
|
| 743 |
+
"text/html": [
|
| 744 |
+
" View run at <a href='https://wandb.ai/paul-scotti/found/runs/mini_nomask_logitsCLS_downstream_40ep_l' target=\"_blank\">https://wandb.ai/paul-scotti/found/runs/mini_nomask_logitsCLS_downstream_40ep_l</a>"
|
| 745 |
+
],
|
| 746 |
+
"text/plain": [
|
| 747 |
+
"<IPython.core.display.HTML object>"
|
| 748 |
+
]
|
| 749 |
+
},
|
| 750 |
+
"metadata": {},
|
| 751 |
+
"output_type": "display_data"
|
| 752 |
+
}
|
| 753 |
+
],
|
| 754 |
+
"source": [
|
| 755 |
+
"if utils.is_interactive():\n",
|
| 756 |
+
"# wandb_log = False\n",
|
| 757 |
+
" ckpt_saving = False\n",
|
| 758 |
+
"if local_rank==0 and wandb_log: # only use main process for wandb logging\n",
|
| 759 |
+
" import wandb\n",
|
| 760 |
+
" wandb_project = 'found'\n",
|
| 761 |
+
" print(f\"wandb {wandb_project} run {model_name}\")\n",
|
| 762 |
+
" # need to configure wandb beforehand in terminal with \"wandb init\"!\n",
|
| 763 |
+
" wandb_config = {\n",
|
| 764 |
+
" \"model_name\": model_name,\n",
|
| 765 |
+
" \"global_batch_size\": global_batch_size,\n",
|
| 766 |
+
" \"batch_size\": batch_size,\n",
|
| 767 |
+
" \"num_epochs\": num_epochs,\n",
|
| 768 |
+
" \"num_samples_per_epoch\": num_samples_per_epoch,\n",
|
| 769 |
+
" \"test_num_samples_per_epoch\": test_num_samples_per_epoch,\n",
|
| 770 |
+
" \"num_iterations_per_epoch\": num_iterations_per_epoch,\n",
|
| 771 |
+
" \"encoder_model\": encoder_model,\n",
|
| 772 |
+
" \"decoder_model\": decoder_model,\n",
|
| 773 |
+
" \"tube_start_masking_ratio\": tube_start_masking_ratio,\n",
|
| 774 |
+
" \"tube_end_masking_ratio\": tube_end_masking_ratio,\n",
|
| 775 |
+
" \"decoder_mask_ratio\": decoder_mask_ratio,\n",
|
| 776 |
+
" \"num_frames\": num_frames,\n",
|
| 777 |
+
" \"patch_size\": patch_size,\n",
|
| 778 |
+
" \"frame_patch_size\": frame_patch_size,\n",
|
| 779 |
+
" \"use_contrastive_loss\": use_contrastive_loss,\n",
|
| 780 |
+
" \"use_cls_token\": use_cls_token,\n",
|
| 781 |
+
" \"contrastive_loss_weight\": contrastive_loss_weight,\n",
|
| 782 |
+
" \"num_params\": num_params,\n",
|
| 783 |
+
" \"max_lr\": max_lr,\n",
|
| 784 |
+
" \"ckpt_interval\": ckpt_interval,\n",
|
| 785 |
+
" \"ckpt_saving\": ckpt_saving,\n",
|
| 786 |
+
" \"seed\": seed,\n",
|
| 787 |
+
" \"distributed\": distributed,\n",
|
| 788 |
+
" \"num_devices\": num_devices,\n",
|
| 789 |
+
" \"world_size\": world_size,\n",
|
| 790 |
+
" \"train_urls\": train_urls,\n",
|
| 791 |
+
" }\n",
|
| 792 |
+
" print(\"wandb_config:\\n\",wandb_config)\n",
|
| 793 |
+
" print(\"wandb_id:\",model_name)\n",
|
| 794 |
+
" wandb.init(\n",
|
| 795 |
+
" id=model_name,\n",
|
| 796 |
+
" project=wandb_project,\n",
|
| 797 |
+
" name=model_name,\n",
|
| 798 |
+
" config=wandb_config,\n",
|
| 799 |
+
" resume=\"allow\",\n",
|
| 800 |
+
" )\n",
|
| 801 |
+
"else:\n",
|
| 802 |
+
" wandb_log = False"
|
| 803 |
+
]
|
| 804 |
+
},
|
| 805 |
+
{
|
| 806 |
+
"cell_type": "markdown",
|
| 807 |
+
"id": "c43a5055-8afd-468a-93bf-32f94bd1d042",
|
| 808 |
+
"metadata": {},
|
| 809 |
+
"source": [
|
| 810 |
+
"# Start training"
|
| 811 |
+
]
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"cell_type": "code",
|
| 815 |
+
"execution_count": 14,
|
| 816 |
+
"id": "f5e394dd-2745-41fa-a5aa-54b7b1373a8f",
|
| 817 |
+
"metadata": {
|
| 818 |
+
"tags": []
|
| 819 |
+
},
|
| 820 |
+
"outputs": [],
|
| 821 |
+
"source": [
|
| 822 |
+
"epoch = 0\n",
|
| 823 |
+
"lrs, train_losses, recon_losses, contrastive_losses = [], [], [], []\n",
|
| 824 |
+
"cos_sim_encoder_output, cos_sim_decoder_output, cos_sim_encoder_output_patchwise = [], [], []\n",
|
| 825 |
+
"probe_losses, probe_accs, test_losses, test_accs = [], [], [], []"
|
| 826 |
+
]
|
| 827 |
+
},
|
| 828 |
+
{
|
| 829 |
+
"cell_type": "code",
|
| 830 |
+
"execution_count": 15,
|
| 831 |
+
"id": "0b9a5605-fd81-41d4-964e-1752ed6e1289",
|
| 832 |
+
"metadata": {
|
| 833 |
+
"tags": []
|
| 834 |
+
},
|
| 835 |
+
"outputs": [],
|
| 836 |
+
"source": [
|
| 837 |
+
"# # resume from ckpt (e.g., if you are resuming from a run that got pre-empted)\n",
|
| 838 |
+
"# load_progress = False\n",
|
| 839 |
+
"# if wandb_log:\n",
|
| 840 |
+
"# if wandb.run.resumed:\n",
|
| 841 |
+
"# load_checkpoint_in_model(model, outdir+\"/last\")\n",
|
| 842 |
+
"# load_progress = True\n",
|
| 843 |
+
"# elif resume_from_ckpt: # if resuming without using wandb\n",
|
| 844 |
+
"# load_checkpoint_in_model(model, outdir+\"/last\")\n",
|
| 845 |
+
"# load_progress = True"
|
| 846 |
+
]
|
| 847 |
+
},
|
| 848 |
+
{
|
| 849 |
+
"cell_type": "code",
|
| 850 |
+
"execution_count": 16,
|
| 851 |
+
"id": "154e8f51-6918-45df-b6b8-ca46c89ba34e",
|
| 852 |
+
"metadata": {},
|
| 853 |
+
"outputs": [],
|
| 854 |
+
"source": [
|
| 855 |
+
"if masking_strategy==\"MNI\":\n",
|
| 856 |
+
" from einops.layers.torch import Rearrange\n",
|
| 857 |
+
" MNI_brain = nib.load(\"/weka/proj-fmri/paulscotti/fMRI-foundation-model/dataset_creation/afni_conversion/tpl-MNI152NLin2009cAsym_res-02_T1w_brain.nii.gz\").get_fdata()\n",
|
| 858 |
+
" brain_pos_voxels = MNI_brain[6:94,8:112,10:82]\n",
|
| 859 |
+
" brain_pos_pats = Rearrange(\n",
|
| 860 |
+
" \"b c (f pf) (d pd) (h ph) (w pw) -> b f d h w (pd ph pw pf c)\",\n",
|
| 861 |
+
" pd=patch_depth,\n",
|
| 862 |
+
" ph=patch_height,\n",
|
| 863 |
+
" pw=patch_width,\n",
|
| 864 |
+
" pf=1,\n",
|
| 865 |
+
" )(torch.Tensor(brain_pos_voxels)[None,None,None])\n",
|
| 866 |
+
" brain_pos_pats_vit = rearrange(brain_pos_pats, \"b ... d -> b (...) d\").mean(-1)[0]"
|
| 867 |
+
]
|
| 868 |
+
},
|
| 869 |
+
{
|
| 870 |
+
"cell_type": "code",
|
| 871 |
+
"execution_count": 18,
|
| 872 |
+
"id": "c3461199-e805-4e9c-8c91-894e83cf8bc3",
|
| 873 |
+
"metadata": {
|
| 874 |
+
"tags": []
|
| 875 |
+
},
|
| 876 |
+
"outputs": [
|
| 877 |
+
{
|
| 878 |
+
"data": {
|
| 879 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 880 |
+
"model_id": "2778883b6d5b442dba4d66aa5a0be678",
|
| 881 |
+
"version_major": 2,
|
| 882 |
+
"version_minor": 0
|
| 883 |
+
},
|
| 884 |
+
"text/plain": [
|
| 885 |
+
"Overall: 0%| | 0/20 [00:00<?, ?it/s]"
|
| 886 |
+
]
|
| 887 |
+
},
|
| 888 |
+
"metadata": {},
|
| 889 |
+
"output_type": "display_data"
|
| 890 |
+
},
|
| 891 |
+
{
|
| 892 |
+
"name": "stdout",
|
| 893 |
+
"output_type": "stream",
|
| 894 |
+
"text": [
|
| 895 |
+
"0 0.078125 2.080991744995117\n",
|
| 896 |
+
"test 0 0.146484375 2.087881088256836\n",
|
| 897 |
+
"test 1 0.033203125 2.1139793395996094\n",
|
| 898 |
+
"{'train/loss': 0.6546683609485626, 'train/recon_losses': nan, 'train/contrastive_losses': 6.546683311462402, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6011962890625, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.2939453125, 'train/probe_losses': 2.080991744995117, 'train/probe_accs': 0.078125, 'test/probe_losses': 2.1009302139282227, 'test/probe_accs': 0.08984375, 'lr': 3e-06, 'epoch': 0, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n"
|
| 899 |
+
]
|
| 900 |
+
},
|
| 901 |
+
{
|
| 902 |
+
"name": "stderr",
|
| 903 |
+
"output_type": "stream",
|
| 904 |
+
"text": [
|
| 905 |
+
"/admin/home-paulscotti/found/lib/python3.10/site-packages/numpy/core/fromnumeric.py:3504: RuntimeWarning: Mean of empty slice.\n",
|
| 906 |
+
" return _methods._mean(a, axis=axis, dtype=dtype,\n",
|
| 907 |
+
"/admin/home-paulscotti/found/lib/python3.10/site-packages/numpy/core/_methods.py:129: RuntimeWarning: invalid value encountered in scalar divide\n",
|
| 908 |
+
" ret = ret.dtype.type(ret / rcount)\n"
|
| 909 |
+
]
|
| 910 |
+
},
|
| 911 |
+
{
|
| 912 |
+
"name": "stdout",
|
| 913 |
+
"output_type": "stream",
|
| 914 |
+
"text": [
|
| 915 |
+
"0 0.2265625 2.132457733154297\n",
|
| 916 |
+
"test 0 0.10546875 2.0733165740966797\n",
|
| 917 |
+
"test 1 0.1875 1.9566822052001953\n",
|
| 918 |
+
"{'train/loss': 0.646026149392128, 'train/recon_losses': nan, 'train/contrastive_losses': 6.460261344909668, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6077880859375, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.294189453125, 'train/probe_losses': 2.132457733154297, 'train/probe_accs': 0.2265625, 'test/probe_losses': 2.0149993896484375, 'test/probe_accs': 0.146484375, 'lr': 3e-06, 'epoch': 1, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 919 |
+
"0 0.123046875 2.136058807373047\n",
|
| 920 |
+
"test 0 0.119140625 2.041532516479492\n",
|
| 921 |
+
"test 1 0.275390625 1.9539203643798828\n",
|
| 922 |
+
"{'train/loss': 0.6089650392532349, 'train/recon_losses': nan, 'train/contrastive_losses': 6.0896501541137695, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.609375, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.3001708984375, 'train/probe_losses': 2.136058807373047, 'train/probe_accs': 0.123046875, 'test/probe_losses': 1.9977264404296875, 'test/probe_accs': 0.197265625, 'lr': 3e-06, 'epoch': 2, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 923 |
+
"0 0.294921875 1.9231395721435547\n",
|
| 924 |
+
"test 0 0.123046875 1.9924964904785156\n",
|
| 925 |
+
"test 1 0.232421875 2.002117156982422\n",
|
| 926 |
+
"{'train/loss': 0.6081273704767227, 'train/recon_losses': nan, 'train/contrastive_losses': 6.081273555755615, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6173095703125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.29730224609375, 'train/probe_losses': 1.9231395721435547, 'train/probe_accs': 0.294921875, 'test/probe_losses': 1.9973068237304688, 'test/probe_accs': 0.177734375, 'lr': 3e-06, 'epoch': 3, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 927 |
+
"0 0.15625 1.9835872650146484\n",
|
| 928 |
+
"test 0 0.162109375 2.0044002532958984\n",
|
| 929 |
+
"test 1 0.181640625 1.993692398071289\n",
|
| 930 |
+
"{'train/loss': 0.6120030879974365, 'train/recon_losses': nan, 'train/contrastive_losses': 6.120030879974365, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.61279296875, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.30224609375, 'train/probe_losses': 1.9835872650146484, 'train/probe_accs': 0.15625, 'test/probe_losses': 1.9990463256835938, 'test/probe_accs': 0.171875, 'lr': 3e-06, 'epoch': 4, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 931 |
+
"0 0.08203125 2.095348358154297\n",
|
| 932 |
+
"test 0 0.052734375 2.236387252807617\n",
|
| 933 |
+
"test 1 0.0703125 2.2432994842529297\n",
|
| 934 |
+
"{'train/loss': 0.6117484122514725, 'train/recon_losses': nan, 'train/contrastive_losses': 6.117484092712402, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6046142578125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.3111572265625, 'train/probe_losses': 2.095348358154297, 'train/probe_accs': 0.08203125, 'test/probe_losses': 2.2398433685302734, 'test/probe_accs': 0.0615234375, 'lr': 3e-06, 'epoch': 5, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 935 |
+
"0 0.15625 2.082639694213867\n",
|
| 936 |
+
"test 0 0.01171875 2.237241744995117\n",
|
| 937 |
+
"test 1 0.05859375 2.2262916564941406\n",
|
| 938 |
+
"{'train/loss': 0.610412523150444, 'train/recon_losses': nan, 'train/contrastive_losses': 6.104125022888184, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6123046875, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.30657958984375, 'train/probe_losses': 2.082639694213867, 'train/probe_accs': 0.15625, 'test/probe_losses': 2.231766700744629, 'test/probe_accs': 0.03515625, 'lr': 3e-06, 'epoch': 6, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 939 |
+
"0 0.00390625 2.2390995025634766\n",
|
| 940 |
+
"test 0 0.1015625 2.056934356689453\n",
|
| 941 |
+
"test 1 0.064453125 1.9691143035888672\n",
|
| 942 |
+
"{'train/loss': 0.6085265427827835, 'train/recon_losses': nan, 'train/contrastive_losses': 6.085265159606934, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.63623046875, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31298828125, 'train/probe_losses': 2.2390995025634766, 'train/probe_accs': 0.00390625, 'test/probe_losses': 2.01302433013916, 'test/probe_accs': 0.0830078125, 'lr': 3e-06, 'epoch': 7, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 943 |
+
"0 0.494140625 1.8124122619628906\n",
|
| 944 |
+
"test 0 0.392578125 1.8616065979003906\n",
|
| 945 |
+
"test 1 0.3828125 1.8176498413085938\n",
|
| 946 |
+
"{'train/loss': 0.6146479845046997, 'train/recon_losses': nan, 'train/contrastive_losses': 6.146479606628418, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.613525390625, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31793212890625, 'train/probe_losses': 1.8124122619628906, 'train/probe_accs': 0.494140625, 'test/probe_losses': 1.8396282196044922, 'test/probe_accs': 0.3876953125, 'lr': 3e-06, 'epoch': 8, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 947 |
+
"0 0.021484375 2.2320098876953125\n",
|
| 948 |
+
"test 0 0.025390625 2.181182861328125\n",
|
| 949 |
+
"test 1 0.033203125 2.175107955932617\n",
|
| 950 |
+
"{'train/loss': 0.6042157411575317, 'train/recon_losses': nan, 'train/contrastive_losses': 6.042157173156738, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6165771484375, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31536865234375, 'train/probe_losses': 2.2320098876953125, 'train/probe_accs': 0.021484375, 'test/probe_losses': 2.178145408630371, 'test/probe_accs': 0.029296875, 'lr': 3e-06, 'epoch': 9, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 951 |
+
"0 0.04296875 2.068845748901367\n",
|
| 952 |
+
"test 0 0.095703125 2.147918701171875\n",
|
| 953 |
+
"test 1 0.275390625 2.0189361572265625\n",
|
| 954 |
+
"{'train/loss': 0.6057839542627335, 'train/recon_losses': nan, 'train/contrastive_losses': 6.057839393615723, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6259765625, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.30718994140625, 'train/probe_losses': 2.068845748901367, 'train/probe_accs': 0.04296875, 'test/probe_losses': 2.0834274291992188, 'test/probe_accs': 0.185546875, 'lr': 3e-06, 'epoch': 10, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 955 |
+
"0 0.248046875 2.012929916381836\n",
|
| 956 |
+
"test 0 0.1875 2.1199779510498047\n",
|
| 957 |
+
"test 1 0.177734375 2.0317916870117188\n",
|
| 958 |
+
"{'train/loss': 0.6008661240339279, 'train/recon_losses': nan, 'train/contrastive_losses': 6.008661270141602, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.624267578125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.3172607421875, 'train/probe_losses': 2.012929916381836, 'train/probe_accs': 0.248046875, 'test/probe_losses': 2.0758848190307617, 'test/probe_accs': 0.1826171875, 'lr': 3e-06, 'epoch': 11, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 959 |
+
"0 0.15625 2.024129867553711\n",
|
| 960 |
+
"test 0 0.361328125 1.8665599822998047\n",
|
| 961 |
+
"test 1 0.22265625 1.9156570434570312\n",
|
| 962 |
+
"{'train/loss': 0.6274944394826889, 'train/recon_losses': nan, 'train/contrastive_losses': 6.274944305419922, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.62109375, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.3148193359375, 'train/probe_losses': 2.024129867553711, 'train/probe_accs': 0.15625, 'test/probe_losses': 1.891108512878418, 'test/probe_accs': 0.2919921875, 'lr': 3e-06, 'epoch': 12, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 963 |
+
"0 0.046875 2.0176239013671875\n",
|
| 964 |
+
"test 0 0.140625 1.9483413696289062\n",
|
| 965 |
+
"test 1 0.287109375 1.853952407836914\n",
|
| 966 |
+
"{'train/loss': 0.5929619073867798, 'train/recon_losses': nan, 'train/contrastive_losses': 5.929618835449219, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6199951171875, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31878662109375, 'train/probe_losses': 2.0176239013671875, 'train/probe_accs': 0.046875, 'test/probe_losses': 1.9011468887329102, 'test/probe_accs': 0.2138671875, 'lr': 3e-06, 'epoch': 13, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 967 |
+
"0 0.025390625 2.3530330657958984\n",
|
| 968 |
+
"test 0 0.22265625 1.9583473205566406\n",
|
| 969 |
+
"test 1 0.26171875 2.0058231353759766\n",
|
| 970 |
+
"{'train/loss': 0.6034502238035202, 'train/recon_losses': nan, 'train/contrastive_losses': 6.034502029418945, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.636962890625, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31683349609375, 'train/probe_losses': 2.3530330657958984, 'train/probe_accs': 0.025390625, 'test/probe_losses': 1.9820852279663086, 'test/probe_accs': 0.2421875, 'lr': 3e-06, 'epoch': 14, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 971 |
+
"0 0.037109375 2.5336132049560547\n",
|
| 972 |
+
"test 0 0.06640625 2.2813949584960938\n",
|
| 973 |
+
"test 1 0.345703125 1.9364681243896484\n",
|
| 974 |
+
"{'train/loss': 0.5937714576721191, 'train/recon_losses': nan, 'train/contrastive_losses': 5.937714576721191, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6195068359375, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31719970703125, 'train/probe_losses': 2.5336132049560547, 'train/probe_accs': 0.037109375, 'test/probe_losses': 2.108931541442871, 'test/probe_accs': 0.2060546875, 'lr': 3e-06, 'epoch': 15, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 975 |
+
"0 0.080078125 2.155559539794922\n",
|
| 976 |
+
"test 0 0.056640625 2.157787322998047\n",
|
| 977 |
+
"test 1 0.146484375 2.017892837524414\n",
|
| 978 |
+
"{'train/loss': 0.6005350202322006, 'train/recon_losses': nan, 'train/contrastive_losses': 6.005350112915039, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6202392578125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.3182373046875, 'train/probe_losses': 2.155559539794922, 'train/probe_accs': 0.080078125, 'test/probe_losses': 2.0878400802612305, 'test/probe_accs': 0.1015625, 'lr': 3e-06, 'epoch': 16, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 979 |
+
"0 0.111328125 2.099821090698242\n",
|
| 980 |
+
"test 0 0.4296875 1.8980712890625\n",
|
| 981 |
+
"test 1 0.294921875 1.9661064147949219\n",
|
| 982 |
+
"{'train/loss': 0.6011319160461426, 'train/recon_losses': nan, 'train/contrastive_losses': 6.011319160461426, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6202392578125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31549072265625, 'train/probe_losses': 2.099821090698242, 'train/probe_accs': 0.111328125, 'test/probe_losses': 1.932088851928711, 'test/probe_accs': 0.3623046875, 'lr': 3e-06, 'epoch': 17, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 983 |
+
"0 0.119140625 2.0135326385498047\n",
|
| 984 |
+
"test 0 0.265625 1.861032485961914\n",
|
| 985 |
+
"test 1 0.158203125 1.972625732421875\n",
|
| 986 |
+
"{'train/loss': 0.5891992747783661, 'train/recon_losses': nan, 'train/contrastive_losses': 5.891992568969727, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.6190185546875, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.316650390625, 'train/probe_losses': 2.0135326385498047, 'train/probe_accs': 0.119140625, 'test/probe_losses': 1.9168291091918945, 'test/probe_accs': 0.2119140625, 'lr': 3e-06, 'epoch': 18, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n",
|
| 987 |
+
"0 0.181640625 2.0815296173095703\n",
|
| 988 |
+
"test 0 0.154296875 2.060464859008789\n",
|
| 989 |
+
"test 1 0.46875 1.8413829803466797\n",
|
| 990 |
+
"{'train/loss': 0.5968149304389954, 'train/recon_losses': nan, 'train/contrastive_losses': 5.968149185180664, 'train/num_steps': 0, 'train/cos_sim_encoder_output': 0.61767578125, 'train/cos_sim_decoder_output': nan, 'train/cos_sim_encoder_output_patchwise': 0.31536865234375, 'train/probe_losses': 2.0815296173095703, 'train/probe_accs': 0.181640625, 'test/probe_losses': 1.9509239196777344, 'test/probe_accs': 0.3115234375, 'lr': 3e-06, 'epoch': 19, 'tube_mask_ratio': 0.75, 'decoder_mask_ratio': 0.75}\n"
|
| 991 |
+
]
|
| 992 |
+
}
|
| 993 |
+
],
|
| 994 |
+
"source": [
|
| 995 |
+
"mse = nn.MSELoss()\n",
|
| 996 |
+
"l1 = nn.L1Loss()\n",
|
| 997 |
+
"crossentropy = nn.CrossEntropyLoss()\n",
|
| 998 |
+
"if use_contrastive_loss:\n",
|
| 999 |
+
" contrastive_temps = utils.cosine_anneal(0.004, 0.0075, num_epochs)\n",
|
| 1000 |
+
"progress_bar = tqdm(range(epoch, num_epochs), disable=local_rank!=0, desc=\"Overall\")\n",
|
| 1001 |
+
"for epoch in progress_bar:\n",
|
| 1002 |
+
" # get the masking ratio for the current epoch\n",
|
| 1003 |
+
" tube_mask_ratio = utils.get_masking_ratio(\n",
|
| 1004 |
+
" current_epoch=epoch, \n",
|
| 1005 |
+
" total_epochs=num_epochs, \n",
|
| 1006 |
+
" start_masking_ratio=tube_start_masking_ratio, \n",
|
| 1007 |
+
" end_masking_ratio=tube_end_masking_ratio\n",
|
| 1008 |
+
" )\n",
|
| 1009 |
+
" with torch.cuda.amp.autocast(dtype=data_type):\n",
|
| 1010 |
+
" model.train()\n",
|
| 1011 |
+
" optimizer.train()\n",
|
| 1012 |
+
" for train_i, batch in enumerate(train_dl):\n",
|
| 1013 |
+
" optimizer.zero_grad()\n",
|
| 1014 |
+
"\n",
|
| 1015 |
+
" input_func = batch['func.npy']\n",
|
| 1016 |
+
"\n",
|
| 1017 |
+
" subject_id = batch['subject_id.txt']\n",
|
| 1018 |
+
" subject_id = torch.Tensor([int(subject[-2:]) for subject in subject_id]).long()\n",
|
| 1019 |
+
" subject_id = torch.repeat_interleave(subject_id.long(), 2).to(device)\n",
|
| 1020 |
+
" # session_id = batch['session_id.txt']\n",
|
| 1021 |
+
" # session_id = torch.Tensor([int(session[-2:]) for session in session_id]).long().repeat(2).to(device)\n",
|
| 1022 |
+
" # session_id = torch.repeat_interleave(session_id.long(), 2)\n",
|
| 1023 |
+
"\n",
|
| 1024 |
+
" if masking_strategy==\"None\":\n",
|
| 1025 |
+
" func, _ = aug_transform(input_func)\n",
|
| 1026 |
+
" brain_pos_pats_vit = torch.ones(num_patches_per_timepoint)\n",
|
| 1027 |
+
" elif masking_strategy==\"MNI\":\n",
|
| 1028 |
+
" func, _ = aug_transform(input_func)\n",
|
| 1029 |
+
" else:\n",
|
| 1030 |
+
" func, brain_pos_voxels = aug_transform(input_func)\n",
|
| 1031 |
+
" brain_pos_pats = model.patchify(torch.Tensor(brain_pos_voxels)[None,None,None])\n",
|
| 1032 |
+
" brain_pos_pats_vit = rearrange(brain_pos_pats, \"b ... d -> b (...) d\").mean(-1)[0]\n",
|
| 1033 |
+
"\n",
|
| 1034 |
+
" func = func.reshape(-1, num_frames, \n",
|
| 1035 |
+
" func.shape[-3], func.shape[-2], func.shape[-1])\n",
|
| 1036 |
+
" func = func.unsqueeze(1).clamp(0,1)\n",
|
| 1037 |
+
" \n",
|
| 1038 |
+
" # create encoder and decoder masks\n",
|
| 1039 |
+
" rand_patches = torch.randperm(num_patches_per_timepoint)\n",
|
| 1040 |
+
" \n",
|
| 1041 |
+
" encoder_mask = torch.zeros(num_patches_per_timepoint).to(torch.bool)\n",
|
| 1042 |
+
" encoder_mask[rand_patches[:num_encoder_patches]] = True\n",
|
| 1043 |
+
" encoder_mask = encoder_mask.tile(num_frames//frame_patch_size)\n",
|
| 1044 |
+
" \n",
|
| 1045 |
+
" decoder_mask = torch.zeros(num_patches_per_timepoint).to(torch.bool)\n",
|
| 1046 |
+
" decoder_mask[rand_patches[num_encoder_patches:num_encoder_patches+num_decoder_patches]] = True\n",
|
| 1047 |
+
" decoder_mask = decoder_mask.tile(num_frames//frame_patch_size)\n",
|
| 1048 |
+
"\n",
|
| 1049 |
+
" # encode the tube patches\n",
|
| 1050 |
+
" encoder_out = model(func, encoder_mask=encoder_mask, device=device)\n",
|
| 1051 |
+
" if use_cls_token:\n",
|
| 1052 |
+
" enc_cls_token = encoder_out[:,:1,:]\n",
|
| 1053 |
+
"\n",
|
| 1054 |
+
" # decode both the encoder_out patches and masked decoder patches\n",
|
| 1055 |
+
" decoder_out = model(encoder_out, encoder_mask=encoder_mask, decoder_mask=decoder_mask, device=device)\n",
|
| 1056 |
+
" # subset only the reconstructed decoder patches\n",
|
| 1057 |
+
" output = decoder_out[:, -decoder_mask.sum():]\n",
|
| 1058 |
+
"\n",
|
| 1059 |
+
" # compare to ground truth and calculate loss\n",
|
| 1060 |
+
" target_patches = model.patchify(func)\n",
|
| 1061 |
+
" target_patches_vit = rearrange(target_patches, \"b ... d -> b (...) d\")\n",
|
| 1062 |
+
" target = target_patches_vit.to(device)[:, decoder_mask]\n",
|
| 1063 |
+
"\n",
|
| 1064 |
+
" target_mean = target.mean(0)\n",
|
| 1065 |
+
" target_std = target.std(0)\n",
|
| 1066 |
+
" target_normed = (target - target_mean) / (target_std + 1e-6)\n",
|
| 1067 |
+
"\n",
|
| 1068 |
+
" recon_loss = mse(output, target_normed)\n",
|
| 1069 |
+
" recon_losses.append(recon_loss.item())\n",
|
| 1070 |
+
" loss = recon_loss\n",
|
| 1071 |
+
"\n",
|
| 1072 |
+
" # contrastive loss\n",
|
| 1073 |
+
" if use_contrastive_loss:\n",
|
| 1074 |
+
" # encode the decoder patches\n",
|
| 1075 |
+
" encoder_out2 = model(func, encoder_mask=decoder_mask, device=device)\n",
|
| 1076 |
+
" enc_cls_token2 = encoder_out2[:,:1,:]\n",
|
| 1077 |
+
" \n",
|
| 1078 |
+
" temp = contrastive_temps[epoch]\n",
|
| 1079 |
+
" \n",
|
| 1080 |
+
" logits = (nn.functional.normalize(enc_cls_token.flatten(1),dim=-1) @\n",
|
| 1081 |
+
" nn.functional.normalize(enc_cls_token2.flatten(1),dim=-1).T) / temp\n",
|
| 1082 |
+
" \n",
|
| 1083 |
+
" # logits = (nn.functional.normalize(model.cont(encoder_out.flatten(1)),dim=-1) @\n",
|
| 1084 |
+
" # nn.functional.normalize(model.cont(encoder_out2.flatten(1)),dim=-1).T) / temp\n",
|
| 1085 |
+
" \n",
|
| 1086 |
+
" labels = torch.arange(len(logits)).long().to(device)\n",
|
| 1087 |
+
" loss1 = crossentropy(logits, labels)\n",
|
| 1088 |
+
" # loss1 = -(logits.log_softmax(-1) * labels.softmax(-1)).sum(-1).mean()\n",
|
| 1089 |
+
" loss2 = crossentropy(logits.T, labels)\n",
|
| 1090 |
+
" contr_loss = (loss1 + loss2)/2\n",
|
| 1091 |
+
" \n",
|
| 1092 |
+
" contrastive_losses.append(contr_loss.item())\n",
|
| 1093 |
+
" loss += (contr_loss * contrastive_loss_weight)\n",
|
| 1094 |
+
"\n",
|
| 1095 |
+
" cos_sim_encoder_output_patchwise.append(utils.patchwise_cosine_similarity(encoder_out).mean().item())\n",
|
| 1096 |
+
" cos_sim_encoder_output.append(utils.batchwise_cosine_similarity(encoder_out.flatten(1)/1e3,encoder_out.flatten(1)/1e3)[~torch.eye(len(encoder_out),dtype=torch.bool)].mean().item())\n",
|
| 1097 |
+
" cos_sim_decoder_output.append(utils.batchwise_cosine_similarity(output,output)[~torch.eye(len(output),dtype=torch.bool)].mean().item())\n",
|
| 1098 |
+
"\n",
|
| 1099 |
+
" loss.backward()\n",
|
| 1100 |
+
" optimizer.step()\n",
|
| 1101 |
+
" lrs.append(optimizer.param_groups[0][\"lr\"])\n",
|
| 1102 |
+
" train_losses.append(loss.item())\n",
|
| 1103 |
+
"\n",
|
| 1104 |
+
" if train_i >= (num_iterations_per_epoch-1):\n",
|
| 1105 |
+
" break\n",
|
| 1106 |
+
"\n",
|
| 1107 |
+
" # reset linear_probe\n",
|
| 1108 |
+
" # if use_cls_token:\n",
|
| 1109 |
+
" # linear_probe = LinearProbe((num_patches_per_timepoint+1)*model.encoder_embed_dim)\n",
|
| 1110 |
+
" # else:\n",
|
| 1111 |
+
" # linear_probe = LinearProbe(num_patches_per_timepoint*model.encoder_embed_dim)\n",
|
| 1112 |
+
" linear_probe = LinearProbe(model.encoder_embed_dim)\n",
|
| 1113 |
+
" linear_probe = linear_probe.to(device)\n",
|
| 1114 |
+
" probe_opt_grouped_parameters = [\n",
|
| 1115 |
+
" {'params': [p for n, p in linear_probe.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 1e-2},\n",
|
| 1116 |
+
" {'params': [p for n, p in linear_probe.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},\n",
|
| 1117 |
+
" ]\n",
|
| 1118 |
+
" probe_optimizer = torch.optim.AdamW(probe_opt_grouped_parameters, lr=3e-3)\n",
|
| 1119 |
+
"\n",
|
| 1120 |
+
" if True:#(epoch % 5 == 0) or (epoch == num_epochs-1):\n",
|
| 1121 |
+
" model.eval()\n",
|
| 1122 |
+
" optimizer.eval()\n",
|
| 1123 |
+
" linear_probe.train()\n",
|
| 1124 |
+
" for probe_i, batch in enumerate(train_dl):\n",
|
| 1125 |
+
" probe_optimizer.zero_grad()\n",
|
| 1126 |
+
"\n",
|
| 1127 |
+
" input_func = batch['func.npy']\n",
|
| 1128 |
+
"\n",
|
| 1129 |
+
" subject_id = batch['subject_id.txt']\n",
|
| 1130 |
+
" subject_id = torch.Tensor([int(subject[-2:]) for subject in subject_id]).long()\n",
|
| 1131 |
+
" subject_id = torch.repeat_interleave(subject_id.long(), 2).to(device)\n",
|
| 1132 |
+
"\n",
|
| 1133 |
+
" func, _ = aug_transform(input_func)\n",
|
| 1134 |
+
" func = func.reshape(-1, num_frames, \n",
|
| 1135 |
+
" func.shape[-3], func.shape[-2], func.shape[-1])\n",
|
| 1136 |
+
" func = func.unsqueeze(1).clamp(0,1)\n",
|
| 1137 |
+
"\n",
|
| 1138 |
+
" encoder_mask = torch.ones(num_patches_per_timepoint).to(torch.bool)\n",
|
| 1139 |
+
" encoder_mask = encoder_mask.tile(num_frames//frame_patch_size)\n",
|
| 1140 |
+
"\n",
|
| 1141 |
+
" # encode the tube patches\n",
|
| 1142 |
+
" with torch.no_grad():\n",
|
| 1143 |
+
" encoder_out = model(func, encoder_mask=encoder_mask, device=device)\n",
|
| 1144 |
+
" encoder_out = encoder_out[:,:1,:]\n",
|
| 1145 |
+
" encoder_out = nn.functional.normalize(encoder_out,dim=-1)\n",
|
| 1146 |
+
"\n",
|
| 1147 |
+
" # linear probe\n",
|
| 1148 |
+
" subject_pred = linear_probe(encoder_out.flatten(1).to(device))\n",
|
| 1149 |
+
" probe_loss = crossentropy(subject_pred, subject_id-1) # minus 1 because subject_id is 1-indexed\n",
|
| 1150 |
+
"\n",
|
| 1151 |
+
" probe_accuracy = (torch.max(subject_pred,1).indices == (subject_id-1)).sum() / len(subject_id)\n",
|
| 1152 |
+
" probe_accs.append(probe_accuracy.item())\n",
|
| 1153 |
+
" probe_losses.append(probe_loss.item())\n",
|
| 1154 |
+
"\n",
|
| 1155 |
+
" print(probe_i, probe_accuracy.item(), probe_loss.item())\n",
|
| 1156 |
+
"\n",
|
| 1157 |
+
" probe_loss.backward()\n",
|
| 1158 |
+
" probe_optimizer.step()\n",
|
| 1159 |
+
"\n",
|
| 1160 |
+
" if probe_i >= (probe_num_iterations_per_epoch-1):\n",
|
| 1161 |
+
" break\n",
|
| 1162 |
+
"\n",
|
| 1163 |
+
" for test_i, batch in enumerate(test_dl):\n",
|
| 1164 |
+
" input_func = batch['func.npy']\n",
|
| 1165 |
+
"\n",
|
| 1166 |
+
" subject_id = batch['subject_id.txt']\n",
|
| 1167 |
+
" subject_id = torch.Tensor([int(subject[-2:]) for subject in subject_id]).long()\n",
|
| 1168 |
+
" subject_id = torch.repeat_interleave(subject_id.long(), 2).to(device)\n",
|
| 1169 |
+
"\n",
|
| 1170 |
+
" func, _ = aug_transform(input_func)\n",
|
| 1171 |
+
" func = func.reshape(-1, num_frames, \n",
|
| 1172 |
+
" func.shape[-3], func.shape[-2], func.shape[-1])\n",
|
| 1173 |
+
" func = func.unsqueeze(1).clamp(0,1)\n",
|
| 1174 |
+
"\n",
|
| 1175 |
+
" encoder_mask = torch.ones(num_patches_per_timepoint).to(torch.bool)\n",
|
| 1176 |
+
" encoder_mask = encoder_mask.tile(num_frames//frame_patch_size)\n",
|
| 1177 |
+
"\n",
|
| 1178 |
+
" # encode the tube patches\n",
|
| 1179 |
+
" with torch.no_grad():\n",
|
| 1180 |
+
" encoder_out = model(func, encoder_mask=encoder_mask, device=device)\n",
|
| 1181 |
+
" encoder_out = encoder_out[:,:1,:]\n",
|
| 1182 |
+
" encoder_out = nn.functional.normalize(encoder_out,dim=-1)\n",
|
| 1183 |
+
"\n",
|
| 1184 |
+
" # linear probe\n",
|
| 1185 |
+
" subject_pred = linear_probe(encoder_out.flatten(1).to(device))\n",
|
| 1186 |
+
" test_loss = crossentropy(subject_pred, subject_id-1) # minus 1 because subject_id is 1-indexed\n",
|
| 1187 |
+
"\n",
|
| 1188 |
+
" test_accuracy = (torch.max(subject_pred,1).indices == (subject_id-1)).sum() / len(subject_id)\n",
|
| 1189 |
+
" test_accs.append(test_accuracy.item())\n",
|
| 1190 |
+
" test_losses.append(test_loss.item())\n",
|
| 1191 |
+
"\n",
|
| 1192 |
+
" print(\"test\", test_i, test_accuracy.item(), test_loss.item())\n",
|
| 1193 |
+
"\n",
|
| 1194 |
+
" if test_i >= 1:\n",
|
| 1195 |
+
" break\n",
|
| 1196 |
+
"\n",
|
| 1197 |
+
" logs = {\n",
|
| 1198 |
+
" \"train/loss\": np.mean(train_losses[-(train_i + 1) :]),\n",
|
| 1199 |
+
" \"train/recon_losses\": np.mean(recon_losses[-(train_i + 1) :]),\n",
|
| 1200 |
+
" \"train/contrastive_losses\": np.mean(contrastive_losses[-(train_i + 1) :]),\n",
|
| 1201 |
+
" \"train/num_steps\": len(recon_losses),\n",
|
| 1202 |
+
" \"train/cos_sim_encoder_output\": np.mean(cos_sim_encoder_output[-(train_i + 1) :]),\n",
|
| 1203 |
+
" \"train/cos_sim_decoder_output\": np.mean(cos_sim_decoder_output[-(train_i + 1) :]),\n",
|
| 1204 |
+
" \"train/cos_sim_encoder_output_patchwise\": np.mean(cos_sim_encoder_output_patchwise[-(train_i + 1) :]),\n",
|
| 1205 |
+
" \"train/probe_losses\": np.mean(probe_losses[-(probe_i + 1) :]),\n",
|
| 1206 |
+
" \"train/probe_accs\": np.mean(probe_accs[-(probe_i + 1) :]),\n",
|
| 1207 |
+
" \"test/probe_losses\": np.mean(test_losses[-(test_i + 1) :]),\n",
|
| 1208 |
+
" \"test/probe_accs\": np.mean(test_accs[-(test_i + 1) :]),\n",
|
| 1209 |
+
" \"lr\": np.mean(lrs[-(train_i + 1) :]),\n",
|
| 1210 |
+
" \"epoch\": epoch,\n",
|
| 1211 |
+
" \"tube_mask_ratio\": tube_mask_ratio,\n",
|
| 1212 |
+
" \"decoder_mask_ratio\": decoder_mask_ratio,\n",
|
| 1213 |
+
" }\n",
|
| 1214 |
+
" progress_bar.set_postfix(**logs)\n",
|
| 1215 |
+
" if utils.is_interactive(): print(logs)\n",
|
| 1216 |
+
"\n",
|
| 1217 |
+
" # Plot progress (first sample in batch)\n",
|
| 1218 |
+
" with torch.no_grad():\n",
|
| 1219 |
+
" if utils.is_interactive() or wandb_log:\n",
|
| 1220 |
+
" if epoch % 50 == 0:\n",
|
| 1221 |
+
" output = (output * target_std) + target_mean\n",
|
| 1222 |
+
" idx = 0\n",
|
| 1223 |
+
" \n",
|
| 1224 |
+
" decode_vis = torch.zeros_like(target_patches_vit)\n",
|
| 1225 |
+
" decode_vis[:, decoder_mask] = output.to(decode_vis.device).to(decode_vis.dtype)\n",
|
| 1226 |
+
" decoder_unpatches = rearrange(\n",
|
| 1227 |
+
" decode_vis,\n",
|
| 1228 |
+
" \"b (f d h w) c -> b f d h w c\",\n",
|
| 1229 |
+
" d=img_size[0]//patch_depth,\n",
|
| 1230 |
+
" h=img_size[1]//patch_height,\n",
|
| 1231 |
+
" w=img_size[2]//patch_width,\n",
|
| 1232 |
+
" )\n",
|
| 1233 |
+
" decoder_func = rearrange(\n",
|
| 1234 |
+
" decoder_unpatches,\n",
|
| 1235 |
+
" \"b f d h w (pd ph pw pf c) -> b c (f pf) (d pd) (h ph) (w pw)\",\n",
|
| 1236 |
+
" b=batch_size*2,\n",
|
| 1237 |
+
" f=num_frames//frame_patch_size,\n",
|
| 1238 |
+
" d=img_size[0]//patch_depth,\n",
|
| 1239 |
+
" h=img_size[1]//patch_height,\n",
|
| 1240 |
+
" w=img_size[2]//patch_width,\n",
|
| 1241 |
+
" pd=patch_depth,\n",
|
| 1242 |
+
" ph=patch_height,\n",
|
| 1243 |
+
" pw=patch_width,\n",
|
| 1244 |
+
" pf=frame_patch_size,\n",
|
| 1245 |
+
" )\n",
|
| 1246 |
+
" orig_image = utils.reshape_to_2d(func[idx])\n",
|
| 1247 |
+
" recon_image = utils.reshape_to_2d(decoder_func[idx])\n",
|
| 1248 |
+
"\n",
|
| 1249 |
+
" combined_image = orig_image.clone()\n",
|
| 1250 |
+
" combined_image[recon_image!=0] = recon_image[recon_image!=0]\n",
|
| 1251 |
+
"\n",
|
| 1252 |
+
" random_start = np.arange(3100,3450)\n",
|
| 1253 |
+
" orig_image = transforms.ToPILImage()(orig_image[:,random_start])\n",
|
| 1254 |
+
" recon_image = transforms.ToPILImage()(recon_image[:,random_start])\n",
|
| 1255 |
+
" combined_image = transforms.ToPILImage()(combined_image[:,random_start])\n",
|
| 1256 |
+
"\n",
|
| 1257 |
+
" if wandb_log:\n",
|
| 1258 |
+
" logs[f\"train/orig\"] = wandb.Image(orig_image, caption=f\"epoch{epoch:03d}\")\n",
|
| 1259 |
+
" logs[f\"train/recon\"] = wandb.Image(recon_image, caption=f\"epoch{epoch:03d}\")\n",
|
| 1260 |
+
" logs[f\"train/combined\"] = wandb.Image(combined_image, caption=f\"epoch{epoch:03d}\")\n",
|
| 1261 |
+
" else:\n",
|
| 1262 |
+
" if epoch==0:\n",
|
| 1263 |
+
" print(\"orig_image\")\n",
|
| 1264 |
+
" display(orig_image)\n",
|
| 1265 |
+
" print(\"recon_image\")\n",
|
| 1266 |
+
" display(recon_image)\n",
|
| 1267 |
+
" print(\"combined_image\")\n",
|
| 1268 |
+
" display(combined_image)\n",
|
| 1269 |
+
"\n",
|
| 1270 |
+
" if wandb_log: wandb.log(logs)\n",
|
| 1271 |
+
"\n",
|
| 1272 |
+
" # Save model checkpoint\n",
|
| 1273 |
+
" if (ckpt_saving) and ((epoch % ckpt_interval == 0) or (epoch==num_epochs-1)):\n",
|
| 1274 |
+
" save_ckpt(model,\"last\")\n",
|
| 1275 |
+
"\n",
|
| 1276 |
+
" # wait for other GPUs to catch up if needed\n",
|
| 1277 |
+
" if distributed: dist.barrier()\n",
|
| 1278 |
+
" torch.cuda.empty_cache()\n",
|
| 1279 |
+
" \n",
|
| 1280 |
+
"if distributed:\n",
|
| 1281 |
+
" dist.destroy_process_group()"
|
| 1282 |
+
]
|
| 1283 |
+
},
|
| 1284 |
+
{
|
| 1285 |
+
"cell_type": "code",
|
| 1286 |
+
"execution_count": null,
|
| 1287 |
+
"id": "a6779720-0d08-4aec-87e2-439e0c1e9dca",
|
| 1288 |
+
"metadata": {
|
| 1289 |
+
"tags": []
|
| 1290 |
+
},
|
| 1291 |
+
"outputs": [],
|
| 1292 |
+
"source": [
|
| 1293 |
+
"encoder_mask.device"
|
| 1294 |
+
]
|
| 1295 |
+
},
|
| 1296 |
+
{
|
| 1297 |
+
"cell_type": "code",
|
| 1298 |
+
"execution_count": null,
|
| 1299 |
+
"id": "1840c426-d7f5-4d0c-acd1-5ea47d453b91",
|
| 1300 |
+
"metadata": {
|
| 1301 |
+
"tags": []
|
| 1302 |
+
},
|
| 1303 |
+
"outputs": [],
|
| 1304 |
+
"source": [
|
| 1305 |
+
"decoder_mask.sum()"
|
| 1306 |
+
]
|
| 1307 |
+
},
|
| 1308 |
+
{
|
| 1309 |
+
"cell_type": "code",
|
| 1310 |
+
"execution_count": null,
|
| 1311 |
+
"id": "fff6e847-2511-4f69-aa70-f1471a5b7d07",
|
| 1312 |
+
"metadata": {
|
| 1313 |
+
"tags": []
|
| 1314 |
+
},
|
| 1315 |
+
"outputs": [],
|
| 1316 |
+
"source": [
|
| 1317 |
+
"plt.figure(figsize=(8, 3))\n",
|
| 1318 |
+
"plt.plot(probe_losses)\n",
|
| 1319 |
+
"# plt.title(\"Training re-construction losses\")\n",
|
| 1320 |
+
"plt.show()\n",
|
| 1321 |
+
"if use_contrastive_loss:\n",
|
| 1322 |
+
" plt.figure(figsize=(8, 3))\n",
|
| 1323 |
+
" plt.plot(contrastive_losses)\n",
|
| 1324 |
+
" plt.title(\"Training contrastive losses\")\n",
|
| 1325 |
+
" plt.show()"
|
| 1326 |
+
]
|
| 1327 |
+
}
|
| 1328 |
+
],
|
| 1329 |
+
"metadata": {
|
| 1330 |
+
"kernelspec": {
|
| 1331 |
+
"display_name": "fmri_new",
|
| 1332 |
+
"language": "python",
|
| 1333 |
+
"name": "fmri_new"
|
| 1334 |
+
},
|
| 1335 |
+
"language_info": {
|
| 1336 |
+
"codemirror_mode": {
|
| 1337 |
+
"name": "ipython",
|
| 1338 |
+
"version": 3
|
| 1339 |
+
},
|
| 1340 |
+
"file_extension": ".py",
|
| 1341 |
+
"mimetype": "text/x-python",
|
| 1342 |
+
"name": "python",
|
| 1343 |
+
"nbconvert_exporter": "python",
|
| 1344 |
+
"pygments_lexer": "ipython3",
|
| 1345 |
+
"version": "3.8.10"
|
| 1346 |
+
}
|
| 1347 |
+
},
|
| 1348 |
+
"nbformat": 4,
|
| 1349 |
+
"nbformat_minor": 5
|
| 1350 |
+
}
|
fMRI-foundation-model-old/fMRI-MAE/accel.slurm
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH --account=fmri
|
| 3 |
+
#SBATCH --partition=h80i
|
| 4 |
+
#SBATCH --job-name=found
|
| 5 |
+
#SBATCH --ntasks-per-node=1
|
| 6 |
+
#SBATCH --nodes=1
|
| 7 |
+
#SBATCH --gres=gpu:1
|
| 8 |
+
#SBATCH --gpus-per-task=1 # Set to equal gres=gpu:#!
|
| 9 |
+
#SBATCH --time=35:00:00 # total run time limit (HH:MM:SS)
|
| 10 |
+
#SBATCH -e slurms/%j.err # first create a "slurms" folder in current directory to store logs
|
| 11 |
+
#SBATCH -o slurms/%j.out
|
| 12 |
+
#SBATCH --comment=medarc
|
| 13 |
+
#SBATCH --no-requeue
|
| 14 |
+
#SBATCH --exclude=ip-10-0-134-244,ip-10-0-139-120,ip-10-0-139-20,ip-10-0-168-162
|
| 15 |
+
|
| 16 |
+
export NUM_GPUS=1 # Set to equal gres=gpu:#!
|
| 17 |
+
echo NUM_GPUS=$NUM_GPUS
|
| 18 |
+
|
| 19 |
+
source ~/.bashrc
|
| 20 |
+
|
| 21 |
+
cd /weka/proj-fmri/paulscotti/fMRI-foundation-model/fMRI-MAE/
|
| 22 |
+
jupyter nbconvert main.ipynb --to python
|
| 23 |
+
if [ $? -ne 0 ]; then
|
| 24 |
+
echo "Error: Conversion of ipynb to Python failed. Exiting."
|
| 25 |
+
exit 1
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
# Make sure another job doesnt use same port, here using random number
|
| 29 |
+
export MASTER_PORT=$((RANDOM % (19000 - 11000 + 1) + 11000))
|
| 30 |
+
export HOSTNAMES=$(scontrol show hostnames "$SLURM_JOB_NODELIST")
|
| 31 |
+
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
|
| 32 |
+
echo MASTER_ADDR=${MASTER_ADDR}
|
| 33 |
+
echo MASTER_PORT=${MASTER_PORT}
|
| 34 |
+
echo WORLD_SIZE=$((${SLURM_NNODES} * ${NUM_GPUS}))
|
| 35 |
+
|
| 36 |
+
# Other settings relevant for multi-node
|
| 37 |
+
export NCCL_DEBUG=WARN #INFO
|
| 38 |
+
export NCCL_PROTO=simple
|
| 39 |
+
export FI_EFA_USE_DEVICE_RDMA=1
|
| 40 |
+
export FI_EFA_FORK_SAFE=1
|
| 41 |
+
export FI_LOG_LEVEL=1
|
| 42 |
+
export PYTHONFAULTHANDLER=1
|
| 43 |
+
export CUDA_LAUNCH_BLOCKING=0
|
| 44 |
+
export OMPI_MCA_mtl_base_verbose=1
|
| 45 |
+
export FI_EFA_ENABLE_SHM_TRANSFER=0
|
| 46 |
+
export FI_PROVIDER=efa
|
| 47 |
+
export FI_EFA_TX_MIN_CREDITS=64
|
| 48 |
+
export NCCL_TREE_THRESHOLD=0
|
| 49 |
+
export TORCH_DISTRIBUTED_DEBUG=DETAIL
|
| 50 |
+
export NCCL_P2P_DISABLE=1
|
| 51 |
+
module load openmpi cuda/12.1
|
| 52 |
+
|
| 53 |
+
srun torchrun \
|
| 54 |
+
--nproc_per_node=$NUM_GPUS \
|
| 55 |
+
--nnodes=$SLURM_NNODES \
|
| 56 |
+
--rdzv_id=$SLURM_JOBID \
|
| 57 |
+
--rdzv_backend=c10d \
|
| 58 |
+
--rdzv_endpoint=${MASTER_ADDR}:$MASTER_PORT \
|
| 59 |
+
--rdzv_conf=timeout=90 \
|
| 60 |
+
main.py
|
| 61 |
+
|
| 62 |
+
if [ $? -ne 0 ]; then
|
| 63 |
+
echo "Error: srun command failed. Please check the logs for more details."
|
| 64 |
+
exit 1
|
| 65 |
+
fi
|
fMRI-foundation-model-old/fMRI-MAE/dataloader.py
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from torch import Tensor, stack
|
| 3 |
+
from torch.utils.data import IterDataPipe, DataLoader
|
| 4 |
+
from torch.utils.data.datapipes._decorator import functional_datapipe
|
| 5 |
+
from torch.utils.data.datapipes.iter.callable import MapperIterDataPipe
|
| 6 |
+
from torch.utils.data.datapipes.utils.common import StreamWrapper, match_masks
|
| 7 |
+
from torchdata.datapipes.iter import IterableWrapper, IterDataPipe, TarArchiveLoader
|
| 8 |
+
from torchdata.datapipes.iter.load.s3io import S3FileLoaderIterDataPipe
|
| 9 |
+
from torchdata.datapipes.utils.common import validate_pathname_binary_tuple
|
| 10 |
+
import warnings
|
| 11 |
+
import subprocess
|
| 12 |
+
import tarfile
|
| 13 |
+
import tempfile
|
| 14 |
+
import time
|
| 15 |
+
import random
|
| 16 |
+
from omegaconf import DictConfig, ListConfig
|
| 17 |
+
import webdataset as wds
|
| 18 |
+
import re
|
| 19 |
+
import os
|
| 20 |
+
import copy
|
| 21 |
+
from io import BufferedIOBase, BytesIO, RawIOBase
|
| 22 |
+
from typing import (
|
| 23 |
+
IO,
|
| 24 |
+
Any,
|
| 25 |
+
Callable,
|
| 26 |
+
Dict,
|
| 27 |
+
Iterable,
|
| 28 |
+
Iterator,
|
| 29 |
+
List,
|
| 30 |
+
Optional,
|
| 31 |
+
Sequence,
|
| 32 |
+
Tuple,
|
| 33 |
+
TypeVar,
|
| 34 |
+
Union,
|
| 35 |
+
cast,
|
| 36 |
+
)
|
| 37 |
+
T_co = TypeVar("T_co", covariant=True)
|
| 38 |
+
|
| 39 |
+
class ShuffledListDataPipe(IterDataPipe[T_co]):
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
source_list: List[T_co],
|
| 43 |
+
*,
|
| 44 |
+
shuffle: bool = True,
|
| 45 |
+
cycle: Union[bool, int] = True,
|
| 46 |
+
):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.source = source_list
|
| 49 |
+
self._enabled = shuffle
|
| 50 |
+
self._seed = None
|
| 51 |
+
self._rng = random.Random()
|
| 52 |
+
if isinstance(cycle, bool):
|
| 53 |
+
if cycle:
|
| 54 |
+
self._cycle = -1
|
| 55 |
+
else:
|
| 56 |
+
self._cycle = 1
|
| 57 |
+
else:
|
| 58 |
+
assert isinstance(cycle, int)
|
| 59 |
+
self._cycle = cycle
|
| 60 |
+
|
| 61 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 62 |
+
source = copy.copy(self.source)
|
| 63 |
+
cycle = self._cycle
|
| 64 |
+
epochs = 0
|
| 65 |
+
while cycle == -1 or epochs < cycle:
|
| 66 |
+
if self._enabled:
|
| 67 |
+
self._rng.shuffle(source)
|
| 68 |
+
yield from source
|
| 69 |
+
epochs += 1
|
| 70 |
+
|
| 71 |
+
def __len__(self):
|
| 72 |
+
if self.count == -1:
|
| 73 |
+
raise TypeError(
|
| 74 |
+
f"This {type(self).__name__} instance cycles forever, and "
|
| 75 |
+
f"therefore doesn't have valid length."
|
| 76 |
+
)
|
| 77 |
+
else:
|
| 78 |
+
return self.count * len(self.source)
|
| 79 |
+
|
| 80 |
+
def __getstate__(self):
|
| 81 |
+
state = (
|
| 82 |
+
self.source,
|
| 83 |
+
self._enabled,
|
| 84 |
+
self._seed,
|
| 85 |
+
self._rng.getstate(),
|
| 86 |
+
self._valid_iterator_id,
|
| 87 |
+
self._number_of_samples_yielded,
|
| 88 |
+
)
|
| 89 |
+
if IterDataPipe.getstate_hook is not None:
|
| 90 |
+
return IterDataPipe.getstate_hook(state)
|
| 91 |
+
return state
|
| 92 |
+
|
| 93 |
+
def __setstate__(self, state):
|
| 94 |
+
(
|
| 95 |
+
self.source,
|
| 96 |
+
self._enabled,
|
| 97 |
+
self._seed,
|
| 98 |
+
rng_state,
|
| 99 |
+
self._valid_iterator_id,
|
| 100 |
+
self._number_of_samples_yielded,
|
| 101 |
+
) = state
|
| 102 |
+
self._rng = random.Random()
|
| 103 |
+
self._rng.setstate(rng_state)
|
| 104 |
+
|
| 105 |
+
def get_job_temp_dir(dl_root: str) -> str:
|
| 106 |
+
try:
|
| 107 |
+
job_or_array_id = (
|
| 108 |
+
os.environ.get("SLURM_ARRAY_JOB_ID", "") or os.environ["SLURM_JOB_ID"]
|
| 109 |
+
)
|
| 110 |
+
task_id = os.environ.get("SLURM_ARRAY_TASK_ID", "") or "0"
|
| 111 |
+
return os.path.join(dl_root, f"JOB_{job_or_array_id}/TASK_{task_id}")
|
| 112 |
+
except KeyError as e:
|
| 113 |
+
print("SLURM_JOB_ID env var not set: You sure this job is healthy?")
|
| 114 |
+
raise e
|
| 115 |
+
|
| 116 |
+
def _shard_expand(s: str) -> List[str]:
|
| 117 |
+
expansion = r"\{[0-9]+\.\.[0-9]+\}"
|
| 118 |
+
m = re.search(expansion, s)
|
| 119 |
+
if not m:
|
| 120 |
+
return [s]
|
| 121 |
+
prefix = s[: m.start()]
|
| 122 |
+
rest = _shard_expand(s[m.end() :])
|
| 123 |
+
rng = s[m.start() + 1 : m.end() - 1]
|
| 124 |
+
lohi = rng.split("..")
|
| 125 |
+
if len(lohi[0]) == len(lohi[1]) and lohi[0].startswith("0"):
|
| 126 |
+
fmt = "{prefix}{i:0>{l}d}{r}"
|
| 127 |
+
elif len(lohi[0]) <= len(lohi[1]):
|
| 128 |
+
if lohi[0].startswith("0") and lohi[0] != "0":
|
| 129 |
+
raise ValueError(
|
| 130 |
+
"shard_expand: low bound must not start with 0 if low bound is shorter"
|
| 131 |
+
)
|
| 132 |
+
fmt = "{prefix}{i}{r}"
|
| 133 |
+
else:
|
| 134 |
+
raise ValueError("shard_expand: low bound must be shorter than high bound")
|
| 135 |
+
lo, hi = (int(x) for x in lohi)
|
| 136 |
+
if lo >= hi:
|
| 137 |
+
raise ValueError(f"shard_expand: bad range in in shard spec {s}.")
|
| 138 |
+
result = []
|
| 139 |
+
for i in range(lo, hi + 1):
|
| 140 |
+
for r in rest:
|
| 141 |
+
expanded: str = fmt.format(prefix=prefix, i=i, r=r, l=len(lohi[1]))
|
| 142 |
+
result.append(expanded)
|
| 143 |
+
return result
|
| 144 |
+
|
| 145 |
+
@functional_datapipe("custom_shard_expand")
|
| 146 |
+
class CustomShardExpanderIterDataPipe(IterDataPipe[str]):
|
| 147 |
+
def __init__(self, source_datapipe: IterDataPipe[str]) -> None:
|
| 148 |
+
super().__init__()
|
| 149 |
+
self.source_datapipe: IterDataPipe[str] = source_datapipe
|
| 150 |
+
|
| 151 |
+
def __iter__(self) -> Iterator[str]:
|
| 152 |
+
for path in self.source_datapipe:
|
| 153 |
+
yield from _shard_expand(path)
|
| 154 |
+
|
| 155 |
+
def warn_and_continue(exn):
|
| 156 |
+
"""Call in an exception handler to ignore any exception, issue a warning, and continue."""
|
| 157 |
+
print(exn)
|
| 158 |
+
warnings.warn(repr(exn))
|
| 159 |
+
time.sleep(0.05)
|
| 160 |
+
return True
|
| 161 |
+
error_handler = warn_and_continue
|
| 162 |
+
|
| 163 |
+
def is_stream_handle(data):
|
| 164 |
+
obj_to_check = data.file_obj if isinstance(data, StreamWrapper) else data
|
| 165 |
+
return isinstance(obj_to_check, (BufferedIOBase, RawIOBase))
|
| 166 |
+
|
| 167 |
+
def read_stream_handles(data):
|
| 168 |
+
if not is_stream_handle(data):
|
| 169 |
+
return data
|
| 170 |
+
else:
|
| 171 |
+
ds = data
|
| 172 |
+
data = b"".join(data)
|
| 173 |
+
ds.close()
|
| 174 |
+
del ds
|
| 175 |
+
return data
|
| 176 |
+
|
| 177 |
+
def stream_reader(sample):
|
| 178 |
+
return {k: read_stream_handles(v) for k, v in sample.items()}
|
| 179 |
+
|
| 180 |
+
def to_our_format(sample):
|
| 181 |
+
wds_key = sample.pop("__key__")
|
| 182 |
+
sample = {k.lstrip("."): v for k, v in sample.items()}
|
| 183 |
+
sample["__key__"] = wds_key.split("/")[-1]
|
| 184 |
+
sample["__url__"] = "/".join(wds_key.split("/")[:-1])
|
| 185 |
+
return sample
|
| 186 |
+
|
| 187 |
+
def add_processors(
|
| 188 |
+
datapipeline,
|
| 189 |
+
processors: Optional[ListConfig],
|
| 190 |
+
description: str,
|
| 191 |
+
error_handler: Callable = warn_and_continue,
|
| 192 |
+
):
|
| 193 |
+
if not processors:
|
| 194 |
+
return datapipeline
|
| 195 |
+
else:
|
| 196 |
+
for i, processor_config in enumerate(processors):
|
| 197 |
+
processor = instantiate(processor_config)
|
| 198 |
+
if isinstance(processor, AbstractFilter):
|
| 199 |
+
print(
|
| 200 |
+
f"Adding filter {processor.__class__.__name__} as {description} #{i} "
|
| 201 |
+
f"to the datapipeline"
|
| 202 |
+
)
|
| 203 |
+
datapipeline = datapipeline.filter(processor.filter)
|
| 204 |
+
elif isinstance(processor, AbstractMapper):
|
| 205 |
+
print(
|
| 206 |
+
f"Adding mapper {processor.__class__.__name__} as {description} #{i} "
|
| 207 |
+
f"to the datapipeline"
|
| 208 |
+
)
|
| 209 |
+
datapipeline = datapipeline.map_with_handler(
|
| 210 |
+
processor.map,
|
| 211 |
+
handler=error_handler,
|
| 212 |
+
called_cls_name=processor.__class__.__name__,
|
| 213 |
+
)
|
| 214 |
+
else:
|
| 215 |
+
raise TypeError(
|
| 216 |
+
f"chosen {description} {processor.__class__.__name__} should be either subclass"
|
| 217 |
+
"AbstractMapper or AbstractFilter"
|
| 218 |
+
)
|
| 219 |
+
return datapipeline
|
| 220 |
+
|
| 221 |
+
@functional_datapipe("map_with_handler")
|
| 222 |
+
class MapperWithErrorHandlingIterDataPipe(MapperIterDataPipe):
|
| 223 |
+
def __init__(
|
| 224 |
+
self,
|
| 225 |
+
datapipe: IterDataPipe,
|
| 226 |
+
fn: Callable,
|
| 227 |
+
handler: Callable = wds.reraise_exception,
|
| 228 |
+
input_col: Optional[Union[str, int]] = None,
|
| 229 |
+
output_col: Optional[Union[str, int]] = None,
|
| 230 |
+
called_cls_name: Optional[str] = None,
|
| 231 |
+
):
|
| 232 |
+
# for now, disbable input and output col since this is never used anyways
|
| 233 |
+
if input_col is not None:
|
| 234 |
+
raise NotImplementedError("`input_col` argument currently not supported")
|
| 235 |
+
|
| 236 |
+
if output_col is not None:
|
| 237 |
+
raise NotImplementedError("`output_col` argument currently not supported")
|
| 238 |
+
|
| 239 |
+
super().__init__(datapipe, fn)
|
| 240 |
+
self.handler = handler
|
| 241 |
+
self._apply_fn_ = self._apply_fn
|
| 242 |
+
|
| 243 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 244 |
+
for data in self.datapipe:
|
| 245 |
+
try:
|
| 246 |
+
res = self._apply_fn_(data)
|
| 247 |
+
if res is None:
|
| 248 |
+
continue
|
| 249 |
+
yield res
|
| 250 |
+
except Exception as e:
|
| 251 |
+
if self.handler(e):
|
| 252 |
+
continue
|
| 253 |
+
else:
|
| 254 |
+
raise e
|
| 255 |
+
|
| 256 |
+
__S3_TOOLS__ = {
|
| 257 |
+
"s3": ["/usr/local/bin/aws", "s3"],
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
def is_tar(x: str) -> bool:
|
| 261 |
+
return x.endswith(".tar")
|
| 262 |
+
|
| 263 |
+
def ls_aws(
|
| 264 |
+
path: str,
|
| 265 |
+
tool: str = "s3",
|
| 266 |
+
recursive: bool = True,
|
| 267 |
+
raise_errors: bool = True,
|
| 268 |
+
skip_files: bool = True,
|
| 269 |
+
):
|
| 270 |
+
assert path.startswith("s3://"), path
|
| 271 |
+
|
| 272 |
+
# in case we have a file, set recursive to false
|
| 273 |
+
isfile = bool(os.path.splitext(path)[1])
|
| 274 |
+
if isfile and skip_files:
|
| 275 |
+
return [path]
|
| 276 |
+
|
| 277 |
+
if not path.endswith("/") and not isfile:
|
| 278 |
+
path = path + "/"
|
| 279 |
+
cmd = [*__S3_TOOLS__[tool], "ls", f"{path}"]
|
| 280 |
+
|
| 281 |
+
if recursive and not isfile:
|
| 282 |
+
cmd += ["--recursive"]
|
| 283 |
+
try:
|
| 284 |
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 285 |
+
result.check_returncode()
|
| 286 |
+
|
| 287 |
+
stdout = result.stdout.decode().split("\n")
|
| 288 |
+
# return empty list in case path is not a real file/directory
|
| 289 |
+
if not stdout[0]:
|
| 290 |
+
return []
|
| 291 |
+
|
| 292 |
+
# return path if it is an exisiting file
|
| 293 |
+
if isfile:
|
| 294 |
+
return [path]
|
| 295 |
+
|
| 296 |
+
# strip timestamp and object size
|
| 297 |
+
out = [line.split(" ")[-1] for line in stdout if line]
|
| 298 |
+
if recursive:
|
| 299 |
+
# aws s3 ls returns prefix+filename
|
| 300 |
+
bucket = path[: path.find("/", len("s3://"))] # s3://<bucket>/...
|
| 301 |
+
out = [os.path.join(bucket, o) for o in out]
|
| 302 |
+
else:
|
| 303 |
+
# aws s3 ls returns filename only
|
| 304 |
+
out = [os.path.join(path, o) for i in out]
|
| 305 |
+
|
| 306 |
+
return out
|
| 307 |
+
except subprocess.CalledProcessError as e:
|
| 308 |
+
print(f"Got exception while trying to load data! {e.__class__.__name__}: {e}")
|
| 309 |
+
if raise_errors:
|
| 310 |
+
raise e
|
| 311 |
+
else:
|
| 312 |
+
return []
|
| 313 |
+
|
| 314 |
+
@functional_datapipe("wrapped_load_files_by_s3")
|
| 315 |
+
class WrappedS3FileLoaderIterDataPipe(S3FileLoaderIterDataPipe):
|
| 316 |
+
def __init__(
|
| 317 |
+
self,
|
| 318 |
+
*args,
|
| 319 |
+
ignore_missing_files: bool = False,
|
| 320 |
+
**kwargs,
|
| 321 |
+
):
|
| 322 |
+
super().__init__(*args, **kwargs)
|
| 323 |
+
self.ignore_missing_files = ignore_missing_files
|
| 324 |
+
|
| 325 |
+
def __iter__(self) -> Iterator[Tuple[str, StreamWrapper]]:
|
| 326 |
+
# same as parent but maybe ignoring errors
|
| 327 |
+
for url in self.source_datapipe:
|
| 328 |
+
try:
|
| 329 |
+
yield url, StreamWrapper(BytesIO(self.handler.s3_read(url)))
|
| 330 |
+
except ValueError as e:
|
| 331 |
+
if not self.ignore_missing_files:
|
| 332 |
+
raise FileNotFoundError(url)
|
| 333 |
+
else:
|
| 334 |
+
print(f"Warning: Could not download {url}")
|
| 335 |
+
|
| 336 |
+
@functional_datapipe("list_files_with_aws_cli")
|
| 337 |
+
class S3CLIFileListerIterDataPipe(IterDataPipe[T_co]):
|
| 338 |
+
def __init__(
|
| 339 |
+
self,
|
| 340 |
+
source_datapipe: Union[str, Sequence[str], IterDataPipe],
|
| 341 |
+
s3_tool: str = "s3",
|
| 342 |
+
n_retries: int = 10,
|
| 343 |
+
sleep_interval: float = 0.01,
|
| 344 |
+
):
|
| 345 |
+
if isinstance(source_datapipe, str):
|
| 346 |
+
source_datapipe = [
|
| 347 |
+
source_datapipe,
|
| 348 |
+
]
|
| 349 |
+
if not isinstance(source_datapipe, IterDataPipe):
|
| 350 |
+
self.datapipe: IterDataPipe = IterableWrapper(source_datapipe) # type: ignore[assignment]
|
| 351 |
+
else:
|
| 352 |
+
self.datapipe = source_datapipe
|
| 353 |
+
|
| 354 |
+
assert s3_tool in __S3_TOOLS__, f"`s3_tool` has to be in {list(__S3_TOOLS__)}"
|
| 355 |
+
|
| 356 |
+
self.s3_tool = s3_tool
|
| 357 |
+
|
| 358 |
+
self.n_retries = n_retries
|
| 359 |
+
self.sleep = sleep_interval
|
| 360 |
+
|
| 361 |
+
def __iter__(self) -> Iterator[str]:
|
| 362 |
+
for root in self.datapipe:
|
| 363 |
+
files = None
|
| 364 |
+
for _ in range(self.n_retries):
|
| 365 |
+
try:
|
| 366 |
+
files = ls_aws(
|
| 367 |
+
path=root, tool=self.s3_tool, recursive=True, raise_errors=True
|
| 368 |
+
)
|
| 369 |
+
break
|
| 370 |
+
except subprocess.CalledProcessError:
|
| 371 |
+
time.sleep(self.sleep)
|
| 372 |
+
|
| 373 |
+
if files is None:
|
| 374 |
+
print(
|
| 375 |
+
f"Could not ls data expected under {root} in {self.n_retries} tries. Not yielding ..."
|
| 376 |
+
)
|
| 377 |
+
continue
|
| 378 |
+
|
| 379 |
+
for file in files:
|
| 380 |
+
yield file
|
| 381 |
+
|
| 382 |
+
def _download_tar(url, scratch, verbose=False, n_retries=100):
|
| 383 |
+
start = time.perf_counter()
|
| 384 |
+
if verbose:
|
| 385 |
+
_log(f"downloading {url}")
|
| 386 |
+
uid = "".join(filter(str.isalnum, os.path.splitext(url)[0]))
|
| 387 |
+
idx = 0
|
| 388 |
+
path = os.path.join(scratch, f"{os.getpid()}.{uid}.{idx}.tar")
|
| 389 |
+
while os.path.exists(path):
|
| 390 |
+
idx += 1
|
| 391 |
+
path = os.path.join(scratch, f"{os.getpid()}.{uid}.{idx}.tar")
|
| 392 |
+
|
| 393 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 394 |
+
for i_try in range(n_retries):
|
| 395 |
+
p = subprocess.call(
|
| 396 |
+
[
|
| 397 |
+
"/usr/local/bin/aws",
|
| 398 |
+
"s3",
|
| 399 |
+
"cp",
|
| 400 |
+
url,
|
| 401 |
+
path,
|
| 402 |
+
"--quiet",
|
| 403 |
+
]
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
if p != 0:
|
| 407 |
+
# mostly to work around
|
| 408 |
+
# Error when retrieving credentials from Ec2InstanceMetadata: No
|
| 409 |
+
# credentials found in credential_source referenced in profile
|
| 410 |
+
# default
|
| 411 |
+
if i_try == 0:
|
| 412 |
+
# check if the requested file actually exists
|
| 413 |
+
if not ls_aws(url, skip_files=False, raise_errors=False):
|
| 414 |
+
raise FileNotFoundError(url)
|
| 415 |
+
|
| 416 |
+
if i_try + 1 < n_retries:
|
| 417 |
+
time.sleep(random.uniform(0.0, 0.1))
|
| 418 |
+
continue
|
| 419 |
+
else:
|
| 420 |
+
raise Exception(
|
| 421 |
+
f"File {url} should exist but failed to download after trying {i_try+1} times."
|
| 422 |
+
)
|
| 423 |
+
break
|
| 424 |
+
|
| 425 |
+
if verbose:
|
| 426 |
+
_log(
|
| 427 |
+
f"wrote {path} in {time.perf_counter() - start} secs after {i_try+1} tries."
|
| 428 |
+
)
|
| 429 |
+
return path
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
@functional_datapipe("download_with_s3_cli")
|
| 433 |
+
class S3CLITarDownloader(IterDataPipe[T_co]):
|
| 434 |
+
def __init__(
|
| 435 |
+
self,
|
| 436 |
+
source_datapipe,
|
| 437 |
+
n_retries: int = 10,
|
| 438 |
+
verbose: bool = False,
|
| 439 |
+
dl_root: str = "/scratch",
|
| 440 |
+
mode: str = "r:*",
|
| 441 |
+
aws_kwargs: Optional[dict] = None,
|
| 442 |
+
ignore_missing_files: bool = False,
|
| 443 |
+
):
|
| 444 |
+
self.source_datapipe = source_datapipe
|
| 445 |
+
assert os.path.isdir(dl_root), f"`dl_root` {dl_root} is not a valid directory"
|
| 446 |
+
# get slurm job id and create a subdir in `dl_root` to download all the tars to
|
| 447 |
+
dl_root = get_job_temp_dir(dl_root)
|
| 448 |
+
os.makedirs(dl_root, exist_ok=True)
|
| 449 |
+
self.dl_root = dl_root
|
| 450 |
+
self.mode = mode
|
| 451 |
+
self.n_retries = n_retries
|
| 452 |
+
self.ignore_missing_files = ignore_missing_files
|
| 453 |
+
|
| 454 |
+
self.verbose = verbose
|
| 455 |
+
|
| 456 |
+
def _yield_next(self, url: str, local_path: str):
|
| 457 |
+
if self.verbose:
|
| 458 |
+
_log("popping queue")
|
| 459 |
+
|
| 460 |
+
if self.verbose:
|
| 461 |
+
_log(f"loading {local_path}")
|
| 462 |
+
tarstream = tarfile.open(local_path, self.mode)
|
| 463 |
+
|
| 464 |
+
if self.verbose:
|
| 465 |
+
_log(f"yielding {url}")
|
| 466 |
+
|
| 467 |
+
yield (url, StreamWrapper(tarstream))
|
| 468 |
+
if self.verbose:
|
| 469 |
+
_log("new tar request")
|
| 470 |
+
|
| 471 |
+
if self.verbose:
|
| 472 |
+
_log(f"removing previous tar at {local_path}")
|
| 473 |
+
try:
|
| 474 |
+
os.remove(local_path)
|
| 475 |
+
except FileNotFoundError:
|
| 476 |
+
_log(
|
| 477 |
+
f"WARNING: Could not find previous tar for deletion. Unless a clean-up was triggered this is unexpected. The location was {local_path}"
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
def __iter__(self):
|
| 481 |
+
with tempfile.TemporaryDirectory(dir=self.dl_root) as scratch:
|
| 482 |
+
for url in self.source_datapipe:
|
| 483 |
+
try:
|
| 484 |
+
local_path = _download_tar(
|
| 485 |
+
url, scratch, verbose=self.verbose, n_retries=self.n_retries
|
| 486 |
+
)
|
| 487 |
+
except FileNotFoundError as e:
|
| 488 |
+
if not self.ignore_missing_files:
|
| 489 |
+
raise e
|
| 490 |
+
else:
|
| 491 |
+
yield from self._yield_next(url, local_path)
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
@functional_datapipe("load_from_tar_and_handle_error")
|
| 495 |
+
class TarArchiveLoaderWithErrorHandlingIterDataPipe(TarArchiveLoader):
|
| 496 |
+
def __init__(
|
| 497 |
+
self,
|
| 498 |
+
datapipe: Iterable[Tuple[str, BufferedIOBase]],
|
| 499 |
+
mode: str = "r:*",
|
| 500 |
+
length: int = -1,
|
| 501 |
+
handler: Callable = wds.reraise_exception,
|
| 502 |
+
):
|
| 503 |
+
super().__init__(datapipe=datapipe, mode=mode, length=length)
|
| 504 |
+
self.handler = handler
|
| 505 |
+
|
| 506 |
+
def __iter__(self) -> Iterator[Tuple[str, BufferedIOBase]]:
|
| 507 |
+
for data in self.datapipe:
|
| 508 |
+
validate_pathname_binary_tuple(data)
|
| 509 |
+
pathname, data_stream = data
|
| 510 |
+
try:
|
| 511 |
+
if isinstance(data_stream, StreamWrapper) and isinstance(
|
| 512 |
+
data_stream.file_obj, tarfile.TarFile
|
| 513 |
+
):
|
| 514 |
+
tar = data_stream.file_obj
|
| 515 |
+
else:
|
| 516 |
+
reading_mode = (
|
| 517 |
+
self.mode
|
| 518 |
+
if hasattr(data_stream, "seekable") and data_stream.seekable()
|
| 519 |
+
else self.mode.replace(":", "|")
|
| 520 |
+
)
|
| 521 |
+
# typing.cast is used here to silence mypy's type checker
|
| 522 |
+
tar = tarfile.open(
|
| 523 |
+
fileobj=cast(Optional[IO[bytes]], data_stream),
|
| 524 |
+
mode=reading_mode,
|
| 525 |
+
)
|
| 526 |
+
for tarinfo in tar:
|
| 527 |
+
if not tarinfo.isfile():
|
| 528 |
+
continue
|
| 529 |
+
extracted_fobj = tar.extractfile(tarinfo)
|
| 530 |
+
if extracted_fobj is None:
|
| 531 |
+
warnings.warn(
|
| 532 |
+
f"failed to extract file {tarinfo.name} from source tarfile {pathname}"
|
| 533 |
+
)
|
| 534 |
+
raise tarfile.ExtractError
|
| 535 |
+
inner_pathname = os.path.normpath(
|
| 536 |
+
os.path.join(pathname, tarinfo.name)
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
yield inner_pathname, StreamWrapper(extracted_fobj, data_stream, name=inner_pathname) # type: ignore[misc]
|
| 540 |
+
except Exception as e:
|
| 541 |
+
warnings.warn(
|
| 542 |
+
f"Unable to extract files from corrupted tarfile stream {pathname} due to: {e}, abort!"
|
| 543 |
+
)
|
| 544 |
+
if self.handler(e):
|
| 545 |
+
if hasattr(e, "args") and len(e.args) > 0:
|
| 546 |
+
e.args = (e.args[0] + " @ " + str(pathname),) + e.args[1:]
|
| 547 |
+
finally:
|
| 548 |
+
if isinstance(data_stream, StreamWrapper):
|
| 549 |
+
data_stream.autoclose()
|
| 550 |
+
|
| 551 |
+
class DictCollator:
|
| 552 |
+
def __init__(
|
| 553 |
+
self,
|
| 554 |
+
combine_tensors: bool = True,
|
| 555 |
+
combine_scalars: bool = True,
|
| 556 |
+
timeout=None,
|
| 557 |
+
):
|
| 558 |
+
self.combine_tensors = combine_tensors
|
| 559 |
+
self.combine_scalars = combine_scalars
|
| 560 |
+
|
| 561 |
+
collate = self._collate
|
| 562 |
+
if timeout is not None:
|
| 563 |
+
collate = timeout_wrapper(collate, timeout=timeout)
|
| 564 |
+
self.collate = collate
|
| 565 |
+
|
| 566 |
+
def __call__(self, samples):
|
| 567 |
+
return self.collate(samples)
|
| 568 |
+
|
| 569 |
+
def _collate(self, samples):
|
| 570 |
+
keys = set.intersection(*[set(sample.keys()) for sample in samples])
|
| 571 |
+
batched = {key: [] for key in keys}
|
| 572 |
+
|
| 573 |
+
for s in samples:
|
| 574 |
+
[batched[key].append(s[key]) for key in batched]
|
| 575 |
+
|
| 576 |
+
result = {}
|
| 577 |
+
for key in batched:
|
| 578 |
+
if isinstance(batched[key][0], (int, float)):
|
| 579 |
+
if self.combine_scalars:
|
| 580 |
+
result[key] = np.array(list(batched[key]))
|
| 581 |
+
elif isinstance(batched[key][0], Tensor):
|
| 582 |
+
if self.combine_tensors:
|
| 583 |
+
result[key] = stack(list(batched[key]))
|
| 584 |
+
elif isinstance(batched[key][0], np.ndarray):
|
| 585 |
+
if self.combine_tensors:
|
| 586 |
+
result[key] = np.array(list(batched[key]))
|
| 587 |
+
else:
|
| 588 |
+
result[key] = list(batched[key])
|
| 589 |
+
|
| 590 |
+
del samples
|
| 591 |
+
del batched
|
| 592 |
+
return result
|
| 593 |
+
|
| 594 |
+
def timeout_wrapper(func: Callable, timeout: Optional[float] = None) -> Callable:
|
| 595 |
+
if timeout is None or timeout <= 0.0:
|
| 596 |
+
return func
|
| 597 |
+
|
| 598 |
+
@functools.wraps(func)
|
| 599 |
+
def wrapper(*args, **kwargs):
|
| 600 |
+
result = [None]
|
| 601 |
+
exception = [None]
|
| 602 |
+
event = threading.Event()
|
| 603 |
+
|
| 604 |
+
def wrapped_func():
|
| 605 |
+
try:
|
| 606 |
+
result[0] = func(*args, **kwargs)
|
| 607 |
+
except Exception as e:
|
| 608 |
+
exception[0] = e
|
| 609 |
+
finally:
|
| 610 |
+
event.set()
|
| 611 |
+
|
| 612 |
+
thread = threading.Thread(target=wrapped_func)
|
| 613 |
+
thread.start()
|
| 614 |
+
event.wait(timeout)
|
| 615 |
+
|
| 616 |
+
if not event.is_set():
|
| 617 |
+
raise TimeoutError(f"Function call timed out (longer than {timeout} secs).")
|
| 618 |
+
|
| 619 |
+
thread.join()
|
| 620 |
+
|
| 621 |
+
if exception[0] is not None:
|
| 622 |
+
err = exception[0]
|
| 623 |
+
del exception
|
| 624 |
+
raise err
|
| 625 |
+
|
| 626 |
+
del thread
|
| 627 |
+
del exception
|
| 628 |
+
del wrapped_func
|
| 629 |
+
del event
|
| 630 |
+
del args
|
| 631 |
+
del kwargs
|
| 632 |
+
|
| 633 |
+
ret = result[0]
|
| 634 |
+
del result
|
| 635 |
+
return ret
|
| 636 |
+
|
| 637 |
+
return wrapper
|
| 638 |
+
|
| 639 |
+
class DecoderWithTimeout(wds.Decoder):
|
| 640 |
+
"""Decode samples using a list of handlers.
|
| 641 |
+
|
| 642 |
+
For each key/data item, this iterates through the list of
|
| 643 |
+
handlers until some handler returns something other than None.
|
| 644 |
+
"""
|
| 645 |
+
|
| 646 |
+
def __init__(self, *args, timeout: Optional[float] = None, **kwargs):
|
| 647 |
+
super().__init__(*args, **kwargs)
|
| 648 |
+
|
| 649 |
+
if timeout is not None:
|
| 650 |
+
self.decode = timeout_wrapper(self.decode, timeout)
|
| 651 |
+
|
| 652 |
+
def setup_decoder(
|
| 653 |
+
decoders,
|
| 654 |
+
additional_decoder_kwargs: Optional[Union[DictConfig, Dict]] = None,
|
| 655 |
+
):
|
| 656 |
+
if not additional_decoder_kwargs:
|
| 657 |
+
additional_decoder_kwargs = {}
|
| 658 |
+
|
| 659 |
+
if not isinstance(decoders, (List, ListConfig)):
|
| 660 |
+
decoders = [decoders]
|
| 661 |
+
handlers = []
|
| 662 |
+
for decoder_spec in decoders:
|
| 663 |
+
if isinstance(decoder_spec, (Dict, DictConfig)):
|
| 664 |
+
decoder = instantiate(decoder_spec)
|
| 665 |
+
elif isinstance(decoder_spec, str):
|
| 666 |
+
decoder = wds.autodecode.ImageHandler(decoder_spec)
|
| 667 |
+
else:
|
| 668 |
+
raise TypeError(f"{decoder_spec} not a thing for decoders.")
|
| 669 |
+
handlers.append(decoder)
|
| 670 |
+
print(f"Adding decoder {decoder.__class__.__name__} to decoders.")
|
| 671 |
+
|
| 672 |
+
decoder = DecoderWithTimeout(
|
| 673 |
+
handlers,
|
| 674 |
+
partial=additional_decoder_kwargs.pop("partial", True),
|
| 675 |
+
**additional_decoder_kwargs, # todo
|
| 676 |
+
)
|
| 677 |
+
return decoder
|
| 678 |
+
|
| 679 |
+
def create_dataset(urls, is_s3=False, prefetch=None,
|
| 680 |
+
s3_buffer_size=None, s3_ignore_missing_files=True,
|
| 681 |
+
sample_shuffle=1, shard_shuffle=1000, cycle=True,
|
| 682 |
+
split_workers_along_tars=True, decoders="torch"):
|
| 683 |
+
if isinstance(urls, str):
|
| 684 |
+
urls = [urls]
|
| 685 |
+
dp = IterableWrapper(urls)
|
| 686 |
+
dp = dp.custom_shard_expand()
|
| 687 |
+
if is_s3:
|
| 688 |
+
dp = dp.list_files_with_aws_cli().filter(is_tar)
|
| 689 |
+
else:
|
| 690 |
+
dp = dp.list_files(masks="*.tar", recursive=True)
|
| 691 |
+
|
| 692 |
+
dp = ShuffledListDataPipe(list(dp), shuffle=shard_shuffle > 1, cycle=cycle)
|
| 693 |
+
|
| 694 |
+
if split_workers_along_tars:
|
| 695 |
+
dp = dp.sharding_filter()
|
| 696 |
+
|
| 697 |
+
if is_s3:
|
| 698 |
+
# if prefetch is None:
|
| 699 |
+
# s3_buffer_size = int(s3_buffer_size) if s3_buffer_size is not None else None
|
| 700 |
+
# dp = dp.wrapped_load_files_by_s3(
|
| 701 |
+
# buffer_size=s3_buffer_size,
|
| 702 |
+
# ignore_missing_files=s3_ignore_missing_files,
|
| 703 |
+
# )
|
| 704 |
+
dp = dp.download_with_s3_cli(ignore_missing_files=s3_ignore_missing_files)
|
| 705 |
+
else:
|
| 706 |
+
dp = dp.open_files(mode="b")
|
| 707 |
+
|
| 708 |
+
if prefetch is not None:
|
| 709 |
+
dp = dp.custom_prefetch(buffer_size=prefetch)
|
| 710 |
+
|
| 711 |
+
dp = dp.load_from_tar_and_handle_error(handler=error_handler)
|
| 712 |
+
dp = dp.webdataset()
|
| 713 |
+
|
| 714 |
+
if not split_workers_along_tars:
|
| 715 |
+
dp = dp.sharding_filter()
|
| 716 |
+
|
| 717 |
+
dp = dp.map_with_handler(
|
| 718 |
+
stream_reader,
|
| 719 |
+
handler=error_handler,
|
| 720 |
+
called_cls_name="StreamReader",
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
dp = dp.shuffle(buffer_size=sample_shuffle) if sample_shuffle > 1 else dp
|
| 724 |
+
|
| 725 |
+
dp = dp.map_with_handler(
|
| 726 |
+
to_our_format,
|
| 727 |
+
handler=error_handler,
|
| 728 |
+
called_cls_name="ToOurFormat",
|
| 729 |
+
)
|
| 730 |
+
|
| 731 |
+
decoder: DecoderWithTimeout = setup_decoder(decoders) # ,additional_decoder_kwargs)
|
| 732 |
+
|
| 733 |
+
dp = dp.map_with_handler(
|
| 734 |
+
decoder.decode,
|
| 735 |
+
handler=error_handler,
|
| 736 |
+
called_cls_name="Decoder",
|
| 737 |
+
)
|
| 738 |
+
|
| 739 |
+
return dp
|
| 740 |
+
|
| 741 |
+
def create_loader(
|
| 742 |
+
datapipeline: IterDataPipe,
|
| 743 |
+
batch_size: int,
|
| 744 |
+
num_workers: int,
|
| 745 |
+
partial: bool = False,
|
| 746 |
+
collation_fn: Optional[Union[Callable, Dict, DictConfig]] = DictCollator(),
|
| 747 |
+
batched_transforms: Optional[ListConfig] = None,
|
| 748 |
+
loader_kwargs: Optional[Union[Dict, DictConfig]] = None,
|
| 749 |
+
) -> DataLoader:
|
| 750 |
+
if not loader_kwargs:
|
| 751 |
+
loader_kwargs = {}
|
| 752 |
+
|
| 753 |
+
loader_kwargs.pop("shuffle", None)
|
| 754 |
+
if not batched_transforms:
|
| 755 |
+
batched_transforms = []
|
| 756 |
+
|
| 757 |
+
print("#" * 100)
|
| 758 |
+
print("Building dataloader with the following parameters")
|
| 759 |
+
print(f"batch_size: {batch_size}, num_workers: {num_workers}")
|
| 760 |
+
for key in loader_kwargs:
|
| 761 |
+
print(key, ": ", loader_kwargs[key])
|
| 762 |
+
print("#" * 100)
|
| 763 |
+
|
| 764 |
+
datapipeline = datapipeline.batch(batch_size, drop_last=not partial)
|
| 765 |
+
|
| 766 |
+
if isinstance(collation_fn, (Dict, DictConfig)):
|
| 767 |
+
collation_fn = instantiate(collation_fn)
|
| 768 |
+
datapipeline = datapipeline.collate(collate_fn=collation_fn)
|
| 769 |
+
loader = DataLoader(
|
| 770 |
+
datapipeline, batch_size=None, num_workers=num_workers, **loader_kwargs
|
| 771 |
+
)
|
| 772 |
+
return loader
|
fMRI-foundation-model-old/fMRI-MAE/downstream.ipynb
ADDED
|
@@ -0,0 +1,1162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "0859a181-0681-489e-9939-8d47614b9fa8",
|
| 7 |
+
"metadata": {
|
| 8 |
+
"tags": []
|
| 9 |
+
},
|
| 10 |
+
"outputs": [
|
| 11 |
+
{
|
| 12 |
+
"name": "stdout",
|
| 13 |
+
"output_type": "stream",
|
| 14 |
+
"text": [
|
| 15 |
+
"LOCAL RANK=0\n",
|
| 16 |
+
"NUM GPUS=1\n",
|
| 17 |
+
"GLOBAL RANK=0\n",
|
| 18 |
+
"batch_size 32\n",
|
| 19 |
+
"skipping deepspeed reconfiguration...\n",
|
| 20 |
+
"PID of this process = 617152\n",
|
| 21 |
+
"device: cuda\n",
|
| 22 |
+
"Distributed environment: NO\n",
|
| 23 |
+
"Num processes: 1\n",
|
| 24 |
+
"Process index: 0\n",
|
| 25 |
+
"Local process index: 0\n",
|
| 26 |
+
"Device: cuda\n",
|
| 27 |
+
"\n",
|
| 28 |
+
"Mixed precision type: fp16\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"distributed = False num_devices = 1 local rank = 0 world size = 1 data_type = torch.float16\n"
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"name": "stderr",
|
| 35 |
+
"output_type": "stream",
|
| 36 |
+
"text": [
|
| 37 |
+
"/admin/home-paulscotti/found/lib/python3.10/site-packages/accelerate/accelerator.py:432: FutureWarning: Passing the following arguments to `Accelerator` is deprecated and will be removed in version 1.0 of Accelerate: dict_keys(['split_batches']). Please pass an `accelerate.DataLoaderConfiguration` instead: \n",
|
| 38 |
+
"dataloader_config = DataLoaderConfiguration(split_batches=False)\n",
|
| 39 |
+
" warnings.warn(\n"
|
| 40 |
+
]
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"source": [
|
| 44 |
+
"# Import packages and setup gpu configuration.\n",
|
| 45 |
+
"# This code block shouldnt need to be adjusted!\n",
|
| 46 |
+
"import os\n",
|
| 47 |
+
"import sys\n",
|
| 48 |
+
"import json\n",
|
| 49 |
+
"import yaml\n",
|
| 50 |
+
"import numpy as np\n",
|
| 51 |
+
"import pandas as pd\n",
|
| 52 |
+
"import copy\n",
|
| 53 |
+
"import math\n",
|
| 54 |
+
"from einops import rearrange\n",
|
| 55 |
+
"from einops.layers.torch import Rearrange\n",
|
| 56 |
+
"import time\n",
|
| 57 |
+
"import random\n",
|
| 58 |
+
"import h5py\n",
|
| 59 |
+
"import webdataset as wds\n",
|
| 60 |
+
"import gc\n",
|
| 61 |
+
"import matplotlib.pyplot as plt\n",
|
| 62 |
+
"import torch\n",
|
| 63 |
+
"import torch.nn as nn\n",
|
| 64 |
+
"import torch.nn.functional as F\n",
|
| 65 |
+
"from torchvision import transforms\n",
|
| 66 |
+
"import utils\n",
|
| 67 |
+
"from models import *\n",
|
| 68 |
+
"from mindeye_models import *\n",
|
| 69 |
+
"import nibabel as nib\n",
|
| 70 |
+
"from nilearn import plotting\n",
|
| 71 |
+
"\n",
|
| 72 |
+
"# tf32 data type is faster than standard float32\n",
|
| 73 |
+
"torch.backends.cuda.matmul.allow_tf32 = True\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"### Multi-GPU config ###\n",
|
| 76 |
+
"local_rank = os.getenv('LOCAL_RANK')\n",
|
| 77 |
+
"if local_rank is None: \n",
|
| 78 |
+
" local_rank = 0\n",
|
| 79 |
+
"else:\n",
|
| 80 |
+
" local_rank = int(local_rank)\n",
|
| 81 |
+
"print(f\"LOCAL RANK={local_rank}\")\n",
|
| 82 |
+
"\n",
|
| 83 |
+
"num_devices = os.getenv('NUM_GPUS')\n",
|
| 84 |
+
"if num_devices is None: \n",
|
| 85 |
+
" num_devices = 1\n",
|
| 86 |
+
"else:\n",
|
| 87 |
+
" num_devices = int(num_devices)\n",
|
| 88 |
+
"print(f\"NUM GPUS={num_devices}\")\n",
|
| 89 |
+
"distributed = True if num_devices>1 else False\n",
|
| 90 |
+
"\n",
|
| 91 |
+
"global_rank = os.getenv('RANK')\n",
|
| 92 |
+
"if global_rank is None:\n",
|
| 93 |
+
" global_rank = 0\n",
|
| 94 |
+
"else:\n",
|
| 95 |
+
" global_rank = int(global_rank)\n",
|
| 96 |
+
"print(f\"GLOBAL RANK={global_rank}\")\n",
|
| 97 |
+
"\n",
|
| 98 |
+
"if utils.is_interactive():\n",
|
| 99 |
+
" # Following allows you to change functions in models.py or utils.py and \n",
|
| 100 |
+
" # have this notebook automatically update with your revisions\n",
|
| 101 |
+
" %load_ext autoreload\n",
|
| 102 |
+
" %autoreload 2\n",
|
| 103 |
+
" from tqdm.notebook import tqdm\n",
|
| 104 |
+
"else:\n",
|
| 105 |
+
" from tqdm import tqdm\n",
|
| 106 |
+
"\n",
|
| 107 |
+
"# Load parameters from yaml config\n",
|
| 108 |
+
"config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)\n",
|
| 109 |
+
"\n",
|
| 110 |
+
"# create global variables from the config\n",
|
| 111 |
+
"for attribute_name in config.keys():\n",
|
| 112 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 113 |
+
" \n",
|
| 114 |
+
"# Load MindEye parameters from yaml config (will override any params with same name)\n",
|
| 115 |
+
"mindeye_config = yaml.load(open('mindeye_config.yaml', 'r'), Loader=yaml.FullLoader)\n",
|
| 116 |
+
"\n",
|
| 117 |
+
"# create global variables from the config\n",
|
| 118 |
+
"for attribute_name in mindeye_config.keys():\n",
|
| 119 |
+
" globals()[attribute_name] = mindeye_config[f'{attribute_name}']\n",
|
| 120 |
+
"\n",
|
| 121 |
+
"data_type = torch.float16 # change depending on your mixed_precision\n",
|
| 122 |
+
"\n",
|
| 123 |
+
"batch_size = global_batch_size // num_devices\n",
|
| 124 |
+
"print(\"batch_size\", batch_size)\n",
|
| 125 |
+
" \n",
|
| 126 |
+
"# First use \"accelerate config\" in terminal and setup using deepspeed stage 2 with CPU offloading\n",
|
| 127 |
+
"from accelerate import Accelerator\n",
|
| 128 |
+
"accelerator = Accelerator(split_batches=False, mixed_precision=\"fp16\")\n",
|
| 129 |
+
"\n",
|
| 130 |
+
"from accelerate.state import AcceleratorState\n",
|
| 131 |
+
"try:\n",
|
| 132 |
+
" AcceleratorState().deepspeed_plugin.deepspeed_config['train_micro_batch_size_per_gpu'] = batch_size\n",
|
| 133 |
+
" print(\"deepspeed reconfigured, train_micro_batch_size_per_gpu = \", batch_size)\n",
|
| 134 |
+
"except:\n",
|
| 135 |
+
" print(\"skipping deepspeed reconfiguration...\")\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"print(\"PID of this process =\",os.getpid())\n",
|
| 138 |
+
"device = accelerator.device\n",
|
| 139 |
+
"print(\"device:\",device)\n",
|
| 140 |
+
"world_size = accelerator.state.num_processes\n",
|
| 141 |
+
"num_workers = num_devices\n",
|
| 142 |
+
"print(accelerator.state)\n",
|
| 143 |
+
"\n",
|
| 144 |
+
"print(\"distributed =\",distributed, \"num_devices =\", num_devices, \"local rank =\", local_rank, \"world size =\", world_size, \"data_type =\", data_type)\n",
|
| 145 |
+
"print = accelerator.print # only print if local_rank=0"
|
| 146 |
+
]
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"cell_type": "markdown",
|
| 150 |
+
"id": "c7b35bc5-8674-4061-a606-f44249d167df",
|
| 151 |
+
"metadata": {},
|
| 152 |
+
"source": [
|
| 153 |
+
"# Configuration"
|
| 154 |
+
]
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"cell_type": "code",
|
| 158 |
+
"execution_count": 2,
|
| 159 |
+
"id": "d9fa256a-cc44-4346-8d6c-1bed3779d5b0",
|
| 160 |
+
"metadata": {
|
| 161 |
+
"tags": []
|
| 162 |
+
},
|
| 163 |
+
"outputs": [
|
| 164 |
+
{
|
| 165 |
+
"name": "stdout",
|
| 166 |
+
"output_type": "stream",
|
| 167 |
+
"text": [
|
| 168 |
+
"mae config\n",
|
| 169 |
+
"\n",
|
| 170 |
+
" {'model_name': 'framepatchsize1_4gpu_bs8_3e7_norm_rope_atomcos_001contrastiveweight', 'use_cls_token': False, 'use_contrastive_loss': True, 'contrastive_loss_weight': 0.001, 'batch_size': 8, 'num_workers': 10, 'num_epochs': 100, 'seed': 42, 'max_lr': 3e-07, 'num_samples_per_epoch': 1024, 'ckpt_saving': True, 'ckpt_interval': 50, 'resume_from_ckpt': True, 'wandb_log': True, 'tube_start_masking_ratio': 0.9, 'tube_end_masking_ratio': 0.9, 'decoder_mask_ratio': 0.85, 'patch_size': [8, 8, 8], 'frame_patch_size': 1, 'use_rope_emb': True, 'masking_strategy': 'MNI', 'encoder_model': 'vit_base', 'decoder_model': 'vit_base', 'img_size': [88, 104, 72], 'num_frames': 4, 'is_s3': False, 'train_urls': ['/weka/proj-fmri/shared/NSD_MNI_wds/{000000..000738}.tar']}\n",
|
| 171 |
+
"mindeye_config\n",
|
| 172 |
+
" {'model_name': 'downstream_framepatchsize4_bs8_300ep_contr_nolinear', 'mae_model_name': 'framepatchsize1_4gpu_bs8_300ep_NEWcont_3e8_norm_rope_LONG', 'global_batch_size': 32, 'mixed_precision': 'fp16', 'num_epochs': 12, 'seed': 42, 'max_lr': 0.0003, 'multi_subject': False, 'multisubject_ckpt': 'None', 'ckpt_saving': False, 'ckpt_interval': 99, 'resume_from_ckpt': False, 'wandb_log': False, 'in_dim': 393216, 'hidden_dim': 2056, 'drop': 0.15, 'mixup_pct': 0.0, 'nsd_wds_path': '/weka/proj-fmri/shared/mindeyev2_dataset/wds', 'nsd_raw_path': '/weka/proj-fmri/shared/mindeyev2_dataset', 'nsd_image_path': '/weka/proj-fmri/shared/mindeyev2_dataset', 'num_sessions': 40}\n",
|
| 173 |
+
"mae_ckpt_pth /weka/proj-fmri/paulscotti/fMRI-foundation-model/ckpts/framepatchsize1_4gpu_bs8_300ep_NEWcont_3e8_norm_rope_LONG/last.pth\n",
|
| 174 |
+
"outdir /weka/proj-fmri/paulscotti/fMRI-foundation-model/ckpts/downstream_framepatchsize4_bs8_300ep_contr_nolinear\n",
|
| 175 |
+
"use_cls_token False\n",
|
| 176 |
+
"num_patches 5148\n"
|
| 177 |
+
]
|
| 178 |
+
}
|
| 179 |
+
],
|
| 180 |
+
"source": [
|
| 181 |
+
"print(\"mae config\\n\\n\",config)\n",
|
| 182 |
+
"print(\"mindeye_config\\n\",mindeye_config)\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"# if utils.is_interactive():\n",
|
| 185 |
+
"# ckpt_saving = False\n",
|
| 186 |
+
"# wandb_log = False\n",
|
| 187 |
+
"\n",
|
| 188 |
+
"# seed all random functions\n",
|
| 189 |
+
"utils.seed_everything(seed)\n",
|
| 190 |
+
"\n",
|
| 191 |
+
"mae_ckpt_pth = os.path.abspath(f'../ckpts/{mae_model_name}/last.pth')\n",
|
| 192 |
+
"print(\"mae_ckpt_pth\", mae_ckpt_pth)\n",
|
| 193 |
+
"\n",
|
| 194 |
+
"outdir = os.path.abspath(f'../ckpts/{model_name}')\n",
|
| 195 |
+
"print(\"outdir\", outdir)\n",
|
| 196 |
+
"\n",
|
| 197 |
+
"use_cls_token = False #True if use_contrastive_loss else use_cls_token\n",
|
| 198 |
+
"print(\"use_cls_token\", use_cls_token)\n",
|
| 199 |
+
"\n",
|
| 200 |
+
"if type(patch_size) == int:\n",
|
| 201 |
+
" patch_size = [patch_size,patch_size,patch_size]\n",
|
| 202 |
+
"patch_depth = patch_size[0]\n",
|
| 203 |
+
"patch_height = patch_size[1]\n",
|
| 204 |
+
"patch_width = patch_size[2]\n",
|
| 205 |
+
"\n",
|
| 206 |
+
"num_patches = int(\n",
|
| 207 |
+
" (img_size[0] / patch_depth)\n",
|
| 208 |
+
" * (img_size[1] / patch_height)\n",
|
| 209 |
+
" * (img_size[2] / patch_width)\n",
|
| 210 |
+
" * num_frames\n",
|
| 211 |
+
")\n",
|
| 212 |
+
"print(\"num_patches\", num_patches)"
|
| 213 |
+
]
|
| 214 |
+
},
|
| 215 |
+
{
|
| 216 |
+
"cell_type": "markdown",
|
| 217 |
+
"id": "8115109a-30a1-448e-a1ea-1b115ed8cb6d",
|
| 218 |
+
"metadata": {},
|
| 219 |
+
"source": [
|
| 220 |
+
"# Load pretrained foundation model"
|
| 221 |
+
]
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"cell_type": "code",
|
| 225 |
+
"execution_count": 3,
|
| 226 |
+
"id": "1b40287e-bab2-4791-9407-e63ad5b0a3b4",
|
| 227 |
+
"metadata": {
|
| 228 |
+
"tags": []
|
| 229 |
+
},
|
| 230 |
+
"outputs": [
|
| 231 |
+
{
|
| 232 |
+
"name": "stdout",
|
| 233 |
+
"output_type": "stream",
|
| 234 |
+
"text": [
|
| 235 |
+
"param counts:\n",
|
| 236 |
+
"170,830,848 total\n",
|
| 237 |
+
"170,830,848 trainable\n"
|
| 238 |
+
]
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"data": {
|
| 242 |
+
"text/plain": [
|
| 243 |
+
"170830848"
|
| 244 |
+
]
|
| 245 |
+
},
|
| 246 |
+
"execution_count": 3,
|
| 247 |
+
"metadata": {},
|
| 248 |
+
"output_type": "execute_result"
|
| 249 |
+
}
|
| 250 |
+
],
|
| 251 |
+
"source": [
|
| 252 |
+
"vit_size = {\n",
|
| 253 |
+
" \"encoder\": encoder_model,\n",
|
| 254 |
+
" \"decoder\": decoder_model\n",
|
| 255 |
+
"}\n",
|
| 256 |
+
" \n",
|
| 257 |
+
"model = get_vit(\n",
|
| 258 |
+
" size=vit_size,\n",
|
| 259 |
+
" image_size=img_size, # depth, height, width\n",
|
| 260 |
+
" image_patch_size=(patch_depth,patch_height,patch_width), # depth, height, width patch size\n",
|
| 261 |
+
" frames=num_frames,\n",
|
| 262 |
+
" frame_patch_size=frame_patch_size,\n",
|
| 263 |
+
" channels=1,\n",
|
| 264 |
+
" use_rope_emb=use_rope_emb,\n",
|
| 265 |
+
" use_cls_token=use_cls_token,\n",
|
| 266 |
+
")\n",
|
| 267 |
+
"utils.count_params(model)"
|
| 268 |
+
]
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"cell_type": "markdown",
|
| 272 |
+
"id": "97b45ad3-e8d5-40cd-b041-1defdbf15155",
|
| 273 |
+
"metadata": {},
|
| 274 |
+
"source": [
|
| 275 |
+
"## Load pretrained ckpt for MAE foundation model"
|
| 276 |
+
]
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"cell_type": "code",
|
| 280 |
+
"execution_count": 4,
|
| 281 |
+
"id": "62bb3f5f-cf7f-42c5-ab8c-d4bf143ba727",
|
| 282 |
+
"metadata": {
|
| 283 |
+
"tags": []
|
| 284 |
+
},
|
| 285 |
+
"outputs": [],
|
| 286 |
+
"source": [
|
| 287 |
+
"def save_ckpt(tag):\n",
|
| 288 |
+
" ckpt_path = outdir+f'/{tag}.pth'\n",
|
| 289 |
+
" if accelerator.is_main_process:\n",
|
| 290 |
+
" unwrapped_model = accelerator.unwrap_model(model)\n",
|
| 291 |
+
" torch.save({\n",
|
| 292 |
+
" 'epoch': epoch,\n",
|
| 293 |
+
" 'model_state_dict': unwrapped_model.state_dict(),\n",
|
| 294 |
+
" 'optimizer_state_dict': optimizer.state_dict(),\n",
|
| 295 |
+
" 'lr_scheduler': lr_scheduler.state_dict(),\n",
|
| 296 |
+
" 'train_losses': losses,\n",
|
| 297 |
+
" 'test_losses': test_losses,\n",
|
| 298 |
+
" 'lrs': lrs,\n",
|
| 299 |
+
" }, ckpt_path)\n",
|
| 300 |
+
" print(f\"\\n---saved {outdir}/{tag} ckpt!---\\n\")\n",
|
| 301 |
+
"\n",
|
| 302 |
+
"def load_ckpt(tag,load_lr=True,load_optimizer=True,load_epoch=True,strict=True,outdir=outdir,multisubj_loading=False): \n",
|
| 303 |
+
" print(f\"\\n---loading {outdir}/{tag}.pth ckpt---\\n\")\n",
|
| 304 |
+
" checkpoint = torch.load(outdir+'/last.pth', map_location='cpu')\n",
|
| 305 |
+
" state_dict = checkpoint['model_state_dict']\n",
|
| 306 |
+
" if multisubj_loading: # remove incompatible ridge layer that will otherwise error\n",
|
| 307 |
+
" state_dict.pop('ridge.linears.0.weight',None)\n",
|
| 308 |
+
" model.load_state_dict(state_dict, strict=strict)\n",
|
| 309 |
+
" if load_epoch:\n",
|
| 310 |
+
" globals()[\"epoch\"] = checkpoint['epoch']\n",
|
| 311 |
+
" print(\"Epoch\",epoch)\n",
|
| 312 |
+
" if load_optimizer:\n",
|
| 313 |
+
" optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n",
|
| 314 |
+
" if load_lr:\n",
|
| 315 |
+
" lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])\n",
|
| 316 |
+
" del checkpoint"
|
| 317 |
+
]
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
"cell_type": "code",
|
| 321 |
+
"execution_count": 5,
|
| 322 |
+
"id": "6c426853-d268-4daf-ac19-faa40c2cd362",
|
| 323 |
+
"metadata": {
|
| 324 |
+
"tags": []
|
| 325 |
+
},
|
| 326 |
+
"outputs": [],
|
| 327 |
+
"source": [
|
| 328 |
+
"checkpoint = torch.load(mae_ckpt_pth, map_location=device)\n",
|
| 329 |
+
"model.load_state_dict(checkpoint['model_state_dict'])#, strict=False)\n",
|
| 330 |
+
"\n",
|
| 331 |
+
"# set foundation model to evaluation\n",
|
| 332 |
+
"model.eval()\n",
|
| 333 |
+
"model.requires_grad_(False)\n",
|
| 334 |
+
"model.to(device)\n",
|
| 335 |
+
"pass"
|
| 336 |
+
]
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"cell_type": "markdown",
|
| 340 |
+
"id": "12fd89a4-955f-439d-b3c9-8f261c06eed2",
|
| 341 |
+
"metadata": {},
|
| 342 |
+
"source": [
|
| 343 |
+
"# Setup MindEye model"
|
| 344 |
+
]
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"cell_type": "code",
|
| 348 |
+
"execution_count": 6,
|
| 349 |
+
"id": "1dae53d1-abff-4ddb-98cd-2cab04b9f0cb",
|
| 350 |
+
"metadata": {
|
| 351 |
+
"tags": []
|
| 352 |
+
},
|
| 353 |
+
"outputs": [],
|
| 354 |
+
"source": [
|
| 355 |
+
"nsddata_raw_stimuli = pd.read_csv(f\"{nsd_raw_path}/nsddata_rawdata.csv\")\n",
|
| 356 |
+
"TR_delay = 3 # to account for bold hrf\n",
|
| 357 |
+
"train_TRs = np.round(nsddata_raw_stimuli[nsddata_raw_stimuli['shared1000'] == False]['global_TR_onsets'].values + TR_delay).astype(np.int32)\n",
|
| 358 |
+
"test_TRs = np.round(nsddata_raw_stimuli[nsddata_raw_stimuli['shared1000'] == True]['global_TR_onsets'].values + TR_delay).astype(np.int32)"
|
| 359 |
+
]
|
| 360 |
+
},
|
| 361 |
+
{
|
| 362 |
+
"cell_type": "code",
|
| 363 |
+
"execution_count": 7,
|
| 364 |
+
"id": "4d5015e9-120e-4240-886c-6959796e8faf",
|
| 365 |
+
"metadata": {
|
| 366 |
+
"tags": []
|
| 367 |
+
},
|
| 368 |
+
"outputs": [
|
| 369 |
+
{
|
| 370 |
+
"name": "stdout",
|
| 371 |
+
"output_type": "stream",
|
| 372 |
+
"text": [
|
| 373 |
+
"Loaded all 73k possible NSD images! torch.Size([73000, 3, 224, 224])\n"
|
| 374 |
+
]
|
| 375 |
+
}
|
| 376 |
+
],
|
| 377 |
+
"source": [
|
| 378 |
+
"# Load 73k NSD images\n",
|
| 379 |
+
"f = h5py.File(f'{nsd_image_path}/coco_images_224_float16.hdf5', 'r')\n",
|
| 380 |
+
"images = f['images'][:] \n",
|
| 381 |
+
"images = torch.Tensor(images).to(\"cpu\").to(data_type)\n",
|
| 382 |
+
"print(\"Loaded all 73k possible NSD images!\", images.shape)\n",
|
| 383 |
+
"\n",
|
| 384 |
+
"# Load MindEye hdf5\n",
|
| 385 |
+
"f = h5py.File(f'{nsd_raw_path}/subj01_mnidata.h5', 'r') #subj01_rawdata_old.h5\n",
|
| 386 |
+
"mindeye_global_trs = f['global_trs'][:]\n",
|
| 387 |
+
"mindeye_funcs = f['funcs']"
|
| 388 |
+
]
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"cell_type": "code",
|
| 392 |
+
"execution_count": 8,
|
| 393 |
+
"id": "d79bcf35-45a6-4dc5-90da-c96c3a596401",
|
| 394 |
+
"metadata": {
|
| 395 |
+
"tags": []
|
| 396 |
+
},
|
| 397 |
+
"outputs": [
|
| 398 |
+
{
|
| 399 |
+
"data": {
|
| 400 |
+
"text/plain": [
|
| 401 |
+
"FrozenOpenCLIPImageEmbedder(\n",
|
| 402 |
+
" (model): CLIP(\n",
|
| 403 |
+
" (visual): VisionTransformer(\n",
|
| 404 |
+
" (conv1): Conv2d(3, 1664, kernel_size=(14, 14), stride=(14, 14), bias=False)\n",
|
| 405 |
+
" (patch_dropout): Identity()\n",
|
| 406 |
+
" (ln_pre): LayerNorm((1664,), eps=1e-05, elementwise_affine=True)\n",
|
| 407 |
+
" (transformer): Transformer(\n",
|
| 408 |
+
" (resblocks): ModuleList(\n",
|
| 409 |
+
" (0-47): 48 x ResidualAttentionBlock(\n",
|
| 410 |
+
" (ln_1): LayerNorm((1664,), eps=1e-05, elementwise_affine=True)\n",
|
| 411 |
+
" (attn): MultiheadAttention(\n",
|
| 412 |
+
" (out_proj): NonDynamicallyQuantizableLinear(in_features=1664, out_features=1664, bias=True)\n",
|
| 413 |
+
" )\n",
|
| 414 |
+
" (ls_1): Identity()\n",
|
| 415 |
+
" (ln_2): LayerNorm((1664,), eps=1e-05, elementwise_affine=True)\n",
|
| 416 |
+
" (mlp): Sequential(\n",
|
| 417 |
+
" (c_fc): Linear(in_features=1664, out_features=8192, bias=True)\n",
|
| 418 |
+
" (gelu): GELU(approximate='none')\n",
|
| 419 |
+
" (c_proj): Linear(in_features=8192, out_features=1664, bias=True)\n",
|
| 420 |
+
" )\n",
|
| 421 |
+
" (ls_2): Identity()\n",
|
| 422 |
+
" )\n",
|
| 423 |
+
" )\n",
|
| 424 |
+
" )\n",
|
| 425 |
+
" (ln_post): LayerNorm((1664,), eps=1e-05, elementwise_affine=True)\n",
|
| 426 |
+
" )\n",
|
| 427 |
+
" (token_embedding): Embedding(49408, 1280)\n",
|
| 428 |
+
" (ln_final): LayerNorm((1280,), eps=1e-05, elementwise_affine=True)\n",
|
| 429 |
+
" )\n",
|
| 430 |
+
")"
|
| 431 |
+
]
|
| 432 |
+
},
|
| 433 |
+
"execution_count": 8,
|
| 434 |
+
"metadata": {},
|
| 435 |
+
"output_type": "execute_result"
|
| 436 |
+
}
|
| 437 |
+
],
|
| 438 |
+
"source": [
|
| 439 |
+
"# clip_img_embedder = FrozenOpenCLIPImageEmbedder(\n",
|
| 440 |
+
"# arch=\"ViT-bigG-14\",\n",
|
| 441 |
+
"# version=\"laion2b_s39b_b160k\",\n",
|
| 442 |
+
"# output_tokens=True,\n",
|
| 443 |
+
"# only_tokens=True,\n",
|
| 444 |
+
"# )\n",
|
| 445 |
+
"# clip_seq_dim = 256\n",
|
| 446 |
+
"# clip_emb_dim = 1664\n",
|
| 447 |
+
"# clip_img_embedder.to(device)\n",
|
| 448 |
+
"\n",
|
| 449 |
+
"clip_img_embedder = FrozenOpenCLIPImageEmbedder(\n",
|
| 450 |
+
" arch=\"ViT-bigG-14\",\n",
|
| 451 |
+
" version=\"laion2b_s39b_b160k\",\n",
|
| 452 |
+
" output_tokens=False,\n",
|
| 453 |
+
" only_tokens=False,\n",
|
| 454 |
+
" init_device=device,\n",
|
| 455 |
+
" device=device,\n",
|
| 456 |
+
")\n",
|
| 457 |
+
"clip_seq_dim = 1\n",
|
| 458 |
+
"clip_emb_dim = 1280\n",
|
| 459 |
+
"clip_img_embedder.to(device)"
|
| 460 |
+
]
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"cell_type": "code",
|
| 464 |
+
"execution_count": 9,
|
| 465 |
+
"id": "d89d95f0-b52e-439f-ba5d-c13e2897fe31",
|
| 466 |
+
"metadata": {
|
| 467 |
+
"tags": []
|
| 468 |
+
},
|
| 469 |
+
"outputs": [
|
| 470 |
+
{
|
| 471 |
+
"name": "stdout",
|
| 472 |
+
"output_type": "stream",
|
| 473 |
+
"text": [
|
| 474 |
+
"dividing batch size by subj_list, which will then be concatenated across subj during training...\n",
|
| 475 |
+
"batch_size = 32 num_iterations_per_epoch = 11 num_samples_per_epoch = 375\n",
|
| 476 |
+
"Training with 40 sessions\n",
|
| 477 |
+
"/weka/proj-fmri/shared/mindeyev2_dataset/wds/subj01/train/{0..39}.tar\n",
|
| 478 |
+
"Loaded all subj train dls and betas!\n",
|
| 479 |
+
"\n",
|
| 480 |
+
"/weka/proj-fmri/shared/mindeyev2_dataset/wds/subj01/new_test/0.tar\n",
|
| 481 |
+
"Loaded test dl for subj1!\n",
|
| 482 |
+
"\n"
|
| 483 |
+
]
|
| 484 |
+
}
|
| 485 |
+
],
|
| 486 |
+
"source": [
|
| 487 |
+
"subj = s = 1\n",
|
| 488 |
+
"subj_list = [subj]\n",
|
| 489 |
+
"\n",
|
| 490 |
+
"# if multi_subject:\n",
|
| 491 |
+
"# nsessions_allsubj=np.array([40, 40, 32, 30, 40, 32, 40, 30])\n",
|
| 492 |
+
"# num_samples_per_epoch = (750*40) // num_devices \n",
|
| 493 |
+
"# else:\n",
|
| 494 |
+
"# num_samples_per_epoch = (750*num_sessions) // num_devices \n",
|
| 495 |
+
"\n",
|
| 496 |
+
"num_samples_per_epoch = 375 #(750*num_sessions) // num_devices\n",
|
| 497 |
+
"\n",
|
| 498 |
+
"print(\"dividing batch size by subj_list, which will then be concatenated across subj during training...\") \n",
|
| 499 |
+
"batch_size = batch_size // len(subj_list)\n",
|
| 500 |
+
"num_iterations_per_epoch = num_samples_per_epoch // (batch_size*len(subj_list))\n",
|
| 501 |
+
"print(\"batch_size =\", batch_size, \"num_iterations_per_epoch =\",num_iterations_per_epoch, \"num_samples_per_epoch =\",num_samples_per_epoch)\n",
|
| 502 |
+
"\n",
|
| 503 |
+
"train_data = {}\n",
|
| 504 |
+
"train_dl = {}\n",
|
| 505 |
+
"\n",
|
| 506 |
+
"print(f\"Training with {num_sessions} sessions\")\n",
|
| 507 |
+
"train_url = f\"{nsd_wds_path}/subj0{s}/train/\" + \"{0..\" + f\"{num_sessions-1}\" + \"}.tar\"\n",
|
| 508 |
+
"print(train_url)\n",
|
| 509 |
+
" \n",
|
| 510 |
+
"train_data[f'subj0{s}'] = wds.WebDataset(train_url,resampled=True,nodesplitter=utils.my_split_by_node)\\\n",
|
| 511 |
+
" .shuffle(750, initial=1500, rng=random.Random(42))\\\n",
|
| 512 |
+
" .decode(\"torch\")\\\n",
|
| 513 |
+
" .rename(behav=\"behav.npy\", past_behav=\"past_behav.npy\", future_behav=\"future_behav.npy\", olds_behav=\"olds_behav.npy\")\\\n",
|
| 514 |
+
" .to_tuple(*[\"behav\", \"past_behav\", \"future_behav\", \"olds_behav\"])\n",
|
| 515 |
+
"# train_dl[f'subj0{s}'] = torch.utils.data.DataLoader(train_data[f'subj0{s}'], batch_size=batch_size, shuffle=False, drop_last=True, pin_memory=True)\n",
|
| 516 |
+
"train_dl[f'subj0{s}'] = wds.WebLoader(\n",
|
| 517 |
+
" train_data[f'subj0{s}'].batched(batch_size), \n",
|
| 518 |
+
" pin_memory=True,\n",
|
| 519 |
+
" shuffle=False,\n",
|
| 520 |
+
" batch_size=None,\n",
|
| 521 |
+
" num_workers=num_workers, \n",
|
| 522 |
+
" persistent_workers=num_workers>0,\n",
|
| 523 |
+
").with_epoch(num_iterations_per_epoch)\n",
|
| 524 |
+
"\n",
|
| 525 |
+
"print(\"Loaded all subj train dls and betas!\\n\")\n",
|
| 526 |
+
"if subj==3:\n",
|
| 527 |
+
" num_test=2371\n",
|
| 528 |
+
"elif subj==4:\n",
|
| 529 |
+
" num_test=2188\n",
|
| 530 |
+
"elif subj==6:\n",
|
| 531 |
+
" num_test=2371\n",
|
| 532 |
+
"elif subj==8:\n",
|
| 533 |
+
" num_test=2188\n",
|
| 534 |
+
"else:\n",
|
| 535 |
+
" num_test=3000\n",
|
| 536 |
+
"test_url = f\"{nsd_wds_path}/subj0{subj}/new_test/\" + \"0.tar\"\n",
|
| 537 |
+
"print(test_url)\n",
|
| 538 |
+
"test_data = wds.WebDataset(test_url,resampled=True,nodesplitter=utils.my_split_by_node)\\\n",
|
| 539 |
+
" .decode(\"torch\")\\\n",
|
| 540 |
+
" .rename(behav=\"behav.npy\", past_behav=\"past_behav.npy\", future_behav=\"future_behav.npy\", olds_behav=\"olds_behav.npy\")\\\n",
|
| 541 |
+
" .to_tuple(*[\"behav\", \"past_behav\", \"future_behav\", \"olds_behav\"])\n",
|
| 542 |
+
"# test_dl = torch.utils.data.DataLoader(test_data, batch_size=num_test, shuffle=False, drop_last=True, pin_memory=True)\n",
|
| 543 |
+
"test_dl = wds.WebLoader(\n",
|
| 544 |
+
" test_data.batched(num_test),\n",
|
| 545 |
+
" pin_memory=True,\n",
|
| 546 |
+
" shuffle=False,\n",
|
| 547 |
+
" batch_size=None,\n",
|
| 548 |
+
" num_workers=num_workers,\n",
|
| 549 |
+
" persistent_workers=num_workers>0,\n",
|
| 550 |
+
").with_epoch(10)\n",
|
| 551 |
+
"print(f\"Loaded test dl for subj{subj}!\\n\")"
|
| 552 |
+
]
|
| 553 |
+
},
|
| 554 |
+
{
|
| 555 |
+
"cell_type": "code",
|
| 556 |
+
"execution_count": 10,
|
| 557 |
+
"id": "e100e116-4b98-4c15-bd8e-252871e72517",
|
| 558 |
+
"metadata": {
|
| 559 |
+
"tags": []
|
| 560 |
+
},
|
| 561 |
+
"outputs": [
|
| 562 |
+
{
|
| 563 |
+
"name": "stdout",
|
| 564 |
+
"output_type": "stream",
|
| 565 |
+
"text": [
|
| 566 |
+
"param counts:\n",
|
| 567 |
+
"507,388,672 total\n",
|
| 568 |
+
"507,388,672 trainable\n",
|
| 569 |
+
"param counts:\n",
|
| 570 |
+
"507,388,672 total\n",
|
| 571 |
+
"507,388,672 trainable\n"
|
| 572 |
+
]
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"data": {
|
| 576 |
+
"text/plain": [
|
| 577 |
+
"507388672"
|
| 578 |
+
]
|
| 579 |
+
},
|
| 580 |
+
"execution_count": 10,
|
| 581 |
+
"metadata": {},
|
| 582 |
+
"output_type": "execute_result"
|
| 583 |
+
}
|
| 584 |
+
],
|
| 585 |
+
"source": [
|
| 586 |
+
"class MindEyeModule(nn.Module):\n",
|
| 587 |
+
" def __init__(self):\n",
|
| 588 |
+
" super(MindEyeModule, self).__init__()\n",
|
| 589 |
+
" def forward(self, x):\n",
|
| 590 |
+
" return x\n",
|
| 591 |
+
"\n",
|
| 592 |
+
"# class RidgeRegression(torch.nn.Module):\n",
|
| 593 |
+
"# # make sure to add weight_decay when initializing optimizer\n",
|
| 594 |
+
"# def __init__(self, input_sizes, out_features, seq_len=1): \n",
|
| 595 |
+
"# super(RidgeRegression, self).__init__()\n",
|
| 596 |
+
"# self.seq_len = seq_len\n",
|
| 597 |
+
"# self.out_features = out_features\n",
|
| 598 |
+
"# self.linears = torch.nn.ModuleList([\n",
|
| 599 |
+
"# torch.nn.Linear(input_size, out_features) for input_size in input_sizes\n",
|
| 600 |
+
"# ])\n",
|
| 601 |
+
"# def forward(self, x, subj_idx):\n",
|
| 602 |
+
"# out = torch.cat([self.linears[subj_idx](x[:,seq]).unsqueeze(1) for seq in range(self.seq_len)], dim=1)\n",
|
| 603 |
+
"# return out\n",
|
| 604 |
+
" \n",
|
| 605 |
+
"class MLP(torch.nn.Module):\n",
|
| 606 |
+
" def __init__(self, input_sizes, out_features, seq_len=0): \n",
|
| 607 |
+
" super(MLP, self).__init__()\n",
|
| 608 |
+
" self.input_sizes = input_sizes[0]\n",
|
| 609 |
+
" self.out_features = out_features\n",
|
| 610 |
+
" self.mlp = nn.Sequential(\n",
|
| 611 |
+
" nn.LayerNorm(self.input_sizes),\n",
|
| 612 |
+
" nn.GELU(),\n",
|
| 613 |
+
" nn.Linear(self.input_sizes, out_features),\n",
|
| 614 |
+
" nn.LayerNorm(out_features),\n",
|
| 615 |
+
" nn.GELU(),\n",
|
| 616 |
+
" nn.Linear(out_features, out_features),\n",
|
| 617 |
+
" nn.LayerNorm(out_features),\n",
|
| 618 |
+
" nn.GELU(),\n",
|
| 619 |
+
" nn.Linear(out_features, out_features)\n",
|
| 620 |
+
" )\n",
|
| 621 |
+
" def forward(self, x, z=None):\n",
|
| 622 |
+
" out = self.mlp(x[:,0])\n",
|
| 623 |
+
" return out\n",
|
| 624 |
+
"\n",
|
| 625 |
+
"\n",
|
| 626 |
+
"mindeye = MindEyeModule()\n",
|
| 627 |
+
"mindeye.ridge = MLP(np.array([in_dim]), out_features=clip_emb_dim*clip_seq_dim)\n",
|
| 628 |
+
"# mindeye.backbone = BrainNetwork(h=hidden_dim, in_dim=hidden_dim, n_blocks=4, drop=drop,\n",
|
| 629 |
+
"# clip_size=clip_emb_dim, out_dim=clip_emb_dim*clip_seq_dim, clip_scale=1)\n",
|
| 630 |
+
"utils.count_params(mindeye.ridge)\n",
|
| 631 |
+
"# utils.count_params(mindeye.backbone)\n",
|
| 632 |
+
"utils.count_params(mindeye)"
|
| 633 |
+
]
|
| 634 |
+
},
|
| 635 |
+
{
|
| 636 |
+
"cell_type": "code",
|
| 637 |
+
"execution_count": 11,
|
| 638 |
+
"id": "47ad9ec0-e0b5-4d2c-9d9b-027a85d176b1",
|
| 639 |
+
"metadata": {
|
| 640 |
+
"tags": []
|
| 641 |
+
},
|
| 642 |
+
"outputs": [
|
| 643 |
+
{
|
| 644 |
+
"name": "stdout",
|
| 645 |
+
"output_type": "stream",
|
| 646 |
+
"text": [
|
| 647 |
+
"total_steps 132\n",
|
| 648 |
+
"\n",
|
| 649 |
+
"Done with model preparations!\n",
|
| 650 |
+
"param counts:\n",
|
| 651 |
+
"507,388,672 total\n",
|
| 652 |
+
"507,388,672 trainable\n"
|
| 653 |
+
]
|
| 654 |
+
}
|
| 655 |
+
],
|
| 656 |
+
"source": [
|
| 657 |
+
"no_decay = [\"bias\", \"LayerNorm.bias\", \"LayerNorm.weight\"]\n",
|
| 658 |
+
"opt_grouped_parameters = [\n",
|
| 659 |
+
" {'params': [p for n, p in mindeye.ridge.named_parameters()], 'weight_decay': 1e-2},\n",
|
| 660 |
+
" # {'params': [p for n, p in mindeye.backbone.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 1e-2},\n",
|
| 661 |
+
" # {'params': [p for n, p in mindeye.backbone.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},\n",
|
| 662 |
+
"]\n",
|
| 663 |
+
"\n",
|
| 664 |
+
"optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=max_lr)\n",
|
| 665 |
+
"\n",
|
| 666 |
+
"total_steps = num_epochs * num_iterations_per_epoch\n",
|
| 667 |
+
"print(\"total_steps\", total_steps)\n",
|
| 668 |
+
"pct_start = 2/num_epochs if num_epochs>1 else 1.\n",
|
| 669 |
+
"lr_scheduler = torch.optim.lr_scheduler.OneCycleLR(\n",
|
| 670 |
+
" optimizer,\n",
|
| 671 |
+
" max_lr=max_lr,\n",
|
| 672 |
+
" total_steps=total_steps,\n",
|
| 673 |
+
")\n",
|
| 674 |
+
"\n",
|
| 675 |
+
"print(\"\\nDone with model preparations!\")\n",
|
| 676 |
+
"num_params = utils.count_params(mindeye)"
|
| 677 |
+
]
|
| 678 |
+
},
|
| 679 |
+
{
|
| 680 |
+
"cell_type": "markdown",
|
| 681 |
+
"id": "2661e670-89b3-4598-a0ba-774e376d0047",
|
| 682 |
+
"metadata": {},
|
| 683 |
+
"source": [
|
| 684 |
+
"# Start wandb (if enabled)"
|
| 685 |
+
]
|
| 686 |
+
},
|
| 687 |
+
{
|
| 688 |
+
"cell_type": "code",
|
| 689 |
+
"execution_count": 12,
|
| 690 |
+
"id": "eae9f959-4457-428a-afb5-45e852490ffe",
|
| 691 |
+
"metadata": {},
|
| 692 |
+
"outputs": [],
|
| 693 |
+
"source": [
|
| 694 |
+
"if accelerator.is_main_process and wandb_log: # only use main process for wandb logging\n",
|
| 695 |
+
" import wandb\n",
|
| 696 |
+
" wandb_project = 'found_downstream'\n",
|
| 697 |
+
" print(f\"wandb {wandb_project} run {model_name}\")\n",
|
| 698 |
+
" # need to configure wandb beforehand in terminal with \"wandb init\"!\n",
|
| 699 |
+
" wandb_config = {\n",
|
| 700 |
+
" \"model_name\": model_name,\n",
|
| 701 |
+
" \"mae_model_name\": mae_model_name,\n",
|
| 702 |
+
" \"global_batch_size\": global_batch_size,\n",
|
| 703 |
+
" \"batch_size\": batch_size,\n",
|
| 704 |
+
" \"num_epochs\": num_epochs,\n",
|
| 705 |
+
" \"num_sessions\": num_sessions,\n",
|
| 706 |
+
" \"num_samples_per_epoch\": num_samples_per_epoch,\n",
|
| 707 |
+
" \"in_dim\": in_dim,\n",
|
| 708 |
+
" \"hidden_dim\": hidden_dim,\n",
|
| 709 |
+
" \"mixup_pct\": mixup_pct,\n",
|
| 710 |
+
" \"num_params\": num_params,\n",
|
| 711 |
+
" \"max_lr\": max_lr,\n",
|
| 712 |
+
" \"ckpt_interval\": ckpt_interval,\n",
|
| 713 |
+
" \"ckpt_saving\": ckpt_saving,\n",
|
| 714 |
+
" \"seed\": seed,\n",
|
| 715 |
+
" \"distributed\": distributed,\n",
|
| 716 |
+
" \"num_devices\": num_devices,\n",
|
| 717 |
+
" \"world_size\": world_size,\n",
|
| 718 |
+
" \"train_url\": train_url,\n",
|
| 719 |
+
" \"test_url\": test_url,\n",
|
| 720 |
+
" }\n",
|
| 721 |
+
" print(\"wandb_config:\\n\",wandb_config)\n",
|
| 722 |
+
" print(\"wandb_id:\",model_name)\n",
|
| 723 |
+
" wandb.init(\n",
|
| 724 |
+
" id=model_name,\n",
|
| 725 |
+
" project=wandb_project,\n",
|
| 726 |
+
" name=model_name,\n",
|
| 727 |
+
" config=wandb_config,\n",
|
| 728 |
+
" resume=\"allow\",\n",
|
| 729 |
+
" )\n",
|
| 730 |
+
"else:\n",
|
| 731 |
+
" wandb_log = False"
|
| 732 |
+
]
|
| 733 |
+
},
|
| 734 |
+
{
|
| 735 |
+
"cell_type": "markdown",
|
| 736 |
+
"id": "7230f232-1700-4967-951d-fb566ae637b3",
|
| 737 |
+
"metadata": {},
|
| 738 |
+
"source": [
|
| 739 |
+
"# Train MindEye model using foundation model inputs"
|
| 740 |
+
]
|
| 741 |
+
},
|
| 742 |
+
{
|
| 743 |
+
"cell_type": "code",
|
| 744 |
+
"execution_count": 13,
|
| 745 |
+
"id": "a8b849b8-04b2-4908-9b46-a4fa9a565d5a",
|
| 746 |
+
"metadata": {
|
| 747 |
+
"tags": []
|
| 748 |
+
},
|
| 749 |
+
"outputs": [],
|
| 750 |
+
"source": [
|
| 751 |
+
"epoch = 0\n",
|
| 752 |
+
"losses, test_losses, lrs = [], [], []\n",
|
| 753 |
+
"best_test_loss = 1e9\n",
|
| 754 |
+
"torch.cuda.empty_cache()"
|
| 755 |
+
]
|
| 756 |
+
},
|
| 757 |
+
{
|
| 758 |
+
"cell_type": "code",
|
| 759 |
+
"execution_count": 14,
|
| 760 |
+
"id": "b91b0e3c-c939-4717-8abc-2f3b8c39d012",
|
| 761 |
+
"metadata": {},
|
| 762 |
+
"outputs": [],
|
| 763 |
+
"source": [
|
| 764 |
+
"# if masking_strategy==\"MNI\":\n",
|
| 765 |
+
"from einops.layers.torch import Rearrange\n",
|
| 766 |
+
"\n",
|
| 767 |
+
"MNI_brain = nib.load(\"/weka/proj-fmri/paulscotti/fMRI-foundation-model/dataset_creation/afni_conversion/tpl-MNI152NLin2009cAsym_res-02_T1w_brain.nii.gz\").get_fdata()\n",
|
| 768 |
+
"brain_pos_voxels = MNI_brain[6:94,8:112,10:82]\n",
|
| 769 |
+
"\n",
|
| 770 |
+
"# brain_pos_voxels = brain_pos_voxels[:,30:31,:]\n",
|
| 771 |
+
"\n",
|
| 772 |
+
"brain_pos_pats = Rearrange(\n",
|
| 773 |
+
" \"b c (f pf) (d pd) (h ph) (w pw) -> b f d h w (pd ph pw pf c)\",\n",
|
| 774 |
+
" pd=patch_depth,\n",
|
| 775 |
+
" ph=patch_height,\n",
|
| 776 |
+
" pw=patch_width,\n",
|
| 777 |
+
" pf=1,\n",
|
| 778 |
+
" )(torch.Tensor(brain_pos_voxels)[None,None,None])\n",
|
| 779 |
+
"\n",
|
| 780 |
+
"brain_pos_pats_vit = rearrange(brain_pos_pats, \"b ... d -> b (...) d\").mean(-1)[0]\n",
|
| 781 |
+
" \n",
|
| 782 |
+
"tube_mask = torch.zeros(num_patches // num_frames).to(torch.bool)\n",
|
| 783 |
+
"batch_positive_approx = (brain_pos_pats_vit > 0)\n",
|
| 784 |
+
"mask_idx_candidates = torch.where(batch_positive_approx)[0]\n",
|
| 785 |
+
"mask_idx_candidates = mask_idx_candidates[torch.randperm(len(mask_idx_candidates))]\n",
|
| 786 |
+
"tube_idx = mask_idx_candidates[:int(num_patches / num_frames * (1 - tube_end_masking_ratio))]\n",
|
| 787 |
+
"tube_mask[tube_idx] = True\n",
|
| 788 |
+
"tube_mask = tube_mask.tile(num_frames//frame_patch_size)"
|
| 789 |
+
]
|
| 790 |
+
},
|
| 791 |
+
{
|
| 792 |
+
"cell_type": "code",
|
| 793 |
+
"execution_count": 15,
|
| 794 |
+
"id": "cac228e7-bdf8-4cc7-ab9d-891b5bd5abbd",
|
| 795 |
+
"metadata": {},
|
| 796 |
+
"outputs": [],
|
| 797 |
+
"source": [
|
| 798 |
+
"# load multisubject stage1 ckpt if set\n",
|
| 799 |
+
"if multisubject_ckpt!=\"None\" and not resume_from_ckpt:\n",
|
| 800 |
+
" load_ckpt(\"last\",outdir=multisubject_ckpt,load_lr=False,load_optimizer=False,load_epoch=False,strict=False,multisubj_loading=True)\n",
|
| 801 |
+
" \n",
|
| 802 |
+
"# load saved ckpt model weights into current model\n",
|
| 803 |
+
"if resume_from_ckpt:\n",
|
| 804 |
+
" load_ckpt(\"last\",load_lr=True,load_optimizer=True,load_epoch=True)\n",
|
| 805 |
+
"elif wandb_log:\n",
|
| 806 |
+
" if wandb.run.resumed:\n",
|
| 807 |
+
" load_ckpt(\"last\",load_lr=True,load_optimizer=True,load_epoch=True)"
|
| 808 |
+
]
|
| 809 |
+
},
|
| 810 |
+
{
|
| 811 |
+
"cell_type": "code",
|
| 812 |
+
"execution_count": 16,
|
| 813 |
+
"id": "b0a23e2f-eb06-4940-9b41-ca0e36dd210f",
|
| 814 |
+
"metadata": {},
|
| 815 |
+
"outputs": [],
|
| 816 |
+
"source": [
|
| 817 |
+
"train_dls = [train_dl[f'subj0{s}'] for s in subj_list]\n",
|
| 818 |
+
"\n",
|
| 819 |
+
"mindeye, optimizer, *train_dls, lr_scheduler = accelerator.prepare(mindeye, optimizer, *train_dls, lr_scheduler)\n",
|
| 820 |
+
"# leaving out test_dl since we will only have local_rank 0 device do evals"
|
| 821 |
+
]
|
| 822 |
+
},
|
| 823 |
+
{
|
| 824 |
+
"cell_type": "code",
|
| 825 |
+
"execution_count": 17,
|
| 826 |
+
"id": "dd08879b-0177-4cac-9438-a82ce0805bc1",
|
| 827 |
+
"metadata": {
|
| 828 |
+
"tags": []
|
| 829 |
+
},
|
| 830 |
+
"outputs": [
|
| 831 |
+
{
|
| 832 |
+
"name": "stdout",
|
| 833 |
+
"output_type": "stream",
|
| 834 |
+
"text": [
|
| 835 |
+
"downstream_framepatchsize4_bs8_300ep_contr_nolinear starting with epoch 0 / 12\n"
|
| 836 |
+
]
|
| 837 |
+
},
|
| 838 |
+
{
|
| 839 |
+
"data": {
|
| 840 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 841 |
+
"model_id": "60a889b1ee2d4d7a8acff2ffaf3a1ca1",
|
| 842 |
+
"version_major": 2,
|
| 843 |
+
"version_minor": 0
|
| 844 |
+
},
|
| 845 |
+
"text/plain": [
|
| 846 |
+
" 0%| | 0/12 [00:00<?, ?it/s]"
|
| 847 |
+
]
|
| 848 |
+
},
|
| 849 |
+
"metadata": {},
|
| 850 |
+
"output_type": "display_data"
|
| 851 |
+
},
|
| 852 |
+
{
|
| 853 |
+
"data": {
|
| 854 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 855 |
+
"model_id": "6cc02dd1cf08486892e038df7de47cf8",
|
| 856 |
+
"version_major": 2,
|
| 857 |
+
"version_minor": 0
|
| 858 |
+
},
|
| 859 |
+
"text/plain": [
|
| 860 |
+
" 0%| | 0/11 [00:00<?, ?it/s]"
|
| 861 |
+
]
|
| 862 |
+
},
|
| 863 |
+
"metadata": {},
|
| 864 |
+
"output_type": "display_data"
|
| 865 |
+
},
|
| 866 |
+
{
|
| 867 |
+
"ename": "NameError",
|
| 868 |
+
"evalue": "name 'err' is not defined",
|
| 869 |
+
"output_type": "error",
|
| 870 |
+
"traceback": [
|
| 871 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
| 872 |
+
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
|
| 873 |
+
"Cell \u001b[0;32mIn[17], line 51\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[38;5;66;03m## Process it through pretrained MAE ##\u001b[39;00m\n\u001b[1;32m 50\u001b[0m encoder_out \u001b[38;5;241m=\u001b[39m model(voxels_raw, encoder_mask\u001b[38;5;241m=\u001b[39mtube_mask)\n\u001b[0;32m---> 51\u001b[0m \u001b[43merr\u001b[49m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;66;03m# encoder_out = bn(encoder_out)\u001b[39;00m\n\u001b[1;32m 54\u001b[0m voxel0 \u001b[38;5;241m=\u001b[39m encoder_out\u001b[38;5;241m.\u001b[39mflatten(\u001b[38;5;241m1\u001b[39m)\u001b[38;5;241m.\u001b[39munsqueeze(\u001b[38;5;241m1\u001b[39m)\n",
|
| 874 |
+
"\u001b[0;31mNameError\u001b[0m: name 'err' is not defined"
|
| 875 |
+
]
|
| 876 |
+
}
|
| 877 |
+
],
|
| 878 |
+
"source": [
|
| 879 |
+
"print(f\"{model_name} starting with epoch {epoch} / {num_epochs}\")\n",
|
| 880 |
+
"progress_bar = tqdm(range(epoch, num_epochs), disable=not accelerator.is_main_process)\n",
|
| 881 |
+
"mse = nn.MSELoss()\n",
|
| 882 |
+
"soft_loss_temps = utils.cosine_anneal(0.004, 0.0075, num_epochs - int(mixup_pct * num_epochs))\n",
|
| 883 |
+
"\n",
|
| 884 |
+
"bn = nn.BatchNorm1d(512,affine=False).to(device)\n",
|
| 885 |
+
"\n",
|
| 886 |
+
"test_image=None\n",
|
| 887 |
+
"num_test_eval=batch_size # should instead be average same-image 300 to mimic MindEye2 retrieval evaluation\n",
|
| 888 |
+
"\n",
|
| 889 |
+
"for epoch in progress_bar:\n",
|
| 890 |
+
" mindeye.train()\n",
|
| 891 |
+
"\n",
|
| 892 |
+
" fwd_percent_correct = 0.\n",
|
| 893 |
+
" bwd_percent_correct = 0.\n",
|
| 894 |
+
" test_fwd_percent_correct = 0.\n",
|
| 895 |
+
" test_bwd_percent_correct = 0.\n",
|
| 896 |
+
" loss_clip_total = 0.\n",
|
| 897 |
+
" test_loss_clip_total = 0.\n",
|
| 898 |
+
"\n",
|
| 899 |
+
" # pre-load all batches for this epoch (it's MUCH faster to pre-load in bulk than to separate loading per batch)\n",
|
| 900 |
+
" voxel_iters = {} # empty dict because diff subjects have differing # of voxels\n",
|
| 901 |
+
" image_iters = torch.zeros(num_iterations_per_epoch, batch_size*len(subj_list), 3, 224, 224).float()\n",
|
| 902 |
+
" annot_iters = {}\n",
|
| 903 |
+
" perm_iters, betas_iters, select_iters = {}, {}, {}\n",
|
| 904 |
+
" for s, train_dl in enumerate(train_dls):\n",
|
| 905 |
+
" with torch.cuda.amp.autocast(dtype=data_type):\n",
|
| 906 |
+
" for iter, (behav0, past_behav0, future_behav0, old_behav0) in enumerate(tqdm(train_dl,total=num_iterations_per_epoch)):\n",
|
| 907 |
+
" image0 = images[behav0[:,0,0].cpu().long()].float()\n",
|
| 908 |
+
" image_iters[iter,s*batch_size:s*batch_size+batch_size] = image0\n",
|
| 909 |
+
"\n",
|
| 910 |
+
" # if images are not fully preloaded, then can do this inefficient but more memory friendly approach\n",
|
| 911 |
+
" # for ib,b in enumerate(behav0[:,0,0].cpu().long()):\n",
|
| 912 |
+
" # if ib==0:\n",
|
| 913 |
+
" # image0 = torch.Tensor(images[[b]])\n",
|
| 914 |
+
" # else:\n",
|
| 915 |
+
" # image0 = torch.vstack((image0, torch.Tensor(images[[b]])))\n",
|
| 916 |
+
" # image_iters[iter,s*batch_size:s*batch_size+batch_size] = image0\n",
|
| 917 |
+
" \n",
|
| 918 |
+
" # get the corresponding raw voxel time series\n",
|
| 919 |
+
" for ib,b in enumerate(behav0[:,0,5].cpu().long().numpy()):\n",
|
| 920 |
+
" tr = (nsddata_raw_stimuli[nsddata_raw_stimuli['global_trial'].isin([b.item()])]['global_TR_onsets'].values + TR_delay).astype(np.int32).item()\n",
|
| 921 |
+
" if ib==0:\n",
|
| 922 |
+
" voxels_raw = mindeye_funcs[tr-2:tr+2][None][None]\n",
|
| 923 |
+
" else:\n",
|
| 924 |
+
" voxels_raw = np.vstack((voxels_raw, mindeye_funcs[tr-2:tr+2][None][None]))\n",
|
| 925 |
+
" voxels_raw = torch.Tensor(voxels_raw).clamp(0,1).to(device)\n",
|
| 926 |
+
" \n",
|
| 927 |
+
" ## Process it through pretrained MAE ##\n",
|
| 928 |
+
" encoder_out = model(voxels_raw, encoder_mask=tube_mask)\n",
|
| 929 |
+
" err\n",
|
| 930 |
+
" # encoder_out = bn(encoder_out)\n",
|
| 931 |
+
" \n",
|
| 932 |
+
" voxel0 = encoder_out.flatten(1).unsqueeze(1)\n",
|
| 933 |
+
" voxel0 = nn.functional.normalize(voxel0,dim=-1).cpu()\n",
|
| 934 |
+
" \n",
|
| 935 |
+
" assert len(voxel0) == batch_size\n",
|
| 936 |
+
"\n",
|
| 937 |
+
" if epoch < int(mixup_pct * num_epochs):\n",
|
| 938 |
+
" voxel0, perm, betas, select = utils.mixco(voxel0)\n",
|
| 939 |
+
" perm_iters[f\"subj0{subj_list[s]}_iter{iter}\"] = perm\n",
|
| 940 |
+
" betas_iters[f\"subj0{subj_list[s]}_iter{iter}\"] = betas\n",
|
| 941 |
+
" select_iters[f\"subj0{subj_list[s]}_iter{iter}\"] = select\n",
|
| 942 |
+
"\n",
|
| 943 |
+
" voxel_iters[f\"subj0{subj_list[s]}_iter{iter}\"] = voxel0\n",
|
| 944 |
+
"\n",
|
| 945 |
+
" if iter >= num_iterations_per_epoch:\n",
|
| 946 |
+
" break\n",
|
| 947 |
+
"\n",
|
| 948 |
+
" # you now have voxel_iters and image_iters with num_iterations_per_epoch batches each\n",
|
| 949 |
+
" for train_i in range(num_iterations_per_epoch):\n",
|
| 950 |
+
" with torch.cuda.amp.autocast(dtype=data_type):\n",
|
| 951 |
+
" optimizer.zero_grad()\n",
|
| 952 |
+
" loss=0.\n",
|
| 953 |
+
"\n",
|
| 954 |
+
" voxel_list = [voxel_iters[f\"subj0{s}_iter{train_i}\"].detach().to(device) for s in subj_list]\n",
|
| 955 |
+
" image = image_iters[train_i].detach()\n",
|
| 956 |
+
" image = image.to(device)\n",
|
| 957 |
+
"\n",
|
| 958 |
+
" clip_target = clip_img_embedder(image)\n",
|
| 959 |
+
" assert not torch.any(torch.isnan(clip_target))\n",
|
| 960 |
+
"\n",
|
| 961 |
+
" if epoch < int(mixup_pct * num_epochs):\n",
|
| 962 |
+
" perm_list = [perm_iters[f\"subj0{s}_iter{train_i}\"].detach().to(device) for s in subj_list]\n",
|
| 963 |
+
" perm = torch.cat(perm_list, dim=0)\n",
|
| 964 |
+
" betas_list = [betas_iters[f\"subj0{s}_iter{train_i}\"].detach().to(device) for s in subj_list]\n",
|
| 965 |
+
" betas = torch.cat(betas_list, dim=0)\n",
|
| 966 |
+
" select_list = [select_iters[f\"subj0{s}_iter{train_i}\"].detach().to(device) for s in subj_list]\n",
|
| 967 |
+
" select = torch.cat(select_list, dim=0)\n",
|
| 968 |
+
"\n",
|
| 969 |
+
" voxel_ridge_list = [mindeye.ridge(voxel_list[si],si) for si,s in enumerate(subj_list)]\n",
|
| 970 |
+
" clip_voxels = torch.cat(voxel_ridge_list, dim=0)\n",
|
| 971 |
+
"\n",
|
| 972 |
+
"# backbone, clip_voxels = mindeye.backbone(voxel_ridge)\n",
|
| 973 |
+
"\n",
|
| 974 |
+
" clip_voxels_norm = nn.functional.normalize(clip_voxels.flatten(1), dim=-1)\n",
|
| 975 |
+
" clip_target_norm = nn.functional.normalize(clip_target.flatten(1), dim=-1)\n",
|
| 976 |
+
"\n",
|
| 977 |
+
" if epoch < int(mixup_pct * num_epochs): \n",
|
| 978 |
+
" loss_clip = utils.mixco_nce(\n",
|
| 979 |
+
" clip_voxels_norm,\n",
|
| 980 |
+
" clip_target_norm,\n",
|
| 981 |
+
" temp=.006,\n",
|
| 982 |
+
" perm=perm, betas=betas, select=select)\n",
|
| 983 |
+
" else:\n",
|
| 984 |
+
" epoch_temp = soft_loss_temps[epoch-int(mixup_pct*num_epochs)]\n",
|
| 985 |
+
" loss_clip = utils.soft_clip_loss(\n",
|
| 986 |
+
" clip_voxels_norm,\n",
|
| 987 |
+
" clip_target_norm,\n",
|
| 988 |
+
" temp=epoch_temp)\n",
|
| 989 |
+
"\n",
|
| 990 |
+
" loss_clip_total += loss_clip.item()\n",
|
| 991 |
+
" loss += loss_clip\n",
|
| 992 |
+
"\n",
|
| 993 |
+
" # forward and backward top 1 accuracy \n",
|
| 994 |
+
" labels = torch.arange(len(clip_voxels_norm)).to(clip_voxels_norm.device) \n",
|
| 995 |
+
" fwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_voxels_norm, clip_target_norm), labels, k=1).item()\n",
|
| 996 |
+
" bwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_target_norm, clip_voxels_norm), labels, k=1).item()\n",
|
| 997 |
+
"\n",
|
| 998 |
+
" utils.check_loss(loss)\n",
|
| 999 |
+
" accelerator.backward(loss)\n",
|
| 1000 |
+
" optimizer.step()\n",
|
| 1001 |
+
"\n",
|
| 1002 |
+
" losses.append(loss.item())\n",
|
| 1003 |
+
" lrs.append(optimizer.param_groups[0]['lr'])\n",
|
| 1004 |
+
"\n",
|
| 1005 |
+
" lr_scheduler.step()\n",
|
| 1006 |
+
"\n",
|
| 1007 |
+
" mindeye.eval()\n",
|
| 1008 |
+
" if local_rank==0:\n",
|
| 1009 |
+
" with torch.no_grad(), torch.cuda.amp.autocast(dtype=data_type): \n",
|
| 1010 |
+
" for test_i, (behav, past_behav, future_behav, old_behav) in enumerate(test_dl): \n",
|
| 1011 |
+
" loss=0. \n",
|
| 1012 |
+
"\n",
|
| 1013 |
+
" coco_idx = behav[:,0,0].cpu().long()\n",
|
| 1014 |
+
" _,test_indices = np.unique(coco_idx, return_index=True)\n",
|
| 1015 |
+
" test_indices = np.random.permutation(test_indices)[:num_test_eval]\n",
|
| 1016 |
+
" image = images[coco_idx[test_indices]].float().to(device)\n",
|
| 1017 |
+
" \n",
|
| 1018 |
+
" # get the corresponding raw voxel time series\n",
|
| 1019 |
+
" for ib,b in enumerate(behav[test_indices,0,5].cpu().long().numpy()):\n",
|
| 1020 |
+
" tr = (nsddata_raw_stimuli[nsddata_raw_stimuli['global_trial'].isin([b.item()])]['global_TR_onsets'].values + TR_delay).astype(np.int32).item()\n",
|
| 1021 |
+
" if ib==0:\n",
|
| 1022 |
+
" voxels_raw = mindeye_funcs[tr-2:tr+2][None][None]\n",
|
| 1023 |
+
" else:\n",
|
| 1024 |
+
" voxels_raw = np.vstack((voxels_raw, mindeye_funcs[tr-2:tr+2][None][None]))\n",
|
| 1025 |
+
" voxels_raw = torch.Tensor(voxels_raw).clamp(0,1).to(device)\n",
|
| 1026 |
+
" \n",
|
| 1027 |
+
" ## Process it through pretrained MAE ##\n",
|
| 1028 |
+
" encoder_out = model(voxels_raw, encoder_mask=tube_mask)\n",
|
| 1029 |
+
" # encoder_out = bn(encoder_out)\n",
|
| 1030 |
+
" \n",
|
| 1031 |
+
" voxel = encoder_out.flatten(1).unsqueeze(1)\n",
|
| 1032 |
+
" voxel = nn.functional.normalize(voxel,dim=-1)\n",
|
| 1033 |
+
"\n",
|
| 1034 |
+
" assert len(image) == num_test_eval\n",
|
| 1035 |
+
"\n",
|
| 1036 |
+
" clip_target = clip_img_embedder(image.float())\n",
|
| 1037 |
+
" \n",
|
| 1038 |
+
" clip_voxels_norm = nn.functional.normalize(voxel.flatten(1), dim=-1)\n",
|
| 1039 |
+
"\n",
|
| 1040 |
+
" clip_voxels = mindeye.ridge(voxel,0) # 0th index of subj_list\n",
|
| 1041 |
+
" \n",
|
| 1042 |
+
"# backbone, clip_voxels = mindeye.backbone(voxel_ridge)\n",
|
| 1043 |
+
"\n",
|
| 1044 |
+
" clip_voxels_norm = nn.functional.normalize(clip_voxels.flatten(1), dim=-1)\n",
|
| 1045 |
+
" clip_target_norm = nn.functional.normalize(clip_target.flatten(1), dim=-1)\n",
|
| 1046 |
+
" \n",
|
| 1047 |
+
" loss_clip = utils.soft_clip_loss(\n",
|
| 1048 |
+
" clip_voxels_norm,\n",
|
| 1049 |
+
" clip_target_norm,\n",
|
| 1050 |
+
" temp=.006)\n",
|
| 1051 |
+
"\n",
|
| 1052 |
+
" test_loss_clip_total += loss_clip.item()\n",
|
| 1053 |
+
" loss += loss_clip\n",
|
| 1054 |
+
"\n",
|
| 1055 |
+
" # forward and backward top 1 accuracy \n",
|
| 1056 |
+
" labels = torch.arange(len(clip_voxels_norm)).to(clip_voxels_norm.device) \n",
|
| 1057 |
+
" test_fwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_voxels_norm, clip_target_norm), labels, k=1).item()\n",
|
| 1058 |
+
" test_bwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_target_norm, clip_voxels_norm), labels, k=1).item()\n",
|
| 1059 |
+
" \n",
|
| 1060 |
+
" utils.check_loss(loss) \n",
|
| 1061 |
+
" test_losses.append(loss.item())\n",
|
| 1062 |
+
"\n",
|
| 1063 |
+
" logs = {\"train/loss\": np.mean(losses[-(train_i+1):]),\n",
|
| 1064 |
+
" \"test/loss\": np.mean(test_losses[-(test_i+1):]),\n",
|
| 1065 |
+
" \"train/lr\": lrs[-1],\n",
|
| 1066 |
+
" \"train/num_steps\": len(losses),\n",
|
| 1067 |
+
" \"test/num_steps\": len(test_losses),\n",
|
| 1068 |
+
" \"train/fwd_pct_correct\": fwd_percent_correct / (train_i + 1),\n",
|
| 1069 |
+
" \"train/bwd_pct_correct\": bwd_percent_correct / (train_i + 1),\n",
|
| 1070 |
+
" \"test/test_fwd_pct_correct\": test_fwd_percent_correct / (test_i + 1),\n",
|
| 1071 |
+
" \"test/test_bwd_pct_correct\": test_bwd_percent_correct / (test_i + 1),\n",
|
| 1072 |
+
" \"train/loss_clip_total\": loss_clip_total / (train_i + 1),\n",
|
| 1073 |
+
" \"test/loss_clip_total\": test_loss_clip_total / (test_i + 1),\n",
|
| 1074 |
+
" }\n",
|
| 1075 |
+
"\n",
|
| 1076 |
+
" progress_bar.set_postfix(**logs)\n",
|
| 1077 |
+
" if wandb_log: wandb.log(logs)\n",
|
| 1078 |
+
" \n",
|
| 1079 |
+
" # Save model checkpoint\n",
|
| 1080 |
+
" if (ckpt_saving) and (epoch % ckpt_interval == 0):\n",
|
| 1081 |
+
" save_ckpt()\n",
|
| 1082 |
+
"\n",
|
| 1083 |
+
" # wait for other GPUs to catch up if needed\n",
|
| 1084 |
+
" accelerator.wait_for_everyone()\n",
|
| 1085 |
+
" torch.cuda.empty_cache()\n",
|
| 1086 |
+
" gc.collect()"
|
| 1087 |
+
]
|
| 1088 |
+
},
|
| 1089 |
+
{
|
| 1090 |
+
"cell_type": "code",
|
| 1091 |
+
"execution_count": null,
|
| 1092 |
+
"id": "6277d8b8-63ab-4741-942f-69bb0caff816",
|
| 1093 |
+
"metadata": {},
|
| 1094 |
+
"outputs": [],
|
| 1095 |
+
"source": [
|
| 1096 |
+
"# torch.save(image0,\"image0.pt\")\n",
|
| 1097 |
+
"# torch.save(encoder_out,\"encoder_out0.pt\")"
|
| 1098 |
+
]
|
| 1099 |
+
},
|
| 1100 |
+
{
|
| 1101 |
+
"cell_type": "code",
|
| 1102 |
+
"execution_count": null,
|
| 1103 |
+
"id": "a06050bc-86a2-4134-a661-ead4afe3e1e7",
|
| 1104 |
+
"metadata": {
|
| 1105 |
+
"tags": []
|
| 1106 |
+
},
|
| 1107 |
+
"outputs": [],
|
| 1108 |
+
"source": [
|
| 1109 |
+
"plt.figure(figsize=(8, 3))\n",
|
| 1110 |
+
"plt.plot(losses)\n",
|
| 1111 |
+
"plt.title(\"Training losses\")\n",
|
| 1112 |
+
"plt.show()\n",
|
| 1113 |
+
"\n",
|
| 1114 |
+
"plt.figure(figsize=(8, 3))\n",
|
| 1115 |
+
"plt.plot(test_losses)\n",
|
| 1116 |
+
"plt.title(\"Test losses\")\n",
|
| 1117 |
+
"plt.show()"
|
| 1118 |
+
]
|
| 1119 |
+
},
|
| 1120 |
+
{
|
| 1121 |
+
"cell_type": "code",
|
| 1122 |
+
"execution_count": null,
|
| 1123 |
+
"id": "d38371ee-6e16-4db4-a096-f3f9ebdd73cf",
|
| 1124 |
+
"metadata": {
|
| 1125 |
+
"tags": []
|
| 1126 |
+
},
|
| 1127 |
+
"outputs": [],
|
| 1128 |
+
"source": [
|
| 1129 |
+
"voxel_list[0].shape"
|
| 1130 |
+
]
|
| 1131 |
+
},
|
| 1132 |
+
{
|
| 1133 |
+
"cell_type": "code",
|
| 1134 |
+
"execution_count": null,
|
| 1135 |
+
"id": "0885cc21-c8b2-4b6f-950d-913d6317ea40",
|
| 1136 |
+
"metadata": {},
|
| 1137 |
+
"outputs": [],
|
| 1138 |
+
"source": []
|
| 1139 |
+
}
|
| 1140 |
+
],
|
| 1141 |
+
"metadata": {
|
| 1142 |
+
"kernelspec": {
|
| 1143 |
+
"display_name": "found",
|
| 1144 |
+
"language": "python",
|
| 1145 |
+
"name": "found"
|
| 1146 |
+
},
|
| 1147 |
+
"language_info": {
|
| 1148 |
+
"codemirror_mode": {
|
| 1149 |
+
"name": "ipython",
|
| 1150 |
+
"version": 3
|
| 1151 |
+
},
|
| 1152 |
+
"file_extension": ".py",
|
| 1153 |
+
"mimetype": "text/x-python",
|
| 1154 |
+
"name": "python",
|
| 1155 |
+
"nbconvert_exporter": "python",
|
| 1156 |
+
"pygments_lexer": "ipython3",
|
| 1157 |
+
"version": "3.10.13"
|
| 1158 |
+
}
|
| 1159 |
+
},
|
| 1160 |
+
"nbformat": 4,
|
| 1161 |
+
"nbformat_minor": 5
|
| 1162 |
+
}
|
fMRI-foundation-model-old/fMRI-MAE/downstream.py
ADDED
|
@@ -0,0 +1,731 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Import packages and setup gpu configuration.
|
| 2 |
+
# This code block shouldnt need to be adjusted!
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import sys
|
| 6 |
+
import json
|
| 7 |
+
import yaml
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import copy
|
| 11 |
+
import math
|
| 12 |
+
from einops import rearrange
|
| 13 |
+
from einops.layers.torch import Rearrange
|
| 14 |
+
import time
|
| 15 |
+
import random
|
| 16 |
+
import h5py
|
| 17 |
+
import webdataset as wds
|
| 18 |
+
import gc
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn as nn
|
| 22 |
+
import torch.nn.functional as F
|
| 23 |
+
from torchvision import transforms
|
| 24 |
+
import utils
|
| 25 |
+
from models import *
|
| 26 |
+
from mindeye_models import *
|
| 27 |
+
import nibabel as nib
|
| 28 |
+
from nilearn import plotting
|
| 29 |
+
from functools import partial
|
| 30 |
+
|
| 31 |
+
# tf32 data type is faster than standard float32
|
| 32 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 33 |
+
|
| 34 |
+
### Multi-GPU config ###
|
| 35 |
+
local_rank = os.getenv('LOCAL_RANK')
|
| 36 |
+
if local_rank is None:
|
| 37 |
+
local_rank = 0
|
| 38 |
+
else:
|
| 39 |
+
local_rank = int(local_rank)
|
| 40 |
+
print(f"LOCAL RANK={local_rank}")
|
| 41 |
+
|
| 42 |
+
num_devices = os.getenv('NUM_GPUS')
|
| 43 |
+
if num_devices is None:
|
| 44 |
+
num_devices = 1
|
| 45 |
+
else:
|
| 46 |
+
num_devices = int(num_devices)
|
| 47 |
+
print(f"NUM GPUS={num_devices}")
|
| 48 |
+
distributed = True if num_devices>1 else False
|
| 49 |
+
|
| 50 |
+
global_rank = os.getenv('RANK')
|
| 51 |
+
if global_rank is None:
|
| 52 |
+
global_rank = 0
|
| 53 |
+
else:
|
| 54 |
+
global_rank = int(global_rank)
|
| 55 |
+
print(f"GLOBAL RANK={global_rank}")
|
| 56 |
+
|
| 57 |
+
from tqdm import tqdm
|
| 58 |
+
|
| 59 |
+
# Load parameters from yaml config
|
| 60 |
+
config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)
|
| 61 |
+
|
| 62 |
+
# create global variables from the config
|
| 63 |
+
for attribute_name in config.keys():
|
| 64 |
+
globals()[attribute_name] = config[f'{attribute_name}']
|
| 65 |
+
|
| 66 |
+
# Load MindEye parameters from yaml config (will override any params with same name)
|
| 67 |
+
mindeye_config = yaml.load(open('mindeye_config.yaml', 'r'), Loader=yaml.FullLoader)
|
| 68 |
+
|
| 69 |
+
# create global variables from the config
|
| 70 |
+
for attribute_name in mindeye_config.keys():
|
| 71 |
+
globals()[attribute_name] = mindeye_config[f'{attribute_name}']
|
| 72 |
+
|
| 73 |
+
data_type = torch.float32 # change depending on your mixed_precision
|
| 74 |
+
|
| 75 |
+
batch_size = global_batch_size // num_devices
|
| 76 |
+
print("batch_size", batch_size)
|
| 77 |
+
|
| 78 |
+
# First use "accelerate config" in terminal and setup using deepspeed stage 2 with CPU offloading
|
| 79 |
+
from accelerate import Accelerator
|
| 80 |
+
accelerator = Accelerator(split_batches=False, mixed_precision="fp16")
|
| 81 |
+
|
| 82 |
+
from accelerate.state import AcceleratorState
|
| 83 |
+
try:
|
| 84 |
+
AcceleratorState().deepspeed_plugin.deepspeed_config['train_micro_batch_size_per_gpu'] = batch_size
|
| 85 |
+
print("deepspeed reconfigured, train_micro_batch_size_per_gpu = ", batch_size)
|
| 86 |
+
except:
|
| 87 |
+
print("skipping deepspeed reconfiguration...")
|
| 88 |
+
|
| 89 |
+
print("PID of this process =",os.getpid())
|
| 90 |
+
device = accelerator.device
|
| 91 |
+
print("device:",device)
|
| 92 |
+
world_size = accelerator.state.num_processes
|
| 93 |
+
num_workers = num_devices
|
| 94 |
+
print(accelerator.state)
|
| 95 |
+
|
| 96 |
+
print("distributed =",distributed, "num_devices =", num_devices, "local rank =", local_rank, "world size =", world_size, "data_type =", data_type)
|
| 97 |
+
print = accelerator.print # only print if local_rank=0
|
| 98 |
+
|
| 99 |
+
print("mae config\n\n",config)
|
| 100 |
+
print("mindeye_config\n",mindeye_config)
|
| 101 |
+
|
| 102 |
+
# if utils.is_interactive():
|
| 103 |
+
# ckpt_saving = False
|
| 104 |
+
# wandb_log = False
|
| 105 |
+
|
| 106 |
+
# seed all random functions
|
| 107 |
+
utils.seed_everything(seed)
|
| 108 |
+
|
| 109 |
+
mae_ckpt_pth = os.path.abspath(f'../ckpts/{mae_model_name}/last.pth')
|
| 110 |
+
print("mae_ckpt_pth", mae_ckpt_pth)
|
| 111 |
+
|
| 112 |
+
outdir = os.path.abspath(f'../ckpts/{model_name}')
|
| 113 |
+
os.makedirs(outdir,exist_ok=True)
|
| 114 |
+
print("outdir", outdir)
|
| 115 |
+
|
| 116 |
+
if type(patch_size) == int:
|
| 117 |
+
patch_size = [patch_size,patch_size,patch_size]
|
| 118 |
+
patch_depth = patch_size[0]
|
| 119 |
+
patch_height = patch_size[1]
|
| 120 |
+
patch_width = patch_size[2]
|
| 121 |
+
|
| 122 |
+
num_patches = int(
|
| 123 |
+
(img_size[0] / patch_depth)
|
| 124 |
+
* (img_size[1] / patch_height)
|
| 125 |
+
* (img_size[2] / patch_width)
|
| 126 |
+
* num_frames
|
| 127 |
+
)
|
| 128 |
+
print("num_patches", num_patches)
|
| 129 |
+
|
| 130 |
+
vit_size = {
|
| 131 |
+
"encoder": encoder_model,
|
| 132 |
+
"decoder": decoder_model
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
model = get_vit(
|
| 136 |
+
size=vit_size,
|
| 137 |
+
image_size=img_size, # depth, height, width
|
| 138 |
+
image_patch_size=(patch_depth,patch_height,patch_width), # depth, height, width patch size
|
| 139 |
+
frames=num_frames,
|
| 140 |
+
frame_patch_size=frame_patch_size,
|
| 141 |
+
channels=1,
|
| 142 |
+
use_rope_emb=use_rope_emb,
|
| 143 |
+
use_cls_token=use_cls_token,
|
| 144 |
+
use_decoder_same_emb_dim=use_decoder_same_emb_dim,
|
| 145 |
+
decoder_depth=decoder_depth
|
| 146 |
+
)
|
| 147 |
+
utils.count_params(model)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def save_ckpt(tag):
|
| 151 |
+
ckpt_path = outdir+f'/{tag}.pth'
|
| 152 |
+
if accelerator.is_main_process:
|
| 153 |
+
unwrapped_model = accelerator.unwrap_model(model)
|
| 154 |
+
if tag == "last" and os.path.exists(ckpt_path):
|
| 155 |
+
shutil.copyfile(ckpt_path, os.path.join(outdir, f'{tag}_old.pth'))
|
| 156 |
+
torch.save({
|
| 157 |
+
'epoch': epoch,
|
| 158 |
+
'model_state_dict': unwrapped_model.state_dict(),
|
| 159 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 160 |
+
'lr_scheduler': lr_scheduler.state_dict(),
|
| 161 |
+
'train_losses': losses,
|
| 162 |
+
'test_losses': test_losses,
|
| 163 |
+
'lrs': lrs,
|
| 164 |
+
}, ckpt_path)
|
| 165 |
+
if tag == "last" and os.path.exists(os.path.join(outdir, f'{tag}_old.pth')):
|
| 166 |
+
os.remove(os.path.join(outdir, f'{tag}_old.pth'))
|
| 167 |
+
print(f"\n---saved {outdir}/{tag} ckpt!---\n")
|
| 168 |
+
|
| 169 |
+
def load_ckpt(tag,load_lr=True,load_optimizer=True,load_epoch=True,strict=True,outdir=outdir,multisubj_loading=False):
|
| 170 |
+
print(f"\n---loading {outdir}/{tag}.pth ckpt---\n")
|
| 171 |
+
checkpoint = torch.load(outdir+'/last.pth', map_location='cpu')
|
| 172 |
+
state_dict = checkpoint['model_state_dict']
|
| 173 |
+
if multisubj_loading: # remove incompatible ridge layer that will otherwise error
|
| 174 |
+
state_dict.pop('ridge.linears.0.weight',None)
|
| 175 |
+
model.load_state_dict(state_dict, strict=strict)
|
| 176 |
+
if load_epoch:
|
| 177 |
+
globals()["epoch"] = checkpoint['epoch']
|
| 178 |
+
print("Epoch",epoch)
|
| 179 |
+
if load_optimizer:
|
| 180 |
+
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
|
| 181 |
+
if load_lr:
|
| 182 |
+
lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
|
| 183 |
+
del checkpoint
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
checkpoint = torch.load(mae_ckpt_pth, map_location=device)
|
| 187 |
+
try:
|
| 188 |
+
model.load_state_dict(checkpoint['model_state_dict'], strict=False)
|
| 189 |
+
except:
|
| 190 |
+
pass
|
| 191 |
+
|
| 192 |
+
# set foundation model to evaluation
|
| 193 |
+
model.eval()
|
| 194 |
+
model.requires_grad_(False)
|
| 195 |
+
model.to(device)
|
| 196 |
+
pass
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
nsddata_raw_stimuli = pd.read_csv(f"{nsd_raw_path}/nsddata_rawdata.csv")
|
| 200 |
+
TR_delay = 3 # to account for bold hrf
|
| 201 |
+
train_TRs = np.round(nsddata_raw_stimuli[nsddata_raw_stimuli['shared1000'] == False]['global_TR_onsets'].values + TR_delay).astype(np.int32)
|
| 202 |
+
test_TRs = np.round(nsddata_raw_stimuli[nsddata_raw_stimuli['shared1000'] == True]['global_TR_onsets'].values + TR_delay).astype(np.int32)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
# Load 73k NSD images
|
| 206 |
+
f = h5py.File(f'{nsd_image_path}/coco_images_224_float16.hdf5', 'r')
|
| 207 |
+
images = f['images'][:]
|
| 208 |
+
images = torch.Tensor(images).to("cpu").to(data_type)
|
| 209 |
+
print("Loaded all 73k possible NSD images!", images.shape)
|
| 210 |
+
|
| 211 |
+
# Load MindEye hdf5
|
| 212 |
+
f = h5py.File(f'{nsd_raw_path}/subj01_mnidata.h5', 'r') #subj01_rawdata_old.h5
|
| 213 |
+
mindeye_global_trs = f['global_trs'][:]
|
| 214 |
+
mindeye_funcs = f['funcs']
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# clip_img_embedder = FrozenOpenCLIPImageEmbedder(
|
| 218 |
+
# arch="ViT-bigG-14",
|
| 219 |
+
# version="laion2b_s39b_b160k",
|
| 220 |
+
# output_tokens=True,
|
| 221 |
+
# only_tokens=True,
|
| 222 |
+
# )
|
| 223 |
+
# clip_seq_dim = 256
|
| 224 |
+
# clip_emb_dim = 1664
|
| 225 |
+
# clip_img_embedder.to(device)
|
| 226 |
+
|
| 227 |
+
clip_img_embedder = FrozenOpenCLIPImageEmbedder(
|
| 228 |
+
arch="ViT-bigG-14",
|
| 229 |
+
version="laion2b_s39b_b160k",
|
| 230 |
+
output_tokens=False,
|
| 231 |
+
only_tokens=False,
|
| 232 |
+
init_device=device,
|
| 233 |
+
device=device,
|
| 234 |
+
cache_dir="/weka/proj-fmri/shared/cache"
|
| 235 |
+
)
|
| 236 |
+
clip_seq_dim = 1
|
| 237 |
+
clip_emb_dim = 1280
|
| 238 |
+
clip_img_embedder.to(device)
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
subj = s = 1
|
| 242 |
+
subj_list = [subj]
|
| 243 |
+
|
| 244 |
+
# if multi_subject:
|
| 245 |
+
# nsessions_allsubj=np.array([40, 40, 32, 30, 40, 32, 40, 30])
|
| 246 |
+
# num_samples_per_epoch = (750*40) // num_devices
|
| 247 |
+
# else:
|
| 248 |
+
# num_samples_per_epoch = (750*num_sessions) // num_devices
|
| 249 |
+
|
| 250 |
+
# num_samples_per_epoch = 1024 #(750*num_sessions) // num_devices
|
| 251 |
+
|
| 252 |
+
print("dividing batch size by subj_list, which will then be concatenated across subj during training...")
|
| 253 |
+
batch_size = batch_size // len(subj_list)
|
| 254 |
+
num_iterations_per_epoch = num_samples_per_epoch // (batch_size*len(subj_list))
|
| 255 |
+
print("batch_size =", batch_size, "num_iterations_per_epoch =",num_iterations_per_epoch, "num_samples_per_epoch =",num_samples_per_epoch)
|
| 256 |
+
|
| 257 |
+
train_data = {}
|
| 258 |
+
train_dl = {}
|
| 259 |
+
|
| 260 |
+
print(f"Training with {num_sessions} sessions")
|
| 261 |
+
train_url = f"{nsd_wds_path}/subj0{s}/train/" + "{0.." + f"{num_sessions-1}" + "}.tar"
|
| 262 |
+
print(train_url)
|
| 263 |
+
|
| 264 |
+
train_data[f'subj0{s}'] = wds.WebDataset(train_url,resampled=True,nodesplitter=utils.my_split_by_node)\
|
| 265 |
+
.shuffle(750, initial=1500, rng=random.Random(42))\
|
| 266 |
+
.decode("torch")\
|
| 267 |
+
.rename(behav="behav.npy", past_behav="past_behav.npy", future_behav="future_behav.npy", olds_behav="olds_behav.npy")\
|
| 268 |
+
.to_tuple(*["behav", "past_behav", "future_behav", "olds_behav"])
|
| 269 |
+
# train_dl[f'subj0{s}'] = torch.utils.data.DataLoader(train_data[f'subj0{s}'], batch_size=batch_size, shuffle=False, drop_last=True, pin_memory=True)
|
| 270 |
+
train_dl[f'subj0{s}'] = wds.WebLoader(
|
| 271 |
+
train_data[f'subj0{s}'].batched(batch_size),
|
| 272 |
+
pin_memory=True,
|
| 273 |
+
shuffle=False,
|
| 274 |
+
batch_size=None,
|
| 275 |
+
num_workers=num_workers,
|
| 276 |
+
persistent_workers=num_workers>0,
|
| 277 |
+
).with_epoch(num_iterations_per_epoch)
|
| 278 |
+
|
| 279 |
+
print("Loaded all subj train dls and betas!\n")
|
| 280 |
+
if subj==3:
|
| 281 |
+
num_test=2371
|
| 282 |
+
elif subj==4:
|
| 283 |
+
num_test=2188
|
| 284 |
+
elif subj==6:
|
| 285 |
+
num_test=2371
|
| 286 |
+
elif subj==8:
|
| 287 |
+
num_test=2188
|
| 288 |
+
else:
|
| 289 |
+
num_test=300 # 3000
|
| 290 |
+
test_url = f"{nsd_wds_path}/subj0{subj}/new_test/" + "0.tar"
|
| 291 |
+
print(test_url)
|
| 292 |
+
test_data = wds.WebDataset(test_url,resampled=True,nodesplitter=utils.my_split_by_node)\
|
| 293 |
+
.decode("torch")\
|
| 294 |
+
.rename(behav="behav.npy", past_behav="past_behav.npy", future_behav="future_behav.npy", olds_behav="olds_behav.npy")\
|
| 295 |
+
.to_tuple(*["behav", "past_behav", "future_behav", "olds_behav"])
|
| 296 |
+
# test_dl = torch.utils.data.DataLoader(test_data, batch_size=num_test, shuffle=False, drop_last=True, pin_memory=True)
|
| 297 |
+
test_dl = wds.WebLoader(
|
| 298 |
+
test_data.batched(num_test),
|
| 299 |
+
pin_memory=True,
|
| 300 |
+
shuffle=False,
|
| 301 |
+
batch_size=None,
|
| 302 |
+
num_workers=num_workers,
|
| 303 |
+
persistent_workers=num_workers>0,
|
| 304 |
+
).with_epoch(10)
|
| 305 |
+
print(f"Loaded test dl for subj{subj}!\n")
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
class MindEyeModule(nn.Module):
|
| 309 |
+
def __init__(self):
|
| 310 |
+
super(MindEyeModule, self).__init__()
|
| 311 |
+
def forward(self, x):
|
| 312 |
+
return x
|
| 313 |
+
|
| 314 |
+
# class RidgeRegression(torch.nn.Module):
|
| 315 |
+
# # make sure to add weight_decay when initializing optimizer
|
| 316 |
+
# def __init__(self, input_sizes, out_features, seq_len=1):
|
| 317 |
+
# super(RidgeRegression, self).__init__()
|
| 318 |
+
# self.seq_len = seq_len
|
| 319 |
+
# self.out_features = out_features
|
| 320 |
+
# self.linears = torch.nn.ModuleList([
|
| 321 |
+
# torch.nn.Linear(input_size, out_features) for input_size in input_sizes
|
| 322 |
+
# ])
|
| 323 |
+
# def forward(self, x, subj_idx):
|
| 324 |
+
# out = torch.cat([self.linears[subj_idx](x[:,seq]).unsqueeze(1) for seq in range(self.seq_len)], dim=1)
|
| 325 |
+
# return out
|
| 326 |
+
|
| 327 |
+
class MLP(torch.nn.Module):
|
| 328 |
+
def __init__(self, input_sizes, out_features, seq_len=0):
|
| 329 |
+
super(MLP, self).__init__()
|
| 330 |
+
self.input_sizes = input_sizes[0]
|
| 331 |
+
self.out_features = out_features
|
| 332 |
+
self.mlp = nn.Sequential(
|
| 333 |
+
nn.LayerNorm(self.input_sizes),
|
| 334 |
+
nn.GELU(),
|
| 335 |
+
nn.Linear(self.input_sizes, hidden_dim//2),
|
| 336 |
+
nn.LayerNorm(hidden_dim//2),
|
| 337 |
+
nn.GELU(),
|
| 338 |
+
nn.Dropout(drop),
|
| 339 |
+
nn.Linear(hidden_dim//2, hidden_dim),
|
| 340 |
+
nn.LayerNorm(hidden_dim),
|
| 341 |
+
nn.GELU(),
|
| 342 |
+
nn.Dropout(drop),
|
| 343 |
+
nn.Linear(hidden_dim, out_features)
|
| 344 |
+
)
|
| 345 |
+
def forward(self, x, z=None):
|
| 346 |
+
out = self.mlp(x[:,0])
|
| 347 |
+
return out
|
| 348 |
+
|
| 349 |
+
class ReversibleBrainNetwork(nn.Module):
|
| 350 |
+
def __init__(self, out_dim=768, in_dim=15724, h=4096, n_blocks=4, norm_type='bn', act_first=True,
|
| 351 |
+
encoder_tokens=257, reverse=True, **kwargs):
|
| 352 |
+
super().__init__()
|
| 353 |
+
norm_func = partial(nn.BatchNorm1d, num_features=h) if norm_type == 'bn' else partial(nn.LayerNorm, normalized_shape=h)
|
| 354 |
+
act_fn = partial(nn.ReLU, inplace=True) if norm_type == 'bn' else nn.GELU
|
| 355 |
+
act_and_norm = (act_fn, norm_func) if act_first else (norm_func, act_fn)
|
| 356 |
+
# self.temp = nn.Parameter(torch.tensor(.006))
|
| 357 |
+
start_dim, end_dim = (out_dim*encoder_tokens, in_dim) if reverse else (in_dim, out_dim*encoder_tokens)
|
| 358 |
+
self.lin0 = nn.Sequential(
|
| 359 |
+
nn.Linear(start_dim, h),
|
| 360 |
+
*[item() for item in act_and_norm],
|
| 361 |
+
nn.Dropout(0.5),
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
self.mlp = nn.ModuleList([
|
| 365 |
+
nn.Sequential(
|
| 366 |
+
nn.Linear(h, h),
|
| 367 |
+
*[item() for item in act_and_norm],
|
| 368 |
+
nn.Dropout(drop)
|
| 369 |
+
) for _ in range(n_blocks)
|
| 370 |
+
])
|
| 371 |
+
|
| 372 |
+
self.lin1 = nn.Linear(h, end_dim, bias=True)
|
| 373 |
+
self.n_blocks = n_blocks
|
| 374 |
+
|
| 375 |
+
def forward(self, x, *args, **kwargs):
|
| 376 |
+
x = self.lin0(x.flatten(1)) # bs, h
|
| 377 |
+
residual = x
|
| 378 |
+
for res_block in range(self.n_blocks):
|
| 379 |
+
x = self.mlp[res_block](x)
|
| 380 |
+
x += residual
|
| 381 |
+
residual = x
|
| 382 |
+
x = x.reshape(len(x), -1)
|
| 383 |
+
x = self.lin1(x)
|
| 384 |
+
return x
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
mindeye = MindEyeModule()
|
| 388 |
+
# mindeye.ridge = MLP(np.array([in_dim]), out_features=clip_emb_dim*clip_seq_dim)
|
| 389 |
+
mindeye.ridge = ReversibleBrainNetwork(in_dim=in_dim, h=hidden_dim, out_dim=clip_emb_dim,
|
| 390 |
+
encoder_tokens=1, norm_type='ln', act_first=False, reverse=False)
|
| 391 |
+
|
| 392 |
+
# mindeye.backbone = BrainNetwork(h=hidden_dim, in_dim=hidden_dim, n_blocks=4, drop=drop,
|
| 393 |
+
# clip_size=clip_emb_dim, out_dim=clip_emb_dim*clip_seq_dim, clip_scale=1)
|
| 394 |
+
utils.count_params(mindeye.ridge)
|
| 395 |
+
# utils.count_params(mindeye.backbone)
|
| 396 |
+
utils.count_params(mindeye)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
no_decay = ["bias", "LayerNorm.bias", "LayerNorm.weight"]
|
| 400 |
+
opt_grouped_parameters = [
|
| 401 |
+
{'params': [p for n, p in mindeye.ridge.named_parameters()], 'weight_decay': 1e-2},
|
| 402 |
+
# {'params': [p for n, p in mindeye.backbone.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 1e-2},
|
| 403 |
+
# {'params': [p for n, p in mindeye.backbone.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},
|
| 404 |
+
]
|
| 405 |
+
|
| 406 |
+
optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=max_lr)
|
| 407 |
+
|
| 408 |
+
total_steps = iters_scale_factor * num_epochs * num_iterations_per_epoch
|
| 409 |
+
print("total_steps", total_steps)
|
| 410 |
+
pct_start = 2/num_epochs if num_epochs>1 else 1.
|
| 411 |
+
lr_scheduler = torch.optim.lr_scheduler.OneCycleLR(
|
| 412 |
+
optimizer,
|
| 413 |
+
max_lr=max_lr,
|
| 414 |
+
total_steps=total_steps,
|
| 415 |
+
pct_start=0.1
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
print("\nDone with model preparations!")
|
| 419 |
+
num_params = utils.count_params(mindeye)
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
if accelerator.is_main_process and wandb_log: # only use main process for wandb logging
|
| 423 |
+
import wandb
|
| 424 |
+
wandb_project = 'found_downstream'
|
| 425 |
+
print(f"wandb {wandb_project} run {model_name}")
|
| 426 |
+
# need to configure wandb beforehand in terminal with "wandb init"!
|
| 427 |
+
wandb_config = {
|
| 428 |
+
"model_name": model_name,
|
| 429 |
+
"mae_model_name": mae_model_name,
|
| 430 |
+
"global_batch_size": global_batch_size,
|
| 431 |
+
"batch_size": batch_size,
|
| 432 |
+
"num_epochs": num_epochs,
|
| 433 |
+
"num_sessions": num_sessions,
|
| 434 |
+
"num_samples_per_epoch": num_samples_per_epoch,
|
| 435 |
+
"in_dim": in_dim,
|
| 436 |
+
"hidden_dim": hidden_dim,
|
| 437 |
+
"mixup_pct": mixup_pct,
|
| 438 |
+
"num_params": num_params,
|
| 439 |
+
"max_lr": max_lr,
|
| 440 |
+
"ckpt_interval": ckpt_interval,
|
| 441 |
+
"ckpt_saving": ckpt_saving,
|
| 442 |
+
"seed": seed,
|
| 443 |
+
"distributed": distributed,
|
| 444 |
+
"num_devices": num_devices,
|
| 445 |
+
"world_size": world_size,
|
| 446 |
+
"train_url": train_url,
|
| 447 |
+
"test_url": test_url,
|
| 448 |
+
}
|
| 449 |
+
print("wandb_config:\n",wandb_config)
|
| 450 |
+
print("wandb_id:",model_name)
|
| 451 |
+
wandb.init(
|
| 452 |
+
id=model_name,
|
| 453 |
+
project=wandb_project,
|
| 454 |
+
name=model_name,
|
| 455 |
+
config=wandb_config,
|
| 456 |
+
resume="allow",
|
| 457 |
+
)
|
| 458 |
+
else:
|
| 459 |
+
wandb_log = False
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
epoch = 0
|
| 463 |
+
losses, test_losses, lrs = [], [], []
|
| 464 |
+
best_test_loss = 1e9
|
| 465 |
+
torch.cuda.empty_cache()
|
| 466 |
+
|
| 467 |
+
# if masking_strategy=="MNI":
|
| 468 |
+
from einops.layers.torch import Rearrange
|
| 469 |
+
|
| 470 |
+
MNI_brain = nib.load("/weka/proj-fmri/paulscotti/fMRI-foundation-model/dataset_creation/afni_conversion/tpl-MNI152NLin2009cAsym_res-02_T1w_brain.nii.gz").get_fdata()
|
| 471 |
+
brain_pos_voxels = utils.crop_or_pad(torch.from_numpy(MNI_brain[6:94,8:112,10:82]), img_size)
|
| 472 |
+
# brain_pos_voxels = MNI_brain[10:90,12:108,14:78]
|
| 473 |
+
|
| 474 |
+
# brain_pos_voxels = brain_pos_voxels[:,30:31,:]
|
| 475 |
+
|
| 476 |
+
brain_pos_pats = Rearrange(
|
| 477 |
+
"b c (f pf) (d pd) (h ph) (w pw) -> b f d h w (pd ph pw pf c)",
|
| 478 |
+
pd=patch_depth,
|
| 479 |
+
ph=patch_height,
|
| 480 |
+
pw=patch_width,
|
| 481 |
+
pf=1,
|
| 482 |
+
)(brain_pos_voxels[None,None,None])
|
| 483 |
+
|
| 484 |
+
brain_pos_pats_vit = rearrange(brain_pos_pats, "b ... d -> b (...) d").mean(-1)[0]
|
| 485 |
+
|
| 486 |
+
tube_mask = torch.zeros(num_patches // num_frames).to(torch.bool)
|
| 487 |
+
batch_positive_approx = (brain_pos_pats_vit > 0)
|
| 488 |
+
mask_idx_candidates = torch.where(batch_positive_approx)[0]
|
| 489 |
+
mask_idx_candidates = mask_idx_candidates[torch.randperm(len(mask_idx_candidates))]
|
| 490 |
+
tube_idx = mask_idx_candidates[:int(num_patches / num_frames * (1 - tube_end_masking_ratio))]
|
| 491 |
+
tube_mask[tube_idx] = True
|
| 492 |
+
tube_mask = tube_mask.tile(num_frames//frame_patch_size)
|
| 493 |
+
|
| 494 |
+
# load multisubject stage1 ckpt if set
|
| 495 |
+
if multisubject_ckpt!="None" and not resume_from_ckpt:
|
| 496 |
+
load_ckpt("last",outdir=multisubject_ckpt,load_lr=False,load_optimizer=False,load_epoch=False,strict=False,multisubj_loading=True)
|
| 497 |
+
|
| 498 |
+
# load saved ckpt model weights into current model
|
| 499 |
+
if resume_from_ckpt:
|
| 500 |
+
load_ckpt("last",load_lr=True,load_optimizer=True,load_epoch=True)
|
| 501 |
+
elif wandb_log:
|
| 502 |
+
if wandb.run.resumed:
|
| 503 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')) or os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 504 |
+
if os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 505 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')):
|
| 506 |
+
# this is corrupted
|
| 507 |
+
os.remove(os.path.join(outdir, f'last.pth'))
|
| 508 |
+
# set last_old as last
|
| 509 |
+
shutil.move(os.path.join(outdir, f'last_old.pth'), os.path.join(outdir, f'last.pth'))
|
| 510 |
+
|
| 511 |
+
# ckpt_path = os.path.join(outdir, 'last.pth')
|
| 512 |
+
# resume_from_ckpt = True
|
| 513 |
+
load_ckpt("last",load_lr=True,load_optimizer=True,load_epoch=True)
|
| 514 |
+
|
| 515 |
+
train_dls = [train_dl[f'subj0{s}'] for s in subj_list]
|
| 516 |
+
|
| 517 |
+
mindeye, optimizer, *train_dls, lr_scheduler = accelerator.prepare(mindeye, optimizer, *train_dls, lr_scheduler)
|
| 518 |
+
# leaving out test_dl since we will only have local_rank 0 device do evals
|
| 519 |
+
|
| 520 |
+
print(f"{model_name} starting with epoch {epoch} / {num_epochs}")
|
| 521 |
+
progress_bar = tqdm(range(epoch, num_epochs), disable=not accelerator.is_main_process)
|
| 522 |
+
mse = nn.MSELoss()
|
| 523 |
+
soft_loss_temps = utils.cosine_anneal(0.004, 0.0075, num_epochs - int(mixup_pct * num_epochs))
|
| 524 |
+
|
| 525 |
+
bn = nn.BatchNorm1d(512,affine=False).to(device)
|
| 526 |
+
|
| 527 |
+
test_image=None
|
| 528 |
+
num_test_eval=batch_size # should instead be average same-image 300 to mimic MindEye2 retrieval evaluation
|
| 529 |
+
|
| 530 |
+
for epoch in progress_bar:
|
| 531 |
+
mindeye.train()
|
| 532 |
+
|
| 533 |
+
fwd_percent_correct = 0.
|
| 534 |
+
bwd_percent_correct = 0.
|
| 535 |
+
test_fwd_percent_correct = 0.
|
| 536 |
+
test_bwd_percent_correct = 0.
|
| 537 |
+
loss_clip_total = 0.
|
| 538 |
+
test_loss_clip_total = 0.
|
| 539 |
+
|
| 540 |
+
# pre-load all batches for this epoch (it's MUCH faster to pre-load in bulk than to separate loading per batch)
|
| 541 |
+
voxel_iters = {} # empty dict because diff subjects have differing # of voxels
|
| 542 |
+
image_iters = torch.zeros(num_iterations_per_epoch, batch_size*len(subj_list), 3, 224, 224).float()
|
| 543 |
+
annot_iters = {}
|
| 544 |
+
perm_iters, betas_iters, select_iters = {}, {}, {}
|
| 545 |
+
for s, train_dl in enumerate(train_dls):
|
| 546 |
+
with torch.cuda.amp.autocast(dtype=data_type):
|
| 547 |
+
for iter, (behav0, past_behav0, future_behav0, old_behav0) in enumerate(tqdm(train_dl,total=num_iterations_per_epoch)):
|
| 548 |
+
image0 = images[behav0[:,0,0].cpu().long()].float()
|
| 549 |
+
image_iters[iter,s*batch_size:s*batch_size+batch_size] = image0
|
| 550 |
+
|
| 551 |
+
# if images are not fully preloaded, then can do this inefficient but more memory friendly approach
|
| 552 |
+
# for ib,b in enumerate(behav0[:,0,0].cpu().long()):
|
| 553 |
+
# if ib==0:
|
| 554 |
+
# image0 = torch.Tensor(images[[b]])
|
| 555 |
+
# else:
|
| 556 |
+
# image0 = torch.vstack((image0, torch.Tensor(images[[b]])))
|
| 557 |
+
# image_iters[iter,s*batch_size:s*batch_size+batch_size] = image0
|
| 558 |
+
|
| 559 |
+
# get the corresponding raw voxel time series
|
| 560 |
+
for ib,b in enumerate(behav0[:,0,5].cpu().long().numpy()):
|
| 561 |
+
tr = (nsddata_raw_stimuli[nsddata_raw_stimuli['global_trial'].isin([b.item()])]['global_TR_onsets'].values + TR_delay).astype(np.int32).item()
|
| 562 |
+
if ib==0:
|
| 563 |
+
voxels_raw = mindeye_funcs[tr-2:tr+2][None][None]
|
| 564 |
+
else:
|
| 565 |
+
voxels_raw = np.vstack((voxels_raw, mindeye_funcs[tr-2:tr+2][None][None]))
|
| 566 |
+
voxels_raw = torch.Tensor(voxels_raw).clamp(0,1).to(device)
|
| 567 |
+
voxels_raw = utils.crop_or_pad(voxels_raw, img_size)
|
| 568 |
+
|
| 569 |
+
## Process it through pretrained MAE ##
|
| 570 |
+
encoder_out = model(voxels_raw, encoder_mask=tube_mask)
|
| 571 |
+
# encoder_out = bn(encoder_out)
|
| 572 |
+
|
| 573 |
+
voxel0 = encoder_out.flatten(1).unsqueeze(1).cpu()
|
| 574 |
+
# voxel0 = nn.functional.normalize(voxel0,dim=-1).cpu()
|
| 575 |
+
|
| 576 |
+
assert len(voxel0) == batch_size
|
| 577 |
+
|
| 578 |
+
if epoch < int(mixup_pct * num_epochs):
|
| 579 |
+
voxel0, perm, betas, select = utils.mixco(voxel0)
|
| 580 |
+
perm_iters[f"subj0{subj_list[s]}_iter{iter}"] = perm
|
| 581 |
+
betas_iters[f"subj0{subj_list[s]}_iter{iter}"] = betas
|
| 582 |
+
select_iters[f"subj0{subj_list[s]}_iter{iter}"] = select
|
| 583 |
+
|
| 584 |
+
voxel_iters[f"subj0{subj_list[s]}_iter{iter}"] = voxel0
|
| 585 |
+
|
| 586 |
+
if iter >= num_iterations_per_epoch:
|
| 587 |
+
break
|
| 588 |
+
|
| 589 |
+
# you now have voxel_iters and image_iters with num_iterations_per_epoch batches each
|
| 590 |
+
for train_i in range(iters_scale_factor * num_iterations_per_epoch):
|
| 591 |
+
with torch.cuda.amp.autocast(dtype=data_type):
|
| 592 |
+
optimizer.zero_grad()
|
| 593 |
+
loss=0.
|
| 594 |
+
|
| 595 |
+
actual_idx = train_i % num_iterations_per_epoch
|
| 596 |
+
|
| 597 |
+
voxel_list = [voxel_iters[f"subj0{s}_iter{actual_idx}"].detach().to(device) for s in subj_list]
|
| 598 |
+
image = image_iters[actual_idx].detach()
|
| 599 |
+
image = image.to(device)
|
| 600 |
+
|
| 601 |
+
clip_target = clip_img_embedder(image)
|
| 602 |
+
assert not torch.any(torch.isnan(clip_target))
|
| 603 |
+
|
| 604 |
+
if epoch < int(mixup_pct * num_epochs):
|
| 605 |
+
perm_list = [perm_iters[f"subj0{s}_iter{actual_idx}"].detach().to(device) for s in subj_list]
|
| 606 |
+
perm = torch.cat(perm_list, dim=0)
|
| 607 |
+
betas_list = [betas_iters[f"subj0{s}_iter{actual_idx}"].detach().to(device) for s in subj_list]
|
| 608 |
+
betas = torch.cat(betas_list, dim=0)
|
| 609 |
+
select_list = [select_iters[f"subj0{s}_iter{actual_idx}"].detach().to(device) for s in subj_list]
|
| 610 |
+
select = torch.cat(select_list, dim=0)
|
| 611 |
+
|
| 612 |
+
voxel_ridge_list = [mindeye.ridge(voxel_list[si],si) for si,s in enumerate(subj_list)]
|
| 613 |
+
clip_voxels = torch.cat(voxel_ridge_list, dim=0)
|
| 614 |
+
|
| 615 |
+
# backbone, clip_voxels = mindeye.backbone(voxel_ridge)
|
| 616 |
+
|
| 617 |
+
clip_voxels_norm = nn.functional.normalize(clip_voxels.flatten(1), dim=-1)
|
| 618 |
+
clip_target_norm = nn.functional.normalize(clip_target.flatten(1), dim=-1)
|
| 619 |
+
|
| 620 |
+
if epoch < int(mixup_pct * num_epochs):
|
| 621 |
+
loss_clip = utils.mixco_nce(
|
| 622 |
+
clip_voxels_norm,
|
| 623 |
+
clip_target_norm,
|
| 624 |
+
temp=.006,
|
| 625 |
+
perm=perm, betas=betas, select=select)
|
| 626 |
+
else:
|
| 627 |
+
epoch_temp = soft_loss_temps[epoch-int(mixup_pct*num_epochs)]
|
| 628 |
+
loss_clip = utils.soft_clip_loss(
|
| 629 |
+
clip_voxels_norm,
|
| 630 |
+
clip_target_norm,
|
| 631 |
+
temp=epoch_temp)
|
| 632 |
+
|
| 633 |
+
loss_clip_total += loss_clip.item()
|
| 634 |
+
loss += loss_clip
|
| 635 |
+
|
| 636 |
+
# forward and backward top 1 accuracy
|
| 637 |
+
labels = torch.arange(len(clip_voxels_norm)).to(clip_voxels_norm.device)
|
| 638 |
+
fwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_voxels_norm, clip_target_norm), labels, k=1).item()
|
| 639 |
+
bwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_target_norm, clip_voxels_norm), labels, k=1).item()
|
| 640 |
+
|
| 641 |
+
utils.check_loss(loss)
|
| 642 |
+
accelerator.backward(loss)
|
| 643 |
+
optimizer.step()
|
| 644 |
+
|
| 645 |
+
losses.append(loss.item())
|
| 646 |
+
lrs.append(optimizer.param_groups[0]['lr'])
|
| 647 |
+
|
| 648 |
+
lr_scheduler.step()
|
| 649 |
+
|
| 650 |
+
mindeye.eval()
|
| 651 |
+
if local_rank==0:
|
| 652 |
+
with torch.no_grad(), torch.cuda.amp.autocast(dtype=data_type):
|
| 653 |
+
for test_i, (behav, past_behav, future_behav, old_behav) in enumerate(test_dl):
|
| 654 |
+
loss=0.
|
| 655 |
+
|
| 656 |
+
coco_idx = behav[:,0,0].cpu().long()
|
| 657 |
+
_,test_indices = np.unique(coco_idx, return_index=True)
|
| 658 |
+
test_indices = np.random.permutation(test_indices)[:num_test_eval]
|
| 659 |
+
image = images[coco_idx[test_indices]].float().to(device)
|
| 660 |
+
|
| 661 |
+
# get the corresponding raw voxel time series
|
| 662 |
+
for ib,b in enumerate(behav[test_indices,0,5].cpu().long().numpy()):
|
| 663 |
+
tr = (nsddata_raw_stimuli[nsddata_raw_stimuli['global_trial'].isin([b.item()])]['global_TR_onsets'].values + TR_delay).astype(np.int32).item()
|
| 664 |
+
if ib==0:
|
| 665 |
+
voxels_raw = mindeye_funcs[tr-2:tr+2][None][None]
|
| 666 |
+
else:
|
| 667 |
+
voxels_raw = np.vstack((voxels_raw, mindeye_funcs[tr-2:tr+2][None][None]))
|
| 668 |
+
voxels_raw = torch.Tensor(voxels_raw).clamp(0,1).to(device)
|
| 669 |
+
voxels_raw = utils.crop_or_pad(voxels_raw, img_size)
|
| 670 |
+
|
| 671 |
+
## Process it through pretrained MAE ##
|
| 672 |
+
encoder_out = model(voxels_raw, encoder_mask=tube_mask)
|
| 673 |
+
# encoder_out = bn(encoder_out)
|
| 674 |
+
|
| 675 |
+
voxel = encoder_out.flatten(1).unsqueeze(1)
|
| 676 |
+
# voxel = nn.functional.normalize(voxel,dim=-1)
|
| 677 |
+
|
| 678 |
+
assert len(image) == num_test_eval
|
| 679 |
+
|
| 680 |
+
clip_target = clip_img_embedder(image.float())
|
| 681 |
+
|
| 682 |
+
clip_voxels_norm = nn.functional.normalize(voxel.flatten(1), dim=-1)
|
| 683 |
+
|
| 684 |
+
clip_voxels = mindeye.ridge(voxel,0) # 0th index of subj_list
|
| 685 |
+
|
| 686 |
+
# backbone, clip_voxels = mindeye.backbone(voxel_ridge)
|
| 687 |
+
|
| 688 |
+
clip_voxels_norm = nn.functional.normalize(clip_voxels.flatten(1), dim=-1)
|
| 689 |
+
clip_target_norm = nn.functional.normalize(clip_target.flatten(1), dim=-1)
|
| 690 |
+
|
| 691 |
+
loss_clip = utils.soft_clip_loss(
|
| 692 |
+
clip_voxels_norm,
|
| 693 |
+
clip_target_norm,
|
| 694 |
+
temp=.006)
|
| 695 |
+
|
| 696 |
+
test_loss_clip_total += loss_clip.item()
|
| 697 |
+
loss += loss_clip
|
| 698 |
+
|
| 699 |
+
# forward and backward top 1 accuracy
|
| 700 |
+
labels = torch.arange(len(clip_voxels_norm)).to(clip_voxels_norm.device)
|
| 701 |
+
test_fwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_voxels_norm, clip_target_norm), labels, k=1).item()
|
| 702 |
+
test_bwd_percent_correct += utils.topk(utils.prenormed_batchwise_cosine_similarity(clip_target_norm, clip_voxels_norm), labels, k=1).item()
|
| 703 |
+
|
| 704 |
+
utils.check_loss(loss)
|
| 705 |
+
test_losses.append(loss.item())
|
| 706 |
+
|
| 707 |
+
logs = {"train/loss": np.mean(losses[-(train_i+1):]),
|
| 708 |
+
"test/loss": np.mean(test_losses[-(test_i+1):]),
|
| 709 |
+
"train/lr": lrs[-1],
|
| 710 |
+
"train/num_steps": len(losses),
|
| 711 |
+
"test/num_steps": len(test_losses),
|
| 712 |
+
"train/fwd_pct_correct": fwd_percent_correct / (train_i + 1),
|
| 713 |
+
"train/bwd_pct_correct": bwd_percent_correct / (train_i + 1),
|
| 714 |
+
"test/test_fwd_pct_correct": test_fwd_percent_correct / (test_i + 1),
|
| 715 |
+
"test/test_bwd_pct_correct": test_bwd_percent_correct / (test_i + 1),
|
| 716 |
+
"train/loss_clip_total": loss_clip_total / (train_i + 1),
|
| 717 |
+
"test/loss_clip_total": test_loss_clip_total / (test_i + 1),
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
progress_bar.set_postfix(**logs)
|
| 721 |
+
if wandb_log: wandb.log(logs)
|
| 722 |
+
|
| 723 |
+
# Save model checkpoint
|
| 724 |
+
if (ckpt_saving) and (epoch % ckpt_interval == 0):
|
| 725 |
+
save_ckpt('last')
|
| 726 |
+
|
| 727 |
+
# wait for other GPUs to catch up if needed
|
| 728 |
+
accelerator.wait_for_everyone()
|
| 729 |
+
torch.cuda.empty_cache()
|
| 730 |
+
gc.collect()
|
| 731 |
+
|
fMRI-foundation-model-old/fMRI-MAE/downstream.slurm
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH --account=fmri
|
| 3 |
+
#SBATCH --partition=a40
|
| 4 |
+
#SBATCH --job-name=downMAE
|
| 5 |
+
#SBATCH --nodes=1
|
| 6 |
+
#SBATCH --gres=gpu:1
|
| 7 |
+
#SBATCH --time=20:00:00 # total run time limit (HH:MM:SS)
|
| 8 |
+
#SBATCH -e slurms/%j.err # first create a "slurms" folder in current directory to store logs
|
| 9 |
+
#SBATCH -o slurms/%j.out
|
| 10 |
+
#SBATCH --comment=medarc
|
| 11 |
+
#SBATCH --no-requeue
|
| 12 |
+
#SBATCH --exclusive
|
| 13 |
+
|
| 14 |
+
export NUM_GPUS=1 # Set to equal gres=gpu:#!
|
| 15 |
+
|
| 16 |
+
source ~/.bashrc
|
| 17 |
+
cd /weka/proj-fmri/paulscotti/fMRI-foundation-model/fMRI-MAE/
|
| 18 |
+
jupyter nbconvert downstream.ipynb --to python
|
| 19 |
+
|
| 20 |
+
# Make sure another job doesnt use same port, here using random number
|
| 21 |
+
export MASTER_PORT=$((RANDOM % (19000 - 11000 + 1) + 11000))
|
| 22 |
+
export HOSTNAMES=$(scontrol show hostnames "$SLURM_JOB_NODELIST")
|
| 23 |
+
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
|
| 24 |
+
export COUNT_NODE=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | wc -l)
|
| 25 |
+
echo MASTER_ADDR=${MASTER_ADDR}
|
| 26 |
+
echo MASTER_PORT=${MASTER_PORT}
|
| 27 |
+
echo WORLD_SIZE=${COUNT_NODE}
|
| 28 |
+
|
| 29 |
+
accelerate launch --num_processes=$(($NUM_GPUS * $COUNT_NODE)) --num_machines=$COUNT_NODE --main_process_ip=$MASTER_ADDR --main_process_port=$MASTER_PORT --mixed_precision=fp16 downstream.py
|
fMRI-foundation-model-old/fMRI-MAE/mindeye_models.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
class MindEyeModule(nn.Module):
|
| 7 |
+
def __init__(self):
|
| 8 |
+
super().__init__()
|
| 9 |
+
def forward(self, x):
|
| 10 |
+
return x
|
| 11 |
+
class RidgeRegression(nn.Module):
|
| 12 |
+
# make sure to add weight_decay when initializing optimizer
|
| 13 |
+
def __init__(self, input_sizes, out_features, seq_len=1):
|
| 14 |
+
super().__init__()
|
| 15 |
+
self.out_features = out_features
|
| 16 |
+
self.linears = torch.nn.ModuleList([
|
| 17 |
+
torch.nn.Linear(input_size, out_features) for input_size in input_sizes
|
| 18 |
+
])
|
| 19 |
+
self.seq_len = seq_len
|
| 20 |
+
def forward(self, x, subj_idx):
|
| 21 |
+
out = torch.cat([self.linears[subj_idx](x[:,seq]).unsqueeze(1) for seq in range(self.seq_len)], dim=1)
|
| 22 |
+
return out
|
| 23 |
+
class BrainNetwork(nn.Module):
|
| 24 |
+
def __init__(self, h=1024, in_dim=15724, out_dim=768, seq_len=1, n_blocks=4, drop=.15, clip_size=768, clip_scale=1):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.seq_len = seq_len
|
| 27 |
+
self.h = h
|
| 28 |
+
self.clip_size = clip_size
|
| 29 |
+
self.clip_scale = clip_scale
|
| 30 |
+
self.mixer_blocks1 = nn.ModuleList([
|
| 31 |
+
self.mixer_block1(h, drop) for _ in range(n_blocks)
|
| 32 |
+
])
|
| 33 |
+
self.mixer_blocks2 = nn.ModuleList([
|
| 34 |
+
self.mixer_block2(seq_len, drop) for _ in range(n_blocks)
|
| 35 |
+
])
|
| 36 |
+
|
| 37 |
+
# Output linear layer
|
| 38 |
+
self.backbone_linear = nn.Linear(h * seq_len, out_dim, bias=True)
|
| 39 |
+
self.clip_proj = self.projector(clip_size, clip_size, h=clip_size)
|
| 40 |
+
|
| 41 |
+
def projector(self, in_dim, out_dim, h=2048):
|
| 42 |
+
return nn.Sequential(
|
| 43 |
+
nn.LayerNorm(in_dim),
|
| 44 |
+
nn.GELU(),
|
| 45 |
+
nn.Linear(in_dim, h),
|
| 46 |
+
nn.LayerNorm(h),
|
| 47 |
+
nn.GELU(),
|
| 48 |
+
nn.Linear(h, h),
|
| 49 |
+
nn.LayerNorm(h),
|
| 50 |
+
nn.GELU(),
|
| 51 |
+
nn.Linear(h, out_dim)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def mlp(self, in_dim, out_dim, drop):
|
| 55 |
+
return nn.Sequential(
|
| 56 |
+
nn.Linear(in_dim, out_dim),
|
| 57 |
+
nn.GELU(),
|
| 58 |
+
nn.Dropout(drop),
|
| 59 |
+
nn.Linear(out_dim, out_dim),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
def mixer_block1(self, h, drop):
|
| 63 |
+
return nn.Sequential(
|
| 64 |
+
nn.LayerNorm(h),
|
| 65 |
+
self.mlp(h, h, drop), # Token mixing
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
def mixer_block2(self, seq_len, drop):
|
| 69 |
+
return nn.Sequential(
|
| 70 |
+
nn.LayerNorm(seq_len),
|
| 71 |
+
self.mlp(seq_len, seq_len, drop) # Channel mixing
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def forward(self, x):
|
| 75 |
+
# make empty tensors
|
| 76 |
+
c = torch.Tensor([0.])
|
| 77 |
+
|
| 78 |
+
# Mixer blocks
|
| 79 |
+
residual1 = x
|
| 80 |
+
residual2 = x.permute(0,2,1)
|
| 81 |
+
for block1, block2 in zip(self.mixer_blocks1,self.mixer_blocks2):
|
| 82 |
+
x = block1(x) + residual1
|
| 83 |
+
residual1 = x
|
| 84 |
+
x = x.permute(0,2,1)
|
| 85 |
+
|
| 86 |
+
x = block2(x) + residual2
|
| 87 |
+
residual2 = x
|
| 88 |
+
x = x.permute(0,2,1)
|
| 89 |
+
|
| 90 |
+
x = x.reshape(x.size(0), -1)
|
| 91 |
+
backbone = self.backbone_linear(x).reshape(len(x), -1, self.clip_size)
|
| 92 |
+
if self.clip_scale>0:
|
| 93 |
+
c = self.clip_proj(backbone)
|
| 94 |
+
|
| 95 |
+
return backbone, c
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
import open_clip
|
| 99 |
+
import kornia
|
| 100 |
+
class FrozenOpenCLIPImageEmbedder(nn.Module):
|
| 101 |
+
"""
|
| 102 |
+
Uses the OpenCLIP vision transformer encoder for images
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
def __init__(
|
| 106 |
+
self,
|
| 107 |
+
arch="ViT-H-14",
|
| 108 |
+
version="laion2b_s32b_b79k",
|
| 109 |
+
device="cuda",
|
| 110 |
+
init_device="cpu",
|
| 111 |
+
max_length=77,
|
| 112 |
+
freeze=True,
|
| 113 |
+
antialias=True,
|
| 114 |
+
ucg_rate=0.0,
|
| 115 |
+
unsqueeze_dim=False,
|
| 116 |
+
repeat_to_max_len=False,
|
| 117 |
+
num_image_crops=0,
|
| 118 |
+
output_tokens=False,
|
| 119 |
+
l2_norm_tokens=False,
|
| 120 |
+
only_tokens=False,
|
| 121 |
+
cache_dir: Optional[str] = None,
|
| 122 |
+
):
|
| 123 |
+
super().__init__()
|
| 124 |
+
model, _, _ = open_clip.create_model_and_transforms(
|
| 125 |
+
arch,
|
| 126 |
+
device=torch.device(init_device),
|
| 127 |
+
pretrained=version,
|
| 128 |
+
cache_dir=cache_dir,
|
| 129 |
+
)
|
| 130 |
+
del model.transformer
|
| 131 |
+
self.model = model
|
| 132 |
+
self.max_crops = num_image_crops
|
| 133 |
+
self.pad_to_max_len = self.max_crops > 0
|
| 134 |
+
self.repeat_to_max_len = repeat_to_max_len and (not self.pad_to_max_len)
|
| 135 |
+
self.device = device
|
| 136 |
+
self.max_length = max_length
|
| 137 |
+
if freeze:
|
| 138 |
+
self.freeze()
|
| 139 |
+
|
| 140 |
+
self.antialias = antialias
|
| 141 |
+
|
| 142 |
+
self.register_buffer(
|
| 143 |
+
"mean", torch.Tensor([0.48145466, 0.4578275, 0.40821073]), persistent=False
|
| 144 |
+
)
|
| 145 |
+
self.register_buffer(
|
| 146 |
+
"std", torch.Tensor([0.26862954, 0.26130258, 0.27577711]), persistent=False
|
| 147 |
+
)
|
| 148 |
+
self.ucg_rate = ucg_rate
|
| 149 |
+
self.unsqueeze_dim = unsqueeze_dim
|
| 150 |
+
self.stored_batch = None
|
| 151 |
+
self.model.visual.output_tokens = output_tokens
|
| 152 |
+
self.output_tokens = output_tokens
|
| 153 |
+
if only_tokens:
|
| 154 |
+
assert output_tokens
|
| 155 |
+
self.only_tokens = only_tokens
|
| 156 |
+
self.l2_norm_tokens = l2_norm_tokens
|
| 157 |
+
if l2_norm_tokens:
|
| 158 |
+
assert output_tokens
|
| 159 |
+
|
| 160 |
+
def preprocess(self, x):
|
| 161 |
+
# normalize to [0,1]
|
| 162 |
+
x = kornia.geometry.resize(
|
| 163 |
+
x,
|
| 164 |
+
(224, 224),
|
| 165 |
+
interpolation="bicubic",
|
| 166 |
+
align_corners=True,
|
| 167 |
+
antialias=self.antialias,
|
| 168 |
+
)
|
| 169 |
+
x = (x + 1.0) / 2.0
|
| 170 |
+
# renormalize according to clip
|
| 171 |
+
x = kornia.enhance.normalize(x, self.mean, self.std)
|
| 172 |
+
return x
|
| 173 |
+
|
| 174 |
+
def freeze(self):
|
| 175 |
+
self.model = self.model.eval()
|
| 176 |
+
for param in self.parameters():
|
| 177 |
+
param.requires_grad = False
|
| 178 |
+
|
| 179 |
+
def forward(self, image, no_dropout=False):
|
| 180 |
+
z = self.encode_with_vision_transformer(image)
|
| 181 |
+
tokens = None
|
| 182 |
+
if self.output_tokens:
|
| 183 |
+
z, tokens = z[0], z[1]
|
| 184 |
+
z = z.to(image.dtype)
|
| 185 |
+
if self.ucg_rate > 0.0 and not no_dropout and not (self.max_crops > 0):
|
| 186 |
+
z = (
|
| 187 |
+
torch.bernoulli(
|
| 188 |
+
(1.0 - self.ucg_rate) * torch.ones(z.shape[0], device=z.device)
|
| 189 |
+
)[:, None]
|
| 190 |
+
* z
|
| 191 |
+
)
|
| 192 |
+
if tokens is not None:
|
| 193 |
+
tokens = (
|
| 194 |
+
expand_dims_like(
|
| 195 |
+
torch.bernoulli(
|
| 196 |
+
(1.0 - self.ucg_rate)
|
| 197 |
+
* torch.ones(tokens.shape[0], device=tokens.device)
|
| 198 |
+
),
|
| 199 |
+
tokens,
|
| 200 |
+
)
|
| 201 |
+
* tokens
|
| 202 |
+
)
|
| 203 |
+
if self.unsqueeze_dim:
|
| 204 |
+
z = z[:, None, :]
|
| 205 |
+
if self.output_tokens:
|
| 206 |
+
assert not self.repeat_to_max_len
|
| 207 |
+
assert not self.pad_to_max_len
|
| 208 |
+
if self.only_tokens:
|
| 209 |
+
return tokens
|
| 210 |
+
return tokens, z
|
| 211 |
+
if self.repeat_to_max_len:
|
| 212 |
+
if z.dim() == 2:
|
| 213 |
+
z_ = z[:, None, :]
|
| 214 |
+
else:
|
| 215 |
+
z_ = z
|
| 216 |
+
return repeat(z_, "b 1 d -> b n d", n=self.max_length), z
|
| 217 |
+
elif self.pad_to_max_len:
|
| 218 |
+
assert z.dim() == 3
|
| 219 |
+
z_pad = torch.cat(
|
| 220 |
+
(
|
| 221 |
+
z,
|
| 222 |
+
torch.zeros(
|
| 223 |
+
z.shape[0],
|
| 224 |
+
self.max_length - z.shape[1],
|
| 225 |
+
z.shape[2],
|
| 226 |
+
device=z.device,
|
| 227 |
+
),
|
| 228 |
+
),
|
| 229 |
+
1,
|
| 230 |
+
)
|
| 231 |
+
return z_pad, z_pad[:, 0, ...]
|
| 232 |
+
return z
|
| 233 |
+
|
| 234 |
+
def encode_with_vision_transformer(self, img):
|
| 235 |
+
# if self.max_crops > 0:
|
| 236 |
+
# img = self.preprocess_by_cropping(img)
|
| 237 |
+
if img.dim() == 5:
|
| 238 |
+
assert self.max_crops == img.shape[1]
|
| 239 |
+
img = rearrange(img, "b n c h w -> (b n) c h w")
|
| 240 |
+
img = self.preprocess(img)
|
| 241 |
+
if not self.output_tokens:
|
| 242 |
+
assert not self.model.visual.output_tokens
|
| 243 |
+
x = self.model.visual(img)
|
| 244 |
+
tokens = None
|
| 245 |
+
else:
|
| 246 |
+
assert self.model.visual.output_tokens
|
| 247 |
+
x, tokens = self.model.visual(img)
|
| 248 |
+
if self.l2_norm_tokens:
|
| 249 |
+
token_shape = tokens.shape
|
| 250 |
+
tokens = tokens.flatten(1)
|
| 251 |
+
tokens = torch.nn.functional.normalize(tokens, dim=-1)
|
| 252 |
+
tokens = (tokens - .0002) / .0015
|
| 253 |
+
tokens = tokens.view(token_shape)
|
| 254 |
+
tokens = (tokens * 1.0957) + .1598
|
| 255 |
+
if self.max_crops > 0:
|
| 256 |
+
x = rearrange(x, "(b n) d -> b n d", n=self.max_crops)
|
| 257 |
+
# drop out between 0 and all along the sequence axis
|
| 258 |
+
x = (
|
| 259 |
+
torch.bernoulli(
|
| 260 |
+
(1.0 - self.ucg_rate)
|
| 261 |
+
* torch.ones(x.shape[0], x.shape[1], 1, device=x.device)
|
| 262 |
+
)
|
| 263 |
+
* x
|
| 264 |
+
)
|
| 265 |
+
if tokens is not None:
|
| 266 |
+
tokens = rearrange(tokens, "(b n) t d -> b t (n d)", n=self.max_crops)
|
| 267 |
+
logpy.warning(
|
| 268 |
+
f"You are running very experimental token-concat in {self.__class__.__name__}. "
|
| 269 |
+
f"Check what you are doing, and then remove this message."
|
| 270 |
+
)
|
| 271 |
+
if self.output_tokens:
|
| 272 |
+
return x, tokens
|
| 273 |
+
return x
|
| 274 |
+
|
| 275 |
+
def encode(self, text):
|
| 276 |
+
return self(text)
|
fMRI-foundation-model-old/fMRI-MAE/models.py
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
from einops.layers.torch import Rearrange
|
| 8 |
+
from rope import RotaryPositionalEmbeddings4D
|
| 9 |
+
|
| 10 |
+
def posemb_sincos_4d(patches, temperature=10000, dtype=torch.float32):
|
| 11 |
+
_, f, d, h, w, dim, device, dtype = (*patches.shape, patches.device, patches.dtype)
|
| 12 |
+
|
| 13 |
+
z, y, x, t = torch.meshgrid(
|
| 14 |
+
torch.arange(f, device=device),
|
| 15 |
+
torch.arange(d, device=device),
|
| 16 |
+
torch.arange(h, device=device),
|
| 17 |
+
torch.arange(w, device=device),
|
| 18 |
+
indexing="ij",
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
fourier_dim = dim // 8
|
| 22 |
+
|
| 23 |
+
omega = torch.arange(fourier_dim, device=device) / (fourier_dim - 1)
|
| 24 |
+
omega = 1.0 / (temperature**omega)
|
| 25 |
+
|
| 26 |
+
z, y, x, t = [v.flatten()[:, None] * omega[None, :] for v in [z, y, x, t]]
|
| 27 |
+
|
| 28 |
+
pe = torch.cat(
|
| 29 |
+
(z.sin(), z.cos(), y.sin(), y.cos(), x.sin(), x.cos(), t.sin(), t.cos()), dim=1
|
| 30 |
+
)
|
| 31 |
+
pe = F.pad(pe, (0, dim - (fourier_dim * 8)))
|
| 32 |
+
return pe.type(dtype)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class FeedForward(nn.Module):
|
| 36 |
+
def __init__(self, embed_dim, hidden_dim):
|
| 37 |
+
super().__init__()
|
| 38 |
+
self.net = nn.Sequential(
|
| 39 |
+
nn.LayerNorm(embed_dim),
|
| 40 |
+
nn.Linear(embed_dim, hidden_dim),
|
| 41 |
+
nn.GELU(),
|
| 42 |
+
nn.Linear(hidden_dim, embed_dim),
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
def forward(self, x):
|
| 46 |
+
return self.net(x)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class Attention(nn.Module):
|
| 50 |
+
def __init__(
|
| 51 |
+
self,
|
| 52 |
+
embed_dim: int,
|
| 53 |
+
num_heads: int = 8,
|
| 54 |
+
dim_head: int = 64,
|
| 55 |
+
use_rope: bool = False,
|
| 56 |
+
cls_token: bool = False,
|
| 57 |
+
):
|
| 58 |
+
super().__init__()
|
| 59 |
+
inner_dim = dim_head * num_heads
|
| 60 |
+
self.num_heads = num_heads
|
| 61 |
+
self.scale = dim_head**-0.5
|
| 62 |
+
self.use_rope = use_rope
|
| 63 |
+
self.cls_token = cls_token
|
| 64 |
+
self.norm = nn.LayerNorm(embed_dim)
|
| 65 |
+
|
| 66 |
+
self.attend = nn.Softmax(dim=-1)
|
| 67 |
+
|
| 68 |
+
self.to_qkv = nn.Linear(embed_dim, inner_dim * 3, bias=False)
|
| 69 |
+
self.to_out = nn.Linear(inner_dim, embed_dim, bias=False)
|
| 70 |
+
|
| 71 |
+
def forward(
|
| 72 |
+
self,
|
| 73 |
+
x: torch.Tensor,
|
| 74 |
+
pos_embed: Optional[nn.Module],
|
| 75 |
+
mask: Optional[torch.Tensor] = None,
|
| 76 |
+
):
|
| 77 |
+
x = self.norm(x)
|
| 78 |
+
|
| 79 |
+
qkv = self.to_qkv(x).chunk(3, dim=-1)
|
| 80 |
+
q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b h n d", h=self.num_heads), qkv)
|
| 81 |
+
if self.use_rope:
|
| 82 |
+
if pos_embed is None:
|
| 83 |
+
raise ValueError(
|
| 84 |
+
"For RoPE embeddings `pos_embed` should be \
|
| 85 |
+
passed to the Attention forward."
|
| 86 |
+
)
|
| 87 |
+
# apply RoPE other than CLS token if it's included.
|
| 88 |
+
if self.cls_token:
|
| 89 |
+
q_cls = q[:, :, :1, :]
|
| 90 |
+
k_cls = k[:, :, :1, :]
|
| 91 |
+
q = q[:, :, 1:, :]
|
| 92 |
+
k = k[:, :, 1:, :]
|
| 93 |
+
q = pos_embed(q, mask=mask)
|
| 94 |
+
k = pos_embed(k, mask=mask)
|
| 95 |
+
if self.cls_token:
|
| 96 |
+
q = torch.cat([q_cls, q], dim=2)
|
| 97 |
+
k = torch.cat([k_cls, k], dim=2)
|
| 98 |
+
|
| 99 |
+
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
|
| 100 |
+
|
| 101 |
+
# print("q", q.shape) # B num_heads N D
|
| 102 |
+
# print("dots", dots.shape) # B num_heads N N
|
| 103 |
+
attn = self.attend(dots)
|
| 104 |
+
|
| 105 |
+
out = torch.matmul(attn, v)
|
| 106 |
+
out = rearrange(out, "b h n d -> b n (h d)")
|
| 107 |
+
return self.to_out(out)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class Transformer(nn.Module):
|
| 111 |
+
def __init__(
|
| 112 |
+
self,
|
| 113 |
+
embed_dim: int,
|
| 114 |
+
depth: int,
|
| 115 |
+
num_heads: int,
|
| 116 |
+
dim_head: int,
|
| 117 |
+
mlp_dim: int,
|
| 118 |
+
use_rope: bool = False,
|
| 119 |
+
grid_height: Optional[int] = None,
|
| 120 |
+
grid_width: Optional[int] = None,
|
| 121 |
+
grid_depth: Optional[int] = None,
|
| 122 |
+
grid_time: Optional[int] = None,
|
| 123 |
+
cls_token: bool = False,
|
| 124 |
+
**args,
|
| 125 |
+
):
|
| 126 |
+
super().__init__()
|
| 127 |
+
self.embed_dim = embed_dim
|
| 128 |
+
self.num_heads = num_heads
|
| 129 |
+
self.mlp_dim = mlp_dim
|
| 130 |
+
self.dim_head = dim_head
|
| 131 |
+
self.norm = nn.LayerNorm(embed_dim)
|
| 132 |
+
self.layers = nn.ModuleList([])
|
| 133 |
+
for _ in range(depth):
|
| 134 |
+
self.layers.append(
|
| 135 |
+
nn.ModuleList(
|
| 136 |
+
[
|
| 137 |
+
Attention(
|
| 138 |
+
embed_dim,
|
| 139 |
+
num_heads=num_heads,
|
| 140 |
+
dim_head=dim_head,
|
| 141 |
+
use_rope=use_rope,
|
| 142 |
+
cls_token=cls_token,
|
| 143 |
+
),
|
| 144 |
+
FeedForward(embed_dim, mlp_dim),
|
| 145 |
+
]
|
| 146 |
+
)
|
| 147 |
+
)
|
| 148 |
+
# RoPE positional embeddings
|
| 149 |
+
self.use_rope = use_rope
|
| 150 |
+
if self.use_rope:
|
| 151 |
+
self.rope_pos_emb = RotaryPositionalEmbeddings4D(
|
| 152 |
+
d=dim_head,
|
| 153 |
+
grid_depth=grid_depth,
|
| 154 |
+
grid_height=grid_height,
|
| 155 |
+
grid_width=grid_width,
|
| 156 |
+
grid_time=grid_time,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
def forward(self, x, mask: Optional[torch.Tensor] = None):
|
| 160 |
+
for attn, ff in self.layers:
|
| 161 |
+
x = (
|
| 162 |
+
attn(
|
| 163 |
+
x,
|
| 164 |
+
pos_embed=self.rope_pos_emb if self.use_rope else None,
|
| 165 |
+
mask=mask,
|
| 166 |
+
)
|
| 167 |
+
+ x
|
| 168 |
+
)
|
| 169 |
+
x = ff(x) + x
|
| 170 |
+
return self.norm(x)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class VisionTransformerMAE(nn.Module):
|
| 174 |
+
def __init__(
|
| 175 |
+
self,
|
| 176 |
+
*,
|
| 177 |
+
encoder,
|
| 178 |
+
decoder,
|
| 179 |
+
image_size,
|
| 180 |
+
image_patch_size,
|
| 181 |
+
frames,
|
| 182 |
+
frame_patch_size,
|
| 183 |
+
channels=1,
|
| 184 |
+
use_rope_emb: bool = False,
|
| 185 |
+
use_cls_token: bool = False,
|
| 186 |
+
num_ids=1000,
|
| 187 |
+
**args,
|
| 188 |
+
):
|
| 189 |
+
super().__init__()
|
| 190 |
+
image_depth, image_height, image_width = image_size
|
| 191 |
+
patch_depth, patch_height, patch_width = image_patch_size
|
| 192 |
+
|
| 193 |
+
self.encoder_transformer = encoder
|
| 194 |
+
self.decoder_transformer = decoder
|
| 195 |
+
|
| 196 |
+
# Check divisibility
|
| 197 |
+
assert (image_depth % patch_depth == 0 and image_height % patch_height == 0 and image_width %
|
| 198 |
+
patch_width == 0), "Image dimensions must be divisible by the patch size."
|
| 199 |
+
assert (frames % frame_patch_size == 0), "Frames must be divisible by the frame patch size"
|
| 200 |
+
|
| 201 |
+
self.patch_dim = channels * patch_depth * patch_height * patch_width * frame_patch_size
|
| 202 |
+
|
| 203 |
+
self.num_patches = image_size[0]//image_patch_size[0] * image_size[1]//image_patch_size[1] * image_size[2]//image_patch_size[2] * frames
|
| 204 |
+
|
| 205 |
+
self.patchify = Rearrange(
|
| 206 |
+
"b c (f pf) (d pd) (h ph) (w pw) -> b f d h w (pd ph pw pf c)",
|
| 207 |
+
pd=patch_depth,
|
| 208 |
+
ph=patch_height,
|
| 209 |
+
pw=patch_width,
|
| 210 |
+
pf=frame_patch_size,
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
self.unpatchify = nn.Sequential(
|
| 214 |
+
Rearrange(
|
| 215 |
+
"b (f d h w) (pd ph pw pf c) -> b f d h w (pd ph pw pf c)",
|
| 216 |
+
c=channels,
|
| 217 |
+
d=image_depth,
|
| 218 |
+
h=image_height,
|
| 219 |
+
w=image_width,
|
| 220 |
+
pd=patch_depth,
|
| 221 |
+
ph=patch_height,
|
| 222 |
+
pw=patch_width,
|
| 223 |
+
pf=frame_patch_size,
|
| 224 |
+
)
|
| 225 |
+
)
|
| 226 |
+
self.encoder_embed_dim = self.encoder_transformer.embed_dim
|
| 227 |
+
|
| 228 |
+
self.patch_to_emb = nn.Sequential(
|
| 229 |
+
nn.LayerNorm(self.patch_dim),
|
| 230 |
+
nn.Linear(self.patch_dim, self.encoder_embed_dim),
|
| 231 |
+
nn.LayerNorm(self.encoder_embed_dim),
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
self.use_rope_emb = use_rope_emb
|
| 235 |
+
if not self.use_rope_emb:
|
| 236 |
+
self.posemb_sincos_4d = posemb_sincos_4d(
|
| 237 |
+
torch.zeros(
|
| 238 |
+
1,
|
| 239 |
+
frames // frame_patch_size,
|
| 240 |
+
image_depth // patch_depth,
|
| 241 |
+
image_height // patch_height,
|
| 242 |
+
image_width // patch_width,
|
| 243 |
+
self.encoder_embed_dim,
|
| 244 |
+
)
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
# cls token
|
| 248 |
+
self.use_cls_token = use_cls_token
|
| 249 |
+
if use_cls_token:
|
| 250 |
+
self.cls_token = nn.Parameter(torch.zeros(1, 1, self.encoder_embed_dim))
|
| 251 |
+
|
| 252 |
+
if not isinstance(self.decoder_transformer, nn.Identity):
|
| 253 |
+
self.decoder_embed_dim = self.decoder_transformer.embed_dim
|
| 254 |
+
if self.encoder_embed_dim != self.decoder_embed_dim:
|
| 255 |
+
self.encoder_to_decoder = nn.Linear(self.encoder_embed_dim, self.decoder_embed_dim, bias=False)
|
| 256 |
+
self.mask_token = nn.Parameter(torch.zeros(1, 1, self.decoder_embed_dim))
|
| 257 |
+
|
| 258 |
+
self.decoder_proj = nn.Sequential(
|
| 259 |
+
nn.LayerNorm(self.decoder_embed_dim),
|
| 260 |
+
nn.GELU(),
|
| 261 |
+
nn.Linear(self.decoder_embed_dim, self.patch_dim),
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
def forward(self, x, encoder_mask=None, decoder_mask=None, device="cuda", verbose=False):
|
| 265 |
+
# ENCODER
|
| 266 |
+
if decoder_mask is None:
|
| 267 |
+
if verbose: print(x.shape)
|
| 268 |
+
x = self.patchify(x)
|
| 269 |
+
if verbose: print("patched", x.shape)
|
| 270 |
+
x = rearrange(x, "b ... d -> b (...) d")
|
| 271 |
+
if verbose: print("reshaped", x.shape)
|
| 272 |
+
|
| 273 |
+
x = x[:, encoder_mask]
|
| 274 |
+
if verbose: print("masked", x.shape)
|
| 275 |
+
|
| 276 |
+
x = self.patch_to_emb(x.to(device))
|
| 277 |
+
if verbose: print("patched_emb", x.shape)
|
| 278 |
+
|
| 279 |
+
if not self.use_rope_emb:
|
| 280 |
+
if verbose: print("pe", self.posemb_sincos_4d.shape)
|
| 281 |
+
x = x + self.posemb_sincos_4d[encoder_mask].to(device)
|
| 282 |
+
if self.use_cls_token:
|
| 283 |
+
cls_tokens = self.cls_token.expand(len(x), -1, -1)
|
| 284 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 285 |
+
if verbose: print("masked", x.shape)
|
| 286 |
+
x = self.encoder_transformer(x, mask=encoder_mask if self.use_rope_emb else None)
|
| 287 |
+
if verbose: print(x.shape)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# if verbose: print(x.shape)
|
| 291 |
+
# x = self.patchify(x)
|
| 292 |
+
# if verbose: print("patched", x.shape)
|
| 293 |
+
# x = self.patch_to_emb(x.to(device))
|
| 294 |
+
# if verbose: print("patched_emb", x.shape)
|
| 295 |
+
# x = rearrange(x, "b ... d -> b (...) d")
|
| 296 |
+
# if verbose: print("reshaped", x.shape)
|
| 297 |
+
# if not self.use_rope_emb:
|
| 298 |
+
# if verbose: print("pe", self.posemb_sincos_4d.shape)
|
| 299 |
+
# x = x + self.posemb_sincos_4d.to(x.device)
|
| 300 |
+
# if verbose: print("x", x.shape)
|
| 301 |
+
# x = x[:, encoder_mask]
|
| 302 |
+
# if self.use_cls_token:
|
| 303 |
+
# cls_tokens = self.cls_token.expand(len(x), -1, -1)
|
| 304 |
+
# x = torch.cat((cls_tokens, x), dim=1)
|
| 305 |
+
# if verbose: print("masked", x.shape)
|
| 306 |
+
# x = self.encoder_transformer(x, mask=encoder_mask if self.use_rope_emb else None)
|
| 307 |
+
# if verbose: print(x.shape)
|
| 308 |
+
else: # DECODER
|
| 309 |
+
if verbose: print(x.shape)
|
| 310 |
+
if self.encoder_embed_dim != self.decoder_embed_dim:
|
| 311 |
+
x = self.encoder_to_decoder(x.to(device))
|
| 312 |
+
if verbose: print("Linear", x.shape)
|
| 313 |
+
B, _, _ = x.shape
|
| 314 |
+
N = decoder_mask.sum()
|
| 315 |
+
mask = None
|
| 316 |
+
if not self.use_rope_emb:
|
| 317 |
+
pos_embed = self.posemb_sincos_4d.to(x.device)
|
| 318 |
+
if verbose: print("pe", pos_embed.shape)
|
| 319 |
+
if self.encoder_embed_dim != self.decoder_embed_dim:
|
| 320 |
+
pos_embed = self.encoder_to_decoder(pos_embed)
|
| 321 |
+
if verbose: print("Linear pe", pos_embed.shape)
|
| 322 |
+
pos_emd_encoder = pos_embed[encoder_mask]
|
| 323 |
+
pos_emd_decoder = pos_embed[decoder_mask]
|
| 324 |
+
if verbose: print("pos_emd_encoder", pos_emd_encoder.shape)
|
| 325 |
+
if verbose: print("pos_emd_decoder", pos_emd_decoder.shape)
|
| 326 |
+
if self.use_cls_token:
|
| 327 |
+
cls_tokens = x[:,:1,:]
|
| 328 |
+
x = x[:,1:,:]
|
| 329 |
+
|
| 330 |
+
x = torch.cat([x + pos_emd_encoder,
|
| 331 |
+
self.mask_token.repeat(B, N, 1) + pos_emd_decoder],
|
| 332 |
+
dim=1)
|
| 333 |
+
if self.use_cls_token:
|
| 334 |
+
x = torch.cat([cls_tokens, x], dim=1)
|
| 335 |
+
else:
|
| 336 |
+
mask = torch.cat((torch.where(encoder_mask)[0], torch.where(decoder_mask)[0]))
|
| 337 |
+
# No abs positional embeddings for RoPE
|
| 338 |
+
x = torch.cat([x,
|
| 339 |
+
self.mask_token.repeat(B, N - 1 if self.use_cls_token else N, 1)],
|
| 340 |
+
dim=1)
|
| 341 |
+
if verbose: print("x_concat", x.shape)
|
| 342 |
+
x = self.decoder_transformer(x, mask=mask)
|
| 343 |
+
if verbose: print(x.shape)
|
| 344 |
+
x = self.decoder_proj(x)
|
| 345 |
+
if verbose: print("proj", x.shape)
|
| 346 |
+
return x
|
| 347 |
+
|
| 348 |
+
def transformer_mini(**args):
|
| 349 |
+
return Transformer(
|
| 350 |
+
embed_dim=48,
|
| 351 |
+
depth=6,
|
| 352 |
+
num_heads=2,
|
| 353 |
+
mlp_dim=1024,
|
| 354 |
+
dim_head=64,
|
| 355 |
+
**args
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
def transformer_small(**args):
|
| 359 |
+
return Transformer(
|
| 360 |
+
embed_dim=384,
|
| 361 |
+
depth=12,
|
| 362 |
+
num_heads=6,
|
| 363 |
+
mlp_dim=1536,
|
| 364 |
+
dim_head=64,
|
| 365 |
+
**args
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
def transformer_base(**args):
|
| 369 |
+
return Transformer(
|
| 370 |
+
embed_dim=768,
|
| 371 |
+
depth=12,
|
| 372 |
+
num_heads=12,
|
| 373 |
+
mlp_dim=3072,
|
| 374 |
+
dim_head=64,
|
| 375 |
+
**args
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
def transformer_large(**args):
|
| 379 |
+
return Transformer(
|
| 380 |
+
embed_dim=1024,
|
| 381 |
+
depth=24,
|
| 382 |
+
num_heads=16,
|
| 383 |
+
mlp_dim=4096,
|
| 384 |
+
dim_head=64,
|
| 385 |
+
**args
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
def transformer_huge(**args):
|
| 389 |
+
return Transformer(
|
| 390 |
+
embed_dim=1280,
|
| 391 |
+
depth=32,
|
| 392 |
+
num_heads=16,
|
| 393 |
+
mlp_dim=5120,
|
| 394 |
+
dim_head=64,
|
| 395 |
+
**args
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
transformer_mapping = {
|
| 399 |
+
"vit_mini": transformer_mini,
|
| 400 |
+
"vit_small": transformer_small,
|
| 401 |
+
"vit_base": transformer_base,
|
| 402 |
+
"vit_large": transformer_large,
|
| 403 |
+
"vit_huge": transformer_huge,
|
| 404 |
+
"none": nn.Identity
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
def get_vit(size, **args):
|
| 408 |
+
encoder = transformer_mapping[size["encoder"]](**args)
|
| 409 |
+
if args['use_decoder_same_emb_dim']:
|
| 410 |
+
depth = args.pop('decoder_depth', None)
|
| 411 |
+
assert depth is not None and depth > 0 , "for same emb dim decoder depth has to be provided and be >0"
|
| 412 |
+
decoder = Transformer(
|
| 413 |
+
embed_dim=encoder.embed_dim,
|
| 414 |
+
depth=depth,
|
| 415 |
+
num_heads=encoder.num_heads,
|
| 416 |
+
mlp_dim=encoder.mlp_dim,
|
| 417 |
+
dim_head=encoder.dim_head,
|
| 418 |
+
**args
|
| 419 |
+
)
|
| 420 |
+
else:
|
| 421 |
+
decoder = transformer_mapping[size["decoder"]](**args)
|
| 422 |
+
return VisionTransformerMAE(encoder=encoder, decoder=decoder, **args)
|
| 423 |
+
|
fMRI-foundation-model-old/fMRI-MAE/rope.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RotaryPositionalEmbeddings4D(nn.Module):
|
| 8 |
+
def __init__(
|
| 9 |
+
self,
|
| 10 |
+
d: int,
|
| 11 |
+
grid_height: int,
|
| 12 |
+
grid_width: int,
|
| 13 |
+
grid_depth: int,
|
| 14 |
+
grid_time: int,
|
| 15 |
+
base: int = 10_000,
|
| 16 |
+
):
|
| 17 |
+
super().__init__()
|
| 18 |
+
assert d % 4 == 0, f"{d} is not divisible by 4."
|
| 19 |
+
self.base = base
|
| 20 |
+
self.d = d
|
| 21 |
+
self.grid_height = grid_height
|
| 22 |
+
self.grid_width = grid_width
|
| 23 |
+
self.grid_depth = grid_depth
|
| 24 |
+
self.grid_time = grid_time
|
| 25 |
+
self.cos_cached = None
|
| 26 |
+
self.sin_cached = None
|
| 27 |
+
|
| 28 |
+
def _build_cache_1d(self, effective_d: int, seq_idx: torch.Tensor) -> torch.Tensor:
|
| 29 |
+
seq_idx = seq_idx.reshape(-1) # List of positions
|
| 30 |
+
theta = 1.0 / (
|
| 31 |
+
self.base ** (torch.arange(0, effective_d, 2).float() / effective_d)
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
idx_theta = torch.einsum("n,d->nd", seq_idx, theta)
|
| 35 |
+
idx_theta2 = torch.cat([idx_theta, idx_theta], dim=1)
|
| 36 |
+
return idx_theta2
|
| 37 |
+
|
| 38 |
+
def _build_cache(self, x: torch.Tensor) -> None:
|
| 39 |
+
# x: batched tensor
|
| 40 |
+
# x.shape -> Batch, Seq Length, Embed Dim
|
| 41 |
+
if self.cos_cached is not None and x.shape[1] <= self.cos_cached.shape[1]:
|
| 42 |
+
# if cache is already built
|
| 43 |
+
return
|
| 44 |
+
# get the positions
|
| 45 |
+
grid_h = torch.arange(self.grid_height, dtype=torch.float32)
|
| 46 |
+
grid_w = torch.arange(self.grid_width, dtype=torch.float32)
|
| 47 |
+
grid_d = torch.arange(self.grid_depth, dtype=torch.float32)
|
| 48 |
+
grid_t = torch.arange(self.grid_time, dtype=torch.float32)
|
| 49 |
+
|
| 50 |
+
grid = torch.meshgrid(
|
| 51 |
+
grid_t, grid_d, grid_h, grid_w, indexing="xy"
|
| 52 |
+
) # This order should match with i/p
|
| 53 |
+
grid = torch.stack(grid, axis=0)
|
| 54 |
+
grid = grid.reshape(
|
| 55 |
+
[4, 1, self.grid_time, self.grid_depth, self.grid_height, self.grid_width]
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# Get the embedings
|
| 59 |
+
emb_t = self._build_cache_1d(self.d // 4, grid[0]) # 1/4 in-case of 4D
|
| 60 |
+
emb_d = self._build_cache_1d(self.d // 4, grid[1])
|
| 61 |
+
emb_h = self._build_cache_1d(self.d // 4, grid[2]) # (T*D*H*W, embedding_dim/4)
|
| 62 |
+
emb_w = self._build_cache_1d(self.d // 4, grid[3])
|
| 63 |
+
emb = torch.concatenate(
|
| 64 |
+
[emb_t, emb_d, emb_h, emb_w], axis=1
|
| 65 |
+
) # (T*D*H*W, embedding_dim)
|
| 66 |
+
emb = emb.to(x.device)
|
| 67 |
+
# cache sin and cos
|
| 68 |
+
self.cos_cached = emb.cos()[
|
| 69 |
+
None, None, :, :
|
| 70 |
+
] # batch, Num Heads, Seq Len, Embed Dim
|
| 71 |
+
self.sin_cached = emb.sin()[None, None, :, :]
|
| 72 |
+
|
| 73 |
+
def _neg_half(self, x: torch.Tensor) -> torch.Tensor:
|
| 74 |
+
d_2 = self.d // 2
|
| 75 |
+
return torch.cat([-x[:, :, :, d_2:], x[:, :, :, :d_2]], dim=-1)
|
| 76 |
+
|
| 77 |
+
def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor]):
|
| 78 |
+
"""
|
| 79 |
+
Args:
|
| 80 |
+
-----
|
| 81 |
+
x: query or key vector
|
| 82 |
+
mask: boolean vector of length sequence length.
|
| 83 |
+
True for the non-masked positions.
|
| 84 |
+
"""
|
| 85 |
+
self._build_cache(x)
|
| 86 |
+
x_rope, x_pass = x[..., : self.d], x[..., self.d :]
|
| 87 |
+
neg_half_x = self._neg_half(x_rope)
|
| 88 |
+
x_rope = (
|
| 89 |
+
(x_rope * self.cos_cached[:, :, : x.shape[1], :])
|
| 90 |
+
+ (neg_half_x * self.sin_cached[:, :, : x.shape[1], :])
|
| 91 |
+
if mask is None
|
| 92 |
+
else (
|
| 93 |
+
x_rope * self.cos_cached[:, :, mask, :]
|
| 94 |
+
+ neg_half_x * self.sin_cached[:, :, mask, :]
|
| 95 |
+
)
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
return torch.cat((x_rope, x_pass), dim=-1)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
# img size 4, 64 64 48
|
| 103 |
+
# patch size 1, 8, 8, 8
|
| 104 |
+
# num of patches across time, depth, height, and width -> 4, 8, 8, 6
|
| 105 |
+
rot_embed = RotaryPositionalEmbeddings4D(
|
| 106 |
+
d=512, grid_depth=8, grid_height=8, grid_width=6, grid_time=4
|
| 107 |
+
)
|
| 108 |
+
query_ = torch.randn(
|
| 109 |
+
10, 1, 8 * 8 * 6 * 4, 512
|
| 110 |
+
) # Batch, Heads, Num Tokens/Seq Length, Embedding Dims
|
| 111 |
+
print(rot_embed(query_).shape)
|
fMRI-foundation-model-old/fMRI-MAE/utils.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from io import BytesIO
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
from PIL import Image
|
| 8 |
+
from skimage import filters
|
| 9 |
+
from torchvision import transforms
|
| 10 |
+
import nibabel as nib
|
| 11 |
+
from nilearn import plotting
|
| 12 |
+
import matplotlib.pyplot as plt
|
| 13 |
+
import re
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
|
| 17 |
+
def my_split_by_node(urls): return urls
|
| 18 |
+
|
| 19 |
+
def is_interactive():
|
| 20 |
+
import __main__ as main
|
| 21 |
+
|
| 22 |
+
return not hasattr(main, "__file__")
|
| 23 |
+
|
| 24 |
+
def my_split_by_node(urls): return urls
|
| 25 |
+
|
| 26 |
+
def seed_everything(seed=0, cudnn_deterministic=True):
|
| 27 |
+
random.seed(seed)
|
| 28 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 29 |
+
np.random.seed(seed)
|
| 30 |
+
torch.manual_seed(seed)
|
| 31 |
+
torch.cuda.manual_seed(seed)
|
| 32 |
+
torch.cuda.manual_seed_all(seed)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def grayscale_decoder(image_data):
|
| 36 |
+
return np.array(Image.open(BytesIO(image_data))).astype(np.float32) / 65535
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def numpy_decoder(npy_data):
|
| 40 |
+
return np.load(BytesIO(npy_data))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def reshape_to_2d(tensor):
|
| 44 |
+
if tensor.ndim == 5:
|
| 45 |
+
tensor = tensor[0]
|
| 46 |
+
assert tensor.ndim == 4
|
| 47 |
+
return rearrange(tensor, "b h w c -> (b h) (c w)")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def reshape_to_original(tensor_2d, h=64, w=64, c=48):
|
| 51 |
+
# print(tensor_2d.shape) # torch.Size([1, 256, 3072])
|
| 52 |
+
return rearrange(tensor_2d, "(tr h) (c w) -> tr h w c", h=h, w=w, c=c)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def plot_numpy_nii(image):
|
| 56 |
+
while image.ndim > 3:
|
| 57 |
+
image = image[0]
|
| 58 |
+
nii = nib.Nifti1Image(image.astype(np.float32), np.eye(4)) # noqa
|
| 59 |
+
plotting.plot_epi(nii, cmap="gray")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def threshold_based_masking(org_images):
|
| 63 |
+
thresholds = filters.threshold_multiotsu(org_images.numpy(), classes=3)
|
| 64 |
+
brain_segmentation = org_images > thresholds.min()
|
| 65 |
+
return brain_segmentation
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_brain_pos_patches(
|
| 69 |
+
func,
|
| 70 |
+
patch_depth=8,
|
| 71 |
+
patch_height=8,
|
| 72 |
+
patch_width=8,
|
| 73 |
+
frame_patch_size=1,
|
| 74 |
+
masking_strategy="conservative",
|
| 75 |
+
):
|
| 76 |
+
_, _, depth = func.shape
|
| 77 |
+
if masking_strategy == "conservative":
|
| 78 |
+
func = func.sum(axis=(-1), keepdim=True).repeat(1, 1, depth)
|
| 79 |
+
else:
|
| 80 |
+
raise Exception("Not implemented other masking strategies than conservative.")
|
| 81 |
+
|
| 82 |
+
return func
|
| 83 |
+
|
| 84 |
+
def crop_or_pad(tensor, new_shape):
|
| 85 |
+
# Ensure the tensor has at least three dimensions
|
| 86 |
+
if tensor.dim() < 3:
|
| 87 |
+
raise ValueError("Tensor must have at least 3 dimensions")
|
| 88 |
+
|
| 89 |
+
# Current dimensions of the last three axes
|
| 90 |
+
current_shape = tensor.shape[-3:]
|
| 91 |
+
|
| 92 |
+
# Compute padding and cropping needed for each dimension
|
| 93 |
+
padding_crop = [(ns - cs) for ns, cs in zip(new_shape, current_shape)]
|
| 94 |
+
if sum(padding_crop)==0:
|
| 95 |
+
return tensor
|
| 96 |
+
|
| 97 |
+
# Apply cropping if necessary
|
| 98 |
+
if any(pc < 0 for pc in padding_crop):
|
| 99 |
+
crop_slices = [slice(-pc//2, ns-pc//2) if pc < 0 else slice(None) for pc, ns in zip(padding_crop, new_shape)]
|
| 100 |
+
tensor = tensor[..., crop_slices[0], crop_slices[1], crop_slices[2]]
|
| 101 |
+
|
| 102 |
+
# Calculate padding to apply after cropping if necessary
|
| 103 |
+
pad_values = [(max(0, pc)//2, max(0, pc) - max(0, pc)//2) for pc in padding_crop]
|
| 104 |
+
|
| 105 |
+
# Apply padding
|
| 106 |
+
tensor = F.pad(tensor, pad_values[2] + pad_values[1] + pad_values[0])
|
| 107 |
+
|
| 108 |
+
return tensor
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class DataPrepper:
|
| 112 |
+
def __init__(
|
| 113 |
+
self,
|
| 114 |
+
num_frames=4,
|
| 115 |
+
masking_strategy="MNI",
|
| 116 |
+
patch_depth=8,
|
| 117 |
+
patch_height=8,
|
| 118 |
+
patch_width=8,
|
| 119 |
+
frame_patch_size=1,
|
| 120 |
+
image_size=[88, 104, 72]
|
| 121 |
+
):
|
| 122 |
+
self.num_frames = num_frames
|
| 123 |
+
self.masking_strategy = masking_strategy
|
| 124 |
+
self.patch_depth = 8
|
| 125 |
+
self.patch_height = 8
|
| 126 |
+
self.patch_width = 8
|
| 127 |
+
self.frame_patch_size = 1
|
| 128 |
+
self.image_size=image_size
|
| 129 |
+
|
| 130 |
+
def __call__(self, func):
|
| 131 |
+
start_timepoint = np.random.choice(np.arange(func.shape[1] - self.num_frames))
|
| 132 |
+
timepoints = np.arange(start_timepoint, start_timepoint + self.num_frames)
|
| 133 |
+
|
| 134 |
+
func = func[:,timepoints]
|
| 135 |
+
|
| 136 |
+
# crop image_size acc to config
|
| 137 |
+
func = crop_or_pad(func, self.image_size)
|
| 138 |
+
|
| 139 |
+
if self.masking_strategy=="MNI" or self.masking_strategy=="None":
|
| 140 |
+
return func, None
|
| 141 |
+
|
| 142 |
+
brain_segmentation = threshold_based_masking(func.mean(1))
|
| 143 |
+
pos_patches = None
|
| 144 |
+
for brain in brain_segmentation:
|
| 145 |
+
output = get_brain_pos_patches(
|
| 146 |
+
brain,
|
| 147 |
+
patch_depth=self.patch_depth,
|
| 148 |
+
patch_height=self.patch_height,
|
| 149 |
+
patch_width=self.patch_width,
|
| 150 |
+
frame_patch_size=self.frame_patch_size,
|
| 151 |
+
masking_strategy=self.masking_strategy,
|
| 152 |
+
)
|
| 153 |
+
if pos_patches is None:
|
| 154 |
+
pos_patches = output[None]
|
| 155 |
+
else:
|
| 156 |
+
pos_patches = torch.vstack((pos_patches, output[None]))
|
| 157 |
+
return func, pos_patches
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def plot_slices(unpatches):
|
| 161 |
+
if unpatches.ndim == 5:
|
| 162 |
+
unpatches = unpatches[0]
|
| 163 |
+
return transforms.ToPILImage()(reshape_to_2d(unpatches))
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def check_loss(loss):
|
| 167 |
+
if loss.isnan().any():
|
| 168 |
+
raise ValueError('NaN loss')
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def count_params(model):
|
| 172 |
+
total = sum(p.numel() for p in model.parameters())
|
| 173 |
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 174 |
+
print("param counts:\n{:,} total\n{:,} trainable".format(total, trainable))
|
| 175 |
+
return trainable
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def contrastive_loss(
|
| 179 |
+
cls_token1: torch.Tensor, cls_token2: torch.Tensor, temperature: torch.Tensor
|
| 180 |
+
):
|
| 181 |
+
feat1 = cls_token1 / cls_token1.norm(dim=1, keepdim=True)
|
| 182 |
+
feat2 = cls_token2 / cls_token2.norm(dim=1, keepdim=True)
|
| 183 |
+
|
| 184 |
+
cosine_sim = feat1 @ feat2.T
|
| 185 |
+
logit_scale = temperature.exp() # log scale, learned during training
|
| 186 |
+
feat1 = cosine_sim * logit_scale
|
| 187 |
+
feat2 = feat1.T
|
| 188 |
+
|
| 189 |
+
labels = torch.arange(feat1.shape[0]).to(feat1.device)
|
| 190 |
+
loss = (
|
| 191 |
+
torch.nn.functional.cross_entropy(feat1, labels)
|
| 192 |
+
+ torch.nn.functional.cross_entropy(feat2, labels)
|
| 193 |
+
) / 2
|
| 194 |
+
return loss
|
| 195 |
+
|
| 196 |
+
### MindEye functions ###
|
| 197 |
+
|
| 198 |
+
def soft_clip_loss(preds, targs, temp=0.006):
|
| 199 |
+
clip_clip = (targs @ targs.T)/temp
|
| 200 |
+
brain_clip = (preds @ targs.T)/temp
|
| 201 |
+
loss1 = -(brain_clip.log_softmax(-1) * clip_clip.softmax(-1)).sum(-1).mean()
|
| 202 |
+
loss2 = -(brain_clip.T.log_softmax(-1) * clip_clip.softmax(-1)).sum(-1).mean()
|
| 203 |
+
|
| 204 |
+
loss = (loss1 + loss2)/2
|
| 205 |
+
return loss
|
| 206 |
+
|
| 207 |
+
def cosine_anneal(start, end, steps):
|
| 208 |
+
return end + (start - end)/2 * (1 + torch.cos(torch.pi*torch.arange(steps)/(steps-1)))
|
| 209 |
+
|
| 210 |
+
def mixco(voxels, beta=0.15, s_thresh=0.5, perm=None, betas=None, select=None):
|
| 211 |
+
if perm is None:
|
| 212 |
+
perm = torch.randperm(voxels.shape[0])
|
| 213 |
+
voxels_shuffle = voxels[perm].to(voxels.device,dtype=voxels.dtype)
|
| 214 |
+
if betas is None:
|
| 215 |
+
betas = torch.distributions.Beta(beta, beta).sample([voxels.shape[0]]).to(voxels.device,dtype=voxels.dtype)
|
| 216 |
+
if select is None:
|
| 217 |
+
select = (torch.rand(voxels.shape[0]) <= s_thresh).to(voxels.device)
|
| 218 |
+
betas_shape = [-1] + [1]*(len(voxels.shape)-1)
|
| 219 |
+
voxels[select] = voxels[select] * betas[select].reshape(*betas_shape) + \
|
| 220 |
+
voxels_shuffle[select] * (1 - betas[select]).reshape(*betas_shape)
|
| 221 |
+
betas[~select] = 1
|
| 222 |
+
return voxels, perm, betas, select
|
| 223 |
+
|
| 224 |
+
def mixco_nce(preds, targs, temp=0.1, perm=None, betas=None, select=None, distributed=False,
|
| 225 |
+
accelerator=None, local_rank=None, bidirectional=True):
|
| 226 |
+
brain_clip = (preds @ targs.T)/temp
|
| 227 |
+
|
| 228 |
+
if perm is not None and betas is not None and select is not None:
|
| 229 |
+
probs = torch.diag(betas)
|
| 230 |
+
probs[torch.arange(preds.shape[0]).to(preds.device), perm] = 1 - betas
|
| 231 |
+
|
| 232 |
+
loss = -(brain_clip.log_softmax(-1) * probs).sum(-1).mean()
|
| 233 |
+
if bidirectional:
|
| 234 |
+
loss2 = -(brain_clip.T.log_softmax(-1) * probs.T).sum(-1).mean()
|
| 235 |
+
loss = (loss + loss2)/2
|
| 236 |
+
return loss
|
| 237 |
+
else:
|
| 238 |
+
loss = F.cross_entropy(brain_clip, torch.arange(brain_clip.shape[0]).to(brain_clip.device))
|
| 239 |
+
if bidirectional:
|
| 240 |
+
loss2 = F.cross_entropy(brain_clip.T, torch.arange(brain_clip.shape[0]).to(brain_clip.device))
|
| 241 |
+
loss = (loss + loss2)/2
|
| 242 |
+
return loss
|
| 243 |
+
|
| 244 |
+
def topk(similarities,labels,k=5):
|
| 245 |
+
if k > similarities.shape[0]:
|
| 246 |
+
k = similarities.shape[0]
|
| 247 |
+
topsum=0
|
| 248 |
+
for i in range(k):
|
| 249 |
+
topsum += torch.sum(torch.argsort(similarities,axis=1)[:,-(i+1)] == labels)/len(labels)
|
| 250 |
+
return topsum
|
| 251 |
+
|
| 252 |
+
def patchwise_cosine_similarity(latents1,latents2=None):
|
| 253 |
+
if latents2 is None:
|
| 254 |
+
latents_norm = latents1/latents1.norm(dim=-1, keepdim=True)
|
| 255 |
+
cos_sim = torch.bmm(latents_norm, latents_norm.permute(0,2,1))
|
| 256 |
+
else:
|
| 257 |
+
latents_norm1 = latents1/latents1.norm(dim=-1, keepdim=True)
|
| 258 |
+
latents_norm2 = latents2/latents2.norm(dim=-1, keepdim=True)
|
| 259 |
+
cos_sim = latents_norm1 @ latents_norm2.T
|
| 260 |
+
return cos_sim
|
| 261 |
+
|
| 262 |
+
def batchwise_cosine_similarity(Z,B):
|
| 263 |
+
Z = Z.flatten(1)
|
| 264 |
+
B = B.flatten(1).T
|
| 265 |
+
Z_norm = torch.linalg.norm(Z, dim=1, keepdim=True) # Size (n, 1).
|
| 266 |
+
B_norm = torch.linalg.norm(B, dim=0, keepdim=True) # Size (1, b).
|
| 267 |
+
cosine_similarity = ((Z @ B) / (Z_norm @ B_norm)).T
|
| 268 |
+
return cosine_similarity
|
| 269 |
+
|
| 270 |
+
def prenormed_batchwise_cosine_similarity(Z,B):
|
| 271 |
+
return (Z @ B.T).T
|
| 272 |
+
|
| 273 |
+
def torch_to_Image(x):
|
| 274 |
+
if x.ndim==4:
|
| 275 |
+
x=x[0]
|
| 276 |
+
return transforms.ToPILImage()(x)
|
| 277 |
+
|
| 278 |
+
def get_masking_ratio(current_epoch, total_epochs, start_masking_ratio, end_masking_ratio):
|
| 279 |
+
"""Returns the masking ratio for the current epochs. Linearly increase the masking ratio over the span of the training"""
|
| 280 |
+
return start_masking_ratio + (end_masking_ratio-start_masking_ratio) * ((current_epoch+1)/total_epochs)
|
| 281 |
+
|
| 282 |
+
def view_brain(data,cut_coords=None):
|
| 283 |
+
if torch.is_tensor(data):
|
| 284 |
+
data = data.numpy()
|
| 285 |
+
if data.ndim==5:
|
| 286 |
+
new_nii = nib.Nifti1Image((data[0,0].astype(np.float32)-.5)*2, np.eye(4))
|
| 287 |
+
elif data.ndim==4:
|
| 288 |
+
new_nii = nib.Nifti1Image((data[0].astype(np.float32)-.5)*2, np.eye(4))
|
| 289 |
+
elif data.ndim==3:
|
| 290 |
+
new_nii = nib.Nifti1Image((data.astype(np.float32)-.5)*2, np.eye(4))
|
| 291 |
+
else:
|
| 292 |
+
raise Exception("Check dimensionality of your brain data")
|
| 293 |
+
return plotting.view_img(new_nii, bg_img=None, cut_coords=cut_coords, vmax=1, cmap=plt.cm.gray, threshold=None)
|
| 294 |
+
|
| 295 |
+
def get_first_tar(train_urls):
|
| 296 |
+
if isinstance(train_urls, list):
|
| 297 |
+
# If train_urls is a list, get the first element
|
| 298 |
+
url = train_urls[0]
|
| 299 |
+
else:
|
| 300 |
+
# If train_urls is a string, treat it as the only element
|
| 301 |
+
url = train_urls
|
| 302 |
+
|
| 303 |
+
# Extract the first tar file using regular expression
|
| 304 |
+
match = re.search(r'\{(\d+)\.\.', url)
|
| 305 |
+
if match:
|
| 306 |
+
first_tar = match.group(1)
|
| 307 |
+
return f"/scratch/fmri_foundation_datasets/NSD_MNI_wds/{first_tar}.tar"
|
| 308 |
+
else:
|
| 309 |
+
return None
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
class VICRegHandler(nn.Module):
|
| 313 |
+
def __init__(self, in_dim, num_layers=3, act=nn.GELU, h=1024, out_dim=4096):
|
| 314 |
+
super().__init__()
|
| 315 |
+
self.projector = nn.Sequential(
|
| 316 |
+
nn.Linear(in_dim, h),
|
| 317 |
+
nn.LayerNorm(h),
|
| 318 |
+
act(),
|
| 319 |
+
nn.Linear(h, h),
|
| 320 |
+
nn.LayerNorm(h),
|
| 321 |
+
act(),
|
| 322 |
+
nn.Linear(h, out_dim),
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
def forward(self, x):
|
| 326 |
+
return self.projector(x)
|
| 327 |
+
|
| 328 |
+
@staticmethod
|
| 329 |
+
def filter_global_to_local(l, enc_mask, dec_mask):
|
| 330 |
+
'''Get the subset of global tokens that correspond to encoder mask only'''
|
| 331 |
+
comb_mask = enc_mask | dec_mask
|
| 332 |
+
comb_indices = torch.where(comb_mask)[0]
|
| 333 |
+
enc_indices = torch.where(enc_mask)[0]
|
| 334 |
+
# enc_set = set(enc_indices.cpu().tolist())
|
| 335 |
+
|
| 336 |
+
# new_mask = torch.zeros_like(comb_indices, dtype=bool)
|
| 337 |
+
# for i, idx in enumerate(comb_indices):
|
| 338 |
+
# if idx in enc_set:
|
| 339 |
+
# new_mask[i] = True
|
| 340 |
+
|
| 341 |
+
new_mask = torch.isin(comb_indices, enc_indices)
|
| 342 |
+
return l[:, new_mask]
|
| 343 |
+
|
| 344 |
+
@staticmethod
|
| 345 |
+
def vicreg_loss(l1, l2, gamma=1.0, lamda=25, mu=25, nu=1, rand_frac=0.2, use_vic_cls=True, eps=1e-4):
|
| 346 |
+
if use_vic_cls:
|
| 347 |
+
# always keep cls and pick a random set of tokens
|
| 348 |
+
rand_indices = torch.cat([torch.tensor([0]), 1+torch.randperm(l1.shape[1]-1)])[:int(rand_frac*l1.shape[1])]
|
| 349 |
+
else:
|
| 350 |
+
# drop cls tokens from loss calc
|
| 351 |
+
l1 = l1[:, 1:]
|
| 352 |
+
l2 = l2[:, 1:]
|
| 353 |
+
rand_indices = torch.randperm(l1.shape[1])[:int(rand_frac*l1.shape[1])]
|
| 354 |
+
|
| 355 |
+
std_l1 = torch.sqrt(l1.flatten(1).var(dim=0)+eps) # nxd
|
| 356 |
+
std_l2 = torch.sqrt(l2.flatten(1).var(dim=0)+eps) # nxd
|
| 357 |
+
var_loss = F.relu(gamma - std_l1).mean() + F.relu(gamma - std_l2).mean()
|
| 358 |
+
del std_l1, std_l2
|
| 359 |
+
|
| 360 |
+
sim_loss = F.mse_loss(l1, l2)
|
| 361 |
+
|
| 362 |
+
l1 = l1 - l1.mean(0, keepdim=True) # b,n,d
|
| 363 |
+
l2 = l2 - l2.mean(0, keepdim=True)
|
| 364 |
+
|
| 365 |
+
l1_sub = l1[:, rand_indices]
|
| 366 |
+
del l1
|
| 367 |
+
cov_l1 = torch.bmm(l1_sub.permute(1,2,0), l1_sub.permute(1,0,2))/(l1_sub.shape[0]-1) # 0.1*n,d,d
|
| 368 |
+
cov_loss = ((cov_l1**2).sum() - (torch.diagonal(cov_l1, dim1=1,dim2=2)**2).sum())/(l1_sub.shape[1]*l1_sub.shape[2])
|
| 369 |
+
del cov_l1, l1_sub
|
| 370 |
+
|
| 371 |
+
l2_sub = l2[:, rand_indices]
|
| 372 |
+
del l2
|
| 373 |
+
cov_l2 = torch.bmm(l2_sub.permute(1,2,0), l2_sub.permute(1,0,2))/(l2_sub.shape[0]-1)
|
| 374 |
+
cov_loss = cov_loss + ((cov_l2**2).sum() - (torch.diagonal(cov_l2, dim1=1,dim2=2)**2).sum())/(l2_sub.shape[1]*l2_sub.shape[2]) # div by nxd
|
| 375 |
+
del cov_l2, l2_sub
|
| 376 |
+
|
| 377 |
+
vic_loss = lamda * sim_loss + mu * var_loss + nu * cov_loss
|
| 378 |
+
|
| 379 |
+
return vic_loss
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
class SimCLRHandler(nn.Module):
|
| 383 |
+
def __init__(self, in_dim, num_layers=2, act=nn.GELU, out_dim=1024):
|
| 384 |
+
super().__init__()
|
| 385 |
+
self.projector = nn.Sequential(
|
| 386 |
+
nn.Linear(in_dim, in_dim),
|
| 387 |
+
act(),
|
| 388 |
+
nn.Linear(in_dim, max(in_dim,out_dim)),
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
def forward(self, x):
|
| 392 |
+
return self.projector(x)
|
| 393 |
+
|
| 394 |
+
@staticmethod
|
| 395 |
+
def simclr_loss(lats, temp=0.006):
|
| 396 |
+
logits = (nn.functional.normalize(lats.flatten(1),dim=-1) @
|
| 397 |
+
nn.functional.normalize(lats.flatten(1),dim=-1).T) / temp
|
| 398 |
+
|
| 399 |
+
labels = torch.diag_embed(
|
| 400 |
+
torch.ones(logits.shape[0] // 2), offset=logits.shape[0] // 2
|
| 401 |
+
) + torch.diag_embed(torch.ones(logits.shape[0] // 2), offset=-logits.shape[0] // 2)
|
| 402 |
+
labels = labels.to(lats.device)
|
| 403 |
+
|
| 404 |
+
mask = torch.ones_like(logits).bool()
|
| 405 |
+
torch.diagonal(mask).fill_(False)
|
| 406 |
+
|
| 407 |
+
labels = labels[mask].reshape(logits.shape[0], logits.shape[0]-1)
|
| 408 |
+
logits = logits[mask].reshape(*labels.shape)
|
| 409 |
+
|
| 410 |
+
contr_loss = -(logits.log_softmax(-1) * labels).sum(-1).mean()
|
| 411 |
+
|
| 412 |
+
return contr_loss
|
fMRI-foundation-model-old/flat/.ipynb_checkpoints/main-checkpoint.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import sys
|
| 4 |
+
import json
|
| 5 |
+
import yaml
|
| 6 |
+
import numpy as np
|
| 7 |
+
import copy
|
| 8 |
+
import math
|
| 9 |
+
import time
|
| 10 |
+
import random
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
import webdataset as wds
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
from torchvision import transforms
|
| 18 |
+
import utils
|
| 19 |
+
from flat_models import *
|
| 20 |
+
|
| 21 |
+
# tf32 data type is faster than standard float32
|
| 22 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 23 |
+
# following fixes a Conv3D CUDNN_NOT_SUPPORTED error
|
| 24 |
+
torch.backends.cudnn.benchmark = True
|
| 25 |
+
|
| 26 |
+
### Multi-GPU config ###
|
| 27 |
+
device_count = torch.cuda.device_count()
|
| 28 |
+
print(f"Number of available CUDA devices: {device_count}")
|
| 29 |
+
|
| 30 |
+
local_rank = os.getenv('LOCAL_RANK')
|
| 31 |
+
if local_rank is None:
|
| 32 |
+
local_rank = 0
|
| 33 |
+
else:
|
| 34 |
+
local_rank = int(local_rank)
|
| 35 |
+
print(f"LOCAL RANK={local_rank}")
|
| 36 |
+
|
| 37 |
+
num_devices = os.getenv('NUM_GPUS')
|
| 38 |
+
if num_devices is None:
|
| 39 |
+
num_devices = 1
|
| 40 |
+
else:
|
| 41 |
+
num_devices = int(num_devices)
|
| 42 |
+
print(f"NUM GPUS={num_devices}")
|
| 43 |
+
distributed = True if num_devices>1 else False
|
| 44 |
+
if distributed: assert device_count==num_devices
|
| 45 |
+
|
| 46 |
+
node = os.getenv('SLURM_NODEID')
|
| 47 |
+
if node is None:
|
| 48 |
+
node = 0
|
| 49 |
+
else:
|
| 50 |
+
node = int(node)
|
| 51 |
+
print(f"NODE={node}")
|
| 52 |
+
|
| 53 |
+
global_rank = os.getenv('RANK')
|
| 54 |
+
if global_rank is None:
|
| 55 |
+
global_rank = 0
|
| 56 |
+
else:
|
| 57 |
+
global_rank = int(global_rank)
|
| 58 |
+
print(f"GLOBAL RANK={global_rank}")
|
| 59 |
+
|
| 60 |
+
world_size = os.getenv('WORLD_SIZE')
|
| 61 |
+
if world_size is None:
|
| 62 |
+
world_size = 1
|
| 63 |
+
else:
|
| 64 |
+
world_size = int(world_size)
|
| 65 |
+
print(f"WORLD_SIZE={world_size}")
|
| 66 |
+
|
| 67 |
+
# Load parameters from yaml config
|
| 68 |
+
config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)
|
| 69 |
+
|
| 70 |
+
print("\n__CONFIG__")
|
| 71 |
+
for attribute_name in config.keys():
|
| 72 |
+
print(f"{attribute_name} = {config[attribute_name]}")
|
| 73 |
+
globals()[attribute_name] = config[f'{attribute_name}']
|
| 74 |
+
print("\n")
|
| 75 |
+
|
| 76 |
+
# Create outdir for ckpt and config.yaml
|
| 77 |
+
outdir = os.path.abspath(f'checkpoints/{model_name}')
|
| 78 |
+
print("outdir", outdir)
|
| 79 |
+
|
| 80 |
+
# Load previous config.yaml if available
|
| 81 |
+
if os.path.exists(f"{outdir}/config.yaml"):
|
| 82 |
+
config = yaml.load(open(f"{outdir}/config.yaml", 'r'), Loader=yaml.FullLoader)
|
| 83 |
+
print(f"Loaded config.yaml from ckpt folder {outdir}")
|
| 84 |
+
|
| 85 |
+
# create global variables from the config
|
| 86 |
+
print("\n__REPLACING_CONFIG__")
|
| 87 |
+
for attribute_name in config.keys():
|
| 88 |
+
print(f"{attribute_name} = {config[attribute_name]}")
|
| 89 |
+
globals()[attribute_name] = config[f'{attribute_name}']
|
| 90 |
+
print("\n")
|
| 91 |
+
|
| 92 |
+
data_type = torch.float32 # change depending on your mixed_precision
|
| 93 |
+
global_batch_size = batch_size * world_size
|
| 94 |
+
|
| 95 |
+
# FSDP Setup
|
| 96 |
+
if distributed:
|
| 97 |
+
import torch.distributed as dist
|
| 98 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 99 |
+
from torch.distributed.fsdp.api import BackwardPrefetch, CPUOffload, ShardingStrategy
|
| 100 |
+
import functools
|
| 101 |
+
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy
|
| 102 |
+
print(f"setting device to cuda:{local_rank}")
|
| 103 |
+
torch.cuda.set_device(local_rank)
|
| 104 |
+
device = torch.device('cuda',local_rank)
|
| 105 |
+
dist.init_process_group("nccl", rank=global_rank, world_size=world_size)
|
| 106 |
+
print(f"\nSuccessfully set cuda:{local_rank} | global_rank{global_rank} | node{node}")
|
| 107 |
+
dist.barrier()
|
| 108 |
+
print(f"global_rank{global_rank} passed barrier")
|
| 109 |
+
else:
|
| 110 |
+
device = torch.device('cuda')
|
| 111 |
+
|
| 112 |
+
print("PID of this process =",os.getpid())
|
| 113 |
+
print("device =", device, "distributed =",distributed, "num_devices =", num_devices, "local rank =", local_rank, "world size =", world_size, "data_type =", data_type)
|
| 114 |
+
|
| 115 |
+
# seed all random functions
|
| 116 |
+
utils.seed_everything(seed + global_rank)
|
| 117 |
+
|
| 118 |
+
from util.hcp_flat import load_hcp_flat_mask
|
| 119 |
+
from util.hcp_flat import create_hcp_flat
|
| 120 |
+
from util.losses import *
|
| 121 |
+
import util.visualize as vis
|
| 122 |
+
|
| 123 |
+
if utils.is_interactive(): # Use less samples per epoch for debugging
|
| 124 |
+
num_samples_per_epoch = 2000
|
| 125 |
+
test_num_samples_per_epoch = 2000
|
| 126 |
+
|
| 127 |
+
model = mae_vit_small_fmri(
|
| 128 |
+
decoder_embed_dim=decoder_embed_dim,
|
| 129 |
+
t_patch_size=t_patch_size,
|
| 130 |
+
pred_t_dim=pred_t_dim,
|
| 131 |
+
decoder_depth=4,
|
| 132 |
+
cls_embed=cls_embed,
|
| 133 |
+
norm_pix_loss=norm_pix_loss,
|
| 134 |
+
no_qkv_bias=no_qkv_bias,
|
| 135 |
+
sep_pos_embed=sep_pos_embed,
|
| 136 |
+
trunc_init=trunc_init,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
if use_contrastive_loss:
|
| 140 |
+
model.simclr_handler = SimCLRHandler(model.embed_dim).to(device)
|
| 141 |
+
if use_vic_loss:
|
| 142 |
+
model.vicreg_handler = VICRegHandler(model.embed_dim).to(device)
|
| 143 |
+
|
| 144 |
+
# state = torch.load("checkpoints/checkpoint-00099.pth", map_location="cpu")
|
| 145 |
+
# model.load_state_dict(state["model"])
|
| 146 |
+
|
| 147 |
+
num_batches = num_samples_per_epoch // (num_devices * batch_size)
|
| 148 |
+
test_num_batches = test_num_samples_per_epoch // (num_devices * batch_size)
|
| 149 |
+
print("num_batches", num_batches)
|
| 150 |
+
print("test_num_batches", test_num_batches)
|
| 151 |
+
|
| 152 |
+
## Train ##
|
| 153 |
+
train_dataset = create_hcp_flat(root=hcp_flat_path,
|
| 154 |
+
training=True, frames=num_frames)
|
| 155 |
+
train_dl = wds.WebLoader(
|
| 156 |
+
train_dataset.batched(batch_size, partial=False),
|
| 157 |
+
batch_size=None,
|
| 158 |
+
shuffle=False,
|
| 159 |
+
num_workers=num_workers,
|
| 160 |
+
pin_memory=True,
|
| 161 |
+
)
|
| 162 |
+
train_dl = train_dl.with_epoch(num_batches)
|
| 163 |
+
|
| 164 |
+
## Test ##
|
| 165 |
+
test_dataset = create_hcp_flat(root=hcp_flat_path,
|
| 166 |
+
training=False, frames=num_frames)
|
| 167 |
+
test_dl = wds.WebLoader(
|
| 168 |
+
test_dataset.batched(batch_size, partial=False),
|
| 169 |
+
batch_size=None,
|
| 170 |
+
shuffle=False,
|
| 171 |
+
num_workers=num_workers,
|
| 172 |
+
pin_memory=True,
|
| 173 |
+
)
|
| 174 |
+
test_dl = test_dl.with_epoch(test_num_batches)
|
| 175 |
+
|
| 176 |
+
print(f"\nChecking distributed setup on global_rank {global_rank}...")
|
| 177 |
+
from util.video_vit import Attention
|
| 178 |
+
if distributed:
|
| 179 |
+
my_auto_wrap_policy = functools.partial(
|
| 180 |
+
transformer_auto_wrap_policy,
|
| 181 |
+
transformer_layer_cls={
|
| 182 |
+
Attention, # <--- Your Transformer layer class
|
| 183 |
+
},
|
| 184 |
+
)
|
| 185 |
+
print(f"\nPrepping FSDP on {global_rank} {node}...\n")
|
| 186 |
+
model = model.to(device)
|
| 187 |
+
model = FSDP(
|
| 188 |
+
model,
|
| 189 |
+
sharding_strategy=ShardingStrategy.HYBRID_SHARD,
|
| 190 |
+
auto_wrap_policy=my_auto_wrap_policy,
|
| 191 |
+
use_orig_params=False,
|
| 192 |
+
cpu_offload=None, #CPUOffload(offload_params=True)
|
| 193 |
+
sync_module_states=True,
|
| 194 |
+
limit_all_gathers=True, # See https://github.com/pytorch/pytorch/issues/91165
|
| 195 |
+
device_id=device,
|
| 196 |
+
)
|
| 197 |
+
print(f"\nSuccessfully loaded FSDP model to device on global_rank {global_rank}\n")
|
| 198 |
+
dist.barrier()
|
| 199 |
+
else:
|
| 200 |
+
print(f"\nNot training distributed! global_rank {global_rank}")
|
| 201 |
+
model.to(device)
|
| 202 |
+
|
| 203 |
+
no_decay = ["bias", "LayerNorm.bias", "LayerNorm.weight"]
|
| 204 |
+
opt_grouped_parameters = [
|
| 205 |
+
{'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.05},
|
| 206 |
+
{'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},
|
| 207 |
+
]
|
| 208 |
+
|
| 209 |
+
lr = base_lr * global_batch_size / 256
|
| 210 |
+
print(f"multiply base lr {base_lr} by effective batch size {global_batch_size}")
|
| 211 |
+
print(f"lr = {lr}")
|
| 212 |
+
|
| 213 |
+
optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=lr, betas=(0.9, 0.95))
|
| 214 |
+
|
| 215 |
+
def adjust_learning_rate(optimizer, epoch, warmup_epochs=5, min_lr=0.0):
|
| 216 |
+
"""Decay the learning rate with half-cycle cosine after warmup"""
|
| 217 |
+
if epoch < warmup_epochs:
|
| 218 |
+
lr_ = lr * epoch / warmup_epochs
|
| 219 |
+
else:
|
| 220 |
+
lr_ = min_lr + (lr - min_lr) * 0.5 * (
|
| 221 |
+
1.0
|
| 222 |
+
+ math.cos(
|
| 223 |
+
math.pi
|
| 224 |
+
* (epoch - warmup_epochs)
|
| 225 |
+
/ (num_epochs - warmup_epochs)
|
| 226 |
+
)
|
| 227 |
+
)
|
| 228 |
+
for param_group in optimizer.param_groups:
|
| 229 |
+
param_group["lr"] = lr_
|
| 230 |
+
return lr_
|
| 231 |
+
|
| 232 |
+
print("\nDone with model preparations!")
|
| 233 |
+
num_params = utils.count_params(model)
|
| 234 |
+
|
| 235 |
+
def save_ckpt(model,tag="last"):
|
| 236 |
+
if distributed: dist.barrier()
|
| 237 |
+
model_states = model.state_dict()
|
| 238 |
+
if global_rank == 0:
|
| 239 |
+
os.makedirs(outdir,exist_ok=True)
|
| 240 |
+
ckpt_path = outdir+f'/{tag}.pth'
|
| 241 |
+
|
| 242 |
+
if tag == "last" and os.path.exists(ckpt_path):
|
| 243 |
+
shutil.copyfile(os.path.join(outdir, f'{tag}.pth'), os.path.join(outdir, f'{tag}_old.pth'))
|
| 244 |
+
# print(f'saving {ckpt_path}',flush=True)
|
| 245 |
+
if tag=='last':
|
| 246 |
+
torch.save({
|
| 247 |
+
'epoch': epoch,
|
| 248 |
+
'model_state_dict': model_states,
|
| 249 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 250 |
+
}, ckpt_path)
|
| 251 |
+
else:
|
| 252 |
+
torch.save({
|
| 253 |
+
'epoch': epoch,
|
| 254 |
+
'model_state_dict': model_states,
|
| 255 |
+
}, ckpt_path)
|
| 256 |
+
|
| 257 |
+
if tag == "last" and os.path.exists(os.path.join(outdir, f'{tag}_old.pth')):
|
| 258 |
+
os.remove(os.path.join(outdir, f'{tag}_old.pth'))
|
| 259 |
+
print(f"\n---saved {ckpt_path}!---\n")
|
| 260 |
+
|
| 261 |
+
# save the config.yaml
|
| 262 |
+
if not os.path.exists(f"{outdir}/config.yaml"):
|
| 263 |
+
with open(f"{outdir}/config.yaml", 'w') as file:
|
| 264 |
+
yaml.dump(config, file)
|
| 265 |
+
print(f"saved {outdir}/config.yaml!")
|
| 266 |
+
|
| 267 |
+
# auto resume
|
| 268 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')) or os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 269 |
+
if os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 270 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')):
|
| 271 |
+
# this is corrupted
|
| 272 |
+
os.remove(os.path.join(outdir, f'last.pth'))
|
| 273 |
+
# set last_old as last
|
| 274 |
+
shutil.move(os.path.join(outdir, f'last_old.pth'), os.path.join(outdir, f'last.pth'))
|
| 275 |
+
|
| 276 |
+
ckpt_path = os.path.join(outdir, 'last.pth')
|
| 277 |
+
resume_from_ckpt = True
|
| 278 |
+
|
| 279 |
+
epoch = 0
|
| 280 |
+
if resume_from_ckpt:
|
| 281 |
+
print("\n---resuming from ckpt_path---\n", ckpt_path)
|
| 282 |
+
checkpoint = torch.load(ckpt_path, map_location=device)
|
| 283 |
+
epoch = checkpoint['epoch']+1
|
| 284 |
+
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
|
| 285 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 286 |
+
# total_steps_done = epoch*num_iterations_per_epoch
|
| 287 |
+
# for _ in range(total_steps_done):
|
| 288 |
+
# lr_scheduler.step()
|
| 289 |
+
del checkpoint
|
| 290 |
+
torch.cuda.empty_cache()
|
| 291 |
+
|
| 292 |
+
if utils.is_interactive():
|
| 293 |
+
wandb_log = False
|
| 294 |
+
ckpt_saving = False
|
| 295 |
+
if local_rank==0 and wandb_log: # only use main process for wandb logging
|
| 296 |
+
import wandb
|
| 297 |
+
wandb_project = 'fMRI-foundation-model'
|
| 298 |
+
print(f"wandb {wandb_project} run {model_name}")
|
| 299 |
+
# need to configure wandb beforehand in terminal with "wandb init"!
|
| 300 |
+
wandb_config = {
|
| 301 |
+
"model_name": model_name,
|
| 302 |
+
"global_batch_size": global_batch_size,
|
| 303 |
+
"batch_size": batch_size,
|
| 304 |
+
"num_epochs": num_epochs,
|
| 305 |
+
"cls_embed": cls_embed,
|
| 306 |
+
"norm_pix_loss": norm_pix_loss,
|
| 307 |
+
"t_patch_size": t_patch_size,
|
| 308 |
+
"pred_t_dim": pred_t_dim,
|
| 309 |
+
"mask_ratio": mask_ratio,
|
| 310 |
+
"num_frames": num_frames,
|
| 311 |
+
"sep_pos_embed": sep_pos_embed,
|
| 312 |
+
"decoder_embed_dim": decoder_embed_dim,
|
| 313 |
+
"use_contrastive_loss": use_contrastive_loss,
|
| 314 |
+
"num_params": num_params,
|
| 315 |
+
"base_lr": base_lr,
|
| 316 |
+
"lr": lr,
|
| 317 |
+
"num_samples_per_epoch": num_samples_per_epoch,
|
| 318 |
+
"test_num_samples_per_epoch": test_num_samples_per_epoch,
|
| 319 |
+
"num_epochs": num_epochs,
|
| 320 |
+
"grad_clip": grad_clip,
|
| 321 |
+
"ckpt_interval": ckpt_interval,
|
| 322 |
+
"ckpt_saving": ckpt_saving,
|
| 323 |
+
"print_interval": print_interval,
|
| 324 |
+
"seed": seed,
|
| 325 |
+
"distributed": distributed,
|
| 326 |
+
"num_devices": num_devices,
|
| 327 |
+
"world_size": world_size,
|
| 328 |
+
}
|
| 329 |
+
print("wandb_config:\n",wandb_config)
|
| 330 |
+
print("wandb_id:",model_name)
|
| 331 |
+
wandb.init(
|
| 332 |
+
id=model_name,
|
| 333 |
+
project=wandb_project,
|
| 334 |
+
name=model_name,
|
| 335 |
+
config=wandb_config,
|
| 336 |
+
resume="allow",
|
| 337 |
+
)
|
| 338 |
+
else:
|
| 339 |
+
wandb_log = False
|
| 340 |
+
|
| 341 |
+
# lrs, train_losses, test_losses = [], [], []
|
| 342 |
+
# train_losses1, train_losses2, train_losses3, train_losses4 = [], [], [], []
|
| 343 |
+
|
| 344 |
+
epoch = 0
|
| 345 |
+
lrs, train_losses, recon_losses, contrastive_losses, vic_losses = [], [], [], [], []
|
| 346 |
+
cos_sim_encoder_output, cos_sim_decoder_output, cos_sim_encoder_output_patchwise = [], [], []
|
| 347 |
+
probe_losses, probe_accs, test_losses, test_accs = [], [], [], []
|
| 348 |
+
cos_sim_encoder_output_patchwise_test, cos_sim_encoder_output_test = [], []
|
| 349 |
+
|
| 350 |
+
mse = nn.MSELoss()
|
| 351 |
+
l1 = nn.L1Loss()
|
| 352 |
+
crossentropy = nn.CrossEntropyLoss()
|
| 353 |
+
|
| 354 |
+
if use_contrastive_loss:
|
| 355 |
+
contrastive_temps = utils.cosine_anneal(0.004, 0.0075, num_epochs)
|
| 356 |
+
|
| 357 |
+
grad_scaler = torch.cuda.amp.GradScaler(enabled=True)
|
| 358 |
+
progress_bar = tqdm(range(epoch, num_epochs), disable=local_rank!=0, desc="Overall")
|
| 359 |
+
for epoch in progress_bar:
|
| 360 |
+
model.train()
|
| 361 |
+
for train_i, batch in enumerate(train_dl):
|
| 362 |
+
optimizer.zero_grad()
|
| 363 |
+
adjust_learning_rate(optimizer, train_i / num_batches + epoch)
|
| 364 |
+
|
| 365 |
+
input_func = batch[1]
|
| 366 |
+
|
| 367 |
+
input_func = input_func.to(dtype=data_type, device=device, non_blocking=True)
|
| 368 |
+
if len(input_func.shape) == 6:
|
| 369 |
+
b, r, c, t, h, w = input_func.shape
|
| 370 |
+
input_func = input_func.reshape(b * r, c, t, h, w)
|
| 371 |
+
|
| 372 |
+
ids_shuffle, ids_restore = get_ids_shuffle(input_func.shape[0], input_func.device, model)
|
| 373 |
+
|
| 374 |
+
# adjust number to keep relative to image mask
|
| 375 |
+
if model.img_mask is not None:
|
| 376 |
+
len_keep = int(model.patch_embed.t_grid_size * model.n_mask_patches * (1 - mask_ratio))
|
| 377 |
+
else:
|
| 378 |
+
len_keep = int(ids_restore.shape[1] * (1 - mask_ratio))
|
| 379 |
+
|
| 380 |
+
# keep the first subset
|
| 381 |
+
ids_keep = ids_shuffle[:, :len_keep]
|
| 382 |
+
lats = model.forward_encoder_with_mask(input_func, ids_keep)
|
| 383 |
+
|
| 384 |
+
if use_decoder:
|
| 385 |
+
mask = torch.ones_like(ids_restore)
|
| 386 |
+
mask[:, :len_keep] = 0
|
| 387 |
+
# unshuffle to get the binary mask
|
| 388 |
+
mask = torch.gather(mask, dim=1, index=ids_restore)
|
| 389 |
+
|
| 390 |
+
pred = model.forward_decoder(lats, ids_restore)
|
| 391 |
+
recon_loss = model.forward_loss(input_func, pred, mask)
|
| 392 |
+
|
| 393 |
+
recon_losses.append(recon_loss.item())
|
| 394 |
+
loss = recon_loss
|
| 395 |
+
else:
|
| 396 |
+
recon_loss = torch.nan
|
| 397 |
+
recon_losses.append(recon_loss)
|
| 398 |
+
loss = 0
|
| 399 |
+
|
| 400 |
+
if use_contrastive_loss and not use_vic_loss:
|
| 401 |
+
ids_keep2 = ids_shuffle[:, :2*len_keep]
|
| 402 |
+
lats2 = model.forward_encoder_with_mask(input_func, ids_keep2)
|
| 403 |
+
|
| 404 |
+
enc_cls_token = lats[:, :1]
|
| 405 |
+
enc_cls_token2 = lats2[:,:1]
|
| 406 |
+
|
| 407 |
+
temp = contrastive_temps[epoch]
|
| 408 |
+
|
| 409 |
+
all_cls = torch.cat([enc_cls_token, enc_cls_token2], dim=0)
|
| 410 |
+
all_cls_proj = model.simclr_handler(all_cls)
|
| 411 |
+
|
| 412 |
+
contr_loss = SimCLRHandler.simclr_loss(all_cls_proj, temp)
|
| 413 |
+
|
| 414 |
+
contrastive_losses.append(contr_loss.item())
|
| 415 |
+
loss += (contr_loss * contrastive_loss_weight)
|
| 416 |
+
# new loss
|
| 417 |
+
elif use_vic_loss:
|
| 418 |
+
ids_keep2 = ids_shuffle[:, :2*len_keep]
|
| 419 |
+
lats2 = model.forward_encoder_with_mask(input_func, ids_keep2)
|
| 420 |
+
|
| 421 |
+
enc_cls_token = lats[:, :1]
|
| 422 |
+
enc_cls_token2 = lats2[:,:1]
|
| 423 |
+
|
| 424 |
+
l1 = lats
|
| 425 |
+
l2 = lats2[:, :lats.shape[1]]
|
| 426 |
+
|
| 427 |
+
l1_proj = model.vicreg_handler(l1)
|
| 428 |
+
l2_proj = model.vicreg_handler(l2)
|
| 429 |
+
|
| 430 |
+
vic_loss = VICRegHandler.vicreg_loss(l1_proj, l2_proj, gamma=gamma, lamda=lamda, mu=mu, nu=nu,
|
| 431 |
+
rand_frac=rand_frac, use_vic_cls=use_vic_cls)
|
| 432 |
+
|
| 433 |
+
if use_contrastive_loss:
|
| 434 |
+
temp = contrastive_temps[epoch]
|
| 435 |
+
all_cls = torch.cat([enc_cls_token, enc_cls_token2], dim=0)
|
| 436 |
+
all_cls_proj = model.simclr_handler(all_cls)
|
| 437 |
+
|
| 438 |
+
contr_loss = SimCLRHandler.simclr_loss(all_cls_proj, temp)
|
| 439 |
+
contrastive_losses.append(contr_loss.item())
|
| 440 |
+
else:
|
| 441 |
+
contr_loss = 0
|
| 442 |
+
contrastive_losses.append(0)
|
| 443 |
+
|
| 444 |
+
vic_losses.append(vic_loss.item())
|
| 445 |
+
loss += (contr_loss * contrastive_loss_weight + vic_loss * vic_loss_weight)
|
| 446 |
+
else:
|
| 447 |
+
vic_losses.append(0)
|
| 448 |
+
contrastive_losses.append(0)
|
| 449 |
+
|
| 450 |
+
grad_scaler.scale(loss).backward()
|
| 451 |
+
grad_scaler.unscale_(optimizer)
|
| 452 |
+
if grad_clip:
|
| 453 |
+
nn.utils.clip_grad_norm_(model.parameters(), max_norm=grad_clip)
|
| 454 |
+
grad_scaler.step(optimizer)
|
| 455 |
+
grad_scaler.update()
|
| 456 |
+
torch.cuda.synchronize()
|
| 457 |
+
|
| 458 |
+
cos_sim_encoder_output_patchwise.append(utils.patchwise_cosine_similarity(lats)[~torch.eye(lats.shape[1], dtype=bool)[None].expand(lats.shape[0],-1,-1)].mean().item())
|
| 459 |
+
cos_sim_encoder_output.append(utils.batchwise_cosine_similarity(lats.flatten(1)/1e3,lats.flatten(1)/1e3)[~torch.eye(len(lats),dtype=torch.bool)].mean().item())
|
| 460 |
+
if use_decoder:
|
| 461 |
+
cos_sim_decoder_output.append(utils.batchwise_cosine_similarity(pred, pred)[~torch.eye(len(pred),dtype=torch.bool)].mean().item())
|
| 462 |
+
|
| 463 |
+
lrs.append(optimizer.param_groups[0]["lr"])
|
| 464 |
+
train_losses.append(loss.item())
|
| 465 |
+
|
| 466 |
+
if ((train_i%print_interval)==0 or (train_i==num_batches-1)) and train_i>0:
|
| 467 |
+
print(f"Ep. {epoch} | loss {np.mean(train_losses[-print_interval:]):.3f} | lr {optimizer.param_groups[0]['lr']} | {train_i}/{num_batches}")
|
| 468 |
+
if wandb_log:
|
| 469 |
+
logs = {"train/loss": np.mean(train_losses[-print_interval:])}
|
| 470 |
+
# epoch_1000x as x-axis calibrates different curves when batch size changes
|
| 471 |
+
epoch_1000x = int((train_i / num_batches + epoch) * 1000)
|
| 472 |
+
wandb.log(logs, step=epoch_1000x)
|
| 473 |
+
|
| 474 |
+
if utils.is_interactive() or wandb_log:
|
| 475 |
+
print(f"Ep. {epoch} | loss {np.mean(train_losses[-print_interval:]):.3f} | lr {optimizer.param_groups[0]['lr']} | {train_i}/{num_batches}")
|
| 476 |
+
with torch.no_grad():
|
| 477 |
+
if norm_pix_loss:
|
| 478 |
+
normed_input_func, patch_info = model.patchify(input_func, alter_patch_info=False, return_patch_info=True)
|
| 479 |
+
target_mean = normed_input_func.mean(dim=-1, keepdim=True)
|
| 480 |
+
target_var = normed_input_func.var(dim=-1, keepdim=True)
|
| 481 |
+
normed_input_func = (normed_input_func - target_mean) / (target_var + 1.0e-6) ** 0.5
|
| 482 |
+
normed_input_func = model.unpatchify(normed_input_func, patch_info=patch_info)
|
| 483 |
+
|
| 484 |
+
vis_out = vis.plot_mask_pred(
|
| 485 |
+
model, normed_input_func, pred, mask,
|
| 486 |
+
mean=0.5, std=0.2,
|
| 487 |
+
)
|
| 488 |
+
else:
|
| 489 |
+
vis_out = vis.plot_mask_pred(
|
| 490 |
+
model, input_func, pred, mask,
|
| 491 |
+
mean=0.5, std=0.2,
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
if wandb_log:
|
| 495 |
+
logs = {"train/mask_pred": wandb.Image(vis_out)}
|
| 496 |
+
epoch_1000x = int((train_i / num_batches + epoch) * 1000)
|
| 497 |
+
wandb.log(logs, step=epoch_1000x)
|
| 498 |
+
else:
|
| 499 |
+
display(vis_out)
|
| 500 |
+
|
| 501 |
+
logs = {
|
| 502 |
+
"train/loss": np.mean(train_losses[-(train_i + 1) :]),
|
| 503 |
+
"train/recon_losses": np.mean(recon_losses[-(train_i + 1) :]),
|
| 504 |
+
"train/contrastive_losses": np.mean(contrastive_losses[-(train_i + 1) :]),
|
| 505 |
+
"train/vic_losses": np.mean(vic_losses[-(train_i + 1) :]),
|
| 506 |
+
"train/num_steps": len(recon_losses),
|
| 507 |
+
"train/cos_sim_encoder_output": np.mean(cos_sim_encoder_output[-(train_i + 1) :]),
|
| 508 |
+
"train/cos_sim_decoder_output": np.mean(cos_sim_decoder_output[-(train_i + 1) :]) if use_decoder else np.nan,
|
| 509 |
+
"train/cos_sim_encoder_output_patchwise": np.mean(cos_sim_encoder_output_patchwise[-(train_i + 1) :]),
|
| 510 |
+
"lr": np.mean(lrs[-(train_i + 1) :]),
|
| 511 |
+
"epoch": epoch,
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
# Evaluate performance on held-out test dataset
|
| 515 |
+
model.eval()
|
| 516 |
+
with torch.no_grad():
|
| 517 |
+
for test_i, batch in enumerate(test_dl):
|
| 518 |
+
input_func = batch[1]
|
| 519 |
+
|
| 520 |
+
input_func = input_func.to(dtype=data_type, device=device, non_blocking=True)
|
| 521 |
+
if len(input_func.shape) == 6:
|
| 522 |
+
b, r, c, t, h, w = input_func.shape
|
| 523 |
+
input_func = input_func.reshape(b * r, c, t, h, w)
|
| 524 |
+
|
| 525 |
+
if not use_contrastive_loss:
|
| 526 |
+
loss, pred, mask, latent = model(input_func, mask_ratio=mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 527 |
+
else:
|
| 528 |
+
loss1, loss2, loss3, pred1, pred2, mask1, mask2, true_mask, latent1, latent2 = model(input_func, mask_ratio=mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 529 |
+
pred, mask, latent = pred1, mask1, latent1
|
| 530 |
+
|
| 531 |
+
# contrastive loss
|
| 532 |
+
temp = contrastive_temps[epoch]
|
| 533 |
+
logits = (nn.functional.normalize(latent1[:,:1].flatten(1),dim=-1) @
|
| 534 |
+
nn.functional.normalize(latent2[:,:1].flatten(1),dim=-1).T) / temp
|
| 535 |
+
labels = torch.arange(len(logits)).long().to(device)
|
| 536 |
+
contr_loss = (crossentropy(logits, labels) + crossentropy(logits.T, labels)) / 2
|
| 537 |
+
|
| 538 |
+
loss = loss1 + loss2 + loss3 + contr_loss
|
| 539 |
+
|
| 540 |
+
test_losses.append(loss.item())
|
| 541 |
+
|
| 542 |
+
cos_sim_encoder_output_patchwise_test.append(utils.patchwise_cosine_similarity(latent)[~torch.eye(latent.shape[1], dtype=bool)[None].expand(latent.shape[0],-1,-1)].mean().item())
|
| 543 |
+
cos_sim_encoder_output_test.append(utils.batchwise_cosine_similarity(latent.flatten(1)/1e3, latent.flatten(1)/1e3)[~torch.eye(len(latent),dtype=torch.bool)].mean().item())
|
| 544 |
+
|
| 545 |
+
if test_i%print_interval==0 and test_i>0:
|
| 546 |
+
print(f"Test | loss {np.mean(test_losses[-print_interval:]):.3f} | {test_i}/{test_num_batches}")
|
| 547 |
+
|
| 548 |
+
print(f"Test | iter {test_i} | loss {np.mean(test_losses[-test_i:]):.3f}")
|
| 549 |
+
if wandb_log:
|
| 550 |
+
logs.update({
|
| 551 |
+
"test/loss": np.mean(test_losses[-test_i:]),
|
| 552 |
+
"test/cos_sim_encoder_output": np.mean(cos_sim_encoder_output_test[-(test_i + 1) :]),
|
| 553 |
+
"test/cos_sim_encoder_output_patchwise": np.mean(cos_sim_encoder_output_patchwise_test[-(test_i + 1) :]),
|
| 554 |
+
})
|
| 555 |
+
wandb.log(logs)
|
| 556 |
+
|
| 557 |
+
# Plot progress (first sample in batch)
|
| 558 |
+
if utils.is_interactive() or wandb_log:
|
| 559 |
+
with torch.no_grad():
|
| 560 |
+
if norm_pix_loss:
|
| 561 |
+
normed_input_func, patch_info = model.patchify(input_func, alter_patch_info=False, return_patch_info=True)
|
| 562 |
+
target_mean = normed_input_func.mean(dim=-1, keepdim=True)
|
| 563 |
+
target_var = normed_input_func.var(dim=-1, keepdim=True)
|
| 564 |
+
normed_input_func = (normed_input_func - target_mean) / (target_var + 1.0e-6) ** 0.5
|
| 565 |
+
normed_input_func = model.unpatchify(normed_input_func, patch_info=patch_info)
|
| 566 |
+
|
| 567 |
+
vis_out = vis.plot_mask_pred(
|
| 568 |
+
model, normed_input_func, pred, mask,
|
| 569 |
+
mean=0.5, std=0.2,
|
| 570 |
+
)
|
| 571 |
+
else:
|
| 572 |
+
vis_out = vis.plot_mask_pred(
|
| 573 |
+
model, input_func, pred, mask,
|
| 574 |
+
mean=0.5, std=0.2,
|
| 575 |
+
)
|
| 576 |
+
|
| 577 |
+
if wandb_log:
|
| 578 |
+
logs = {"test/mask_pred": wandb.Image(vis_out)}
|
| 579 |
+
wandb.log(logs)
|
| 580 |
+
else:
|
| 581 |
+
display(vis_out)
|
| 582 |
+
|
| 583 |
+
# Save model checkpoint
|
| 584 |
+
if ckpt_saving and epoch>0 and ((epoch % ckpt_interval == 0) or (epoch==num_epochs-1)):
|
| 585 |
+
save_ckpt(model,f"epoch{epoch}")
|
| 586 |
+
|
| 587 |
+
# wait for other GPUs to catch up if needed
|
| 588 |
+
if distributed: dist.barrier()
|
| 589 |
+
|
| 590 |
+
# close any open plots
|
| 591 |
+
plt.close()
|
| 592 |
+
|
| 593 |
+
if distributed: dist.destroy_process_group()
|
fMRI-foundation-model-old/flat/.ipynb_checkpoints/main2-checkpoint.ipynb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [],
|
| 3 |
+
"metadata": {},
|
| 4 |
+
"nbformat": 4,
|
| 5 |
+
"nbformat_minor": 5
|
| 6 |
+
}
|
fMRI-foundation-model-old/flat/__pycache__/flat_models.cpython-310.pyc
ADDED
|
Binary file (15.2 kB). View file
|
|
|
fMRI-foundation-model-old/flat/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (9.53 kB). View file
|
|
|
fMRI-foundation-model-old/flat/accel.slurm
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH --account=fmri
|
| 3 |
+
#SBATCH --partition=p5
|
| 4 |
+
#SBATCH --job-name=found
|
| 5 |
+
#SBATCH --ntasks-per-node=1
|
| 6 |
+
#SBATCH --nodes=1
|
| 7 |
+
#SBATCH --gres=gpu:1
|
| 8 |
+
#SBATCH --gpus-per-task=1 # Set to equal gres=gpu:#!
|
| 9 |
+
#SBATCH --cpus-per-task=176 # 40 / 80 / 176 distributed across node
|
| 10 |
+
#SBATCH --time=23:00:00 # total run time limit (HH:MM:SS)
|
| 11 |
+
#SBATCH -e slurms/%j.err # first create a "slurms" folder in current directory to store logs
|
| 12 |
+
#SBATCH -o slurms/%j.out
|
| 13 |
+
#SBATCH --comment=medarc
|
| 14 |
+
#SBATCH --no-requeue
|
| 15 |
+
|
| 16 |
+
export NUM_GPUS=1 # Set to equal gres=gpu:#!
|
| 17 |
+
echo NUM_GPUS=$NUM_GPUS
|
| 18 |
+
|
| 19 |
+
source ~/.bashrc
|
| 20 |
+
|
| 21 |
+
cd /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/
|
| 22 |
+
jupyter nbconvert main.ipynb --to python
|
| 23 |
+
if [ $? -ne 0 ]; then
|
| 24 |
+
echo "Error: Conversion of ipynb to Python failed. Exiting."
|
| 25 |
+
exit 1
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
# Make sure another job doesnt use same port, here using random number
|
| 29 |
+
export MASTER_PORT=$((RANDOM % (19000 - 11000 + 1) + 11000))
|
| 30 |
+
export HOSTNAMES=$(scontrol show hostnames "$SLURM_JOB_NODELIST")
|
| 31 |
+
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
|
| 32 |
+
echo MASTER_ADDR=${MASTER_ADDR}
|
| 33 |
+
echo MASTER_PORT=${MASTER_PORT}
|
| 34 |
+
echo WORLD_SIZE=$((${SLURM_NNODES} * ${NUM_GPUS}))
|
| 35 |
+
|
| 36 |
+
srun torchrun \
|
| 37 |
+
--nproc_per_node=$NUM_GPUS \
|
| 38 |
+
--nnodes=$SLURM_NNODES \
|
| 39 |
+
main.py
|
| 40 |
+
|
| 41 |
+
if [ $? -ne 0 ]; then
|
| 42 |
+
echo "Error: srun command failed. Please check the logs for more details."
|
| 43 |
+
exit 1
|
| 44 |
+
fi
|
fMRI-foundation-model-old/flat/config.yaml
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_name: "flat_NORM_contrastive_"
|
| 2 |
+
hcp_flat_path: "/weka/proj-medarc/shared/hcp_flat"
|
| 3 |
+
|
| 4 |
+
use_contrastive_loss: True
|
| 5 |
+
use_decoder: True
|
| 6 |
+
patch_size: 16
|
| 7 |
+
|
| 8 |
+
decoder_embed_dim: 512
|
| 9 |
+
num_frames: 16
|
| 10 |
+
mask_ratio: .75
|
| 11 |
+
pred_t_dim: 8
|
| 12 |
+
t_patch_size: 2
|
| 13 |
+
cls_embed: True
|
| 14 |
+
no_qkv_bias: False
|
| 15 |
+
sep_pos_embed: True
|
| 16 |
+
trunc_init: False
|
| 17 |
+
norm_pix_loss: True
|
| 18 |
+
contrastive_loss_weight: 1.0 # 2.0
|
| 19 |
+
|
| 20 |
+
# VICReg params
|
| 21 |
+
use_vic_loss: True
|
| 22 |
+
vic_loss_weight: 1.0 # 0.05 # 1.0
|
| 23 |
+
gamma: 0.5 # 1.0 # 0.5
|
| 24 |
+
# mu->v, lamda->i, nu->c
|
| 25 |
+
mu: 25
|
| 26 |
+
lamda: 25
|
| 27 |
+
nu: 1
|
| 28 |
+
# fraction of tokens to use for cov loss (all tokens don't fit in mem)
|
| 29 |
+
rand_frac: 0.1 # 0.2
|
| 30 |
+
# whether to apply vic to cls tok (if cont is also on, 2 losses will apply to cls token)
|
| 31 |
+
use_vic_cls: True
|
| 32 |
+
norm_pix_loss: False
|
| 33 |
+
pct_masks_to_decode: 1
|
| 34 |
+
|
| 35 |
+
# Training Configs
|
| 36 |
+
batch_size: 32
|
| 37 |
+
num_workers: 10
|
| 38 |
+
num_epochs: 100
|
| 39 |
+
seed: 42
|
| 40 |
+
base_lr: 3.0e-4 # Keep the x.0 else will be converted to string
|
| 41 |
+
num_samples_per_epoch: 200000
|
| 42 |
+
test_num_samples_per_epoch: 50000
|
| 43 |
+
grad_clip: 1.0 # set 0 for no clip
|
| 44 |
+
grad_accumulation_steps: 1
|
| 45 |
+
test_set: False
|
| 46 |
+
|
| 47 |
+
# Downstream probe config
|
| 48 |
+
probe_num_samples_per_epoch: 100000
|
| 49 |
+
probe_num_epochs: 30
|
| 50 |
+
probe_batch_size: 8
|
| 51 |
+
probe_base_lr: 3.0e-4
|
| 52 |
+
|
| 53 |
+
# Saving progress
|
| 54 |
+
ckpt_saving: True
|
| 55 |
+
ckpt_interval: 5 # in epochs
|
| 56 |
+
print_interval: 20 # in steps
|
| 57 |
+
resume_from_ckpt: False
|
| 58 |
+
wandb_log: False
|
fMRI-foundation-model-old/flat/flat_models.py
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
# References:
|
| 8 |
+
# timm: https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
| 9 |
+
# DeiT: https://github.com/facebookresearch/deit
|
| 10 |
+
# MAE: https://github.com/facebookresearch/mae
|
| 11 |
+
# MAE-ST: https://github.com/facebookresearch/mae_st
|
| 12 |
+
# --------------------------------------------------------
|
| 13 |
+
|
| 14 |
+
from functools import partial
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
from einops import rearrange
|
| 19 |
+
from util import video_vit
|
| 20 |
+
from util.logging import master_print as print
|
| 21 |
+
from util.hcp_flat import load_hcp_flat_mask
|
| 22 |
+
import copy
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class MaskedAutoencoderViT(nn.Module):
|
| 26 |
+
"""Masked Autoencoder with VisionTransformer backbone"""
|
| 27 |
+
|
| 28 |
+
def __init__(
|
| 29 |
+
self,
|
| 30 |
+
img_size=224,
|
| 31 |
+
patch_size=16,
|
| 32 |
+
in_chans=3,
|
| 33 |
+
embed_dim=1024,
|
| 34 |
+
depth=24,
|
| 35 |
+
num_heads=16,
|
| 36 |
+
decoder_embed_dim=512,
|
| 37 |
+
decoder_depth=8,
|
| 38 |
+
decoder_num_heads=16,
|
| 39 |
+
mlp_ratio=4.0,
|
| 40 |
+
norm_layer=nn.LayerNorm,
|
| 41 |
+
norm_pix_loss=False,
|
| 42 |
+
num_frames=16,
|
| 43 |
+
t_patch_size=2,
|
| 44 |
+
patch_embed=video_vit.PatchEmbed,
|
| 45 |
+
no_qkv_bias=False,
|
| 46 |
+
sep_pos_embed=True,
|
| 47 |
+
trunc_init=False,
|
| 48 |
+
cls_embed=True,
|
| 49 |
+
pred_t_dim=8,
|
| 50 |
+
img_mask=None,
|
| 51 |
+
pct_masks_to_decode=1,
|
| 52 |
+
**kwargs,
|
| 53 |
+
):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.trunc_init = trunc_init
|
| 56 |
+
self.sep_pos_embed = sep_pos_embed
|
| 57 |
+
self.cls_embed = cls_embed
|
| 58 |
+
self.pred_t_dim = pred_t_dim
|
| 59 |
+
self.t_pred_patch_size = t_patch_size * pred_t_dim // num_frames
|
| 60 |
+
self.embed_dim = embed_dim
|
| 61 |
+
|
| 62 |
+
self.pct_masks_to_decode = pct_masks_to_decode
|
| 63 |
+
|
| 64 |
+
self.patch_embed = patch_embed(
|
| 65 |
+
img_size,
|
| 66 |
+
patch_size,
|
| 67 |
+
in_chans,
|
| 68 |
+
embed_dim,
|
| 69 |
+
num_frames,
|
| 70 |
+
t_patch_size,
|
| 71 |
+
)
|
| 72 |
+
num_patches = self.patch_embed.num_patches
|
| 73 |
+
input_size = self.patch_embed.input_size
|
| 74 |
+
self.input_size = input_size
|
| 75 |
+
|
| 76 |
+
if self.cls_embed:
|
| 77 |
+
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
| 78 |
+
self.decoder_cls_token = nn.Parameter(torch.zeros(1, 1, decoder_embed_dim))
|
| 79 |
+
|
| 80 |
+
if sep_pos_embed:
|
| 81 |
+
self.pos_embed_spatial = nn.Parameter(
|
| 82 |
+
torch.zeros(1, input_size[1] * input_size[2], embed_dim)
|
| 83 |
+
)
|
| 84 |
+
self.pos_embed_temporal = nn.Parameter(
|
| 85 |
+
torch.zeros(1, input_size[0], embed_dim)
|
| 86 |
+
)
|
| 87 |
+
if self.cls_embed:
|
| 88 |
+
self.pos_embed_class = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
| 89 |
+
else:
|
| 90 |
+
if self.cls_embed:
|
| 91 |
+
_num_patches = num_patches + 1
|
| 92 |
+
else:
|
| 93 |
+
_num_patches = num_patches
|
| 94 |
+
|
| 95 |
+
self.pos_embed = nn.Parameter(
|
| 96 |
+
torch.zeros(1, _num_patches, embed_dim),
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
self.blocks = nn.ModuleList(
|
| 100 |
+
[
|
| 101 |
+
video_vit.Block(
|
| 102 |
+
embed_dim,
|
| 103 |
+
num_heads,
|
| 104 |
+
mlp_ratio,
|
| 105 |
+
qkv_bias=not no_qkv_bias,
|
| 106 |
+
qk_scale=None,
|
| 107 |
+
norm_layer=norm_layer,
|
| 108 |
+
)
|
| 109 |
+
for i in range(depth)
|
| 110 |
+
]
|
| 111 |
+
)
|
| 112 |
+
self.norm = norm_layer(embed_dim)
|
| 113 |
+
|
| 114 |
+
self.decoder_embed = nn.Linear(embed_dim, decoder_embed_dim, bias=True)
|
| 115 |
+
|
| 116 |
+
self.mask_token = nn.Parameter(torch.zeros(1, 1, decoder_embed_dim))
|
| 117 |
+
|
| 118 |
+
if sep_pos_embed:
|
| 119 |
+
self.decoder_pos_embed_spatial = nn.Parameter(
|
| 120 |
+
torch.zeros(1, input_size[1] * input_size[2], decoder_embed_dim)
|
| 121 |
+
)
|
| 122 |
+
self.decoder_pos_embed_temporal = nn.Parameter(
|
| 123 |
+
torch.zeros(1, input_size[0], decoder_embed_dim)
|
| 124 |
+
)
|
| 125 |
+
if self.cls_embed:
|
| 126 |
+
self.decoder_pos_embed_class = nn.Parameter(
|
| 127 |
+
torch.zeros(1, 1, decoder_embed_dim)
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
if self.cls_embed:
|
| 131 |
+
_num_patches = num_patches + 1
|
| 132 |
+
else:
|
| 133 |
+
_num_patches = num_patches
|
| 134 |
+
|
| 135 |
+
self.decoder_pos_embed = nn.Parameter(
|
| 136 |
+
torch.zeros(1, _num_patches, decoder_embed_dim),
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
self.decoder_blocks = nn.ModuleList(
|
| 140 |
+
[
|
| 141 |
+
video_vit.Block(
|
| 142 |
+
decoder_embed_dim,
|
| 143 |
+
decoder_num_heads,
|
| 144 |
+
mlp_ratio,
|
| 145 |
+
qkv_bias=not no_qkv_bias,
|
| 146 |
+
qk_scale=None,
|
| 147 |
+
norm_layer=norm_layer,
|
| 148 |
+
)
|
| 149 |
+
for i in range(decoder_depth)
|
| 150 |
+
]
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
self.decoder_norm = norm_layer(decoder_embed_dim)
|
| 154 |
+
self.decoder_pred = nn.Linear(
|
| 155 |
+
decoder_embed_dim,
|
| 156 |
+
self.t_pred_patch_size * patch_size**2 * in_chans,
|
| 157 |
+
bias=True,
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
self.norm_pix_loss = norm_pix_loss
|
| 161 |
+
|
| 162 |
+
self.initialize_mask(img_mask)
|
| 163 |
+
self.initialize_weights()
|
| 164 |
+
|
| 165 |
+
print("model initialized")
|
| 166 |
+
|
| 167 |
+
def initialize_weights(self):
|
| 168 |
+
if self.cls_embed:
|
| 169 |
+
torch.nn.init.trunc_normal_(self.cls_token, std=0.02)
|
| 170 |
+
if self.sep_pos_embed:
|
| 171 |
+
torch.nn.init.trunc_normal_(self.pos_embed_spatial, std=0.02)
|
| 172 |
+
torch.nn.init.trunc_normal_(self.pos_embed_temporal, std=0.02)
|
| 173 |
+
|
| 174 |
+
torch.nn.init.trunc_normal_(self.decoder_pos_embed_spatial, std=0.02)
|
| 175 |
+
torch.nn.init.trunc_normal_(self.decoder_pos_embed_temporal, std=0.02)
|
| 176 |
+
|
| 177 |
+
if self.cls_embed:
|
| 178 |
+
torch.nn.init.trunc_normal_(self.pos_embed_class, std=0.02)
|
| 179 |
+
torch.nn.init.trunc_normal_(self.decoder_pos_embed_class, std=0.02)
|
| 180 |
+
else:
|
| 181 |
+
torch.nn.init.trunc_normal_(self.pos_embed, std=0.02)
|
| 182 |
+
torch.nn.init.trunc_normal_(self.decoder_pos_embed, std=0.02)
|
| 183 |
+
w = self.patch_embed.proj.weight.data
|
| 184 |
+
if self.trunc_init:
|
| 185 |
+
torch.nn.init.trunc_normal_(w)
|
| 186 |
+
torch.nn.init.trunc_normal_(self.mask_token, std=0.02)
|
| 187 |
+
else:
|
| 188 |
+
torch.nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 189 |
+
torch.nn.init.normal_(self.mask_token, std=0.02)
|
| 190 |
+
|
| 191 |
+
# initialize nn.Linear and nn.LayerNorm
|
| 192 |
+
self.apply(self._init_weights)
|
| 193 |
+
|
| 194 |
+
def _init_weights(self, m):
|
| 195 |
+
if isinstance(m, nn.Linear):
|
| 196 |
+
# we use xavier_uniform following official JAX ViT:
|
| 197 |
+
if self.trunc_init:
|
| 198 |
+
nn.init.trunc_normal_(m.weight, std=0.02)
|
| 199 |
+
else:
|
| 200 |
+
torch.nn.init.xavier_uniform_(m.weight)
|
| 201 |
+
if isinstance(m, nn.Linear) and m.bias is not None:
|
| 202 |
+
nn.init.constant_(m.bias, 0)
|
| 203 |
+
elif isinstance(m, nn.LayerNorm):
|
| 204 |
+
nn.init.constant_(m.bias, 0)
|
| 205 |
+
nn.init.constant_(m.weight, 1.0)
|
| 206 |
+
|
| 207 |
+
def initialize_mask(self, img_mask):
|
| 208 |
+
if img_mask is not None:
|
| 209 |
+
img_mask = torch.as_tensor(img_mask > 0).float()
|
| 210 |
+
|
| 211 |
+
H, W = img_mask.shape
|
| 212 |
+
img_mask_patches = self.patchify(
|
| 213 |
+
img_mask
|
| 214 |
+
.view(1, 1, 1, H, W)
|
| 215 |
+
.repeat(1, self.patch_embed.in_chans, self.pred_t_dim, 1, 1)
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
patch_mask = rearrange(
|
| 219 |
+
img_mask,
|
| 220 |
+
"(h ph) (w pw) -> (h w) (ph pw)",
|
| 221 |
+
ph=self.patch_embed.patch_size[0],
|
| 222 |
+
pw=self.patch_embed.patch_size[1],
|
| 223 |
+
).any(dim=1).float()
|
| 224 |
+
patch_mask_indices, = patch_mask.nonzero(as_tuple=True)
|
| 225 |
+
|
| 226 |
+
self.register_buffer("img_mask", img_mask)
|
| 227 |
+
self.register_buffer("img_mask_patches", img_mask_patches)
|
| 228 |
+
self.register_buffer("patch_mask", patch_mask)
|
| 229 |
+
self.register_buffer("patch_mask_indices", patch_mask_indices)
|
| 230 |
+
self.n_mask_patches = int(len(patch_mask_indices) * self.pct_masks_to_decode)
|
| 231 |
+
else:
|
| 232 |
+
self.register_buffer("img_mask", None)
|
| 233 |
+
self.register_buffer("img_mask_patches", None)
|
| 234 |
+
self.register_buffer("patch_mask", None)
|
| 235 |
+
self.register_buffer("patch_mask_indices", None)
|
| 236 |
+
self.n_mask_patches = None
|
| 237 |
+
|
| 238 |
+
def patchify(self, imgs):
|
| 239 |
+
"""
|
| 240 |
+
imgs: (N, C, T, H, W)
|
| 241 |
+
x: (N, L, patch_size**2 *C)
|
| 242 |
+
"""
|
| 243 |
+
N, C, T, H, W = imgs.shape
|
| 244 |
+
ph, pw = self.patch_embed.patch_size
|
| 245 |
+
u = self.t_pred_patch_size
|
| 246 |
+
assert H % ph == 0 and W % pw == 0 and T % u == 0
|
| 247 |
+
h = H // ph
|
| 248 |
+
w = W // pw
|
| 249 |
+
t = T // u
|
| 250 |
+
|
| 251 |
+
x = imgs.reshape(shape=(N, C, t, u, h, ph, w, pw))
|
| 252 |
+
x = torch.einsum("nctuhpwq->nthwupqc", x)
|
| 253 |
+
x = x.reshape(shape=(N, t * h * w, u * ph * pw * C))
|
| 254 |
+
self.patch_info = (N, C, T, H, W, ph, pw, u, t, h, w)
|
| 255 |
+
return x
|
| 256 |
+
|
| 257 |
+
def unpatchify(self, x):
|
| 258 |
+
"""
|
| 259 |
+
x: (N, L, patch_size**2 *C)
|
| 260 |
+
imgs: (N, C, H, W)
|
| 261 |
+
"""
|
| 262 |
+
N, C, T, H, W, ph, pw, u, t, h, w = self.patch_info
|
| 263 |
+
|
| 264 |
+
x = x.reshape(shape=(N, t, h, w, u, ph, pw, C))
|
| 265 |
+
|
| 266 |
+
x = torch.einsum("nthwupqc->nctuhpwq", x)
|
| 267 |
+
imgs = x.reshape(shape=(N, C, T, H, W))
|
| 268 |
+
return imgs
|
| 269 |
+
|
| 270 |
+
def random_masking(self, x, mask_ratio, use_contrastive_loss=False):
|
| 271 |
+
"""
|
| 272 |
+
Perform per-sample random masking by per-sample shuffling.
|
| 273 |
+
Per-sample shuffling is done by argsort random noise.
|
| 274 |
+
x: [N, L, D], sequence
|
| 275 |
+
"""
|
| 276 |
+
N, L, D = x.shape # batch, length, dim
|
| 277 |
+
T = self.patch_embed.t_grid_size
|
| 278 |
+
H, W = self.patch_embed.grid_size
|
| 279 |
+
assert L == T * H * W
|
| 280 |
+
|
| 281 |
+
# adjust number to keep relative to image mask
|
| 282 |
+
if self.img_mask is not None:
|
| 283 |
+
len_keep = int(T * self.n_mask_patches * (1 - mask_ratio))
|
| 284 |
+
else:
|
| 285 |
+
len_keep = int(L * (1 - mask_ratio))
|
| 286 |
+
|
| 287 |
+
noise = torch.rand(N, L, device=x.device) # noise in [0, 1]
|
| 288 |
+
|
| 289 |
+
# shift missing patches to not be selected
|
| 290 |
+
if self.img_mask is not None:
|
| 291 |
+
noise = noise.view(N, T, H * W)
|
| 292 |
+
noise = noise + (1.0 - self.patch_mask)
|
| 293 |
+
noise = noise.view(N, L)
|
| 294 |
+
|
| 295 |
+
# sort noise for each sample
|
| 296 |
+
ids_shuffle = torch.argsort(
|
| 297 |
+
noise, dim=1
|
| 298 |
+
) # ascend: small is keep, large is remove
|
| 299 |
+
ids_restore = torch.argsort(ids_shuffle, dim=1)
|
| 300 |
+
|
| 301 |
+
# keep the first subset
|
| 302 |
+
ids_keep = ids_shuffle[:, :len_keep]
|
| 303 |
+
if not use_contrastive_loss:
|
| 304 |
+
x_masked = torch.gather(x, dim=1, index=ids_keep.unsqueeze(-1).repeat(1, 1, D))
|
| 305 |
+
else:
|
| 306 |
+
x_masked1 = torch.gather(x, dim=1, index=ids_keep[:,:len_keep//2].unsqueeze(-1).repeat(1, 1, D))
|
| 307 |
+
x_masked2 = torch.gather(x, dim=1, index=ids_keep[:,len_keep//2:len_keep].unsqueeze(-1).repeat(1, 1, D))
|
| 308 |
+
|
| 309 |
+
if not use_contrastive_loss:
|
| 310 |
+
# generate the binary mask: 0 is keep, 1 is remove
|
| 311 |
+
mask = torch.ones([N, L], device=x.device)
|
| 312 |
+
mask[:, :len_keep] = 0
|
| 313 |
+
# unshuffle to get the binary mask
|
| 314 |
+
mask = torch.gather(mask, dim=1, index=ids_restore)
|
| 315 |
+
else:
|
| 316 |
+
# generate the binary mask: 0 is keep, 1 is remove
|
| 317 |
+
mask1 = torch.ones([N, L], device=x.device)
|
| 318 |
+
mask2 = torch.ones([N, L], device=x.device)
|
| 319 |
+
mask1[:, :len_keep//2] = 0
|
| 320 |
+
mask2[:, len_keep//2:len_keep] = 0
|
| 321 |
+
# unshuffle to get the binary mask
|
| 322 |
+
mask1 = torch.gather(mask1, dim=1, index=ids_restore)
|
| 323 |
+
mask2 = torch.gather(mask2, dim=1, index=ids_restore)
|
| 324 |
+
|
| 325 |
+
if not use_contrastive_loss:
|
| 326 |
+
return x_masked, mask, ids_restore, ids_keep
|
| 327 |
+
else:
|
| 328 |
+
return [x_masked1,x_masked2], [mask1,mask2], ids_restore, ids_keep
|
| 329 |
+
|
| 330 |
+
def forward_encoder(self, x, mask_ratio, use_contrastive_loss=False):
|
| 331 |
+
x = self.patch_embed(x)
|
| 332 |
+
|
| 333 |
+
N, T, L, C = x.shape
|
| 334 |
+
|
| 335 |
+
x = x.reshape(N, T * L, C)
|
| 336 |
+
|
| 337 |
+
# masking: length -> length * mask_ratio
|
| 338 |
+
if not use_contrastive_loss:
|
| 339 |
+
x, mask, ids_restore, ids_keep = self.random_masking(x, mask_ratio)
|
| 340 |
+
x = x.view(N, -1, C)
|
| 341 |
+
else:
|
| 342 |
+
[x1,x2], [mask1,mask2], ids_restore, ids_keep = self.random_masking(x, mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 343 |
+
x1 = x1.view(len(x1), -1, C)
|
| 344 |
+
x2 = x2.view(len(x2), -1, C)
|
| 345 |
+
# append cls token
|
| 346 |
+
if self.cls_embed:
|
| 347 |
+
cls_token = self.cls_token
|
| 348 |
+
cls_tokens = cls_token.expand(x.shape[0], -1, -1)
|
| 349 |
+
if not use_contrastive_loss:
|
| 350 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 351 |
+
else:
|
| 352 |
+
x1 = torch.cat((cls_tokens, x1), dim=1)
|
| 353 |
+
x2 = torch.cat((cls_tokens, x2), dim=1)
|
| 354 |
+
|
| 355 |
+
# add pos embed w/o cls token
|
| 356 |
+
if self.sep_pos_embed:
|
| 357 |
+
pos_embed = self.pos_embed_spatial.repeat(
|
| 358 |
+
1, self.input_size[0], 1
|
| 359 |
+
) + torch.repeat_interleave(
|
| 360 |
+
self.pos_embed_temporal,
|
| 361 |
+
self.input_size[1] * self.input_size[2],
|
| 362 |
+
dim=1,
|
| 363 |
+
)
|
| 364 |
+
pos_embed = pos_embed.expand(x.shape[0], -1, -1)
|
| 365 |
+
pos_embed = torch.gather(
|
| 366 |
+
pos_embed,
|
| 367 |
+
dim=1,
|
| 368 |
+
index=ids_keep.unsqueeze(-1).repeat(1, 1, pos_embed.shape[2]),
|
| 369 |
+
)
|
| 370 |
+
if self.cls_embed:
|
| 371 |
+
pos_embed = torch.cat(
|
| 372 |
+
[
|
| 373 |
+
self.pos_embed_class.expand(pos_embed.shape[0], -1, -1),
|
| 374 |
+
pos_embed,
|
| 375 |
+
],
|
| 376 |
+
1,
|
| 377 |
+
)
|
| 378 |
+
else:
|
| 379 |
+
if self.cls_embed:
|
| 380 |
+
cls_ind = 1
|
| 381 |
+
else:
|
| 382 |
+
cls_ind = 0
|
| 383 |
+
pos_embed = self.pos_embed[:, cls_ind:, :].expand(x.shape[0], -1, -1)
|
| 384 |
+
pos_embed = torch.gather(
|
| 385 |
+
pos_embed,
|
| 386 |
+
dim=1,
|
| 387 |
+
index=ids_keep.unsqueeze(-1).repeat(1, 1, pos_embed.shape[2]),
|
| 388 |
+
)
|
| 389 |
+
if self.cls_embed:
|
| 390 |
+
pos_embed = torch.cat(
|
| 391 |
+
[
|
| 392 |
+
self.pos_embed[:, :1, :].expand(x.shape[0], -1, -1),
|
| 393 |
+
pos_embed,
|
| 394 |
+
],
|
| 395 |
+
1,
|
| 396 |
+
)
|
| 397 |
+
if not use_contrastive_loss:
|
| 398 |
+
x = x.view([N, -1, C]) + pos_embed
|
| 399 |
+
else:
|
| 400 |
+
x1 = x1.view([len(x1), -1, C]) + pos_embed[:,:x1.shape[1]]
|
| 401 |
+
x2 = x2.view([len(x2), -1, C]) + torch.cat((pos_embed[:,:1], pos_embed[:,x1.shape[1]:]),dim=1)
|
| 402 |
+
|
| 403 |
+
if not use_contrastive_loss:
|
| 404 |
+
# apply Transformer blocks
|
| 405 |
+
for blk in self.blocks:
|
| 406 |
+
x = blk(x)
|
| 407 |
+
x = self.norm(x)
|
| 408 |
+
else:
|
| 409 |
+
# apply Transformer blocks
|
| 410 |
+
for blk in self.blocks:
|
| 411 |
+
x1 = blk(x1)
|
| 412 |
+
x2 = blk(x2)
|
| 413 |
+
x1 = self.norm(x1)
|
| 414 |
+
x2 = self.norm(x2)
|
| 415 |
+
|
| 416 |
+
if not use_contrastive_loss:
|
| 417 |
+
if self.cls_embed:
|
| 418 |
+
# remove cls token
|
| 419 |
+
x = x[:, 1:, :]
|
| 420 |
+
|
| 421 |
+
return x, mask, ids_restore
|
| 422 |
+
else:
|
| 423 |
+
if self.cls_embed:
|
| 424 |
+
# remove cls token
|
| 425 |
+
x1 = x1[:, 1:, :]
|
| 426 |
+
x2 = x2[:, 1:, :]
|
| 427 |
+
|
| 428 |
+
return [x1,x2], [mask1,mask2], ids_restore
|
| 429 |
+
|
| 430 |
+
def forward_encoder_with_mask(self, x, ids_keep):
|
| 431 |
+
# embed patches
|
| 432 |
+
x = self.patch_embed(x)
|
| 433 |
+
N, T, L, C = x.shape
|
| 434 |
+
|
| 435 |
+
x = x.reshape(N, T * L, C)
|
| 436 |
+
# mask out tokens
|
| 437 |
+
x = torch.gather(x, dim=1, index=ids_keep.unsqueeze(-1).repeat(1, 1, C))
|
| 438 |
+
|
| 439 |
+
# append cls token
|
| 440 |
+
if self.cls_embed:
|
| 441 |
+
cls_token = self.cls_token
|
| 442 |
+
cls_tokens = cls_token.expand(x.shape[0], -1, -1)
|
| 443 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 444 |
+
|
| 445 |
+
# add pos embed w/o cls token
|
| 446 |
+
if self.sep_pos_embed:
|
| 447 |
+
pos_embed = self.pos_embed_spatial.repeat(
|
| 448 |
+
1, self.input_size[0], 1
|
| 449 |
+
) + torch.repeat_interleave(
|
| 450 |
+
self.pos_embed_temporal,
|
| 451 |
+
self.input_size[1] * self.input_size[2],
|
| 452 |
+
dim=1,
|
| 453 |
+
)
|
| 454 |
+
pos_embed = pos_embed.expand(x.shape[0], -1, -1)
|
| 455 |
+
pos_embed = torch.gather(
|
| 456 |
+
pos_embed,
|
| 457 |
+
dim=1,
|
| 458 |
+
index=ids_keep.unsqueeze(-1).repeat(1, 1, pos_embed.shape[2]),
|
| 459 |
+
)
|
| 460 |
+
if self.cls_embed:
|
| 461 |
+
pos_embed = torch.cat(
|
| 462 |
+
[
|
| 463 |
+
self.pos_embed_class.expand(pos_embed.shape[0], -1, -1),
|
| 464 |
+
pos_embed,
|
| 465 |
+
],
|
| 466 |
+
1,
|
| 467 |
+
)
|
| 468 |
+
else:
|
| 469 |
+
if self.cls_embed:
|
| 470 |
+
cls_ind = 1
|
| 471 |
+
else:
|
| 472 |
+
cls_ind = 0
|
| 473 |
+
pos_embed = self.pos_embed[:, cls_ind:, :].expand(x.shape[0], -1, -1)
|
| 474 |
+
pos_embed = torch.gather(
|
| 475 |
+
pos_embed,
|
| 476 |
+
dim=1,
|
| 477 |
+
index=ids_keep.unsqueeze(-1).repeat(1, 1, pos_embed.shape[2]),
|
| 478 |
+
)
|
| 479 |
+
if self.cls_embed:
|
| 480 |
+
pos_embed = torch.cat(
|
| 481 |
+
[
|
| 482 |
+
self.pos_embed[:, :1, :].expand(x.shape[0], -1, -1),
|
| 483 |
+
pos_embed,
|
| 484 |
+
],
|
| 485 |
+
1,
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
x = x.view([N, -1, C]) + pos_embed
|
| 489 |
+
|
| 490 |
+
for blk in self.blocks:
|
| 491 |
+
x = blk(x)
|
| 492 |
+
x = self.norm(x)
|
| 493 |
+
return x
|
| 494 |
+
|
| 495 |
+
def forward_decoder(self, x, ids_restore, use_contrastive_loss=False):
|
| 496 |
+
N = x.shape[0]
|
| 497 |
+
T = self.patch_embed.t_grid_size
|
| 498 |
+
H, W = self.patch_embed.grid_size
|
| 499 |
+
|
| 500 |
+
# embed tokens
|
| 501 |
+
x = self.decoder_embed(x)
|
| 502 |
+
C = x.shape[-1]
|
| 503 |
+
|
| 504 |
+
# append mask tokens to sequence
|
| 505 |
+
mask_tokens = self.mask_token.repeat(N, T * H * W + 0 - x.shape[1], 1)
|
| 506 |
+
x_ = torch.cat([x[:, :, :], mask_tokens], dim=1) # no cls token
|
| 507 |
+
x_ = x_.view([N, T * H * W, C])
|
| 508 |
+
x_ = torch.gather(
|
| 509 |
+
x_, dim=1, index=ids_restore.unsqueeze(-1).repeat(1, 1, x_.shape[2])
|
| 510 |
+
) # unshuffle
|
| 511 |
+
x = x_.view([N, T * H * W, C])
|
| 512 |
+
# append cls token
|
| 513 |
+
if self.cls_embed:
|
| 514 |
+
decoder_cls_token = self.decoder_cls_token
|
| 515 |
+
decoder_cls_tokens = decoder_cls_token.expand(x.shape[0], -1, -1)
|
| 516 |
+
x = torch.cat((decoder_cls_tokens, x), dim=1)
|
| 517 |
+
|
| 518 |
+
if self.sep_pos_embed:
|
| 519 |
+
decoder_pos_embed = self.decoder_pos_embed_spatial.repeat(
|
| 520 |
+
1, self.input_size[0], 1
|
| 521 |
+
) + torch.repeat_interleave(
|
| 522 |
+
self.decoder_pos_embed_temporal,
|
| 523 |
+
self.input_size[1] * self.input_size[2],
|
| 524 |
+
dim=1,
|
| 525 |
+
)
|
| 526 |
+
if self.cls_embed:
|
| 527 |
+
decoder_pos_embed = torch.cat(
|
| 528 |
+
[
|
| 529 |
+
self.decoder_pos_embed_class.expand(
|
| 530 |
+
decoder_pos_embed.shape[0], -1, -1
|
| 531 |
+
),
|
| 532 |
+
decoder_pos_embed,
|
| 533 |
+
],
|
| 534 |
+
1,
|
| 535 |
+
)
|
| 536 |
+
else:
|
| 537 |
+
decoder_pos_embed = self.decoder_pos_embed[:, :, :]
|
| 538 |
+
|
| 539 |
+
# add pos embed
|
| 540 |
+
x = x + decoder_pos_embed
|
| 541 |
+
|
| 542 |
+
attn = self.decoder_blocks[0].attn
|
| 543 |
+
|
| 544 |
+
# drop patches outside image mask (and then only keep a subset a la VideoMAE2)
|
| 545 |
+
if self.img_mask is not None:
|
| 546 |
+
if self.cls_embed:
|
| 547 |
+
decoder_cls_tokens, x = x[:, :1, :], x[:, 1:, :]
|
| 548 |
+
x = x.view([N, T, H * W, C])
|
| 549 |
+
# x = x[:, :, self.patch_mask_indices]
|
| 550 |
+
|
| 551 |
+
# drop patches randomly to preserve memory (VideoMAE2 approach)
|
| 552 |
+
included_patches = self.patch_mask_indices
|
| 553 |
+
num_to_select = int(self.pct_masks_to_decode * len(included_patches))
|
| 554 |
+
selected_idx = torch.randperm(len(included_patches))[:num_to_select]
|
| 555 |
+
included_patches = included_patches[selected_idx]
|
| 556 |
+
x = x[:, :, included_patches]
|
| 557 |
+
|
| 558 |
+
x = x.view([N, T * self.n_mask_patches, C])
|
| 559 |
+
if self.cls_embed:
|
| 560 |
+
x = torch.cat((decoder_cls_tokens, x), dim=1)
|
| 561 |
+
|
| 562 |
+
# apply Transformer blocks
|
| 563 |
+
for blk in self.decoder_blocks:
|
| 564 |
+
x = blk(x)
|
| 565 |
+
x = self.decoder_norm(x)
|
| 566 |
+
|
| 567 |
+
# predictor projection
|
| 568 |
+
x = self.decoder_pred(x)
|
| 569 |
+
|
| 570 |
+
if self.cls_embed:
|
| 571 |
+
# remove cls token
|
| 572 |
+
x = x[:, 1:, :]
|
| 573 |
+
|
| 574 |
+
# fill outside mask with zeros
|
| 575 |
+
if self.img_mask is not None:
|
| 576 |
+
C = x.shape[-1]
|
| 577 |
+
x = x.view([N, T, self.n_mask_patches, C])
|
| 578 |
+
x_ = torch.zeros([N, T, H * W, C], dtype=x.dtype, device=x.device)
|
| 579 |
+
x = x_.scatter(
|
| 580 |
+
2, included_patches.view(1, 1, -1, 1).expand(N, T, self.n_mask_patches, C), x,
|
| 581 |
+
)
|
| 582 |
+
x = x.view([N, T * H * W, C])
|
| 583 |
+
|
| 584 |
+
return x
|
| 585 |
+
|
| 586 |
+
def forward_loss(self, imgs, pred, mask):
|
| 587 |
+
"""
|
| 588 |
+
imgs: [N, C, T, H, W]
|
| 589 |
+
pred: [N, t*h*w, u*p*p*C]
|
| 590 |
+
mask: [N, t*h*w], 0 is keep, 1 is remove,
|
| 591 |
+
"""
|
| 592 |
+
_imgs = torch.index_select(
|
| 593 |
+
imgs,
|
| 594 |
+
2,
|
| 595 |
+
torch.linspace(
|
| 596 |
+
0,
|
| 597 |
+
imgs.shape[2] - 1,
|
| 598 |
+
self.pred_t_dim,
|
| 599 |
+
)
|
| 600 |
+
.long()
|
| 601 |
+
.to(imgs.device),
|
| 602 |
+
)
|
| 603 |
+
target = self.patchify(_imgs)
|
| 604 |
+
if self.norm_pix_loss:
|
| 605 |
+
mean = target.mean(dim=-1, keepdim=True)
|
| 606 |
+
var = target.var(dim=-1, keepdim=True)
|
| 607 |
+
target = (target - mean) / (var + 1.0e-6) ** 0.5
|
| 608 |
+
|
| 609 |
+
loss = (pred - target) ** 2
|
| 610 |
+
if self.img_mask is not None:
|
| 611 |
+
# exclude missing pixels from loss
|
| 612 |
+
mask = mask.unsqueeze(-1) * self.img_mask_patches
|
| 613 |
+
else:
|
| 614 |
+
loss = loss.mean(dim=-1) # [N, L], mean loss per patch
|
| 615 |
+
|
| 616 |
+
loss = (loss * mask).sum() / mask.sum() # mean loss on removed patches
|
| 617 |
+
return loss
|
| 618 |
+
|
| 619 |
+
def forward_cyclic_loss(self, pred1, pred2, mask):
|
| 620 |
+
"""
|
| 621 |
+
mask1 and mask2 encoder outputs should be the same since they are predicting the same held-out true mask
|
| 622 |
+
"""
|
| 623 |
+
loss = (pred1 - pred2) ** 2
|
| 624 |
+
if self.img_mask is not None:
|
| 625 |
+
# exclude missing pixels from loss
|
| 626 |
+
mask = mask.unsqueeze(-1) * self.img_mask_patches
|
| 627 |
+
else:
|
| 628 |
+
loss = loss.mean(dim=-1) # [N, L], mean loss per patch
|
| 629 |
+
|
| 630 |
+
loss = (loss * mask).sum() / mask.sum() # mean loss on removed patches
|
| 631 |
+
return loss
|
| 632 |
+
|
| 633 |
+
def forward(self, imgs, mask_ratio=0.75, use_contrastive_loss=False):
|
| 634 |
+
latent, mask, ids_restore = self.forward_encoder(imgs, mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 635 |
+
if not use_contrastive_loss:
|
| 636 |
+
pred = self.forward_decoder(latent, ids_restore, use_contrastive_loss=use_contrastive_loss) # [N, L, p*p*C]
|
| 637 |
+
loss = self.forward_loss(imgs, pred, mask)
|
| 638 |
+
return loss, pred, mask, latent
|
| 639 |
+
else:
|
| 640 |
+
latent1, latent2 = latent
|
| 641 |
+
mask1, mask2 = mask
|
| 642 |
+
true_mask = copy.deepcopy(mask1)
|
| 643 |
+
true_mask[mask2==0]=0 # dont try to predict the masks that were fed to the other encoder
|
| 644 |
+
pred1 = self.forward_decoder(latent1, ids_restore, use_contrastive_loss=use_contrastive_loss) # [N, L, p*p*C]
|
| 645 |
+
pred2 = self.forward_decoder(latent2, ids_restore, use_contrastive_loss=use_contrastive_loss) # [N, L, p*p*C]
|
| 646 |
+
loss1 = self.forward_loss(imgs, pred1, true_mask)
|
| 647 |
+
loss2 = self.forward_loss(imgs, pred2, true_mask)
|
| 648 |
+
loss3 = self.forward_cyclic_loss(pred1, pred2, true_mask)
|
| 649 |
+
return loss1, loss2, loss3, pred1, pred2, mask1, mask2, true_mask, latent1, latent2
|
| 650 |
+
|
| 651 |
+
def forward_features(self, x, global_pool=True):
|
| 652 |
+
# embed patches
|
| 653 |
+
x = self.patch_embed(x)
|
| 654 |
+
N, T, L, C = x.shape # T: temporal; L: spatial
|
| 655 |
+
|
| 656 |
+
x = x.view([N, T * L, C])
|
| 657 |
+
|
| 658 |
+
# append cls token
|
| 659 |
+
if self.cls_embed:
|
| 660 |
+
cls_token = self.cls_token
|
| 661 |
+
cls_tokens = cls_token.expand(x.shape[0], -1, -1)
|
| 662 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 663 |
+
|
| 664 |
+
if self.sep_pos_embed:
|
| 665 |
+
pos_embed = self.pos_embed_spatial.repeat(
|
| 666 |
+
1, self.input_size[0], 1
|
| 667 |
+
) + torch.repeat_interleave(
|
| 668 |
+
self.pos_embed_temporal,
|
| 669 |
+
self.input_size[1] * self.input_size[2],
|
| 670 |
+
dim=1,
|
| 671 |
+
)
|
| 672 |
+
if self.cls_embed:
|
| 673 |
+
pos_embed = torch.cat(
|
| 674 |
+
[
|
| 675 |
+
self.pos_embed_class.expand(pos_embed.shape[0], -1, -1),
|
| 676 |
+
pos_embed,
|
| 677 |
+
],
|
| 678 |
+
1,
|
| 679 |
+
)
|
| 680 |
+
else:
|
| 681 |
+
pos_embed = self.pos_embed[:, :, :]
|
| 682 |
+
x = x + pos_embed
|
| 683 |
+
|
| 684 |
+
# drop patches outside image mask
|
| 685 |
+
if self.img_mask is not None:
|
| 686 |
+
if self.cls_embed:
|
| 687 |
+
cls_tokens, x = x[:, :1, :], x[:, 1:, :]
|
| 688 |
+
x = x.view([N, T, L, C])
|
| 689 |
+
x = x[:, :, self.patch_mask_indices]
|
| 690 |
+
x = x.view([N, T * self.n_mask_patches, C])
|
| 691 |
+
if self.cls_embed:
|
| 692 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 693 |
+
|
| 694 |
+
# apply Transformer blocks
|
| 695 |
+
for blk in self.blocks:
|
| 696 |
+
x = blk(x)
|
| 697 |
+
|
| 698 |
+
if global_pool:
|
| 699 |
+
if self.cls_embed:
|
| 700 |
+
x = x[:, 1:, :]
|
| 701 |
+
x = x.mean(dim=1)
|
| 702 |
+
return x
|
| 703 |
+
|
| 704 |
+
def forward_head(self, x):
|
| 705 |
+
# classifier
|
| 706 |
+
x = self.norm(x)
|
| 707 |
+
# x = self.fc_norm(x)
|
| 708 |
+
x = self.dropout(x)
|
| 709 |
+
x = self.head(x)
|
| 710 |
+
|
| 711 |
+
return x
|
| 712 |
+
|
| 713 |
+
def mask_fill(self, x):
|
| 714 |
+
N, L, C = x.shape
|
| 715 |
+
T = self.patch_embed.t_grid_size
|
| 716 |
+
H, W = self.patch_embed.grid_size
|
| 717 |
+
assert L == T * self.n_mask_patches
|
| 718 |
+
|
| 719 |
+
x = x.view(N, T, -1, C)
|
| 720 |
+
x_ = torch.zeros([N, T, H * W, C], dtype=x.dtype, device=x.device)
|
| 721 |
+
x = x_.scatter(
|
| 722 |
+
2, self.patch_mask_indices.view(1, 1, -1, 1).expand(N, T, -1, C), x,
|
| 723 |
+
)
|
| 724 |
+
return x
|
| 725 |
+
|
| 726 |
+
|
| 727 |
+
def mae_vit_small_fmri(num_heads=6,**kwargs):
|
| 728 |
+
model = MaskedAutoencoderViT(
|
| 729 |
+
img_size=(144, 320),
|
| 730 |
+
in_chans=1,
|
| 731 |
+
embed_dim=384,
|
| 732 |
+
depth=12,
|
| 733 |
+
num_heads=num_heads,
|
| 734 |
+
mlp_ratio=4,
|
| 735 |
+
norm_layer=partial(nn.LayerNorm, eps=1e-6),
|
| 736 |
+
img_mask=load_hcp_flat_mask(),
|
| 737 |
+
**kwargs,
|
| 738 |
+
)
|
| 739 |
+
return model
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
def mae_vit_base_fmri(**kwargs):
|
| 743 |
+
model = MaskedAutoencoderViT(
|
| 744 |
+
img_size=(144, 320),
|
| 745 |
+
in_chans=1,
|
| 746 |
+
embed_dim=768,
|
| 747 |
+
depth=12,
|
| 748 |
+
num_heads=12,
|
| 749 |
+
mlp_ratio=4,
|
| 750 |
+
norm_layer=partial(nn.LayerNorm, eps=1e-6),
|
| 751 |
+
img_mask=load_hcp_flat_mask(),
|
| 752 |
+
**kwargs,
|
| 753 |
+
)
|
| 754 |
+
return model
|
| 755 |
+
|
| 756 |
+
|
| 757 |
+
def mae_vit_large_fmri(**kwargs):
|
| 758 |
+
model = MaskedAutoencoderViT(
|
| 759 |
+
img_size=(144, 320),
|
| 760 |
+
in_chans=1,
|
| 761 |
+
embed_dim=1024,
|
| 762 |
+
depth=24,
|
| 763 |
+
num_heads=16,
|
| 764 |
+
mlp_ratio=4,
|
| 765 |
+
norm_layer=partial(nn.LayerNorm, eps=1e-6),
|
| 766 |
+
img_mask=load_hcp_flat_mask(),
|
| 767 |
+
**kwargs,
|
| 768 |
+
)
|
| 769 |
+
return model
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
def mae_vit_huge_fmri(**kwargs):
|
| 773 |
+
model = MaskedAutoencoderViT(
|
| 774 |
+
img_size=(144, 320),
|
| 775 |
+
in_chans=1,
|
| 776 |
+
embed_dim=1280,
|
| 777 |
+
depth=32,
|
| 778 |
+
num_heads=16,
|
| 779 |
+
mlp_ratio=4,
|
| 780 |
+
norm_layer=partial(nn.LayerNorm, eps=1e-6),
|
| 781 |
+
img_mask=load_hcp_flat_mask(),
|
| 782 |
+
**kwargs,
|
| 783 |
+
)
|
| 784 |
+
return model
|
fMRI-foundation-model-old/flat/main.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
fMRI-foundation-model-old/flat/main.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import sys
|
| 4 |
+
import json
|
| 5 |
+
import yaml
|
| 6 |
+
import numpy as np
|
| 7 |
+
import copy
|
| 8 |
+
import math
|
| 9 |
+
import time
|
| 10 |
+
import random
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
import webdataset as wds
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
from torchvision import transforms
|
| 18 |
+
import utils
|
| 19 |
+
from flat_models import *
|
| 20 |
+
|
| 21 |
+
# tf32 data type is faster than standard float32
|
| 22 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 23 |
+
# following fixes a Conv3D CUDNN_NOT_SUPPORTED error
|
| 24 |
+
torch.backends.cudnn.benchmark = True
|
| 25 |
+
|
| 26 |
+
### Multi-GPU config ###
|
| 27 |
+
device_count = torch.cuda.device_count()
|
| 28 |
+
print(f"Number of available CUDA devices: {device_count}")
|
| 29 |
+
|
| 30 |
+
local_rank = os.getenv('LOCAL_RANK')
|
| 31 |
+
if local_rank is None:
|
| 32 |
+
local_rank = 0
|
| 33 |
+
else:
|
| 34 |
+
local_rank = int(local_rank)
|
| 35 |
+
print(f"LOCAL RANK={local_rank}")
|
| 36 |
+
|
| 37 |
+
num_devices = os.getenv('NUM_GPUS')
|
| 38 |
+
if num_devices is None:
|
| 39 |
+
num_devices = 1
|
| 40 |
+
else:
|
| 41 |
+
num_devices = int(num_devices)
|
| 42 |
+
print(f"NUM GPUS={num_devices}")
|
| 43 |
+
distributed = True if num_devices>1 else False
|
| 44 |
+
if distributed: assert device_count==num_devices
|
| 45 |
+
|
| 46 |
+
node = os.getenv('SLURM_NODEID')
|
| 47 |
+
if node is None:
|
| 48 |
+
node = 0
|
| 49 |
+
else:
|
| 50 |
+
node = int(node)
|
| 51 |
+
print(f"NODE={node}")
|
| 52 |
+
|
| 53 |
+
global_rank = os.getenv('RANK')
|
| 54 |
+
if global_rank is None:
|
| 55 |
+
global_rank = 0
|
| 56 |
+
else:
|
| 57 |
+
global_rank = int(global_rank)
|
| 58 |
+
print(f"GLOBAL RANK={global_rank}")
|
| 59 |
+
|
| 60 |
+
world_size = os.getenv('WORLD_SIZE')
|
| 61 |
+
if world_size is None:
|
| 62 |
+
world_size = 1
|
| 63 |
+
else:
|
| 64 |
+
world_size = int(world_size)
|
| 65 |
+
print(f"WORLD_SIZE={world_size}")
|
| 66 |
+
|
| 67 |
+
# Load parameters from yaml config
|
| 68 |
+
config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)
|
| 69 |
+
|
| 70 |
+
print("\n__CONFIG__")
|
| 71 |
+
for attribute_name in config.keys():
|
| 72 |
+
print(f"{attribute_name} = {config[attribute_name]}")
|
| 73 |
+
globals()[attribute_name] = config[f'{attribute_name}']
|
| 74 |
+
print("\n")
|
| 75 |
+
|
| 76 |
+
# Create outdir for ckpt and config.yaml
|
| 77 |
+
outdir = os.path.abspath(f'checkpoints/{model_name}')
|
| 78 |
+
print("outdir", outdir)
|
| 79 |
+
|
| 80 |
+
# Load previous config.yaml if available
|
| 81 |
+
if os.path.exists(f"{outdir}/config.yaml"):
|
| 82 |
+
config = yaml.load(open(f"{outdir}/config.yaml", 'r'), Loader=yaml.FullLoader)
|
| 83 |
+
print(f"Loaded config.yaml from ckpt folder {outdir}")
|
| 84 |
+
|
| 85 |
+
# create global variables from the config
|
| 86 |
+
print("\n__REPLACING_CONFIG__")
|
| 87 |
+
for attribute_name in config.keys():
|
| 88 |
+
print(f"{attribute_name} = {config[attribute_name]}")
|
| 89 |
+
globals()[attribute_name] = config[f'{attribute_name}']
|
| 90 |
+
print("\n")
|
| 91 |
+
|
| 92 |
+
data_type = torch.float32 # change depending on your mixed_precision
|
| 93 |
+
global_batch_size = batch_size * world_size
|
| 94 |
+
|
| 95 |
+
# FSDP Setup
|
| 96 |
+
if distributed:
|
| 97 |
+
import torch.distributed as dist
|
| 98 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 99 |
+
from torch.distributed.fsdp.api import BackwardPrefetch, CPUOffload, ShardingStrategy
|
| 100 |
+
import functools
|
| 101 |
+
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy
|
| 102 |
+
print(f"setting device to cuda:{local_rank}")
|
| 103 |
+
torch.cuda.set_device(local_rank)
|
| 104 |
+
device = torch.device('cuda',local_rank)
|
| 105 |
+
dist.init_process_group("nccl", rank=global_rank, world_size=world_size)
|
| 106 |
+
print(f"\nSuccessfully set cuda:{local_rank} | global_rank{global_rank} | node{node}")
|
| 107 |
+
dist.barrier()
|
| 108 |
+
print(f"global_rank{global_rank} passed barrier")
|
| 109 |
+
else:
|
| 110 |
+
device = torch.device('cuda')
|
| 111 |
+
|
| 112 |
+
print("PID of this process =",os.getpid())
|
| 113 |
+
print("device =", device, "distributed =",distributed, "num_devices =", num_devices, "local rank =", local_rank, "world size =", world_size, "data_type =", data_type)
|
| 114 |
+
|
| 115 |
+
# seed all random functions
|
| 116 |
+
utils.seed_everything(seed + global_rank)
|
| 117 |
+
|
| 118 |
+
from util.hcp_flat import load_hcp_flat_mask
|
| 119 |
+
from util.hcp_flat import create_hcp_flat
|
| 120 |
+
from util.losses import *
|
| 121 |
+
import util.visualize as vis
|
| 122 |
+
|
| 123 |
+
if utils.is_interactive(): # Use less samples per epoch for debugging
|
| 124 |
+
num_samples_per_epoch = 2000
|
| 125 |
+
test_num_samples_per_epoch = 2000
|
| 126 |
+
|
| 127 |
+
model = mae_vit_small_fmri(
|
| 128 |
+
decoder_embed_dim=decoder_embed_dim,
|
| 129 |
+
t_patch_size=t_patch_size,
|
| 130 |
+
pred_t_dim=pred_t_dim,
|
| 131 |
+
decoder_depth=4,
|
| 132 |
+
cls_embed=cls_embed,
|
| 133 |
+
norm_pix_loss=norm_pix_loss,
|
| 134 |
+
no_qkv_bias=no_qkv_bias,
|
| 135 |
+
sep_pos_embed=sep_pos_embed,
|
| 136 |
+
trunc_init=trunc_init,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
if use_contrastive_loss:
|
| 140 |
+
model.simclr_handler = SimCLRHandler(model.embed_dim).to(device)
|
| 141 |
+
if use_vic_loss:
|
| 142 |
+
model.vicreg_handler = VICRegHandler(model.embed_dim).to(device)
|
| 143 |
+
|
| 144 |
+
# state = torch.load("checkpoints/checkpoint-00099.pth", map_location="cpu")
|
| 145 |
+
# model.load_state_dict(state["model"])
|
| 146 |
+
|
| 147 |
+
num_batches = num_samples_per_epoch // (num_devices * batch_size)
|
| 148 |
+
test_num_batches = test_num_samples_per_epoch // (num_devices * batch_size)
|
| 149 |
+
print("num_batches", num_batches)
|
| 150 |
+
print("test_num_batches", test_num_batches)
|
| 151 |
+
|
| 152 |
+
## Train ##
|
| 153 |
+
train_dataset = create_hcp_flat(root=hcp_flat_path,
|
| 154 |
+
training=True, frames=num_frames)
|
| 155 |
+
train_dl = wds.WebLoader(
|
| 156 |
+
train_dataset.batched(batch_size, partial=False),
|
| 157 |
+
batch_size=None,
|
| 158 |
+
shuffle=False,
|
| 159 |
+
num_workers=num_workers,
|
| 160 |
+
pin_memory=True,
|
| 161 |
+
)
|
| 162 |
+
train_dl = train_dl.with_epoch(num_batches)
|
| 163 |
+
|
| 164 |
+
## Test ##
|
| 165 |
+
test_dataset = create_hcp_flat(root=hcp_flat_path,
|
| 166 |
+
training=False, frames=num_frames)
|
| 167 |
+
test_dl = wds.WebLoader(
|
| 168 |
+
test_dataset.batched(batch_size, partial=False),
|
| 169 |
+
batch_size=None,
|
| 170 |
+
shuffle=False,
|
| 171 |
+
num_workers=num_workers,
|
| 172 |
+
pin_memory=True,
|
| 173 |
+
)
|
| 174 |
+
test_dl = test_dl.with_epoch(test_num_batches)
|
| 175 |
+
|
| 176 |
+
print(f"\nChecking distributed setup on global_rank {global_rank}...")
|
| 177 |
+
from util.video_vit import Attention
|
| 178 |
+
if distributed:
|
| 179 |
+
my_auto_wrap_policy = functools.partial(
|
| 180 |
+
transformer_auto_wrap_policy,
|
| 181 |
+
transformer_layer_cls={
|
| 182 |
+
Attention, # <--- Your Transformer layer class
|
| 183 |
+
},
|
| 184 |
+
)
|
| 185 |
+
print(f"\nPrepping FSDP on {global_rank} {node}...\n")
|
| 186 |
+
model = model.to(device)
|
| 187 |
+
model = FSDP(
|
| 188 |
+
model,
|
| 189 |
+
sharding_strategy=ShardingStrategy.HYBRID_SHARD,
|
| 190 |
+
auto_wrap_policy=my_auto_wrap_policy,
|
| 191 |
+
use_orig_params=False,
|
| 192 |
+
cpu_offload=None, #CPUOffload(offload_params=True)
|
| 193 |
+
sync_module_states=True,
|
| 194 |
+
limit_all_gathers=True, # See https://github.com/pytorch/pytorch/issues/91165
|
| 195 |
+
device_id=device,
|
| 196 |
+
)
|
| 197 |
+
print(f"\nSuccessfully loaded FSDP model to device on global_rank {global_rank}\n")
|
| 198 |
+
dist.barrier()
|
| 199 |
+
else:
|
| 200 |
+
print(f"\nNot training distributed! global_rank {global_rank}")
|
| 201 |
+
model.to(device)
|
| 202 |
+
|
| 203 |
+
no_decay = ["bias", "LayerNorm.bias", "LayerNorm.weight"]
|
| 204 |
+
opt_grouped_parameters = [
|
| 205 |
+
{'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.05},
|
| 206 |
+
{'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},
|
| 207 |
+
]
|
| 208 |
+
|
| 209 |
+
lr = base_lr * global_batch_size / 256
|
| 210 |
+
print(f"multiply base lr {base_lr} by effective batch size {global_batch_size}")
|
| 211 |
+
print(f"lr = {lr}")
|
| 212 |
+
|
| 213 |
+
optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=lr, betas=(0.9, 0.95))
|
| 214 |
+
|
| 215 |
+
def adjust_learning_rate(optimizer, epoch, warmup_epochs=5, min_lr=0.0):
|
| 216 |
+
"""Decay the learning rate with half-cycle cosine after warmup"""
|
| 217 |
+
if epoch < warmup_epochs:
|
| 218 |
+
lr_ = lr * epoch / warmup_epochs
|
| 219 |
+
else:
|
| 220 |
+
lr_ = min_lr + (lr - min_lr) * 0.5 * (
|
| 221 |
+
1.0
|
| 222 |
+
+ math.cos(
|
| 223 |
+
math.pi
|
| 224 |
+
* (epoch - warmup_epochs)
|
| 225 |
+
/ (num_epochs - warmup_epochs)
|
| 226 |
+
)
|
| 227 |
+
)
|
| 228 |
+
for param_group in optimizer.param_groups:
|
| 229 |
+
param_group["lr"] = lr_
|
| 230 |
+
return lr_
|
| 231 |
+
|
| 232 |
+
print("\nDone with model preparations!")
|
| 233 |
+
num_params = utils.count_params(model)
|
| 234 |
+
|
| 235 |
+
def save_ckpt(model,tag="last"):
|
| 236 |
+
if distributed: dist.barrier()
|
| 237 |
+
model_states = model.state_dict()
|
| 238 |
+
if global_rank == 0:
|
| 239 |
+
os.makedirs(outdir,exist_ok=True)
|
| 240 |
+
ckpt_path = outdir+f'/{tag}.pth'
|
| 241 |
+
|
| 242 |
+
if tag == "last" and os.path.exists(ckpt_path):
|
| 243 |
+
shutil.copyfile(os.path.join(outdir, f'{tag}.pth'), os.path.join(outdir, f'{tag}_old.pth'))
|
| 244 |
+
# print(f'saving {ckpt_path}',flush=True)
|
| 245 |
+
if tag=='last':
|
| 246 |
+
torch.save({
|
| 247 |
+
'epoch': epoch,
|
| 248 |
+
'model_state_dict': model_states,
|
| 249 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 250 |
+
}, ckpt_path)
|
| 251 |
+
else:
|
| 252 |
+
torch.save({
|
| 253 |
+
'epoch': epoch,
|
| 254 |
+
'model_state_dict': model_states,
|
| 255 |
+
}, ckpt_path)
|
| 256 |
+
|
| 257 |
+
if tag == "last" and os.path.exists(os.path.join(outdir, f'{tag}_old.pth')):
|
| 258 |
+
os.remove(os.path.join(outdir, f'{tag}_old.pth'))
|
| 259 |
+
print(f"\n---saved {ckpt_path}!---\n")
|
| 260 |
+
|
| 261 |
+
# save the config.yaml
|
| 262 |
+
if not os.path.exists(f"{outdir}/config.yaml"):
|
| 263 |
+
with open(f"{outdir}/config.yaml", 'w') as file:
|
| 264 |
+
yaml.dump(config, file)
|
| 265 |
+
print(f"saved {outdir}/config.yaml!")
|
| 266 |
+
|
| 267 |
+
# auto resume
|
| 268 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')) or os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 269 |
+
if os.path.exists(os.path.join(outdir, 'last_old.pth')):
|
| 270 |
+
if os.path.exists(os.path.join(outdir, 'last.pth')):
|
| 271 |
+
# this is corrupted
|
| 272 |
+
os.remove(os.path.join(outdir, f'last.pth'))
|
| 273 |
+
# set last_old as last
|
| 274 |
+
shutil.move(os.path.join(outdir, f'last_old.pth'), os.path.join(outdir, f'last.pth'))
|
| 275 |
+
|
| 276 |
+
ckpt_path = os.path.join(outdir, 'last.pth')
|
| 277 |
+
resume_from_ckpt = True
|
| 278 |
+
|
| 279 |
+
epoch = 0
|
| 280 |
+
if resume_from_ckpt:
|
| 281 |
+
print("\n---resuming from ckpt_path---\n", ckpt_path)
|
| 282 |
+
checkpoint = torch.load(ckpt_path, map_location=device)
|
| 283 |
+
epoch = checkpoint['epoch']+1
|
| 284 |
+
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
|
| 285 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 286 |
+
# total_steps_done = epoch*num_iterations_per_epoch
|
| 287 |
+
# for _ in range(total_steps_done):
|
| 288 |
+
# lr_scheduler.step()
|
| 289 |
+
del checkpoint
|
| 290 |
+
torch.cuda.empty_cache()
|
| 291 |
+
|
| 292 |
+
if utils.is_interactive():
|
| 293 |
+
wandb_log = False
|
| 294 |
+
ckpt_saving = False
|
| 295 |
+
if local_rank==0 and wandb_log: # only use main process for wandb logging
|
| 296 |
+
import wandb
|
| 297 |
+
wandb_project = 'fMRI-foundation-model'
|
| 298 |
+
print(f"wandb {wandb_project} run {model_name}")
|
| 299 |
+
# need to configure wandb beforehand in terminal with "wandb init"!
|
| 300 |
+
wandb_config = {
|
| 301 |
+
"model_name": model_name,
|
| 302 |
+
"global_batch_size": global_batch_size,
|
| 303 |
+
"batch_size": batch_size,
|
| 304 |
+
"num_epochs": num_epochs,
|
| 305 |
+
"cls_embed": cls_embed,
|
| 306 |
+
"norm_pix_loss": norm_pix_loss,
|
| 307 |
+
"t_patch_size": t_patch_size,
|
| 308 |
+
"pred_t_dim": pred_t_dim,
|
| 309 |
+
"mask_ratio": mask_ratio,
|
| 310 |
+
"num_frames": num_frames,
|
| 311 |
+
"sep_pos_embed": sep_pos_embed,
|
| 312 |
+
"decoder_embed_dim": decoder_embed_dim,
|
| 313 |
+
"use_contrastive_loss": use_contrastive_loss,
|
| 314 |
+
"num_params": num_params,
|
| 315 |
+
"base_lr": base_lr,
|
| 316 |
+
"lr": lr,
|
| 317 |
+
"num_samples_per_epoch": num_samples_per_epoch,
|
| 318 |
+
"test_num_samples_per_epoch": test_num_samples_per_epoch,
|
| 319 |
+
"num_epochs": num_epochs,
|
| 320 |
+
"grad_clip": grad_clip,
|
| 321 |
+
"ckpt_interval": ckpt_interval,
|
| 322 |
+
"ckpt_saving": ckpt_saving,
|
| 323 |
+
"print_interval": print_interval,
|
| 324 |
+
"seed": seed,
|
| 325 |
+
"distributed": distributed,
|
| 326 |
+
"num_devices": num_devices,
|
| 327 |
+
"world_size": world_size,
|
| 328 |
+
}
|
| 329 |
+
print("wandb_config:\n",wandb_config)
|
| 330 |
+
print("wandb_id:",model_name)
|
| 331 |
+
wandb.init(
|
| 332 |
+
id=model_name,
|
| 333 |
+
project=wandb_project,
|
| 334 |
+
name=model_name,
|
| 335 |
+
config=wandb_config,
|
| 336 |
+
resume="allow",
|
| 337 |
+
)
|
| 338 |
+
else:
|
| 339 |
+
wandb_log = False
|
| 340 |
+
|
| 341 |
+
# lrs, train_losses, test_losses = [], [], []
|
| 342 |
+
# train_losses1, train_losses2, train_losses3, train_losses4 = [], [], [], []
|
| 343 |
+
|
| 344 |
+
epoch = 0
|
| 345 |
+
lrs, train_losses, recon_losses, contrastive_losses, vic_losses = [], [], [], [], []
|
| 346 |
+
cos_sim_encoder_output, cos_sim_decoder_output, cos_sim_encoder_output_patchwise = [], [], []
|
| 347 |
+
probe_losses, probe_accs, test_losses, test_accs = [], [], [], []
|
| 348 |
+
cos_sim_encoder_output_patchwise_test, cos_sim_encoder_output_test = [], []
|
| 349 |
+
|
| 350 |
+
mse = nn.MSELoss()
|
| 351 |
+
l1 = nn.L1Loss()
|
| 352 |
+
crossentropy = nn.CrossEntropyLoss()
|
| 353 |
+
|
| 354 |
+
if use_contrastive_loss:
|
| 355 |
+
contrastive_temps = utils.cosine_anneal(0.004, 0.0075, num_epochs)
|
| 356 |
+
|
| 357 |
+
grad_scaler = torch.cuda.amp.GradScaler(enabled=True)
|
| 358 |
+
progress_bar = tqdm(range(epoch, num_epochs), disable=local_rank!=0, desc="Overall")
|
| 359 |
+
for epoch in progress_bar:
|
| 360 |
+
model.train()
|
| 361 |
+
for train_i, batch in enumerate(train_dl):
|
| 362 |
+
optimizer.zero_grad()
|
| 363 |
+
adjust_learning_rate(optimizer, train_i / num_batches + epoch)
|
| 364 |
+
|
| 365 |
+
input_func = batch[1]
|
| 366 |
+
|
| 367 |
+
input_func = input_func.to(dtype=data_type, device=device, non_blocking=True)
|
| 368 |
+
if len(input_func.shape) == 6:
|
| 369 |
+
b, r, c, t, h, w = input_func.shape
|
| 370 |
+
input_func = input_func.reshape(b * r, c, t, h, w)
|
| 371 |
+
|
| 372 |
+
ids_shuffle, ids_restore = get_ids_shuffle(input_func.shape[0], input_func.device, model)
|
| 373 |
+
|
| 374 |
+
# adjust number to keep relative to image mask
|
| 375 |
+
if model.img_mask is not None:
|
| 376 |
+
len_keep = int(model.patch_embed.t_grid_size * model.n_mask_patches * (1 - mask_ratio))
|
| 377 |
+
else:
|
| 378 |
+
len_keep = int(ids_restore.shape[1] * (1 - mask_ratio))
|
| 379 |
+
|
| 380 |
+
# keep the first subset
|
| 381 |
+
ids_keep = ids_shuffle[:, :len_keep]
|
| 382 |
+
lats = model.forward_encoder_with_mask(input_func, ids_keep)
|
| 383 |
+
|
| 384 |
+
if use_decoder:
|
| 385 |
+
mask = torch.ones_like(ids_restore)
|
| 386 |
+
mask[:, :len_keep] = 0
|
| 387 |
+
# unshuffle to get the binary mask
|
| 388 |
+
mask = torch.gather(mask, dim=1, index=ids_restore)
|
| 389 |
+
|
| 390 |
+
pred = model.forward_decoder(lats, ids_restore)
|
| 391 |
+
recon_loss = model.forward_loss(input_func, pred, mask)
|
| 392 |
+
|
| 393 |
+
recon_losses.append(recon_loss.item())
|
| 394 |
+
loss = recon_loss
|
| 395 |
+
else:
|
| 396 |
+
recon_loss = torch.nan
|
| 397 |
+
recon_losses.append(recon_loss)
|
| 398 |
+
loss = 0
|
| 399 |
+
|
| 400 |
+
if use_contrastive_loss and not use_vic_loss:
|
| 401 |
+
ids_keep2 = ids_shuffle[:, :2*len_keep]
|
| 402 |
+
lats2 = model.forward_encoder_with_mask(input_func, ids_keep2)
|
| 403 |
+
|
| 404 |
+
enc_cls_token = lats[:, :1]
|
| 405 |
+
enc_cls_token2 = lats2[:,:1]
|
| 406 |
+
|
| 407 |
+
temp = contrastive_temps[epoch]
|
| 408 |
+
|
| 409 |
+
all_cls = torch.cat([enc_cls_token, enc_cls_token2], dim=0)
|
| 410 |
+
all_cls_proj = model.simclr_handler(all_cls)
|
| 411 |
+
|
| 412 |
+
contr_loss = SimCLRHandler.simclr_loss(all_cls_proj, temp)
|
| 413 |
+
|
| 414 |
+
contrastive_losses.append(contr_loss.item())
|
| 415 |
+
loss += (contr_loss * contrastive_loss_weight)
|
| 416 |
+
# new loss
|
| 417 |
+
elif use_vic_loss:
|
| 418 |
+
ids_keep2 = ids_shuffle[:, :2*len_keep]
|
| 419 |
+
lats2 = model.forward_encoder_with_mask(input_func, ids_keep2)
|
| 420 |
+
|
| 421 |
+
enc_cls_token = lats[:, :1]
|
| 422 |
+
enc_cls_token2 = lats2[:,:1]
|
| 423 |
+
|
| 424 |
+
l1 = lats
|
| 425 |
+
l2 = lats2[:, :lats.shape[1]]
|
| 426 |
+
|
| 427 |
+
l1_proj = model.vicreg_handler(l1)
|
| 428 |
+
l2_proj = model.vicreg_handler(l2)
|
| 429 |
+
|
| 430 |
+
vic_loss = VICRegHandler.vicreg_loss(l1_proj, l2_proj, gamma=gamma, lamda=lamda, mu=mu, nu=nu,
|
| 431 |
+
rand_frac=rand_frac, use_vic_cls=use_vic_cls)
|
| 432 |
+
|
| 433 |
+
if use_contrastive_loss:
|
| 434 |
+
temp = contrastive_temps[epoch]
|
| 435 |
+
all_cls = torch.cat([enc_cls_token, enc_cls_token2], dim=0)
|
| 436 |
+
all_cls_proj = model.simclr_handler(all_cls)
|
| 437 |
+
|
| 438 |
+
contr_loss = SimCLRHandler.simclr_loss(all_cls_proj, temp)
|
| 439 |
+
contrastive_losses.append(contr_loss.item())
|
| 440 |
+
else:
|
| 441 |
+
contr_loss = 0
|
| 442 |
+
contrastive_losses.append(0)
|
| 443 |
+
|
| 444 |
+
vic_losses.append(vic_loss.item())
|
| 445 |
+
loss += (contr_loss * contrastive_loss_weight + vic_loss * vic_loss_weight)
|
| 446 |
+
else:
|
| 447 |
+
vic_losses.append(0)
|
| 448 |
+
contrastive_losses.append(0)
|
| 449 |
+
|
| 450 |
+
grad_scaler.scale(loss).backward()
|
| 451 |
+
grad_scaler.unscale_(optimizer)
|
| 452 |
+
if grad_clip:
|
| 453 |
+
nn.utils.clip_grad_norm_(model.parameters(), max_norm=grad_clip)
|
| 454 |
+
grad_scaler.step(optimizer)
|
| 455 |
+
grad_scaler.update()
|
| 456 |
+
torch.cuda.synchronize()
|
| 457 |
+
|
| 458 |
+
cos_sim_encoder_output_patchwise.append(utils.patchwise_cosine_similarity(lats)[~torch.eye(lats.shape[1], dtype=bool)[None].expand(lats.shape[0],-1,-1)].mean().item())
|
| 459 |
+
cos_sim_encoder_output.append(utils.batchwise_cosine_similarity(lats.flatten(1)/1e3,lats.flatten(1)/1e3)[~torch.eye(len(lats),dtype=torch.bool)].mean().item())
|
| 460 |
+
if use_decoder:
|
| 461 |
+
cos_sim_decoder_output.append(utils.batchwise_cosine_similarity(pred, pred)[~torch.eye(len(pred),dtype=torch.bool)].mean().item())
|
| 462 |
+
|
| 463 |
+
lrs.append(optimizer.param_groups[0]["lr"])
|
| 464 |
+
train_losses.append(loss.item())
|
| 465 |
+
|
| 466 |
+
if ((train_i%print_interval)==0 or (train_i==num_batches-1)) and train_i>0:
|
| 467 |
+
print(f"Ep. {epoch} | loss {np.mean(train_losses[-print_interval:]):.3f} | lr {optimizer.param_groups[0]['lr']} | {train_i}/{num_batches}")
|
| 468 |
+
if wandb_log:
|
| 469 |
+
logs = {"train/loss": np.mean(train_losses[-print_interval:])}
|
| 470 |
+
# epoch_1000x as x-axis calibrates different curves when batch size changes
|
| 471 |
+
epoch_1000x = int((train_i / num_batches + epoch) * 1000)
|
| 472 |
+
wandb.log(logs, step=epoch_1000x)
|
| 473 |
+
|
| 474 |
+
if utils.is_interactive() or wandb_log:
|
| 475 |
+
print(f"Ep. {epoch} | loss {np.mean(train_losses[-print_interval:]):.3f} | lr {optimizer.param_groups[0]['lr']} | {train_i}/{num_batches}")
|
| 476 |
+
with torch.no_grad():
|
| 477 |
+
if norm_pix_loss:
|
| 478 |
+
normed_input_func, patch_info = model.patchify(input_func, alter_patch_info=False, return_patch_info=True)
|
| 479 |
+
target_mean = normed_input_func.mean(dim=-1, keepdim=True)
|
| 480 |
+
target_var = normed_input_func.var(dim=-1, keepdim=True)
|
| 481 |
+
normed_input_func = (normed_input_func - target_mean) / (target_var + 1.0e-6) ** 0.5
|
| 482 |
+
normed_input_func = model.unpatchify(normed_input_func, patch_info=patch_info)
|
| 483 |
+
|
| 484 |
+
vis_out = vis.plot_mask_pred(
|
| 485 |
+
model, normed_input_func, pred, mask,
|
| 486 |
+
mean=0.5, std=0.2,
|
| 487 |
+
)
|
| 488 |
+
else:
|
| 489 |
+
vis_out = vis.plot_mask_pred(
|
| 490 |
+
model, input_func, pred, mask,
|
| 491 |
+
mean=0.5, std=0.2,
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
if wandb_log:
|
| 495 |
+
logs = {"train/mask_pred": wandb.Image(vis_out)}
|
| 496 |
+
epoch_1000x = int((train_i / num_batches + epoch) * 1000)
|
| 497 |
+
wandb.log(logs, step=epoch_1000x)
|
| 498 |
+
else:
|
| 499 |
+
display(vis_out)
|
| 500 |
+
|
| 501 |
+
logs = {
|
| 502 |
+
"train/loss": np.mean(train_losses[-(train_i + 1) :]),
|
| 503 |
+
"train/recon_losses": np.mean(recon_losses[-(train_i + 1) :]),
|
| 504 |
+
"train/contrastive_losses": np.mean(contrastive_losses[-(train_i + 1) :]),
|
| 505 |
+
"train/vic_losses": np.mean(vic_losses[-(train_i + 1) :]),
|
| 506 |
+
"train/num_steps": len(recon_losses),
|
| 507 |
+
"train/cos_sim_encoder_output": np.mean(cos_sim_encoder_output[-(train_i + 1) :]),
|
| 508 |
+
"train/cos_sim_decoder_output": np.mean(cos_sim_decoder_output[-(train_i + 1) :]) if use_decoder else np.nan,
|
| 509 |
+
"train/cos_sim_encoder_output_patchwise": np.mean(cos_sim_encoder_output_patchwise[-(train_i + 1) :]),
|
| 510 |
+
"lr": np.mean(lrs[-(train_i + 1) :]),
|
| 511 |
+
"epoch": epoch,
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
# Evaluate performance on held-out test dataset
|
| 515 |
+
model.eval()
|
| 516 |
+
with torch.no_grad():
|
| 517 |
+
for test_i, batch in enumerate(test_dl):
|
| 518 |
+
input_func = batch[1]
|
| 519 |
+
|
| 520 |
+
input_func = input_func.to(dtype=data_type, device=device, non_blocking=True)
|
| 521 |
+
if len(input_func.shape) == 6:
|
| 522 |
+
b, r, c, t, h, w = input_func.shape
|
| 523 |
+
input_func = input_func.reshape(b * r, c, t, h, w)
|
| 524 |
+
|
| 525 |
+
if not use_contrastive_loss:
|
| 526 |
+
loss, pred, mask, latent = model(input_func, mask_ratio=mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 527 |
+
else:
|
| 528 |
+
loss1, loss2, loss3, pred1, pred2, mask1, mask2, true_mask, latent1, latent2 = model(input_func, mask_ratio=mask_ratio, use_contrastive_loss=use_contrastive_loss)
|
| 529 |
+
pred, mask, latent = pred1, mask1, latent1
|
| 530 |
+
|
| 531 |
+
# contrastive loss
|
| 532 |
+
temp = contrastive_temps[epoch]
|
| 533 |
+
logits = (nn.functional.normalize(latent1[:,:1].flatten(1),dim=-1) @
|
| 534 |
+
nn.functional.normalize(latent2[:,:1].flatten(1),dim=-1).T) / temp
|
| 535 |
+
labels = torch.arange(len(logits)).long().to(device)
|
| 536 |
+
contr_loss = (crossentropy(logits, labels) + crossentropy(logits.T, labels)) / 2
|
| 537 |
+
|
| 538 |
+
loss = loss1 + loss2 + loss3 + contr_loss
|
| 539 |
+
|
| 540 |
+
test_losses.append(loss.item())
|
| 541 |
+
|
| 542 |
+
cos_sim_encoder_output_patchwise_test.append(utils.patchwise_cosine_similarity(latent)[~torch.eye(latent.shape[1], dtype=bool)[None].expand(latent.shape[0],-1,-1)].mean().item())
|
| 543 |
+
cos_sim_encoder_output_test.append(utils.batchwise_cosine_similarity(latent.flatten(1)/1e3, latent.flatten(1)/1e3)[~torch.eye(len(latent),dtype=torch.bool)].mean().item())
|
| 544 |
+
|
| 545 |
+
if test_i%print_interval==0 and test_i>0:
|
| 546 |
+
print(f"Test | loss {np.mean(test_losses[-print_interval:]):.3f} | {test_i}/{test_num_batches}")
|
| 547 |
+
|
| 548 |
+
print(f"Test | iter {test_i} | loss {np.mean(test_losses[-test_i:]):.3f}")
|
| 549 |
+
if wandb_log:
|
| 550 |
+
logs.update({
|
| 551 |
+
"test/loss": np.mean(test_losses[-test_i:]),
|
| 552 |
+
"test/cos_sim_encoder_output": np.mean(cos_sim_encoder_output_test[-(test_i + 1) :]),
|
| 553 |
+
"test/cos_sim_encoder_output_patchwise": np.mean(cos_sim_encoder_output_patchwise_test[-(test_i + 1) :]),
|
| 554 |
+
})
|
| 555 |
+
wandb.log(logs)
|
| 556 |
+
|
| 557 |
+
# Plot progress (first sample in batch)
|
| 558 |
+
if utils.is_interactive() or wandb_log:
|
| 559 |
+
with torch.no_grad():
|
| 560 |
+
if norm_pix_loss:
|
| 561 |
+
normed_input_func, patch_info = model.patchify(input_func, alter_patch_info=False, return_patch_info=True)
|
| 562 |
+
target_mean = normed_input_func.mean(dim=-1, keepdim=True)
|
| 563 |
+
target_var = normed_input_func.var(dim=-1, keepdim=True)
|
| 564 |
+
normed_input_func = (normed_input_func - target_mean) / (target_var + 1.0e-6) ** 0.5
|
| 565 |
+
normed_input_func = model.unpatchify(normed_input_func, patch_info=patch_info)
|
| 566 |
+
|
| 567 |
+
vis_out = vis.plot_mask_pred(
|
| 568 |
+
model, normed_input_func, pred, mask,
|
| 569 |
+
mean=0.5, std=0.2,
|
| 570 |
+
)
|
| 571 |
+
else:
|
| 572 |
+
vis_out = vis.plot_mask_pred(
|
| 573 |
+
model, input_func, pred, mask,
|
| 574 |
+
mean=0.5, std=0.2,
|
| 575 |
+
)
|
| 576 |
+
|
| 577 |
+
if wandb_log:
|
| 578 |
+
logs = {"test/mask_pred": wandb.Image(vis_out)}
|
| 579 |
+
wandb.log(logs)
|
| 580 |
+
else:
|
| 581 |
+
display(vis_out)
|
| 582 |
+
|
| 583 |
+
# Save model checkpoint
|
| 584 |
+
if ckpt_saving and epoch>0 and ((epoch % ckpt_interval == 0) or (epoch==num_epochs-1)):
|
| 585 |
+
save_ckpt(model,f"epoch{epoch}")
|
| 586 |
+
|
| 587 |
+
# wait for other GPUs to catch up if needed
|
| 588 |
+
if distributed: dist.barrier()
|
| 589 |
+
|
| 590 |
+
# close any open plots
|
| 591 |
+
plt.close()
|
| 592 |
+
|
| 593 |
+
if distributed: dist.destroy_process_group()
|
fMRI-foundation-model-old/flat/main2.ipynb
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 4,
|
| 6 |
+
"id": "a0834886-8336-4648-bd48-804b5e472b3d",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import os\n",
|
| 11 |
+
"import shutil\n",
|
| 12 |
+
"import sys\n",
|
| 13 |
+
"import json\n",
|
| 14 |
+
"import yaml\n",
|
| 15 |
+
"import numpy as np\n",
|
| 16 |
+
"import copy\n",
|
| 17 |
+
"import math\n",
|
| 18 |
+
"import time\n",
|
| 19 |
+
"import random\n",
|
| 20 |
+
"from tqdm import tqdm\n",
|
| 21 |
+
"import webdataset as wds\n",
|
| 22 |
+
"import matplotlib.pyplot as plt\n",
|
| 23 |
+
"import torch\n",
|
| 24 |
+
"import torch.nn as nn\n",
|
| 25 |
+
"from torchvision import transforms\n",
|
| 26 |
+
"import utils\n",
|
| 27 |
+
"from flat_models import *\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"# tf32 data type is faster than standard float32\n",
|
| 30 |
+
"torch.backends.cuda.matmul.allow_tf32 = True\n",
|
| 31 |
+
"# following fixes a Conv3D CUDNN_NOT_SUPPORTED error\n",
|
| 32 |
+
"torch.backends.cudnn.benchmark = True\n"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"cell_type": "code",
|
| 37 |
+
"execution_count": 5,
|
| 38 |
+
"id": "7a8e5e12-b9ac-4baf-91eb-b4c25740c578",
|
| 39 |
+
"metadata": {},
|
| 40 |
+
"outputs": [
|
| 41 |
+
{
|
| 42 |
+
"name": "stdout",
|
| 43 |
+
"output_type": "stream",
|
| 44 |
+
"text": [
|
| 45 |
+
"Number of available CUDA devices: 1\n",
|
| 46 |
+
"LOCAL RANK=0\n",
|
| 47 |
+
"NUM GPUS=1\n",
|
| 48 |
+
"NODE=0\n",
|
| 49 |
+
"GLOBAL RANK=0\n",
|
| 50 |
+
"WORLD_SIZE=1\n"
|
| 51 |
+
]
|
| 52 |
+
}
|
| 53 |
+
],
|
| 54 |
+
"source": [
|
| 55 |
+
"\n",
|
| 56 |
+
"### Multi-GPU config ###\n",
|
| 57 |
+
"device_count = torch.cuda.device_count()\n",
|
| 58 |
+
"print(f\"Number of available CUDA devices: {device_count}\")\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"local_rank = os.getenv('LOCAL_RANK')\n",
|
| 61 |
+
"if local_rank is None: \n",
|
| 62 |
+
" local_rank = 0\n",
|
| 63 |
+
"else:\n",
|
| 64 |
+
" local_rank = int(local_rank)\n",
|
| 65 |
+
"print(f\"LOCAL RANK={local_rank}\")\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"num_devices = os.getenv('NUM_GPUS')\n",
|
| 68 |
+
"if num_devices is None: \n",
|
| 69 |
+
" num_devices = 1\n",
|
| 70 |
+
"else:\n",
|
| 71 |
+
" num_devices = int(num_devices)\n",
|
| 72 |
+
"print(f\"NUM GPUS={num_devices}\")\n",
|
| 73 |
+
"distributed = True if num_devices>1 else False\n",
|
| 74 |
+
"if distributed: assert device_count==num_devices\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"node = os.getenv('SLURM_NODEID')\n",
|
| 77 |
+
"if node is None:\n",
|
| 78 |
+
" node = 0\n",
|
| 79 |
+
"else:\n",
|
| 80 |
+
" node = int(node)\n",
|
| 81 |
+
"print(f\"NODE={node}\")\n",
|
| 82 |
+
"\n",
|
| 83 |
+
"global_rank = os.getenv('RANK')\n",
|
| 84 |
+
"if global_rank is None:\n",
|
| 85 |
+
" global_rank = 0\n",
|
| 86 |
+
"else:\n",
|
| 87 |
+
" global_rank = int(global_rank)\n",
|
| 88 |
+
"print(f\"GLOBAL RANK={global_rank}\")\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"world_size = os.getenv('WORLD_SIZE')\n",
|
| 91 |
+
"if world_size is None: \n",
|
| 92 |
+
" world_size = 1\n",
|
| 93 |
+
"else:\n",
|
| 94 |
+
" world_size = int(world_size)\n",
|
| 95 |
+
"print(f\"WORLD_SIZE={world_size}\")"
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"cell_type": "code",
|
| 100 |
+
"execution_count": 6,
|
| 101 |
+
"id": "65ff5246-4ff5-457d-8509-646b319093c6",
|
| 102 |
+
"metadata": {},
|
| 103 |
+
"outputs": [
|
| 104 |
+
{
|
| 105 |
+
"name": "stdout",
|
| 106 |
+
"output_type": "stream",
|
| 107 |
+
"text": [
|
| 108 |
+
"\n",
|
| 109 |
+
"__CONFIG__\n",
|
| 110 |
+
"model_name = flat_NORM_contrastive_\n",
|
| 111 |
+
"hcp_flat_path = /weka/proj-medarc/shared/hcp_flat\n",
|
| 112 |
+
"use_contrastive_loss = True\n",
|
| 113 |
+
"use_decoder = True\n",
|
| 114 |
+
"patch_size = 16\n",
|
| 115 |
+
"decoder_embed_dim = 512\n",
|
| 116 |
+
"num_frames = 16\n",
|
| 117 |
+
"mask_ratio = 0.75\n",
|
| 118 |
+
"pred_t_dim = 8\n",
|
| 119 |
+
"t_patch_size = 2\n",
|
| 120 |
+
"cls_embed = True\n",
|
| 121 |
+
"no_qkv_bias = False\n",
|
| 122 |
+
"sep_pos_embed = True\n",
|
| 123 |
+
"trunc_init = False\n",
|
| 124 |
+
"norm_pix_loss = False\n",
|
| 125 |
+
"contrastive_loss_weight = 1.0\n",
|
| 126 |
+
"use_vic_loss = True\n",
|
| 127 |
+
"vic_loss_weight = 1.0\n",
|
| 128 |
+
"gamma = 0.5\n",
|
| 129 |
+
"mu = 25\n",
|
| 130 |
+
"lamda = 25\n",
|
| 131 |
+
"nu = 1\n",
|
| 132 |
+
"rand_frac = 0.1\n",
|
| 133 |
+
"use_vic_cls = True\n",
|
| 134 |
+
"pct_masks_to_decode = 1\n",
|
| 135 |
+
"batch_size = 32\n",
|
| 136 |
+
"num_workers = 10\n",
|
| 137 |
+
"num_epochs = 100\n",
|
| 138 |
+
"seed = 42\n",
|
| 139 |
+
"base_lr = 0.0003\n",
|
| 140 |
+
"num_samples_per_epoch = 200000\n",
|
| 141 |
+
"test_num_samples_per_epoch = 50000\n",
|
| 142 |
+
"grad_clip = 1.0\n",
|
| 143 |
+
"grad_accumulation_steps = 1\n",
|
| 144 |
+
"test_set = False\n",
|
| 145 |
+
"probe_num_samples_per_epoch = 100000\n",
|
| 146 |
+
"probe_num_epochs = 30\n",
|
| 147 |
+
"probe_batch_size = 8\n",
|
| 148 |
+
"probe_base_lr = 0.0003\n",
|
| 149 |
+
"ckpt_saving = True\n",
|
| 150 |
+
"ckpt_interval = 5\n",
|
| 151 |
+
"print_interval = 20\n",
|
| 152 |
+
"resume_from_ckpt = False\n",
|
| 153 |
+
"wandb_log = False\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"\n",
|
| 156 |
+
"outdir /weka/proj-fmri/ckadirt/fMRI-foundation-model/flat/checkpoints/flat_NORM_contrastive_\n"
|
| 157 |
+
]
|
| 158 |
+
}
|
| 159 |
+
],
|
| 160 |
+
"source": [
|
| 161 |
+
"\n",
|
| 162 |
+
"# Load parameters from yaml config\n",
|
| 163 |
+
"config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)\n",
|
| 164 |
+
"\n",
|
| 165 |
+
"print(\"\\n__CONFIG__\")\n",
|
| 166 |
+
"for attribute_name in config.keys():\n",
|
| 167 |
+
" print(f\"{attribute_name} = {config[attribute_name]}\")\n",
|
| 168 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 169 |
+
"print(\"\\n\")\n",
|
| 170 |
+
"\n",
|
| 171 |
+
"# Create outdir for ckpt and config.yaml\n",
|
| 172 |
+
"outdir = os.path.abspath(f'checkpoints/{model_name}')\n",
|
| 173 |
+
"print(\"outdir\", outdir)\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"# Load previous config.yaml if available\n",
|
| 176 |
+
"if os.path.exists(f\"{outdir}/config.yaml\"):\n",
|
| 177 |
+
" config = yaml.load(open(f\"{outdir}/config.yaml\", 'r'), Loader=yaml.FullLoader)\n",
|
| 178 |
+
" print(f\"Loaded config.yaml from ckpt folder {outdir}\")\n",
|
| 179 |
+
"\n",
|
| 180 |
+
" # create global variables from the config\n",
|
| 181 |
+
" print(\"\\n__REPLACING_CONFIG__\")\n",
|
| 182 |
+
" for attribute_name in config.keys():\n",
|
| 183 |
+
" print(f\"{attribute_name} = {config[attribute_name]}\")\n",
|
| 184 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 185 |
+
" print(\"\\n\")\n",
|
| 186 |
+
"\n",
|
| 187 |
+
"data_type = torch.float32 # change depending on your mixed_precision\n",
|
| 188 |
+
"global_batch_size = batch_size * world_size\n"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"cell_type": "code",
|
| 193 |
+
"execution_count": 7,
|
| 194 |
+
"id": "b5b30c27-04e9-4566-ac33-cc2d69e27711",
|
| 195 |
+
"metadata": {},
|
| 196 |
+
"outputs": [
|
| 197 |
+
{
|
| 198 |
+
"name": "stdout",
|
| 199 |
+
"output_type": "stream",
|
| 200 |
+
"text": [
|
| 201 |
+
"PID of this process = 925118\n",
|
| 202 |
+
"device = cuda distributed = False num_devices = 1 local rank = 0 world size = 1 data_type = torch.float32\n"
|
| 203 |
+
]
|
| 204 |
+
}
|
| 205 |
+
],
|
| 206 |
+
"source": [
|
| 207 |
+
"\n",
|
| 208 |
+
"# FSDP Setup\n",
|
| 209 |
+
"if distributed:\n",
|
| 210 |
+
" import torch.distributed as dist\n",
|
| 211 |
+
" from torch.distributed.fsdp import FullyShardedDataParallel as FSDP\n",
|
| 212 |
+
" from torch.distributed.fsdp.api import BackwardPrefetch, CPUOffload, ShardingStrategy\n",
|
| 213 |
+
" import functools\n",
|
| 214 |
+
" from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy\n",
|
| 215 |
+
" print(f\"setting device to cuda:{local_rank}\")\n",
|
| 216 |
+
" torch.cuda.set_device(local_rank)\n",
|
| 217 |
+
" device = torch.device('cuda',local_rank)\n",
|
| 218 |
+
" dist.init_process_group(\"nccl\", rank=global_rank, world_size=world_size)\n",
|
| 219 |
+
" print(f\"\\nSuccessfully set cuda:{local_rank} | global_rank{global_rank} | node{node}\")\n",
|
| 220 |
+
" dist.barrier() \n",
|
| 221 |
+
" print(f\"global_rank{global_rank} passed barrier\")\n",
|
| 222 |
+
"else:\n",
|
| 223 |
+
" device = torch.device('cuda')\n",
|
| 224 |
+
"\n",
|
| 225 |
+
"print(\"PID of this process =\",os.getpid())\n",
|
| 226 |
+
"print(\"device =\", device, \"distributed =\",distributed, \"num_devices =\", num_devices, \"local rank =\", local_rank, \"world size =\", world_size, \"data_type =\", data_type)\n",
|
| 227 |
+
"\n",
|
| 228 |
+
"# seed all random functions\n",
|
| 229 |
+
"utils.seed_everything(seed + global_rank)"
|
| 230 |
+
]
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"cell_type": "code",
|
| 234 |
+
"execution_count": 8,
|
| 235 |
+
"id": "ad692bcb-a166-46d5-97f7-292c91cd4443",
|
| 236 |
+
"metadata": {},
|
| 237 |
+
"outputs": [
|
| 238 |
+
{
|
| 239 |
+
"name": "stdout",
|
| 240 |
+
"output_type": "stream",
|
| 241 |
+
"text": [
|
| 242 |
+
"img_size (144, 320) patch_size (16, 16) frames 16 t_patch_size 2\n",
|
| 243 |
+
"model initialized\n",
|
| 244 |
+
"num_batches 62\n",
|
| 245 |
+
"test_num_batches 62\n"
|
| 246 |
+
]
|
| 247 |
+
}
|
| 248 |
+
],
|
| 249 |
+
"source": [
|
| 250 |
+
"\n",
|
| 251 |
+
"from util.hcp_flat import load_hcp_flat_mask\n",
|
| 252 |
+
"from util.hcp_flat import create_hcp_flat\n",
|
| 253 |
+
"from util.losses import *\n",
|
| 254 |
+
"import util.visualize as vis\n",
|
| 255 |
+
"\n",
|
| 256 |
+
"if utils.is_interactive(): # Use less samples per epoch for debugging\n",
|
| 257 |
+
" num_samples_per_epoch = 2000\n",
|
| 258 |
+
" test_num_samples_per_epoch = 2000\n",
|
| 259 |
+
"\n",
|
| 260 |
+
"model = mae_vit_small_fmri(\n",
|
| 261 |
+
" decoder_embed_dim=decoder_embed_dim,\n",
|
| 262 |
+
" t_patch_size=t_patch_size,\n",
|
| 263 |
+
" pred_t_dim=pred_t_dim,\n",
|
| 264 |
+
" decoder_depth=4,\n",
|
| 265 |
+
" cls_embed=cls_embed,\n",
|
| 266 |
+
" norm_pix_loss=norm_pix_loss,\n",
|
| 267 |
+
" no_qkv_bias=no_qkv_bias,\n",
|
| 268 |
+
" sep_pos_embed=sep_pos_embed,\n",
|
| 269 |
+
" trunc_init=trunc_init,\n",
|
| 270 |
+
")\n",
|
| 271 |
+
"\n",
|
| 272 |
+
"if use_contrastive_loss:\n",
|
| 273 |
+
" model.simclr_handler = SimCLRHandler(model.embed_dim).to(device)\n",
|
| 274 |
+
"if use_vic_loss:\n",
|
| 275 |
+
" model.vicreg_handler = VICRegHandler(model.embed_dim).to(device)\n",
|
| 276 |
+
"\n",
|
| 277 |
+
"# state = torch.load(\"checkpoints/checkpoint-00099.pth\", map_location=\"cpu\")\n",
|
| 278 |
+
"# model.load_state_dict(state[\"model\"])\n",
|
| 279 |
+
"\n",
|
| 280 |
+
"num_batches = num_samples_per_epoch // (num_devices * batch_size)\n",
|
| 281 |
+
"test_num_batches = test_num_samples_per_epoch // (num_devices * batch_size)\n",
|
| 282 |
+
"print(\"num_batches\", num_batches)\n",
|
| 283 |
+
"print(\"test_num_batches\", test_num_batches)"
|
| 284 |
+
]
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"cell_type": "code",
|
| 288 |
+
"execution_count": 9,
|
| 289 |
+
"id": "63da3ad3-116f-498d-ae01-d75585b010f2",
|
| 290 |
+
"metadata": {},
|
| 291 |
+
"outputs": [],
|
| 292 |
+
"source": [
|
| 293 |
+
"## Train ##\n",
|
| 294 |
+
"train_dataset = create_hcp_flat(root=hcp_flat_path, \n",
|
| 295 |
+
" training=True, frames=num_frames)\n",
|
| 296 |
+
"train_dl = wds.WebLoader(\n",
|
| 297 |
+
" train_dataset.batched(batch_size, partial=False),\n",
|
| 298 |
+
" batch_size=None,\n",
|
| 299 |
+
" shuffle=False,\n",
|
| 300 |
+
" num_workers=num_workers,\n",
|
| 301 |
+
" pin_memory=True,\n",
|
| 302 |
+
")\n",
|
| 303 |
+
"train_dl = train_dl.with_epoch(num_batches)\n",
|
| 304 |
+
"\n",
|
| 305 |
+
"## Test ##\n",
|
| 306 |
+
"test_dataset = create_hcp_flat(root=hcp_flat_path,\n",
|
| 307 |
+
" training=False, frames=num_frames)\n",
|
| 308 |
+
"test_dl = wds.WebLoader(\n",
|
| 309 |
+
" test_dataset.batched(batch_size, partial=False),\n",
|
| 310 |
+
" batch_size=None,\n",
|
| 311 |
+
" shuffle=False,\n",
|
| 312 |
+
" num_workers=num_workers,\n",
|
| 313 |
+
" pin_memory=True,\n",
|
| 314 |
+
")\n",
|
| 315 |
+
"test_dl = test_dl.with_epoch(test_num_batches)"
|
| 316 |
+
]
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"cell_type": "code",
|
| 320 |
+
"execution_count": 10,
|
| 321 |
+
"id": "9ad20649-e51e-4e5c-9fb4-f78d33edaea0",
|
| 322 |
+
"metadata": {},
|
| 323 |
+
"outputs": [
|
| 324 |
+
{
|
| 325 |
+
"name": "stdout",
|
| 326 |
+
"output_type": "stream",
|
| 327 |
+
"text": [
|
| 328 |
+
"\n",
|
| 329 |
+
"Checking distributed setup on global_rank 0...\n",
|
| 330 |
+
"\n",
|
| 331 |
+
"Not training distributed! global_rank 0\n",
|
| 332 |
+
"multiply base lr 0.0003 by effective batch size 32\n",
|
| 333 |
+
"lr = 3.75e-05\n"
|
| 334 |
+
]
|
| 335 |
+
}
|
| 336 |
+
],
|
| 337 |
+
"source": [
|
| 338 |
+
"\n",
|
| 339 |
+
"print(f\"\\nChecking distributed setup on global_rank {global_rank}...\")\n",
|
| 340 |
+
"from util.video_vit import Attention\n",
|
| 341 |
+
"if distributed:\n",
|
| 342 |
+
" my_auto_wrap_policy = functools.partial(\n",
|
| 343 |
+
" transformer_auto_wrap_policy, \n",
|
| 344 |
+
" transformer_layer_cls={\n",
|
| 345 |
+
" Attention, # <--- Your Transformer layer class\n",
|
| 346 |
+
" },\n",
|
| 347 |
+
" )\n",
|
| 348 |
+
" print(f\"\\nPrepping FSDP on {global_rank} {node}...\\n\")\n",
|
| 349 |
+
" model = model.to(device)\n",
|
| 350 |
+
" model = FSDP(\n",
|
| 351 |
+
" model,\n",
|
| 352 |
+
" sharding_strategy=ShardingStrategy.HYBRID_SHARD,\n",
|
| 353 |
+
" auto_wrap_policy=my_auto_wrap_policy,\n",
|
| 354 |
+
" use_orig_params=False,\n",
|
| 355 |
+
" cpu_offload=None, #CPUOffload(offload_params=True)\n",
|
| 356 |
+
" sync_module_states=True,\n",
|
| 357 |
+
" limit_all_gathers=True, # See https://github.com/pytorch/pytorch/issues/91165\n",
|
| 358 |
+
" device_id=device,\n",
|
| 359 |
+
" )\n",
|
| 360 |
+
" print(f\"\\nSuccessfully loaded FSDP model to device on global_rank {global_rank}\\n\")\n",
|
| 361 |
+
" dist.barrier()\n",
|
| 362 |
+
"else:\n",
|
| 363 |
+
" print(f\"\\nNot training distributed! global_rank {global_rank}\")\n",
|
| 364 |
+
" model.to(device)\n",
|
| 365 |
+
"\n",
|
| 366 |
+
"no_decay = [\"bias\", \"LayerNorm.bias\", \"LayerNorm.weight\"]\n",
|
| 367 |
+
"opt_grouped_parameters = [\n",
|
| 368 |
+
" {'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.05},\n",
|
| 369 |
+
" {'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},\n",
|
| 370 |
+
"]\n",
|
| 371 |
+
"\n",
|
| 372 |
+
"lr = base_lr * global_batch_size / 256\n",
|
| 373 |
+
"print(f\"multiply base lr {base_lr} by effective batch size {global_batch_size}\")\n",
|
| 374 |
+
"print(f\"lr = {lr}\")\n",
|
| 375 |
+
"\n",
|
| 376 |
+
"optimizer = torch.optim.AdamW(opt_grouped_parameters, lr=lr, betas=(0.9, 0.95))"
|
| 377 |
+
]
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"cell_type": "code",
|
| 381 |
+
"execution_count": 11,
|
| 382 |
+
"id": "cef70e06-d181-46c0-a36c-12aa944a8441",
|
| 383 |
+
"metadata": {},
|
| 384 |
+
"outputs": [
|
| 385 |
+
{
|
| 386 |
+
"name": "stdout",
|
| 387 |
+
"output_type": "stream",
|
| 388 |
+
"text": [
|
| 389 |
+
"\n",
|
| 390 |
+
"Done with model preparations!\n",
|
| 391 |
+
"param counts:\n",
|
| 392 |
+
"40,789,504 total\n",
|
| 393 |
+
"40,789,504 trainable\n"
|
| 394 |
+
]
|
| 395 |
+
}
|
| 396 |
+
],
|
| 397 |
+
"source": [
|
| 398 |
+
"\n",
|
| 399 |
+
"def adjust_learning_rate(optimizer, epoch, warmup_epochs=5, min_lr=0.0):\n",
|
| 400 |
+
" \"\"\"Decay the learning rate with half-cycle cosine after warmup\"\"\"\n",
|
| 401 |
+
" if epoch < warmup_epochs:\n",
|
| 402 |
+
" lr_ = lr * epoch / warmup_epochs\n",
|
| 403 |
+
" else:\n",
|
| 404 |
+
" lr_ = min_lr + (lr - min_lr) * 0.5 * (\n",
|
| 405 |
+
" 1.0\n",
|
| 406 |
+
" + math.cos(\n",
|
| 407 |
+
" math.pi\n",
|
| 408 |
+
" * (epoch - warmup_epochs)\n",
|
| 409 |
+
" / (num_epochs - warmup_epochs)\n",
|
| 410 |
+
" )\n",
|
| 411 |
+
" )\n",
|
| 412 |
+
" for param_group in optimizer.param_groups:\n",
|
| 413 |
+
" param_group[\"lr\"] = lr_\n",
|
| 414 |
+
" return lr_\n",
|
| 415 |
+
"\n",
|
| 416 |
+
"print(\"\\nDone with model preparations!\")\n",
|
| 417 |
+
"num_params = utils.count_params(model)\n"
|
| 418 |
+
]
|
| 419 |
+
},
|
| 420 |
+
{
|
| 421 |
+
"cell_type": "code",
|
| 422 |
+
"execution_count": null,
|
| 423 |
+
"id": "1deff37d-c9c6-48e8-b1c4-1cec41f23fca",
|
| 424 |
+
"metadata": {},
|
| 425 |
+
"outputs": [],
|
| 426 |
+
"source": []
|
| 427 |
+
}
|
| 428 |
+
],
|
| 429 |
+
"metadata": {
|
| 430 |
+
"kernelspec": {
|
| 431 |
+
"display_name": "Python 3 (ipykernel)",
|
| 432 |
+
"language": "python",
|
| 433 |
+
"name": "python3"
|
| 434 |
+
},
|
| 435 |
+
"language_info": {
|
| 436 |
+
"codemirror_mode": {
|
| 437 |
+
"name": "ipython",
|
| 438 |
+
"version": 3
|
| 439 |
+
},
|
| 440 |
+
"file_extension": ".py",
|
| 441 |
+
"mimetype": "text/x-python",
|
| 442 |
+
"name": "python",
|
| 443 |
+
"nbconvert_exporter": "python",
|
| 444 |
+
"pygments_lexer": "ipython3",
|
| 445 |
+
"version": "3.10.14"
|
| 446 |
+
}
|
| 447 |
+
},
|
| 448 |
+
"nbformat": 4,
|
| 449 |
+
"nbformat_minor": 5
|
| 450 |
+
}
|
fMRI-foundation-model-old/flat/parquet_probe.ipynb
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "b8e236f1-385a-4d93-bb39-bea3ee384d76",
|
| 7 |
+
"metadata": {
|
| 8 |
+
"tags": []
|
| 9 |
+
},
|
| 10 |
+
"outputs": [
|
| 11 |
+
{
|
| 12 |
+
"name": "stdout",
|
| 13 |
+
"output_type": "stream",
|
| 14 |
+
"text": [
|
| 15 |
+
"outdir /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/clane\n",
|
| 16 |
+
"Loaded config.yaml from ckpt folder /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/clane\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"__CONFIG__\n",
|
| 19 |
+
"base_lr = 0.001\n",
|
| 20 |
+
"batch_size = 32\n",
|
| 21 |
+
"ckpt_interval = 5\n",
|
| 22 |
+
"ckpt_saving = True\n",
|
| 23 |
+
"cls_embed = True\n",
|
| 24 |
+
"decoder_embed_dim = 512\n",
|
| 25 |
+
"grad_clip = 1.0\n",
|
| 26 |
+
"hcp_flat_path = /weka/proj-fmri/shared/hcp_flat\n",
|
| 27 |
+
"mask_ratio = 0.75\n",
|
| 28 |
+
"model_name = clane\n",
|
| 29 |
+
"no_qkv_bias = False\n",
|
| 30 |
+
"norm_pix_loss = False\n",
|
| 31 |
+
"num_epochs = 50\n",
|
| 32 |
+
"num_frames = 16\n",
|
| 33 |
+
"num_samples_per_epoch = 200000\n",
|
| 34 |
+
"num_workers = 10\n",
|
| 35 |
+
"pred_t_dim = 8\n",
|
| 36 |
+
"print_interval = 20\n",
|
| 37 |
+
"probe_num_samples_per_epoch = 100000\n",
|
| 38 |
+
"probe_num_epochs = 30\n",
|
| 39 |
+
"probe_batch_size = 8\n",
|
| 40 |
+
"probe_base_lr = 0.0003\n",
|
| 41 |
+
"resume_from_ckpt = True\n",
|
| 42 |
+
"seed = 42\n",
|
| 43 |
+
"sep_pos_embed = True\n",
|
| 44 |
+
"t_patch_size = 2\n",
|
| 45 |
+
"test_num_samples_per_epoch = 50000\n",
|
| 46 |
+
"trunc_init = False\n",
|
| 47 |
+
"use_contrastive_loss = False\n",
|
| 48 |
+
"wandb_log = True\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"Number of available CUDA devices: 1\n",
|
| 52 |
+
"LOCAL RANK=0\n",
|
| 53 |
+
"NUM GPUS=1\n",
|
| 54 |
+
"NODE=0\n",
|
| 55 |
+
"GLOBAL RANK=0\n",
|
| 56 |
+
"WORLD_SIZE=1\n",
|
| 57 |
+
"PID of this process = 448416\n",
|
| 58 |
+
"device = cuda distributed = False num_devices = 1 local rank = 0 world size = 1 data_type = torch.float32\n"
|
| 59 |
+
]
|
| 60 |
+
}
|
| 61 |
+
],
|
| 62 |
+
"source": [
|
| 63 |
+
"# Import packages and setup gpu configuration.\n",
|
| 64 |
+
"# This code block shouldnt need to be adjusted!\n",
|
| 65 |
+
"import os\n",
|
| 66 |
+
"import sys\n",
|
| 67 |
+
"import json\n",
|
| 68 |
+
"import yaml\n",
|
| 69 |
+
"import numpy as np\n",
|
| 70 |
+
"import copy\n",
|
| 71 |
+
"import math\n",
|
| 72 |
+
"import time\n",
|
| 73 |
+
"import random\n",
|
| 74 |
+
"from tqdm import tqdm\n",
|
| 75 |
+
"import webdataset as wds\n",
|
| 76 |
+
"import matplotlib.pyplot as plt\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"import torch\n",
|
| 79 |
+
"import torch.nn as nn\n",
|
| 80 |
+
"from torchvision import transforms\n",
|
| 81 |
+
"import utils\n",
|
| 82 |
+
"from flat_models import *\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"# tf32 data type is faster than standard float32\n",
|
| 85 |
+
"torch.backends.cuda.matmul.allow_tf32 = True\n",
|
| 86 |
+
"# following fixes a Conv3D CUDNN_NOT_SUPPORTED error\n",
|
| 87 |
+
"torch.backends.cudnn.benchmark = True\n",
|
| 88 |
+
"\n",
|
| 89 |
+
"## MODEL TO LOAD ##\n",
|
| 90 |
+
"model_name = \"clane\"\n",
|
| 91 |
+
"parquet_folder = \"epoch99\"\n",
|
| 92 |
+
"outdir = os.path.abspath(f'checkpoints/{model_name}')\n",
|
| 93 |
+
"print(\"outdir\", outdir)\n",
|
| 94 |
+
"# Load previous config.yaml if available\n",
|
| 95 |
+
"if os.path.exists(f\"{outdir}/config.yaml\"):\n",
|
| 96 |
+
" config = yaml.load(open(f\"{outdir}/config.yaml\", 'r'), Loader=yaml.FullLoader)\n",
|
| 97 |
+
" print(f\"Loaded config.yaml from ckpt folder {outdir}\")\n",
|
| 98 |
+
" # create global variables from the config\n",
|
| 99 |
+
" print(\"\\n__CONFIG__\")\n",
|
| 100 |
+
" for attribute_name in config.keys():\n",
|
| 101 |
+
" print(f\"{attribute_name} = {config[attribute_name]}\")\n",
|
| 102 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 103 |
+
" print(\"\\n\")\n",
|
| 104 |
+
"\n",
|
| 105 |
+
"### Multi-GPU config ###\n",
|
| 106 |
+
"device_count = torch.cuda.device_count()\n",
|
| 107 |
+
"print(f\"Number of available CUDA devices: {device_count}\")\n",
|
| 108 |
+
"\n",
|
| 109 |
+
"local_rank = os.getenv('LOCAL_RANK')\n",
|
| 110 |
+
"if local_rank is None: \n",
|
| 111 |
+
" local_rank = 0\n",
|
| 112 |
+
"else:\n",
|
| 113 |
+
" local_rank = int(local_rank)\n",
|
| 114 |
+
"print(f\"LOCAL RANK={local_rank}\")\n",
|
| 115 |
+
"\n",
|
| 116 |
+
"num_devices = os.getenv('NUM_GPUS')\n",
|
| 117 |
+
"if num_devices is None: \n",
|
| 118 |
+
" num_devices = 1\n",
|
| 119 |
+
"else:\n",
|
| 120 |
+
" num_devices = int(num_devices)\n",
|
| 121 |
+
"print(f\"NUM GPUS={num_devices}\")\n",
|
| 122 |
+
"distributed = True if num_devices>1 else False\n",
|
| 123 |
+
"if distributed: assert device_count==num_devices\n",
|
| 124 |
+
"\n",
|
| 125 |
+
"node = os.getenv('SLURM_NODEID')\n",
|
| 126 |
+
"if node is None:\n",
|
| 127 |
+
" node = 0\n",
|
| 128 |
+
"else:\n",
|
| 129 |
+
" node = int(node)\n",
|
| 130 |
+
"print(f\"NODE={node}\")\n",
|
| 131 |
+
"\n",
|
| 132 |
+
"global_rank = os.getenv('RANK')\n",
|
| 133 |
+
"if global_rank is None:\n",
|
| 134 |
+
" global_rank = 0\n",
|
| 135 |
+
"else:\n",
|
| 136 |
+
" global_rank = int(global_rank)\n",
|
| 137 |
+
"print(f\"GLOBAL RANK={global_rank}\")\n",
|
| 138 |
+
"\n",
|
| 139 |
+
"world_size = os.getenv('WORLD_SIZE')\n",
|
| 140 |
+
"if world_size is None: \n",
|
| 141 |
+
" world_size = 1\n",
|
| 142 |
+
"else:\n",
|
| 143 |
+
" world_size = int(world_size)\n",
|
| 144 |
+
"print(f\"WORLD_SIZE={world_size}\")\n",
|
| 145 |
+
"\n",
|
| 146 |
+
"if utils.is_interactive():\n",
|
| 147 |
+
" # Following allows you to change functions in models.py or utils.py and \n",
|
| 148 |
+
" # have this notebook automatically update with your revisions\n",
|
| 149 |
+
" %load_ext autoreload\n",
|
| 150 |
+
" %autoreload 2\n",
|
| 151 |
+
"\n",
|
| 152 |
+
"batch_size = probe_batch_size\n",
|
| 153 |
+
"num_epochs = probe_num_epochs\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"data_type = torch.float32 # change depending on your mixed_precision\n",
|
| 156 |
+
"global_batch_size = batch_size * world_size\n",
|
| 157 |
+
"\n",
|
| 158 |
+
"# FSDP Setup\n",
|
| 159 |
+
"if distributed:\n",
|
| 160 |
+
" import torch.distributed as dist\n",
|
| 161 |
+
" import torch.multiprocessing as mp\n",
|
| 162 |
+
" from torch.distributed.fsdp import FullyShardedDataParallel as FSDP\n",
|
| 163 |
+
" from torch.distributed.fsdp.api import BackwardPrefetch, CPUOffload, ShardingStrategy\n",
|
| 164 |
+
" import functools\n",
|
| 165 |
+
" from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy\n",
|
| 166 |
+
" print(f\"setting device to cuda:{local_rank}\")\n",
|
| 167 |
+
" torch.cuda.set_device(local_rank)\n",
|
| 168 |
+
" device = torch.device('cuda',local_rank)\n",
|
| 169 |
+
" dist.init_process_group(\"nccl\", rank=global_rank, world_size=world_size)\n",
|
| 170 |
+
" print(f\"\\nSuccessfully set cuda:{local_rank} | global_rank{global_rank} | node{node}\")\n",
|
| 171 |
+
" dist.barrier()\n",
|
| 172 |
+
" print(f\"global_rank{global_rank} passed barrier\")\n",
|
| 173 |
+
"else:\n",
|
| 174 |
+
" device = torch.device('cuda')\n",
|
| 175 |
+
"\n",
|
| 176 |
+
"print(\"PID of this process =\",os.getpid())\n",
|
| 177 |
+
"print(\"device =\", device, \"distributed =\",distributed, \"num_devices =\", num_devices, \"local rank =\", local_rank, \"world size =\", world_size, \"data_type =\", data_type)\n",
|
| 178 |
+
"\n",
|
| 179 |
+
"# seed all random functions\n",
|
| 180 |
+
"utils.seed_everything(seed + global_rank)"
|
| 181 |
+
]
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"cell_type": "markdown",
|
| 185 |
+
"id": "ab15aca0-148e-435f-b8f2-7a708b61a6d9",
|
| 186 |
+
"metadata": {},
|
| 187 |
+
"source": [
|
| 188 |
+
"# hcp_flat"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"cell_type": "code",
|
| 193 |
+
"execution_count": 2,
|
| 194 |
+
"id": "de1a5b87-fa69-44e1-bdb9-bd9e257485c6",
|
| 195 |
+
"metadata": {
|
| 196 |
+
"tags": []
|
| 197 |
+
},
|
| 198 |
+
"outputs": [
|
| 199 |
+
{
|
| 200 |
+
"name": "stdout",
|
| 201 |
+
"output_type": "stream",
|
| 202 |
+
"text": [
|
| 203 |
+
"img_size (144, 320) patch_size (16, 16) frames 16 t_patch_size 2\n",
|
| 204 |
+
"model initialized\n",
|
| 205 |
+
"\n",
|
| 206 |
+
"Loaded checkpoint epoch99.pth from /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/clane\n",
|
| 207 |
+
"\n"
|
| 208 |
+
]
|
| 209 |
+
}
|
| 210 |
+
],
|
| 211 |
+
"source": [
|
| 212 |
+
"from util.hcp_flat import load_hcp_flat_mask\n",
|
| 213 |
+
"from util.hcp_flat import create_hcp_flat\n",
|
| 214 |
+
"import util.visualize as vis\n",
|
| 215 |
+
"\n",
|
| 216 |
+
"if utils.is_interactive(): # Use less samples per epoch for debugging\n",
|
| 217 |
+
" probe_num_samples_per_epoch = 100000\n",
|
| 218 |
+
" test_num_samples_per_epoch = 100000\n",
|
| 219 |
+
" num_epochs = 10\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"model = mae_vit_small_fmri(\n",
|
| 222 |
+
" patch_size=16,\n",
|
| 223 |
+
" decoder_embed_dim=decoder_embed_dim,\n",
|
| 224 |
+
" t_patch_size=t_patch_size,\n",
|
| 225 |
+
" pred_t_dim=pred_t_dim,\n",
|
| 226 |
+
" decoder_depth=4,\n",
|
| 227 |
+
" cls_embed=cls_embed,\n",
|
| 228 |
+
" norm_pix_loss=norm_pix_loss,\n",
|
| 229 |
+
" no_qkv_bias=no_qkv_bias,\n",
|
| 230 |
+
" sep_pos_embed=sep_pos_embed,\n",
|
| 231 |
+
" trunc_init=trunc_init,\n",
|
| 232 |
+
")\n",
|
| 233 |
+
"\n",
|
| 234 |
+
"# Load ckpt\n",
|
| 235 |
+
"if not os.path.exists(outdir) or not os.path.isdir(outdir):\n",
|
| 236 |
+
" print(f\"\\nCheckpoint folder {outdir} does not exist.\\n\")\n",
|
| 237 |
+
"else:\n",
|
| 238 |
+
" checkpoint_files = [f for f in os.listdir(outdir) if f.endswith('.pth')]\n",
|
| 239 |
+
"\n",
|
| 240 |
+
" # Find the latest ckpt to load\n",
|
| 241 |
+
" epoch_numbers = []\n",
|
| 242 |
+
" for file in checkpoint_files:\n",
|
| 243 |
+
" try:\n",
|
| 244 |
+
" epoch_number = int(file.split('epoch')[-1].split('.')[0])\n",
|
| 245 |
+
" epoch_numbers.append(epoch_number)\n",
|
| 246 |
+
" except ValueError:\n",
|
| 247 |
+
" continue\n",
|
| 248 |
+
" latest_epoch = max(epoch_numbers)\n",
|
| 249 |
+
" checkpoint_name = f\"epoch{latest_epoch}.pth\"\n",
|
| 250 |
+
" \n",
|
| 251 |
+
" ### Or provide the specific checkpoint you want to load\n",
|
| 252 |
+
" # checkpoint_name = \"epoch10.pth\" #\"epoch15.pth\"\n",
|
| 253 |
+
"\n",
|
| 254 |
+
" # Load the checkpoint\n",
|
| 255 |
+
" checkpoint_path = os.path.join(outdir, checkpoint_name)\n",
|
| 256 |
+
" state = torch.load(checkpoint_path)\n",
|
| 257 |
+
" model.load_state_dict(state[\"model\"], strict=True) #model_state_dict\n",
|
| 258 |
+
"\n",
|
| 259 |
+
" print(f\"\\nLoaded checkpoint {checkpoint_name} from {outdir}\\n\")\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"model.eval()\n",
|
| 262 |
+
"model.requires_grad_(False)\n",
|
| 263 |
+
"model.to(device)\n",
|
| 264 |
+
"pass"
|
| 265 |
+
]
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"cell_type": "code",
|
| 269 |
+
"execution_count": 3,
|
| 270 |
+
"id": "c3461199-e805-4e9c-8c91-894e83cf8bc3",
|
| 271 |
+
"metadata": {
|
| 272 |
+
"tags": []
|
| 273 |
+
},
|
| 274 |
+
"outputs": [
|
| 275 |
+
{
|
| 276 |
+
"name": "stdout",
|
| 277 |
+
"output_type": "stream",
|
| 278 |
+
"text": [
|
| 279 |
+
"Target: trial_type\n",
|
| 280 |
+
"train: (118656, 9), test: (12816, 9)\n",
|
| 281 |
+
"X_train: (118656, 384), X_test: (12816, 384)\n",
|
| 282 |
+
"classes (21): ['0bk_body' '0bk_faces' '0bk_places' '0bk_tools' '2bk_body' '2bk_faces'\n",
|
| 283 |
+
" '2bk_places' '2bk_tools' 'fear' 'lf' 'lh' 'match' 'math' 'mental' 'neut'\n",
|
| 284 |
+
" 'relation' 'rf' 'rh' 'rnd' 'story' 't']\n",
|
| 285 |
+
"\n",
|
| 286 |
+
"y_train: (118656,) [14 8 14 8 14 13 13 13 13 18 18 13 15 11 11 15 11 15 15 11]\n",
|
| 287 |
+
"y_test: (12816,) [ 7 7 0 0 5 5 3 3 4 4 6 6 17 9 20 16 10 20 9 17]\n",
|
| 288 |
+
"\n",
|
| 289 |
+
"train_ind: 106790 [ 71756 44749 102949 6302 76306 43438 88189 46933 66308 102792]\n",
|
| 290 |
+
"val_ind: 11866 [73083 36571 52192 51200 2619 399 2375 2327 41809 84134]\n",
|
| 291 |
+
"Fitting PCA projection\n",
|
| 292 |
+
"Fitting logistic regression\n",
|
| 293 |
+
"Done:\n",
|
| 294 |
+
"{\"target\": \"trial_type\", \"train_acc\": 0.9191310047757281, \"val_acc\": 0.9014832293949099, \"test_acc\": 0.899812734082397}\n"
|
| 295 |
+
]
|
| 296 |
+
}
|
| 297 |
+
],
|
| 298 |
+
"source": [
|
| 299 |
+
"import argparse\n",
|
| 300 |
+
"import json\n",
|
| 301 |
+
"import os\n",
|
| 302 |
+
"import pickle\n",
|
| 303 |
+
"from pathlib import Path\n",
|
| 304 |
+
"\n",
|
| 305 |
+
"import pandas as pd\n",
|
| 306 |
+
"import numpy as np\n",
|
| 307 |
+
"from sklearn.decomposition import PCA\n",
|
| 308 |
+
"from sklearn.linear_model import LogisticRegressionCV\n",
|
| 309 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 310 |
+
"from sklearn.preprocessing import LabelEncoder\n",
|
| 311 |
+
"\n",
|
| 312 |
+
"target = \"trial_type\"\n",
|
| 313 |
+
"print(f\"Target: {target}\")\n",
|
| 314 |
+
"\n",
|
| 315 |
+
"train_features = pd.read_parquet(f\"{outdir}/{parquet_folder}/train.parquet\")\n",
|
| 316 |
+
"test_features = pd.read_parquet(f\"{outdir}/{parquet_folder}/test.parquet\")\n",
|
| 317 |
+
"print(f\"train: {train_features.shape}, test: {test_features.shape}\")\n",
|
| 318 |
+
"\n",
|
| 319 |
+
"X_train = np.stack(train_features[\"feature\"])\n",
|
| 320 |
+
"X_test = np.stack(test_features[\"feature\"])\n",
|
| 321 |
+
"print(f\"X_train: {X_train.shape}, X_test: {X_test.shape}\")\n",
|
| 322 |
+
"\n",
|
| 323 |
+
"if target == \"task\":\n",
|
| 324 |
+
" labels_train = train_features[\"task\"].str.rstrip(\"1234\").values\n",
|
| 325 |
+
" labels_test = test_features[\"task\"].str.rstrip(\"1234\").values\n",
|
| 326 |
+
"elif target == \"trial_type\":\n",
|
| 327 |
+
" labels_train = train_features[\"trial_type\"].values\n",
|
| 328 |
+
" labels_test = test_features[\"trial_type\"].values\n",
|
| 329 |
+
"\n",
|
| 330 |
+
"label_enc = LabelEncoder()\n",
|
| 331 |
+
"y_train = label_enc.fit_transform(labels_train)\n",
|
| 332 |
+
"y_test = label_enc.transform(labels_test)\n",
|
| 333 |
+
"\n",
|
| 334 |
+
"print(f\"classes ({len(label_enc.classes_)}): {label_enc.classes_}\")\n",
|
| 335 |
+
"print(\n",
|
| 336 |
+
" f\"\\ny_train: {y_train.shape} {y_train[:20]}\\n\"\n",
|
| 337 |
+
" f\"y_test: {y_test.shape} {y_test[:20]}\"\n",
|
| 338 |
+
")\n",
|
| 339 |
+
"del train_features, test_features\n",
|
| 340 |
+
"\n",
|
| 341 |
+
"train_ind, val_ind = train_test_split(\n",
|
| 342 |
+
" np.arange(len(X_train)), train_size=0.9, random_state=42\n",
|
| 343 |
+
")\n",
|
| 344 |
+
"print(\n",
|
| 345 |
+
" f\"\\ntrain_ind: {len(train_ind)} {train_ind[:10]}\\n\"\n",
|
| 346 |
+
" f\"val_ind: {len(val_ind)} {val_ind[:10]}\"\n",
|
| 347 |
+
")\n",
|
| 348 |
+
"X_train, X_val = X_train[train_ind], X_train[val_ind]\n",
|
| 349 |
+
"y_train, y_val = y_train[train_ind], y_train[val_ind]\n",
|
| 350 |
+
"\n",
|
| 351 |
+
"print(\"Fitting PCA projection\")\n",
|
| 352 |
+
"pca = PCA(n_components=384, whiten=True, svd_solver=\"randomized\")\n",
|
| 353 |
+
"pca.fit(X_train)\n",
|
| 354 |
+
"\n",
|
| 355 |
+
"X_train = pca.transform(X_train)\n",
|
| 356 |
+
"X_val = pca.transform(X_val)\n",
|
| 357 |
+
"X_test = pca.transform(X_test)\n",
|
| 358 |
+
"\n",
|
| 359 |
+
"print(\"Fitting logistic regression\")\n",
|
| 360 |
+
"clf = LogisticRegressionCV()\n",
|
| 361 |
+
"clf.fit(X_train, y_train)\n",
|
| 362 |
+
"\n",
|
| 363 |
+
"train_acc = clf.score(X_train, y_train)\n",
|
| 364 |
+
"val_acc = clf.score(X_val, y_val)\n",
|
| 365 |
+
"test_acc = clf.score(X_test, y_test)\n",
|
| 366 |
+
"\n",
|
| 367 |
+
"result = {\n",
|
| 368 |
+
" \"target\": target,\n",
|
| 369 |
+
" \"train_acc\": train_acc,\n",
|
| 370 |
+
" \"val_acc\": val_acc,\n",
|
| 371 |
+
" \"test_acc\": test_acc,\n",
|
| 372 |
+
"}\n",
|
| 373 |
+
"\n",
|
| 374 |
+
"print(f\"Done:\\n{json.dumps(result)}\")"
|
| 375 |
+
]
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"cell_type": "code",
|
| 379 |
+
"execution_count": null,
|
| 380 |
+
"id": "f9d84788-0ba5-42b9-9e04-b03790997504",
|
| 381 |
+
"metadata": {},
|
| 382 |
+
"outputs": [],
|
| 383 |
+
"source": []
|
| 384 |
+
}
|
| 385 |
+
],
|
| 386 |
+
"metadata": {
|
| 387 |
+
"kernelspec": {
|
| 388 |
+
"display_name": "found2",
|
| 389 |
+
"language": "python",
|
| 390 |
+
"name": "found2"
|
| 391 |
+
},
|
| 392 |
+
"language_info": {
|
| 393 |
+
"codemirror_mode": {
|
| 394 |
+
"name": "ipython",
|
| 395 |
+
"version": 3
|
| 396 |
+
},
|
| 397 |
+
"file_extension": ".py",
|
| 398 |
+
"mimetype": "text/x-python",
|
| 399 |
+
"name": "python",
|
| 400 |
+
"nbconvert_exporter": "python",
|
| 401 |
+
"pygments_lexer": "ipython3",
|
| 402 |
+
"version": "3.10.14"
|
| 403 |
+
}
|
| 404 |
+
},
|
| 405 |
+
"nbformat": 4,
|
| 406 |
+
"nbformat_minor": 5
|
| 407 |
+
}
|
fMRI-foundation-model-old/flat/probe.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
fMRI-foundation-model-old/flat/probe_prepper.ipynb
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "b8e236f1-385a-4d93-bb39-bea3ee384d76",
|
| 7 |
+
"metadata": {
|
| 8 |
+
"tags": []
|
| 9 |
+
},
|
| 10 |
+
"outputs": [
|
| 11 |
+
{
|
| 12 |
+
"name": "stdout",
|
| 13 |
+
"output_type": "stream",
|
| 14 |
+
"text": [
|
| 15 |
+
"outdir /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/ps16_mask9_3losses_bs32\n",
|
| 16 |
+
"Loaded config.yaml from ckpt folder /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/ps16_mask9_3losses_bs32\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"__CONFIG__\n",
|
| 19 |
+
"base_lr = 0.001\n",
|
| 20 |
+
"batch_size = 32\n",
|
| 21 |
+
"ckpt_interval = 5\n",
|
| 22 |
+
"ckpt_saving = True\n",
|
| 23 |
+
"cls_embed = True\n",
|
| 24 |
+
"decoder_embed_dim = 512\n",
|
| 25 |
+
"grad_clip = 1.0\n",
|
| 26 |
+
"hcp_flat_path = /weka/proj-medarc/shared/hcp_flat\n",
|
| 27 |
+
"mask_ratio = 0.9\n",
|
| 28 |
+
"model_name = ps16_mask9_3losses_bs32\n",
|
| 29 |
+
"no_qkv_bias = False\n",
|
| 30 |
+
"norm_pix_loss = False\n",
|
| 31 |
+
"num_epochs = 100\n",
|
| 32 |
+
"num_frames = 16\n",
|
| 33 |
+
"num_samples_per_epoch = 200000\n",
|
| 34 |
+
"num_workers = 10\n",
|
| 35 |
+
"patch_size = 16\n",
|
| 36 |
+
"pred_t_dim = 8\n",
|
| 37 |
+
"print_interval = 20\n",
|
| 38 |
+
"probe_base_lr = 0.0003\n",
|
| 39 |
+
"probe_batch_size = 8\n",
|
| 40 |
+
"probe_num_epochs = 30\n",
|
| 41 |
+
"probe_num_samples_per_epoch = 100000\n",
|
| 42 |
+
"resume_from_ckpt = True\n",
|
| 43 |
+
"seed = 42\n",
|
| 44 |
+
"sep_pos_embed = True\n",
|
| 45 |
+
"t_patch_size = 2\n",
|
| 46 |
+
"test_num_samples_per_epoch = 50000\n",
|
| 47 |
+
"trunc_init = False\n",
|
| 48 |
+
"use_contrastive_loss = True\n",
|
| 49 |
+
"wandb_log = True\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"Number of available CUDA devices: 1\n",
|
| 53 |
+
"LOCAL RANK=0\n",
|
| 54 |
+
"NUM GPUS=1\n",
|
| 55 |
+
"NODE=0\n",
|
| 56 |
+
"GLOBAL RANK=0\n",
|
| 57 |
+
"WORLD_SIZE=1\n",
|
| 58 |
+
"PID of this process = 2361819\n",
|
| 59 |
+
"device = cuda distributed = False num_devices = 1 local rank = 0 world size = 1 data_type = torch.float32\n"
|
| 60 |
+
]
|
| 61 |
+
}
|
| 62 |
+
],
|
| 63 |
+
"source": [
|
| 64 |
+
"# Import packages and setup gpu configuration.\n",
|
| 65 |
+
"# This code block shouldnt need to be adjusted!\n",
|
| 66 |
+
"import os\n",
|
| 67 |
+
"import sys\n",
|
| 68 |
+
"import json\n",
|
| 69 |
+
"import yaml\n",
|
| 70 |
+
"import numpy as np\n",
|
| 71 |
+
"import copy\n",
|
| 72 |
+
"import math\n",
|
| 73 |
+
"import time\n",
|
| 74 |
+
"import datetime\n",
|
| 75 |
+
"import random\n",
|
| 76 |
+
"from tqdm import tqdm\n",
|
| 77 |
+
"import webdataset as wds\n",
|
| 78 |
+
"import matplotlib.pyplot as plt\n",
|
| 79 |
+
"\n",
|
| 80 |
+
"import torch\n",
|
| 81 |
+
"import torch.nn as nn\n",
|
| 82 |
+
"from torchvision import transforms\n",
|
| 83 |
+
"import utils\n",
|
| 84 |
+
"from flat_models import *\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"from elbow.sinks import BufferedParquetWriter\n",
|
| 87 |
+
"\n",
|
| 88 |
+
"# tf32 data type is faster than standard float32\n",
|
| 89 |
+
"torch.backends.cuda.matmul.allow_tf32 = True\n",
|
| 90 |
+
"# following fixes a Conv3D CUDNN_NOT_SUPPORTED error\n",
|
| 91 |
+
"torch.backends.cudnn.benchmark = True\n",
|
| 92 |
+
"\n",
|
| 93 |
+
"## MODEL TO LOAD ##\n",
|
| 94 |
+
"model_name = \"ps16_mask9_3losses_bs32\"\n",
|
| 95 |
+
"outdir = os.path.abspath(f'checkpoints/{model_name}')\n",
|
| 96 |
+
"print(\"outdir\", outdir)\n",
|
| 97 |
+
"# Load previous config.yaml if available\n",
|
| 98 |
+
"if os.path.exists(f\"{outdir}/config.yaml\"):\n",
|
| 99 |
+
" config = yaml.load(open(f\"{outdir}/config.yaml\", 'r'), Loader=yaml.FullLoader)\n",
|
| 100 |
+
" print(f\"Loaded config.yaml from ckpt folder {outdir}\")\n",
|
| 101 |
+
" # create global variables from the config\n",
|
| 102 |
+
" print(\"\\n__CONFIG__\")\n",
|
| 103 |
+
" for attribute_name in config.keys():\n",
|
| 104 |
+
" print(f\"{attribute_name} = {config[attribute_name]}\")\n",
|
| 105 |
+
" globals()[attribute_name] = config[f'{attribute_name}']\n",
|
| 106 |
+
" print(\"\\n\")\n",
|
| 107 |
+
"\n",
|
| 108 |
+
"### Multi-GPU config ###\n",
|
| 109 |
+
"device_count = torch.cuda.device_count()\n",
|
| 110 |
+
"print(f\"Number of available CUDA devices: {device_count}\")\n",
|
| 111 |
+
"\n",
|
| 112 |
+
"local_rank = os.getenv('LOCAL_RANK')\n",
|
| 113 |
+
"if local_rank is None: \n",
|
| 114 |
+
" local_rank = 0\n",
|
| 115 |
+
"else:\n",
|
| 116 |
+
" local_rank = int(local_rank)\n",
|
| 117 |
+
"print(f\"LOCAL RANK={local_rank}\")\n",
|
| 118 |
+
"\n",
|
| 119 |
+
"num_devices = os.getenv('NUM_GPUS')\n",
|
| 120 |
+
"if num_devices is None: \n",
|
| 121 |
+
" num_devices = 1\n",
|
| 122 |
+
"else:\n",
|
| 123 |
+
" num_devices = int(num_devices)\n",
|
| 124 |
+
"print(f\"NUM GPUS={num_devices}\")\n",
|
| 125 |
+
"distributed = True if num_devices>1 else False\n",
|
| 126 |
+
"if distributed: assert device_count==num_devices\n",
|
| 127 |
+
"\n",
|
| 128 |
+
"node = os.getenv('SLURM_NODEID')\n",
|
| 129 |
+
"if node is None:\n",
|
| 130 |
+
" node = 0\n",
|
| 131 |
+
"else:\n",
|
| 132 |
+
" node = int(node)\n",
|
| 133 |
+
"print(f\"NODE={node}\")\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"global_rank = os.getenv('RANK')\n",
|
| 136 |
+
"if global_rank is None:\n",
|
| 137 |
+
" global_rank = 0\n",
|
| 138 |
+
"else:\n",
|
| 139 |
+
" global_rank = int(global_rank)\n",
|
| 140 |
+
"print(f\"GLOBAL RANK={global_rank}\")\n",
|
| 141 |
+
"\n",
|
| 142 |
+
"world_size = os.getenv('WORLD_SIZE')\n",
|
| 143 |
+
"if world_size is None: \n",
|
| 144 |
+
" world_size = 1\n",
|
| 145 |
+
"else:\n",
|
| 146 |
+
" world_size = int(world_size)\n",
|
| 147 |
+
"print(f\"WORLD_SIZE={world_size}\")\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"if utils.is_interactive():\n",
|
| 150 |
+
" # Following allows you to change functions in models.py or utils.py and \n",
|
| 151 |
+
" # have this notebook automatically update with your revisions\n",
|
| 152 |
+
" %load_ext autoreload\n",
|
| 153 |
+
" %autoreload 2\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"batch_size = probe_batch_size\n",
|
| 156 |
+
"num_epochs = probe_num_epochs\n",
|
| 157 |
+
"hcp_flat_path = \"/weka/proj-medarc/shared/hcp_flat\"\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"data_type = torch.float32 # change depending on your mixed_precision\n",
|
| 160 |
+
"global_batch_size = batch_size * world_size\n",
|
| 161 |
+
"\n",
|
| 162 |
+
"device = torch.device('cuda')\n",
|
| 163 |
+
"\n",
|
| 164 |
+
"print(\"PID of this process =\",os.getpid())\n",
|
| 165 |
+
"print(\"device =\", device, \"distributed =\",distributed, \"num_devices =\", num_devices, \"local rank =\", local_rank, \"world size =\", world_size, \"data_type =\", data_type)\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"# seed all random functions\n",
|
| 168 |
+
"utils.seed_everything(seed + global_rank)"
|
| 169 |
+
]
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"cell_type": "markdown",
|
| 173 |
+
"id": "ab15aca0-148e-435f-b8f2-7a708b61a6d9",
|
| 174 |
+
"metadata": {},
|
| 175 |
+
"source": [
|
| 176 |
+
"# hcp_flat"
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"cell_type": "code",
|
| 181 |
+
"execution_count": 2,
|
| 182 |
+
"id": "de1a5b87-fa69-44e1-bdb9-bd9e257485c6",
|
| 183 |
+
"metadata": {
|
| 184 |
+
"tags": []
|
| 185 |
+
},
|
| 186 |
+
"outputs": [
|
| 187 |
+
{
|
| 188 |
+
"name": "stdout",
|
| 189 |
+
"output_type": "stream",
|
| 190 |
+
"text": [
|
| 191 |
+
"img_size (144, 320) patch_size (16, 16) frames 16 t_patch_size 2\n",
|
| 192 |
+
"model initialized\n"
|
| 193 |
+
]
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"data": {
|
| 197 |
+
"text/plain": [
|
| 198 |
+
"MaskedAutoencoderViT(\n",
|
| 199 |
+
" (patch_embed): PatchEmbed(\n",
|
| 200 |
+
" (proj): Conv3d(1, 384, kernel_size=(2, 16, 16), stride=(2, 16, 16))\n",
|
| 201 |
+
" )\n",
|
| 202 |
+
" (blocks): ModuleList(\n",
|
| 203 |
+
" (0-11): 12 x Block(\n",
|
| 204 |
+
" (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n",
|
| 205 |
+
" (attn): Attention(\n",
|
| 206 |
+
" (q): Linear(in_features=384, out_features=384, bias=True)\n",
|
| 207 |
+
" (k): Linear(in_features=384, out_features=384, bias=True)\n",
|
| 208 |
+
" (v): Linear(in_features=384, out_features=384, bias=True)\n",
|
| 209 |
+
" (proj): Linear(in_features=384, out_features=384, bias=True)\n",
|
| 210 |
+
" (proj_drop): Dropout(p=0.0, inplace=False)\n",
|
| 211 |
+
" )\n",
|
| 212 |
+
" (drop_path): Identity()\n",
|
| 213 |
+
" (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n",
|
| 214 |
+
" (mlp): Mlp(\n",
|
| 215 |
+
" (fc1): Linear(in_features=384, out_features=1536, bias=True)\n",
|
| 216 |
+
" (act): GELU(approximate='none')\n",
|
| 217 |
+
" (drop1): Dropout(p=0.0, inplace=False)\n",
|
| 218 |
+
" (norm): Identity()\n",
|
| 219 |
+
" (fc2): Linear(in_features=1536, out_features=384, bias=True)\n",
|
| 220 |
+
" (drop2): Dropout(p=0.0, inplace=False)\n",
|
| 221 |
+
" )\n",
|
| 222 |
+
" )\n",
|
| 223 |
+
" )\n",
|
| 224 |
+
" (norm): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n",
|
| 225 |
+
" (decoder_embed): Linear(in_features=384, out_features=512, bias=True)\n",
|
| 226 |
+
" (decoder_blocks): ModuleList(\n",
|
| 227 |
+
" (0-3): 4 x Block(\n",
|
| 228 |
+
" (norm1): LayerNorm((512,), eps=1e-06, elementwise_affine=True)\n",
|
| 229 |
+
" (attn): Attention(\n",
|
| 230 |
+
" (q): Linear(in_features=512, out_features=512, bias=True)\n",
|
| 231 |
+
" (k): Linear(in_features=512, out_features=512, bias=True)\n",
|
| 232 |
+
" (v): Linear(in_features=512, out_features=512, bias=True)\n",
|
| 233 |
+
" (proj): Linear(in_features=512, out_features=512, bias=True)\n",
|
| 234 |
+
" (proj_drop): Dropout(p=0.0, inplace=False)\n",
|
| 235 |
+
" )\n",
|
| 236 |
+
" (drop_path): Identity()\n",
|
| 237 |
+
" (norm2): LayerNorm((512,), eps=1e-06, elementwise_affine=True)\n",
|
| 238 |
+
" (mlp): Mlp(\n",
|
| 239 |
+
" (fc1): Linear(in_features=512, out_features=2048, bias=True)\n",
|
| 240 |
+
" (act): GELU(approximate='none')\n",
|
| 241 |
+
" (drop1): Dropout(p=0.0, inplace=False)\n",
|
| 242 |
+
" (norm): Identity()\n",
|
| 243 |
+
" (fc2): Linear(in_features=2048, out_features=512, bias=True)\n",
|
| 244 |
+
" (drop2): Dropout(p=0.0, inplace=False)\n",
|
| 245 |
+
" )\n",
|
| 246 |
+
" )\n",
|
| 247 |
+
" )\n",
|
| 248 |
+
" (decoder_norm): LayerNorm((512,), eps=1e-06, elementwise_affine=True)\n",
|
| 249 |
+
" (decoder_pred): Linear(in_features=512, out_features=256, bias=True)\n",
|
| 250 |
+
")"
|
| 251 |
+
]
|
| 252 |
+
},
|
| 253 |
+
"execution_count": 2,
|
| 254 |
+
"metadata": {},
|
| 255 |
+
"output_type": "execute_result"
|
| 256 |
+
}
|
| 257 |
+
],
|
| 258 |
+
"source": [
|
| 259 |
+
"from util.hcp_flat import load_hcp_flat_mask\n",
|
| 260 |
+
"from util.hcp_flat import create_hcp_flat\n",
|
| 261 |
+
"import util.visualize as vis\n",
|
| 262 |
+
"\n",
|
| 263 |
+
"model = mae_vit_small_fmri(\n",
|
| 264 |
+
" patch_size=patch_size,\n",
|
| 265 |
+
" decoder_embed_dim=decoder_embed_dim,\n",
|
| 266 |
+
" t_patch_size=t_patch_size,\n",
|
| 267 |
+
" pred_t_dim=pred_t_dim,\n",
|
| 268 |
+
" decoder_depth=4,\n",
|
| 269 |
+
" cls_embed=cls_embed,\n",
|
| 270 |
+
" norm_pix_loss=norm_pix_loss,\n",
|
| 271 |
+
" no_qkv_bias=no_qkv_bias,\n",
|
| 272 |
+
" sep_pos_embed=sep_pos_embed,\n",
|
| 273 |
+
" trunc_init=trunc_init,\n",
|
| 274 |
+
")\n",
|
| 275 |
+
"\n",
|
| 276 |
+
"model.eval()\n",
|
| 277 |
+
"model.requires_grad_(False)\n",
|
| 278 |
+
"model.to(device)"
|
| 279 |
+
]
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"cell_type": "markdown",
|
| 283 |
+
"id": "3dd51ddf-fb71-48f4-bdbd-88753b44d2aa",
|
| 284 |
+
"metadata": {},
|
| 285 |
+
"source": [
|
| 286 |
+
"## Create dataset and data loaders"
|
| 287 |
+
]
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"cell_type": "code",
|
| 291 |
+
"execution_count": 3,
|
| 292 |
+
"id": "3e330cd2-8f4f-4ca7-ae68-16698e90060f",
|
| 293 |
+
"metadata": {
|
| 294 |
+
"tags": []
|
| 295 |
+
},
|
| 296 |
+
"outputs": [
|
| 297 |
+
{
|
| 298 |
+
"name": "stdout",
|
| 299 |
+
"output_type": "stream",
|
| 300 |
+
"text": [
|
| 301 |
+
"changed batch_size to 64\n"
|
| 302 |
+
]
|
| 303 |
+
}
|
| 304 |
+
],
|
| 305 |
+
"source": [
|
| 306 |
+
"batch_size = 64 #12\n",
|
| 307 |
+
"print(f\"changed batch_size to {batch_size}\")\n",
|
| 308 |
+
"\n",
|
| 309 |
+
"## Train ##\n",
|
| 310 |
+
"train_dataset = create_hcp_flat(root=hcp_flat_path, \n",
|
| 311 |
+
" split=\"train\", frames=num_frames, \n",
|
| 312 |
+
" clip_mode=\"event\", shuffle=False)\n",
|
| 313 |
+
"train_dl = wds.WebLoader(\n",
|
| 314 |
+
" train_dataset.batched(batch_size, partial=False),\n",
|
| 315 |
+
" batch_size=None,\n",
|
| 316 |
+
" shuffle=False,\n",
|
| 317 |
+
" num_workers=num_workers,\n",
|
| 318 |
+
" pin_memory=True,\n",
|
| 319 |
+
")\n",
|
| 320 |
+
"\n",
|
| 321 |
+
"## Test ##\n",
|
| 322 |
+
"test_dataset = create_hcp_flat(root=hcp_flat_path, \n",
|
| 323 |
+
" split=\"test\", frames=num_frames, \n",
|
| 324 |
+
" clip_mode=\"event\", shuffle=False)\n",
|
| 325 |
+
"test_dl = wds.WebLoader(\n",
|
| 326 |
+
" test_dataset.batched(batch_size, partial=False),\n",
|
| 327 |
+
" batch_size=None,\n",
|
| 328 |
+
" shuffle=False,\n",
|
| 329 |
+
" num_workers=num_workers,\n",
|
| 330 |
+
" pin_memory=True,\n",
|
| 331 |
+
")"
|
| 332 |
+
]
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"cell_type": "markdown",
|
| 336 |
+
"id": "2b8e6baa-4b1c-4f38-b078-70b2b092d14d",
|
| 337 |
+
"metadata": {},
|
| 338 |
+
"source": [
|
| 339 |
+
"# Load checkpoint"
|
| 340 |
+
]
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"cell_type": "code",
|
| 344 |
+
"execution_count": 4,
|
| 345 |
+
"id": "4da73c08-ca61-48ef-9e63-b70db6f07a59",
|
| 346 |
+
"metadata": {
|
| 347 |
+
"tags": []
|
| 348 |
+
},
|
| 349 |
+
"outputs": [
|
| 350 |
+
{
|
| 351 |
+
"name": "stdout",
|
| 352 |
+
"output_type": "stream",
|
| 353 |
+
"text": [
|
| 354 |
+
"\n",
|
| 355 |
+
"Loaded checkpoint epoch99.pth from /weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/ps16_mask9_3losses_bs32\n",
|
| 356 |
+
"\n"
|
| 357 |
+
]
|
| 358 |
+
}
|
| 359 |
+
],
|
| 360 |
+
"source": [
|
| 361 |
+
"if not os.path.exists(outdir) or not os.path.isdir(outdir):\n",
|
| 362 |
+
" print(f\"\\nCheckpoint folder {outdir} does not exist.\\n\")\n",
|
| 363 |
+
" err\n",
|
| 364 |
+
"else:\n",
|
| 365 |
+
" checkpoint_files = [f for f in os.listdir(outdir) if f.endswith('.pth')]\n",
|
| 366 |
+
"\n",
|
| 367 |
+
" # Extract epoch numbers and find the largest\n",
|
| 368 |
+
" epoch_numbers = []\n",
|
| 369 |
+
" for file in checkpoint_files:\n",
|
| 370 |
+
" try:\n",
|
| 371 |
+
" epoch_number = int(file.split('epoch')[-1].split('.')[0])\n",
|
| 372 |
+
" epoch_numbers.append(epoch_number)\n",
|
| 373 |
+
" except ValueError:\n",
|
| 374 |
+
" continue\n",
|
| 375 |
+
" latest_epoch = max(epoch_numbers)\n",
|
| 376 |
+
" latest_checkpoint = f\"epoch{latest_epoch}.pth\"\n",
|
| 377 |
+
" \n",
|
| 378 |
+
" # # Or specify epoch number \n",
|
| 379 |
+
" # latest_checkpoint = \"epoch15.pth\"\n",
|
| 380 |
+
"\n",
|
| 381 |
+
" # Load the checkpoint\n",
|
| 382 |
+
" checkpoint_path = os.path.join(outdir, latest_checkpoint)\n",
|
| 383 |
+
"\n",
|
| 384 |
+
" state = torch.load(checkpoint_path)\n",
|
| 385 |
+
" model.load_state_dict(state[\"model_state_dict\"], strict=True)\n",
|
| 386 |
+
"\n",
|
| 387 |
+
" print(f\"\\nLoaded checkpoint {latest_checkpoint} from {outdir}\\n\")"
|
| 388 |
+
]
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"cell_type": "markdown",
|
| 392 |
+
"id": "c43a5055-8afd-468a-93bf-32f94bd1d042",
|
| 393 |
+
"metadata": {},
|
| 394 |
+
"source": [
|
| 395 |
+
"# Start extraction"
|
| 396 |
+
]
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"cell_type": "code",
|
| 400 |
+
"execution_count": 5,
|
| 401 |
+
"id": "bf1e2df3-6c55-4218-89ea-348c5aa4f8b8",
|
| 402 |
+
"metadata": {
|
| 403 |
+
"tags": []
|
| 404 |
+
},
|
| 405 |
+
"outputs": [
|
| 406 |
+
{
|
| 407 |
+
"name": "stdout",
|
| 408 |
+
"output_type": "stream",
|
| 409 |
+
"text": [
|
| 410 |
+
"/weka/proj-fmri/paulscotti/fMRI-foundation-model/flat/checkpoints/ps16_mask9_3losses_bs32/epoch99\n"
|
| 411 |
+
]
|
| 412 |
+
}
|
| 413 |
+
],
|
| 414 |
+
"source": [
|
| 415 |
+
"outdir_parquet = f'{outdir}/{latest_checkpoint[:-4]}'\n",
|
| 416 |
+
"print(outdir_parquet)\n",
|
| 417 |
+
"\n",
|
| 418 |
+
"os.makedirs(outdir_parquet,exist_ok=True)"
|
| 419 |
+
]
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"cell_type": "code",
|
| 423 |
+
"execution_count": 6,
|
| 424 |
+
"id": "1e1bbe95-5fbf-4f8d-bc7a-17d5983a94d3",
|
| 425 |
+
"metadata": {},
|
| 426 |
+
"outputs": [],
|
| 427 |
+
"source": [
|
| 428 |
+
"# for i, (samples, samples_meta) in enumerate(tqdm(train_dl)):\n",
|
| 429 |
+
"# samples = samples.to(device, non_blocking=True)\n",
|
| 430 |
+
"## using batch_size=12: 49284it [14:03, 58.44it/s]"
|
| 431 |
+
]
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"cell_type": "code",
|
| 435 |
+
"execution_count": 7,
|
| 436 |
+
"id": "6507067f-1ece-46ac-ad8c-24ef7a9b3a58",
|
| 437 |
+
"metadata": {
|
| 438 |
+
"tags": []
|
| 439 |
+
},
|
| 440 |
+
"outputs": [],
|
| 441 |
+
"source": [
|
| 442 |
+
"@torch.no_grad()\n",
|
| 443 |
+
"def extract_features(dl):\n",
|
| 444 |
+
" for samples, samples_meta in tqdm(dl,total=49284):\n",
|
| 445 |
+
" samples = samples.to(device, non_blocking=True)\n",
|
| 446 |
+
"\n",
|
| 447 |
+
" features = model.forward_features(samples)\n",
|
| 448 |
+
"\n",
|
| 449 |
+
" features = features.cpu().numpy()\n",
|
| 450 |
+
"\n",
|
| 451 |
+
" for feat, meta in zip(features, samples_meta):\n",
|
| 452 |
+
" yield {\"feature\": feat, **meta}"
|
| 453 |
+
]
|
| 454 |
+
},
|
| 455 |
+
{
|
| 456 |
+
"cell_type": "code",
|
| 457 |
+
"execution_count": 8,
|
| 458 |
+
"id": "c3461199-e805-4e9c-8c91-894e83cf8bc3",
|
| 459 |
+
"metadata": {
|
| 460 |
+
"tags": []
|
| 461 |
+
},
|
| 462 |
+
"outputs": [
|
| 463 |
+
{
|
| 464 |
+
"name": "stdout",
|
| 465 |
+
"output_type": "stream",
|
| 466 |
+
"text": [
|
| 467 |
+
"Start extract\n"
|
| 468 |
+
]
|
| 469 |
+
},
|
| 470 |
+
{
|
| 471 |
+
"name": "stderr",
|
| 472 |
+
"output_type": "stream",
|
| 473 |
+
"text": [
|
| 474 |
+
" 0%|▏ | 197/49284 [00:29<2:03:32, 6.62it/s]\n"
|
| 475 |
+
]
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"name": "stdout",
|
| 479 |
+
"output_type": "stream",
|
| 480 |
+
"text": [
|
| 481 |
+
"Extract time 0:00:29\n",
|
| 482 |
+
"389945344\n"
|
| 483 |
+
]
|
| 484 |
+
}
|
| 485 |
+
],
|
| 486 |
+
"source": [
|
| 487 |
+
"utils.seed_everything(seed)\n",
|
| 488 |
+
"\n",
|
| 489 |
+
"print(\"Start extract\")\n",
|
| 490 |
+
"start_time = time.time()\n",
|
| 491 |
+
"\n",
|
| 492 |
+
"with BufferedParquetWriter(f\"{outdir_parquet}/test.parquet\", blocking=True) as writer:\n",
|
| 493 |
+
" for sample in extract_features(test_dl):\n",
|
| 494 |
+
" writer.write(sample)\n",
|
| 495 |
+
"\n",
|
| 496 |
+
"total_time = time.time() - start_time\n",
|
| 497 |
+
"total_time_str = str(datetime.timedelta(seconds=int(total_time)))\n",
|
| 498 |
+
"print(\"Extract time {}\".format(total_time_str))\n",
|
| 499 |
+
"print(torch.cuda.memory_allocated())"
|
| 500 |
+
]
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"cell_type": "code",
|
| 504 |
+
"execution_count": 9,
|
| 505 |
+
"id": "a898429c-b013-4989-86ef-fbf75ca15971",
|
| 506 |
+
"metadata": {
|
| 507 |
+
"tags": []
|
| 508 |
+
},
|
| 509 |
+
"outputs": [
|
| 510 |
+
{
|
| 511 |
+
"name": "stdout",
|
| 512 |
+
"output_type": "stream",
|
| 513 |
+
"text": [
|
| 514 |
+
"Start extract\n"
|
| 515 |
+
]
|
| 516 |
+
},
|
| 517 |
+
{
|
| 518 |
+
"name": "stderr",
|
| 519 |
+
"output_type": "stream",
|
| 520 |
+
"text": [
|
| 521 |
+
" 4%|█▎ | 1849/49284 [03:59<1:42:32, 7.71it/s]\n"
|
| 522 |
+
]
|
| 523 |
+
},
|
| 524 |
+
{
|
| 525 |
+
"name": "stdout",
|
| 526 |
+
"output_type": "stream",
|
| 527 |
+
"text": [
|
| 528 |
+
"Extract time 0:03:59\n",
|
| 529 |
+
"389945344\n"
|
| 530 |
+
]
|
| 531 |
+
}
|
| 532 |
+
],
|
| 533 |
+
"source": [
|
| 534 |
+
"utils.seed_everything(seed)\n",
|
| 535 |
+
"\n",
|
| 536 |
+
"print(\"Start extract\")\n",
|
| 537 |
+
"start_time = time.time()\n",
|
| 538 |
+
"\n",
|
| 539 |
+
"with BufferedParquetWriter(f\"{outdir_parquet}/train.parquet\", blocking=True) as writer:\n",
|
| 540 |
+
" for sample in extract_features(train_dl):\n",
|
| 541 |
+
" writer.write(sample)\n",
|
| 542 |
+
"\n",
|
| 543 |
+
"total_time = time.time() - start_time\n",
|
| 544 |
+
"total_time_str = str(datetime.timedelta(seconds=int(total_time)))\n",
|
| 545 |
+
"print(\"Extract time {}\".format(total_time_str))\n",
|
| 546 |
+
"print(torch.cuda.memory_allocated())"
|
| 547 |
+
]
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"cell_type": "code",
|
| 551 |
+
"execution_count": null,
|
| 552 |
+
"id": "6ffb97e6-c61c-4026-82fb-b1729f51c40a",
|
| 553 |
+
"metadata": {},
|
| 554 |
+
"outputs": [],
|
| 555 |
+
"source": []
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"cell_type": "code",
|
| 559 |
+
"execution_count": null,
|
| 560 |
+
"id": "2404f355-090f-48b7-8b04-5c76e7bb7e04",
|
| 561 |
+
"metadata": {},
|
| 562 |
+
"outputs": [],
|
| 563 |
+
"source": []
|
| 564 |
+
}
|
| 565 |
+
],
|
| 566 |
+
"metadata": {
|
| 567 |
+
"kernelspec": {
|
| 568 |
+
"display_name": "found",
|
| 569 |
+
"language": "python",
|
| 570 |
+
"name": "found"
|
| 571 |
+
},
|
| 572 |
+
"language_info": {
|
| 573 |
+
"codemirror_mode": {
|
| 574 |
+
"name": "ipython",
|
| 575 |
+
"version": 3
|
| 576 |
+
},
|
| 577 |
+
"file_extension": ".py",
|
| 578 |
+
"mimetype": "text/x-python",
|
| 579 |
+
"name": "python",
|
| 580 |
+
"nbconvert_exporter": "python",
|
| 581 |
+
"pygments_lexer": "ipython3",
|
| 582 |
+
"version": "3.10.14"
|
| 583 |
+
}
|
| 584 |
+
},
|
| 585 |
+
"nbformat": 4,
|
| 586 |
+
"nbformat_minor": 5
|
| 587 |
+
}
|
fMRI-foundation-model-old/flat/unique_tasks.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dcb37cf8032fa5fc0f90f567deeaa92890c59581d1710dd33da51da9fc2ebb53
|
| 3 |
+
size 688
|
fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/hcp_flat-checkpoint.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
from functools import partial
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Any, Dict, Iterable, Optional, Tuple, Union
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import webdataset as wds
|
| 10 |
+
from torch.utils.data import IterableDataset
|
| 11 |
+
|
| 12 |
+
HCP_FLAT_ROOT = "https://huggingface.co/datasets/bold-ai/HCP-Flat/resolve/main"
|
| 13 |
+
NUM_SHARDS = {"train": 1629, "test": 174}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def create_hcp_flat(
|
| 17 |
+
root: Optional[str] = None,
|
| 18 |
+
training: bool = True,
|
| 19 |
+
shards: Optional[Union[int, Iterable[int]]] = None,
|
| 20 |
+
frames: int = 16,
|
| 21 |
+
cache_dir: Optional[str] = None,
|
| 22 |
+
) -> wds.WebDataset:
|
| 23 |
+
"""
|
| 24 |
+
Create HCP-Flat dataset. Yields samples of (key, images) where key is the webdataset
|
| 25 |
+
sample key and images is shape (C, T, H, W).
|
| 26 |
+
|
| 27 |
+
References:
|
| 28 |
+
https://github.com/webdataset/webdataset/issues/250#issuecomment-1454094496
|
| 29 |
+
https://github.com/tmbdev-archive/webdataset-imagenet-2/blob/main/imagenet.py
|
| 30 |
+
https://github.com/huggingface/pytorch-image-models/blob/main/timm/data/readers/reader_wds.py
|
| 31 |
+
"""
|
| 32 |
+
root = root or os.environ.get("HCP_FLAT_ROOT") or HCP_FLAT_ROOT
|
| 33 |
+
split = "train" if training else "test"
|
| 34 |
+
|
| 35 |
+
shards = shards or NUM_SHARDS[split]
|
| 36 |
+
if isinstance(shards, int):
|
| 37 |
+
shards = range(shards)
|
| 38 |
+
assert (
|
| 39 |
+
min(shards) >= 0 and max(shards) < NUM_SHARDS[split]
|
| 40 |
+
), f"Invalid shards {shards}; expected in [0, {NUM_SHARDS[split]})"
|
| 41 |
+
|
| 42 |
+
urls = [f"{root}/{split}/hcp-flat_{split}_{shard:06d}.tar" for shard in shards]
|
| 43 |
+
|
| 44 |
+
# In training, we resample shards with replacement independently in every worker and
|
| 45 |
+
# yield batches up to the target number of samples. In test, we iterate over the
|
| 46 |
+
# shards in order, with workers getting interleaving shards, and yield batches up to
|
| 47 |
+
# the target samples. In a distributed setting with variable size shards, setting a
|
| 48 |
+
# fixed number of samples is the easiest way to get balanced batches per worker. In
|
| 49 |
+
# training we will still see all data eventually. But in test, it means we cut off
|
| 50 |
+
# some data.
|
| 51 |
+
|
| 52 |
+
# Note that in training this does not do deterministic shuffling, which we would
|
| 53 |
+
# need for exact reproducibility. They get determistic shuffling in timm, but it's
|
| 54 |
+
# more complicated.
|
| 55 |
+
|
| 56 |
+
# Note that we are splitting the long timeseries into clips after shuffling, which
|
| 57 |
+
# means clips from the same series will appear consecutively in the batch(es).
|
| 58 |
+
# I think this is not too bad. It is basically equivalent to training on longer
|
| 59 |
+
# sequences. Clipping before shuffling might be preferred, but it results in a bad
|
| 60 |
+
# system memory leak (https://github.com/webdataset/webdataset/issues/354).
|
| 61 |
+
dataset = (
|
| 62 |
+
wds.WebDataset(
|
| 63 |
+
urls,
|
| 64 |
+
resampled=training,
|
| 65 |
+
shardshuffle=1000 if training else False,
|
| 66 |
+
nodesplitter=wds.split_by_node,
|
| 67 |
+
select_files=select_extensions(("bold.npy","meta.json")),
|
| 68 |
+
cache_dir=cache_dir,
|
| 69 |
+
)
|
| 70 |
+
.shuffle(1000 if training else 0)
|
| 71 |
+
.decode()
|
| 72 |
+
.map(partial(extract_images, mask=load_hcp_flat_mask()))
|
| 73 |
+
.compose(to_clips(frames))
|
| 74 |
+
)
|
| 75 |
+
return dataset
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def select_extensions(extensions: Tuple[str, ...]):
|
| 79 |
+
extensions_set = set(extensions)
|
| 80 |
+
def select_files(fname: str):
|
| 81 |
+
suffix = ".".join(fname.split(".")[1:])
|
| 82 |
+
return suffix in extensions_set
|
| 83 |
+
return select_files
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def extract_images(sample: Dict[str, Any], mask: torch.Tensor):
|
| 87 |
+
key = sample["__key__"]
|
| 88 |
+
images = sample["bold.npy"]
|
| 89 |
+
meta = sample["meta.json"]['task']
|
| 90 |
+
|
| 91 |
+
images = torch.from_numpy(images) / 255.0
|
| 92 |
+
images = (images - 0.5) / 0.2
|
| 93 |
+
images = unmask(images, mask)
|
| 94 |
+
# (C, T, H, W,)
|
| 95 |
+
images = images.unsqueeze(0)
|
| 96 |
+
|
| 97 |
+
return key, images, meta
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def unmask(images: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
|
| 101 |
+
images_unmasked = torch.zeros(
|
| 102 |
+
(images.shape[0], *mask.shape), dtype=images.dtype, device=images.device
|
| 103 |
+
)
|
| 104 |
+
images_unmasked[:, mask] = images
|
| 105 |
+
return images_unmasked
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def to_clips(frames: int = 16):
|
| 109 |
+
def _filter(src: IterableDataset[Tuple[str, torch.Tensor, str]]):
|
| 110 |
+
for key, images, meta in src:
|
| 111 |
+
offset = random.randint(0, frames)
|
| 112 |
+
for start in range(offset, images.shape[1] - frames, frames):
|
| 113 |
+
yield key, images[:, start : start + frames], meta
|
| 114 |
+
return _filter
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def load_hcp_flat_mask() -> torch.Tensor:
|
| 118 |
+
mask = np.load(Path(__file__).parents[1] / "hcp-flat_mask.npy")
|
| 119 |
+
mask = torch.as_tensor(mask)
|
| 120 |
+
return mask
|
fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/misc-checkpoint.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
# References:
|
| 8 |
+
# DeiT: https://github.com/facebookresearch/deit
|
| 9 |
+
# BEiT: https://github.com/microsoft/unilm/tree/master/beit
|
| 10 |
+
# --------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
import builtins
|
| 13 |
+
import datetime
|
| 14 |
+
import math
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
from collections import defaultdict, deque, OrderedDict
|
| 18 |
+
|
| 19 |
+
import util.logging as logging
|
| 20 |
+
import psutil
|
| 21 |
+
import torch
|
| 22 |
+
import torch.distributed as dist
|
| 23 |
+
from iopath.common.file_io import g_pathmgr as pathmgr
|
| 24 |
+
from util.logging import master_print as print
|
| 25 |
+
from torch import inf
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
logger = logging.get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class SmoothedValue:
|
| 32 |
+
"""Track a series of values and provide access to smoothed values over a
|
| 33 |
+
window or the global series average.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self, window_size=20, fmt=None):
|
| 37 |
+
if fmt is None:
|
| 38 |
+
fmt = "{median:.4f} ({global_avg:.4f})"
|
| 39 |
+
self.deque = deque(maxlen=window_size)
|
| 40 |
+
self.total = 0.0
|
| 41 |
+
self.count = 0
|
| 42 |
+
self.fmt = fmt
|
| 43 |
+
|
| 44 |
+
def update(self, value, n=1):
|
| 45 |
+
self.deque.append(value)
|
| 46 |
+
self.count += n
|
| 47 |
+
self.total += value * n
|
| 48 |
+
|
| 49 |
+
def synchronize_between_processes(self):
|
| 50 |
+
"""
|
| 51 |
+
Warning: does not synchronize the deque!
|
| 52 |
+
"""
|
| 53 |
+
if not is_dist_avail_and_initialized():
|
| 54 |
+
return
|
| 55 |
+
t = torch.tensor([self.count, self.total], dtype=torch.float64, device="cuda")
|
| 56 |
+
dist.barrier()
|
| 57 |
+
dist.all_reduce(t)
|
| 58 |
+
t = t.tolist()
|
| 59 |
+
self.count = int(t[0])
|
| 60 |
+
self.total = t[1]
|
| 61 |
+
|
| 62 |
+
@property
|
| 63 |
+
def median(self):
|
| 64 |
+
d = torch.tensor(list(self.deque))
|
| 65 |
+
return d.median().item()
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def avg(self):
|
| 69 |
+
d = torch.tensor(list(self.deque), dtype=torch.float32)
|
| 70 |
+
return d.mean().item()
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def global_avg(self):
|
| 74 |
+
return self.total / self.count
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def max(self):
|
| 78 |
+
return max(self.deque)
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def value(self):
|
| 82 |
+
return self.deque[-1]
|
| 83 |
+
|
| 84 |
+
def __str__(self):
|
| 85 |
+
return self.fmt.format(
|
| 86 |
+
median=self.median,
|
| 87 |
+
avg=self.avg,
|
| 88 |
+
global_avg=self.global_avg,
|
| 89 |
+
max=self.max,
|
| 90 |
+
value=self.value,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class MetricLogger:
|
| 95 |
+
def __init__(self, delimiter="\t"):
|
| 96 |
+
self.meters = defaultdict(SmoothedValue)
|
| 97 |
+
self.delimiter = delimiter
|
| 98 |
+
|
| 99 |
+
def update(self, **kwargs):
|
| 100 |
+
for k, v in kwargs.items():
|
| 101 |
+
if v is None:
|
| 102 |
+
continue
|
| 103 |
+
if isinstance(v, torch.Tensor):
|
| 104 |
+
v = v.item()
|
| 105 |
+
assert isinstance(v, (float, int))
|
| 106 |
+
self.meters[k].update(v)
|
| 107 |
+
|
| 108 |
+
def __getattr__(self, attr):
|
| 109 |
+
if attr in self.meters:
|
| 110 |
+
return self.meters[attr]
|
| 111 |
+
if attr in self.__dict__:
|
| 112 |
+
return self.__dict__[attr]
|
| 113 |
+
raise AttributeError(
|
| 114 |
+
"'{}' object has no attribute '{}'".format(type(self).__name__, attr)
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
def __str__(self):
|
| 118 |
+
loss_str = []
|
| 119 |
+
for name, meter in self.meters.items():
|
| 120 |
+
loss_str.append("{}: {}".format(name, str(meter)))
|
| 121 |
+
return self.delimiter.join(loss_str)
|
| 122 |
+
|
| 123 |
+
def synchronize_between_processes(self):
|
| 124 |
+
for meter in self.meters.values():
|
| 125 |
+
meter.synchronize_between_processes()
|
| 126 |
+
|
| 127 |
+
def add_meter(self, name, meter):
|
| 128 |
+
self.meters[name] = meter
|
| 129 |
+
|
| 130 |
+
def log_every(self, iterable, print_freq, header=None, total_steps=None):
|
| 131 |
+
i = 0
|
| 132 |
+
total_steps = total_steps or len(iterable)
|
| 133 |
+
if not header:
|
| 134 |
+
header = ""
|
| 135 |
+
start_time = time.time()
|
| 136 |
+
end = time.time()
|
| 137 |
+
iter_time = SmoothedValue(fmt="{avg:.4f}")
|
| 138 |
+
data_time = SmoothedValue(fmt="{avg:.4f}")
|
| 139 |
+
space_fmt = ":" + str(len(str(total_steps))) + "d"
|
| 140 |
+
log_msg = [
|
| 141 |
+
header,
|
| 142 |
+
"[{0" + space_fmt + "}/{1}]",
|
| 143 |
+
"eta: {eta}",
|
| 144 |
+
"{meters}",
|
| 145 |
+
"time: {time}",
|
| 146 |
+
"data: {data}",
|
| 147 |
+
]
|
| 148 |
+
if torch.cuda.is_available():
|
| 149 |
+
log_msg.append("max mem: {memory:.0f}")
|
| 150 |
+
log_msg = self.delimiter.join(log_msg)
|
| 151 |
+
MB = 1024.0 * 1024.0
|
| 152 |
+
for obj in iterable:
|
| 153 |
+
data_time.update(time.time() - end)
|
| 154 |
+
yield obj
|
| 155 |
+
iter_time.update(time.time() - end)
|
| 156 |
+
if i % print_freq == 0 or i == total_steps - 1:
|
| 157 |
+
eta_seconds = iter_time.global_avg * (total_steps - i)
|
| 158 |
+
eta_string = str(datetime.timedelta(seconds=int(eta_seconds)))
|
| 159 |
+
if torch.cuda.is_available():
|
| 160 |
+
print(
|
| 161 |
+
log_msg.format(
|
| 162 |
+
i,
|
| 163 |
+
total_steps,
|
| 164 |
+
eta=eta_string,
|
| 165 |
+
meters=str(self),
|
| 166 |
+
time=str(iter_time),
|
| 167 |
+
data=str(data_time),
|
| 168 |
+
memory=torch.cuda.max_memory_allocated() / MB,
|
| 169 |
+
)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
else:
|
| 173 |
+
print(
|
| 174 |
+
log_msg.format(
|
| 175 |
+
i,
|
| 176 |
+
total_steps,
|
| 177 |
+
eta=eta_string,
|
| 178 |
+
meters=str(self),
|
| 179 |
+
time=str(iter_time),
|
| 180 |
+
data=str(data_time),
|
| 181 |
+
)
|
| 182 |
+
)
|
| 183 |
+
i += 1
|
| 184 |
+
end = time.time()
|
| 185 |
+
total_time = time.time() - start_time
|
| 186 |
+
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
| 187 |
+
print(
|
| 188 |
+
"{} Total time: {} ({:.4f} s / it)".format(
|
| 189 |
+
header, total_time_str, total_time / total_steps
|
| 190 |
+
)
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def setup_for_distributed(is_master):
|
| 195 |
+
"""
|
| 196 |
+
This function disables printing when not in master process
|
| 197 |
+
"""
|
| 198 |
+
builtin_print = builtins.print
|
| 199 |
+
|
| 200 |
+
def print(*args, **kwargs):
|
| 201 |
+
force = kwargs.pop("force", False)
|
| 202 |
+
force = force or (get_world_size() > 8)
|
| 203 |
+
if is_master or force:
|
| 204 |
+
now = datetime.datetime.now().time()
|
| 205 |
+
builtin_print("[{}] ".format(now), end="") # print with time stamp
|
| 206 |
+
builtin_print(*args, **kwargs)
|
| 207 |
+
|
| 208 |
+
builtins.print = print
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def is_dist_avail_and_initialized():
|
| 212 |
+
if not dist.is_available():
|
| 213 |
+
return False
|
| 214 |
+
if not dist.is_initialized():
|
| 215 |
+
return False
|
| 216 |
+
return True
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def get_world_size():
|
| 220 |
+
if not is_dist_avail_and_initialized():
|
| 221 |
+
return 1
|
| 222 |
+
return dist.get_world_size()
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def get_rank():
|
| 226 |
+
if not is_dist_avail_and_initialized():
|
| 227 |
+
return 0
|
| 228 |
+
return dist.get_rank()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def is_main_process():
|
| 232 |
+
return get_rank() == 0
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def save_on_master(state, path):
|
| 236 |
+
if is_main_process():
|
| 237 |
+
print(f"save path {path}")
|
| 238 |
+
with pathmgr.open(path, "wb") as f:
|
| 239 |
+
torch.save(state, f)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def init_distributed_mode(args):
|
| 243 |
+
if args.no_env:
|
| 244 |
+
pass
|
| 245 |
+
elif args.dist_on_itp:
|
| 246 |
+
args.rank = int(os.environ["OMPI_COMM_WORLD_RANK"])
|
| 247 |
+
args.world_size = int(os.environ["OMPI_COMM_WORLD_SIZE"])
|
| 248 |
+
args.gpu = int(os.environ["OMPI_COMM_WORLD_LOCAL_RANK"])
|
| 249 |
+
args.dist_url = "tcp://%s:%s" % (
|
| 250 |
+
os.environ["MASTER_ADDR"],
|
| 251 |
+
os.environ["MASTER_PORT"],
|
| 252 |
+
)
|
| 253 |
+
os.environ["LOCAL_RANK"] = str(args.gpu)
|
| 254 |
+
os.environ["RANK"] = str(args.rank)
|
| 255 |
+
os.environ["WORLD_SIZE"] = str(args.world_size)
|
| 256 |
+
# ["RANK", "WORLD_SIZE", "MASTER_ADDR", "MASTER_PORT", "LOCAL_RANK"]
|
| 257 |
+
elif "RANK" in os.environ and "WORLD_SIZE" in os.environ:
|
| 258 |
+
args.rank = int(os.environ["RANK"])
|
| 259 |
+
args.world_size = int(os.environ["WORLD_SIZE"])
|
| 260 |
+
args.gpu = int(os.environ["LOCAL_RANK"])
|
| 261 |
+
elif "SLURM_PROCID" in os.environ:
|
| 262 |
+
args.rank = int(os.environ["SLURM_PROCID"])
|
| 263 |
+
args.gpu = args.rank % torch.cuda.device_count()
|
| 264 |
+
else:
|
| 265 |
+
print("Not using distributed mode")
|
| 266 |
+
setup_for_distributed(is_master=True) # hack
|
| 267 |
+
args.distributed = False
|
| 268 |
+
return
|
| 269 |
+
|
| 270 |
+
args.distributed = True
|
| 271 |
+
|
| 272 |
+
torch.cuda.set_device(args.gpu)
|
| 273 |
+
args.dist_backend = "nccl"
|
| 274 |
+
print(
|
| 275 |
+
"| distributed init (rank {}): {}, gpu {}".format(
|
| 276 |
+
args.rank, args.dist_url, args.gpu
|
| 277 |
+
),
|
| 278 |
+
# flush=True,
|
| 279 |
+
)
|
| 280 |
+
torch.distributed.init_process_group(
|
| 281 |
+
backend=args.dist_backend,
|
| 282 |
+
world_size=args.world_size,
|
| 283 |
+
rank=args.rank,
|
| 284 |
+
)
|
| 285 |
+
# init_method=args.dist_url,
|
| 286 |
+
torch.distributed.barrier()
|
| 287 |
+
setup_for_distributed(args.rank == 0)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class NativeScalerWithGradNormCount:
|
| 291 |
+
state_dict_key = "amp_scaler"
|
| 292 |
+
|
| 293 |
+
def __init__(self, fp32=False):
|
| 294 |
+
self._scaler = torch.cuda.amp.GradScaler(enabled=not fp32)
|
| 295 |
+
|
| 296 |
+
def __call__(
|
| 297 |
+
self,
|
| 298 |
+
loss,
|
| 299 |
+
optimizer,
|
| 300 |
+
clip_grad=None,
|
| 301 |
+
parameters=None,
|
| 302 |
+
create_graph=False,
|
| 303 |
+
update_grad=True,
|
| 304 |
+
):
|
| 305 |
+
self._scaler.scale(loss).backward(create_graph=create_graph)
|
| 306 |
+
if update_grad:
|
| 307 |
+
if clip_grad is not None:
|
| 308 |
+
assert parameters is not None
|
| 309 |
+
self._scaler.unscale_(
|
| 310 |
+
optimizer
|
| 311 |
+
) # unscale the gradients of optimizer's assigned params in-place
|
| 312 |
+
norm = torch.nn.utils.clip_grad_norm_(parameters, clip_grad)
|
| 313 |
+
else:
|
| 314 |
+
self._scaler.unscale_(optimizer)
|
| 315 |
+
norm = get_grad_norm_(parameters)
|
| 316 |
+
self._scaler.step(optimizer)
|
| 317 |
+
self._scaler.update()
|
| 318 |
+
else:
|
| 319 |
+
norm = None
|
| 320 |
+
return norm
|
| 321 |
+
|
| 322 |
+
def state_dict(self):
|
| 323 |
+
return self._scaler.state_dict()
|
| 324 |
+
|
| 325 |
+
def load_state_dict(self, state_dict):
|
| 326 |
+
self._scaler.load_state_dict(state_dict)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def get_grad_norm_(parameters, norm_type: float = 2.0) -> torch.Tensor:
|
| 330 |
+
if isinstance(parameters, torch.Tensor):
|
| 331 |
+
parameters = [parameters]
|
| 332 |
+
parameters = [p for p in parameters if p.grad is not None]
|
| 333 |
+
norm_type = float(norm_type)
|
| 334 |
+
if len(parameters) == 0:
|
| 335 |
+
return torch.tensor(0.0)
|
| 336 |
+
device = parameters[0].grad.device
|
| 337 |
+
if norm_type == inf:
|
| 338 |
+
total_norm = max(p.grad.detach().abs().max().to(device) for p in parameters)
|
| 339 |
+
else:
|
| 340 |
+
total_norm = torch.norm(
|
| 341 |
+
torch.stack(
|
| 342 |
+
[torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]
|
| 343 |
+
),
|
| 344 |
+
norm_type,
|
| 345 |
+
)
|
| 346 |
+
return total_norm
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def save_model(args, epoch, model, model_without_ddp, optimizer, loss_scaler):
|
| 350 |
+
checkpoint_path = "{}/checkpoint-{:05d}.pth".format(args.output_dir, epoch)
|
| 351 |
+
to_save = {
|
| 352 |
+
"model": model_without_ddp.state_dict(),
|
| 353 |
+
"optimizer": optimizer.state_dict(),
|
| 354 |
+
"epoch": epoch,
|
| 355 |
+
"scaler": loss_scaler.state_dict(),
|
| 356 |
+
"args": args,
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
save_on_master(to_save, checkpoint_path)
|
| 360 |
+
return checkpoint_path
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def get_last_checkpoint(args):
|
| 364 |
+
"""
|
| 365 |
+
Get the last checkpoint from the checkpointing folder.
|
| 366 |
+
Args:
|
| 367 |
+
path_to_job (string): the path to the folder of the current job.
|
| 368 |
+
"""
|
| 369 |
+
d = args.output_dir
|
| 370 |
+
names = pathmgr.ls(d) if pathmgr.exists(d) else []
|
| 371 |
+
names = [f for f in names if "checkpoint" in f]
|
| 372 |
+
if len(names) == 0:
|
| 373 |
+
print("No checkpoints found in '{}'.".format(d))
|
| 374 |
+
return None
|
| 375 |
+
else:
|
| 376 |
+
# Sort the checkpoints by epoch.
|
| 377 |
+
name = sorted(names)[-1]
|
| 378 |
+
return os.path.join(d, name)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def load_model(args, model_without_ddp, optimizer, loss_scaler):
|
| 382 |
+
if not args.resume:
|
| 383 |
+
args.resume = get_last_checkpoint(args)
|
| 384 |
+
if args.resume:
|
| 385 |
+
if args.resume.startswith("https"):
|
| 386 |
+
checkpoint = torch.hub.load_state_dict_from_url(
|
| 387 |
+
args.resume, map_location="cpu", check_hash=True
|
| 388 |
+
)
|
| 389 |
+
else:
|
| 390 |
+
with pathmgr.open(args.resume, "rb") as f:
|
| 391 |
+
checkpoint = torch.load(f, map_location="cpu")
|
| 392 |
+
model_without_ddp.load_state_dict(checkpoint["model"])
|
| 393 |
+
print("Resume checkpoint %s" % args.resume)
|
| 394 |
+
if (
|
| 395 |
+
"optimizer" in checkpoint
|
| 396 |
+
and "epoch" in checkpoint
|
| 397 |
+
and not (hasattr(args, "eval") and args.eval)
|
| 398 |
+
):
|
| 399 |
+
optimizer.load_state_dict(checkpoint["optimizer"])
|
| 400 |
+
args.start_epoch = checkpoint["epoch"] + 1
|
| 401 |
+
if "scaler" in checkpoint:
|
| 402 |
+
loss_scaler.load_state_dict(checkpoint["scaler"])
|
| 403 |
+
print("With optim & sched!")
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def all_reduce_mean(x):
|
| 407 |
+
world_size = get_world_size()
|
| 408 |
+
if world_size > 1:
|
| 409 |
+
x_reduce = torch.tensor(x).cuda()
|
| 410 |
+
dist.all_reduce(x_reduce)
|
| 411 |
+
x_reduce /= world_size
|
| 412 |
+
return x_reduce.item()
|
| 413 |
+
else:
|
| 414 |
+
return x
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def gpu_mem_usage():
|
| 418 |
+
"""
|
| 419 |
+
Compute the GPU memory usage for the current device (GB).
|
| 420 |
+
"""
|
| 421 |
+
if torch.cuda.is_available():
|
| 422 |
+
mem_usage_bytes = torch.cuda.max_memory_allocated()
|
| 423 |
+
else:
|
| 424 |
+
mem_usage_bytes = 0
|
| 425 |
+
return mem_usage_bytes / 1024**3
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def cpu_mem_usage():
|
| 429 |
+
"""
|
| 430 |
+
Compute the system memory (RAM) usage for the current device (GB).
|
| 431 |
+
Returns:
|
| 432 |
+
usage (float): used memory (GB).
|
| 433 |
+
total (float): total memory (GB).
|
| 434 |
+
"""
|
| 435 |
+
vram = psutil.virtual_memory()
|
| 436 |
+
usage = (vram.total - vram.available) / 1024**3
|
| 437 |
+
total = vram.total / 1024**3
|
| 438 |
+
|
| 439 |
+
return usage, total
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def all_gather(tensors):
|
| 443 |
+
"""
|
| 444 |
+
All gathers the provided tensors from all processes across machines.
|
| 445 |
+
Args:
|
| 446 |
+
tensors (list): tensors to perform all gather across all processes in
|
| 447 |
+
all machines.
|
| 448 |
+
"""
|
| 449 |
+
|
| 450 |
+
gather_list = []
|
| 451 |
+
output_tensor = []
|
| 452 |
+
world_size = dist.get_world_size()
|
| 453 |
+
for tensor in tensors:
|
| 454 |
+
tensor_placeholder = [torch.ones_like(tensor) for _ in range(world_size)]
|
| 455 |
+
dist.all_gather(tensor_placeholder, tensor, async_op=False)
|
| 456 |
+
gather_list.append(tensor_placeholder)
|
| 457 |
+
for gathered_tensor in gather_list:
|
| 458 |
+
output_tensor.append(torch.cat(gathered_tensor, dim=0))
|
| 459 |
+
return output_tensor
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def add_weight_decay(model, weight_decay=1e-5, skip_list=(), bias_wd=False):
|
| 463 |
+
decay = []
|
| 464 |
+
no_decay = []
|
| 465 |
+
for name, param in model.named_parameters():
|
| 466 |
+
if not param.requires_grad:
|
| 467 |
+
continue # frozen weights
|
| 468 |
+
if (
|
| 469 |
+
(not bias_wd)
|
| 470 |
+
and len(param.shape) == 1
|
| 471 |
+
or name.endswith(".bias")
|
| 472 |
+
or name in skip_list
|
| 473 |
+
):
|
| 474 |
+
no_decay.append(param)
|
| 475 |
+
else:
|
| 476 |
+
decay.append(param)
|
| 477 |
+
return [
|
| 478 |
+
{"params": no_decay, "weight_decay": 0.0},
|
| 479 |
+
{"params": decay, "weight_decay": weight_decay},
|
| 480 |
+
]
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def inflate(model_2d, model_3d):
|
| 484 |
+
state_dict_inflated = OrderedDict()
|
| 485 |
+
for k, v2d in model_2d.items():
|
| 486 |
+
if "patch_embed.proj.weight" in k:
|
| 487 |
+
v3d = model_3d[k]
|
| 488 |
+
v3d = v2d.unsqueeze(2).repeat(1, 1, v3d.shape[2], 1, 1) / v3d.shape[2]
|
| 489 |
+
state_dict_inflated[k] = v3d.clone()
|
| 490 |
+
elif "pos_embed" in k:
|
| 491 |
+
pos_embed_cls, pos_embed_spatial = torch.split(v2d, [1, 196], dim=1)
|
| 492 |
+
state_dict_inflated["pos_embed_cls"] = pos_embed_cls.clone()
|
| 493 |
+
state_dict_inflated["pos_embed"] = pos_embed_spatial.clone()
|
| 494 |
+
else:
|
| 495 |
+
state_dict_inflated[k] = v2d.clone()
|
| 496 |
+
return state_dict_inflated
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def convert_checkpoint(model_2d):
|
| 500 |
+
state_dict_inflated = OrderedDict()
|
| 501 |
+
for k, v2d in model_2d.items():
|
| 502 |
+
if "head.projection.weight" in k:
|
| 503 |
+
state_dict_inflated["head.weight"] = v2d.clone()
|
| 504 |
+
elif "head.projection.bias" in k:
|
| 505 |
+
state_dict_inflated["head.bias"] = v2d.clone()
|
| 506 |
+
else:
|
| 507 |
+
state_dict_inflated[k] = v2d.clone()
|
| 508 |
+
return state_dict_inflated
|
fMRI-foundation-model-old/flat/util/.ipynb_checkpoints/visualize-checkpoint.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from typing import Any, Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from matplotlib import pyplot as plt
|
| 6 |
+
from matplotlib.figure import Figure
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
plt.rcParams["figure.dpi"] = 150
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def plot_mask_pred(
|
| 13 |
+
model: torch.nn.Module,
|
| 14 |
+
imgs: torch.Tensor,
|
| 15 |
+
pred: torch.Tensor,
|
| 16 |
+
mask: torch.Tensor,
|
| 17 |
+
mean: Optional[Any] = None,
|
| 18 |
+
std: Optional[Any] = None,
|
| 19 |
+
nrow: int = 8,
|
| 20 |
+
norm_pix_loss: bool = False,
|
| 21 |
+
):
|
| 22 |
+
# imgs: [N, C, T, H, W]
|
| 23 |
+
# pred: [N, t*h*w, u*p*p*C]
|
| 24 |
+
# mask: [N, t*h*w], 0 is keep, 1 is remove,
|
| 25 |
+
target = torch.index_select(
|
| 26 |
+
imgs,
|
| 27 |
+
2,
|
| 28 |
+
torch.linspace(
|
| 29 |
+
0,
|
| 30 |
+
imgs.shape[2] - 1,
|
| 31 |
+
model.pred_t_dim,
|
| 32 |
+
)
|
| 33 |
+
.long()
|
| 34 |
+
.to(imgs.device),
|
| 35 |
+
)
|
| 36 |
+
if norm_pix_loss:
|
| 37 |
+
target_mean = target.mean(dim=-1, keepdim=True)
|
| 38 |
+
target_var = target.var(dim=-1, keepdim=True)
|
| 39 |
+
target = torch.einsum("ncthw->nthwc", target)
|
| 40 |
+
target = target.flatten(0, 1)[:nrow].cpu()
|
| 41 |
+
|
| 42 |
+
pred = pred.detach()
|
| 43 |
+
if norm_pix_loss:
|
| 44 |
+
pred = (pred * target_var) + target_mean
|
| 45 |
+
pred = model.unpatchify(pred)
|
| 46 |
+
pred = torch.einsum("ncthw->nthwc", pred).cpu()
|
| 47 |
+
pred = pred.flatten(0, 1)[:nrow].cpu()
|
| 48 |
+
|
| 49 |
+
mask = mask.unsqueeze(-1).repeat(
|
| 50 |
+
1, 1, model.patch_embed.patch_size[0]**2 * imgs.shape[1]
|
| 51 |
+
) # (N, T*H*W, p*p*c)
|
| 52 |
+
mask = model.unpatchify(mask) # 1 is removing, 0 is keeping
|
| 53 |
+
mask = torch.einsum("ncthw->nthwc", mask).cpu()
|
| 54 |
+
mask = mask.flatten(0, 1)[:nrow].cpu()
|
| 55 |
+
|
| 56 |
+
# masked image
|
| 57 |
+
im_masked = target * (1 - mask)
|
| 58 |
+
|
| 59 |
+
# MAE reconstruction pasted with visible patches
|
| 60 |
+
im_paste = target * (1 - mask) + pred * mask
|
| 61 |
+
|
| 62 |
+
if model.img_mask is not None:
|
| 63 |
+
img_mask = model.img_mask.cpu()
|
| 64 |
+
else:
|
| 65 |
+
img_mask = None
|
| 66 |
+
|
| 67 |
+
H, W = target.shape[1:3]
|
| 68 |
+
ploth = 2.0
|
| 69 |
+
plotw = (W / H) * ploth
|
| 70 |
+
nrow = len(target)
|
| 71 |
+
ncol = 3
|
| 72 |
+
fig, axs = plt.subplots(
|
| 73 |
+
nrow, ncol, figsize=(plotw * ncol, ploth * nrow), squeeze=False
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
for ii in range(nrow):
|
| 77 |
+
plt.sca(axs[ii, 0])
|
| 78 |
+
imshow(im_masked[ii], mean=mean, std=std, mask=img_mask)
|
| 79 |
+
|
| 80 |
+
plt.sca(axs[ii, 1])
|
| 81 |
+
imshow(im_paste[ii], mean=mean, std=std, mask=img_mask)
|
| 82 |
+
|
| 83 |
+
plt.sca(axs[ii, 2])
|
| 84 |
+
imshow(target[ii], mean=mean, std=std, mask=img_mask)
|
| 85 |
+
|
| 86 |
+
plt.tight_layout(pad=0.25)
|
| 87 |
+
return fig
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def imshow(
|
| 91 |
+
image: torch.Tensor,
|
| 92 |
+
mean: Optional[Any] = None,
|
| 93 |
+
std: Optional[Any] = None,
|
| 94 |
+
mask: Optional[torch.Tensor] = None,
|
| 95 |
+
**kwargs,
|
| 96 |
+
):
|
| 97 |
+
# image: (H, W, C)
|
| 98 |
+
assert image.shape[2] in (1, 3)
|
| 99 |
+
if image.shape[2] == 1:
|
| 100 |
+
kwargs = {
|
| 101 |
+
"cmap": "gray",
|
| 102 |
+
"vmin": 0.0,
|
| 103 |
+
"vmax": 1.0,
|
| 104 |
+
"interpolation": "nearest",
|
| 105 |
+
**kwargs,
|
| 106 |
+
}
|
| 107 |
+
if mean is not None:
|
| 108 |
+
mean = torch.as_tensor(mean)
|
| 109 |
+
std = torch.as_tensor(std)
|
| 110 |
+
image = torch.clip(image * std + mean, 0.0, 1.0)
|
| 111 |
+
if mask is not None:
|
| 112 |
+
image = mask.unsqueeze(-1) * image
|
| 113 |
+
plt.imshow(image, **kwargs)
|
| 114 |
+
plt.axis("off")
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def fig2pil(fig: Figure, format: str = "png") -> Image.Image:
|
| 118 |
+
with io.BytesIO() as f:
|
| 119 |
+
fig.savefig(f, format=format)
|
| 120 |
+
f.seek(0)
|
| 121 |
+
img = Image.open(f)
|
| 122 |
+
img.load()
|
| 123 |
+
return img
|
fMRI-foundation-model-old/flat/util/__pycache__/hcp_flat.cpython-310.pyc
ADDED
|
Binary file (3.9 kB). View file
|
|
|
fMRI-foundation-model-old/flat/util/__pycache__/logging.cpython-310.pyc
ADDED
|
Binary file (3.38 kB). View file
|
|
|
fMRI-foundation-model-old/flat/util/__pycache__/losses.cpython-310.pyc
ADDED
|
Binary file (4.24 kB). View file
|
|
|
fMRI-foundation-model-old/flat/util/__pycache__/video_vit.cpython-310.pyc
ADDED
|
Binary file (4.21 kB). View file
|
|
|
fMRI-foundation-model-old/flat/util/__pycache__/visualize.cpython-310.pyc
ADDED
|
Binary file (2.57 kB). View file
|
|
|
fMRI-foundation-model-old/flat/util/hcp_flat.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
from functools import partial
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Any, Dict, Iterable, Optional, Tuple, Union
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import webdataset as wds
|
| 10 |
+
from torch.utils.data import IterableDataset
|
| 11 |
+
|
| 12 |
+
HCP_FLAT_ROOT = "https://huggingface.co/datasets/bold-ai/HCP-Flat/resolve/main"
|
| 13 |
+
NUM_SHARDS = {"train": 1629, "test": 174}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def create_hcp_flat(
|
| 17 |
+
root: Optional[str] = None,
|
| 18 |
+
training: bool = True,
|
| 19 |
+
shards: Optional[Union[int, Iterable[int]]] = None,
|
| 20 |
+
frames: int = 16,
|
| 21 |
+
cache_dir: Optional[str] = None,
|
| 22 |
+
) -> wds.WebDataset:
|
| 23 |
+
"""
|
| 24 |
+
Create HCP-Flat dataset. Yields samples of (key, images) where key is the webdataset
|
| 25 |
+
sample key and images is shape (C, T, H, W).
|
| 26 |
+
|
| 27 |
+
References:
|
| 28 |
+
https://github.com/webdataset/webdataset/issues/250#issuecomment-1454094496
|
| 29 |
+
https://github.com/tmbdev-archive/webdataset-imagenet-2/blob/main/imagenet.py
|
| 30 |
+
https://github.com/huggingface/pytorch-image-models/blob/main/timm/data/readers/reader_wds.py
|
| 31 |
+
"""
|
| 32 |
+
root = root or os.environ.get("HCP_FLAT_ROOT") or HCP_FLAT_ROOT
|
| 33 |
+
split = "train" if training else "test"
|
| 34 |
+
|
| 35 |
+
shards = shards or NUM_SHARDS[split]
|
| 36 |
+
if isinstance(shards, int):
|
| 37 |
+
shards = range(shards)
|
| 38 |
+
assert (
|
| 39 |
+
min(shards) >= 0 and max(shards) < NUM_SHARDS[split]
|
| 40 |
+
), f"Invalid shards {shards}; expected in [0, {NUM_SHARDS[split]})"
|
| 41 |
+
|
| 42 |
+
urls = [f"{root}/{split}/hcp-flat_{split}_{shard:06d}.tar" for shard in shards]
|
| 43 |
+
|
| 44 |
+
# In training, we resample shards with replacement independently in every worker and
|
| 45 |
+
# yield batches up to the target number of samples. In test, we iterate over the
|
| 46 |
+
# shards in order, with workers getting interleaving shards, and yield batches up to
|
| 47 |
+
# the target samples. In a distributed setting with variable size shards, setting a
|
| 48 |
+
# fixed number of samples is the easiest way to get balanced batches per worker. In
|
| 49 |
+
# training we will still see all data eventually. But in test, it means we cut off
|
| 50 |
+
# some data.
|
| 51 |
+
|
| 52 |
+
# Note that in training this does not do deterministic shuffling, which we would
|
| 53 |
+
# need for exact reproducibility. They get determistic shuffling in timm, but it's
|
| 54 |
+
# more complicated.
|
| 55 |
+
|
| 56 |
+
# Note that we are splitting the long timeseries into clips after shuffling, which
|
| 57 |
+
# means clips from the same series will appear consecutively in the batch(es).
|
| 58 |
+
# I think this is not too bad. It is basically equivalent to training on longer
|
| 59 |
+
# sequences. Clipping before shuffling might be preferred, but it results in a bad
|
| 60 |
+
# system memory leak (https://github.com/webdataset/webdataset/issues/354).
|
| 61 |
+
dataset = (
|
| 62 |
+
wds.WebDataset(
|
| 63 |
+
urls,
|
| 64 |
+
resampled=training,
|
| 65 |
+
shardshuffle=1000 if training else False,
|
| 66 |
+
nodesplitter=wds.split_by_node,
|
| 67 |
+
select_files=select_extensions(("bold.npy","meta.json")),
|
| 68 |
+
cache_dir=cache_dir,
|
| 69 |
+
)
|
| 70 |
+
.decode()
|
| 71 |
+
.map(partial(extract_images, mask=load_hcp_flat_mask()))
|
| 72 |
+
.compose(to_clips(frames))
|
| 73 |
+
.shuffle(2500 if training else 0)
|
| 74 |
+
)
|
| 75 |
+
return dataset
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def select_extensions(extensions: Tuple[str, ...]):
|
| 79 |
+
extensions_set = set(extensions)
|
| 80 |
+
def select_files(fname: str):
|
| 81 |
+
suffix = ".".join(fname.split(".")[1:])
|
| 82 |
+
return suffix in extensions_set
|
| 83 |
+
return select_files
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def extract_images(sample: Dict[str, Any], mask: torch.Tensor):
|
| 87 |
+
key = sample["__key__"]
|
| 88 |
+
images = sample["bold.npy"]
|
| 89 |
+
meta = sample["meta.json"]['task']
|
| 90 |
+
|
| 91 |
+
images = torch.from_numpy(images) / 255.0
|
| 92 |
+
images = (images - 0.5) / 0.2
|
| 93 |
+
images = unmask(images, mask)
|
| 94 |
+
# (C, T, H, W,)
|
| 95 |
+
images = images.unsqueeze(0)
|
| 96 |
+
|
| 97 |
+
return key, images, meta
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def unmask(images: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
|
| 101 |
+
images_unmasked = torch.zeros(
|
| 102 |
+
(images.shape[0], *mask.shape), dtype=images.dtype, device=images.device
|
| 103 |
+
)
|
| 104 |
+
images_unmasked[:, mask] = images
|
| 105 |
+
return images_unmasked
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def to_clips(frames: int = 16):
|
| 109 |
+
def _filter(src: IterableDataset[Tuple[str, torch.Tensor, str]]):
|
| 110 |
+
for key, images, meta in src:
|
| 111 |
+
offset = random.randint(0, frames)
|
| 112 |
+
for start in range(offset, images.shape[1] - frames, frames):
|
| 113 |
+
# yield key, images[:, start : start + frames].copy(), meta
|
| 114 |
+
yield key, images[:, start : start + frames].clone(), meta
|
| 115 |
+
return _filter
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def load_hcp_flat_mask() -> torch.Tensor:
|
| 119 |
+
mask = np.load(Path(__file__).parents[1] / "hcp-flat_mask.npy")
|
| 120 |
+
mask = torch.as_tensor(mask)
|
| 121 |
+
return mask
|
fMRI-foundation-model-old/flat/util/logging.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
"""Logging."""
|
| 6 |
+
|
| 7 |
+
import atexit
|
| 8 |
+
import builtins
|
| 9 |
+
import decimal
|
| 10 |
+
import functools
|
| 11 |
+
import logging
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
import simplejson
|
| 16 |
+
import torch
|
| 17 |
+
import torch.distributed as dist
|
| 18 |
+
from iopath.common.file_io import g_pathmgr as pathmgr
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def is_master_proc(multinode=False):
|
| 22 |
+
"""
|
| 23 |
+
Determines if the current process is the master process.
|
| 24 |
+
"""
|
| 25 |
+
if dist.is_initialized():
|
| 26 |
+
if multinode:
|
| 27 |
+
return dist.get_rank() % dist.get_world_size() == 0
|
| 28 |
+
else:
|
| 29 |
+
return dist.get_rank() % torch.cuda.device_count() == 0
|
| 30 |
+
else:
|
| 31 |
+
return True
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _suppress_print():
|
| 35 |
+
"""
|
| 36 |
+
Suppresses printing from the current process.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def print_pass(*objects, sep=" ", end="\n", file=sys.stdout, flush=False):
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
builtins.print = print_pass
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@functools.lru_cache(maxsize=None)
|
| 46 |
+
def _cached_log_stream(filename):
|
| 47 |
+
# Use 1K buffer if writing to cloud storage.
|
| 48 |
+
io = pathmgr.open(filename, "a", buffering=1024 if "://" in filename else -1)
|
| 49 |
+
atexit.register(io.close)
|
| 50 |
+
return io
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def setup_logging(output_dir=None):
|
| 54 |
+
"""
|
| 55 |
+
Sets up the logging for multiple processes. Only enable the logging for the
|
| 56 |
+
master process, and suppress logging for the non-master processes.
|
| 57 |
+
"""
|
| 58 |
+
# Set up logging format.
|
| 59 |
+
if is_master_proc():
|
| 60 |
+
# Enable logging for the master process.
|
| 61 |
+
logging.root.handlers = []
|
| 62 |
+
else:
|
| 63 |
+
# Suppress logging for non-master processes.
|
| 64 |
+
_suppress_print()
|
| 65 |
+
|
| 66 |
+
logger = logging.getLogger()
|
| 67 |
+
logger.setLevel(logging.DEBUG)
|
| 68 |
+
logger.propagate = False
|
| 69 |
+
plain_formatter = logging.Formatter(
|
| 70 |
+
"[%(asctime)s][%(levelname)s] %(filename)s: %(lineno)3d: %(message)s",
|
| 71 |
+
datefmt="%m/%d %H:%M:%S",
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
if is_master_proc():
|
| 75 |
+
ch = logging.StreamHandler(stream=sys.stdout)
|
| 76 |
+
ch.setLevel(logging.DEBUG)
|
| 77 |
+
ch.setFormatter(plain_formatter)
|
| 78 |
+
logger.addHandler(ch)
|
| 79 |
+
|
| 80 |
+
if output_dir is not None and is_master_proc(multinode=True):
|
| 81 |
+
filename = os.path.join(output_dir, "stdout.log")
|
| 82 |
+
fh = logging.StreamHandler(_cached_log_stream(filename))
|
| 83 |
+
fh.setLevel(logging.DEBUG)
|
| 84 |
+
fh.setFormatter(plain_formatter)
|
| 85 |
+
logger.addHandler(fh)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def get_logger(name):
|
| 89 |
+
"""
|
| 90 |
+
Retrieve the logger with the specified name or, if name is None, return a
|
| 91 |
+
logger which is the root logger of the hierarchy.
|
| 92 |
+
Args:
|
| 93 |
+
name (string): name of the logger.
|
| 94 |
+
"""
|
| 95 |
+
return logging.getLogger(name)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def log_json_stats(stats):
|
| 99 |
+
"""
|
| 100 |
+
Logs json stats.
|
| 101 |
+
Args:
|
| 102 |
+
stats (dict): a dictionary of statistical information to log.
|
| 103 |
+
"""
|
| 104 |
+
stats = {
|
| 105 |
+
k: decimal.Decimal("{:.5f}".format(v)) if isinstance(v, float) else v
|
| 106 |
+
for k, v in stats.items()
|
| 107 |
+
}
|
| 108 |
+
json_stats = simplejson.dumps(stats, sort_keys=True, use_decimal=True)
|
| 109 |
+
logger = get_logger(__name__)
|
| 110 |
+
print("json_stats: {:s}".format(json_stats))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def master_print(*args, **kwargs):
|
| 114 |
+
if is_master_proc():
|
| 115 |
+
print(*args, **kwargs)
|
| 116 |
+
else:
|
| 117 |
+
pass
|
fMRI-foundation-model-old/flat/util/losses.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
from einops import rearrange
|
| 6 |
+
import re
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
|
| 10 |
+
def get_ids_shuffle(batch_size, device, model, mask_ratio=0.75):
|
| 11 |
+
N = batch_size
|
| 12 |
+
T = model.patch_embed.t_grid_size
|
| 13 |
+
H, W = model.patch_embed.grid_size
|
| 14 |
+
L = T * H * W
|
| 15 |
+
|
| 16 |
+
noise = torch.rand(N, L, device=device) # noise in [0, 1]
|
| 17 |
+
|
| 18 |
+
# shift missing patches to not be selected
|
| 19 |
+
if model.img_mask is not None:
|
| 20 |
+
noise = noise.view(N, T, H * W)
|
| 21 |
+
noise = noise + (1.0 - model.patch_mask)
|
| 22 |
+
noise = noise.view(N, L)
|
| 23 |
+
|
| 24 |
+
# sort noise for each sample
|
| 25 |
+
ids_shuffle = torch.argsort(
|
| 26 |
+
noise, dim=1
|
| 27 |
+
) # ascend: small is keep, large is remove
|
| 28 |
+
ids_restore = torch.argsort(ids_shuffle, dim=1)
|
| 29 |
+
|
| 30 |
+
return ids_shuffle, ids_restore
|
| 31 |
+
|
| 32 |
+
class VICRegHandler(nn.Module):
|
| 33 |
+
def __init__(self, in_dim, num_layers=3, act=nn.GELU, h=1024, out_dim=4096):
|
| 34 |
+
super().__init__()
|
| 35 |
+
self.projector = nn.Sequential(
|
| 36 |
+
nn.Linear(in_dim, h),
|
| 37 |
+
nn.LayerNorm(h),
|
| 38 |
+
act(),
|
| 39 |
+
nn.Linear(h, h),
|
| 40 |
+
nn.LayerNorm(h),
|
| 41 |
+
act(),
|
| 42 |
+
nn.Linear(h, out_dim),
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
def forward(self, x):
|
| 46 |
+
return self.projector(x)
|
| 47 |
+
|
| 48 |
+
@staticmethod
|
| 49 |
+
def filter_global_to_local(l, enc_mask, dec_mask):
|
| 50 |
+
'''Get the subset of global tokens that correspond to encoder mask only'''
|
| 51 |
+
comb_mask = enc_mask | dec_mask
|
| 52 |
+
comb_indices = torch.where(comb_mask)[0]
|
| 53 |
+
enc_indices = torch.where(enc_mask)[0]
|
| 54 |
+
# enc_set = set(enc_indices.cpu().tolist())
|
| 55 |
+
|
| 56 |
+
# new_mask = torch.zeros_like(comb_indices, dtype=bool)
|
| 57 |
+
# for i, idx in enumerate(comb_indices):
|
| 58 |
+
# if idx in enc_set:
|
| 59 |
+
# new_mask[i] = True
|
| 60 |
+
|
| 61 |
+
new_mask = torch.isin(comb_indices, enc_indices)
|
| 62 |
+
return l[:, new_mask]
|
| 63 |
+
|
| 64 |
+
@staticmethod
|
| 65 |
+
def vicreg_loss(l1, l2, gamma=1.0, lamda=25, mu=25, nu=1, rand_frac=0.2, use_vic_cls=True, eps=1e-4):
|
| 66 |
+
if use_vic_cls:
|
| 67 |
+
# always keep cls and pick a random set of tokens
|
| 68 |
+
rand_indices = torch.cat([torch.tensor([0]), 1+torch.randperm(l1.shape[1]-1)])[:int(rand_frac*l1.shape[1])]
|
| 69 |
+
else:
|
| 70 |
+
# drop cls tokens from loss calc
|
| 71 |
+
l1 = l1[:, 1:]
|
| 72 |
+
l2 = l2[:, 1:]
|
| 73 |
+
rand_indices = torch.randperm(l1.shape[1])[:int(rand_frac*l1.shape[1])]
|
| 74 |
+
|
| 75 |
+
std_l1 = torch.sqrt(l1.flatten(1).var(dim=0)+eps) # nxd
|
| 76 |
+
std_l2 = torch.sqrt(l2.flatten(1).var(dim=0)+eps) # nxd
|
| 77 |
+
var_loss = F.relu(gamma - std_l1).mean() + F.relu(gamma - std_l2).mean()
|
| 78 |
+
del std_l1, std_l2
|
| 79 |
+
|
| 80 |
+
sim_loss = F.mse_loss(l1, l2)
|
| 81 |
+
|
| 82 |
+
l1 = l1 - l1.mean(0, keepdim=True) # b,n,d
|
| 83 |
+
l2 = l2 - l2.mean(0, keepdim=True)
|
| 84 |
+
|
| 85 |
+
l1_sub = l1[:, rand_indices]
|
| 86 |
+
del l1
|
| 87 |
+
cov_l1 = torch.bmm(l1_sub.permute(1,2,0), l1_sub.permute(1,0,2))/(l1_sub.shape[0]-1) # 0.1*n,d,d
|
| 88 |
+
cov_loss = ((cov_l1**2).sum() - (torch.diagonal(cov_l1, dim1=1,dim2=2)**2).sum())/(l1_sub.shape[1]*l1_sub.shape[2])
|
| 89 |
+
del cov_l1, l1_sub
|
| 90 |
+
|
| 91 |
+
l2_sub = l2[:, rand_indices]
|
| 92 |
+
del l2
|
| 93 |
+
cov_l2 = torch.bmm(l2_sub.permute(1,2,0), l2_sub.permute(1,0,2))/(l2_sub.shape[0]-1)
|
| 94 |
+
cov_loss = cov_loss + ((cov_l2**2).sum() - (torch.diagonal(cov_l2, dim1=1,dim2=2)**2).sum())/(l2_sub.shape[1]*l2_sub.shape[2]) # div by nxd
|
| 95 |
+
del cov_l2, l2_sub
|
| 96 |
+
|
| 97 |
+
vic_loss = lamda * sim_loss + mu * var_loss + nu * cov_loss
|
| 98 |
+
|
| 99 |
+
return vic_loss
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class SimCLRHandler(nn.Module):
|
| 103 |
+
def __init__(self, in_dim, num_layers=2, act=nn.GELU, out_dim=1024):
|
| 104 |
+
super().__init__()
|
| 105 |
+
self.projector = nn.Sequential(
|
| 106 |
+
nn.Linear(in_dim, in_dim),
|
| 107 |
+
act(),
|
| 108 |
+
nn.Linear(in_dim, max(in_dim,out_dim)),
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
def forward(self, x):
|
| 112 |
+
return self.projector(x)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def simclr_loss(lats, temp=0.006):
|
| 116 |
+
logits = (nn.functional.normalize(lats.flatten(1),dim=-1) @
|
| 117 |
+
nn.functional.normalize(lats.flatten(1),dim=-1).T) / temp
|
| 118 |
+
|
| 119 |
+
labels = torch.diag_embed(
|
| 120 |
+
torch.ones(logits.shape[0] // 2), offset=logits.shape[0] // 2
|
| 121 |
+
) + torch.diag_embed(torch.ones(logits.shape[0] // 2), offset=-logits.shape[0] // 2)
|
| 122 |
+
labels = labels.to(lats.device)
|
| 123 |
+
|
| 124 |
+
mask = torch.ones_like(logits).bool()
|
| 125 |
+
torch.diagonal(mask).fill_(False)
|
| 126 |
+
|
| 127 |
+
labels = labels[mask].reshape(logits.shape[0], logits.shape[0]-1)
|
| 128 |
+
logits = logits[mask].reshape(*labels.shape)
|
| 129 |
+
|
| 130 |
+
contr_loss = -(logits.log_softmax(-1) * labels).sum(-1).mean()
|
| 131 |
+
|
| 132 |
+
return contr_loss
|
fMRI-foundation-model-old/flat/util/lr_decay.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
# References:
|
| 8 |
+
# ELECTRA https://github.com/google-research/electra
|
| 9 |
+
# BEiT: https://github.com/microsoft/unilm/tree/master/beit
|
| 10 |
+
# --------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def param_groups_lrd(
|
| 16 |
+
model, weight_decay=0.05, no_weight_decay_list=[], layer_decay=0.75
|
| 17 |
+
):
|
| 18 |
+
"""
|
| 19 |
+
Parameter groups for layer-wise lr decay
|
| 20 |
+
Following BEiT: https://github.com/microsoft/unilm/blob/master/beit/optim_factory.py#L58
|
| 21 |
+
"""
|
| 22 |
+
param_group_names = {}
|
| 23 |
+
param_groups = {}
|
| 24 |
+
|
| 25 |
+
num_layers = len(model.blocks) + 1
|
| 26 |
+
|
| 27 |
+
layer_scales = list(layer_decay ** (num_layers - i) for i in range(num_layers + 1))
|
| 28 |
+
|
| 29 |
+
for n, p in model.named_parameters():
|
| 30 |
+
if not p.requires_grad:
|
| 31 |
+
continue
|
| 32 |
+
|
| 33 |
+
# no decay: all 1D parameters and model specific ones
|
| 34 |
+
if p.ndim == 1 or n in no_weight_decay_list:
|
| 35 |
+
g_decay = "no_decay"
|
| 36 |
+
this_decay = 0.0
|
| 37 |
+
else:
|
| 38 |
+
g_decay = "decay"
|
| 39 |
+
this_decay = weight_decay
|
| 40 |
+
|
| 41 |
+
layer_id = get_layer_id_for_vit(n, num_layers)
|
| 42 |
+
group_name = "layer_%d_%s" % (layer_id, g_decay)
|
| 43 |
+
|
| 44 |
+
if group_name not in param_group_names:
|
| 45 |
+
this_scale = layer_scales[layer_id]
|
| 46 |
+
|
| 47 |
+
param_group_names[group_name] = {
|
| 48 |
+
"lr_scale": this_scale,
|
| 49 |
+
"weight_decay": this_decay,
|
| 50 |
+
"params": [],
|
| 51 |
+
}
|
| 52 |
+
param_groups[group_name] = {
|
| 53 |
+
"lr_scale": this_scale,
|
| 54 |
+
"weight_decay": this_decay,
|
| 55 |
+
"params": [],
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
param_group_names[group_name]["params"].append(n)
|
| 59 |
+
param_groups[group_name]["params"].append(p)
|
| 60 |
+
|
| 61 |
+
print("parameter groups: \n%s" % json.dumps(param_group_names, indent=2))
|
| 62 |
+
|
| 63 |
+
return list(param_groups.values())
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_layer_id_for_vit(name, num_layers):
|
| 67 |
+
"""
|
| 68 |
+
Assign a parameter with its layer id
|
| 69 |
+
Following BEiT: https://github.com/microsoft/unilm/blob/master/beit/optim_factory.py#L33
|
| 70 |
+
"""
|
| 71 |
+
if name in [
|
| 72 |
+
"cls_token",
|
| 73 |
+
"mask_token",
|
| 74 |
+
]:
|
| 75 |
+
return 0
|
| 76 |
+
elif name.startswith("patch_embed"):
|
| 77 |
+
return 0
|
| 78 |
+
elif name.startswith("pos_embed"):
|
| 79 |
+
return 0
|
| 80 |
+
elif name.startswith("blocks"):
|
| 81 |
+
return int(name.split(".")[1]) + 1
|
| 82 |
+
else:
|
| 83 |
+
return num_layers
|
fMRI-foundation-model-old/flat/util/lr_sched.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import math
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def adjust_learning_rate(optimizer, epoch, args):
|
| 11 |
+
"""Decay the learning rate with half-cycle cosine after warmup"""
|
| 12 |
+
if epoch < args.warmup_epochs:
|
| 13 |
+
lr = args.lr * epoch / args.warmup_epochs
|
| 14 |
+
else:
|
| 15 |
+
lr = args.min_lr + (args.lr - args.min_lr) * 0.5 * (
|
| 16 |
+
1.0
|
| 17 |
+
+ math.cos(
|
| 18 |
+
math.pi
|
| 19 |
+
* (epoch - args.warmup_epochs)
|
| 20 |
+
/ (args.epochs - args.warmup_epochs)
|
| 21 |
+
)
|
| 22 |
+
)
|
| 23 |
+
for param_group in optimizer.param_groups:
|
| 24 |
+
if "lr_scale" in param_group:
|
| 25 |
+
param_group["lr"] = lr * param_group["lr_scale"]
|
| 26 |
+
else:
|
| 27 |
+
param_group["lr"] = lr
|
| 28 |
+
return lr
|
fMRI-foundation-model-old/flat/util/meters.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
from sklearn.metrics import average_precision_score
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def topks_correct(preds, labels, ks):
|
| 11 |
+
"""
|
| 12 |
+
Given the predictions, labels, and a list of top-k values, compute the
|
| 13 |
+
number of correct predictions for each top-k value.
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
preds (array): array of predictions. Dimension is batchsize
|
| 17 |
+
N x ClassNum.
|
| 18 |
+
labels (array): array of labels. Dimension is batchsize N.
|
| 19 |
+
ks (list): list of top-k values. For example, ks = [1, 5] correspods
|
| 20 |
+
to top-1 and top-5.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
topks_correct (list): list of numbers, where the `i`-th entry
|
| 24 |
+
corresponds to the number of top-`ks[i]` correct predictions.
|
| 25 |
+
"""
|
| 26 |
+
assert preds.size(0) == labels.size(
|
| 27 |
+
0
|
| 28 |
+
), "Batch dim of predictions and labels must match"
|
| 29 |
+
# Find the top max_k predictions for each sample
|
| 30 |
+
_top_max_k_vals, top_max_k_inds = torch.topk(
|
| 31 |
+
preds, max(ks), dim=1, largest=True, sorted=True
|
| 32 |
+
)
|
| 33 |
+
# (batch_size, max_k) -> (max_k, batch_size).
|
| 34 |
+
top_max_k_inds = top_max_k_inds.t()
|
| 35 |
+
# (batch_size, ) -> (max_k, batch_size).
|
| 36 |
+
rep_max_k_labels = labels.view(1, -1).expand_as(top_max_k_inds)
|
| 37 |
+
# (i, j) = 1 if top i-th prediction for the j-th sample is correct.
|
| 38 |
+
top_max_k_correct = top_max_k_inds.eq(rep_max_k_labels)
|
| 39 |
+
# Compute the number of topk correct predictions for each k.
|
| 40 |
+
topks_correct = [top_max_k_correct[:k, :].float().sum() for k in ks]
|
| 41 |
+
return topks_correct
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def topk_errors(preds, labels, ks):
|
| 45 |
+
"""
|
| 46 |
+
Computes the top-k error for each k.
|
| 47 |
+
Args:
|
| 48 |
+
preds (array): array of predictions. Dimension is N.
|
| 49 |
+
labels (array): array of labels. Dimension is N.
|
| 50 |
+
ks (list): list of ks to calculate the top accuracies.
|
| 51 |
+
"""
|
| 52 |
+
num_topks_correct = topks_correct(preds, labels, ks)
|
| 53 |
+
return [(1.0 - x / preds.size(0)) * 100.0 for x in num_topks_correct]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def topk_accuracies(preds, labels, ks):
|
| 57 |
+
"""
|
| 58 |
+
Computes the top-k accuracy for each k.
|
| 59 |
+
Args:
|
| 60 |
+
preds (array): array of predictions. Dimension is N.
|
| 61 |
+
labels (array): array of labels. Dimension is N.
|
| 62 |
+
ks (list): list of ks to calculate the top accuracies.
|
| 63 |
+
"""
|
| 64 |
+
num_topks_correct = topks_correct(preds, labels, ks)
|
| 65 |
+
return [(x / preds.size(0)) * 100.0 for x in num_topks_correct]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_map(preds, labels):
|
| 69 |
+
"""
|
| 70 |
+
Compute mAP for multi-label case.
|
| 71 |
+
Args:
|
| 72 |
+
preds (numpy tensor): num_examples x num_classes.
|
| 73 |
+
labels (numpy tensor): num_examples x num_classes.
|
| 74 |
+
Returns:
|
| 75 |
+
mean_ap (int): final mAP score.
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
print("Getting mAP for {} examples".format(preds.shape[0]))
|
| 79 |
+
|
| 80 |
+
preds = preds[:, ~(np.all(labels == 0, axis=0))]
|
| 81 |
+
labels = labels[:, ~(np.all(labels == 0, axis=0))]
|
| 82 |
+
aps = [0]
|
| 83 |
+
try:
|
| 84 |
+
aps = average_precision_score(labels, preds, average=None)
|
| 85 |
+
except ValueError:
|
| 86 |
+
print(
|
| 87 |
+
"Average precision requires a sufficient number of samples \
|
| 88 |
+
in a batch which are missing in this sample."
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
mean_ap = np.mean(aps)
|
| 92 |
+
return mean_ap
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class TestMeter:
|
| 96 |
+
"""
|
| 97 |
+
Perform the multi-view ensemble for testing: each video with an unique index
|
| 98 |
+
will be sampled with multiple clips, and the predictions of the clips will
|
| 99 |
+
be aggregated to produce the final prediction for the video.
|
| 100 |
+
The accuracy is calculated with the given ground truth labels.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
def __init__(
|
| 104 |
+
self,
|
| 105 |
+
num_videos,
|
| 106 |
+
num_clips,
|
| 107 |
+
num_cls,
|
| 108 |
+
overall_iters,
|
| 109 |
+
multi_label=False,
|
| 110 |
+
ensemble_method="sum",
|
| 111 |
+
):
|
| 112 |
+
"""
|
| 113 |
+
Construct tensors to store the predictions and labels. Expect to get
|
| 114 |
+
num_clips predictions from each video, and calculate the metrics on
|
| 115 |
+
num_videos videos.
|
| 116 |
+
Args:
|
| 117 |
+
num_videos (int): number of videos to test.
|
| 118 |
+
num_clips (int): number of clips sampled from each video for
|
| 119 |
+
aggregating the final prediction for the video.
|
| 120 |
+
num_cls (int): number of classes for each prediction.
|
| 121 |
+
overall_iters (int): overall iterations for testing.
|
| 122 |
+
multi_label (bool): if True, use map as the metric.
|
| 123 |
+
ensemble_method (str): method to perform the ensemble, options
|
| 124 |
+
include "sum", and "max".
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
self.num_clips = num_clips
|
| 128 |
+
self.overall_iters = overall_iters
|
| 129 |
+
self.multi_label = multi_label
|
| 130 |
+
self.ensemble_method = ensemble_method
|
| 131 |
+
# Initialize tensors.
|
| 132 |
+
self.video_preds = torch.zeros((num_videos, num_cls))
|
| 133 |
+
if multi_label:
|
| 134 |
+
self.video_preds -= 1e10
|
| 135 |
+
|
| 136 |
+
self.video_labels = (
|
| 137 |
+
torch.zeros((num_videos, num_cls))
|
| 138 |
+
if multi_label
|
| 139 |
+
else torch.zeros((num_videos)).long()
|
| 140 |
+
)
|
| 141 |
+
self.clip_count = torch.zeros((num_videos)).long()
|
| 142 |
+
self.topk_accs = []
|
| 143 |
+
self.stats = {}
|
| 144 |
+
|
| 145 |
+
# Reset metric.
|
| 146 |
+
self.reset()
|
| 147 |
+
|
| 148 |
+
def reset(self):
|
| 149 |
+
"""
|
| 150 |
+
Reset the metric.
|
| 151 |
+
"""
|
| 152 |
+
self.clip_count.zero_()
|
| 153 |
+
self.video_preds.zero_()
|
| 154 |
+
if self.multi_label:
|
| 155 |
+
self.video_preds -= 1e10
|
| 156 |
+
self.video_labels.zero_()
|
| 157 |
+
|
| 158 |
+
def update_stats(self, preds, labels, clip_ids):
|
| 159 |
+
"""
|
| 160 |
+
Collect the predictions from the current batch and perform on-the-flight
|
| 161 |
+
summation as ensemble.
|
| 162 |
+
Args:
|
| 163 |
+
preds (tensor): predictions from the current batch. Dimension is
|
| 164 |
+
N x C where N is the batch size and C is the channel size
|
| 165 |
+
(num_cls).
|
| 166 |
+
labels (tensor): the corresponding labels of the current batch.
|
| 167 |
+
Dimension is N.
|
| 168 |
+
clip_ids (tensor): clip indexes of the current batch, dimension is
|
| 169 |
+
N.
|
| 170 |
+
"""
|
| 171 |
+
for ind in range(preds.shape[0]):
|
| 172 |
+
vid_id = int(clip_ids[ind]) // self.num_clips
|
| 173 |
+
if self.video_labels[vid_id].sum() > 0:
|
| 174 |
+
assert torch.equal(
|
| 175 |
+
self.video_labels[vid_id].type(torch.FloatTensor),
|
| 176 |
+
labels[ind].type(torch.FloatTensor),
|
| 177 |
+
)
|
| 178 |
+
self.video_labels[vid_id] = labels[ind]
|
| 179 |
+
if self.ensemble_method == "sum":
|
| 180 |
+
self.video_preds[vid_id] += preds[ind]
|
| 181 |
+
elif self.ensemble_method == "max":
|
| 182 |
+
self.video_preds[vid_id] = torch.max(
|
| 183 |
+
self.video_preds[vid_id], preds[ind]
|
| 184 |
+
)
|
| 185 |
+
else:
|
| 186 |
+
raise NotImplementedError(
|
| 187 |
+
"Ensemble Method {} is not supported".format(self.ensemble_method)
|
| 188 |
+
)
|
| 189 |
+
self.clip_count[vid_id] += 1
|
| 190 |
+
|
| 191 |
+
def log_iter_stats(self, cur_iter):
|
| 192 |
+
"""
|
| 193 |
+
Log the stats.
|
| 194 |
+
Args:
|
| 195 |
+
cur_iter (int): the current iteration of testing.
|
| 196 |
+
"""
|
| 197 |
+
stats = {
|
| 198 |
+
"split": "test_iter",
|
| 199 |
+
"cur_iter": "{}".format(cur_iter + 1),
|
| 200 |
+
}
|
| 201 |
+
print(stats)
|
| 202 |
+
|
| 203 |
+
def finalize_metrics(self, ks=(1, 5)):
|
| 204 |
+
"""
|
| 205 |
+
Calculate and log the final ensembled metrics.
|
| 206 |
+
ks (tuple): list of top-k values for topk_accuracies. For example,
|
| 207 |
+
ks = (1, 5) correspods to top-1 and top-5 accuracy.
|
| 208 |
+
"""
|
| 209 |
+
if not all(self.clip_count == self.num_clips):
|
| 210 |
+
print(
|
| 211 |
+
"clip count {} ~= num clips {}".format(
|
| 212 |
+
", ".join(
|
| 213 |
+
[
|
| 214 |
+
"{}: {}".format(i, k)
|
| 215 |
+
for i, k in enumerate(self.clip_count.tolist())
|
| 216 |
+
]
|
| 217 |
+
),
|
| 218 |
+
self.num_clips,
|
| 219 |
+
)
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
self.stats = {"split": "test_final"}
|
| 223 |
+
if self.multi_label:
|
| 224 |
+
map = get_map(
|
| 225 |
+
self.video_preds.cpu().numpy(), self.video_labels.cpu().numpy()
|
| 226 |
+
)
|
| 227 |
+
self.stats["map"] = map
|
| 228 |
+
else:
|
| 229 |
+
num_topks_correct = topks_correct(self.video_preds, self.video_labels, ks)
|
| 230 |
+
topks = [(x / self.video_preds.size(0)) * 100.0 for x in num_topks_correct]
|
| 231 |
+
assert len({len(ks), len(topks)}) == 1
|
| 232 |
+
for k, topk in zip(ks, topks):
|
| 233 |
+
self.stats["top{}_acc".format(k)] = "{:.{prec}f}".format(topk, prec=2)
|
| 234 |
+
print(self.stats)
|
fMRI-foundation-model-old/flat/util/misc.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
# References:
|
| 8 |
+
# DeiT: https://github.com/facebookresearch/deit
|
| 9 |
+
# BEiT: https://github.com/microsoft/unilm/tree/master/beit
|
| 10 |
+
# --------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
import builtins
|
| 13 |
+
import datetime
|
| 14 |
+
import math
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
from collections import defaultdict, deque, OrderedDict
|
| 18 |
+
|
| 19 |
+
import util.logging as logging
|
| 20 |
+
import psutil
|
| 21 |
+
import torch
|
| 22 |
+
import torch.distributed as dist
|
| 23 |
+
from iopath.common.file_io import g_pathmgr as pathmgr
|
| 24 |
+
from util.logging import master_print as print
|
| 25 |
+
from torch import inf
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
logger = logging.get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class SmoothedValue:
|
| 32 |
+
"""Track a series of values and provide access to smoothed values over a
|
| 33 |
+
window or the global series average.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self, window_size=20, fmt=None):
|
| 37 |
+
if fmt is None:
|
| 38 |
+
fmt = "{median:.4f} ({global_avg:.4f})"
|
| 39 |
+
self.deque = deque(maxlen=window_size)
|
| 40 |
+
self.total = 0.0
|
| 41 |
+
self.count = 0
|
| 42 |
+
self.fmt = fmt
|
| 43 |
+
|
| 44 |
+
def update(self, value, n=1):
|
| 45 |
+
self.deque.append(value)
|
| 46 |
+
self.count += n
|
| 47 |
+
self.total += value * n
|
| 48 |
+
|
| 49 |
+
def synchronize_between_processes(self):
|
| 50 |
+
"""
|
| 51 |
+
Warning: does not synchronize the deque!
|
| 52 |
+
"""
|
| 53 |
+
if not is_dist_avail_and_initialized():
|
| 54 |
+
return
|
| 55 |
+
t = torch.tensor([self.count, self.total], dtype=torch.float64, device="cuda")
|
| 56 |
+
dist.barrier()
|
| 57 |
+
dist.all_reduce(t)
|
| 58 |
+
t = t.tolist()
|
| 59 |
+
self.count = int(t[0])
|
| 60 |
+
self.total = t[1]
|
| 61 |
+
|
| 62 |
+
@property
|
| 63 |
+
def median(self):
|
| 64 |
+
d = torch.tensor(list(self.deque))
|
| 65 |
+
return d.median().item()
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def avg(self):
|
| 69 |
+
d = torch.tensor(list(self.deque), dtype=torch.float32)
|
| 70 |
+
return d.mean().item()
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def global_avg(self):
|
| 74 |
+
return self.total / self.count
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def max(self):
|
| 78 |
+
return max(self.deque)
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def value(self):
|
| 82 |
+
return self.deque[-1]
|
| 83 |
+
|
| 84 |
+
def __str__(self):
|
| 85 |
+
return self.fmt.format(
|
| 86 |
+
median=self.median,
|
| 87 |
+
avg=self.avg,
|
| 88 |
+
global_avg=self.global_avg,
|
| 89 |
+
max=self.max,
|
| 90 |
+
value=self.value,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class MetricLogger:
|
| 95 |
+
def __init__(self, delimiter="\t"):
|
| 96 |
+
self.meters = defaultdict(SmoothedValue)
|
| 97 |
+
self.delimiter = delimiter
|
| 98 |
+
|
| 99 |
+
def update(self, **kwargs):
|
| 100 |
+
for k, v in kwargs.items():
|
| 101 |
+
if v is None:
|
| 102 |
+
continue
|
| 103 |
+
if isinstance(v, torch.Tensor):
|
| 104 |
+
v = v.item()
|
| 105 |
+
assert isinstance(v, (float, int))
|
| 106 |
+
self.meters[k].update(v)
|
| 107 |
+
|
| 108 |
+
def __getattr__(self, attr):
|
| 109 |
+
if attr in self.meters:
|
| 110 |
+
return self.meters[attr]
|
| 111 |
+
if attr in self.__dict__:
|
| 112 |
+
return self.__dict__[attr]
|
| 113 |
+
raise AttributeError(
|
| 114 |
+
"'{}' object has no attribute '{}'".format(type(self).__name__, attr)
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
def __str__(self):
|
| 118 |
+
loss_str = []
|
| 119 |
+
for name, meter in self.meters.items():
|
| 120 |
+
loss_str.append("{}: {}".format(name, str(meter)))
|
| 121 |
+
return self.delimiter.join(loss_str)
|
| 122 |
+
|
| 123 |
+
def synchronize_between_processes(self):
|
| 124 |
+
for meter in self.meters.values():
|
| 125 |
+
meter.synchronize_between_processes()
|
| 126 |
+
|
| 127 |
+
def add_meter(self, name, meter):
|
| 128 |
+
self.meters[name] = meter
|
| 129 |
+
|
| 130 |
+
def log_every(self, iterable, print_freq, header=None, total_steps=None):
|
| 131 |
+
i = 0
|
| 132 |
+
total_steps = total_steps or len(iterable)
|
| 133 |
+
if not header:
|
| 134 |
+
header = ""
|
| 135 |
+
start_time = time.time()
|
| 136 |
+
end = time.time()
|
| 137 |
+
iter_time = SmoothedValue(fmt="{avg:.4f}")
|
| 138 |
+
data_time = SmoothedValue(fmt="{avg:.4f}")
|
| 139 |
+
space_fmt = ":" + str(len(str(total_steps))) + "d"
|
| 140 |
+
log_msg = [
|
| 141 |
+
header,
|
| 142 |
+
"[{0" + space_fmt + "}/{1}]",
|
| 143 |
+
"eta: {eta}",
|
| 144 |
+
"{meters}",
|
| 145 |
+
"time: {time}",
|
| 146 |
+
"data: {data}",
|
| 147 |
+
]
|
| 148 |
+
if torch.cuda.is_available():
|
| 149 |
+
log_msg.append("max mem: {memory:.0f}")
|
| 150 |
+
log_msg = self.delimiter.join(log_msg)
|
| 151 |
+
MB = 1024.0 * 1024.0
|
| 152 |
+
for obj in iterable:
|
| 153 |
+
data_time.update(time.time() - end)
|
| 154 |
+
yield obj
|
| 155 |
+
iter_time.update(time.time() - end)
|
| 156 |
+
if i % print_freq == 0 or i == total_steps - 1:
|
| 157 |
+
eta_seconds = iter_time.global_avg * (total_steps - i)
|
| 158 |
+
eta_string = str(datetime.timedelta(seconds=int(eta_seconds)))
|
| 159 |
+
if torch.cuda.is_available():
|
| 160 |
+
print(
|
| 161 |
+
log_msg.format(
|
| 162 |
+
i,
|
| 163 |
+
total_steps,
|
| 164 |
+
eta=eta_string,
|
| 165 |
+
meters=str(self),
|
| 166 |
+
time=str(iter_time),
|
| 167 |
+
data=str(data_time),
|
| 168 |
+
memory=torch.cuda.max_memory_allocated() / MB,
|
| 169 |
+
)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
else:
|
| 173 |
+
print(
|
| 174 |
+
log_msg.format(
|
| 175 |
+
i,
|
| 176 |
+
total_steps,
|
| 177 |
+
eta=eta_string,
|
| 178 |
+
meters=str(self),
|
| 179 |
+
time=str(iter_time),
|
| 180 |
+
data=str(data_time),
|
| 181 |
+
)
|
| 182 |
+
)
|
| 183 |
+
i += 1
|
| 184 |
+
end = time.time()
|
| 185 |
+
total_time = time.time() - start_time
|
| 186 |
+
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
| 187 |
+
print(
|
| 188 |
+
"{} Total time: {} ({:.4f} s / it)".format(
|
| 189 |
+
header, total_time_str, total_time / total_steps
|
| 190 |
+
)
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def setup_for_distributed(is_master):
|
| 195 |
+
"""
|
| 196 |
+
This function disables printing when not in master process
|
| 197 |
+
"""
|
| 198 |
+
builtin_print = builtins.print
|
| 199 |
+
|
| 200 |
+
def print(*args, **kwargs):
|
| 201 |
+
force = kwargs.pop("force", False)
|
| 202 |
+
force = force or (get_world_size() > 8)
|
| 203 |
+
if is_master or force:
|
| 204 |
+
now = datetime.datetime.now().time()
|
| 205 |
+
builtin_print("[{}] ".format(now), end="") # print with time stamp
|
| 206 |
+
builtin_print(*args, **kwargs)
|
| 207 |
+
|
| 208 |
+
builtins.print = print
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def is_dist_avail_and_initialized():
|
| 212 |
+
if not dist.is_available():
|
| 213 |
+
return False
|
| 214 |
+
if not dist.is_initialized():
|
| 215 |
+
return False
|
| 216 |
+
return True
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def get_world_size():
|
| 220 |
+
if not is_dist_avail_and_initialized():
|
| 221 |
+
return 1
|
| 222 |
+
return dist.get_world_size()
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def get_rank():
|
| 226 |
+
if not is_dist_avail_and_initialized():
|
| 227 |
+
return 0
|
| 228 |
+
return dist.get_rank()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def is_main_process():
|
| 232 |
+
return get_rank() == 0
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def save_on_master(state, path):
|
| 236 |
+
if is_main_process():
|
| 237 |
+
print(f"save path {path}")
|
| 238 |
+
with pathmgr.open(path, "wb") as f:
|
| 239 |
+
torch.save(state, f)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def init_distributed_mode(args):
|
| 243 |
+
if args.no_env:
|
| 244 |
+
pass
|
| 245 |
+
elif args.dist_on_itp:
|
| 246 |
+
args.rank = int(os.environ["OMPI_COMM_WORLD_RANK"])
|
| 247 |
+
args.world_size = int(os.environ["OMPI_COMM_WORLD_SIZE"])
|
| 248 |
+
args.gpu = int(os.environ["OMPI_COMM_WORLD_LOCAL_RANK"])
|
| 249 |
+
args.dist_url = "tcp://%s:%s" % (
|
| 250 |
+
os.environ["MASTER_ADDR"],
|
| 251 |
+
os.environ["MASTER_PORT"],
|
| 252 |
+
)
|
| 253 |
+
os.environ["LOCAL_RANK"] = str(args.gpu)
|
| 254 |
+
os.environ["RANK"] = str(args.rank)
|
| 255 |
+
os.environ["WORLD_SIZE"] = str(args.world_size)
|
| 256 |
+
# ["RANK", "WORLD_SIZE", "MASTER_ADDR", "MASTER_PORT", "LOCAL_RANK"]
|
| 257 |
+
elif "RANK" in os.environ and "WORLD_SIZE" in os.environ:
|
| 258 |
+
args.rank = int(os.environ["RANK"])
|
| 259 |
+
args.world_size = int(os.environ["WORLD_SIZE"])
|
| 260 |
+
args.gpu = int(os.environ["LOCAL_RANK"])
|
| 261 |
+
elif "SLURM_PROCID" in os.environ:
|
| 262 |
+
args.rank = int(os.environ["SLURM_PROCID"])
|
| 263 |
+
args.gpu = args.rank % torch.cuda.device_count()
|
| 264 |
+
else:
|
| 265 |
+
print("Not using distributed mode")
|
| 266 |
+
setup_for_distributed(is_master=True) # hack
|
| 267 |
+
args.distributed = False
|
| 268 |
+
return
|
| 269 |
+
|
| 270 |
+
args.distributed = True
|
| 271 |
+
|
| 272 |
+
torch.cuda.set_device(args.gpu)
|
| 273 |
+
args.dist_backend = "nccl"
|
| 274 |
+
print(
|
| 275 |
+
"| distributed init (rank {}): {}, gpu {}".format(
|
| 276 |
+
args.rank, args.dist_url, args.gpu
|
| 277 |
+
),
|
| 278 |
+
# flush=True,
|
| 279 |
+
)
|
| 280 |
+
torch.distributed.init_process_group(
|
| 281 |
+
backend=args.dist_backend,
|
| 282 |
+
world_size=args.world_size,
|
| 283 |
+
rank=args.rank,
|
| 284 |
+
)
|
| 285 |
+
# init_method=args.dist_url,
|
| 286 |
+
torch.distributed.barrier()
|
| 287 |
+
setup_for_distributed(args.rank == 0)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class NativeScalerWithGradNormCount:
|
| 291 |
+
state_dict_key = "amp_scaler"
|
| 292 |
+
|
| 293 |
+
def __init__(self, fp32=False):
|
| 294 |
+
self._scaler = torch.cuda.amp.GradScaler(enabled=not fp32)
|
| 295 |
+
|
| 296 |
+
def __call__(
|
| 297 |
+
self,
|
| 298 |
+
loss,
|
| 299 |
+
optimizer,
|
| 300 |
+
clip_grad=None,
|
| 301 |
+
parameters=None,
|
| 302 |
+
create_graph=False,
|
| 303 |
+
update_grad=True,
|
| 304 |
+
):
|
| 305 |
+
self._scaler.scale(loss).backward(create_graph=create_graph)
|
| 306 |
+
if update_grad:
|
| 307 |
+
if clip_grad is not None:
|
| 308 |
+
assert parameters is not None
|
| 309 |
+
self._scaler.unscale_(
|
| 310 |
+
optimizer
|
| 311 |
+
) # unscale the gradients of optimizer's assigned params in-place
|
| 312 |
+
norm = torch.nn.utils.clip_grad_norm_(parameters, clip_grad)
|
| 313 |
+
else:
|
| 314 |
+
self._scaler.unscale_(optimizer)
|
| 315 |
+
norm = get_grad_norm_(parameters)
|
| 316 |
+
self._scaler.step(optimizer)
|
| 317 |
+
self._scaler.update()
|
| 318 |
+
else:
|
| 319 |
+
norm = None
|
| 320 |
+
return norm
|
| 321 |
+
|
| 322 |
+
def state_dict(self):
|
| 323 |
+
return self._scaler.state_dict()
|
| 324 |
+
|
| 325 |
+
def load_state_dict(self, state_dict):
|
| 326 |
+
self._scaler.load_state_dict(state_dict)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def get_grad_norm_(parameters, norm_type: float = 2.0) -> torch.Tensor:
|
| 330 |
+
if isinstance(parameters, torch.Tensor):
|
| 331 |
+
parameters = [parameters]
|
| 332 |
+
parameters = [p for p in parameters if p.grad is not None]
|
| 333 |
+
norm_type = float(norm_type)
|
| 334 |
+
if len(parameters) == 0:
|
| 335 |
+
return torch.tensor(0.0)
|
| 336 |
+
device = parameters[0].grad.device
|
| 337 |
+
if norm_type == inf:
|
| 338 |
+
total_norm = max(p.grad.detach().abs().max().to(device) for p in parameters)
|
| 339 |
+
else:
|
| 340 |
+
total_norm = torch.norm(
|
| 341 |
+
torch.stack(
|
| 342 |
+
[torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]
|
| 343 |
+
),
|
| 344 |
+
norm_type,
|
| 345 |
+
)
|
| 346 |
+
return total_norm
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def save_model(args, epoch, model, model_without_ddp, optimizer, loss_scaler):
|
| 350 |
+
checkpoint_path = "{}/checkpoint-{:05d}.pth".format(args.output_dir, epoch)
|
| 351 |
+
to_save = {
|
| 352 |
+
"model": model_without_ddp.state_dict(),
|
| 353 |
+
"optimizer": optimizer.state_dict(),
|
| 354 |
+
"epoch": epoch,
|
| 355 |
+
"scaler": loss_scaler.state_dict(),
|
| 356 |
+
"args": args,
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
save_on_master(to_save, checkpoint_path)
|
| 360 |
+
return checkpoint_path
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def get_last_checkpoint(args):
|
| 364 |
+
"""
|
| 365 |
+
Get the last checkpoint from the checkpointing folder.
|
| 366 |
+
Args:
|
| 367 |
+
path_to_job (string): the path to the folder of the current job.
|
| 368 |
+
"""
|
| 369 |
+
d = args.output_dir
|
| 370 |
+
names = pathmgr.ls(d) if pathmgr.exists(d) else []
|
| 371 |
+
names = [f for f in names if "checkpoint" in f]
|
| 372 |
+
if len(names) == 0:
|
| 373 |
+
print("No checkpoints found in '{}'.".format(d))
|
| 374 |
+
return None
|
| 375 |
+
else:
|
| 376 |
+
# Sort the checkpoints by epoch.
|
| 377 |
+
name = sorted(names)[-1]
|
| 378 |
+
return os.path.join(d, name)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def load_model(args, model_without_ddp, optimizer, loss_scaler):
|
| 382 |
+
if not args.resume:
|
| 383 |
+
args.resume = get_last_checkpoint(args)
|
| 384 |
+
if args.resume:
|
| 385 |
+
if args.resume.startswith("https"):
|
| 386 |
+
checkpoint = torch.hub.load_state_dict_from_url(
|
| 387 |
+
args.resume, map_location="cpu", check_hash=True
|
| 388 |
+
)
|
| 389 |
+
else:
|
| 390 |
+
with pathmgr.open(args.resume, "rb") as f:
|
| 391 |
+
checkpoint = torch.load(f, map_location="cpu")
|
| 392 |
+
model_without_ddp.load_state_dict(checkpoint["model"])
|
| 393 |
+
print("Resume checkpoint %s" % args.resume)
|
| 394 |
+
if (
|
| 395 |
+
"optimizer" in checkpoint
|
| 396 |
+
and "epoch" in checkpoint
|
| 397 |
+
and not (hasattr(args, "eval") and args.eval)
|
| 398 |
+
):
|
| 399 |
+
optimizer.load_state_dict(checkpoint["optimizer"])
|
| 400 |
+
args.start_epoch = checkpoint["epoch"] + 1
|
| 401 |
+
if "scaler" in checkpoint:
|
| 402 |
+
loss_scaler.load_state_dict(checkpoint["scaler"])
|
| 403 |
+
print("With optim & sched!")
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def all_reduce_mean(x):
|
| 407 |
+
world_size = get_world_size()
|
| 408 |
+
if world_size > 1:
|
| 409 |
+
x_reduce = torch.tensor(x).cuda()
|
| 410 |
+
dist.all_reduce(x_reduce)
|
| 411 |
+
x_reduce /= world_size
|
| 412 |
+
return x_reduce.item()
|
| 413 |
+
else:
|
| 414 |
+
return x
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def gpu_mem_usage():
|
| 418 |
+
"""
|
| 419 |
+
Compute the GPU memory usage for the current device (GB).
|
| 420 |
+
"""
|
| 421 |
+
if torch.cuda.is_available():
|
| 422 |
+
mem_usage_bytes = torch.cuda.max_memory_allocated()
|
| 423 |
+
else:
|
| 424 |
+
mem_usage_bytes = 0
|
| 425 |
+
return mem_usage_bytes / 1024**3
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def cpu_mem_usage():
|
| 429 |
+
"""
|
| 430 |
+
Compute the system memory (RAM) usage for the current device (GB).
|
| 431 |
+
Returns:
|
| 432 |
+
usage (float): used memory (GB).
|
| 433 |
+
total (float): total memory (GB).
|
| 434 |
+
"""
|
| 435 |
+
vram = psutil.virtual_memory()
|
| 436 |
+
usage = (vram.total - vram.available) / 1024**3
|
| 437 |
+
total = vram.total / 1024**3
|
| 438 |
+
|
| 439 |
+
return usage, total
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def all_gather(tensors):
|
| 443 |
+
"""
|
| 444 |
+
All gathers the provided tensors from all processes across machines.
|
| 445 |
+
Args:
|
| 446 |
+
tensors (list): tensors to perform all gather across all processes in
|
| 447 |
+
all machines.
|
| 448 |
+
"""
|
| 449 |
+
|
| 450 |
+
gather_list = []
|
| 451 |
+
output_tensor = []
|
| 452 |
+
world_size = dist.get_world_size()
|
| 453 |
+
for tensor in tensors:
|
| 454 |
+
tensor_placeholder = [torch.ones_like(tensor) for _ in range(world_size)]
|
| 455 |
+
dist.all_gather(tensor_placeholder, tensor, async_op=False)
|
| 456 |
+
gather_list.append(tensor_placeholder)
|
| 457 |
+
for gathered_tensor in gather_list:
|
| 458 |
+
output_tensor.append(torch.cat(gathered_tensor, dim=0))
|
| 459 |
+
return output_tensor
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def add_weight_decay(model, weight_decay=1e-5, skip_list=(), bias_wd=False):
|
| 463 |
+
decay = []
|
| 464 |
+
no_decay = []
|
| 465 |
+
for name, param in model.named_parameters():
|
| 466 |
+
if not param.requires_grad:
|
| 467 |
+
continue # frozen weights
|
| 468 |
+
if (
|
| 469 |
+
(not bias_wd)
|
| 470 |
+
and len(param.shape) == 1
|
| 471 |
+
or name.endswith(".bias")
|
| 472 |
+
or name in skip_list
|
| 473 |
+
):
|
| 474 |
+
no_decay.append(param)
|
| 475 |
+
else:
|
| 476 |
+
decay.append(param)
|
| 477 |
+
return [
|
| 478 |
+
{"params": no_decay, "weight_decay": 0.0},
|
| 479 |
+
{"params": decay, "weight_decay": weight_decay},
|
| 480 |
+
]
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def inflate(model_2d, model_3d):
|
| 484 |
+
state_dict_inflated = OrderedDict()
|
| 485 |
+
for k, v2d in model_2d.items():
|
| 486 |
+
if "patch_embed.proj.weight" in k:
|
| 487 |
+
v3d = model_3d[k]
|
| 488 |
+
v3d = v2d.unsqueeze(2).repeat(1, 1, v3d.shape[2], 1, 1) / v3d.shape[2]
|
| 489 |
+
state_dict_inflated[k] = v3d.clone()
|
| 490 |
+
elif "pos_embed" in k:
|
| 491 |
+
pos_embed_cls, pos_embed_spatial = torch.split(v2d, [1, 196], dim=1)
|
| 492 |
+
state_dict_inflated["pos_embed_cls"] = pos_embed_cls.clone()
|
| 493 |
+
state_dict_inflated["pos_embed"] = pos_embed_spatial.clone()
|
| 494 |
+
else:
|
| 495 |
+
state_dict_inflated[k] = v2d.clone()
|
| 496 |
+
return state_dict_inflated
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def convert_checkpoint(model_2d):
|
| 500 |
+
state_dict_inflated = OrderedDict()
|
| 501 |
+
for k, v2d in model_2d.items():
|
| 502 |
+
if "head.projection.weight" in k:
|
| 503 |
+
state_dict_inflated["head.weight"] = v2d.clone()
|
| 504 |
+
elif "head.projection.bias" in k:
|
| 505 |
+
state_dict_inflated["head.bias"] = v2d.clone()
|
| 506 |
+
else:
|
| 507 |
+
state_dict_inflated[k] = v2d.clone()
|
| 508 |
+
return state_dict_inflated
|
fMRI-foundation-model-old/flat/util/pos_embed.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
# Position embedding utils
|
| 8 |
+
# --------------------------------------------------------
|
| 9 |
+
|
| 10 |
+
import util.logging as logging
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
logger = logging.get_logger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# --------------------------------------------------------
|
| 19 |
+
# Interpolate position embeddings for high-resolution
|
| 20 |
+
# References:
|
| 21 |
+
# DeiT: https://github.com/facebookresearch/deit
|
| 22 |
+
# --------------------------------------------------------
|
| 23 |
+
def interpolate_pos_embed(model, checkpoint_model):
|
| 24 |
+
if "pos_embed" in checkpoint_model:
|
| 25 |
+
pos_embed_checkpoint = checkpoint_model["pos_embed"]
|
| 26 |
+
embedding_size = pos_embed_checkpoint.shape[-1]
|
| 27 |
+
num_patches = model.patch_embed.num_patches
|
| 28 |
+
num_extra_tokens = model.pos_embed.shape[-2] - num_patches
|
| 29 |
+
# height (== width) for the checkpoint position embedding
|
| 30 |
+
orig_size = int((pos_embed_checkpoint.shape[-2] - num_extra_tokens) ** 0.5)
|
| 31 |
+
# height (== width) for the new position embedding
|
| 32 |
+
new_size = int(num_patches**0.5)
|
| 33 |
+
# class_token and dist_token are kept unchanged
|
| 34 |
+
if orig_size != new_size:
|
| 35 |
+
print(
|
| 36 |
+
"Position interpolate from %dx%d to %dx%d"
|
| 37 |
+
% (orig_size, orig_size, new_size, new_size)
|
| 38 |
+
)
|
| 39 |
+
extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens]
|
| 40 |
+
# only the position tokens are interpolated
|
| 41 |
+
pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:]
|
| 42 |
+
pos_tokens = pos_tokens.reshape(
|
| 43 |
+
-1, orig_size, orig_size, embedding_size
|
| 44 |
+
).permute(0, 3, 1, 2)
|
| 45 |
+
pos_tokens = torch.nn.functional.interpolate(
|
| 46 |
+
pos_tokens,
|
| 47 |
+
size=(new_size, new_size),
|
| 48 |
+
mode="bicubic",
|
| 49 |
+
align_corners=False,
|
| 50 |
+
)
|
| 51 |
+
pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)
|
| 52 |
+
new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1)
|
| 53 |
+
checkpoint_model["pos_embed"] = new_pos_embed
|
fMRI-foundation-model-old/flat/util/video_vit.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
import util.logging as logging
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from timm.layers import to_2tuple
|
| 9 |
+
from timm.models.vision_transformer import DropPath, Mlp
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
logger = logging.get_logger(__name__)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class PatchEmbed(nn.Module):
|
| 16 |
+
"""Image to Patch Embedding"""
|
| 17 |
+
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
img_size=224,
|
| 21 |
+
patch_size=16,
|
| 22 |
+
in_chans=3,
|
| 23 |
+
embed_dim=768,
|
| 24 |
+
# temporal related:
|
| 25 |
+
frames=32,
|
| 26 |
+
t_patch_size=4,
|
| 27 |
+
):
|
| 28 |
+
super().__init__()
|
| 29 |
+
img_size = to_2tuple(img_size)
|
| 30 |
+
patch_size = to_2tuple(patch_size)
|
| 31 |
+
assert img_size[1] % patch_size[1] == 0
|
| 32 |
+
assert img_size[0] % patch_size[0] == 0
|
| 33 |
+
assert frames % t_patch_size == 0
|
| 34 |
+
num_patches = (
|
| 35 |
+
(img_size[1] // patch_size[1])
|
| 36 |
+
* (img_size[0] // patch_size[0])
|
| 37 |
+
* (frames // t_patch_size)
|
| 38 |
+
)
|
| 39 |
+
self.input_size = (
|
| 40 |
+
frames // t_patch_size,
|
| 41 |
+
img_size[0] // patch_size[0],
|
| 42 |
+
img_size[1] // patch_size[1],
|
| 43 |
+
)
|
| 44 |
+
print(
|
| 45 |
+
f"img_size {img_size} patch_size {patch_size} frames {frames} t_patch_size {t_patch_size}"
|
| 46 |
+
)
|
| 47 |
+
self.img_size = img_size
|
| 48 |
+
self.patch_size = patch_size
|
| 49 |
+
self.in_chans = in_chans
|
| 50 |
+
|
| 51 |
+
self.frames = frames
|
| 52 |
+
self.t_patch_size = t_patch_size
|
| 53 |
+
|
| 54 |
+
self.num_patches = num_patches
|
| 55 |
+
|
| 56 |
+
self.grid_size = img_size[0] // patch_size[0], img_size[1] // patch_size[1]
|
| 57 |
+
self.t_grid_size = frames // t_patch_size
|
| 58 |
+
|
| 59 |
+
kernel_size = [t_patch_size] + list(patch_size)
|
| 60 |
+
self.proj = nn.Conv3d(
|
| 61 |
+
in_chans, embed_dim, kernel_size=kernel_size, stride=kernel_size
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
def forward(self, x):
|
| 65 |
+
B, C, T, H, W = x.shape
|
| 66 |
+
assert (
|
| 67 |
+
H == self.img_size[0] and W == self.img_size[1]
|
| 68 |
+
), f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
|
| 69 |
+
assert T == self.frames
|
| 70 |
+
x = self.proj(x).flatten(3)
|
| 71 |
+
x = torch.einsum("ncts->ntsc", x) # [N, T, H*W, C]
|
| 72 |
+
return x
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class Attention(nn.Module):
|
| 76 |
+
def __init__(
|
| 77 |
+
self,
|
| 78 |
+
dim,
|
| 79 |
+
num_heads=8,
|
| 80 |
+
qkv_bias=False,
|
| 81 |
+
qk_scale=None,
|
| 82 |
+
attn_drop=0.0,
|
| 83 |
+
proj_drop=0.0,
|
| 84 |
+
input_size=(4, 14, 14),
|
| 85 |
+
):
|
| 86 |
+
super().__init__()
|
| 87 |
+
assert dim % num_heads == 0, "dim should be divisible by num_heads"
|
| 88 |
+
self.num_heads = num_heads
|
| 89 |
+
head_dim = dim // num_heads
|
| 90 |
+
self.scale = qk_scale or head_dim**-0.5
|
| 91 |
+
|
| 92 |
+
self.q = nn.Linear(dim, dim, bias=qkv_bias)
|
| 93 |
+
self.k = nn.Linear(dim, dim, bias=qkv_bias)
|
| 94 |
+
self.v = nn.Linear(dim, dim, bias=qkv_bias)
|
| 95 |
+
assert attn_drop == 0.0 # do not use
|
| 96 |
+
self.proj = nn.Linear(dim, dim)
|
| 97 |
+
self.proj_drop = nn.Dropout(proj_drop)
|
| 98 |
+
self.input_size = input_size
|
| 99 |
+
assert input_size[1] == input_size[2]
|
| 100 |
+
|
| 101 |
+
def forward(self, x):
|
| 102 |
+
B, N, C = x.shape
|
| 103 |
+
q = (
|
| 104 |
+
self.q(x)
|
| 105 |
+
.reshape(B, N, self.num_heads, C // self.num_heads)
|
| 106 |
+
.permute(0, 2, 1, 3)
|
| 107 |
+
)
|
| 108 |
+
k = (
|
| 109 |
+
self.k(x)
|
| 110 |
+
.reshape(B, N, self.num_heads, C // self.num_heads)
|
| 111 |
+
.permute(0, 2, 1, 3)
|
| 112 |
+
)
|
| 113 |
+
v = (
|
| 114 |
+
self.v(x)
|
| 115 |
+
.reshape(B, N, self.num_heads, C // self.num_heads)
|
| 116 |
+
.permute(0, 2, 1, 3)
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
attn = (q @ k.transpose(-2, -1)) * self.scale
|
| 120 |
+
|
| 121 |
+
attn = attn.softmax(dim=-1)
|
| 122 |
+
|
| 123 |
+
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
|
| 124 |
+
x = self.proj(x)
|
| 125 |
+
x = self.proj_drop(x)
|
| 126 |
+
x = x.view(B, -1, C)
|
| 127 |
+
return x
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class Block(nn.Module):
|
| 131 |
+
"""
|
| 132 |
+
Transformer Block with specified Attention function
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
def __init__(
|
| 136 |
+
self,
|
| 137 |
+
dim,
|
| 138 |
+
num_heads,
|
| 139 |
+
mlp_ratio=4.0,
|
| 140 |
+
qkv_bias=False,
|
| 141 |
+
qk_scale=None,
|
| 142 |
+
drop=0.0,
|
| 143 |
+
attn_drop=0.0,
|
| 144 |
+
drop_path=0.0,
|
| 145 |
+
act_layer=nn.GELU,
|
| 146 |
+
norm_layer=nn.LayerNorm,
|
| 147 |
+
attn_func=Attention,
|
| 148 |
+
):
|
| 149 |
+
super().__init__()
|
| 150 |
+
self.norm1 = norm_layer(dim)
|
| 151 |
+
self.attn = attn_func(
|
| 152 |
+
dim,
|
| 153 |
+
num_heads=num_heads,
|
| 154 |
+
qkv_bias=qkv_bias,
|
| 155 |
+
qk_scale=qk_scale,
|
| 156 |
+
attn_drop=attn_drop,
|
| 157 |
+
proj_drop=drop,
|
| 158 |
+
)
|
| 159 |
+
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
|
| 160 |
+
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
|
| 161 |
+
self.norm2 = norm_layer(dim)
|
| 162 |
+
mlp_hidden_dim = int(dim * mlp_ratio)
|
| 163 |
+
self.mlp = Mlp(
|
| 164 |
+
in_features=dim,
|
| 165 |
+
hidden_features=mlp_hidden_dim,
|
| 166 |
+
act_layer=act_layer,
|
| 167 |
+
drop=drop,
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
def forward(self, x):
|
| 171 |
+
x = x + self.drop_path(self.attn(self.norm1(x)))
|
| 172 |
+
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
| 173 |
+
return x
|