项目文件夹

文件
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

114 行
3.6 KiB
Python

"""Compare multi-task loss balancing strategies on a joint classification + regression task.
The dataset is UCI Wine Quality (red) with two output features:
- ``quality`` — the usual 0–10 score, trained as number regression.
- ``recommended`` — a synthetic binary target set to ``quality >= 6``, trained as binary
classification. The two outputs share everything except the final decoder head, so they
compete for the combiner's representational capacity.
For each balancer in :data:`STRATEGIES` the script trains the same model end-to-end and
records validation metrics. The summary table prints the per-task scores plus a
balance-aware geometric mean so you can see which strategy gets both tasks right.
Requires Ludwig 0.15 / PR #4092 for ``nash_mtl``.
Run: ``python compare_balancers.py``
"""
from __future__ import annotations
import logging
import math
from pathlib import Path
import pandas as pd
import yaml
from ludwig.api import LudwigModel
from ludwig.datasets import wine_quality
HERE = Path(__file__).parent
# Strategies to compare. nash_mtl is included only on the future-capabilities branch.
STRATEGIES = [
"none",
"log_transform",
"uncertainty",
"famo",
"gradnorm",
"nash_mtl",
]
def add_binary_target(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df["recommended"] = (df["quality"] >= 6).astype(int)
return df
def build_config(balancer: str) -> dict:
with (HERE / "config_nash_mtl.yaml").open() as f:
config = yaml.safe_load(f)
config["trainer"]["loss_balancing"] = balancer
return config
def run(balancer: str, dataset: pd.DataFrame) -> dict[str, float]:
config = build_config(balancer)
model = LudwigModel(config=config, logging_level=logging.WARNING)
result = model.train(
dataset=dataset,
output_directory=str(HERE / f"results_{balancer}"),
skip_save_processed_input=True,
skip_save_progress=True,
skip_save_unprocessed_output=True,
skip_save_predictions=True,
skip_save_model=True,
)
val = result.train_stats.validation or {}
quality_rmse = min(val["quality"].get("root_mean_squared_error", [float("nan")]))
recommended_acc = max(val["recommended"].get("accuracy", [float("nan")]))
quality_loss = min(val["quality"].get("loss", [float("nan")]))
recommended_loss = min(val["recommended"].get("loss", [float("nan")]))
# Geometric mean of losses is a balance-aware aggregate: a strategy that wrecks one task
# to win the other pays more than a strategy that keeps both reasonable.
geomean = math.sqrt(quality_loss * recommended_loss) if quality_loss and recommended_loss else float("nan")
return {
"quality_rmse": quality_rmse,
"recommended_acc": recommended_acc,
"geomean_loss": geomean,
}
def main() -> None:
dataset = add_binary_target(wine_quality.load())
rows = []
for balancer in STRATEGIES:
print(f"\n=== Training with loss_balancing: {balancer} ===")
try:
scores = run(balancer, dataset)
except Exception as exc:
print(f"[skip] {balancer}: {exc}")
continue
rows.append({"balancer": balancer, **scores})
if not rows:
raise SystemExit("No balancer runs completed successfully.")
summary = pd.DataFrame(rows).set_index("balancer")
summary = summary.sort_values("geomean_loss")
print("\nResults (best-of-training per task, sorted by geomean_loss):")
print(summary.to_string(float_format=lambda v: f"{v:.4f}"))
csv_path = HERE / "balancer_comparison.csv"
summary.to_csv(csv_path)
print(f"\nWrote {csv_path}")
if __name__ == "__main__":
main()