from __future__ import annotations from dataclasses import dataclass from typing import Any, Literal import pandas as pd ParameterType = Literal["int", "float", "bool", "str", "optional_int", "optional_float", "tuple_int"] @dataclass(frozen=True) class Parameter: name: str default: Any kind: ParameterType description: str def p(name: str, default: Any, kind: ParameterType, description: str) -> Parameter: return Parameter(name, default, kind, description) HYPERPARAMETER_SCHEMAS: dict[str, list[Parameter]] = { "MLP": [ p("hidden_layer_sizes", (64, 32), "tuple_int", "隐藏层神经元,例如 128,64"), p("activation", "relu", "str", "激活函数:relu / tanh / logistic / identity"), p("solver", "adam", "str", "优化器:adam / sgd / lbfgs"), p("alpha", 0.0001, "float", "L2 正则强度"), p("batch_size", "auto", "str", "批大小;auto 或整数"), p("learning_rate", "constant", "str", "学习率策略:constant / invscaling / adaptive"), p("learning_rate_init", 0.001, "float", "初始学习率"), p("power_t", 0.5, "float", "invscaling 学习率指数"), p("max_iter", 1000, "int", "最大训练迭代次数"), p("shuffle", True, "bool", "每轮是否打乱样本"), p("tol", 0.0001, "float", "优化停止容差"), p("warm_start", False, "bool", "是否复用上次训练结果"), p("momentum", 0.9, "float", "SGD 动量"), p("nesterovs_momentum", True, "bool", "是否使用 Nesterov 动量"), p("early_stopping", False, "bool", "验证集长期无提升时提前停止;小数据建议 false"), p("validation_fraction", 0.1, "float", "提前停止验证集比例"), p("beta_1", 0.9, "float", "Adam 一阶矩衰减"), p("beta_2", 0.999, "float", "Adam 二阶矩衰减"), p("epsilon", 1e-8, "float", "Adam 数值稳定项"), p("n_iter_no_change", 20, "int", "允许无提升的迭代轮数"), p("max_fun", 20000, "int", "LBFGS 最大函数调用次数"), ], "Random Forest": [ p("n_estimators", 240, "int", "树的数量"), p("criterion", "auto", "str", "划分标准;auto 根据任务选择"), p("max_depth", None, "optional_int", "单棵树最大深度;none 不限制"), p("min_samples_split", 2, "int", "内部节点继续划分的最少样本"), p("min_samples_leaf", 1, "int", "叶节点最少样本"), p("min_weight_fraction_leaf", 0.0, "float", "叶节点最小权重比例"), p("max_features", "auto", "str", "每次划分考察的特征数"), p("max_leaf_nodes", None, "optional_int", "最大叶节点数"), p("min_impurity_decrease", 0.0, "float", "最小不纯度下降"), p("bootstrap", True, "bool", "是否 bootstrap 采样"), p("oob_score", False, "bool", "是否计算袋外分数"), p("n_jobs", -1, "int", "并行任务数;-1 使用全部核心"), p("warm_start", False, "bool", "是否追加训练更多树"), p("class_weight", None, "str", "分类权重:none / balanced / balanced_subsample"), p("ccp_alpha", 0.0, "float", "成本复杂度剪枝强度"), p("max_samples", None, "optional_float", "每棵树 bootstrap 样本比例"), ], "Extra Trees": [ p("n_estimators", 240, "int", "树的数量"), p("criterion", "auto", "str", "划分标准;auto 根据任务选择"), p("max_depth", None, "optional_int", "单棵树最大深度"), p("min_samples_split", 2, "int", "内部节点最少样本"), p("min_samples_leaf", 1, "int", "叶节点最少样本"), p("min_weight_fraction_leaf", 0.0, "float", "叶节点最小权重比例"), p("max_features", "auto", "str", "每次划分考察的特征数"), p("max_leaf_nodes", None, "optional_int", "最大叶节点数"), p("min_impurity_decrease", 0.0, "float", "最小不纯度下降"), p("bootstrap", False, "bool", "是否 bootstrap 采样"), p("oob_score", False, "bool", "是否计算袋外分数"), p("n_jobs", -1, "int", "并行任务数"), p("warm_start", False, "bool", "是否追加训练"), p("class_weight", None, "str", "分类权重"), p("ccp_alpha", 0.0, "float", "成本复杂度剪枝"), p("max_samples", None, "optional_float", "bootstrap 样本比例"), ], "Gradient Boosting": [ p("loss", "auto", "str", "损失函数;auto 根据任务选择"), p("learning_rate", 0.05, "float", "每棵树的贡献缩放"), p("n_estimators", 180, "int", "提升阶段数量"), p("subsample", 0.9, "float", "每阶段使用的样本比例"), p("criterion", "friedman_mse", "str", "树划分标准"), p("min_samples_split", 2, "int", "内部节点最少样本"), p("min_samples_leaf", 1, "int", "叶节点最少样本"), p("min_weight_fraction_leaf", 0.0, "float", "叶节点最小权重比例"), p("max_depth", 3, "int", "基学习器最大深度"), p("min_impurity_decrease", 0.0, "float", "最小不纯度下降"), p("max_features", None, "str", "每次划分使用的特征数"), p("alpha", 0.9, "float", "Huber/Quantile 分位数"), p("max_leaf_nodes", None, "optional_int", "最大叶节点数"), p("warm_start", False, "bool", "是否追加提升阶段"), p("validation_fraction", 0.1, "float", "提前停止验证比例"), p("n_iter_no_change", None, "optional_int", "无提升提前停止轮数"), p("tol", 0.0001, "float", "提前停止容差"), p("ccp_alpha", 0.0, "float", "基学习器剪枝强度"), ], "SVM": [ p("C", 2.0, "float", "正则化强度的倒数"), p("kernel", "rbf", "str", "核函数:rbf / linear / poly / sigmoid"), p("degree", 3, "int", "多项式核次数"), p("gamma", "scale", "str", "核系数:scale / auto / 数值"), p("coef0", 0.0, "float", "poly/sigmoid 核独立项"), p("shrinking", True, "bool", "是否使用 shrinking 启发式"), p("probability", True, "bool", "分类时启用概率估计"), p("tol", 0.001, "float", "停止容差"), p("cache_size", 300, "float", "核缓存大小 MB"), p("class_weight", None, "str", "分类权重:none / balanced"), p("max_iter", -1, "int", "最大迭代;-1 不限制"), p("decision_function_shape", "ovr", "str", "多分类决策:ovr / ovo"), p("break_ties", False, "bool", "分类平票时按置信度裁决"), p("epsilon", 0.1, "float", "SVR epsilon-insensitive 区间"), ], "KNN": [ p("n_neighbors", 7, "int", "参与预测的邻居数量"), p("weights", "distance", "str", "邻居权重:uniform / distance"), p("algorithm", "auto", "str", "近邻搜索:auto / ball_tree / kd_tree / brute"), p("leaf_size", 30, "int", "树结构叶大小"), p("p", 2, "float", "Minkowski 距离指数"), p("metric", "minkowski", "str", "距离度量"), p("n_jobs", -1, "int", "并行任务数"), ], "Decision Tree": [ p("criterion", "auto", "str", "划分标准;auto 根据任务选择"), p("splitter", "best", "str", "划分策略:best / random"), p("max_depth", 12, "optional_int", "最大树深"), p("min_samples_split", 2, "int", "内部节点最少样本"), p("min_samples_leaf", 1, "int", "叶节点最少样本"), p("min_weight_fraction_leaf", 0.0, "float", "叶节点最小权重比例"), p("max_features", None, "str", "划分使用的特征数"), p("max_leaf_nodes", None, "optional_int", "最大叶节点数"), p("min_impurity_decrease", 0.0, "float", "最小不纯度下降"), p("class_weight", None, "str", "分类权重"), p("ccp_alpha", 0.0, "float", "成本复杂度剪枝"), ], "Logistic Regression": [ p("penalty", "l2", "str", "正则类型:l1 / l2 / elasticnet / none"), p("C", 1.0, "float", "正则化强度的倒数"), p("l1_ratio", 0.0, "float", "Elastic Net 的 L1 比例"), p("dual", False, "bool", "是否使用对偶问题"), p("tol", 0.0001, "float", "停止容差"), p("fit_intercept", True, "bool", "是否拟合截距"), p("intercept_scaling", 1.0, "float", "liblinear 截距缩放"), p("class_weight", None, "str", "类别权重:none / balanced"), p("solver", "lbfgs", "str", "求解器:lbfgs / liblinear / saga 等"), p("max_iter", 500, "int", "最大迭代次数"), p("warm_start", False, "bool", "是否复用上次解"), p("n_jobs", -1, "int", "并行任务数"), ], "Linear Regression": [ p("fit_intercept", True, "bool", "是否拟合截距"), p("copy_X", True, "bool", "是否复制输入特征"), p("tol", 1e-6, "float", "稀疏求解停止容差"), p("n_jobs", -1, "int", "并行任务数"), p("positive", False, "bool", "是否约束系数为非负"), ], } def _display(value: Any) -> str: if value is None: return "none" if isinstance(value, tuple): return ",".join(str(item) for item in value) if isinstance(value, bool): return str(value).lower() return str(value) def default_parameter_frame(algorithm: str) -> pd.DataFrame: if algorithm not in HYPERPARAMETER_SCHEMAS: raise ValueError(f"未知算法:{algorithm}") return pd.DataFrame( [ { "Parameter": parameter.name, "Value": _display(parameter.default), "Description": parameter.description, } for parameter in HYPERPARAMETER_SCHEMAS[algorithm] ] ) def _parse_value(parameter: Parameter, raw: Any) -> Any: value = str(raw).strip() lower = value.lower() if parameter.kind in {"optional_int", "optional_float"} and lower in {"none", "null", ""}: return None if parameter.kind == "bool": if lower not in {"true", "false"}: raise ValueError("需要 true 或 false") return lower == "true" if parameter.kind in {"int", "optional_int"}: return int(float(value)) if parameter.kind in {"float", "optional_float"}: return float(value) if parameter.kind == "tuple_int": return tuple(int(item.strip()) for item in value.split(",") if item.strip()) if lower in {"none", "null"}: return None if parameter.name == "batch_size": return int(value) if lower != "auto" else "auto" if parameter.name == "max_features": try: return float(value) if "." in value else int(value) except ValueError: return value if parameter.name == "gamma": try: return float(value) except ValueError: return value return value def parse_parameter_frame(algorithm: str, frame: pd.DataFrame | list[list[Any]]) -> dict[str, Any]: schema = {parameter.name: parameter for parameter in HYPERPARAMETER_SCHEMAS[algorithm]} data = frame if isinstance(frame, pd.DataFrame) else pd.DataFrame(frame) if list(data.columns) != ["Parameter", "Value", "Description"]: data.columns = ["Parameter", "Value", "Description"] parsed: dict[str, Any] = {} for _, row in data.iterrows(): name = str(row["Parameter"]).strip() if name not in schema: raise ValueError(f"未知超参数:{name}") try: parsed[name] = _parse_value(schema[name], row["Value"]) except (TypeError, ValueError) as exc: raise ValueError(f"超参数 {name} 的值无效:{row['Value']}") from exc return parsed