
XGBoost 这套梯度提升树在机器学习项目里的出场率高得离谱尤其是表格数据、风控评分、用户流失预测这类场景很多团队第一版能交付上线的模型就是它。但真正上手之后你会发现XGBoost 的参数表长得吓人官方文档里光 booster 相关参数就有几十个新手很容易一头扎进“全部调一遍”的泥潭里出不来。我这些年带过几个新人几乎每个人都会问同一个问题到底哪些参数值得动哪些保持默认就行这篇内容就围绕 XGBoost 的使用和参数展开把我自己在项目里踩出来的经验完整讲一遍包括环境怎么装、最小可运行代码怎么写、参数背后的计算逻辑是什么、调参顺序怎么排、报错怎么查。不管你是刚开始接触机器学习的学生还是已经能跑通模型但效果调不上来的工程师应该都能从里面找到能直接抄走的东西。1. XGBoost 到底解决了什么问题值不值得上手1.1 从一棵树到加法模型它凭什么比单棵树强先把这个东西讲透后面调参才不会变成瞎试。一棵决策树的问题很明显深度浅了学不动深度深了记性好到把训练集背下来换一批数据就崩。XGBoost 的思路不是把一棵树做大而是一堆弱树接力干活。打个比方你让一群人来猜一箱苹果的重量第一个人猜 10 公斤实际 8 公斤第二个人不去重新猜整体只负责修正前面那个人的误差。每棵树训练的目标就是当前模型预测值与真实值之间的残差把每棵树的结果按学习率加权累加最后得到整体预测。数学上它是一个加法模型第 t 轮的目标函数写成前 t-1 棵树的预测加上第 t 棵树再对损失函数做二阶泰勒展开。为什么用二阶而不是只用一阶梯度因为二阶项带来了类似牛顿法的收敛速度同时 Hessian 可以参与节点分裂的计算让每次分裂的收益评估更准。这也是后面 min_child_weight 这个参数为什么和样本权重、Hessian 相关的原因很多人只知道“调大它防过拟合”却不知道它本质上在约束什么。另外 XGBoost 在目标函数里显式加了正则项叶子节点数量和叶子权重的 L2 范数。这一点是它和传统 GBDT 的一个重要区别——传统 GBDT 靠剪枝、靠树深去控制复杂度XGBoost 把复杂度直接写进损失里让模型在训练过程中就为“别长太复杂”付出代价。再加上列采样、行采样、Shrinkage、列块并行这些工程设计它在中小规模表格数据上往往又快又稳。需要提醒的是XGBoost 强不代表无脑用。它对特征工程仍然敏感类别特征、时间特征、ID 类特征处理不好照样跑不出效果。我见过有人把所有特征一锅端丢进去然后抱怨 AUC 只有 0.6问题其实出在特征而不是模型。1.2 和 LightGBM、CatBoost 之间怎么选这两个经常被拿来对比实际项目里我基本是“先跑基线、再看数据规模”的流程。下面这张表是我自己总结的取舍依据不是绝对结论但能帮你快速判断先试哪个。维度XGBoostLightGBMCatBoost树的生长方式按层生长level-wise按叶子生长leaf-wise对称树oblivious小数据表现稳不容易翻车有时过拟合好但训练慢大数据速度中等快慢类别特征支持2.0 起原生支持 category原生支持原生支持且强缺失值处理自动学习默认方向自动处理自动处理调参难度参数多但资料全参数相对少参数少、默认好用可解释性工具特征重要性、SHAP 生态成熟同左同左我自己的一条经验规律样本量在十万行以内、特征几百个以内的结构化数据XGBoost 的默认参数加上早停通常就能给出一个很能打的基线不需要一上来就上 LightGBM。样本量上千万、特征上千LightGBM 的直方图算法优势会明显放大。类别特征特别多、基数特别高比如几十万量级的商品 IDCatBoost 的目标编码会更省事但代价是训练时间拉长。还有一个常被忽略的点不要只凭一次实验结果就认定谁更好。同一个数据集上XGBoost 和 LightGBM 的 AUC 差个 0.002 到 0.005 太正常了这种差距很多时候换个随机种子就没了。要对比至少固定随机种子跑三到五折看均值和方差。2. 环境准备与第一个能跑起来的 XGBoost 模型2.1 安装方式和版本带来的坑安装本身很简单CPU 版本一行命令pip install xgboost但版本差异是新手最容易踩的坑我把它单独拎出来说。XGBoost 1.x 到 2.x 之间有几处不兼容的改动网上大量教程还是老写法直接抄会报错。第一个是 GPU 相关的写法1.6 之前用tree_methodgpu_hist2.0 之后推荐用devicecuda老写法在新版本会有弃用警告甚至直接失效。第二个是early_stopping_rounds早期版本可以直接传进xgb.train2.0 之后官方建议改用callbacks传入某些版本里直接传这个参数会直接抛参数不存在的错误。第三个是模型保存格式1.x 默认二进制.model2.0 之后 JSON 格式更稳跨版本加载更友好。所以我的建议是项目里先把版本钉死写进requirements.txt别让自己三个月后回来复现不了。查看版本的代码import xgboost as xgb print(xgb.__version__)如果你要在服务器上跑还要注意 CPU 指令集的问题。自己机器上编译的包放到另一台机器上可能因为缺少 AVX 指令集报错最省事的办法是在目标机器上重新pip install别直接拷贝 site-packages 目录。2.2 三十行代码跑通一个二分类先把这条链路走通比看一百页文档都管用。下面是 sklearn 接口的最小示例用乳腺癌数据集演示import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, accuracy_score from xgboost import XGBClassifier data load_breast_cancer() X, y data.data, data.target X_train, X_valid, y_train, y_valid train_test_split( X, y, test_size0.2, random_state42, stratifyy ) model XGBClassifier( n_estimators1000, learning_rate0.05, max_depth5, min_child_weight1, subsample0.8, colsample_bytree0.8, reg_lambda1.0, objectivebinary:logistic, eval_metricauc, tree_methodhist, random_state42, n_jobs-1, ) model.fit( X_train, y_train, eval_set[(X_valid, y_valid)], verboseFalse, ) pred model.predict_proba(X_valid)[:, 1] print(AUC:, roc_auc_score(y_valid, pred)) print(ACC:, accuracy_score(y_valid, model.predict(X_valid))) print(best_iteration:, model.best_iteration)几个细节值得说。n_estimators给 1000 是故意设大的配合早停让它自己决定停在哪一轮比手动设 100 靠谱得多。eval_set必须传验证集否则早停没依据。objective用binary:logistic输出的就是概率不要用multi:softmax处理二分类虽然有些老教程这么写但那是多分类的接口。跑完你会看到 AUC 大概在 0.99 附近这个数据集太干净属于“跑通就行”的级别别拿它当调参效果参考。真正要验证调参能力得找特征有噪声、类别不平衡、缺失值多的数据。2.3 原生 train API 和 sklearn 接口怎么选这是很多人纠结的地方我直接给结论做特征分析和快速实验用 sklearn 接口做精细控制和自定义评估用原生 API。sklearn 接口的好处是能和Pipeline、GridSearchCV、cross_val_score无缝配合代码量少适合放进已有的 sklearn 工作流。原生 API 的好处是你能拿到DMatrix显式管理训练集和验证集控制evals_result、callbacks、iteration_range这些东西做多目标评估、自定义损失、分阶段训练时更灵活。原生 API 的标准骨架长这样import xgboost as xgb dtrain xgb.DMatrix(X_train, labely_train, feature_nameslist(data.feature_names)) dvalid xgb.DMatrix(X_valid, labely_valid, feature_nameslist(data.feature_names)) params { objective: binary:logistic, eval_metric: [auc, logloss], eta: 0.05, max_depth: 5, subsample: 0.8, colsample_bytree: 0.8, tree_method: hist, seed: 42, } evals_result {} bst xgb.train( params, dtrain, num_boost_round1000, evals[(dtrain, train), (dvalid, valid)], evals_resultevals_result, callbacks[xgb.callback.EarlyStopping(rounds50, save_bestTrue)], verbose_eval100, )注意DMatrix里传feature_names是个好习惯后面看特征重要性时能直接对得上名字不然只能看到f0、f1这种编号排查问题非常痛苦。还有evals_result这个字典会把每一轮的评估指标存下来画学习曲线时直接用不用自己再记录。3. 参数体系详解哪些必须懂哪些可以先放着3.1 通用参数决定跑在哪里、怎么跑通用参数管的是宏观行为不直接影响模型拟合能力但配错了会让你的训练慢十倍或者直接失败。booster默认gbtree也就是树模型另一个选项gblinear基本不用线性提升在这个框架里没优势。除非你想做一个超快的基线对比。nthread或者 sklearn 里的n_jobs控制并行线程数。设置成-1用满所有核但在共享服务器上建议设成实际核数的一半避免把别人的任务挤死。这一点很多人不注意结果自己的任务跑得快了同事的任务全卡住。tree_method是重点。auto让框架自己选小数据用exact大数据用approx。实践中我基本固定用hist直方图算法在大中小数据上都表现稳定速度快内存占用低。只有在做精确性对比实验时才用exact。device是 2.0 之后的新参数设成cuda走 GPU。这里要提醒一句GPU 版本不是万能的样本量小、特征少的时候数据搬运的开销可能比计算本身还大反而比 CPU 慢。我的经验是特征维度超过几百、样本超过几十万行再考虑上 GPU。verbosity控制日志级别调试时设成 2 或 3看每轮评估结果生产环境设成 0避免日志刷屏。3.2 树结构参数真正决定模型能力的那一批这一组是调参的主战场我按重要程度排。eta也就是学习率控制每棵树的贡献缩放。默认 0.3实际项目里基本不用默认值常用 0.05 或者 0.1。它和树的数量是一对跷跷板eta 小需要更多轮才收敛但泛化通常更好eta 大收敛快但容易冲过最优点。我的习惯是先用 0.1 跑一版看大概效果正式训练降到 0.03 到 0.05靠早停决定轮数。max_depth默认 6。表格数据上我很少超过 8常用 4 到 6。深度每加一层模型能表达的交互就复杂一层但过拟合风险是成倍上升的。如果你发现验证集效果在训练到几十轮后开始掉先想的是降深度而不是加数据。min_child_weight默认 1这是最容易被低估的参数。它约束的是叶子节点里样本 Hessian 之和的下限。回归任务里 Hessian 恒为 1所以它退化成“叶子最少多少个样本”。二分类里 Hessian 约等于 p(1-p)正负样本五五开时一个样本贡献约 0.25所以 min_child_weight 设 1 其实等于允许叶子只有一个样本几乎没有约束。这解释了为什么它对不平衡数据的过拟合控制效果特别明显。实践中我会从 1 起调逐步加到 5、10、20观察验证集曲线。gamma也叫min_split_loss默认 0。它要求一次分裂带来的损失下降必须超过这个阈值否则就不分裂。可以理解成“生长预算”调大它树更保守叶子更少。常用范围 0 到 5AUC 类任务上我一般试 0、0.5、1、2。subsample和colsample_bytree是行采样和列采样默认都是 1。这两个是性价比很高的防过拟合手段通常设 0.6 到 0.9而且几乎不会让训练变慢。colsample_bylevel和colsample_bynode更细粒度一般不用动除非你在做很精细的搜索。reg_lambda和reg_alpha是 L2 和 L1 正则默认分别是 1 和 0。注意reg_lambda默认就是 1 而不是 0这点和很多人想的不一样。L2 通常设 1 到 10L1 在特征维度特别高、想要稀疏解时设 0.1 到 1。max_delta_step默认 0只有做极度不平衡的二分类、且希望输出概率更稳定时才设 1属于偏门参数。3.3 学习任务参数目标函数和评估指标objective决定模型在优化什么选错了后面怎么调都没用。常用取值如下表。任务类型objective输出说明二分类binary:logistic正类概率多分类multi:softprob每类概率推荐多分类multi:softmax类别编号不输出概率回归reg:squarederror均方误差最常用回归reg:absoluteerrorMAE对离群点更稳排序rank:pairwise成对排序eval_metric用来监控训练过程二分类常用auc、logloss、aucpr回归常用rmse、mae。可以同时传多个比如[auc, logloss]早停按第一个指标来。这里有个细节如果你传的是[logloss, auc]早停会盯着 logloss而不是你最关心的 AUC。想要 AUC 早停就把 AUC 放前面。base_score是全局初始预测默认根据数据自动估计。一般不用手动设除非你在做迁移或者增量训练。scale_pos_weight用于不平衡二分类典型取值是负样本数除以正样本数。这里要说清一个容易混淆的点它改变的是样本权重会改变输出的概率标定但对排序类指标比如 AUC影响通常不大。也就是说如果你的评估是 AUC加这个参数收益有限如果你关心的是精确率和召回率的平衡、或者是概率输出的校准它才真正有用。seed随机种子固定它能让实验可复现。做对比实验时建议至少跑三个不同种子别被单次结果骗了。3.4 参数速查表和它们背后的计算逻辑把上面的内容压缩成一张可以直接查的表。参数默认值常用范围调大后的影响eta0.30.01~0.1收敛快、易过拟合max_depth63~8表达力强、易过拟合min_child_weight11~20更保守、防过拟合gamma00~5更难分裂、树更浅subsample10.6~0.9更随机、防过拟合colsample_bytree10.6~0.9同上reg_lambda11~10权重更平滑reg_alpha00~1产生稀疏解scale_pos_weight1负/正提升正类召回为什么要理解计算逻辑举个例子。有人问我为什么min_child_weight设成 3 在回归任务里几乎没效果而在二分类任务里效果立竿见影。原因是回归的 Hessian 是常数 13 就意味着叶子至少三个样本这几乎不叫约束而二分类里 Hessian 是 p(1-p)正负样本各半时每个样本贡献 0.253 相当于要求叶子至少十二个左右的样本约束一下子就紧了。同一个数值在不同任务里含义完全不同不理解这层就会盲目抄参数。再比如gamma它在分裂时比较的是增益减去 gamma 之后是否大于零。增益本身是由一阶梯度和二阶 Hessian 算出来的所以 gamma 和 min_child_weight 的作用有重叠但不完全一样前者管“这次分裂值不值得”后者管“分裂出来的孩子够不够结实”。排查过拟合时我通常先动 min_child_weight再动 gamma。4. 完整实操从数据到模型的一条龙流程4.1 数据准备与缺失值处理XGBoost 有一个很实用的特性它自己能处理缺失值不需要你提前填充。原理是在每次节点分裂时模型会为缺失值学习一个默认方向把缺失样本分到增益更大的一侧。这个方向是训练时根据数据学出来的不是固定往左或往右。所以对于缺失率不高、且缺失是随机发生的特征我通常直接交给模型处理反而比填均值效果好。但这里有两个边界要注意。第一你的缺失必须是np.nan或者DMatrix能识别的缺失标记如果数据里缺失被写成字符串NULL、-、未知模型会把它当普通类别值处理效果完全不对。第二如果缺失本身带有业务含义比如“从未申请过贷款”这个状态在字段层面表现为空那缺失就是信息这时候除了让模型自己学还可以额外构造一个“是否缺失”的 0/1 指示特征两条路一起走。数据准备的顺序我一般是先划分训练集和验证集再做特征处理避免信息泄露。标准化对树模型不是必须的因为树只关心分裂点顺序不关心数值尺度所以拿到的数据不用做归一化能省一步。类别特征上2.0 之后可以用enable_categoricalTrue配合 pandas 的category类型直接训练但在生产环境里我更倾向做目标编码或者频率编码可控性更强也避免线上推理时类别值超出训练集范围的问题。4.2 训练、早停和交叉验证怎么配合早停是我所有 XGBoost 项目的标配没有例外。它的逻辑很简单每隔多少轮看一次验证集指标如果连续若干轮没有提升就停并回滚到最好的那一轮。这样既省时间又避免过拟合。早停的关键参数是rounds也就是耐心值。我的经验是学习率 0.1 时设 20 到 30学习率 0.05 时设 50学习率 0.01 时设 100。设太小会过早停下设太大等于没早停。交叉验证我通常这样组织先用一次简单划分快速试参数再用 5 折交叉验证确认参数是否稳定。原生 API 有xgb.cv可以一次完成cv_result xgb.cv( params, dtrain, num_boost_round1000, nfold5, stratifiedTrue, early_stopping_rounds50, metrics[auc], seed42, ) print(cv_result.tail(3))注意stratifiedTrue对分类任务很重要能让每折的正负比例保持一致尤其是在类别不平衡的时候不加这个参数某一折里正样本可能只有几个指标波动会大到无法解读。还有一个常见困惑用交叉验证得到了最优轮数最终模型该训练多少轮我的做法是取各折最优轮数的平均值稍微放大一点比如乘以 1.1然后在全量训练集上重新训练不再早停。因为全量数据比单折多需要的轮数通常会略多。4.3 调参的顺序和范围别从最后一个参数开始调调参最常见的错误是拿着一张参数表从头调到尾几十次实验下来效果还不如默认参数。原因是参数之间不独立同一组参数在 max_depth 变化后最优值就变了。我的调参顺序是这样的。第一步固定 eta0.1max_depth6其他默认跑一次基线记下验证集 AUC 和最优轮数。第二步调 max_depth 和 min_child_weight用粗网格比如深度在 3、5、7 里选min_child_weight 在 1、3、5 里选。第三步调 subsample 和 colsample_bytree通常 0.7 到 0.9 之间就能找到不错的值。第四步调 gamma 和正则项这时候模型已经比较稳了主要解决的是残余过拟合。最后一步把 eta 降到 0.03 或 0.05重新用早停跑通常还能涨一点。每一轮调参都只动一组参数其他固定这样你才知道收益来自哪里。用随机搜索代替网格搜索能省很多时间RandomizedSearchCV里的n_iter设 30 到 50 次在参数空间不大时基本够用。注意随机搜索也要固定random_state不然两次搜索的采样点不同结果没法比。4.4 特征重要性和模型解释模型训完下一步是看它到底学了什么。sklearn 接口直接取model.feature_importances_原生接口用bst.get_score(importance_typegain)。这里要强调重要性有三种类型weight是特征被用作分裂点的次数gain是分裂带来的平均增益cover是覆盖的样本量。默认是weight但weight对高基数特征有偏袒连续特征被切分的点多次数自然高。我更信gain它衡量的是实际贡献。importance bst.get_score(importance_typegain) for k, v in sorted(importance.items(), keylambda x: -x[1])[:15]: print(f{k}: {v:.2f})如果要对单样本解释就上 SHAP。shap.TreeExplainer对 XGBoost 支持很好能给出每个特征对单个预测的贡献方向和大小。它在业务上特别有用比如给风控模型做拒绝原因说明或者给运营解释为什么这个用户被判定为高流失风险。计算量上全量 SHAP 在几十万行数据上会有点慢可以抽样计算或者只对需要解释的样本算。5. 常见问题与排查技巧实录5.1 训练集效果好、验证集拉胯怎么办这是最典型的问题。看到训练 AUC 0.99、验证 AUC 0.75别急着怀疑数据泄露先按顺序排查。第一看棵树数量。如果没早停n_estimators设了 1000 就真的跑满 1000 棵过拟合几乎是必然的。加早停再看。第二降 max_depth从 8 降到 4 试试深度对过拟合的影响最大。第三加 min_child_weight从 1 加到 10。第四把 subsample 和 colsample_bytree 各自降到 0.7。这四步走完绝大多数过拟合问题都能缓解。反过来说如果训练集和验证集效果都差那就是欠拟合方向要反过来加深度、加轮数、提高学习率或者检查特征是不是漏了关键信息。我遇到过一次模型怎么做都只有 0.65后来发现是日期特征没做任何处理时间信息完全没被利用补上星期、月份、距今天数之后直接到了 0.82。模型调参救不了特征缺失。5.2 类别不平衡和 scale_pos_weight 的正确用法不平衡数据上很多人的第一反应是调scale_pos_weight但效果经常不理想原因是没弄清评估目标。如果你的目标是提升 AUC这个参数帮助有限如果目标是提升召回它就有用。更完整的处理方案是组合拳先看数据不平衡程度比例在 1:10 以内一般不用特殊处理1:100 以上考虑加scale_pos_weight1:1000 以上可能需要在采样层面做处理。同时把评估指标从准确率换成 AUC、PR-AUC 或者 KS。准确率在不平衡数据上没有任何参考价值我见过模型把所有样本预测成负类准确率还能有 95%。一个实用技巧是把scale_pos_weight设成负正样本比跑一版看 PR 曲线上的阈值怎么取。模型输出的概率不是真实概率需要按业务需求选一个阈值而不是默认的 0.5。这个阈值应该在验证集上根据业务成本来选比如漏掉一个高风险用户的代价是误杀一个正常用户的十倍那阈值就要往低处调。5.3 报错和警告速查下面这张表是我和同事踩过的坑基本都是抄网上老教程导致的。报错或警告原因处理方式特征名包含特殊字符报错JSON 格式不接受[、]、等训练前统一重命名特征early_stopping_rounds参数不存在2.0 之后移到了 callbacks改用xgb.callback.EarlyStoppingGPU 相关参数无效使用了旧版gpu_hist写法2.0 后用devicecuda模型加载时报版本不兼容跨版本加载二进制模型统一版本或改用 JSON 格式训练极慢tree_method 用了 exact或线程数设太高换成 hist检查 nthread特征重要性全是 f0、f1DMatrix 没传 feature_names训练时传入特征名列表验证集指标一直不显示verbose_eval 设为 False 或 None设为整数或 True其中特征名那一条特别隐蔽。有些人从数据库导出的列名里带括号或者百分号本地跑得好好的一序列化就报错查半天查不出来其实就是列名的问题。5.4 几个容易被忽略的细节第一单值特征要提前删掉。如果某列在训练集里只有一个取值它对模型没有任何信息量但会占一个特征位还可能影响列采样的随机性。训练前用nunique()扫一遍。第二注意特征的取值范围在训练集和线上是否一致。模型学到的是分裂阈值线上如果某个特征的单位变了比如从“元”变成“万元”整个模型就废了。这种事故我在实际项目里见过一次排查了两天才定位到。第三随机种子要固定包括seed、subsample相关的随机性都会受它影响。做 A/B 对比实验时两组必须用同一个种子否则差异可能只是随机波动。第四别在验证集上反复调参调到验证集过拟合。如果实验次数超过几十次最好留一个单独的测试集只在最后看一次。我一般会切出 20% 做验证再从训练集里切一份很小的测试集最终报告用测试集的数据。6. 工程化落地时的一些经验6.1 模型保存、加载和跨版本问题保存模型别用 pickle这是我最想强调的一条。pickle 序列化的是 Python 对象状态XGBoost 版本一变就可能加载失败而线上环境升级依赖是常事。正确做法是用框架自带的保存接口bst.save_model(model_v1.json) loaded xgb.Booster() loaded.load_model(model_v1.json)sklearn 接口也一样用model.save_model(model.json)和XGBClassifier().load_model(...)。JSON 格式的兼容性好得多而且体积也小。另外要把训练时用的参数一起存下来。我习惯把 params、特征列表、训练时间、数据版本、评估指标写进一个 JSON 文件和模型放一起。半年后需要复现或者排查线上问题时这些东西能救命。光有一个模型文件没有特征顺序和参数等于没有。6.2 和 LightGBM 对拍以及特征管道的一致性上线前我会做一件事用同一份数据、同一组特征分别训一个 XGBoost 和一个 LightGBM看两个模型在验证集上的表现差异和相关性。如果两个模型指标接近说明特征本身有信号结果可信如果差很多说明可能哪里有问题比如某个模型对缺失值处理方式不同导致的差异。这个对拍花不了多少时间但能避免很多误判。特征管道方面最重要的一点是训练和推理必须走同一套代码。我见过太多事故是因为训练时的特征处理写在一个脚本里线上推理时另一个团队重新写了一遍两边对类别编码、缺失填充、特征顺序的处理不一致模型效果直接崩掉。解决办法是把特征处理封装成一个统一的类或者函数训练和推理都调用它中间不经过任何手工复制粘贴。还有一个小技巧给特征加上顺序校验。训练时把特征名列表存下来推理时先检查输入的特征名和顺序是否一致不一致直接报错别让模型默默给出错误结果。这种防御性代码多写十行能省掉线上排查的两个通宵。最后再分享一个小技巧关于学习率的选择。如果你时间紧想快速得到一个不差的模型就用 eta0.1、max_depth5、subsample0.8、colsample_bytree0.8、min_child_weight3、早停 50 轮这套组合在绝大多数表格数据上都能给出一个能用的基线不需要调任何其他参数。等基线跑出来再按我上面说的顺序慢慢优化。我个人在实际操作中的体会是参数调优带来的提升往往在 1 到 3 个百分点之间而特征工程做对了提升可能是十几个百分点所以别把全部精力花在参数表上多回头看数据本身。