总览
Gradient Boosting 梯度提升,是一种强大的 ML 技术,用于回归和分类问题。
弱学习算法通常更易发现、更易训练。Boosting 系列算法的基本思想是将弱基础模型组合为一个强大的集成。
Gradient Boosting 的基本思想是:不断重复生成弱学习器,每次生成弱学习器的目标是拟合先前累加模型的损失函数的负梯度, 使组合上该弱学习器后的累积模型 损失往负梯度的方向减少。
Gradient Boosting 概述
约定以下符号:
- 输入 \(x\) 输出 \(y\)
- 模型 \(F_m(x)\)
- 弱模型 \(f_m(x)\)
- 损失函数 \(L(\hat{F}_m(x),y)\)
由于 Gradient Boosting 会依次训练出 \(m\) 个模型,所以用下标 \(m\) 区分它们。
在 Gradient Boosting 中,我们对 损失函数相对于模型输出的导数 感兴趣:
\[g(x)=\frac{\partial L(\hat{F}(x),y)}{\partial \hat{F}(x)} \]为了训练出下一个弱模型 \(f_m(x)\),需要拟合模型 \(\hat{F}_{m-1}(x)\) 输出的负梯度:
\[\hat{f}_m= \underset{f}{\text{arg min}}\frac{1}{N}\sum^N_{i=1} (g(x)-f(x_i))^2 \]很容易令人联想到均方误差 \(L(x,y)=\frac{1}{n}\sum(x_i-y_i)^2\)。可以说,获得弱模型就是使用 MSE 损失函数拟合负梯度 \(-g(x)\)。
借助弱模型 \(f_m(x)\) 更新 \(\hat{F}_{m-1}(x)\),获得下一个模型 \(\hat{F}_m(x)\)。其中 \(\gamma\) 是学习率(一般在 0.01 或 0.001):
\[\hat{F}_m(x)=\hat{F}_{m-1}(x)+\gamma\hat{f}_m(x) \]如此,一直重复获得 \(f_m(x)\) 和更新 \(F_{m-1}(x)\) 的步骤,便可逐渐获得强大模型。
第一个弱模型
第一个弱模型通常只是一个常数 \(c\)。找到一个使得损失 \(L\) 最小的常数:
\[\hat{f}_0= \underset{c}{\text{arg min}}\frac{1}{N}\sum^N_{i=1} L(c, y_i) \]对于 MSE 损失,这个 \(c\) 就是 \(y\) 的均值。
Gradient Boosting 的步骤
有了以上的认识,应当能理解 Gradient Boosting 算法流程:
- 初始化一个常数模型
- 拟合模型的负梯度,获得一个弱模型
- 用弱模型更新模型
- 回到 step.2
实现
以下一个示例,从输入 x
预测 sigma
和 mu
。
可见使用到了 DecisionTreeRegressor
。这个学习器易训练,很适合堆叠。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
from sklearn.linear_model import LinearRegression
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
import torch
from torch.distributions.normal import Normal
from torch.autograd import Variable
from typing import List, Optional
class GaussianGradientBoosting:
def __init__(self,
learning_rate: float = 0.025,
max_depth: int = 1,
n_estimators: int =100):
self.learning_rate: float = learning_rate
self.max_depth: int = max_depth
self.n_estimators: int = n_estimators
self.init_mu: Optional[float] = None
self.mu_trees: List[DecisionTreeRegressor] = []
self.init_sigma: Optional[float] = None
self.sigma_trees: List[DecisionTreeRegressor] = []
self.is_trained: bool = False
def predict(self, X: np.array) -> np.array:
assert self.is_trained
mus = self._predict_mus(X).reshape(-1,1)
log_sigmas = np.exp(self._predict_log_sigmas(X).reshape(-1,1))
return np.concatenate([mus, log_sigmas], 1)
def _predict_raw(self, X: np.array) -> np.array:
assert self.is_trained
mus = self._predict_mus(X).reshape(-1,1)
log_sigmas = self._predict_log_sigmas(X).reshape(-1,1)
return np.concatenate([mus, log_sigmas], 1)
def fit(self, X: np.array, y: np.array) -> None:
self._fit_initial(y)
self.is_trained = True
for _ in range(self.n_estimators):
y_pred = self._predict_raw(X)
gradients = self._get_gradients(y, y_pred)
mu_tree = DecisionTreeRegressor(max_depth=self.max_depth)
mu_tree.fit(X, gradients[:,0])
self.mu_trees.append(mu_tree)
sigma_tree = DecisionTreeRegressor(max_depth=self.max_depth)
sigma_tree.fit(X, gradients[:,1])
self.sigma_trees.append(sigma_tree)
def _fit_initial(self, y: np.array) -> None:
assert not self.is_trained
self.init_mu = np.mean(y)
self.init_sigma = np.log(np.std(y))
def _get_gradients(self, y: np.array, y_pred: np.array) -> np.array:
y_torch = torch.tensor(y).float()
y_pred_torch = Variable(torch.tensor(y_pred).float(), requires_grad=True)
normal_dist = Normal(y_pred_torch[:,0], torch.exp(y_pred_torch[:,1])).log_prob(y_torch).sum()
normal_dist.backward()
return y_pred_torch.grad.numpy()
def _predict_mus(self, X: np.array) -> np.array:
output = np.zeros(len(X))
output += self.init_mu
for tree in self.mu_trees:
output += self.learning_rate * tree.predict(X)
return output
def _predict_log_sigmas(self, X: np.array) -> np.array:
output = np.zeros(len(X))
output += self.init_sigma
for tree in self.sigma_trees:
output += self.learning_rate * tree.predict(X)
return output
参考来源
- 周永_52ML,“第06章:深入浅出ML之Boosting家族”,http://www.52caml.com/head_first_ml/ml-chapter6-boosting-family/
- sarem-seitz,“With PyTorch, I can Gradient Boost anything”,https://sarem-seitz.com/posts/with-pytorch-i-can-gradient-boost-anything/
- 王桂波“Gradient Boosting 原理、推导及代码实现”,https://zhuanlan.zhihu.com/p/64863699