Enhance training script to support all-future model target mode and update dataset handling

This commit is contained in:
2026-06-12 11:49:44 +08:00
parent 3125b6119f
commit 82f70945d9
2 changed files with 345 additions and 107 deletions

View File

@@ -221,11 +221,24 @@ all-future / query-conditioned 监督:
## 训练 ## 训练
当前 `train.py` next-step 训练入口,使用 当前 `train.py` 支持 next-token 和 all-future 两类训练入口:
```python - `--model_target_mode next_token`
HealthDataset = NextStepHealthDataset - 使用 `NextStepHealthDataset`
``` - `--target_mode delphi2m` 默认搭配 `Delphi2MLoss` + `token` readout
- `--target_mode uts` 默认搭配 `UniqueTimeSetExponentialLoss` + `same_time_group_end` readout
- `--model_target_mode all_future`
- 使用 `AllFutureHealthDataset`
- 不使用 readout直接对 query hidden 计算风险
- `--dist_mode exponential/weibull/mixed` 分别搭配 `ExponentialLoss`、`WeibullLoss`、`MixedLoss`
模型结构组合由 `model_target_mode × time_mode × dist_mode` 决定:
| 维度 | 可选项 |
| --- | --- |
| `model_target_mode` | `next_token`, `all_future` |
| `time_mode` | `relative`, `absolute` |
| `dist_mode` | `exponential`, `weibull`, `mixed` |
示例: 示例:
@@ -233,6 +246,7 @@ HealthDataset = NextStepHealthDataset
python train.py \ python train.py \
--data_prefix ukb \ --data_prefix ukb \
--labels_file labels.csv \ --labels_file labels.csv \
--model_target_mode next_token \
--target_mode uts \ --target_mode uts \
--n_embd 120 \ --n_embd 120 \
--n_head 10 \ --n_head 10 \
@@ -240,6 +254,17 @@ python train.py \
--n_tab_layer 4 --n_tab_layer 4
``` ```
all-future 示例:
```bash
python train.py \
--data_prefix ukb \
--labels_file labels.csv \
--model_target_mode all_future \
--dist_mode weibull \
--time_mode relative
```
选择额外信息变量: 选择额外信息变量:
```bash ```bash
@@ -252,6 +277,7 @@ python train.py --extra_info_types_file extra_info_types_smoking_alcohol_bmi.txt
- `extra_info_types_file`:训练时使用的列表文件名 - `extra_info_types_file`:训练时使用的列表文件名
- `extra_info_types`:解析后的实际 type id 列表,用于评估脚本复现变量选择 - `extra_info_types`:解析后的实际 type id 列表,用于评估脚本复现变量选择
- `model_target_mode`、`time_mode`、`dist_mode`、`dataset_class`、`collate_fn`、`resolved_loss_name`:用于评估脚本重建模型和输入方式
## 评估 AUC ## 评估 AUC

418
train.py
View File

@@ -30,7 +30,12 @@ from torch.optim import AdamW
from torch.nn.utils import clip_grad_norm_ from torch.nn.utils import clip_grad_norm_
from tqdm.auto import tqdm from tqdm.auto import tqdm
from dataset import HealthDataset, collate_fn from dataset import (
AllFutureHealthDataset,
HealthDataset,
all_future_collate_fn,
collate_fn,
)
from models import DeepHealth from models import DeepHealth
from readouts import build_readout from readouts import build_readout
from losses import build_loss from losses import build_loss
@@ -225,6 +230,53 @@ def split_dataset(
) )
def split_all_future_datasets(
train_dataset: AllFutureHealthDataset,
val_dataset: AllFutureHealthDataset,
test_dataset: AllFutureHealthDataset,
train_ratio: float,
val_ratio: float,
test_ratio: float,
seed: int,
) -> Tuple[Subset, Subset, Subset]:
"""Split all-future datasets by patient, then select validation/test queries."""
total = train_ratio + val_ratio + test_ratio
if not np.isclose(total, 1.0, atol=1e-6):
raise ValueError(
f"train_ratio + val_ratio + test_ratio must equal 1.0, got {total}"
)
n_patients = len(train_dataset.patients)
rng = np.random.RandomState(seed)
patient_indices = rng.permutation(n_patients)
n_train = int(n_patients * train_ratio)
n_val = int(n_patients * val_ratio)
train_patient_idx = patient_indices[:n_train]
val_patient_set = set(int(x) for x in patient_indices[n_train:n_train + n_val])
test_patient_set = set(int(x) for x in patient_indices[n_train + n_val:])
val_query_idx = [
i for i, (pidx, _t_query) in enumerate(val_dataset.valid_queries)
if int(pidx) in val_patient_set
]
test_query_idx = [
i for i, (pidx, _t_query) in enumerate(test_dataset.valid_queries)
if int(pidx) in test_patient_set
]
if not val_query_idx:
raise ValueError("All-future validation split has no valid query samples.")
if not test_query_idx:
raise ValueError("All-future test split has no valid query samples.")
return (
Subset(train_dataset, train_patient_idx),
Subset(val_dataset, np.asarray(val_query_idx, dtype=np.int64)),
Subset(test_dataset, np.asarray(test_query_idx, dtype=np.int64)),
)
def build_model(args: argparse.Namespace, dataset: HealthDataset) -> DeepHealth: def build_model(args: argparse.Namespace, dataset: HealthDataset) -> DeepHealth:
""" """
Build DeepHealth model using metadata from dataset. Build DeepHealth model using metadata from dataset.
@@ -242,9 +294,9 @@ def build_model(args: argparse.Namespace, dataset: HealthDataset) -> DeepHealth:
n_categories=dataset.n_categories, n_categories=dataset.n_categories,
cont_type_ids=dataset.cont_type_ids, cont_type_ids=dataset.cont_type_ids,
n_bins=args.n_bins, n_bins=args.n_bins,
target_mode="next_token", target_mode=args.model_target_mode,
time_mode=args.time_mode, time_mode=args.time_mode,
dist_mode="exponential", dist_mode=args.dist_mode,
dropout=args.dropout, dropout=args.dropout,
) )
@@ -302,7 +354,7 @@ def move_batch_to_device(batch: Dict, device: torch.device) -> Dict:
def compute_loss( def compute_loss(
args: argparse.Namespace, args: argparse.Namespace,
model: DeepHealth, model: DeepHealth,
readout: nn.Module, readout: nn.Module | None,
criterion: nn.Module, criterion: nn.Module,
batch: Dict[str, torch.Tensor], batch: Dict[str, torch.Tensor],
device: torch.device, device: torch.device,
@@ -340,56 +392,100 @@ def compute_loss(
padding_mask = batch["padding_mask"] # (B, L) padding_mask = batch["padding_mask"] # (B, L)
sex = batch["sex"] # (B,) sex = batch["sex"] # (B,)
hidden = model( if args.model_target_mode == "all_future":
event_seq=event_seq, hidden = model(
time_seq=time_seq, event_seq=event_seq,
sex=sex, time_seq=time_seq,
padding_mask=padding_mask, sex=sex,
other_type=batch["other_type"], padding_mask=padding_mask,
other_value=batch["other_value"], t_query=batch["t_query"],
other_value_kind=batch["other_value_kind"], other_type=batch["other_type"],
other_time=batch["other_time"], other_value=batch["other_value"],
target_mode="next_token", other_value_kind=batch["other_value_kind"],
) other_time=batch["other_time"],
target_mode="all_future",
# Apply readout
readout_mask = (
batch["readout_mask"]
if args.readout_name == "same_time_group_end"
else None
)
readout_out = readout(
hidden=hidden,
time_seq=time_seq,
padding_mask=padding_mask,
readout_mask=readout_mask,
)
# Compute risk logits
logits = model.calc_risk(readout_out.hidden)
# Compute loss based on target_mode
if args.target_mode == "delphi2m":
loss_out = criterion(
logits=logits,
target_events=batch["target_event_seq"],
target_times=batch["target_time_seq"],
current_times=batch["time_seq"],
padding_mask=readout_out.readout_mask,
return_components=True,
)
elif args.target_mode == "uts":
loss_out = criterion(
logits=logits,
target_multi_hot=batch["target_multi_hot"],
target_dt_unique=batch["target_dt_unique"],
readout_mask=readout_out.readout_mask,
return_components=True,
) )
logits = model.calc_risk(hidden)
if args.dist_mode == "exponential":
loss = criterion(
logits=logits,
targets=batch["future_targets"],
exposure=batch["exposure"],
)
elif args.dist_mode == "weibull":
loss = criterion(
logits=logits,
weibull_rho=model.calc_weibull_rho(hidden),
targets=batch["future_targets"],
dt=batch["future_dt"],
exposure=batch["exposure"],
)
elif args.dist_mode == "mixed":
loss = criterion(
logits=logits,
death_rho=model.calc_death_rho(hidden),
targets=batch["future_targets"],
dt=batch["future_dt"],
exposure=batch["exposure"],
)
else:
raise ValueError(f"Unknown dist_mode: {args.dist_mode}")
loss_parts = {"total": loss.detach()}
else: else:
raise ValueError(f"Unknown target_mode: {args.target_mode}") if readout is None:
raise ValueError("next_token training requires a readout module")
loss, loss_parts = loss_out hidden = model(
event_seq=event_seq,
time_seq=time_seq,
sex=sex,
padding_mask=padding_mask,
other_type=batch["other_type"],
other_value=batch["other_value"],
other_value_kind=batch["other_value_kind"],
other_time=batch["other_time"],
target_mode="next_token",
)
# Apply readout
readout_mask = (
batch["readout_mask"]
if args.readout_name == "same_time_group_end"
else None
)
readout_out = readout(
hidden=hidden,
time_seq=time_seq,
padding_mask=padding_mask,
readout_mask=readout_mask,
)
# Compute risk logits
logits = model.calc_risk(readout_out.hidden)
# Compute loss based on target_mode
if args.target_mode == "delphi2m":
loss_out = criterion(
logits=logits,
target_events=batch["target_event_seq"],
target_times=batch["target_time_seq"],
current_times=batch["time_seq"],
padding_mask=readout_out.readout_mask,
return_components=True,
)
elif args.target_mode == "uts":
loss_out = criterion(
logits=logits,
target_multi_hot=batch["target_multi_hot"],
target_dt_unique=batch["target_dt_unique"],
readout_mask=readout_out.readout_mask,
return_components=True,
)
else:
raise ValueError(f"Unknown target_mode: {args.target_mode}")
loss, loss_parts = loss_out
# Check for NaN/Inf # Check for NaN/Inf
if not torch.isfinite(loss): if not torch.isfinite(loss):
@@ -406,7 +502,7 @@ def run_one_epoch(
logger: logging.Logger, logger: logging.Logger,
args: argparse.Namespace, args: argparse.Namespace,
model: DeepHealth, model: DeepHealth,
readout: nn.Module, readout: nn.Module | None,
criterion: nn.Module, criterion: nn.Module,
train_loader: DataLoader, train_loader: DataLoader,
optimizer: AdamW, optimizer: AdamW,
@@ -421,7 +517,7 @@ def run_one_epoch(
logger : logging.Logger logger : logging.Logger
args : argparse.Namespace args : argparse.Namespace
model : DeepHealth model : DeepHealth
readout : nn.Module readout : nn.Module or None
criterion : nn.Module criterion : nn.Module
train_loader : DataLoader train_loader : DataLoader
optimizer : AdamW optimizer : AdamW
@@ -522,7 +618,7 @@ def evaluate(
logger: logging.Logger, logger: logging.Logger,
args: argparse.Namespace, args: argparse.Namespace,
model: DeepHealth, model: DeepHealth,
readout: nn.Module, readout: nn.Module | None,
criterion: nn.Module, criterion: nn.Module,
val_loader: DataLoader, val_loader: DataLoader,
device: torch.device, device: torch.device,
@@ -588,13 +684,25 @@ def build_run_metadata(
run_name: str, run_name: str,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Collect resolved training facts needed to rebuild the model for evaluation.""" """Collect resolved training facts needed to rebuild the model for evaluation."""
dataset_class = (
"AllFutureHealthDataset"
if args.model_target_mode == "all_future"
else "NextStepHealthDataset"
)
collate_name = (
"all_future_collate_fn"
if args.model_target_mode == "all_future"
else "next_step_collate_fn"
)
return { return {
"run_name": run_name, "run_name": run_name,
"dataset_class": "NextStepHealthDataset", "dataset_class": dataset_class,
"collate_fn": "next_step_collate_fn", "collate_fn": collate_name,
"model_class": "DeepHealth", "model_class": "DeepHealth",
"model_target_mode": "next_token", "model_target_mode": args.model_target_mode,
"dist_mode": "exponential", "dist_mode": args.dist_mode,
"all_future_min_history_events": int(args.all_future_min_history_events),
"all_future_min_future_events": int(args.all_future_min_future_events),
"extra_info_types_file": ( "extra_info_types_file": (
Path(args.extra_info_types_file).name Path(args.extra_info_types_file).name
if args.extra_info_types_file is not None if args.extra_info_types_file is not None
@@ -621,18 +729,46 @@ def build_run_metadata(
def normalize_training_config(args: argparse.Namespace) -> None: def normalize_training_config(args: argparse.Namespace) -> None:
"""Fill in and validate training options that depend on other flags.""" """Fill in and validate training options that depend on other flags."""
if args.target_mode not in {"delphi2m", "uts"}: if args.model_target_mode not in {"next_token", "all_future"}:
raise ValueError(f"Unknown target_mode: {args.target_mode}") raise ValueError(f"Unknown model_target_mode: {args.model_target_mode}")
if args.dist_mode not in {"exponential", "weibull", "mixed"}:
raise ValueError(f"Unknown dist_mode: {args.dist_mode}")
if args.all_future_min_history_events < 1:
raise ValueError("all_future_min_history_events must be >= 1")
if args.all_future_min_future_events < 1:
raise ValueError("all_future_min_future_events must be >= 1")
if args.model_target_mode == "all_future":
args.target_mode = "all_future"
# gap_5y is always enabled, so preserve NO_EVENT target behavior. # gap_5y is always enabled, so preserve NO_EVENT target behavior.
args.ignore_no_event_in_delphi2m = False args.ignore_no_event_in_delphi2m = False
if args.target_mode == "uts": if args.model_target_mode == "next_token" and args.target_mode == "uts":
args.include_no_event_in_uts_target = True args.include_no_event_in_uts_target = True
def normalize_loss_and_distribution_config(args: argparse.Namespace) -> None: def normalize_loss_and_distribution_config(args: argparse.Namespace) -> None:
"""Validate and resolve loss/distribution options after auto-selection.""" """Validate and resolve loss/distribution options after auto-selection."""
if args.loss_name not in {"delphi2m", "uts"}: next_token_losses = {"delphi2m", "uts"}
all_future_losses = {"exponential", "weibull", "mixed"}
if args.model_target_mode == "all_future":
if args.loss_name not in all_future_losses:
raise ValueError(
"all_future training requires loss_name to be one of "
"exponential, weibull, or mixed."
)
if args.loss_name != args.dist_mode:
raise ValueError(
"all_future loss_name must match dist_mode so risk scoring and "
f"training distribution stay aligned. Got loss_name={args.loss_name!r}, "
f"dist_mode={args.dist_mode!r}."
)
return
if args.target_mode not in {"delphi2m", "uts"}:
raise ValueError(f"Unknown target_mode: {args.target_mode}")
if args.loss_name not in next_token_losses:
raise ValueError( raise ValueError(
"Unknown loss_name. Supported values: delphi2m, uts." "Unknown loss_name. Supported values: delphi2m, uts."
) )
@@ -692,6 +828,12 @@ def main():
parser.add_argument("--time_mode", type=str, default="relative", parser.add_argument("--time_mode", type=str, default="relative",
choices=["relative", "absolute"], choices=["relative", "absolute"],
help="Time encoding mode for disease history") help="Time encoding mode for disease history")
parser.add_argument("--model_target_mode", type=str, default="next_token",
choices=["next_token", "all_future"],
help="Model forward/training mode")
parser.add_argument("--dist_mode", type=str, default="exponential",
choices=["exponential", "weibull", "mixed"],
help="Event-time distribution for model heads and all-future loss")
parser.add_argument("--dropout", type=float, default=0.0, parser.add_argument("--dropout", type=float, default=0.0,
help="Dropout rate") help="Dropout rate")
parser.add_argument("--extra_info_types_file", type=str, default=None, parser.add_argument("--extra_info_types_file", type=str, default=None,
@@ -700,16 +842,20 @@ def main():
# ---- Training Protocol ---- # ---- Training Protocol ----
parser.add_argument("--target_mode", type=str, default="uts", parser.add_argument("--target_mode", type=str, default="uts",
choices=["delphi2m", "uts"], choices=["delphi2m", "uts"],
help="Target supervision mode") help="Next-token supervision mode; ignored for all_future model_target_mode")
parser.add_argument("--readout_name", type=str, default=None, parser.add_argument("--readout_name", type=str, default=None,
help="Readout name (auto-selected if None)") help="Readout name (auto-selected if None)")
parser.add_argument("--readout_reduce", type=str, default="mean", parser.add_argument("--readout_reduce", type=str, default="mean",
choices=["mean", "sum"], choices=["mean", "sum"],
help="Readout reduction for SameTimeGroupEndReadout") help="Readout reduction for SameTimeGroupEndReadout")
parser.add_argument("--all_future_min_history_events", type=int, default=1,
help="Minimum historical events before an all-future query")
parser.add_argument("--all_future_min_future_events", type=int, default=1,
help="Minimum future events after an all-future query")
# ---- Loss ---- # ---- Loss ----
parser.add_argument("--loss_name", type=str, default=None, parser.add_argument("--loss_name", type=str, default=None,
help="Loss name (auto-selected if None): delphi2m, uts") help="Loss name (auto-selected if None): delphi2m, uts, exponential, weibull, mixed")
parser.add_argument("--t_min", type=float, default=0.0027378507871321013, parser.add_argument("--t_min", type=float, default=0.0027378507871321013,
help="Minimum time for loss (1/365.25)") help="Minimum time for loss (1/365.25)")
parser.add_argument("--max_exp_input", type=float, default=60.0, parser.add_argument("--max_exp_input", type=float, default=60.0,
@@ -758,11 +904,32 @@ def main():
configure_torch_for_training(device) configure_torch_for_training(device)
normalize_training_config(args) normalize_training_config(args)
# Auto-select readout if not specified.
if args.model_target_mode == "all_future":
args.readout_name = "none"
elif args.readout_name is None:
args.readout_name = (
"token" if args.target_mode == "delphi2m"
else "same_time_group_end"
)
# Auto-select loss if not specified.
if args.loss_name is None:
if args.model_target_mode == "all_future":
args.loss_name = args.dist_mode
else:
args.loss_name = (
"delphi2m" if args.target_mode == "delphi2m"
else "uts"
)
normalize_loss_and_distribution_config(args)
runs_root = Path("runs") runs_root = Path("runs")
while True: while True:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
run_name = ( run_name = (
f"{args.time_mode}_exponential_{args.target_mode}_" f"{args.time_mode}_{args.dist_mode}_{args.model_target_mode}_{args.loss_name}_"
f"other_tokens_gap_5y_{timestamp}" f"other_tokens_gap_5y_{timestamp}"
) )
run_dir = runs_root / run_name run_dir = runs_root / run_name
@@ -788,45 +955,72 @@ def main():
f"train_ratio + val_ratio + test_ratio must equal 1.0, got {total_ratio}" f"train_ratio + val_ratio + test_ratio must equal 1.0, got {total_ratio}"
) )
# Auto-select readout if not specified
if args.readout_name is None:
args.readout_name = (
"token" if args.target_mode == "delphi2m"
else "same_time_group_end"
)
# Auto-select loss if not specified
if args.loss_name is None:
args.loss_name = (
"delphi2m" if args.target_mode == "delphi2m"
else "uts"
)
normalize_loss_and_distribution_config(args)
logger.info(f"Auto-selected readout: {args.readout_name}") logger.info(f"Auto-selected readout: {args.readout_name}")
logger.info(f"Auto-selected loss: {args.loss_name}") logger.info(f"Auto-selected loss: {args.loss_name}")
# ---- Load Dataset ---- # ---- Load Dataset ----
logger.info("Loading dataset...") logger.info("Loading dataset...")
dataset = HealthDataset( if args.model_target_mode == "all_future":
data_prefix=args.data_prefix, dataset = AllFutureHealthDataset(
labels_file=args.labels_file, data_prefix=args.data_prefix,
no_event_interval_years=args.no_event_interval_years, labels_file=args.labels_file,
include_no_event_in_uts_target=args.include_no_event_in_uts_target, split="train",
extra_info_types=args.extra_info_types, no_event_interval_years=args.no_event_interval_years,
) include_no_event_in_uts_target=args.include_no_event_in_uts_target,
logger.info( min_history_events=args.all_future_min_history_events,
f"Dataset loaded: {len(dataset)} samples, vocab_size={dataset.vocab_size}") min_future_events=args.all_future_min_future_events,
extra_info_types=args.extra_info_types,
)
val_dataset = AllFutureHealthDataset(
data_prefix=args.data_prefix,
labels_file=args.labels_file,
split="valid",
no_event_interval_years=args.no_event_interval_years,
include_no_event_in_uts_target=args.include_no_event_in_uts_target,
min_history_events=args.all_future_min_history_events,
min_future_events=args.all_future_min_future_events,
extra_info_types=args.extra_info_types,
)
test_dataset = AllFutureHealthDataset(
data_prefix=args.data_prefix,
labels_file=args.labels_file,
split="test",
no_event_interval_years=args.no_event_interval_years,
include_no_event_in_uts_target=args.include_no_event_in_uts_target,
min_history_events=args.all_future_min_history_events,
min_future_events=args.all_future_min_future_events,
extra_info_types=args.extra_info_types,
)
train_subset, val_subset, test_subset = split_all_future_datasets(
train_dataset=dataset,
val_dataset=val_dataset,
test_dataset=test_dataset,
train_ratio=args.train_ratio,
val_ratio=args.val_ratio,
test_ratio=args.test_ratio,
seed=args.seed,
)
active_collate_fn = all_future_collate_fn
else:
dataset = HealthDataset(
data_prefix=args.data_prefix,
labels_file=args.labels_file,
no_event_interval_years=args.no_event_interval_years,
include_no_event_in_uts_target=args.include_no_event_in_uts_target,
extra_info_types=args.extra_info_types,
)
train_subset, val_subset, test_subset = split_dataset(
dataset=dataset,
train_ratio=args.train_ratio,
val_ratio=args.val_ratio,
test_ratio=args.test_ratio,
seed=args.seed,
)
active_collate_fn = collate_fn
logger.info(
f"Dataset loaded: {len(dataset)} base samples, vocab_size={dataset.vocab_size}")
# ---- Split Dataset ----
train_subset, val_subset, test_subset = split_dataset(
dataset=dataset,
train_ratio=args.train_ratio,
val_ratio=args.val_ratio,
test_ratio=args.test_ratio,
seed=args.seed,
)
logger.info( logger.info(
f"Dataset split: train={len(train_subset)}, val={len(val_subset)}, test={len(test_subset)}" f"Dataset split: train={len(train_subset)}, val={len(val_subset)}, test={len(test_subset)}"
) )
@@ -837,7 +1031,7 @@ def main():
batch_size=args.batch_size, batch_size=args.batch_size,
sampler=RandomSampler( sampler=RandomSampler(
train_subset, generator=torch.Generator().manual_seed(args.seed)), train_subset, generator=torch.Generator().manual_seed(args.seed)),
collate_fn=collate_fn, collate_fn=active_collate_fn,
num_workers=args.num_workers, num_workers=args.num_workers,
pin_memory=device.type == "cuda", pin_memory=device.type == "cuda",
persistent_workers=args.num_workers > 0, persistent_workers=args.num_workers > 0,
@@ -847,7 +1041,7 @@ def main():
val_subset, val_subset,
batch_size=args.batch_size, batch_size=args.batch_size,
shuffle=False, shuffle=False,
collate_fn=collate_fn, collate_fn=active_collate_fn,
num_workers=args.num_workers, num_workers=args.num_workers,
pin_memory=device.type == "cuda", pin_memory=device.type == "cuda",
persistent_workers=args.num_workers > 0, persistent_workers=args.num_workers > 0,
@@ -857,7 +1051,7 @@ def main():
test_subset, test_subset,
batch_size=args.batch_size, batch_size=args.batch_size,
shuffle=False, shuffle=False,
collate_fn=collate_fn, collate_fn=active_collate_fn,
num_workers=args.num_workers, num_workers=args.num_workers,
pin_memory=device.type == "cuda", pin_memory=device.type == "cuda",
persistent_workers=args.num_workers > 0, persistent_workers=args.num_workers > 0,
@@ -882,7 +1076,9 @@ def main():
f"Adaptive LR: {adaptive_lr:.6f} (base_lr * sqrt(batch_size/128))") f"Adaptive LR: {adaptive_lr:.6f} (base_lr * sqrt(batch_size/128))")
# ---- Build Readout ---- # ---- Build Readout ----
if args.readout_name == "token": if args.model_target_mode == "all_future":
readout = None
elif args.readout_name == "token":
readout = build_readout("token") readout = build_readout("token")
elif args.readout_name == "same_time_group_end": elif args.readout_name == "same_time_group_end":
readout = build_readout("same_time_group_end", readout = build_readout("same_time_group_end",
@@ -894,7 +1090,23 @@ def main():
logger.info(f"Readout: {args.readout_name}") logger.info(f"Readout: {args.readout_name}")
# ---- Build Loss ---- # ---- Build Loss ----
if args.loss_name == "delphi2m": if args.model_target_mode == "all_future":
ignored_idx = {PAD_IDX, CHECKUP_IDX}
if args.loss_name == "exponential":
criterion = build_loss("exponential", ignored_idx=ignored_idx)
elif args.loss_name == "weibull":
criterion = build_loss("weibull", ignored_idx=ignored_idx)
elif args.loss_name == "mixed":
criterion = build_loss(
"mixed",
death_idx=dataset.vocab_size - 1,
ignored_idx=ignored_idx,
)
else:
raise ValueError(f"Unknown all_future loss: {args.loss_name}")
logger.info(
f"Loss: {args.loss_name}, dist_mode={args.dist_mode}, ignored_idx={ignored_idx}")
elif args.loss_name == "delphi2m":
ignored_tokens = {PAD_IDX, CHECKUP_IDX} ignored_tokens = {PAD_IDX, CHECKUP_IDX}
if args.ignore_no_event_in_delphi2m: if args.ignore_no_event_in_delphi2m:
ignored_tokens.add(NO_EVENT_IDX) ignored_tokens.add(NO_EVENT_IDX)