python_随机森林

前言

提醒:
文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。
其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展及意见建议,欢迎评论区讨论交流。


分类与回归算法

机器学习中的分类与回归算法是两种主要的监督学习任务,它们分别用于解决不同类型的问题。以下是这两种算法的总结:

  1. 分类算法
    分类算法用于将数据分成不同的类别,适用于输出为离散标签的问题。常见的分类算法包括:

    1. 逻辑回归:使用逻辑函数来估计概率,用于二分类问题,也可以扩展到多分类问题。
    2. 支持向量机(SVM):通过找到最优的决策边界来最大化样本的分类准确率,适用于高维数据。
    3. 决策树:通过树结构来进行决策,每个节点代表一个特征的选择,叶子节点代表分类结果。
    4. 随机森林:由多个决策树组成的集成学习方法,通过投票来决定最终分类结果。
    5. 梯度提升决策树(GBDT):通过构建和结合多个弱学习器来形成强学习器,适用于分类和回归问题。
    6. 朴素贝叶斯:基于贝叶斯定理,假设特征之间相互独立,适用于文本分类等场景。
    7. K近邻(KNN):根据样本之间的距离进行分类,适用于小规模数据集。
    8. 神经网络:通过多层感知机学习数据的复杂模式,适用于图像、语音等复杂分类问题。
  2. 回归算法
    回归算法用于预测连续数值输出,适用于输出为连续变量的问题。常见的回归算法包括:

    1. 线性回归:通过拟合一条直线来预测目标变量的值,是最简单的回归方法。
    2. 岭回归:线性回归的扩展,通过引入L2正则化项来防止过拟合。
    3. Lasso回归:线性回归的另一种扩展,通过引入L1正则化项来进行特征选择。
    4. 弹性网回归:结合了岭回归和Lasso回归,同时引入L1和L2正则化项。
    5. 决策树回归:使用决策树结构来进行回归预测,适用于非线性关系。
    6. 随机森林回归:由多个决策树组成的集成学习方法,通过平均来决定最终回归结果。
    7. 梯度提升决策树回归(GBDT回归):通过构建和结合多个弱学习器来形成强学习器,适用于回归问题。
    8. 支持向量回归(SVR):支持向量机在回归问题上的应用,通过找到最优的决策边界来最大化样本的回归准确率。
    9. 神经网络回归:通过多层感知机学习数据的复杂模式,适用于复杂的回归问题。
  3. 分类与回归算法的比较

    • 输出类型:分类算法输出离散标签,回归算法输出连续数值。
    • 评估指标:分类算法常用准确率、召回率、F1分数等指标,回归算法常用均方误差(MSE)、均方根误差(RMSE)等指标。
    • 问题类型:分类算法适用于类别预测问题,如垃圾邮件检测;回归算法适用于数值预测问题,如房价预测。 在实际应用中,选择分类还是回归算法取决于问题的性质和需求。有时,可以将回归问题转化为分类问题,或者将分类问题转化为回归问题,具体取决于问题的特点和目标。

随机森林

数学原理

以下是分类算法随机森林的原理及其数学公式的详细介绍:


1. 单棵分类决策树的构建

分类树通过递归划分特征空间,将数据分配到不同的叶子节点,每个叶子节点输出目标类别的概率或多数类标签。关键步骤如下:

分裂准则

分类树通常使用**基尼不纯度(Gini Impurity)信息增益(Information Gain)**作为分裂准则。

  • 基尼不纯度
    对于数据集 D D D,在特征 j j j 和分裂点 s s s 处,计算分裂后的基尼不纯度:
    G ( j , s ) = ∑ k = 1 K ∣ D L , k ∣ ∣ D L ∣ ( 1 − ∣ D L , k ∣ ∣ D L ∣ ) + ∑ k = 1 K ∣ D R , k ∣ ∣ D R ∣ ( 1 − ∣ D R , k ∣ ∣ D R ∣ ) G(j, s) = \sum_{k=1}^K \frac{|D_{L,k}|}{|D_L|} \left(1 - \frac{|D_{L,k}|}{|D_L|}\right) + \sum_{k=1}^K \frac{|D_{R,k}|}{|D_R|} \left(1 - \frac{|D_{R,k}|}{|D_R|}\right) G(j,s)=k=1KDLDL,k(1DLDL,k)+k=1KDRDR,k(1DRDR,k)
    其中:

    • D L D_L DL D R D_R DR 是分裂后的左右子集,
    • ∣ D L , k ∣ |D_{L,k}| DL,k 表示左子集中第 k k k 类样本的数量,
    • K K K 是类别总数。
  • 信息增益
    基于熵(Entropy)的减少:
    IG ( j , s ) = H ( D ) − ( ∣ D L ∣ ∣ D ∣ H ( D L ) + ∣ D R ∣ ∣ D ∣ H ( D R ) ) \text{IG}(j, s) = H(D) - \left( \frac{|D_L|}{|D|} H(D_L) + \frac{|D_R|}{|D|} H(D_R) \right) IG(j,s)=H(D)(DDLH(DL)+DDRH(DR))
    其中,熵 H ( D ) H(D) H(D) 的计算为:
    H ( D ) = − ∑ k = 1 K ∣ D k ∣ ∣ D ∣ log ⁡ 2 ∣ D k ∣ ∣ D ∣ H(D) = -\sum_{k=1}^K \frac{|D_k|}{|D|} \log_2 \frac{|D_k|}{|D|} H(D)=k=1KDDklog2DDk

选择最优分裂:遍历候选特征和分裂点,选择使基尼不纯度最小化或信息增益最大化的组合 ( j ∗ , s ∗ ) (j^*, s^*) (j,s)


2. 随机森林的随机性

随机森林通过以下两种方式引入随机性,提升模型的多样性和泛化能力:

(1) Bootstrap采样(行采样)

每棵树从原始数据集 D D D 中有放回地抽取 n n n 个样本,生成新数据集 D t D_t Dt。未被抽中的样本称为袋外数据(OOB),占比约 1 e ≈ 36.8 % \frac{1}{e} \approx 36.8\% e136.8%

(2) 特征子集随机选择(列采样)

每次分裂时,从全部 p p p 个特征中随机选取 m m m 个候选特征(通常 m = ⌊ p ⌋ m = \lfloor \sqrt{p} \rfloor m=p m = log ⁡ 2 p m = \log_2 p m=log2p)。


3. 随机森林的预测

假设随机森林包含 T T T 棵分类树,对于输入样本 x x x,每棵树输出类别标签 h t ( x ) h_t(x) ht(x)。最终预测通过多数投票确定:
H ( x ) = mode ( { h 1 ( x ) , h 2 ( x ) , … , h T ( x ) } ) H(x) = \text{mode}\left( \{ h_1(x), h_2(x), \dots, h_T(x) \} \right) H(x)=mode({h1(x),h2(x),,hT(x)})
或基于概率平均:
P ( y = k ∣ x ) = 1 T ∑ t = 1 T P t ( y = k ∣ x ) P(y=k | x) = \frac{1}{T} \sum_{t=1}^T P_t(y=k | x) P(y=kx)=T1t=1TPt(y=kx)
其中 P t ( y = k ∣ x ) P_t(y=k | x) Pt(y=kx) 是第 t t t 棵树预测样本 x x x 属于类别 k k k 的概率。


4. 数学性质与优势

  • 偏差-方差权衡:单棵决策树易过拟合(高方差),随机森林通过平均多棵树降低方差。
  • 袋外误差(OOB Error):无需交叉验证,直接利用袋外数据评估模型性能:
    OOB Error = 1 ∣ D OOB ∣ ∑ i ∈ D OOB I ( H ( x i ) ≠ y i ) \text{OOB Error} = \frac{1}{|D_{\text{OOB}}|} \sum_{i \in D_{\text{OOB}}} \mathbb{I}(H(x_i) \neq y_i) OOB Error=DOOB1iDOOBI(H(xi)=yi)
    其中 I ( ⋅ ) \mathbb{I}(\cdot) I() 是指示函数。
  • 特征重要性:通过计算特征在分裂时的平均不纯度下降来评估重要性。

python函数库实现

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data  # 特征矩阵 (150个样本,4个特征)
y = iris.target  # 目标标签 (3个类别)

# 划分数据集:80%训练集,20%测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 初始化随机森林分类器
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)

# 训练模型
rf_model.fit(X_train, y_train)

# 对测试集进行预测
y_pred = rf_model.predict(X_test)

# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy:.2f}")

# 打印分类报告
print("分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

运行结果

在这里插入图片描述

手动实现

import numpy as np
from collections import Counter
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 定义决策树节点类
class DecisionTreeNode:
    def __init__(self, gini, num_samples, num_samples_per_class, predicted_class):
        # 当前节点的基尼不纯度,用于衡量节点的纯度,值越小表示纯度越高
        self.gini = gini  
        # 当前节点包含的样本数量
        self.num_samples = num_samples  
        # 每个类别的样本数量,存储形式为列表,索引对应类别
        self.num_samples_per_class = num_samples_per_class  
        # 预测的类别,通常是样本数量最多的类别
        self.predicted_class = predicted_class  
        # 用于分割的特征索引,确定使用哪个特征进行数据分割
        self.feature_index = 0  
        # 分割的阈值,特征值与该阈值比较来决定样本划分到左子树还是右子树
        self.threshold = 0  
        # 左子树,存储特征值小于等于阈值的样本对应的子节点
        self.left = None  
        # 右子树,存储特征值大于阈值的样本对应的子节点
        self.right = None  

# 定义决策树类
class DecisionTree:
    def __init__(self, max_depth=None):
        # 决策树的最大深度,用于控制树的生长,避免过拟合
        self.max_depth = max_depth  

    def _gini(self, y):
        """计算给定数据集的基尼不纯度"""
        # 数据集的样本数量
        m = len(y)
        # 基尼不纯度的计算公式:1 - 每个类别样本比例的平方和
        return 1.0 - sum((np.sum(y == c) / m) ** 2 for c in np.unique(y))

    def _split(self, X, y, index, threshold):
        """根据特征索引和阈值将数据集分割为左右两部分"""
        # 生成一个布尔掩码,标记特征值小于等于阈值的样本
        left_mask = X[:, index] <= threshold
        # 生成一个布尔掩码,标记特征值大于阈值的样本
        right_mask = X[:, index] > threshold
        # 根据掩码分割特征矩阵和标签向量,返回左右两部分的特征矩阵和标签向量
        return X[left_mask], X[right_mask], y[left_mask], y[right_mask]

    def _best_split(self, X, y):
        """寻找最佳分割点,返回最佳特征索引和阈值"""
        # 样本数量和特征数量
        m, n = X.shape
        # 如果样本数量小于等于 1,无法进行分割,返回 None
        if m <= 1:
            return None, None

        # 计算父节点每个类别的样本数量
        num_parent = [np.sum(y == c) for c in np.unique(y)]
        # 计算父节点的基尼不纯度
        best_gini = 1.0 - sum((num / m) ** 2 for num in num_parent)
        # 初始化最佳特征索引和阈值为 None
        best_index, best_threshold = None, None

        # 遍历每个特征
        for index in range(n):
            # 将特征值和对应的类别按特征值排序
            thresholds, classes = zip(*sorted(zip(X[:, index], y)))
            # 遍历排序后的样本
            for i in range(1, m):
                # 如果相邻样本的类别不同
                if classes[i - 1] != classes[i]:
                    # 计算分割阈值,取相邻特征值的平均值
                    threshold = (thresholds[i - 1] + thresholds[i]) / 2
                    # 根据阈值分割标签向量为左右两部分
                    left_y, right_y = y[X[:, index] <= threshold], y[X[:, index] > threshold]
                    # 计算分割后的基尼不纯度,使用加权平均
                    gini = (len(left_y) / m) * self._gini(left_y) + (len(right_y) / m) * self._gini(right_y)
                    # 如果分割后的基尼不纯度小于当前最佳基尼不纯度
                    if gini < best_gini:
                        # 更新最佳基尼不纯度、最佳特征索引和最佳阈值
                        best_gini, best_index, best_threshold = gini, index, threshold

        return best_index, best_threshold

    def _build_tree(self, X, y, depth=0):
        """递归构建决策树"""
        # 计算每个类别的样本数量
        num_samples_per_class = [np.sum(y == i) for i in np.unique(y)]
        # 预测的类别,即样本数量最多的类别
        predicted_class = np.argmax(num_samples_per_class)
        # 创建当前节点
        node = DecisionTreeNode(
            gini=self._gini(y),
            num_samples=len(y),
            num_samples_per_class=num_samples_per_class,
            predicted_class=predicted_class,
        )

        # 如果当前深度小于最大深度
        if depth < self.max_depth:
            # 寻找最佳分割点
            index, threshold = self._best_split(X, y)
            # 如果存在最佳分割点
            if index is not None:
                # 根据最佳分割点分割数据集
                X_left, X_right, y_left, y_right = self._split(X, y, index, threshold)
                # 记录最佳特征索引
                node.feature_index = index
                # 记录最佳分割阈值
                node.threshold = threshold
                # 递归构建左子树
                node.left = self._build_tree(X_left, y_left, depth + 1)
                # 递归构建右子树
                node.right = self._build_tree(X_right, y_right, depth + 1)
        return node

    def fit(self, X, y):
        """训练决策树"""
        # 调用 _build_tree 方法构建决策树,并将根节点存储在 tree_ 属性中
        self.tree_ = self._build_tree(X, y)

    def _predict_sample(self, x, node):
        """预测单个样本"""
        # 如果当前节点是叶子节点(没有左子树)
        if node.left is None:
            return node.predicted_class
        # 如果样本的特征值小于等于分割阈值
        if x[node.feature_index] <= node.threshold:
            # 递归预测左子树
            return self._predict_sample(x, node.left)
        else:
            # 递归预测右子树
            return self._predict_sample(x, node.right)

    def predict(self, X):
        """预测数据集"""
        # 对数据集中的每个样本调用 _predict_sample 方法进行预测
        return [self._predict_sample(x, self.tree_) for x in X]

# 定义随机森林类
class RandomForest:
    def __init__(self, n_trees=10, max_depth=None, max_features=None):
        # 决策树的数量,即随机森林中包含的决策树的棵数
        self.n_trees = n_trees  
        # 决策树的最大深度,用于控制每棵决策树的生长
        self.max_depth = max_depth  
        # 每棵树考虑的最大特征数,用于引入随机性
        self.max_features = max_features  
        # 存储随机森林中的所有决策树
        self.trees = []  

    def fit(self, X, y):
        """训练随机森林"""
        for _ in range(self.n_trees):
            # 有放回地随机抽样,生成自助样本集
            indices = np.random.choice(len(X), size=len(X), replace=True)
            X_bootstrap = X[indices]
            y_bootstrap = y[indices]
            # 创建决策树
            tree = DecisionTree(max_depth=self.max_depth)
            # 训练决策树
            tree.fit(X_bootstrap, y_bootstrap)
            # 将训练好的决策树添加到随机森林中
            self.trees.append(tree)

    def predict(self, X):
        """预测数据集"""
        predictions = []
        for tree in self.trees:
            # 每棵树对数据集进行预测
            predictions.append(tree.predict(X))
        # 对每一个样本的所有树的预测结果进行多数投票
        final_predictions = []
        for i in range(len(X)):
            sample_predictions = [pred[i] for pred in predictions]
            final_predictions.append(Counter(sample_predictions).most_common(1)[0][0])
        return final_predictions

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 划分训练集和测试集,测试集占比 20%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建随机森林模型,包含 10 棵决策树,最大深度为 3
rf = RandomForest(n_trees=10, max_depth=3)
# 训练随机森林模型
rf.fit(X_train, y_train)
# 在测试集上进行预测
y_pred = rf.predict(X_test)

# 计算预测准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"预测准确率: {accuracy}")

运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值