<jupyter_start><jupyter_text>集成学习(Ensemble Learning)  Voting Classifier 假定在项目中训练好了一些分类器  可能包括: Logistic Regression, SVM, Random Forest, K-Nearest Neighbors,etc.  选取预测结果中最多的那一分类,作为集成学习的输出分类 只有当预测器间相互独立时,集成方法才能获得最好的效果  一种获得多样化预测器的方法是使用完全不同的算法 e.g. 使用三种不同的算法集成Voting Classifier<jupyter_code>from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(random_state=42)
rnd_clf = RandomForestClassifier(random_state=42)
svm_clf = SVC(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)<jupyter_output><empty_output><jupyter_text>查看每一种分类器的准确率<jupyter_code>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))<jupyter_output><empty_output><jupyter_text>soft voting 如果所有的分类器都能预测出分类的概率,则可以计算所有分类器预测概率的平均值,选取概率最大的为输出分类 通常,soft voting可以取得比hard voting更好的效果  在使用中,只需要改变VotingClassifier()中的参数, voting='soft'  Bagging and Pasting 一种获得多样化分类器的方法是使用完全不同的训练算法  另一种方法是, 在训练集的不同随机子集中使用相同的训练算法 在选取训练子集的时候:  1.如果抽样时将样本放回(with replacement) , 则为bagging  2.如果抽样时不将样本放回(without replacement) , 则为pasting 在多个预测器中,bagging和pasting都允许同一个训练实例被抽样多次  但在同一个预测器中,只有bagging允许同一个训练实例被抽样多次 最终的输出结果通常由所有预测器的预测结果聚合而成  聚合函数(aggregation function):  对于分类问题,通常选取频率最高的预测分类  对于回归问题,通常选取预测回归的平均值 所有预测器都是分开平行训练的,因此,可以通过完全不同的CPU核心甚至不同的服务器来进行运算  类似地,预测也可以通过平行方法来进行预测 Scikit-Learn通过BaggingClassifier类,同时提供了bagging和pasting方法对于回归问题,使用BaggingRegressor<jupyter_code>from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), n_estimators=500,
    max_samples=100, bootstrap=True, n_jobs=-1, random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)<jupyter_output><empty_output><jupyter_text>n_estimators=500 : 表示训练500个决策树分类器  max_samples=100 : 每个分类器随机抽样100个实例,如果使用0.1~1.0,则按样本比例进行抽样   bootstrap : True表示使用bagging, False表示使用pasting    n_jobs : 表示需要使用的CPU核心数, -1表示使用所有可用的核心数 如果算法拥有predict_proba()方法,BaggingClassifier将自动使用soft voting(概率预测) 集成方法通常会得到一个相似的bias(欠拟合误差),但会得到一个更小的variance(过拟合误差)  bagging在每个子集上的多样性上更强一些, 因此,bagging的bias会高于pasting,但其variance则会减小(更不容易过拟合)  通常,bagging的表现会由于pasting  Out-of-Bag(oob) Evaluation 使用bagging的情况下,对于任一给定预测器, 一个实例可能会被抽样多次,而其他可能根本不会被抽样  默认下,BaggingClassifier有放回的抽样m个实例(m为训练样本的数据总量)  根据统计学, 这意味着对于每一个分类器,平均抽取训练样本总量中63%的样本  剩余的37%未被抽取的样本成为out-of-bag(oob)  注: 并非所有的分类器的oob都是37%,所有分类器oob的平均值为37% 因为一个预测器在训练的过程中从未见过oob数据,所以可以直接用oob数据进行验证,而无需额外分离出验证数据或使用交叉验证  可以验证每一个预测器在其自身oob数据上的结果的平均值,作为集成的验证结果 在Scikit-Learn中,可以设置oob_score=True,在训练后自动进行oob评估<jupyter_code>bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), n_estimators=500,
    bootstrap=True, n_jobs=-1, oob_score=True, random_state=40)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
bag_clf.oob_decision_function_<jupyter_output><empty_output><jupyter_text>该属性返回每一个训练实例的决策函数,如果算法可以评估概率,则返回的是每一个实例分类的所有概率  Random Patches and Random Subspaces BaggingClassifier类也支持从特征中进行抽样  max_features : 设置最大的抽样特征数  bootstrap_features : 设置有放回特征抽样 or 无放回特征抽样  每一个预测器将在一个拥有随机特征的子集上进行训练 在一个高维的输入数据(e.g.图像)上,这一方法尤为有用  Random Patches 在实例和特征上都进行抽样的方法称为Random Patches  Random Subspaces保持所有训练实例(i.e.,bootstrap=False & max_samples=1.0),  但对特征进行抽样(i.e.,bootstrap_feature & max_features <1.0 ) 的方法称为Random Subspaces 特征抽样会使得预测器更为多样化,在提升一点bias的情况下,获得更低的variance  随机森林(Random Forests) from sklearn.ensemble import RandomForestClassifierrnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1, random_state=42)rnd_clf.fit(X_train, y_train)y_pred_rf = rnd_clf.predict(X_test)b 对于回归问题,使用RandomForestRegressor  RandomForestClassifier拥有所有决策树分类器的超参数, 也拥有BaggingClassifier的超参数 随机森林算法引入了更多随机特性  在训练的过程中也进行了特征的抽样,从一个随机的特征子集中选取特征进行节点的分类  以下BaggingClassifier大致等同于上述的RandomForestClassifier代码<jupyter_code>bag_clf = BaggingClassifier(
    DecisionTreeClassifier(splitter="random", max_leaf_nodes=16, random_state=42),
    n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1, random_state=42)<jupyter_output><empty_output><jupyter_text>Extra-Trees 在训练一个随机森林算法时:一种方法是,在每一个节点,只选取一个随机的特征子集进行分类(split)  第二种方式是, 在第一种方法的前提下, 在每一个节点使用随机的特征阈值来进行分类(split) ,而不是找到最优的特征阈值 这种极端的随机树方法称为: Extremely Randomized Trees ensemble  该方法会产生一个更大的bias和一个更小的variance,同时,该方法的训练速度也更快(因为无需找到最优化的特征阈值) Scikit-Learn 中的API为ExtraTreesClassifier和ExtraTreesRegressor 通常,只有在实际验证后,才能得知Random Forests 和Extra Trees之间孰优孰劣  Feature Importance 在一个决策树中,重要的特征更可能出现在决策树的顶部, 不重要的特征更可能出现在决策树的底部  在理论上,通过计算一个特征在整个森林中的平均深度来评估该特征的重要性<jupyter_code>rnd_clf.feature_importances_<jupyter_output><empty_output><jupyter_text>随机森林能够非常方便地得出哪些特征更为重要,尤其是在特征选择中的应用  Boosting (Hypothesis boosting) Boosting方法是一种用来提高弱分类算法准确度的方法,这种方法通过构造一个预测函数序列,然后以一定的方式将他们组合成一个预测函数。  预测函数序列: 每一个函数都试图修正前一个预测函数  AdaBoost 1.第一个分类器使用整体训练集来进行训练和预测    2.提升训练数据中被错误分类的实例权重  3.第二个分类器用提升过权重的数据来进行训练和预测  4.依次循环  5.训练完所有的分类器后,根据每一个分类器在加权训练数据集中的准确率来确定每一个分类器的权重 AdaBoost有一个很重要的缺点  因为它是一个序列学习技术,因此无法使用平行运算方法  每一个预测函数都必须在前一个预测函数训练好,并评估好以后才能接下去运行 在AdaBoost算法的开始,每一个实例的权重都为$\frac{1}{m}$  第一个预测函数训练好后,它的加权误差率$r_1$通过训练集来进行计算 第j个预测函数的加权误差率(weighted error rate)  $\larger_j = \dfrac{\displaystyle \sum\limits_{\textstyle {i=1 \atop \hat{y}_j^{(i)} \ne y^{(i)}}}^{m}{w^{(i)}}}{\displaystyle \sum\limits_{i=1}^{m}{w^{(i)}}} \quad\text{where }\hat{y}_j^{(i)}\text{ is the }j^{\text{th}}\text{ predictor's prediction for the }i^{\text{th}}\text{ instance.}$ 预测函数的权重:  $\large\begin{split}\alpha_j = \eta \log{\dfrac{1 - r_j}{r_j}}\end{split}$ 其中$\eta$为学习率参数,默认为1  根据对数函数的性质:  如果$r_j=0.5$(即一半的加权误差率),则$\alpha_j=0$  当$r_j>0$(即加权误差率超过50%),则$\alpha_j<0$, 预测函数的权重为负 权重更新规则:  $\begin{split}& \text{ for } i = 1, 2, \dots, m \\& w^{(i)} \leftarrow\begin{cases}w^{(i)} & \text{if }\hat{y_j}^{(i)} = y^{(i)}\\w^{(i)} \exp(\alpha_j) & \text{if }\hat{y_j}^{(i)} \ne y^{(i)}\end{cases}\end{split}$  随后,所有的实例权重都要进行标准化(i.e.,除以 $ \sum_{i=1}^{m}{w^{(i)}} $`) 该规则表明,$\alpha_j$越高(预测函数的权重越高 i.e.,该函数的准确率越高),则其错误分类的实例权重越高   AdaBoost计算所有预测函数的结果,并使用$\alpha_j$对每一个进行加权  预测公式为:  $\hat{y}(\mathbf{x}) = \underset{k}{\operatorname{argmax}}{\sum\limits_{\scriptstyle j=1 \atop \scriptstyle \hat{y}_j(\mathbf{x}) = k}^{N}{\alpha_j}} \quad \text{where }N\text{ is the number of predictors.}$ e.g.  $\hat{y}_j(\mathbf{x}) = k$:  $\hat{y}_j(\mathbf{x}) = 1$ 时, $\hat{y}(\mathbf{x}) = 0.3+0.4 = 0.7$  $\hat{y}_j(\mathbf{x}) = -1$ 时, $\hat{y}(\mathbf{x}) = 0.2+0.1 = 0.3$  因此,最终的分类 $k=1$  AdaBoost在Scikit-Learn中的实现<jupyter_code>from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1), n_estimators=200,
    algorithm="SAMME.R", learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)<jupyter_output><empty_output><jupyter_text>代码中,AdaBoost的base estimator是Decision Stumps  该算法是决策树算法的一个简化版本:  最大深度max_depth=1(即一棵树仅由顶部和两片叶子构成)  该算法是AdaBoostClassifier的默认base estimator 如果AdaBoost在使用过程中发生了过度拟合,则需要减少estimator的数量,或者在base estimator中添加更严格的正则化  Gradient Boosting Gradient Boosting类似于AdaBoost,都是通过序列添加预测函数,后一个预测函数不断修正前一个预测函数  不过该算法不是在每次迭代的时候调节实例的权重, 而是fit前一个预测函数所产生的残差<jupyter_code>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.8]])
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))<jupyter_output><empty_output><jupyter_text>Gradient Boosting在Scikit-Learn中的应用<jupyter_code>from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)<jupyter_output><empty_output><jupyter_text>learning_rate规定了每棵树的影响大小  如果设置一个较小的值,例如0.1,则需要在集成方法中添加更多的树来拟合训练集,通常预测效果会变得更佳 但降低了学习率后,过少的树会造成欠拟合,而过度的树又会造成过度拟合  为了找到最优化树的数量,可以使用Early Stopping方法 另一种简单的方法是使用staged_predict()方法  该方法在Gradient Boosting的每一步都返回验证结果(one tree,two trees,ect.)<jupyter_code>import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=49)

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120, random_state=42)
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)

gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators, random_state=42)
gbrt_best.fit(X_train, y_train)<jupyter_output><empty_output><jupyter_text>上述代码会测试从0~120棵数  如果使用Early Stop方法,则无需在一开始设置一个较大的树的数量  Early Stop 在GradientBoostingRegressor中,  warm_start=True时,当fit()方法被调用时,使Scikit-Learn保存当前存在的树,允许增量训练<jupyter_code>gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True, random_state=42)

min_val_error = float("inf")    #float("inf")表示正无穷, 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  # early stopping<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11073376556550375, 'fraction_numerical': 0.018832973139857982, 'mean_word_length': 8.845997973657548, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '341844', 'license': 'Apache-2.0', 'n_tokens_mistral': 5411, 'n_tokens_neox': 5771, 'n_words': 504, 'path': 'ensemble_learning.ipynb', 'repo_name': 'CatoneCat/machine_learning', 'size': '9717'}