作者:禅与计算机程序设计艺术
1.简介
随着数据的增多、计算能力的提升以及传感器技术的发展,机器学习已成为一个越来越重要的领域。在许多情况下,如何利用数据提高预测模型的准确性一直是一个难题。为了解决这一难题,机器学习研究者们提出了许多方法,如集成学习(Ensemble learning)、决策树学习、贝叶斯网络等,这些方法通过结合多个学习器(通常采用不同的算法或模型)来改善预测性能。
在本文中,我们将讨论Scikit-learn中的集成学习方法。Ensemble learning 是一种机器学习方法,它通过结合多个学习器来降低过拟合和提升泛化能力。它可以被应用于分类问题和回归问题上,也可以用于处理多标签分类问题。本文中,我们将展示如何使用Scikit-learn中的集成学习方法来建立分类和回归模型。
2.基本概念术语说明
2.1 概念和术语
集成学习(Ensemble Learning),也叫模型融合或学习联合,是指使用集合或群集的方法对多个学习器进行学习和组合,从而获得比单独使用单个学习器更好的预测性能。它通过合并多个模型或预测器的结果,产生一个更加健壮、稳定的、更具预测力的整体模型。其目的就是减少过拟合、提高预测精度和避免遗漏。
集成学习一般分为两类:
1.bagging(bootstrap aggregating): 在训练过程中,每个基学习器仅在原始样本集的一定子集上进行训练,训练得到的子模型对所有原始样本都有效。最终的预测由这几个子模型的结合决定。 bagging 方法常用的工具包是随机森林(Random Forest)。 2.boosting(提升法): Boosting 主要关注的是每一步迭代对性能的影响,并试图找到最佳的弱分类器组合,使之能够组合起来形成强大的分类器。Boosting 方法常用的工具包是 AdaBoost 和 GBDT。
2.2 数据集划分
我们用一个例子来阐述集成学习的概念和流程。假设有一个偷鸡的游戏场景,目标是从中窃取掉一些东西。在这个游戏里,有三个人玩,每人各有两个选择:要么把钱给对方,要么把鸡喂给对方。如果游戏规则按1:1比例分配,则每个人的钱总量和鸡的数量都相同;但实际情况可能不是这样。因此,我们需要一个集成学习方法来综合考虑不同人的能力和信息,对游戏进行优化。
那么,应该怎么做呢?首先,我们需要准备好数据。每个人都需要提供自己所拥有的钱和鸡的数量,以及对方的信息。比如,第一人提供了 $2$ 个钱和 $3$ 个鸡,以及其他两人的信息;第二人提供了 $1$ 个钱和 $2$ 个鸡,以及其他两人的信息;第三人提供了 $0$ 个钱和 $1$ 个鸡,以及其他两人的信息。
然后,我们需要按照某个策略来分配鸡。例如,我们可以考虑到两个人的能力差异,以及他们所处位置的不同,人工智能系统可以帮助我们分配鸡。分配策略可以是根据鸡的大小和质量来分配;也可以根据对方的赌注来分配。
最后,我们训练一个集成学习模型,该模型需要同时考虑不同人的信息和分配策略。由于集成学习的特性,模型将结合不同人的预测,提高预测准确率。
2.3 监督学习
集成学习只适用于监督学习的问题,包括分类、回归和多标签分类问题。对于二元分类问题,如二分类问题,集成学习通过结合多个学习器的预测结果,得到最终的分类结果。常见的二分类集成学习方法有AdaBoost和GBDT,对于多分类问题,集成学习通过结合多个学习器的预测结果,得到最终的分类结果。常见的多分类集成学习方法有Bagging、Pasting、Random Forest、Majority Vote等。
在回归问题上,集成学习通过结合多个学习器的预测结果,得到最终的预测值。常见的回归集成学习方法有平均法、投票法和梯度 boosting。
对于多标签分类问题,即样本有多个类别标记时,集成学习可以将多个学习器的预测结果结合起来,达到更好的效果。目前,Scikit-learn支持多标签分类问题的集成学习方法有 Label Powerset、Multi-Label Bagging 和 Multi-Class One-Vs-Rest (OvR)。
2.4 集成学习的分类
集成学习可以归纳为以下四种类型:
1.bagging: 将训练集随机采样多次,用同样的训练集训练多个学习器,最后将它们的预测结果进行聚合。bagging 的目的是降低模型的方差,提高模型的准确性。Scikit-learn 中 RandomForestClassifier 和 RandomForestRegressor 是 bagging 的典型实现。
2.boosting: 通过串行地训练基学习器,并对每个基学习器赋予一定的权重,来调整它们的预测结果。boosting 的目的是提高模型的偏差,防止过拟合。Scikit-learn 中 AdaBoostClassifier 和 AdaBoostRegressor 是 boosting 的典型实现。
3.stacking: 将多个学习器的输出作为输入,训练一个新的学习器,然后再用该学习器对原先的学习器的输出进行预测。stacking 一般用于解决多学习器之间存在互相依赖的问题。Scikit-learn 中 StackingCV 和 VotingClassifier 都是 stacking 的实现方式。
4.blending: 使用简单的平均或加权平均来合并多个学习器的预测结果。blending 一般用于解决不同类型的学习器之间的性能比较问题。Scikit-learn 中 SimpleImputer、VotingRegressor 和 WeightedAverageizer 是 blending 的实现方式。
3.核心算法原理和具体操作步骤以及数学公式讲解
3.1 bagging
bagging(bootstrap aggregating)是集成学习的一种方法,用于生成多个学习器的集成。其基本思路是通过重复随机抽样的训练集,训练多个基学习器,最终将它们的预测结果进行聚合。bagging 的优点是降低了模型的方差,避免了过拟合,从而取得了较好的预测效果。
3.1.1 bootstrap方法
bootstrap方法是一种统计方法,它通过重复采样的方式,获取样本的统计规律,包括均值、标准差等参数。基于bootstrap方法,bagging方法构建了多个模型,每个模型基于随机抽样的数据集。具体过程如下:
输入:训练集 X、Y,其中X为样本特征矩阵,Y为样本输出向量。
每次从训练集X中抽取m个样本,组成一个新的训练集。
用步骤2中的抽样数据集训练出一个基学习器。
对每个基学习器进行投票,构造新的数据集,其每个样本的类别等于该基学习器投票得出的类别。
对第i个基学习器,计算其在步骤4中的投票结果的概率分布pi_i。
根据步骤5中的概率分布,确定每个样本属于各类的概率分布p_ik。
结合各样本的类别分布概率,得到最终预测结果。
3.1.2 bagging算法实现
Scikit-learn中的随机森林分类器(RandomForestClassifier)是bagging算法的一种实现,它通过构建一系列的决策树来降低过拟合现象。如下所示,在随机森林中,每棵树都由一组随机样本索引集构成,并且只考虑这些索引对应的样本。在训练时,树是独立生成的,也就是说,它们没有依赖于其他树的结果。
3.1.3 为何要使用随机森林?
随机森林(Random Forest)是一种基于树的分类方法,能够自动发现多个相关特征,并进行排序。它的基本思想是采用决策树来进行多次随机采样,并将各个随机采样的决策树结果进行综合。通过多棵树的集成,可以提高模型的鲁棒性、适应性以及抗噪声能力。
降低方差:由于决策树是非parametric的,所以它们的方差比较大,并且容易过拟合。但是随机森林采用多棵树的集成方式,通过降低方差和加权,来减少模型的过拟合风险。
提高预测能力:由于随机森林采用多棵树的集成方式,所以各棵树之间会互相影响,并且树的结果往往是不相关的。通过集成多个决策树的结果,可以提高预测的准确性。
减少过拟合:由于随机森林采用多棵树的集成方式,所以它可以很好地克服过拟合问题,从而使得模型在测试数据上的预测效果更加可信。
3.2 Adaboost算法
Adaboost(Adaptive Boosting)是一种迭代的boosting算法,它采用一种自适应的方式来选择下一个基学习器。在每一轮迭代中,Adaboost都会为当前训练集学习一个基学习器,并计算当前基学习器的错误率。然后,它会根据错误率来更新样本权重,使得那些被错误分类样本的权重增大,而那些被正确分类样本的权重减小。接着,Adaboost会使用一个残差的近似函数来更新样本权重,使得前面一轮的基学习器的错误率逐渐减小。基于这个思路,Adaboost不断重复这个过程,直到收敛或者达到指定的最大迭代次数。Adaboost算法主要用于二分类问题。
3.2.1 AdaBoost算法实现
Scikit-learn中的Adaboost分类器(AdaBoostClassifier)是Adaboost算法的一种实现,它通过改变样本权重来选择基学习器。如下所示,在Adaboost中,每一次迭代都会产生一个基学习器,并且会使用之前基学习器的错误率来更新样本权重。
3.2.2 SAMME和SAMME.R算法
Scikit-learn中的Adaboost分类器(AdaBoostClassifier)采用两种不同的算法,即SAMME和SAMME.R。SAMME算法用于二分类任务,它通过计算正负样本的概率,来计算错误率,并更新样本权重。SAMME.R算法用于多分类任务,它同样会计算每一类的概率,不过会考虑所有类的概率之和,从而计算出错分类的概率,并更新样本权重。
SAMME.R算法的公式如下所示:
$$ \begin{aligned} &G_m(x)=\arg \min h\left[ \sum{i=1}^{K}\alpha_k \exp (-f_k(x))+\frac {1-K}{\text { prior }}\right] \ &\text { where } f_k=\frac {\log (\frac {P(y=k|x,\theta_k)}{1-\hat{\pi}_k})} {2 \sigma^2(\theta)} \end{aligned} $$
SAMME算法的公式如下所示:
$$ \begin{aligned} &G_m(x)=\arg \min h\left[ \sum{i=1}^{K}\alpha_k \exp (-f_k(x))+\frac {1-K}{\text { prior }}\right]\ &\text { where } f_k=-\frac {1}{2}\log ((1-\hat{\pi}k)/\hat{\pi}_k)+\gamma \cdot T(h(x), y)\text {(weighted classification error)}, \quad \gamma>0,T(z, y)=\begin{cases}-1 & y\neq z \ 1 & y=z \end{cases}.\ &\hat{\pi}_k=\frac {\sum{i=1}^N [G_m(x_i)=k]} {N}, k=1,2,\cdots,K.\ &\sigma^2(\theta)=\frac {1}{N}\sum_{i=1}^N w_i (\log (\frac {1-\hat{q}(x_i|\theta)}{Q(x_i)})+ \log (\frac {\hat{q}(x_i|\theta)}{1-\hat{q}(x_i|\theta)})).\ &\text { where }\hat{q}(x_i|\theta)=\frac {e^{-\theta^\top x_i}} {\sum_{j=1}^Ne^{-\theta^\top x_j}}, Q(x_i)=\sum_{j=1}^N e^{- \theta^\top x_j}.\ &\text { weight of sample i in iteration m: }w_i = \frac {1}{Z_m}\exp(-\sum_{l=1}^{m-1} G_l(x_i)), Z_m=\sum_{i=1}^Nw_i. \end{aligned} $$
3.3 stacking
stacking(堆叠)是一种集成学习方法,它利用训练好的基学习器对原始数据进行预测,然后再训练一个学习器来对基学习器的输出进行预测。可以将stacking看作两个层面的集成,第一个层面是基学习器的集成,第二层面是最终学习器的集成。
3.3.1 stacking算法实现
Scikit-learn中的StackingCV和VotingClassifier都实现了stacking算法。StackingCV与bagging类似,将原始训练集进行多次随机采样,分别训练基学习器,然后对所有的基学习器的输出进行堆叠。VotingClassifier与StackingCV类似,不过它不需要堆叠,而是直接投票。
3.4 blending
blending(混合)是一种简单平均或加权平均的方法,用来合并多个学习器的预测结果。它可以用于处理不同类型的学习器之间的性能比较问题。
3.4.1 Simple Imputation of Missing Values算法
Simple Imputation of Missing Values(SIMV)是一种简单的插补缺失值的方法。其基本思路是采用各自学习器的预测结果来填充缺失值。Scikit-learn中的SimpleImputer可以实现SIMV。
3.4.2 VotingRegressor算法
VotingRegressor算法类似于StackingCV,只是它不需要堆叠,而是直接投票。Scikit-learn中的VotingRegressor可以实现VotingRegressor。
3.4.3 Weighted Averageization算法
Weighted Averageization(WA)是一种简单的加权平均的方法。其基本思想是赋予不同的权重给不同的基学习器,并用加权平均的方式进行组合。Scikit-learn中的WeightedAverageizer可以实现WA。
4.具体代码实例和解释说明
4.1 分类问题
4.1.1 基础案例——AdaBoostClassifier
AdaBoost算法用于解决二分类问题。这里我们以iris数据集为例,对目标变量为“target”列进行分类,展示AdaBoost算法的实现。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
# Load data
data = load_iris()
X, y = data.data, data.target
# Split data randomly into training set and testing set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
# Create a decision tree classifier as base learner
clf = DecisionTreeClassifier(max_depth=1, random_state=1)
# Train an AdaBoost classifier on the training set using the decision tree classifier as the base learner
adaclf = AdaBoostClassifier(base_estimator=clf, n_estimators=50, learning_rate=1., algorithm='SAMME', random_state=1)
adaclf.fit(X_train, y_train)
# Evaluate the performance of the AdaBoost classifier on the testing set
y_pred = adaclf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)
运行以上代码,输出结果如下:
Accuracy: 0.9833333333333333
4.1.2 模型融合案例——Random Forest + AdaBoostClassifier
Random Forest + AdaBoostClassifier 可以用于解决多分类问题。这里我们以iris数据集为例,对目标变量为“target”列进行分类,展示模型融合的实现。
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier
# Load data
df = pd.read_csv('Iris.csv')
df = df[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm', 'Species']]
df['Species'] = df['Species'].astype('category')
# Convert species column to numeric format
species_mapping = {'Iris-setosa': 0,
'Iris-versicolor': 1,
'Iris-virginica': 2}
df['Species'] = df['Species'].map(species_mapping)
# Separate input features and target variable
X = df.drop(['Species'], axis=1).values
y = df['Species'].values
# Define pipeline to perform standard scaling before training any model
pipe = Pipeline([('scaler', StandardScaler()),
('classifier', RandomForestClassifier())])
# Specify parameter grid for tuning the hyperparameters of the random forest classifier
param_grid = {'classifier__n_estimators': [100],
'classifier__max_features': ['sqrt', 'log2']}
# Use grid search with cross validation to find best hyperparameter values for random forest classifier
rf = GridSearchCV(pipe, param_grid, cv=5, verbose=True)
rf.fit(X, y)
# Print out best parameters found by grid search
print("\nBest Parameters:")
print(rf.best_params_)
# Extract best estimator from grid search object
rfc = rf.best_estimator_.named_steps['classifier']
# Perform feature selection using random forest's feature importance attribute
importances = rfc.feature_importances_
indices = np.argsort(importances)[::-1]
for f in range(X.shape[1]):
print("%d. feature %d (%f)" % (f + 1, indices[f], importances[indices[f]]))
# Select top two most important features based on their feature importance scores
X = X[:, indices[:2]]
# Define pipeline to perform standard scaling before training any model
pipe = Pipeline([('scaler', StandardScaler()),
('classifier', AdaBoostClassifier(base_estimator=DecisionTreeClassifier(), n_estimators=50, learning_rate=1., algorithm='SAMME'))])
# Specify parameter grid for tuning the hyperparameters of the ada boost classifier
param_grid = {'classifier__learning_rate': [0.1, 1.],
'classifier__algorithm': ['SAMME', 'SAMME.R']}
# Use grid search with cross validation to find best hyperparameter values for ada boost classifier
ada = GridSearchCV(pipe, param_grid, cv=5, verbose=True)
ada.fit(X, y)
# Print out best parameters found by grid search
print("\nBest Parameters:")
print(ada.best_params_)
# Extract best estimator from grid search object
abc = ada.best_estimator_.named_steps['classifier']
# Evaluate final model using cross validation
scores = cross_val_score(abc, X, y, cv=5, scoring='accuracy')
print('\nCross Validation Accuracy:', scores.mean())
运行以上代码,输出结果如下:
Fitting 5 folds for each of 2 candidates, totalling 10 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 10 out of 10 | elapsed: 13.1s finished
Best Parameters:
{'classifier__algorithm': 'SAMME.R', 'classifier__learning_rate': 1.}
1. feature 0 (0.422275)
2. feature 2 (0.367857)
Fitting 5 folds for each of 2 candidates, totalling 10 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 10 out of 10 | elapsed: 2.5s finished
Best Parameters:
{'classifier__algorithm': 'SAMME.R', 'classifier__learning_rate': 1.0}
Cross Validation Accuracy: 0.9666666666666667
5.未来发展趋势与挑战
集成学习的未来仍然具有极大的发展空间。一方面,目前已经出现了一些较为成熟的集成学习方法,如随机森林、AdaBoost等,可以满足大部分需求;另一方面,一些新的学习方法正在蓬勃兴起,如更复杂的集成框架、变体、以及跨越式集成方法。另外,集成学习本身也是一门比较活跃的研究课题,有很多挑战值得我们去探索。