假设已经训练好了一些分类器, 每个分类器准确率约为80%。 这时, 要创建一个更好的分类器, 最简单的办法是聚合每个分类器的预测, 然后将得票数最多的结果作为预测类别。 这种大多数投票分类器被称为硬投票分类器。
如下用Scikit-Learn创建并训练一个投票分类器, 由三种不同的分类器组成:
# satellite datasets from sklearn.datasets import make_moons X, y = make_moons(n_samples=100, noise=0.15) X_train, X_test, y_train, y_test = X[:90], X[90:], y[:90], y[90:]
# voting classifier from sklearn.ensemble import RandomForestClassifier from sklearn.ensemble import VotingClassifier from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC log_clf = LogisticRegression() rnd_clf = RandomForestClassifier() svm_clf = SVC() voting_clf = VotingClassifier( estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)], voting='hard') voting_clf.fit(X_train, y_train)
# classification precision from sklearn.metrics import accuracy_score for clf in (log_clf, rnd_clf, svm_clf): clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(clf.__class__.__name__, accuracy_score(y_test, y_pred)) print(voting_clf.__class__.__name__, accuracy_score(y_test, y_pred)) # LogisticRegression 0.9 # RandomForestClassifier 0.9 # SVC 1.0 # VotingClassifier 1.0
如果所有分类器都能算出类别概率(即具有predict_proba()方法), 那么你可以将概率在所有单个分类器上平均, 然后让Scikit-Learn给出平均概率最高的类别做出预测, 这被称为软投票法。
通常, 它比硬投票法表现更优, 因为它给予那些高度自信的投票更高的权重。
只需要用 voting="soft"代替voting=“hard”, 并确保所有分类器可以估算概率。
每个预测器使用的算法相同, 但是在不同训练集的随机子集上进行训练。采样时如果将样本放回, 这种方法叫bagging(bootstrap aggregating,自举汇聚法);采样时样本不放回, 叫pasting。bagging模型一般更好, 偏差略高, 预测器间关联更低, 集成方差更低。
# 训练一个包含500个决策树分类器的集成 # 每次从训练集随机采样50个训练实例进行训练 # 这是bagging的示例, 如果使用pasting,需设置bootstrap=False # n_jobs指示Scikit-Learn使用多少CPU内核进行训练和预测, -1表示让使用所有可用内核 from sklearn.ensemble import BaggingClassifier from sklearn.tree import DecisionTreeClassifier bag_clf = BaggingClassifier( DecisionTreeClassifier(), n_estimators=500, max_samples=50, bootstrap=True, n_jobs=-1) bag_clf.fit(X_train, y_train) y_pred = bag_clf.predict(X_test)
如果基本分类器可以估计类别概率, 则BaggingClassifier自动进行软投票。
包外评估
未被采样的训练实例称为包外(oob)实例
在Scikit-Learn中, 创建BaggingClassifier时, 设置oob_score=True就可以在训练结束后自动进行包外评估
bag_clf = BaggingClassifier( DecisionTreeClassifier(), n_estimators=500, bootstrap=True, n_jobs=--1, oob_score=True) bag_clf.fit(X_train, y_train) bag_clf.oob_score_ # 0.9222222222222223
训练实例的包外决策函数可以通过变量oob_decision_function_获得
bag_clf.oob_decision_function_ ''' array([[0. , 1. ], [1. , 0. ], [0.01030928, 0.98969072], [0. , 1. ], ... [0.97282609, 0.02717391], [0. , 1. ], [0.99415205, 0.00584795], [0. , 1. ]]) '''
BaggingClassifier类可以支持对特征进行采样, 由超参数max_features和bootstrap_features控制。
对训练实例和特征都进行采样,叫随机补丁法。
而保留所有训练实例(bootstrap=False且max_samples=1.0)但对特征进行采样(bootstrap_features=True且/或max_features<1.0)称为随机子空间法
随机森林是决策树的集成,通常用bagging(有时也用pasting)方法训练,训练集大小通过max_samples设置。
from sklearn.ensemble import RandomForestClassifier rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1) rnd_clf.fit(X_train, y_train) y_pred_rf = rnd_clf.predict(X_test)
除少数例外, RandomForestClassifier具有DecisionTreeClassifier的所有超参数(以控制树的生长方式),以及BaggingClassifier的所有超参数来控制集成本身。
随机森林在树的生长上引入了更多的随机性:分裂节点是在一个随机生成的特征子集里搜索最好的特征, 使决策树有更大的多样性。
下面的BaggingClassifier与上述的RandomForestClassifier大致相同:
bag_clf = BaggingClassifier( DecisionTreeClassifier(splitter="random", max_leaf_nodes=16), n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1)
极端随机树
随机森林里单棵树生长过程中, 每个节点在分裂时仅考虑到一个随机子集所包含的特征。如果对每个特征使用随机阈值, 而不是搜索得出的最佳阈值(如常规决策树), 则可能让决策树生长得更随机。
极端随机的决策树组成的森林称为极端随机树集成(Extra-Tree), 以更高的偏差换取更低的偏差,且训练速度比常规随机森林快很多。
使用Scikit-Learn的ExtraTreesClassifier类可以创建一个极端随机数森林, 它的API与 RandomForestClassifier类相同。 同理, ExtraTreeRegressor类与RandomForestRegressor类的API也相同。
一般用交叉验证比较ExtraTreesClassifier类和RandomForestClassifier类的好坏。
特征重要性
Scikit-Learn通过查看使用该特征的树节点平均减少不纯度的程度衡量该特征的重要性。
from sklearn.datasets import load_iris iris = load_iris() rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1) rnd_clf.fit(iris["data"], iris["target"]) for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_): print(name, score) ''' sepal length (cm) 0.09045003660617805 sepal width (cm) 0.023146425767105787 petal length (cm) 0.44988069044206885 petal width (cm) 0.4365228471846474 '''
提升法(boosting)将几个弱学习器结合成一个强学习器
大多数提升法总体思路是循环训练预测器, 每一次都对前序做出一些改正。
adaboost
新预测器通过更多地关注前序欠拟合的训练实例, 使新预测器不断地越来越关注难缠的问题, 从而对前序进行纠正。
from sklearn.ensemble import AdaBoostClassifier ada_clf = AdaBoostClassifier( DecisionTreeClassifier(max_depth=1), n_estimators=200, algorithm="SAMME.R", learning_rate=0.5) ada_clf.fit(X_train, y_train)
如果过拟合, 可以试试减少估算器数量, 或提高基础估算器正则化程度。
梯度提升
让新的预测器针对前一个预测器的残差进行拟合
# 决策树作为基础预测器 ———— 梯度树提升或梯度提升回归树(GBRT) # 首先, 在训练集上拟合一个DecisionTreeRegressor tree_reg1 = DecisionTreeRegressor(max_depth=2) tree_reg1.fit(X, y) # 针对第一个预测器的残差,训练第二个DecisionTreeRegressor y2 = y - tree_reg1.predict(X) tree_reg2 = DecisionTreeRegressor(max_depth=2) tree_reg2.fit(X, y2) # 针对第二个预测器的残差,训练第三个DecisionTreeRegressor y3 = y2 - tree_reg1.predict(X) tree_reg2 = DecisionTreeRegressor(max_depth=2) tree_reg2.fit(X, y3) # 将所有的树的预测相加, 从而对新实例进行预测 y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
训练GBRT, Scikit-Learn的GradientBoostRegressor类, 具有控制决策树生长和集成训练的超参数。
from sklearn.ensemble import GradientBoostRegressor gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0) gbrt.fit(X, y)
超参数learning_rate对每棵树的贡献进行缩放。如果设为低值, 如0.1, 则需要更多树来拟合训练, 泛化误差也更小。
找到树的最佳数量, 可以使用提前停止法。
# 训练一个拥有120棵树的集成, 每个阶段都对集成的预测返回一个迭代器(一棵树, 两棵树...) # 测量每个阶段的验证误差, 从而找到最优数量 import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error X_train, X_test, y_train, y_test = train_test_split(X, y) gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120) gbrt.fit(X_train, y_train) errors = [mean_squared_error(y_val, y_pred) for y_pred in gbrt.staged_predict(X_val)] bst_n_estimators = np.argmin(errors) + 1 gbrt_best = GradientBoostingRegressor(max_depth=2, n_estimators=bst_n_estimators) gbrt_best.fit(X, y)
# 提前停止法 # 验证误差连续五次迭代未改善时, 停止训练 gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True) min_val_error = float("inf") error_going_up = 0 for n_estimators in range(1, 120): gbrt.n_estimators = n_estimators gbrt.fit(X_train, y_train) y_pred = gbrt.predict(X_val) val_error = mean_squared_error(y_val, y_pred) if val_error < min_val_error: min_val_error = val_error error_going_up = 0 else: error_going_up += 1 if error_going_up == 5 break
库XGBoost(Extreme Gradient Boost)提供了梯度提升的优化实现
import xgboost xgb_reg = xgboost.XGBoostRegressor() xgb_reg.fit(X_train, y_train) y_pred = xgb_reg.predict(X_val) # 自动处理提前停止 xgb_reg.fit(X_train, y_train, eval_set[(X_val, y_val)], early_stopping_rounds=2) y_pred = xgb_reg.predict(X_val)