集成学习和随机森林
文章目录
集成学习
假设你去随机问很多人一个很复杂的问题,然后把它们的答案合并起来。通常情况下你会发现这个合并的答案比一个专家的答案要好。这就叫做群体智慧。
同样的,如果你合并了一组分类器的预测(像分类或者回归),你也会得到一个比单一分类器更好的预测结果。这一组分类器就叫做集成;因此,这个技术就叫做集成学习,一个集成学习算法就叫做集成方法。
投票分类
假设你已经训练了一些分类器,每一个都有 80% 的准确率。你可能有了一个逻辑回归、或一个SVM
分类、或一个随机森林分类,或者一个 KNN
分类,或许还有更多,如图
硬投票分类器:创建一个更好的分类器的方法就是去整合每一个分类器的预测然后经过投票去预测分类。这种分类器就叫做硬投票分类器
这种投票分类器得出的结果经常会比集成中最好的一个分类器结果更好。事实上,即使每一个分类器都是一个弱学习器(意味着它们也就比瞎猜好点),集成后仍然是一个强学习器(高准确率),只要有足够多数量,且足够多种类的弱学习器即可。
大数定律
heads_proba = 0.51 # 硬币正面概率,偏倚硬币
coin_tosses = (np.random.rand(10000, 10) < heads_proba).astype(np.int32)
# 模拟投币 10000行10列 ,转换成0、1,反面,正面
cumulative_heads_ratio = np.cumsum(coin_tosses, axis=0) / np.arange(1, 10001).reshape(-1, 1)
# 累计正面的频率即==概率,大数定律
plt.figure(figsize=(8,3.5))
plt.plot(cumulative_heads_ratio)
plt.plot([0, 10000], [0.51, 0.51], "k--", linewidth=2, label="51%") #图中黑虚线,0.51
plt.plot([0, 10000], [0.5, 0.5], "k-", label="50%") #图中黑线,0.5
plt.xlabel("Number of coin tosses")
plt.ylabel("Heads ratio")
plt.legend(loc="lower right")
plt.axis([0, 10000, 0.42, 0.58])
利用大数定律,假设你创建了一个包含 1000 个分类器的集成模型,其中每个分类器的正确率只有 51%(仅比瞎猜好一点点),用大多数硬投票的类别作为预测结果,那么准确率可达到75%! 如果是10000个分类器的集成模型,准确率可达到97%!!
但是,这是有个前提条件的:所有分类器须是完全独立的。彼此毫不相关。也就是说,彼此的所可能犯的错误也毫不相关。
集成不同算法进行训练
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
# sklearn.ensemble 引入集成模块
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
log_clf = LogisticRegression(solver="lbfgs", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
# n_estimators=100 决策树个数,100个
svm_clf = SVC(gamma="scale", random_state=42)
voting_clf = VotingClassifier(
estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)], #三种不同的学习算法
voting='hard') # 硬投票
voting_clf.fit(X_train, y_train) # 训练拟合
from sklearn.metrics import accuracy_score
for clf in (log_clf, rnd_clf, svm_clf, voting_clf): # 循环输出测试准确率ACC,三种分类器,最后一个是前三种的硬投票
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
#LogisticRegression 0.864
#RandomForestClassifier 0.896
#SVC 0.896
#VotingClassifier 0.912
如果集成的所有的分类器都能够预测类别的概率(有predict_proba()
方法),那么可以把概率在所有单个分类器上平均,让 sklearn
给出平均概率最高的类别作为预测结果,这种方式叫做软投票。他经常比硬投票表现的更好,因为它给予高可信的投票更大的权重。
通过把voting="hard"
设置为voting="soft"
,并保证每个分类器可以预测类别的概率。此例中SVC
分类器 默认不行,需要修改超参数probability 设置为True
(这会使 SVC
使用交叉验证去预测类别概率,其降低了训练速度,但它会添加predict_proba()
方法)
log_clf = LogisticRegression(solver="lbfgs", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
svm_clf = SVC(gamma="scale", probability=True, random_state=42)
#设置超参数probability为True
voting_clf = VotingClassifier(
estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
voting='soft') #软投票
voting_clf.fit(X_train, y_train)
from sklearn.metrics import accuracy_score
for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
#LogisticRegression 0.864
#RandomForestClassifier 0.896
#SVC 0.896
#VotingClassifier 0.92
使用Bagging 和 Pasting
若每一个分类器都使用相同的训练算法,但是在不同的训练集上去训练它们。
有放回采样被称为装袋套袋(Bagging)和 无放回采样称为粘贴(pasting)。
顾名思义,Bagging 和 Pasting 都允许训练实例在多个分类器上被多次采样,但只有 Bagging 允许训练示例在同一个分类器上进行进行多次采样。
装袋套袋(Bagging)和 无放回采样称为粘贴(pasting)之所以流行,因为可以充分利用CPU的多核,甚至多服务器并行分布地进行预测。
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
# 引入 accuracy_score
bag_clf = BaggingClassifier(
DecisionTreeClassifier(), n_estimators=500, #用了500个决策树,默认10
max_samples=100, bootstrap=True, random_state=42) #套袋法
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
print(accuracy_score(y_test, y_pred))
#0.904
tree_clf = DecisionTreeClassifier(random_state=42) #单一决策树
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))
#0.856
保外评估(Out-of-Bag)
对于 Bagging 来说,一些实例可能被一些分类器重复采样,但其他的有可能不会被采样。BaggingClassifier
默认是有放回的采样 m个实例 (bootstrap=True),其中 m是训练集的大小,这意味着平均下来只有 63%的训练实例被每个分类器采样,剩下的 37%个没有被采样的训练实例就叫做 Out-of-Bag实例。
因为在训练中分类器从来没有看到过oob
实例,所以它可以在这些实例上进行评估,而不需要单独的验证集或交叉验证。你可以拿出每一个分类器的 oob
来评估集成本身。
随机补丁与随机子空间
BaggingClassifier
也支持采样特征。它被两个超参数max_features
和bootstrap_features
控制。
对于处理高维度输入(例如图片),此方法尤其有效
对训练实例和特征都进行采样被叫做随机补丁(patches)
保留所有的训练实例(例如:bootstrap=False和max_samples=1.0
),但是对特征采样(bootstrap_features=True并且/或者max_features小于 1.0
)叫做随机子空间
随机森林
随机森林是决策树的一种集成
通常是通过 bagging 方法(有时是 pasting 方法)进行训练,
通常用max_samples设置为训练集的大小。
与之前建立一个BaggingClassifier
然后把它放入DecisionTreeClassifier
不同,
还有一种方法:RandomForestClassifier
,这种方法更方便,对决策树更优化。
对于回归任务是:RandomForestRegressor
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=42)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
特征重要度
如果你观察一个单一决策树,重要的特征会出现在更靠近根部的位置,而不重要的特征会经常出现在靠近叶子的位置。
因此,通过计算一个特征在森林的全部树中出现的平均深度来预测特征的重要性。
sklearn
在训练后会自动计算每个特征的重要度。你可以通过feature_importances_
变量来查看结果。
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
mnist.target = mnist.target.astype(np.uint8)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rnd_clf.fit(mnist["data"], mnist["target"])
def plot_digit(data):
image = data.reshape(28, 28)
plt.imshow(image, cmap = mpl.cm.hot,
interpolation="nearest") #插值
plt.axis("off")
plot_digit(rnd_clf.feature_importances_)
cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(), rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['Not important', 'Very important'])
#展示在手写数字的(28*28)图片中,哪些像素是重要特征
提升法(Boosting)
提升法:指将几个弱学习者组合成强学习者的集成方法。
大多数的提升方法的思想就是按顺序去训练分类器,每一个都要尝试修正前面的分类
可用的提升法有很多:目前最流行的是 Adaboost
(适应性提升,Adaptive Boosting
) 和 Gradient Boosting
(梯度提升)
Adaboost
更多关注前序欠拟合的训练实例,从而使新预测器不断地越来越关注于难缠的问题,这就是适应性提升Adaboost
如图:
首先,当训练一个Adaboost
分类器时,该算法先训练一个基础分类器(如决策树),并使用它对训练集进行预测
然后,该算法会增加分类错误的训练实例的相对权重,更改权重。
然后,用更新权重后的训练实例训练第二个分类器,并再次对训练集进行预测,继续更新出错训练实例的权重,以此类推。
from sklearn.ensemble import AdaBoostClassifier #适应性提升
ada_clf = AdaBoostClassifier(
DecisionTreeClassifier(max_depth=1), n_estimators=200,
# 弱学习器的最大迭代次数,或者说最大的弱学习器的个数,默认50 ,太小容易欠拟合,太大容易过拟合
algorithm="SAMME.R", learning_rate=0.5, random_state=42)
# SAMME使用对样本集分类效果作为弱学习器权重 ,而默认的SAMME.R使用了对样本集分类的预测概率大小来作为弱学习器权重。
ada_clf.fit(X_train, y_train)
Gradient Boosting
另一个非常著名的提升算法:梯度提升
它并不像Adaboost
那样每一次迭代都更改实例的权重,而是去使用新的分类器去拟合前面分类器预测的残差。
拟合残差
np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)
from sklearn.tree import DecisionTreeRegressor
tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)
y2 = y - tree_reg1.predict(X) # 残差
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2) # 拟合残差
y3 = y2 - tree_reg2.predict(X) # 残差
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3) # 拟合残差
X_new = np.array([[0.4]])
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
#array([0.49995198])
梯度提升回归树(GBRT)
使用 sklean
中的 GradientBoostingRegressor
来训练 GBRT
集成:梯度提升回归树
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)
gbrt_slow = GradientBoostingRegressor(max_depth=2, n_estimators=200, learning_rate=0.1, random_state=42)
gbrt_slow.fit(X, y)
# learning_rate 默认0.1学习速率/步长0.0-1.0的超参数 每个树学习前一个树的残差的步长
# n_estimators 回归树个数 弱学习器个数
堆叠法提升XGBoost
基本思路:与其使用一些简单的函数(如硬投票)来聚合集成学习中所有预测器的预测,不如训练一个模型来执行这个聚合。
如图,也就是把预测结果作为输入来训练混合器Blender
(最后一层分类器,也是最终的预测器)
基本流程:
-
将训练集分成两个子集,第一个子集用来训练第一层预测器,第二个自己
留存
-
用第一层的预测器在留存的第二个子集上进行预测。即,对于留存集中的每个实例都有三个预测值
-
最后,blender在这个新的训练集上训练,让它学习根据第一层的预测来预测目标值,有点绕,但没问题。
try:
import xgboost
except ImportError as ex:
print("Error: the xgboost library is not installed.")
xgboost = None
# 安装xgboost :pip install xgboost
if xgboost is not None: # not shown in the book
xgb_reg = xgboost.XGBRegressor(random_state=42)
xgb_reg.fit(X_train, y_train)
y_pred = xgb_reg.predict(X_val) #这里X_val 就是留存集,用第一层预测器在留存集上预测
val_error = mean_squared_error(y_val, y_pred) #
print("Validation MSE:", val_error) #
# Validation MSE: 0.004000408205406276
if xgboost is not None:
xgb_reg.fit(X_train, y_train,
eval_set=[(X_val, y_val)], early_stopping_rounds=2)
y_pred = xgb_reg.predict(X_val)
val_error = mean_squared_error(y_val, y_pred)
print("Validation MSE:", val_error)
#Validation MSE: 0.002630868681577655 优化了
====