| |
| """Exact finite-prior Bayes-risk certificate for the participation lower bound. |
| |
| The construction is deliberately small and discrete. For each of E independent |
| local coordinates, a hidden client-level signal theta is either +kappa or |
| -kappa. A participating client returns theta plus an independent |
| Rademacher heterogeneity term, also of magnitude kappa. The learner sees S |
| clients and estimates theta. A uniform prior over theta and exhaustive |
| enumeration of all N-client noise assignments and all S-client subsets gives |
| the Bayes risk. Bayes risk is a lower bound for every estimator under this |
| finite prior. |
| |
| All probabilities and risks are computed with Fraction; no floating-point |
| calculation is used for the certificate. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import itertools |
| import json |
| from collections import defaultdict |
| from fractions import Fraction |
| from math import comb |
| from pathlib import Path |
|
|
|
|
| def exact_unit_risk(n_clients: int, sampled: int) -> dict[str, object]: |
| """Enumerate the two worlds, all noise assignments, and all sample sets. |
| |
| Values are normalized by kappa, so the returned risk is multiplied by |
| kappa**2 for an arbitrary positive kappa. The posterior-mean estimator is |
| Bayes optimal for squared loss. For each observation o with joint masses |
| p_plus and p_minus, its contribution is |
| 4 * p_plus * p_minus / (p_plus + p_minus), |
| which is the exact posterior Bayes risk contribution for theta in {-1,+1}. |
| """ |
| if not (1 <= sampled <= n_clients): |
| raise ValueError("sampled must be in [1, n_clients]") |
|
|
| subsets = tuple(itertools.combinations(range(n_clients), sampled)) |
| subset_count = len(subsets) |
| |
| masses: dict[tuple[int, ...], list[Fraction]] = defaultdict( |
| lambda: [Fraction(0), Fraction(0)] |
| ) |
| per_world = Fraction(1, 2 * (1 << n_clients) * subset_count) |
| for theta_index, theta in enumerate((1, -1)): |
| for noise_mask in range(1 << n_clients): |
| noise = tuple(1 if (noise_mask >> i) & 1 else -1 for i in range(n_clients)) |
| values = tuple(theta + z for z in noise) |
| for subset in subsets: |
| observation = tuple(values[i] for i in subset) |
| masses[observation][theta_index] += per_world |
|
|
| risk = Fraction(0) |
| ambiguous_mass = Fraction(0) |
| for p_plus, p_minus in masses.values(): |
| total = p_plus + p_minus |
| if p_plus and p_minus: |
| ambiguous_mass += total |
| risk += Fraction(4) * p_plus * p_minus / total |
|
|
| expected_formula = Fraction(1, 1 << sampled) |
| if risk != expected_formula: |
| raise AssertionError((n_clients, sampled, risk, expected_formula)) |
|
|
| return { |
| "n_clients": n_clients, |
| "sampled": sampled, |
| "enumerated_noise_assignments_per_world": 1 << n_clients, |
| "enumerated_subsets": subset_count, |
| "joint_world_subset_cases": 2 * (1 << n_clients) * subset_count, |
| "distinct_observations": len(masses), |
| "ambiguous_observation_mass": str(ambiguous_mass), |
| "risk_over_kappa_squared": str(risk), |
| "risk_over_kappa_squared_decimal": float(risk), |
| "closed_form_cross_check": str(expected_formula), |
| } |
|
|
|
|
| def main() -> None: |
| |
| |
| population_sizes = (5, 8, 10) |
| e_values = (1, 2, 4, 8) |
| kappa_values = (Fraction(1, 4), Fraction(1, 2), Fraction(1), Fraction(2)) |
|
|
| base_rows: list[dict[str, object]] = [] |
| for n_clients in population_sizes: |
| for sampled in range(1, n_clients + 1): |
| base_rows.append(exact_unit_risk(n_clients, sampled)) |
|
|
| |
| |
| |
| cells: list[dict[str, object]] = [] |
| min_ratio: Fraction | None = None |
| for base in base_rows: |
| n_clients = int(base["n_clients"]) |
| sampled = int(base["sampled"]) |
| unit_risk = Fraction(str(base["risk_over_kappa_squared"])) |
| for e_local in e_values: |
| for kappa in kappa_values: |
| risk = e_local * kappa * kappa * unit_risk |
| target_scale = Fraction(e_local) * kappa * kappa / sampled |
| ratio = risk / target_scale |
| min_ratio = ratio if min_ratio is None else min(min_ratio, ratio) |
| cells.append( |
| { |
| "N": n_clients, |
| "S": sampled, |
| "E": e_local, |
| "kappa": str(kappa), |
| "bayes_risk": str(risk), |
| "target_E_kappa2_over_S": str(target_scale), |
| "ratio_to_target": str(ratio), |
| } |
| ) |
|
|
| assert min_ratio is not None |
| certificate_constant = Fraction(5, 512) |
| if min_ratio < certificate_constant: |
| raise AssertionError((min_ratio, certificate_constant)) |
|
|
| result = { |
| "construction": "two-world Rademacher heterogeneity, exact finite-prior Bayes risk", |
| "population_sizes": list(population_sizes), |
| "sample_sizes_per_population": { |
| str(n): list(range(1, n + 1)) for n in population_sizes |
| }, |
| "E_values": list(e_values), |
| "kappa_values": [str(k) for k in kappa_values], |
| "base_rows": base_rows, |
| "executed_parameter_cells": len(cells), |
| "cells": cells, |
| "min_ratio_risk_over_E_kappa2_over_S": str(min_ratio), |
| "finite_family_certificate": f"risk >= ({certificate_constant}) * E*kappa^2/S", |
| "bayes_optimality": ( |
| "For squared loss, posterior mean minimizes conditional risk; " |
| "therefore every estimator has expected risk at least this Bayes risk." |
| ), |
| } |
| out = Path(__file__).with_name("bayes_lower_bound_results.json") |
| out.write_text(json.dumps(result, indent=2) + "\n") |
| print(json.dumps({ |
| "output": str(out), |
| "base_rows": len(base_rows), |
| "executed_parameter_cells": len(cells), |
| "min_ratio": str(min_ratio), |
| "certificate": result["finite_family_certificate"], |
| }, sort_keys=True)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|