梯度提升Gradient Boosting

总览

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 算法流程:

  1. 初始化一个常数模型
  2. 拟合模型的负梯度,获得一个弱模型
  3. 用弱模型更新模型
  4. 回到 step.2

实现

以下一个示例,从输入 x 预测 sigmamu

可见使用到了 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

参考来源

posted @ 2024-05-12 20:09  倒地  阅读(25)  评论(0编辑  收藏  举报