File size: 11,848 Bytes
6a18a7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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