简介:这份资源是 Jason Brownlee 所著《XGBoost with Python》英文原版教材的 PDF,面向已具备一定机器学习基础、希望系统掌握梯度提升与 XGBoost 建模的数据分析人员及学生。全书从 Boosting 与 AdaBoost 的由来切入,讲清梯度提升如何通过迭代添加新模型纠正残差,再落到 XGBoost 以决策树为基学习器、依托并行化与正则化兼顾速度与精度的特点,并说明它在机器学习竞赛中表现突出的原因。实践部分以 scikit-learn 接口演示糖尿病发病预测的完整流程,涵盖数据准备、标签编码与缺失值处理、训练集与测试集划分、模型训练与预测、MSE 与 MAE 等评价指标、树深度与学习率调参,以及特征重要性的计算与可视化。压缩包内仅 1 个 PDF 文件,约 1.19MB,章节短小、由浅入深,便于按顺序精读或按主题查阅。目前已有 76 人学习,适合作为梯度提升入门与项目落地的案头参考。
1. 一份 2016 年的 XGBoost with Python 安装说明,照抄为什么必翻车
上周有个开发者拿着这份教材的 PDF 来找我,说照第 4 章的安装小节敲完pip install xgboost,屏幕上全是红字。这不是个例:书成稿时 Python 2.7 还在服役,正文里给的 SciPy 0.17、scikit-learn 0.17.1 那套版本,现在 pip 源上要么找不到对应轮子,要么装上去和系统的 OpenMP 运行库打架。XGBoost with Python 这套环境的安装难点从来不是敲哪条命令,而是版本对齐——Python 大版本、编译器、OpenMP、绘图后端,任意一环错位,import xgboost就直接抛异常。这篇笔记把安装、配置、验证和翻车后的救法按顺序排一遍,适合手里已经有这份教材、想把第 4 章到第 16 章的代码全部跑起来的人。
2. 环境清单与版本选型:Python 3.10 配 xgboost 1.7 这套组合怎么定
原书第 1.2 节给了张需求表,很多人习惯性跳过,直接翻到第 4 章动手。恰恰是这张表决定了后面十几章能不能顺利跑完——书里的代码本身不难,难的是让它在一个依赖关系自洽的环境里执行。我给自己定的基线是 Python 3.10 + xgboost 1.7.6,下面把理由和取舍展开说清楚。
2.1 原书需求表逐行核对:哪些能照抄,哪些必须换
书里写得很明白:Python 2 或 3 都行,作者开发时用的是 2.7.11;SciPy 0.17.0、NumPy 1.11.0、Matplotlib 1.5.1、Pandas 0.18.0、scikit-learn 0.17.1。原文还补了一句「不必严格匹配版本,出问题就升到同版本或更高」。这句话在 2016 年成立,放到现在只能当参考。
| 组件 | 原书标注 | 我实际用的 | 处理方式 |
|---|---|---|---|
| Python | 2.7.11(兼容 3) | 3.10.x | 必须换,2.7 已经停止维护,绝大多数新轮子不再发布 |
| NumPy | 1.11.0 | 1.23.5 | 换,但要和 SciPy 的 ABI 对齐 |
| SciPy | 0.17.0 | 1.9.3 | 换,老版本在新 Python 上没有预编译轮子 |
| Matplotlib | 1.5.1 | 3.7.1 | 换,绘图 API 在这期间改过一轮 |
| Pandas | 0.18.0 | 1.5.3 | 换,2.x 也能用,但书里部分写法在老 API 上更稳 |
| scikit-learn | 0.17.1 | 1.2.2 | 换,cross_validation模块已经移除 |
| XGBoost | 未标注(当时约 0.4) | 1.7.6 | 换,sklearn 包装器的参数名基本没变 |
真正会卡住你的是 scikit-learn 这一行。0.20 版本把sklearn.cross_validation整体挪到了model_selection,书里第 6 章讲 k 折交叉验证那一段如果照抄,会直接抛ImportError。XGBoost 这边反而温和,XGBClassifier和XGBRegressor的max_depth、learning_rate、n_estimators、subsample、colsample_bytree这些参数名从 0.90 到现在就没动过,所以第 13 到 16 章的调参代码几乎可以原样跑。
2.2 隔离方案:venv、conda、容器,选一个就够
不要装到系统 Python 里。XGBoost 依赖 OpenMP 运行库,一旦和系统里其他包的运行库版本冲突,排查起来像个黑匣子。三种隔离方式我都用过,按省事程度排:
# 方案 A:conda,二进制依赖由 conda 统一管,Windows 上最省心 conda create -n xgb-book python=3.10 -y conda activate xgb-book python -V # 期望输出 Python 3.10.x,确认激活生效 which python # 确认指向 envs/xgb-book/bin/python 而不是 /usr/bin/python# 方案 B:venv,系统里已有干净的 Python 3.10 时用 python3.10 -m venv ~/venvs/xgb-book source ~/venvs/xgb-book/bin/activate # Windows 下执行 .\Scripts\activate python -m pip install --upgrade pip# 方案 C:容器,一次镜像反复用,环境最干净 docker run -it --rm -v "$PWD":/work -w /work python:3.10-slim bash-n指定环境名,-y跳过确认;-m venv生成的是独立目录,删掉目录就等于卸干净;容器那条--rm表示退出即销毁,-v把当前目录挂进/work,代码留在宿主机上。我一般走方案 A,原因是 conda 会把 libomp、MKL 这些 C 层依赖一起解决,Windows 上能省掉装编译器和运行库的整轮折腾。
2.3 依赖的安装顺序:为什么 numpy 和 scipy 要先固定版本
一次把所有包装完看着痛快,出问题时却只能看到一串传递依赖的报错,根本定位不到源头。我的习惯是分三批:
# 第一批:升级打包工具链,pip 太老会认不出 manylinux2014 轮子 python -m pip install --upgrade pip setuptools wheel # 第二批:先钉死数值计算底座,这两个包的 ABI 决定了后面所有包能不能跑 python -m pip install "numpy==1.23.5" "scipy==1.9.3" # 第三批:上层库,版本之间要能对上 python -m pip install "scikit-learn==1.2.2" "pandas==1.5.3" "matplotlib==3.7.1" # 第四批:最后装 xgboost,它对 numpy/scipy 有版本下限 python -m pip install "xgboost==1.7.6"第一条命令别省。pip 低于 20 会认不出manylinux2014的轮子标签,现象是「找不到匹配的发行版」,然后 pip 转头去源码编译,编译又失败,报错信息完全指错方向。第二批把 numpy 和 scipy 一起装,是因为 scipy 在编译期就绑定了 numpy 的 C ABI,先装 scipy 后装 numpy 又恰好降级,运行时会看到numpy.dtype size changed这种莫名其妙的错误。xgboost 放最后,纯粹是为了让它的依赖解析走已经装好的版本,不会顺手把你的 numpy 升级掉。
需要走内网或者镜像源时,参数只对当前命令生效,不要写进全局配置:
# -i 只影响这一次安装,换机器时不会因为残留配置拖慢别的包 python -m pip install -i <你的镜像源地址> "xgboost==1.7.6" # 离线场景:先在联网机器把整套轮子下下来 python -m pip download -d ./wheels "numpy==1.23.5" "scipy==1.9.3" \ "scikit-learn==1.2.2" "pandas==1.5.3" "matplotlib==3.7.1" "xgboost==1.7.6" # 拷到目标机器后,完全断网安装 python -m pip install --no-index --find-links=./wheels "xgboost==1.7.6"--no-index是关键,它让 pip 彻底不碰网络索引;--find-links指向本地目录,pip 会在里面按兼容标签挑轮子。内网机器上如果不加--no-index,pip 会先试着联网,超时后再回落本地,一次安装能耗掉好几分钟。
2.4 GPU 版要不要装:书里没写,但这个决定绕不开
书里从第 4 章到第 16 章,全部示例都在 CPU 上跑,第 11 章讲多线程也是 CPU 多核并行,不是 GPU。所以照着教材走,装 CPU 版就够,不需要碰 CUDA。自己后面要上 GPU,就得注意参数体系在版本之间的变化:
import xgboost as xgb print(xgb.__version__) # 1.7.x:GPU 走 tree_method 参数 # model = xgb.XGBRegressor(tree_method="gpu_hist", predictor="gpu_predictor") # 2.x 之后:参数体系调整,改用 device # model = xgb.XGBRegressor(device="cuda")写代码前先打印版本,按大版本决定写哪套参数,别把两套混着抄。我见过有人在 2.x 上写tree_method="gpu_hist",参数被静默忽略,模型老老实实跑在 CPU 上,还以为是自己显卡不行。
3. 装 XGBoost 的三条路径:轮子、conda-forge 与源码编译怎么选
Python 层搞定之后,真正容易翻车的是 xgboost 这个包本身。它是 C++ 写的,Python 包只是一层很薄的壳,壳能 import 成功的前提是底下的动态库能被系统加载器找到。三条安装路径各有适用场景,别一上来就选自虐的那条。
3.1 二进制轮子:三个平台各自的隐性依赖
绝大多数情况走轮子就行,但每个平台都有一两个前置条件:
# Windows:先确认是 64 位解释器,缺 VC++ 运行库会在 import 时报 DLL 错 python -c "import platform; print(platform.architecture())" python -m pip install "xgboost==1.7.6" # macOS:系统不自带 OpenMP,先补上 libomp brew install libomp python -m pip install "xgboost==1.7.6" # Linux:确认 glibc 版本,manylinux2014 的底线是 2.17 ldd --version | head -n 1 python -m pip install "xgboost==1.7.6"Windows 那条platform.architecture()输出必须是64bit。装了 32 位解释器时,pip 会去挑win32标签的轮子,而 xgboost 早就不发 32 位包了,结果是装了个残缺的版本,import 时才炸。macOS 上brew install libomp装完,库文件在/opt/homebrew/opt/libomp/lib(Intel 机器在/usr/local/opt/libomp/lib),可以先看一眼路径,后面排查链接错误时用得上。Linux 的 glibc 检查是给老发行版准备的,CentOS 7 这类系统 glibc 停在 2.17,刚好卡在兼容线上,再老的系统就得考虑容器。
想确认 pip 到底会给你挑哪个轮子,用这行:
# 只打印前 20 行,看 pep425 兼容标签列表里有没有 manylinux2014_x86_64 python -m pip debug --verbose | head -n 20如果列表里只剩linux_x86_64而没有任何manylinux标签,说明 pip 太老或者 Python 是源码编译的,装 xgboost 时必然触发源码构建,那就别怪它编译二十分钟。
3.2 conda-forge 与源码编译:什么时候才值得走
conda 渠道的价值是把 OpenMP、MKL 这些 C 层依赖一起解决,代价是可能和 pip 装的包混在一起打架:
# -c 的顺序决定渠道优先级,conda-forge 放前面才会优先从这里取包 conda install -c conda-forge "xgboost=1.7.6" -y conda list xgboost # 确认来源写着 conda-forge,而不是 defaults混装是这里最容易出的问题。同一个环境里先用 pip 装过 numpy,再用 conda 装 xgboost,conda 会把 numpy 换成自己那份,pip 那边的元数据就对不上了,之后pip check能报出一堆冲突。要么全程 conda,要么全程 pip,别中途换阵营。
源码编译只在两种情况下值得:需要针对特定 CPU 指令集优化,或者要改库本身的代码。命令不算复杂,但要有心理准备:
# --no-binary 只对 xgboost 生效,避免其他包也走源码构建 # --no-cache-dir 防止命中上一次失败留下的坏缓存 python -m pip install "xgboost==1.7.6" --no-binary xgboost --no-cache-dir编译过程要拉 CMake 和 C++ 编译器,内存低于 8G 的机器很容易在链接阶段被 OOM killer 干掉,现象是编译到 60% 左右进程无声无息消失。真要在小内存机器上编译,加MAKEFLAGS="-j2"限制并行度。
3.3 装完立刻做四行自检
装完先别急着跑模型,花三十秒确认壳和库能对上:
# check_env.py —— 一次性把关键包全部 import 一遍 import sys import numpy, scipy, sklearn, pandas, matplotlib import xgboost as xgb print("python ", sys.version.split()[0]) print("numpy ", numpy.__version__, "| scipy", scipy.__version__) print("scikit-learn", sklearn.__version__, "| pandas", pandas.__version__) print("matplotlib ", matplotlib.__version__, "| xgboost", xgb.__version__)先 import 全部再打印,目的是让缺包问题一次性暴露,而不是改一个跑一次。打印版本是为了后面报错时能立刻对上——同一个报错在 1.7 和 2.0 上可能完全不是一回事。
再确认 sklearn 包装器这套接口没变:
from xgboost import XGBRegressor import inspect # 把构造函数的参数全打出来,对照书里第 13 到 16 章用到的名字 print(inspect.signature(XGBRegressor.__init__))输出里能看到max_depth、learning_rate、n_estimators、subsample、colsample_bytree、reg_alpha、reg_lambda,说明书里的调参代码可以直接套。如果这些名字一个都看不到,八成是装了个同名但来历不明的包,先pip show xgboost看看来源。
3.4 把版本锁进文件,给自己留一颗后悔药
环境一旦验证通过,立刻冻结:
python -m pip freeze > requirements.txt这份文件不要用来首次安装,只用来重装和对照。首次安装手动分批装,是为了出问题时能定位;装完再 freeze,是为了换机器时能一分钟重建。我的习惯是在文件开头手写一行注释标上日期和用途,半年后再看还知道这套环境是给哪份教材配的。
4. 配到能跑书里的代码:Jupyter 内核、绘图后端、目录三处
包能 import 了,不代表书里的代码能跑。第 7 章要画决策树、第 8 章要存模型、第 9 章要画特征重要性、第 11 章要压测多线程,每一样都依赖环境里的额外配置。这三处配好,后面基本不用再回头。
4.1 Jupyter 内核注册:激活了环境却还是用旧解释器
最常见的现象是:命令行里conda activate之后一切正常,进了 Jupyter 却提示找不到 xgboost。原因是 notebook 内核指向的是另一个 Python。
# 在当前环境里装内核工具 python -m pip install ipykernel # 把这个解释器注册成一个独立内核 python -m ipykernel install --user --name xgb-book --display-name "Python 3.10 (xgb-book)" # 列出所有内核,确认刚注册的那个在里面 jupyter kernelspec list--user表示装到用户目录,不需要管理员权限;--name是内核的内部标识,命令行里用来指定;--display-name是界面上给人看的名字。两个名字都带上环境名,是为了以后同时开好几个环境时不至于选错。kernelspec list会打印每个内核的kernel.json路径,打开能看到它绑定的 Python 绝对路径,出问题时对着这个文件查最快。
4.2 matplotlib 后端:plt.show()卡死和图不出来的成因
在服务器或者容器里跑第 7 章画树那一段,如果没有图形界面,plt.show()要么卡住要么直接抛no display name and no $DISPLAY environment variable。
import matplotlib matplotlib.use("Agg") # 必须在 import pyplot 之前设置,之后再改无效 import matplotlib.pyplot as plt plt.savefig("figures/tree.png", dpi=150, bbox_inches="tight")Agg是纯文件输出的后端,不依赖任何图形环境。dpi=150是画决策树图比较合适的清晰度,bbox_inches="tight"去掉多余白边,第 7 章那种节点很多的树图不加这个参数会有一大圈空白。
字体这块书里用不上——书上的示例图全是英文标签,默认字体就行。但自己给图加中文标题时会看到方块:
# 只在需要中文标签时设置;axes.unicode_minus 是防止负号显示成方块 plt.rcParams["font.sans-serif"] = ["DejaVu Sans"] plt.rcParams["axes.unicode_minus"] = False4.3 目录约定:书里那套 recipe 代码怎么摆
原书反复建议把每个知识点写成短小的代码配方,攒成自己的配方库。目录结构跟着这个习惯来:
mkdir -p ~/xgb-book/{data,recipes,models,figures} cd ~/xgb-book对应关系很清楚:data放第 5 章数据准备要用的 CSV;models放第 8 章序列化出来的模型文件;figures放第 7 章和第 9 章的树图、特征重要性图;recipes放每个实验的脚本。分开的意义在于,第 8 章讲 pickle 和 joblib 两种序列化方式时,你能一眼看出哪几个文件是哪次实验产出的,不至于把model.pkl和model.joblib混在一起。
再建一个公共模块,把路径和随机种子集中管理:
# recipes/common.py —— 所有实验脚本都从这里导入 import os import random import numpy as np SEED = 7 BASE = os.path.expanduser("~/xgb-book") DATA_DIR = os.path.join(BASE, "data") MODEL_DIR = os.path.join(BASE, "models") FIG_DIR = os.path.join(BASE, "figures") def seed_everything(seed=SEED): """固定 Python 和 numpy 的随机源,保证两次运行结果可对比""" random.seed(seed) np.random.seed(seed)os.path.expanduser把~展开成绝对路径,避免脚本在不同工作目录下执行时找不到文件。seed_everything只固定了 Python 和 numpy 两层,xgboost 自己的随机性由构造参数里的random_state控制,两边都要给同一个值,结果才稳定。
4.4 线程数与随机种子:让两次运行的结果对得上
第 11 章专门讨论多线程,但在那之前,所有实验都得先把线程数固定住,否则调参结果没法比较:
import xgboost as xgb model = xgb.XGBRegressor( n_estimators=100, learning_rate=0.1, max_depth=3, subsample=0.8, colsample_bytree=0.8, random_state=7, # 和 common.py 里的 SEED 保持一致 n_jobs=4, # 显式写死,不要让它自己探测 tree_method="hist", # 比默认的 exact 快,内存占用也低 )n_jobs显式写死这件事在容器里尤其重要:os.cpu_count()读到的是宿主机核数,而容器实际 CPU 配额可能只有两个核,线程开太多会互相抢,训练反而变慢,看着像玄学,其实是调度问题。tree_method="hist"是近似直方图算法,书里默认的exact在数据量小的时候差别不明显,但数据一上规模就能看出差距。这两个参数改动都会影响结果,所以要在实验开始前定好,中途别动。
5. 避坑与排查:五个把我卡住的安装问题
下面五条都是我自己或者帮别人排查时真实遇到过的,按「现象、原因、解决」的顺序记,遇到同款可以照着比对。
5.1 编译与链接类的三个坑
坑一:macOS 上 libomp 找不到
现象是import xgboost抛XGBoostError: XGBoost Library (libxgboost.dylib) could not be loaded,往下翻能看到Library not loaded: @rpath/libomp.dylib。
原因是新版 macOS 不再随系统提供 OpenMP 运行库,而 xgboost 的轮子编译时链接了它。解决办法是先装再验证路径:
brew install libomp brew --prefix libomp # 打印出来的路径下面应该有 lib/libomp.dylib如果装完还是报错,多半是之前用 conda 装过一份 xgboost,两份库在DYLD_LIBRARY_PATH里抢位置,先pip uninstall xgboost清干净再重装。
坑二:Windows 上 DLL load failed
现象是ImportError: DLL load failed while importing core: 找不到指定的模块。
原因通常两个:缺 Visual C++ 2015-2022 运行库,或者 Python 是 32 位的。先查位数:
python -c "import platform; print(platform.architecture())"输出32bit就直接重装 64 位解释器,别在 32 位上浪费时间。输出64bit就去补运行库,装完重启命令行窗口让环境变量生效。
坑三:Linux 上 GLIBCXX 版本不够
现象是ImportError: /lib64/libstdc++.so.6: version 'GLIBCXX_3.4.29' not found。
原因是系统自带的 GCC 太老,libstdc++ 里没有新符号,而 conda 环境里其实带了一份更新的版本,只是加载顺序不对。临时解法:
# 让动态链接器优先用 conda 环境里的 libstdc++ export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$LD_LIBRARY_PATH"这行可以写进环境的激活脚本里。长期解法是升级系统 GCC,但生产机器上动系统库风险不小,能绕就绕。
5.2 运行期的两个坑
坑四:cross_validation模块不存在
现象是ImportError: cannot import name 'cross_validation' from 'sklearn'。
原因是 scikit-learn 0.20 把整个模块改名了,书里第 6 章的示例代码用的还是老名字。改法很直接:
# 老写法(书里第 6 章可能出现) # from sklearn import cross_validation # 新写法 from sklearn.model_selection import KFold, cross_val_score kfold = KFold(n_splits=10, shuffle=True, random_state=7) scores = cross_val_score(model, X, y, cv=kfold, scoring="neg_mean_squared_error")KFold的shuffle=True和random_state是老版本cross_validation.KFold默认没有的,加上之后结果才可复现。
坑五:pickle 反序列化失败
现象是AttributeError: Can't get attribute 'XGBClassifier' on <module 'xgboost' ...>,或者加载后模型行为完全不对。
原因是 pickle 记录的是类的导入路径,跨大版本加载时路径可能变了。书里第 8 章讲 pickle 和 joblib 两种方式,两者都有这个限制。更稳的做法是用 xgboost 自己的格式:
# 存:原生格式带版本信息,跨环境加载更稳 model.save_model("models/model.json") # 读:先构造空模型再加载 from xgboost import XGBRegressor loaded = XGBRegressor() loaded.load_model("models/model.json")如果团队里必须用 pickle,那就把 xgboost 版本写进文件名,比如model-xgb176.pkl,加载前先比对版本,别指望它能跨版本通用。
6. 装完怎么验证跑通:一份冒烟脚本把训练到预测走完
环境装完,最后一步是用一份小脚本把「读数据、训练、预测、评估、存盘、重载」整条链路走一遍。跑通了才算装好,跑不通就说明还有环节没对上。书里第 4 章用的是一份糖尿病相关的 CSV,我平时用 scikit-learn 自带的回归数据做替代,只为验证链路,不追求业务意义:
# smoke_test.py —— 装完环境后的验收脚本 import numpy as np from sklearn.datasets import load_diabetes from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error from xgboost import XGBRegressor # 1) 数据:内置数据集,省掉下载和清洗,专注验证环境 X, y = load_diabetes(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=7 ) # 2) 训练:参数保持和书里第一个模型同一量级 model = XGBRegressor( n_estimators=100, learning_rate=0.1, max_depth=3, subsample=0.8, colsample_bytree=0.8, random_state=7, n_jobs=4, tree_method="hist", ) model.fit(X_train, y_train) # 3) 预测与评估 pred = model.predict(X_test) print("RMSE:", np.sqrt(mean_squared_error(y_test, pred))) # 4) 存盘再加载,验证序列化链路是否完整 model.save_model("models/smoke.json") reload_model = XGBRegressor() reload_model.load_model("models/smoke.json") print("重载后预测一致:", np.allclose(pred, reload_model.predict(X_test)))return_X_y=True直接拿数组,省掉一次 DataFrame 转换;test_size=0.2是常规切分比例,random_state=7和训练参数里的random_state用同一个值,保证切分和模型初始化的随机源一致。最后那句np.allclose是整段里最关键的检查,它验证的不只是模型能不能存,而是存下来再读回来还是同一个模型——这一步不过,第 8 章后面的内容都不用看了。
跑完对照这张表,四项全过才算环境可用:
| 检查项 | 期望结果 | 不符时先查什么 |
|---|---|---|
import xgboost | 无异常,版本 1.7.6 | OpenMP 运行库、VC++ 运行库 |
| RMSE 数值 | 落在几十的量级,不报 NaN | 数据是否被正确标准化、是否有空值 |
| 重载后预测一致 | 输出 True | 存盘和加载是否用了同一个版本 |
| 训练耗时 | 单机几秒内完成 | n_jobs是否开得比实际核数还大 |
还有一个容易被忽略的检查:把n_jobs从 4 改成 1 再跑一次,两次 RMSE 应该在极小误差内一致。不一致说明训练过程里还有没固定住的随机源,后面第 14 到 16 章做参数扫描时,这种抖动会被误判成「参数更好」,白高兴一场。
从那以后我每次装完环境,都先把这份脚本跑一遍,四项全绿才往下写代码。装环境这件事最怕的就是半通不通——import 能过,但画图卡死;训练能跑,但模型存不下来。与其等到第 8 章才发现序列化有问题,不如在第一步就把整条链路验完。希望帮到你。
本文还有配套的精品资源,点击获取