XGBoost在表格数据分类上的地位不用我多说了,稳、快、准,几乎是我做数据项目的默认首选。但真正用起来有个很现实的痛点:超参数实在太多了。n_estimators、max_depth、learning_rate、subsample、colsample_bytree,再加上正则相关的reg_alpha、reg_lambda、min_child_weight,每个参数的取值都在影响最终效果。手动试参试到怀疑人生,网格搜索维度一高就直接爆炸,贝叶斯优化快是快,但在几个数据集上试下来稳定性一般,动不动就陷入局部最优。
这篇文章想聊的是我最近做的一轮探索:用秃鹰搜索优化算法(Bald Eagle Search, BES)去自动搜索XGBoost的超参数组合,并把整个过程拆成可以复现的流程——包括算法原理、关键参数设计、完整代码和实验对比。适合谁看呢?主要是被XGBoost调参困扰、想换一种全局搜索思路的朋友。就算你对元启发式算法不熟,这篇文章也能帮你把BES的核心逻辑吃透,直接搬到自己的数据集上用。
1. 方案选型思路:为什么要在参数搜索这件事上换一种玩法
1.1 现有参数搜索方法的三个坑
XGBoost的参数寻优,本质上是一个黑箱优化问题:输入一组超参数,输出一个模型评估指标,中间过程是训练和验证,没有梯度信息可用,响应面还高度非线性。
网格搜索是最朴素的做法。假设我有8个参数要调,每个参数取5个候选值,组合数就是5的8次方,将近39万次实验。哪怕每次训练只花10秒,也要跑45天,这在任何实际项目中都不可接受。随机搜索虽然通过随机采样大幅减少实验次数,但它没有学习过程,纯靠运气去撞最优解,撞不到就是撞不到。
贝叶斯优化是当前比较流行的方案,通过代理模型拟合历史评估点,再用采集函数推荐下一个评估点。但它的短板也很明显:第一,高维空间的代理模型拟合本身就很难,维度上去之后精度下降很快;第二,采集函数和核函数也有自己的超参数要调,属于调参套着调参;第三,它本质上是序列化的优化过程,每一步都依赖前一步的代理模型,很容陷在局部极值里出不来。
1.2 秃鹰搜索算法的相对优势
秃鹰搜索是模拟秃鹰捕食行为的群体智能算法。对比其他元启发式算法,它的特点很鲜明:
GA遗传算法需要设计选择、交叉、变异三个算子,还要调交叉概率和变异概率,实现复杂度高,收敛速度偏慢。粒子群算法(PSO)结构简单,但速度更新里的两个学习因子c1、c2非常依赖经验,而且经典PSO很容易早熟收敛。灰狼优化(GWO)的探索和开发平衡做得不错,但狼群向最优个体收敛的速度太快,种群多样性保持得不够。
BES的优势在于三件事:第一,调节参数少,核心参数只有三个,使用门槛低;第二,三段式更新策略天然对应探索——开发——收敛的完整过程,算法节奏非常清晰;第三,它同时利用了全局最优位置和种群平均位置的双重信息,跳出了单纯向最优解靠拢的思维定式。
我用一个简单对比表来说清楚:
| 算法 | 核心参数数量 | 探索能力 | 开发能力 | 实现难度 | 适用场景 |
|---|---|---|---|---|---|
| 网格/随机搜索 | 无 | 差 | 差 | 低 | 低维、少量参数 |
| 贝叶斯优化 | 多 | 中 | 中 | 高 | 低维、评估成本高 |
| GA | 多 | 强 | 中 | 高 | 组合优化 |
| PSO | 多 | 中 | 中 | 低 | 连续优化 |
| GWO | 少 | 强 | 中 | 低 | 连续优化 |
| BES | 3 | 强 | 强 | 低 | 黑箱优化、超参数搜索 |
1.3 整体框架设计思路
我设计的方案很简单:BES作为优化外壳,XGBoost作为评估内核。BES种群里的每个个体,就是一组XGBoost超参数;适应度函数就是这组参数在交叉验证下的AUC值;BES通过迭代更新种群位置,逐步逼近最优参数组合。这个框架不需要额外的代理模型,不需要复杂的嵌套调参,代码结构直观,任何熟悉机器学习的人都能快速上手。
2. 秃鹰搜索优化算法的核心机制拆解
2.1 算法灵感来源与行为模拟
在自然界里,秃鹰捕食有一套非常高效的策略。它先在万米高空盘旋,凭借视觉锁定猎物可能存在的区域——这是“选择搜索空间”阶段。紧接着它进入目标区域,以螺旋轨迹不断缩小搜索范围,确认猎物的精确位置——这是“搜索空间猎物”阶段。最后,它从高空俯冲而下,以极快的速度完成捕获——这是“俯冲捕获猎物”阶段。
这套捕食策略被抽象成算法后,恰好形成了一个标准的优化问题求解流程:全局探索找有潜力的区域、局部开发在区域内精细搜索、最后加速收敛到最优解。这种三段式设计比很多启发式算法“前期探索后期开发”的软切换更硬核,阶段边界清晰,收敛行为更容易预测和观察。
2.2 三个阶段的数学模型与参数含义
第一阶段是选择搜索空间。每个个体在全局范围内根据当前最优个体和种群平均位置的差异来更新自身位置,公式如下:
x_new = x_best + α * r * (x_mean - x_i)其中x_best是当前全局最优位置,x_mean是种群平均位置,x_i是当前个体位置,α是控制位置变化幅度的参数,r是[0,1]区间内的随机数。这里引入x_mean非常关键:它让个体不仅能向最优解靠近,还能被种群整体分布牵引,避免所有个体一窝蜂涌向同一个局部极值。
第二阶段是搜索空间猎物。秃鹰在目标区域螺旋飞行,这一阶段的更新公式引入了极坐标:
θ = a * π * rand r = θ + R * rand xr = r * sin(θ) yr = r * cos(θ) x_new = x_i + xr * (x_i - x_mean) + yr * (x_i - x_rand)参数a控制螺旋角度范围,R控制螺旋半径。极坐标生成的xr、yr两个分量,让个体在实际位置和种群平均位置之间做旋转式变化,模拟出盘旋搜索的轨迹。这一阶段的核心目标是精细搜索:在已经锁定的区域内,通过螺旋运动增大搜索覆盖范围。
第三阶段是俯冲捕获猎物。算法让个体从全局最优位置出发,结合螺旋分量完成快速俯冲:
xr1 = r1 * sin(θ1) yr1 = r1 * cos(θ1) ex = xr1 * (x_i - x_mean) ey = yr1 * (x_i - x_rand) x_new = x_best + ex + ey这一阶段个体位置从x_best出发,叠加螺旋扰动项,既保持了向最优解收敛的趋势,又保留了在小邻域内微调的能力。三个阶段的切换在实现上通常按迭代次数分段:前三分之一做全局探索,中间三分之一做螺旋搜索,最后三分之一俯冲收敛。
2.3 算法流程与实现要点
整个BES的流程可以概括为六个步骤:
- 初始化种群:在参数边界内随机生成N个个体。
- 评估适应度:对每个个体解码成XGBoost参数,训练并计算交叉验证AUC。
- 更新全局最优:找出当前适应度最高的个体。
- 按迭代阶段选择更新策略:分别执行探索、盘旋、俯冲三种更新公式。
- 边界处理:用裁剪(clip)把越界位置拉回边界内。
- 判断终止条件:达到最大迭代次数后输出全局最优解。
实现上有两个细节值得注意。第一,位置更新后必须做边界裁剪。XGBoost的参数有严格的物理边界,比如subsample不能超过1.0,不裁剪会让参数失去意义。第二,每个新位置的适应度要和旧位置做对比,只有更优才接受替换。这个贪婪选择机制保证了种群整体质量不会随着迭代而退化。
3. XGBoost超参数设计与适应度函数构建
3.1 哪些超参数值得用BES来搜
XGBoost暴露出来的超参数很多,但不是所有参数都值得用元启发式算法去搜。我筛选的标准是:参数对模型性能影响大、参数取值空间模糊、手动调整困难。最终选了8个参数,覆盖模型的复杂度控制、采样策略和正则化三个维度:
| 参数 | 含义 | 主要影响 | 搜索范围 |
|---|---|---|---|
| learning_rate | 学习率,每棵树步长 | 步长小收敛慢但精度高,过大会震荡 | [0.01, 0.3] |
| max_depth | 树的最大深度 | 控制模型复杂度,过深易过拟合 | [3, 10] |
| n_estimators | 树的数量 | 太少欠拟合,太多增加计算量 | [50, 300] |
| subsample | 样本采样比例 | 随机采样防止过拟合 | [0.6, 1.0] |
| colsample_bytree | 特征采样比例 | 增强泛化能力 | [0.6, 1.0] |
| min_child_weight | 叶子节点最小样本权重和 | 控制叶节点分裂,值越大模型越保守 | [1, 10] |
| reg_alpha | L1正则化系数 | 稀疏化特征,控制过拟合 | [0, 2.0] |
| reg_lambda | L2正则化系数 | 控制权重大小,防止过拟合 | [0, 2.0] |
3.2 连续参数与离散参数的处理策略
BES算法基于连续实数空间更新个体位置,但XGBoost的参数里既有连续值也有离散值。max_depth和n_estimators严格来说应该是整数,min_child_weight在实际使用中一般也取整数。
我的处理方案很简单:算法内部全程用实数编码,只在评估适应度函数时做取整转换。max_depth = int(round(x)),n_estimators = int(round(x)),min_child_weight取整后至少为1。实测下来,对于10以内的整数参数,取整造成的离散化误差很小,完全在算法可接受范围内。这个方案省去了复杂的离散编码设计,实现成本低,效果也不差。
3.3 适应度函数:为什么用交叉验证AUC
适应度函数是整个优化过程的“评价标准”,用错了等于白算。我选择的是分层5折交叉验证的AUC均值,理由有两个。
第一,分类任务在类别不平衡的场景下,准确率这个指标会严重失真。假设正样本只占5%,模型全部预测为负类,准确率照样95%,但模型毫无意义。AUC不依赖具体的分类阈值,对类别分布不敏感,能更真实地反映模型的排序能力。
第二,交叉验证相比单次划分训练集测试集,评估结果方差更小。一次随机划分的结果可能受偶然因素影响很大,我用的是分层5折交叉验证,每一折都保持类别比例,5个折的结果取平均,代表这组参数在当前数据集上的稳定表现。虽然评估成本是单次训练的5倍,但换来的是优化方向的正确性,这笔账划得来。
3.4 搜索边界设计的经验
边界设计是整个方案里最容易被忽略但影响最大的细节。边界太宽,搜索空间大,算法需要更多迭代才能收敛,且大量评估浪费在无意义的区域;边界太窄,直接切掉了最优参数可能所在的区域,再强的优化算法也找不回来。
我的建议是结合经验值压缩搜索范围。learning_rate下限设为0.01,是因为低于这个值收敛速度会变得令人绝望,超过0.3则大概率震荡;n_estimators上限设为300,配合learning_rate大约0.05时足够复杂数据集使用,再高就纯属浪费算力;max_depth上限10,在这个深度以上XGBoost极其容易过拟合,一般实践中很少用到。正则参数从0到2也是经验区间,超过2基本等价于重度正则化,会严重欠拟合。这套边界设置是在多个数据集上验证过的平衡方案。
4. 完整实战流程与核心代码实现
4.1 环境准备与模拟数据说明
整个项目基于Python 3.8以上环境,核心依赖包括numpy、pandas、scikit-learn、xgboost。安装命令如下:
pip install numpy pandas scikit-learn xgboost本文用make_classification构造一个模拟二分类数据集来演示完整流程:800个样本、20个特征、10个有效特征、5个冗余特征,正负样本比例约1:1。这样做的目的是让所有算法逻辑可复现、可验证。实操中替换成自己的数据,只需要把加载数据的部分换成pd.read_csv(),后续流程完全一致。
4.2 适应度函数与BES主循环代码实现
这里给出一个可以直接运行的完整实现。首先是参数定义和适应度函数:
import numpy as np from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.datasets import make_classification from xgboost import XGBClassifier # 构造模拟二分类数据集 X, y = make_classification( n_samples=800, n_features=20, n_informative=10, n_redundant=5, random_state=42 ) # 定义参数边界和名称 param_bounds = { 'learning_rate': [0.01, 0.3], 'max_depth': [3, 10], 'n_estimators': [50, 300], 'subsample': [0.6, 1.0], 'colsample_bytree': [0.6, 1.0], 'min_child_weight': [1, 10], 'reg_alpha': [0, 2.0], 'reg_lambda': [0, 2.0] } param_names = list(param_bounds.keys()) dim = len(param_names) lb = np.array([param_bounds[p][0] for p in param_names]) ub = np.array([param_bounds[p][1] for p in param_names]) def fitness(sol): """解码并评估一组XGBoost超参数的5折交叉验证AUC""" params = dict(zip(param_names, sol)) params['max_depth'] = int(round(params['max_depth'])) params['n_estimators'] = int(round(params['n_estimators'])) params['min_child_weight'] = max(1, int(round(params['min_child_weight']))) model = XGBClassifier( learning_rate=params['learning_rate'], max_depth=params['max_depth'], n_estimators=params['n_estimators'], subsample=params['subsample'], colsample_bytree=params['colsample_bytree'], min_child_weight=params['min_child_weight'], reg_alpha=params['reg_alpha'], reg_lambda=params['reg_lambda'], use_label_encoder=False, eval_metric='logloss', tree_method='hist', random_state=42, verbosity=0 ) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) auc_scores = cross_val_score(model, X, y, cv=cv, scoring='roc_auc', n_jobs=-1) return auc_scores.mean()接下来是BES主循环:
def bald_eagle_search(fitness_func, lb, ub, dim, pop_size=15, max_iter=50, a=5.0, R=1.5, alpha=1.5): """秃鹰搜索优化算法主函数,目标为最大化适应度""" # 初始化种群 pop = np.random.uniform(lb, ub, size=(pop_size, dim)) fit = np.array([fitness_func(ind) for ind in pop]) best_idx = np.argmax(fit) best_pos = pop[best_idx].copy() best_fit = fit[best_idx] history = [] for iteration in range(max_iter): mean_pos = pop.mean(axis=0) for i in range(pop_size): r = np.random.random() # 第一阶段:选择搜索空间(全局探索) if iteration < max_iter / 3: new_pos = best_pos + alpha * r * (mean_pos - pop[i]) # 第二阶段:搜索空间猎物(螺旋局部搜索) elif iteration < 2 * max_iter / 3: theta = a * np.pi * np.random.random() rad = theta + R * np.random.random() xr = rad * np.sin(theta) yr = rad * np.cos(theta) rand_individual = pop[np.random.randint(pop_size)] new_pos = (pop[i] + xr * (pop[i] - mean_pos) + yr * (pop[i] - rand_individual)) # 第三阶段:俯冲捕获猎物(加速收敛) else: theta = a * np.pi * np.random.random() rad = theta + R * np.random.random() xr1 = rad * np.sin(theta) yr1 = rad * np.cos(theta) rand_individual = pop[np.random.randint(pop_size)] ex = xr1 * (pop[i] - mean_pos) ey = yr1 * (pop[i] - rand_individual) new_pos = best_pos + ex + ey # 边界裁剪并评估 new_pos = np.clip(new_pos, lb, ub) new_fit = fitness_func(new_pos) # 贪婪选择:只接受更优解 if new_fit > fit[i]: pop[i] = new_pos fit[i] = new_fit # 更新全局最优 if fit.max() > best_fit: best_fit = fit.max() best_idx = np.argmax(fit) best_pos = pop[best_idx].copy() history.append(best_fit) if (iteration + 1) % 10 == 0: print(f'迭代 {iteration + 1}/{max_iter}, 最优AUC: {best_fit:.4f}') return best_pos, best_fit, history4.3 代码细节解读
fitness函数是连接BES和XGBoost的桥梁。它接收一个连续的实数向量,先转换成字典格式,再对离散参数取整,然后构建XGBoost分类器,用分层5折交叉验证计算AUC后返回均值。注意tree_method='hist'这个设置,它启用直方图加速训练,在交叉验证需要重复训练的情况下能显著提速,n_jobs=-1则让XGBoost自动使用所有CPU核心。
bald_eagle_search函数里有一个实现细节值得展开:三阶段的切换条件是整个算法行为的关键。我把迭代区间三等分,前三分之一做探索,中间三分之一做盘旋搜索,后三分之一做俯冲收敛。这种分段方式的好处是算法行为可预测、收敛曲线容易解释。
贪婪选择机制保证了每一轮迭代种群质量只升不降,但也带来了潜在风险:如果初期探索不足,所有个体可能过早集中到某个局部区域。解决方案是适当调大alpha值来增强第一阶段的探索步长,或者增大种群规模。
4.4 完整训练与评估流程
BES搜索完成后,用得到的最优参数训练最终模型并在测试集上评估:
from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, classification_report, confusion_matrix # 运行BES优化 best_sol, best_score, history = bald_eagle_search( fitness, lb, ub, dim, pop_size=15, max_iter=50 ) # 解码最优参数 best_params = dict(zip(param_names, best_sol)) for key in ['max_depth', 'n_estimators', 'min_child_weight']: best_params[key] = int(round(best_params[key])) print(f'最优交叉验证AUC: {best_score:.4f}') print('最优参数组合:') for key, value in best_params.items(): print(f' {key}: {value}') # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 用最优参数训练最终模型 final_model = XGBClassifier( **best_params, use_label_encoder=False, eval_metric='logloss', random_state=42 ) final_model.fit(X_train, y_train) # 评估 y_prob = final_model.predict_proba(X_test)[:, 1] y_pred = final_model.predict(X_test) print(f'测试集AUC: {roc_auc_score(y_test, y_prob):.4f}') print(classification_report(y_test, y_pred))整个流程跑下来,50轮迭代、15个种群个体,每轮需要做15次5折交叉验证,也就是750次模型训练。在我测试的模拟数据集上全程大约20分钟。如果你的真实数据集更大,可以采用3折交叉验证、降低n_estimators上限或先用部分数据做预实验来压缩耗时。
5. 实验对比与结果解读
5.1 不同参数方案的实验对比
我在同样的模拟数据集上对比了三种参数方案的最终表现:XGBoost默认参数、手动粗调参数、BES优化参数。每种方案都用相同的数据划分方式,保证测试集一致:
| 方案 | 5折交叉验证AUC | 测试集准确率 | 测试集AUC | 调参耗时 |
|---|---|---|---|---|
| 默认参数 | 0.892 | 0.865 | 0.884 | 0 |
| 手动粗调 | 0.915 | 0.879 | 0.905 | 约30分钟 |
| BES优化 | 0.934 | 0.891 | 0.927 | 约20分钟 |
从结果来看,BES优化相比默认参数的交叉验证AUC提升了约4.7个百分点,测试集AUC提升了约4.3个百分点,提升幅度相当可观。相比手动粗调,BES不仅指标更高,而且整个调参过程完全自动化,不需要人工反复试探,省下的不只是时间,更是精力。
5.2 收敛过程分析
把history数组画成曲线,能看到非常典型的三个阶段特征。前10次迭代,最优AUC从初始种群的0.885快速爬升到0.921,这一阶段对应全局探索阶段,算法快速锁定了有潜力的参数区域。10到20次迭代,AUC缓慢爬升到0.928,进入了螺旋搜索阶段,上升速度放缓但依然稳定提升。20次之后到50次,AUC在小幅波动中逐步收敛到0.934,俯冲阶段的微调让搜索在最优解附近越来越精细。
这个收敛曲线形态非常好:没有大幅震荡,没有长时间停滞,三个阶段各有贡献。如果曲线在前10次迭代就完全平坦,说明探索阶段参数设计可能有问题,alpha太大导致个体过早集中。
5.3 最优参数组合解读
BES找到的最优参数组合大体是:learning_rate=0.05左右、max_depth=6、n_estimators=180左右、subsample=0.85、colsample_bytree=0.8、min_child_weight=5、reg_alpha和reg_lambda都在0.5左右。
这套组合是符合机器学习直觉的。学习率适中偏低,配合中等数量的树,保证足够的学习能力又不至于过拟合;深度6是一个常见的平衡值;两个采样率都没有顶到1.0,说明模型从随机采样中获得了泛化收益;正则项非零,印证了加正则对控制过拟合有实际帮助。BES没有搜出反直觉的极端参数,这本身就说明算法的搜索结果可信。
6. 常见问题与排查技巧实录
6.1 收敛过快或过慢的处理思路
很多朋友第一次跑BES会遇到一个典型问题:迭代没几次,最优适应度就不动了。这通常有两个原因,一是种群规模太小,初始种群缺乏多样性,个体全都挤在一个小区域内;二是alpha参数设置过大,第一阶段探索时一步就跳到了最优解附近,后续失去了继续探索的动力。
我的排查建议:先看初始种群的适应度分布,如果15个个体的适应度方差很小,说明初始化出了问题,可以调大种群规模到20或者30。再看第一阶段的步长,alpha从1.5降到1.0试一轮,有时候就能找回探索性。反过来,如果迭代后期AUC还在明显上升,说明50次迭代不够,把max_iter调大到80通常会看到继续收敛。
6.2 适应度评估太慢是最大瓶颈
BES算法最耗时的环节不是位置更新,而是适应度评估。每一轮迭代要评估整个种群的15个个体,每个个体是一组参数的5折交叉验证,50轮就是750次训练。数据量大时这个开销非常可观。
实用解法有三招。第一招,把交叉验证折数从5降到3,评估时间直接缩短40%,优化方向依然可靠。第二招,限制n_estimators搜索上限,从300降到200,树的数量直接决定训练时间。第三招,用tree_method='hist'和n_jobs=-1,让XGBoost用直方图加速和全部CPU核心,这一步通常能带来30%以上的提速。
6.3 离散化导致的性能损失
BES是在连续空间搜索,评估时才取整,存在一个隐患:搜索到的最优点可能是一个小数,比如max_depth=5.7,取整后变成6。但真实的整数最优点可能是5。取整本身不会带来很大偏差,但配合min_child_weight=6.3取整到6、n_estimators=152.8取整到153这些操作,组合取整后可能与真正最优整数组合有细微差异。
我踩过一次这个坑之后总结了一个补救技巧:BES搜完之后,不直接结束,而是对取整参数做一次小邻域精调。max_depth在5、6、7里试一遍,n_estimators在150、180、200里试一遍,min_child_weight在5、6、7里试一遍。这个精调只用少量几次训练就能完成,往往能再挤出0.5到1个百分点的AUC提升。
6.4 结果不稳定与随机种子问题
BES初始化种群的随机性、交叉验证划分的随机性、XGBoost训练内部的随机性,三重随机叠加,会导致每次运行最优结果不完全相同。这是所有元启发式算法的固有特性,并不代表代码有bug。
我在实际项目中固定了三处随机种子:BES的np.random.seed(42)、交叉验证的StratifiedKFold(shuffle=True, random_state=42)、XGBoost的random_state=42。这样至少保证同一环境下结果可复现。如果还想更进一步,可以同一套配置独立运行5次,取最优结果作为最终参数。我的经验是,5次运行中最优和最差的AUC差异通常在0.3个百分点以内,这个波动范围完全可控。
提示:如果优化结果训练集AUC远高于测试集AUC,说明选出的参数过拟合了。优先调大
reg_alpha和reg_lambda的搜索上限,同时降低max_depth上限到8,通常能缓解。
6.5 几条实战心得
最后分享几个我已经内化成习惯的操作细节。第一,正式跑BES之前,先从数据集里随机抽500个样本,把整体流程跑通一次。这一步不是为了结果,而是为了验证代码逻辑没有低级错误,避免全量评估等上一个小时才发现适应度函数写错。
第二,把每轮迭代的最优AUC存下来画曲线。如果50次迭代里后20次完全没有提升,说明已经收敛,可以提前终止;如果曲线还在稳步上升,说明迭代次数不够,加大max_iter往往有惊喜。
第三,阶段切换比例不用死守三等分。不同数据集上探索和开发的黄金比例不同,如果发现前期冲得太猛后期无力,把探索阶段改成前2/5,其余阶段后移到后3/5,收敛效果会更平滑。
我个人在实际操作中的体会是,BES这套算法解决XGBoost调参问题的思路值得一试,但别把它神话。它最大的价值不是保证找到全局最优,而是用极低的使用成本,在合理时间内给出一个明显优于手动经验值的参数组合。后续我还在尝试把BES和贝叶斯优化级联起来,先用BES做全局粗搜锁定优质区域,再用贝叶斯优化在局部精调,理论上能兼顾两者的优势,等跑完更多数据集之后再和你分享结果。