Augmentation and experimental privacy calls
This page extends the composable Python workflow.
Run these calls with the package's Python environment. The real datasets
referenced below are bundled with the package and load through
fs.load_real_data, so no repository-relative paths are needed.
With PyCharm, set MPLBACKEND=Agg in the Console or Run configuration before
starting it if the macOS interactive Matplotlib backend fails. Set
show=False to save figures without opening a window.
Compare the two augmentation methods visually
MVAEC trains a generator at each client and adds its generated observations
to that client. MVAES pools generated observations and redistributes them;
therefore, it is not a no-sharing workflow. Both methods leave the held-out
test set unchanged. The returned dictionaries map each client ID to (X, y)
arrays containing the original observations first, followed by synthetic
observations. The internal MVAE trainer currently runs 500 epochs per client.
import matplotlib.pyplot as plt
import federated_survival as fs
data = fs.load_real_data("gbsg") # bundled GBSG table, ships with the package
split = fs.partition_data(
data, n_clients=3, method="censoring-non-iid", test_size=0.2,
seed=0, standardize=True, missing_values="error",
)
local = fs.augment_clients(
split, method="MVAEC", k=0.3,
sampling="sparse", sparse_gamma=0.1, seed=42,
)
pooled = fs.augment_clients(
split, method="MVAES", k=0.3,
sampling="sparse", sparse_gamma=0.1, seed=42,
)
print(fs.summarize_augmentation(split, local).to_string(index=False))
figure = fs.plot_augmentation_comparison(
split, {"MVAEC": local, "MVAES": pooled},
chart="strip", # x: Client 1/2/3; y: time
output_path="results/gbsg_augmentation_comparison.png",
show=False,
)
plt.close(figure)
This saves one two-panel client-wise scatter plot. Each panel has Client 1–3
on the x-axis and time on the y-axis; colour/marker distinguish original
uncensored, original censored, and synthetic uncensored observations. For a
feature–time scatter instead, call the same function with
chart="scatter", x="feature_2", y="time" and a different output path.
feature_2 is age in this GBSG file, standardized using training data.
Here k=0.3
requests int(0.3 * number_of_uncensored_client_observations) synthetic rows
per client, not 30% of all client observations. The returned local and
pooled arrays stay in memory; no CSV is written automatically.
With the default sampling="sparse", the generator follows the original
FSA-MVAE targeting rule separately for every client:
- retain uncensored observations only;
- split their observed times at the median;
- compare the time spans of the earlier and later halves;
- treat the half with the wider span as the sparse region (a tie selects the later half);
- perturb latent codes belonging to that half by Gaussian noise with standard
deviation
sparse_gamma, then decode exactly the number requested byk.
This is a targeting strategy, not a range constraint: a decoded synthetic
time can still fall outside the selected half. The current plot must therefore
be inspected to determine where generated samples actually landed. The
summarize_augmentation table reports synthetic_in_target_fraction for that
check. Set
sampling="unconditional" to sample latent vectors from the standard normal
prior instead. Increasing sparse_gamma explores farther away from the source
latent codes, but does not guarantee later times and can reduce realism.
The scatter plot is a quality diagnostic, not proof of realistic or valid augmentation. In the current GBSG check, unconditional samples cluster much earlier than the original observations, while sparse-targeted samples mostly enter the selected later half. That improvement does not by itself establish that the joint feature/time distribution is realistic. Inspect survival-time and feature distributions, impossible categorical values, and downstream sensitivity before interpreting augmented experiments.
To train a single federated model with internal augmentation instead of just inspecting the generated arrays:
fit = fs.FederatedSurvival(
model="DeepSurv", protocol="FedAvg", n_clients=3,
global_rounds=2, local_steps=1, random_state=0, k=0.3,
augmentation_sampling="sparse", augmentation_sparse_gamma=0.1,
).fit(split, augmentation="MVAEC") # or "MVAES"
print(fit.get_run_summary())
fit(..., augmentation=...) generates a new augmented dataset internally; it
does not reuse the arrays produced by the plotting calls. The paired
compare_methods API deliberately runs without internal augmentation or
privacy perturbation and should not be described as producing augmented
Center/Federated/Local comparisons.
Tuned augmentation configurations
Whether augmentation helps is decided by the MVAE generator settings, not only
by k and sparse_gamma. The default latent_num=10 is larger than the
feature count of the bundled examples and produces noisy synthetic rows;
shrinking the latent space is the single most impactful change. The
configurations below were validated with five partition seeds (0–4) on the
same dataset, with DeepSurv/FedAvg and identical global_rounds × local_steps
budgets for the plain and the augmented runs.
| Setting | GBSG censoring-non-IID, 3 clients | GBSG Dirichlet (alpha=0.5), 5 clients | Weibull simulation, time-non-IID, 3 clients |
|---|---|---|---|
global_rounds / local_steps |
30 / 1 | 30 / 5 | 30 / 1 |
learning_rate |
0.003 | 0.001 | 0.003 |
augmentation_sampling |
sparse | sparse | unconditional |
k |
0.2 | 0.5 | 0.3 |
latent_num |
5 | 10 | 5 |
beta |
1 | 5 | 5 |
| Plain federated C-index (mean ± SD) | 0.609 ± 0.030 | 0.628 ± 0.030 | 0.580 ± 0.028 |
| Augmented federated C-index (mean ± SD) | 0.637 ± 0.014 | 0.645 ± 0.025 | 0.600 ± 0.024 |
| Centralized (Center) C-index | 0.634 | 0.645 | 0.581 |
| Weighted Local C-index | 0.591 | 0.591 | 0.541 |
Across these five-seed runs the augmented federated model beats the plain federated model in 4/5, 5/5, and 4/5 seeds respectively, matches or exceeds the centralized model on average, and both federated views clearly beat per-client local training. Example call for the GBSG censoring-non-IID setting:
fit = fs.FederatedSurvival(
model="DeepSurv", protocol="FedAvg", n_clients=3,
global_rounds=30, local_steps=1, batch_size=32, learning_rate=0.003,
random_state=42, k=0.2, latent_num=5, beta=1,
augmentation_sampling="sparse", augmentation_sparse_gamma=0.1,
).fit(split, augmentation="MVAEC")
print(fit.get_run_summary()["final_metrics"])
Tuning guidance from the same sweep: reduce latent_num towards the feature
count, raise beta when synthetic event times look unrealistic, prefer
unconditional sampling when clients cover narrow time ranges
(time-non-IID), and keep sparse_gamma small (0.1 and 0.3 performed nearly
identically). These are empirical results for the bundled datasets, not
universal recommendations; re-validate on your own data before claiming an
augmentation benefit.
Configure experimental update perturbation
The high-level estimator passes FSAConfig privacy fields as keyword
arguments. For a Gaussian-noise demonstration with FedProx:
fit = fs.FederatedSurvival(
model="DeepSurv", protocol="FedProx",
protocol_params={"mu": 0.01},
n_clients=3, global_rounds=2, local_steps=1, random_state=0,
use_differential_privacy=True,
dp_mechanism="gaussian",
dp_epsilon=1.0,
dp_delta=1e-5,
dp_sensitivity=1.0,
dp_noise_multiplier=0.05,
dp_clip_norm=1.0,
).fit(split)
print(fit.get_run_summary()["final_metrics"])
print(fit.get_privacy_info())
This example reports test/train C-index and IBS and the configured
perturbation settings. get_privacy_info() deliberately says
formal_accounting_available: False; it does not report an achieved privacy
budget. The numeric values above are smoke-test settings, not privacy or
utility recommendations.
| Field | Current role |
|---|---|
use_differential_privacy |
switches on experimental perturbation of each complete client model update |
dp_mechanism="gaussian" |
noise with scale driven by dp_noise_multiplier and sensitivity |
dp_noise_multiplier |
Gaussian noise multiplier; increasing it increases noise |
dp_clip_norm |
clips a complete client update, not each person's gradient |
dp_sensitivity |
scale input for the perturbation mechanism |
dp_epsilon, dp_delta |
legacy/configuration fields; not an achieved multi-round guarantee for Gaussian training |
dp_mechanism="laplace" |
alternative update perturbation; dp_epsilon influences its noise scale, but no end-to-end accountant exists |
The utility's exponential mechanism selects discrete candidates. It cannot
be used as elementwise noise for continuous model updates in this training
path. The current package does not provide per-example gradient clipping,
privacy amplification, multi-round privacy accounting, or secure aggregation.
Do not claim record-level (epsilon, delta)-DP or use these settings alone as
protection for sensitive clinical data. See Privacy scope.
Neither the paired compare_methods(...) call nor the schema-v1 YAML
experiment interface currently exposes augmentation or privacy settings as
part of its audited Center/Federated/Local workflow. Use the high-level
estimator above for exploratory runs and keep these results separate from
paired baseline figures.