338 lines
8.7 KiB
Bash
338 lines
8.7 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
#
|
||
|
|
# Train the no-extra-information T/O/S disease-history ablation.
|
||
|
|
#
|
||
|
|
# Fixed model:
|
||
|
|
# TrajMixer + all_future + relative + Weibull
|
||
|
|
#
|
||
|
|
# History modes:
|
||
|
|
# timed (T): disease identities, order, and real first-onset times
|
||
|
|
# ordered (O): disease identities and chronological order only
|
||
|
|
# set (S): unordered disease set only
|
||
|
|
#
|
||
|
|
# Jobs assigned to one GPU run sequentially. Different GPUs run in parallel.
|
||
|
|
#
|
||
|
|
# Examples:
|
||
|
|
# bash train_disease_history_ablation_linux.sh --gpus 0,1,2
|
||
|
|
# bash train_disease_history_ablation_linux.sh --gpus 0 --seeds 42 --modes ordered,set
|
||
|
|
# bash train_disease_history_ablation_linux.sh --gpus 0,1 --dry-run
|
||
|
|
#
|
||
|
|
|
||
|
|
set -uo pipefail
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
cd "$SCRIPT_DIR"
|
||
|
|
|
||
|
|
GPU_CSV=""
|
||
|
|
SEED_CSV="42,43,44"
|
||
|
|
MODE_CSV="timed,ordered,set"
|
||
|
|
NUM_WORKERS=4
|
||
|
|
BATCH_SIZE=256
|
||
|
|
PYTHON_BIN="${PYTHON_BIN:-python}"
|
||
|
|
CAMPAIGN_NAME="disease_history_ablation_no_extra"
|
||
|
|
DRY_RUN=0
|
||
|
|
|
||
|
|
EXTRA_INFO_TYPES_FILE="$SCRIPT_DIR/extra_info_types_none.txt"
|
||
|
|
ENTRYPOINT="$SCRIPT_DIR/train_all_future.py"
|
||
|
|
|
||
|
|
usage() {
|
||
|
|
cat <<'EOF'
|
||
|
|
Usage:
|
||
|
|
bash train_disease_history_ablation_linux.sh --gpus GPU_LIST [options]
|
||
|
|
|
||
|
|
Required:
|
||
|
|
--gpus LIST Comma-separated GPU ids, for example 0,1,2.
|
||
|
|
|
||
|
|
Options:
|
||
|
|
--seeds LIST Comma-separated seeds (default: 42,43,44).
|
||
|
|
--modes LIST Subset of timed,ordered,set (default: all three).
|
||
|
|
--batch-size N Batch size per task (default: 256).
|
||
|
|
--num-workers N DataLoader workers per task (default: 4).
|
||
|
|
--python PATH Python executable (default: $PYTHON_BIN or python).
|
||
|
|
--campaign NAME Output campaign name.
|
||
|
|
--dry-run Print commands without creating files or training.
|
||
|
|
-h, --help Show this help message.
|
||
|
|
|
||
|
|
Fixed experiment settings:
|
||
|
|
architecture traj_mixer_v5
|
||
|
|
target all_future
|
||
|
|
time mode relative
|
||
|
|
distribution weibull
|
||
|
|
extra information extra_info_types_none.txt
|
||
|
|
|
||
|
|
Outputs:
|
||
|
|
runs/<campaign>/seed_<seed>/traj_mixer_v5/...
|
||
|
|
batch_logs/<campaign>/seed_<seed>/<mode>.log
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
while (($# > 0)); do
|
||
|
|
case "$1" in
|
||
|
|
--gpus)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --gpus requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
GPU_CSV="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--seeds)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --seeds requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
SEED_CSV="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--modes)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --modes requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
MODE_CSV="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--batch-size)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --batch-size requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
BATCH_SIZE="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--num-workers)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --num-workers requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
NUM_WORKERS="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--python)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --python requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
PYTHON_BIN="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--campaign)
|
||
|
|
[[ $# -ge 2 ]] || {
|
||
|
|
echo "ERROR: --campaign requires a value." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
CAMPAIGN_NAME="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--dry-run)
|
||
|
|
DRY_RUN=1
|
||
|
|
shift
|
||
|
|
;;
|
||
|
|
-h|--help)
|
||
|
|
usage
|
||
|
|
exit 0
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "ERROR: unknown argument: $1" >&2
|
||
|
|
usage >&2
|
||
|
|
exit 2
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
[[ -n "$GPU_CSV" ]] || {
|
||
|
|
echo "ERROR: --gpus is required." >&2
|
||
|
|
usage >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -n "$SEED_CSV" ]] || {
|
||
|
|
echo "ERROR: --seeds must not be empty." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -n "$MODE_CSV" ]] || {
|
||
|
|
echo "ERROR: --modes must not be empty." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]] || {
|
||
|
|
echo "ERROR: --batch-size must be a positive integer." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || {
|
||
|
|
echo "ERROR: --num-workers must be a non-negative integer." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ "$CAMPAIGN_NAME" =~ ^[A-Za-z0-9._-]+$ ]] || {
|
||
|
|
echo "ERROR: --campaign may contain only letters, numbers, ., _, and -." >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -f "$EXTRA_INFO_TYPES_FILE" ]] || {
|
||
|
|
echo "ERROR: missing extra-info file: $EXTRA_INFO_TYPES_FILE" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -f "$ENTRYPOINT" ]] || {
|
||
|
|
echo "ERROR: missing training entrypoint: $ENTRYPOINT" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
command -v "$PYTHON_BIN" >/dev/null 2>&1 || {
|
||
|
|
echo "ERROR: Python executable not found: $PYTHON_BIN" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
|
||
|
|
IFS=',' read -r -a GPU_IDS <<< "$GPU_CSV"
|
||
|
|
declare -A SEEN_GPUS=()
|
||
|
|
for gpu in "${GPU_IDS[@]}"; do
|
||
|
|
[[ -n "$gpu" && "$gpu" =~ ^[A-Za-z0-9._:-]+$ ]] || {
|
||
|
|
echo "ERROR: invalid GPU id: $gpu" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -z "${SEEN_GPUS[$gpu]+x}" ]] || {
|
||
|
|
echo "ERROR: duplicate GPU id: $gpu" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
SEEN_GPUS["$gpu"]=1
|
||
|
|
done
|
||
|
|
|
||
|
|
IFS=',' read -r -a SEEDS <<< "$SEED_CSV"
|
||
|
|
declare -A SEEN_SEEDS=()
|
||
|
|
for seed in "${SEEDS[@]}"; do
|
||
|
|
[[ "$seed" =~ ^[0-9]+$ ]] || {
|
||
|
|
echo "ERROR: invalid seed: $seed" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
[[ -z "${SEEN_SEEDS[$seed]+x}" ]] || {
|
||
|
|
echo "ERROR: duplicate seed: $seed" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
SEEN_SEEDS["$seed"]=1
|
||
|
|
done
|
||
|
|
|
||
|
|
IFS=',' read -r -a MODES <<< "$MODE_CSV"
|
||
|
|
declare -A SEEN_MODES=()
|
||
|
|
for mode in "${MODES[@]}"; do
|
||
|
|
case "$mode" in
|
||
|
|
timed|ordered|set)
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "ERROR: invalid mode: $mode (expected timed, ordered, or set)." >&2
|
||
|
|
exit 2
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
[[ -z "${SEEN_MODES[$mode]+x}" ]] || {
|
||
|
|
echo "ERROR: duplicate mode: $mode" >&2
|
||
|
|
exit 2
|
||
|
|
}
|
||
|
|
SEEN_MODES["$mode"]=1
|
||
|
|
done
|
||
|
|
|
||
|
|
RUNS_ROOT="$SCRIPT_DIR/runs/$CAMPAIGN_NAME"
|
||
|
|
LOG_ROOT="$SCRIPT_DIR/batch_logs/$CAMPAIGN_NAME"
|
||
|
|
if ((!DRY_RUN)); then
|
||
|
|
mkdir -p "$RUNS_ROOT" "$LOG_ROOT"
|
||
|
|
fi
|
||
|
|
|
||
|
|
declare -a JOB_SEEDS=()
|
||
|
|
declare -a JOB_MODES=()
|
||
|
|
for seed in "${SEEDS[@]}"; do
|
||
|
|
for mode in "${MODES[@]}"; do
|
||
|
|
JOB_SEEDS+=("$seed")
|
||
|
|
JOB_MODES+=("$mode")
|
||
|
|
done
|
||
|
|
done
|
||
|
|
|
||
|
|
print_command() {
|
||
|
|
printf '%q ' "$@"
|
||
|
|
printf '\n'
|
||
|
|
}
|
||
|
|
|
||
|
|
run_job() {
|
||
|
|
local job_index="$1"
|
||
|
|
local gpu="$2"
|
||
|
|
local seed="${JOB_SEEDS[$job_index]}"
|
||
|
|
local mode="${JOB_MODES[$job_index]}"
|
||
|
|
local seed_runs_root="$RUNS_ROOT/seed_$seed"
|
||
|
|
local seed_log_root="$LOG_ROOT/seed_$seed"
|
||
|
|
local log_file="$seed_log_root/$mode.log"
|
||
|
|
local -a command=(
|
||
|
|
"$PYTHON_BIN"
|
||
|
|
-u
|
||
|
|
"$ENTRYPOINT"
|
||
|
|
--runs_root "$seed_runs_root"
|
||
|
|
--seed "$seed"
|
||
|
|
--batch_size "$BATCH_SIZE"
|
||
|
|
--num_workers "$NUM_WORKERS"
|
||
|
|
--device cuda
|
||
|
|
--model_architecture traj_mixer_v5
|
||
|
|
--time_mode relative
|
||
|
|
--dist_mode weibull
|
||
|
|
--extra_info_types_file "$EXTRA_INFO_TYPES_FILE"
|
||
|
|
--disease_history_mode "$mode"
|
||
|
|
)
|
||
|
|
|
||
|
|
if ((!DRY_RUN)); then
|
||
|
|
mkdir -p "$seed_runs_root" "$seed_log_root"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "[$(date '+%F %T')] START seed=$seed mode=$mode gpu=$gpu"
|
||
|
|
echo " log=$log_file"
|
||
|
|
if ((DRY_RUN)); then
|
||
|
|
printf ' CUDA_VISIBLE_DEVICES=%q ' "$gpu"
|
||
|
|
print_command "${command[@]}"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if CUDA_VISIBLE_DEVICES="$gpu" PYTHONUNBUFFERED=1 \
|
||
|
|
"${command[@]}" >"$log_file" 2>&1; then
|
||
|
|
echo "[$(date '+%F %T')] DONE seed=$seed mode=$mode gpu=$gpu"
|
||
|
|
return 0
|
||
|
|
else
|
||
|
|
local exit_code=$?
|
||
|
|
echo "[$(date '+%F %T')] FAIL seed=$seed mode=$mode gpu=$gpu exit=$exit_code" >&2
|
||
|
|
echo " See: $log_file" >&2
|
||
|
|
return "$exit_code"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
worker() {
|
||
|
|
local slot="$1"
|
||
|
|
local gpu="${GPU_IDS[$slot]}"
|
||
|
|
local job_index
|
||
|
|
local failed=0
|
||
|
|
|
||
|
|
for ((job_index = slot; job_index < ${#JOB_SEEDS[@]}; job_index += ${#GPU_IDS[@]})); do
|
||
|
|
run_job "$job_index" "$gpu" || failed=1
|
||
|
|
done
|
||
|
|
return "$failed"
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "Campaign: $CAMPAIGN_NAME"
|
||
|
|
echo "Seeds: ${SEEDS[*]}"
|
||
|
|
echo "Modes: ${MODES[*]}"
|
||
|
|
echo "GPUs: ${GPU_IDS[*]}"
|
||
|
|
echo "Configurations per seed: ${#MODES[@]}"
|
||
|
|
echo "Total tasks: ${#JOB_SEEDS[@]}"
|
||
|
|
echo "Runs root: $RUNS_ROOT"
|
||
|
|
echo "Log root: $LOG_ROOT"
|
||
|
|
echo
|
||
|
|
|
||
|
|
declare -a WORKER_PIDS=()
|
||
|
|
for ((slot = 0; slot < ${#GPU_IDS[@]}; slot++)); do
|
||
|
|
worker "$slot" &
|
||
|
|
WORKER_PIDS+=("$!")
|
||
|
|
done
|
||
|
|
|
||
|
|
overall_status=0
|
||
|
|
for pid in "${WORKER_PIDS[@]}"; do
|
||
|
|
wait "$pid" || overall_status=1
|
||
|
|
done
|
||
|
|
|
||
|
|
if ((overall_status != 0)); then
|
||
|
|
echo "One or more training tasks failed. Inspect logs under: $LOG_ROOT" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
if ((DRY_RUN)); then
|
||
|
|
echo "Dry run completed successfully."
|
||
|
|
else
|
||
|
|
echo "All disease-history ablation tasks completed successfully."
|
||
|
|
fi
|