系统设计
核外块运算
在内存不足以容纳全部数据时,算法一次只能处理一部分数据。需要处理一部分之后再读取另一部分,但磁盘读写速度远远小于CPU处理速度
XGBoost的处理:
- 以分块的形式把数据存储在不同磁盘上的数据块内,称为块拆分。存储在不同磁盘上有利于提高磁盘吞吐率。
- 单独开启一个线程用来读取数据,数据处理和数据读取并行进行
- 数据按列进行块压缩,线程在读取时需要先解压
分块并行
构建回归树的过程中,最耗时的是遍历特征时对每个特征值进行排序的操作
XGBoost的处理:
- 先对每个特征对应的特征值进行排序,然后把结果存在一个特定数据结构Block中,Block按列存储,因此每个特征之间互不影响,相互独立
- 除了存储特征值,还要存储索引值,索引值索引了每个特征值指向哪些样本
- 数据以csc格式存储
- block结构可以并行处理,每次计算最优划分节点时只要对block扫描一遍就可以筛选最优gain
- 可以将block分成多个,分布式部署在多个机器上
缓存优化
问题:block数据结构导致缓存命中率低
缓存命中率
缓存命中率(Cache Hit Rate)衡量在处理数据时,所需数据可以直接从缓存中获取的比例。缓存命中率越高,意味着程序运行时需要从主存(RAM)或更慢的存储介质(如硬盘)中读取数据的次数越少,从而可以提高程序的执行效率。
缓存命中率可以通过以下公式计算:
缓存命中率 = 缓存命中次数 总访问次数 缓存命中率=\frac{缓存命中次数}{总访问次数} 缓存命中率=总访问次数缓存命中次数
缓存优化
- 给每一个特征分配一个buffer,存的是样本对应的 g i g_i gi和 h i h_i hi,将非连续的访问变成连续的访问
整体流程
-
加载数据(核外块运算):
- XGBoost使用DMatrix数据结构来存储数据,这种结构优化了数据的存储和访问方式,以便于后续的块运算(block-wise processing)。
- DMatrix支持核外(disk-based)数据操作,这意味着它可以处理存储在磁盘上的数据,而不仅仅是内存中的数据。
-
对每一个特征进行排序(预排序),构建block,分配buffer,进行节点划分:
- XGBoost会对特征值进行排序,这是为了后续的块运算。排序后,数据会被分成多个块(block),每个块包含连续的特征值范围。
- 每个块会被分配到内存中的buffer,以便并行处理。
- 节点划分是指在构建决策树时,XGBoost会根据特征值将数据划分为不同的节点。
-
先确定有没有列采样,然后筛选要用的特征:
- 列采样(column subsampling)是XGBoost的一个特性,它通过随机选择一部分特征来构建每棵树,以增加模型的泛化能力。
- 筛选特征是在构建每棵树之前进行的,以确定哪些特征将被用于分裂节点。
-
看有没有用分位数算法,确定是全局还是局部:
- 分位数算法(Quantile Sketching)是XGBoost用于处理大规模数据集的一种算法,它可以快速地找到近似的分位数。
- 全局分位数是指在整个数据集上计算分位数,而局部分位数是指在每个叶子节点上计算分位数。
-
如果用了分位数算法,要给定超参数:
- 使用分位数算法时,需要设置超参数,如max_bin_by_feature,它控制每个特征的最大桶(bin)数量。
-
根据采集到的特征值进行叶子的统计:
- 在构建树的过程中,XGBoost会统计每个叶子节点上的特征值,以确定最佳的分裂点。
- 这个过程涉及到计算每个可能的分裂点的增益(gain),并选择增益最大的分裂点。
代码实战
python
import xgboost as xgb
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
california_housing = fetch_california_housing(as_frame=True)
X, y = california_housing.data, california_housing.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建XGBoost的DMatrix对象,这是XGBoost数据结构
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
params = {
'objective': 'reg:squarederror', # 回归问题
'max_depth': 6, # 树的最大深度
'eta': 0.3, # 学习率
'subsample': 0.8, # 训练模型时使用样本的比例
'colsample_bytree': 0.8, # 构建树时使用的特征比例
'eval_metric': 'rmse' # 评估指标
}
num_boost_round = 100
bst = xgb.train(params, dtrain, num_boost_round=num_boost_round)
preds = bst.predict(dtest)
rmse = mean_squared_error(y_test, preds, squared=False)
print(f'RMSE: {rmse}')