前言
提醒:
文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。
其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展及意见建议,欢迎评论区讨论交流。
文章目录
分类与回归算法
机器学习中的分类与回归算法是两种主要的监督学习任务,它们分别用于解决不同类型的问题。以下是这两种算法的总结:
分类算法:
分类算法用于将数据分成不同的类别,适用于输出为离散标签的问题。常见的分类算法包括:
- 逻辑回归:使用逻辑函数来估计概率,用于二分类问题,也可以扩展到多分类问题。
- 支持向量机(SVM):通过找到最优的决策边界来最大化样本的分类准确率,适用于高维数据。
- 决策树:通过树结构来进行决策,每个节点代表一个特征的选择,叶子节点代表分类结果。
- 随机森林:由多个决策树组成的集成学习方法,通过投票来决定最终分类结果。
- 梯度提升决策树(GBDT):通过构建和结合多个弱学习器来形成强学习器,适用于分类和回归问题。
- 朴素贝叶斯:基于贝叶斯定理,假设特征之间相互独立,适用于文本分类等场景。
- K近邻(KNN):根据样本之间的距离进行分类,适用于小规模数据集。
- 神经网络:通过多层感知机学习数据的复杂模式,适用于图像、语音等复杂分类问题。
回归算法:
回归算法用于预测连续数值输出,适用于输出为连续变量的问题。常见的回归算法包括:
- 线性回归:通过拟合一条直线来预测目标变量的值,是最简单的回归方法。
- 岭回归:线性回归的扩展,通过引入L2正则化项来防止过拟合。
- Lasso回归:线性回归的另一种扩展,通过引入L1正则化项来进行特征选择。
- 弹性网回归:结合了岭回归和Lasso回归,同时引入L1和L2正则化项。
- 决策树回归:使用决策树结构来进行回归预测,适用于非线性关系。
- 随机森林回归:由多个决策树组成的集成学习方法,通过平均来决定最终回归结果。
- 梯度提升决策树回归(GBDT回归):通过构建和结合多个弱学习器来形成强学习器,适用于回归问题。
- 支持向量回归(SVR):支持向量机在回归问题上的应用,通过找到最优的决策边界来最大化样本的回归准确率。
- 神经网络回归:通过多层感知机学习数据的复杂模式,适用于复杂的回归问题。
分类与回归算法的比较:
- 输出类型:分类算法输出离散标签,回归算法输出连续数值。
- 评估指标:分类算法常用准确率、召回率、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=1∑K∣DL∣∣DL,k∣(1−∣DL∣∣DL,k∣)+k=1∑K∣DR∣∣DR,k∣(1−∣DR∣∣DR,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)−(∣D∣∣DL∣H(DL)+∣D∣∣DR∣H(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=1∑K∣D∣∣Dk∣log2∣D∣∣Dk∣
选择最优分裂:遍历候选特征和分裂点,选择使基尼不纯度最小化或信息增益最大化的组合 ( 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\% e1≈36.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=k∣x)=T1t=1∑TPt(y=k∣x)
其中
P
t
(
y
=
k
∣
x
)
P_t(y=k | x)
Pt(y=k∣x) 是第
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=∣DOOB∣1i∈DOOB∑I(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}")