集成学习(Ensemble Learning)

集成学习的思想是将若干个学习器(分类器&回归器)组合之后产生一个新学习器。 弱分类器(weak learner)指那些分类准确率只稍微好于随机猜测的分类器(error rate < 0.5);

集成算法的成功在于保证弱分类器的多样性(Diversity)。而且集成不稳定的算法 也能够得到一个比较明显的性能提升。

Why need Ensemble Learning?

  • 弱分类器间存在一定的差异性,这会导致分类的边界不同,也就是说可能存在错误。那么将多个弱分类器合并后,就可以得到更加合理的边界,减少整体的错误率,实现更好的效果;
  • 对于数据集过大或者过小,可以分别进行划分和有放回的操作产生不同的数据子集,然后使用数据子集训练不同的分类器,最终再合并成为一个大的分类器;
  • 如果数据的划分边界过于复杂,使用线性模型很难描述情况,那么可以训练多个模型,然后再进行模型的融合;
  • 对于多个异构的特征集的时候,很难进行融合,那么可以考虑每个数据集构建一个分类模型,然后将多个模型融合。

常见的集成学习思想有:

  • Bagging
  • Boosting
  • Stacking

Bagging

Bagging方法又叫做自举汇聚法(Bootstrap Aggregating),思想是:在原始数据集上通过有放回的抽样的方式,重新选择出S个新数据集来分别训练S个分类器的集成技术。

Bagging方法训练出来的模型在预测新样本分类或者回归的时候,会使用多数投票或者求均值的方式来统计最终的分类结果。

Bagging方法的弱学习器可以是基本的算法模型,eg: Linear、Ridge、Lasso、Logistic、Softmax、ID3、C4.5、CART、SVM、KNN等。

备注:Bagging方式是有放回的抽样,并且每个子集的样本数量必须和原始样本数量一致,即有多少个样本就随机抽取多少个样本,所以抽取出来的子集中是存在重复数据的,但是在模型训练的时候会将重复数据删除(相当于去重distinct),也就是说真正用于训练模型的数据集样本和原始样本数是不一致。

Bagging方法_训练过程

Bagging方法_预测过程

import pandas as pd
import numpy as np
import matplotlib as mpl

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import recall_score
from sklearn.ensemble import BaggingClassifier

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False

# 一、加载数据
names = ['A', 'B', 'C', 'D', 'label']
path = '../datas/iris.data'
df = pd.read_csv(path, sep=',', header=None, names=names)
# df.info()
# print(df.head(2))

# 二、数据的清洗
df.replace('?', np.nan, inplace=True)
df.dropna(axis=0, how='any', inplace=True)

# 三、基于业务提取最原始的特征属性X和目标属性Y
X = df[names[:-1]]
Y = df[names[-1]]
Y_label_values = np.unique(Y)
Y_label_values.sort()
print("Y的取值可能:{}".format(Y_label_values))
random_index = np.random.permutation(len(Y))[:5]
print("随机的索引:{}".format(random_index))
print("原始随机的5个Y值:{}".format(np.array(Y[random_index])))
for i in range(len(Y_label_values)):
    Y[Y == Y_label_values[i]] = i
Y = Y.astype(np.float)
print("做完标签值转换后的随机的5个Y值:{}".format(np.array(Y[random_index])))

# 四、数据的划分(将数据划分为训练集和测试集)
print("原始X形状:{}, 原始Y形状:{}".format(X.shape, Y.shape))
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8, random_state=28)
print("训练集样本形状:{}, 测试集样本形状:{}".format(x_train.shape, x_test.shape))

# 六、算法模型的选择/算法模型对象的构建
knn = KNeighborsClassifier(n_neighbors=10)

# 构建Bagging的集成分类算法对象
algo = BaggingClassifier(base_estimator=knn, n_estimators=10)

# 七、算法模型的训练
algo.fit(x_train, y_train)

# 八、模型效果评估
y_hat = algo.predict(x_test)
print("在训练集上的模型效果(分类算法中为准确率):{}".format(algo.score(x_train, y_train)))
print("在测试集上的模型效果(分类算法中为准确率):{}".format(algo.score(x_test, y_test)))
print("在测试集上的召回率的值:{}".format(recall_score(y_true=y_test, y_pred=y_hat, average='micro')))

# 九、输出分类中特有的一些API
print("=" * 100)
y_predict = algo.predict(x_test)
print("预测值:\n{}".format(y_predict))
print("预测的实际类别:\n{}".format([np.array(Y_label_values)[int(i)] for i in y_predict]))
print("=" * 100)
print("属于各个类别的概率值:\n{}".format(algo.predict_proba(x_test)))
print("=" * 100)
View Code

随机森林(Random Forest)

  • 在Bagging策略的基础上进行修改后的一种算法(底层为决策数算法)
  • 从原始样本集(n个样本)中用Bootstrap采样(有放回重采样)选出n个样本;真正用于模型训练的是这抽取出来的样本去重之后的数据集,也就是用户模型训练的样本数目实际上不等于n,应该是小于n。
  • 从所有属性中随机选择K个属性,从K个属性中选择出最佳分割属性作为节点来迭代的创建决策树。
  • 重复以上两步m次,即建立m棵决策树。
  • 这m个决策树形成随机森林,通过投票表决结果决定数据属于那一类。

RF scikit-learn相关参数

https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier

https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.RandomForestRegressor.html#sklearn.ensemble.RandomForestRegressor

import pandas as pd
import numpy as np
import matplotlib as mpl
import warnings
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score
from sklearn.ensemble import RandomForestClassifier

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings(action='ignore')
# 一、加载数据
names = ['A', 'B', 'C', 'D', 'label']
path = '../datas/iris.data'
df = pd.read_csv(path, sep=',', header=None, names=names)

# 二、数据的清洗
df.replace('?', np.nan, inplace=True)
df.dropna(axis=0, how='any', inplace=True)

# 三、基于业务提取最原始的特征属性X和目标属性Y
X = df[names[:2]]
Y = df[names[-1]]
Y_label_values = np.unique(Y)
Y_label_values.sort()
print("Y的取值可能:{}".format(Y_label_values))
random_index = np.random.permutation(len(Y))[:5]
print("随机的索引:{}".format(random_index))
print("原始随机的5个Y值:{}".format(np.array(Y[random_index])))
for i in range(len(Y_label_values)):
    Y[Y == Y_label_values[i]] = i
Y = Y.astype(np.float)
print("做完标签值转换后的随机的5个Y值:{}".format(np.array(Y[random_index])))

# 四、数据的划分(将数据划分为训练集和测试集)
print("原始X形状:{}, 原始Y形状:{}".format(X.shape, Y.shape))
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8, random_state=28)
print("训练集样本形状:{}, 测试集样本形状:{}".format(x_train.shape, x_test.shape))

# 六、算法模型的选择/算法模型对象的构建
# 构建随机森林对象
"""
n_estimators=10, : 随机森林中构建多少个决策树
criterion="gini", : 决策树构建的时候基于什么指标构建
max_depth=None, : 每个决策树的最大深度
min_samples_split=2, : 在决策树构建过程中,当当前数据集的样本数目小于该值的时候,该数据集不进行继续的数据划分
min_samples_leaf=1, : 在决策树构建过程中,每个叶子节点至少要求具有多少个样本数据
max_features="auto", :给定选择划分数据的时候,从多少个随机特征属性中选择,默认是auto
bootstrap=True, : 产生子数据集的时候是否进行重采样的过程,True表示进行,False表示不进行
"""
algo = RandomForestClassifier(n_estimators=10)

# 七、算法模型的训练
algo.fit(x_train, y_train)

# 八、模型效果评估
y_hat = algo.predict(x_test)
print("在训练集上的模型效果(分类算法中为准确率):{}".format(algo.score(x_train, y_train)))
print("在测试集上的模型效果(分类算法中为准确率):{}".format(algo.score(x_test, y_test)))
print("在测试集上的召回率的值:{}".format(recall_score(y_true=y_test, y_pred=y_hat, average='micro')))

# 九、输出分类中特有的一些API
print("=" * 100)
y_predict = algo.predict(x_test)
print("预测值:\n{}".format(y_predict))
print("预测的实际类别:\n{}".format([np.array(Y_label_values)[int(i)] for i in y_predict]))
print("=" * 100)
print("属于各个类别的概率值:\n{}".format(algo.predict_proba(x_test)[:2]))
print("=" * 100)

# 获取所有的子模型(中间的所有决策树)
from sklearn import tree
import pydotplus

estimators_ = algo.estimators_
for i, treemodel in enumerate(estimators_):
    dot_data = tree.export_graphviz(decision_tree=treemodel, out_file=None,
                                    filled=True, rounded=True,
                                    special_characters=True)
    graph = pydotplus.graph_from_dot_data(dot_data)
    graph.write_png('./dt_imgs/rf_tree_{}.png'.format(i))
print('随机森林中各个特征的重要性权重系数:{}'.format(algo.feature_importances_))

# 获取落在决策树上的叶子下标
tmp_x = [[5.1, 3.3], [4.9, 3.0]]
print("临时数据的预测值:{}".format(algo.predict(tmp_x)))
print("临时数据的各个类别的预测概率:{}".format(algo.predict_proba(tmp_x)))
print("临时数据落在各个子决策树上的叶子序号:\n{}".format(algo.apply(tmp_x)))
View Code

RF的推广算法

RF算法在实际应用中具有比较好的特性,应用也比较广泛,主要应用在:分类、 回归、特征转换、异常点检测等。

常见的RF变种算法如下:

Extra Tree

Totally Random Trees Embedding(TRTE)

Isolation Forest

Extra Tree

Extra Tree是RF的一个变种,原理基本和RF一样,区别如下:

  1. RF会随机重采样来作为子决策树的训练集,而Extra Tree每个子决策树采用原始数据集训练;
  2. RF在选择划分特征点的时候会和传统决策树一样,会基于信息增益、信息增益率、基尼系数、均方差等原则来选择最优特征值;而Extra Tree会随机的选择一个特征值来划分决策树。

Extra Tree因为是随机选择特征值的划分点,这样会导致决策树的规模一般大于RF所生成的决策树。也就是说Extra Tree模型的方差相对于RF进一步减少。在某些情况下,Extra Tree的泛化能力比RF的强。

https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html#sklearn.ensemble.ExtraTreesClassifier

https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html#sklearn.ensemble.ExtraTreesRegressor

Totally Random Trees Embedding(TRTE)

TRTE是一种非监督的数据转化方式。将低维的数据集映射到高维,从而让映射到高维的数据更好的应用于分类回归模型。

TRTE算法的转换过程类似RF算法的方法,建立T个决策树来拟合数据(是类似KD-TREE一样基于特征属性的方差选择划分特征)。当决策树构建完成后,数据集里的每个数据在T个决策树中叶子节点的位置就定下来了,将位置信息转换为向量就完成了特征转换操作。

案例:有3棵决策树,各个决策树的叶子节点数目分别为:5,5,4,某个数据x划分到第一个决策树的第3个叶子节点,第二个决策树的第一个叶子节点,第三个决策树的第四个叶子节点,那么最终的x映射特征编码为:(0,0,1,0,0, 1,0,0,0,0, 0,0,0,1)

 https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.RandomTreesEmbedding.html#sklearn.ensemble.RandomTreesEmbedding

import pandas as pd
import numpy as np
import matplotlib as mpl
import warnings
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score
from sklearn.ensemble import RandomTreesEmbedding

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings(action='ignore')
# 一、加载数据
names = ['A', 'B', 'C', 'D', 'label']
path = '../datas/iris.data'
df = pd.read_csv(path, sep=',', header=None, names=names)

# 二、数据的清洗
df.replace('?', np.nan, inplace=True)
df.dropna(axis=0, how='any', inplace=True)

# 三、基于业务提取最原始的特征属性X和目标属性Y
X = df[names[:2]]
Y = df[names[-1]]
Y_label_values = np.unique(Y)
Y_label_values.sort()
random_index = np.random.permutation(len(Y))[:5]
for i in range(len(Y_label_values)):
    Y[Y == Y_label_values[i]] = i
Y = Y.astype(np.float)

# 四、数据的划分(将数据划分为训练集和测试集)
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8, random_state=28)

# 六、算法模型的选择/算法模型对象的构建
algo = RandomTreesEmbedding(n_estimators=3, max_depth=5)

# 七、算法模型的训练
algo.fit(x_train, y_train)

# 获取所有的子模型(中间的所有决策树)
from sklearn import tree
import pydotplus

# estimators_ = algo.estimators_
# for i, treemodel in enumerate(estimators_):
#     dot_data = tree.export_graphviz(decision_tree=treemodel, out_file=None,
#                                     filled=True, rounded=True,
#                                     special_characters=True)
#     graph = pydotplus.graph_from_dot_data(dot_data)
#     graph.write_png('trt_tree_{}.png'.format(i))

# 数据维度扩展
x_train2 = algo.transform(x_train)
x_test2 = algo.transform(x_test)
print("扩展前训练数据大小:{},扩展后数据大小:{}".format(x_train.shape, x_train2.shape))
print("扩展前测试数据大小:{},扩展后数据大小:{}".format(x_test.shape, x_test2.shape))
View Code

Isolation Forest(IForest)

IForest是一种异常点检测算法,使用类似RF的方式来检测异常点;IForest算法和RF算法的区别在于:

  1. 在随机采样的过程中,一般只需要少量数据即可;
  2. 在进行决策树构建过程中,IForest算法会随机选择一个划分特征,并对划分特征随机选择一个划分阈值;
  3. IForest算法构建的决策树一般深度max_depth是比较小的。

区别原因:目的是异常点检测,所以只要能够区分异常的即可,不需要大量数据;另外在异常点检测的过程中,一般不需要太大规模的决策树。

对于异常点的判断,则是将测试样本x拟合到T棵决策树上。计算在每棵树上该样本的叶子节点的深度ht(x)。从而计算出平均深度h(x);然后就可以使用下列公式计算样本点x的异常概率值,p(s,m)的取值范围为[0,1],越接近于1,则是异常点的概率越大。备注:如果落在的叶子节点为正常样本点,那么当前决策树不考虑,如果所有决策树上都是正常样本点,那么直接认为异常点概率为0.

 https://scikit-learn.org/0.19/modules/generated/sklearn.ensemble.IsolationForest.html#sklearn.ensemble.IsolationForest

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False

np.random.seed(28)

# 产生模拟数据
x = 0.3 * np.random.randn(100, 2)
x_train = np.vstack((x + 2, x - 2))
x = 0.3 * np.random.randn(20, 2)
x_test = np.vstack((x + 2, x - 2))
x_outliers = np.random.uniform(low=-4, high=4, size=(20, 2))

# 构建训练模型
algo = IsolationForest(n_estimators=3, random_state=28)
algo.fit(x_train) #如果数据集有异常样本,需要给定参数y。

# 获取预测值(范围为1表示正常样本,返回-1表示异常样本)
y_pred_train = algo.predict(x_train)
print(y_pred_train)
y_pred_test = algo.predict(x_test)
print(y_pred_test)
y_pred_outliers = algo.predict(x_outliers)
print(y_pred_outliers)

# 返回决策函数,模型情况下,在sklearn中,当决策函数值大于0的时候,表示属于正常样本,否则属于异常样本
print(algo.decision_function(x_outliers))
print(algo.decision_function(x_test))

# 获取所有的子模型
# from sklearn import tree
# import pydotplus
#
# estimators_ = algo.estimators_
# for i, treemodel in enumerate(estimators_):
#     dot_data = tree.export_graphviz(decision_tree=treemodel, out_file=None,
#                                     filled=True, rounded=True,
#                                     special_characters=True)
#     graph = pydotplus.graph_from_dot_data(dot_data)
#     graph.write_png('isolation_{}.png'.format(i))
#     np.hstack()

# 画图
xx, yy = np.meshgrid(np.linspace(-5, 5, 50), np.linspace(-5, 5, 50))
z = algo.decision_function(np.c_[xx.ravel(), yy.ravel()])
z = z.reshape(xx.shape)

# contourf画等高线的区域图
plt.contourf(xx, yy, z, cmap=plt.cm.Blues_r)
plt.scatter(x_train[:, 0], x_train[:, 1], c='b')
plt.scatter(x_test[:, 0], x_test[:, 1], c='g')
plt.scatter(x_outliers[:, 0], x_outliers[:, 1], c='r')
plt.show()
View Code

RF随机森林总结

RF的主要优点:

  • 训练可以并行化,对于大规模样本的训练具有速度的优势;
  • 由于进行随机选择决策树划分特征列表,这样在样本维度比较高的时候,仍然具有比较高的训练性能;
  • 给以给出各个特征的重要性列表;
  • 由于存在随机抽样,训练出来的模型方差小,泛化能力强;
  • RF实现简单;
  • 对于部分特征的缺失不敏感。

RF的主要缺点:

  • 在某些噪音比较大的特征上,RF模型容易陷入过拟合;
  • 取值比较多的划分特征对RF的决策会产生更大的影响,从而有可能影响模型的效果。

Boosting

提升学习(Boosting:是一种机器学习技术,可以用于回归和分类的问题,它每一步产生弱预测模型(如决策树),并加权累加到总模型中;如果每一步的弱预测模型的生成都是依据损失函数的梯度方式的,那么就称为梯度提升(Gradient boosting);

提升技术的意义:如果一个问题存在弱预测模型,那么可以通过提升技术的办法 得到一个强预测模型;

 常见的模型有:

Adaboost

Gradient Boosting(GBT/GBDT/GBRT)

AdaBoost算法原理

Adaptive Boosting是一种迭代算法。每轮迭代中会在训练集上产生一个新的学习器,然后使用该学习器对所有样本进行预测,以评估每个样本的重要性(Informative)。换句话来讲就是,算法会为每个样本赋予一个权重,每次用训练好的学习器标注/预测各个样本,如果某个样本点被预测的越正确,则将其权重降低;否则提高样本的权重。权重越高的样本在下一个迭代训练中所占的比重就 越大,也就是说越难区分的样本在训练过程中会变得越重要;

整个迭代过程直到错误率足够小或者达到一定的迭代次数为止。

样本加权

Adaboost算法将基分类器的线性组合作为强分类器,同时给分类误差率较小的基本分类器以大的权值,给分类误差率较大的基分类器以小的权重值;构建的线性组合为:

最终分类器是在线性组合的基础上进行Sign函数转换:

Sign函数

损失函数(以错误率作为损失函数):

损失函数:

第k-1轮的强学习器:

第k轮的强学习器:

损失函数:

使下列公式达到最小值的αm和Gm就是AdaBoost算法的最终求解值:

Gm这个分类器在训练的过程中,是为了让误差率最小,所以可以认为G越小其实就是误差率越小。

对于αm而言,通过求导然后令导数为零,可以得到公式(log对象可以以e为底也可以 以2为底):

Adaboost算法构建过程

1. 假设训练数据集T={(X1 ,Y1 ),(X2 ,Y2 )....(Xn,Yn)}

2. 初始化训练数据权重分布。

3. 使用具有权值分布Dm的训练数据集学习,得到基本分类器

4. 计算Gm(x)在训练集上的分类误差

 

5. 计算Gm(x)模型的权重系数αm:

6. 权重训练数据集的权值分布

7. 这里Zm是规范化因子(归一化)

8. 构建基本分类器的线性组合

9. 得到最终分类器

Adaboost算法的直观理解

权重训练用下列训练样本,试用AdaBoost算法学习一个强分类器

对于m=1

初始化训练数据集的权值分布

在权值分布为D1的训练数据上,阈值v取2.5时误差率最低,故基本分类器为:

G1(x)在训练数据集上的误差率:

计算G1的系数:

分类器sign(f1(x))在训练数据集上有3个误分类点。

对于m=2

更新数据集的权值分布:

在权值分布为D2的训练数据上,阈值v取8.5时误差率最低,故基本分类器为:

G2(x)在训练数据集上的误差率:

计算G2的系数:

分类器sign(f2(x))在训练数据集上有3个误分类点。

对于m=3

更新数据集的权值分布

在权值分布为D3的训练数据上,阈值v取5.5时误差率最低,故基本分类器为:

G3(x)在训练数据集上的误差率:

计算G3的系数:

分类器sign(f3(x))在训练数据集上有0个误分类点;结束循环。

import numpy as np


def entropy(t):
    """
    计算信息熵
    :param t:  是一个概率组成的集合
    :return:
    """
    return np.sum([-p * np.log2(p) for p in t if p != 0])


def h1(y=[1, 1, 1, -1, -1, -1, 1, 1, 1, -1], w=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]):
    """
    计算一下数据的信息熵
    :return:
    """
    # 1. 计算类别为1的概率和
    p1 = np.sum(np.array(w)[np.array(y) == 1])
    # 2. 计算类别为-1的概率和
    p2 = 1 - p1
    # 计算信息熵
    return entropy([p1, p2])


def h2(split=3, y=[1, 1, 1, -1, -1, -1, 1, 1, 1, -1], w=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]):
    y = np.array(y)
    w = np.array(w)

    # 1. 左侧的信息熵
    data_y = y[:split]
    data_w = w[:split]
    p10 = np.sum(data_w)
    p11 = np.sum(data_w[data_y == 1]) / p10
    p12 = 1 - p11
    p1 = entropy([p11, p12])

    # 2. 右侧的信息熵
    data_y = y[split:]
    data_w = w[split:]
    p20 = np.sum(data_w)
    p21 = np.sum(data_w[data_y == 1]) / p20
    p22 = 1 - p21
    p2 = entropy([p21, p22])

    # 3. 计算信息熵
    return p10 * p1 + p20 * p2


def h3(errs=[], w=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]):
    # w更新
    w = np.array(w)
    # 1. 计算错误率
    e = np.sum([w[i] for i in errs])
    # 2. 计算alpha
    alpha = 0.5 * np.log2((1 - e) / e)
    # 3. 计算更新后的权重值
    accs = [i for i in range(len(w)) if i not in errs]
    w[accs] = w[accs] * (np.e ** (-alpha))
    w[errs] = w[errs] * (np.e ** alpha)
    w = w / np.sum(w)

    return e, alpha, w


def h4(errs=[], w=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]):
    # w更新
    w = np.array(w)
    # 1. 计算错误率
    e = np.sum([w[i] for i in errs])
    # 2. 计算alpha
    alpha = 0.5 * np.log((1 - e) / e)
    # 3. 计算更新后的权重值
    accs = [i for i in range(len(w)) if i not in errs]
    w[accs] = w[accs] * (np.e ** (-alpha))
    w[errs] = w[errs] * (np.e ** alpha)
    w = w / np.sum(w)

    return e, alpha, w


def calc1():
    print("第一个子模型的选择")
    print("=" * 100)
    a = h1()
    print("原始数据的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9)))
    e, a1, w = h3(errs=[6, 7, 8])
    print("第1次迭代后的错误率:{}".format(e))
    print("第1次的模型权重为:{}".format(a1))
    print("第1次更新后的样本权重为:{}".format(w))

    print("\n第二个子模型的选择")
    print("=" * 100)
    a = h1(w=w)
    print("总的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3, w=w)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6, w=w)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9, w=w)))
    e, a2, w = h3(errs=[0, 1, 2, 9], w=w)
    print("第2次迭代后的错误率:{}".format(e))
    print("第2次的模型权重为:{}".format(a2))
    print("第2次更新后的样本权重为:{}".format(w))

    print("\n第三个子模型的选择")
    print("=" * 100)
    a = h1(w=w)
    print("总的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3, w=w)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6, w=w)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9, w=w)))
    e, a3, w = h3(errs=[3, 4, 5], w=w)
    print("第3次迭代后的错误率:{}".format(e))
    print("第3次的模型权重为:{}".format(a3))
    print("第3次更新后的样本权重为:{}".format(w))

    print("\n第四个子模型的选择")
    print("=" * 100)
    a = h1(w=w)
    print("总的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3, w=w)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6, w=w)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9, w=w)))
    e, a4, w = h3(errs=[6, 7, 8], w=w)
    print("第4次迭代后的错误率:{}".format(e))
    print("第4次的模型权重为:{}".format(a4))
    print("第4次更新后的样本权重为:{}".format(w))

    print("=" * 100)
    print(a1)
    print(a2)
    print(a3)
    print(a4)

    print("=" * 100)
    print("1:{}".format(a1 - a2 + a3 + a4))
    print("2:{}".format(-a1 - a2 + a3 - a4))
    print("3:{}".format(-a1 + a2 + a3 - a4))
    print("4:{}".format(-a1 + a2 - a3 - a4))


def calc2():
    print("第一个子模型的选择")
    print("=" * 100)
    a = h1()
    print("原始数据的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9)))
    print("第一个子模型选择以2.5划分数据:")
    e, a1, w = h4(errs=[6, 7, 8])
    print("第1次迭代后的错误率:{}".format(e))
    print("第1次的模型权重为:{}".format(a1))
    print("第1次更新后的样本权重为:{}".format(w))

    print("\n第二个子模型的选择")
    print("=" * 100)
    a = h1(w=w)
    print("总的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3, w=w)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6, w=w)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9, w=w)))
    print("第二个子模型选择以8.5划分数据:")
    e, a2, w = h4(errs=[3, 4, 5], w=w)
    print("第2次迭代后的错误率:{}".format(e))
    print("第2次的模型权重为:{}".format(a2))
    print("第2次更新后的样本权重为:{}".format(w))

    print("\n第三个子模型的选择")
    print("=" * 100)
    a = h1(w=w)
    print("总的信息熵:{}".format(a))
    print("以2.5分割的信息增益:{}".format(a - h2(split=3, w=w)))
    print("以5.5分割的信息增益:{}".format(a - h2(split=6, w=w)))
    print("以8.5分割的信息增益:{}".format(a - h2(split=9, w=w)))
    print("第三个子模型选择以5.5划分数据:")
    e, a3, w = h4(errs=[0, 1, 2, 9], w=w)
    print("第3次迭代后的错误率:{}".format(e))
    print("第3次的模型权重为:{}".format(a3))
    print("第3次更新后的样本权重为:{}".format(w))

    print("=" * 100)
    print(a1)
    print(a2)
    print(a3)

    print("=" * 100)
    print("1:{}".format(a1 + a2 - a3))
    print("2:{}".format(-a1 + a2 - a3))
    print("3:{}".format(-a1 + a2 + a3))
    print("4:{}".format(-a1 - a2 + a3))


# print("以2为底数的计算方式:")
# calc1()

print("以e为底数的计算方式:")
calc2()
View Code

AdaBoost scikit-learn相关参数

AdaBoost总结

AdaBoost的优点如下:

  • 可以处理连续值和离散值;
  • 模型的鲁棒性比较强;
  • 解释强,结构简单。

AdaBoost的缺点如下:

  • 对异常样本敏感,异常样本可能会在迭代过程中获得较高的权重值,最终影响模型 效果。

梯度提升迭代决策树GBDT

GBDT也是Boosting算法的一种,但是和AdaBoost算法不同;区别如下:

AdaBoost算法是利用前一轮的弱学习器的误差来更新样本权重值,然后一轮一轮的迭代;GBDT也是迭代,但是GBDT要求弱学习器必须是CART模型,而且GBDT在模型训练的时候,是要求模型预测的样本损失尽可能的小。

备注:所有GBDT算法中,底层都是回归树

别名:GBT(Gradient Boosting Tree)、GTB(Gradient Tree Boosting)、 GBRT(Gradient Boosting Regression Tree)、GBDT(Gradient Boosting Decison Tree)、MART(Multiple Additive Regression Tree)。

GBDT直观理解

当给定步长时候,给定一个步长step,在构建下一棵树的时候使用step*残差值作为输入值,这种方式可以减少过拟合的发生。

梯度提升迭代决策树GBDT

GBDT由三部分构成:DT(Regression Decistion Tree)、GB(Gradient Boosting) 和Shrinkage(衰减)

由多棵决策树组成,所有树的结果累加起来就是最终结果。

迭代决策树和随机森林的区别:

  • 随机森林使用抽取不同的样本构建不同的子树,也就是说第m棵树的构建和前m-1棵树的结果是没有关系的。
  • 迭代决策树在构建子树的时候,使用之前子树构建结果后形成的残差作为输入数据构建下 一个子树;然后最终预测的时候按照子树构建的顺序进行预测,并将预测结果相加。

GBDT算法原理

给定输入向量X和输出变量Y组成的若干训练样本(X1 ,Y1 ),(X2 ,Y2 )......(Xn,Yn),目标 是找到近似函数F(X),使得损失函数L(Y,F(X))的损失值最小。

L损失函数一般采用最小二乘损失函数或者绝对值损失函数:

最优解为:

假定F(X)是一族最优基函数fi(X)的加权和:

以贪心算法的思想扩展得到Fm(X),求解最优f:

以贪心法在每次选择最优基函数f时仍然困难,使用梯度下降的方法近似计算。

给定常数函数F0 (X)。

根据梯度下降计算学习率。

使用数据(xi,αim)(i=1……n )计算拟合残差找到一个CART回归树,得到第m棵树:

更新模型:

import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import r2_score

np.random.seed(28)
x = np.random.randn(10, 2) * 5
y = np.random.randn(10, 1) * 3
y_true = y

algo = DecisionTreeRegressor(max_depth=1)
algo.fit(x, y)
print("训练数据上的效果:{}".format(algo.score(x, y)))
print("实际y值:\n{}".format(y.reshape(-1)))
print("预测y值:\n{}".format(algo.predict(x).reshape(-1)))

'''方式一'''
# GBDT的构建过程(回归, 使用平方和损失函数的执行过程)
models = []
# 构建第一个模型: 在回归中,第一个模型预测为均值
m1 = np.mean(y)
models.append(m1)
# 构建后面的模型(全部为回归决策树)
learn_rate = 0.01
pre_m = m1
n = 10000
for i in range(n):
    # 更改y值
    if i == 0:
        y = learn_rate * pre_m - y
    else:
        y = learn_rate * pre_m.predict(x).reshape(y.shape) - y
    # 模型训练
    model = DecisionTreeRegressor(max_depth=1)
    model.fit(x, y)
    models.append(model)
    pre_m = model
print("模型构建完成")
print("开始预测")
y_hat = np.zeros_like(y)
print(y_hat.shape)
for i in range(n + 1):
    # 获取第i个模型
    model = models[i]
    # 使用模型得到预测值
    if i == 0:
        y_hat = y_hat + learn_rate * model
    else:
        if i % 2 == 0:
            y_hat = y_hat + learn_rate * model.predict(x).reshape(y.shape)
        else:
            y_hat = y_hat - learn_rate * model.predict(x).reshape(y.shape)
print("预测值为:\n{}".format(y_hat.reshape(-1)))
print("单个简单的决策树的效果:{}".format(r2_score(y_true, algo.predict(x))))
print("GBDT的效果:{}".format(r2_score(y_true, y_hat)))

'''方式二'''
# print("\n\n分类效果")
# x = np.random.randn(10, 2) * 5
# y = np.array([1] * 6 + [-1] * 4).astype(np.float)
# y_true = y
# # GBDT的构建过程(分类, 使用平方和损失函数的执行过程)
# models = []
# # 构建第一个模型: 在回归中,第一个预测为ln(正例/负例)
# m1 = np.log(6 / 4)
# models.append(m1)
# # 构建后面的模型(全部为回归决策树)
# learn_rate = 0.01
# pre_m = None
# n = 1000
# for i in range(n):
#     # 更改y值
#     if i == 0:
#         y = y / (1 + np.exp(y * m1))
#     else:
#         y = y / (1 + np.exp(y * pre_m.predict(x).reshape(y.shape)))
#     # 模型训练
#     model = DecisionTreeRegressor(max_depth=1)
#     model.fit(x, y)
#     models.append(model)
#     pre_m = model
# print("模型构建完成")
# print("开始预测")
# y_hat = np.zeros_like(y)
# print(y_hat.shape)
# for i in range(n + 1):
#     # 获取第i个模型
#     model = models[i]
#     # 使用模型得到预测值
#     if i == 0:
#         y_hat = y_hat + model
#     else:
#         y_hat = y_hat + model.predict(x).reshape(y.shape)
# print("预测值为:\n{}".format(y_hat.reshape(-1)))
# print(y_true)
# print("单个简单的决策树的效果:{}".format(r2_score(y_true, algo.predict(x))))
# print("GBDT的效果:{}".format(r2_score(y_true, y_hat)))
View Code
import numpy as np
from sklearn.tree import DecisionTreeRegressor, DecisionTreeClassifier
from sklearn.metrics import r2_score, accuracy_score

print("回归效果")
np.random.seed(28)
x = np.random.randn(10, 2) * 5
y = np.random.randn(10, 1) * 3
y_true = y

algo = DecisionTreeRegressor(max_depth=1)
algo.fit(x, y)
print("训练数据上的效果:{}".format(algo.score(x, y)))
print("实际y值:\n{}".format(y.reshape(-1)))
print("预测y值:\n{}".format(algo.predict(x).reshape(-1)))

# GBDT的构建过程(回归, 使用平方和损失函数的执行过程)
models = []
# 构建第一个模型: 在回归中,第一个模型预测为均值
m1 = np.mean(y)
models.append(m1)
# 构建后面的模型(全部为回归决策树)
learn_rate = 0.01
pre_m = m1
n = 10000
for i in range(n):
    # 更改y值
    if i == 0:
        y = learn_rate * pre_m - y
    else:
        y = learn_rate * pre_m.predict(x).reshape(y.shape) - y
    # 模型训练
    model = DecisionTreeRegressor(max_depth=1)
    model.fit(x, y)
    models.append(model)
    pre_m = model
print("模型构建完成")
print("开始预测")
y_hat = np.zeros_like(y)
print(y_hat.shape)
for i in range(n + 1):
    # 获取第i个模型
    model = models[i]
    # 使用模型得到预测值
    if i == 0:
        y_hat = y_hat + learn_rate * model
    else:
        if i % 2 == 0:
            y_hat = y_hat + learn_rate * model.predict(x).reshape(y.shape)
        else:
            y_hat = y_hat - learn_rate * model.predict(x).reshape(y.shape)
print("实际值为:\n{}".format(y_true.reshape(-1)))
print("预测值为:\n{}".format(y_hat.reshape(-1)))
print("单个简单的决策树的效果:{}".format(r2_score(y_true, algo.predict(x))))
print("GBDT的效果:{}".format(r2_score(y_true, y_hat)))

# https://www.cnblogs.com/ModifyRong/p/7744987.html
print("\n\n分类效果")
x = np.random.randn(10, 2) * 5
y = np.array([1] * 6 + [0] * 4).astype(np.float)
y_true = y
# y编程哑编码的形式
y1 = np.array([0] * 6 + [1] * 4).astype(np.float)
y2 = y
ys = [y1, y2]
# GBDT的构建过程(分类, 使用平方和损失函数的执行过程)
models = []
# 构建第一个模型: 在分类中,第一个预测为ln(正例/负例)
m1 = np.array([0, 0])
models.append(m1)
# 构建后面的模型(全部为回归决策树)
learn_rate = 0.01
pre_m = m1
n = 5
for i in range(n):
    # 更改y值
    tmp_algo = []
    for k in range(2):
        if i == 0:
            p = np.exp(pre_m[k]) / np.sum(np.exp(pre_m))
            ys[k] = ys[k] - p
        else:
            pred_y = np.array(list(map(lambda algo: algo.predict(x), pre_m)))
            p = np.exp(pred_y[k].reshape(y.shape)) / np.sum(np.exp(pred_y), axis=0)
            ys[k] = ys[k] - p
        # 模型训练
        model = DecisionTreeRegressor(max_depth=1)
        model.fit(x, ys[k])
        tmp_algo.append(model)

    models.append(tmp_algo)
    pre_m = tmp_algo

print("模型构建完成")
print("开始预测")
y_hat = np.zeros(shape=(2, y.shape[0]))
print(y_hat.shape)
for i in range(n + 1):
    # 获取第i个模型
    model = models[i]
    # 使用模型得到预测值
    for k in range(2):
        if i == 0:
            y_hat[k] = model[k]
        else:
            y_hat[k] += model[k].predict(x).reshape(y.shape)
print("初步预测值为:\n{}".format(y_hat.T))
print("最终预测值为:\n{}".format(np.argmax(y_hat, axis=0)))
print("实际值为:\n{}".format(y_true))

algo = DecisionTreeClassifier(max_depth=1)
algo.fit(x, y_true)
print("单个简单的决策树的效果:{}".format(accuracy_score(y_true, algo.predict(x))))
print("GBDT的效果:{}".format(accuracy_score(y_true, np.argmax(y_hat, axis=0))))
View Code

GBDT回归算法和分类算法的区别

 两者唯一的区别就是选择不同的损失函数。

回归算法选择的损失函数一般是均方差(最小二乘)或者绝对值误差;而在分类算 法中一般的损失函数选择对数函数来表示。

GBDT scikit-learn相关参数

GBDT总结

GBDT的优点如下:

  • 可以处理连续值和离散值;
  • 在相对少的调参情况下,模型的预测效果也会不错;
  • 模型的鲁棒性比较强。

GBDT的缺点如下:

  • 由于弱学习器之间存在关联关系,难以并行训练模型。也就是模型训练的速度慢。

Bagging、Boosting的区别

1. 样本选择:Bagging算法是有放回的随机采样;Boosting算法是每一轮训练集不变,只是训练集中的每个样例在分类器中的权重发生变化或者目标属性y发生变化,而权重&y值都是根据上一轮的分类结果进行调整;

2. 样例权重:Bagging使用随机抽样,样例是等权重;Boosting根据错误率不断的调整样例的权重值,错误率越大则权重越大(Adaboost);

3. 预测函数:Bagging所有预测模型的权重相等;Boosting算法对于误差小的分类器具有更大的权重(Adaboost)。

4. 并行计算:Bagging算法可以并行生成各个基模型;Boosting理论上只能顺序生产,因为后一个模型需要前一个模型的结果;

5. Bagging是减少模型的variance(方差);Boosting是减少模型的Bias(偏度)。

6. Bagging里每个分类模型都是强分类器,因为降低的是方差,方差过高需要降低是过拟合;Boosting里每个分类模型都是弱分类器,因为降低的是偏度,偏度过高是欠拟合。

 

Bagging对样本重采样,对每一轮的采样数据集都训练一个模型,最后取平均。由于样本集的相似性和使用的同种模型,因此各个模型的具有相似的bias和variance;

Stacking

Stacking是指训练一个模型用于组合(combine)其它模型(基模型/基学习器)的技术。即首先训练出多个不同的模型,然后再以之前训练的各个模型的输出作为输入来新训练一个新的模型,从而得到一个最终的模型。一般情况下使用单层的Logistic回归作为组合模型。

 XGBoost

XGBoost是GBDT算法的一种变种,是一种常用的有监督集成学习算法;是一种伸缩性强、便捷的可并行构建模型的Gradient Boosting算法。

XGBoost官网:https://xgboost.readthedocs.io/en/latest/

XGBoost Github源码位置:https://github.com/dmlc/xgboost

XGBoost支持开发语言:Python、R、Java、Scala、C++等。

XGBoost安装

安装方式一:编译Github上的源码,参考https://xgboost.readthedocs.io/en/latest/build.html

安装方式二:

直接使用python的whl文件进行安装,要求python版本3.5或者 3.6;下载链接:https://www.lfd.uci.edu/~gohlke/pythonlibs/#xgboost;

安装参考命令:pip install f:///xgboost-0.7-cp36-cp36m-win_amd64.whl

CART、GBDT

模型

目标函数:

GBDT

GBDT的目标函数:

XGBoost公式推导

XGBoost的目标函数:

第t次迭代后,模型的预测等于前t-1次的模型加上第t课树的预测:

目标函数可以写成:

将函数中的所有常数项全部去掉,可以得到以下公式:

将函数f和正则项带入公式中得到以下公式:

定义每个叶子节点j上的样本集合为: Ij:

将样本累加操作转换为叶节点的操作:

最终的目标函数:

如果树的结构确定(q函数确定),为了使目标函数最小,可以令导数为0,可以求 得最优的w,将w带入目标函数,可以得到最终的损失为:

说明:gi表示一阶导数,hi表示二阶导数。

XGBoost的学习策略

当树的结构确定的时候,我们可以得到最优的叶子点分数以及对应的最小损失值, 问题在于如何确定树结构?

  • 暴力穷举所有可能的结构,选择损失值最小的;(很难求解)
  • 贪心法,每次尝试选择一个分裂点进行分裂,计算操作前后的增益,选择增益最大 的方式进行分裂。

决策树相关算法计算指标:

  • ID3算法:信息增益
  • C4.5算法:信息增益率
  • CART算法:Gini系数

树节点分裂方法

精确算法:遍历所有特征的所有可能的分割点,计算gain值,选择最大的gain 值对应的(feature,value)进行分割。

近似算法:对于每个特征,只考虑分位点,减少计算复杂度

XGBoost树节点划分方法

XGBoost不是简单的按照样本个数进行分位的,而是按照上一轮的预测误差函 数的二阶导数值作为权重来进行划分的:

XGBoost的其它特性

列采样(column subsampling):借鉴随机森林的做法,支持列抽样,不仅可以降低过 拟合,还可以减少计算量;

支持对缺失值的自动处理。对于特征的值有缺失的样本,XGBoost可以自动学习分裂方向;

XGBoost支持并行。XGBoost的并行是特征粒度上的,在计算特征的Gain的时候,会 并行执行,但是在树的构建过程中,还是串行构建的;

XGBoost算法中加入正则项,用于控制模型的复杂度,最终模型更加不容易过拟合;

XGBoost基学习器支持CART、线性回归、逻辑回归;

GBoost支持自定义损失函数(要求损失函数二阶可导)。

XGBoost相关参数

参考链接:https://xgboost.readthedocs.io/en/latest/python/python_api.html#module-%20xgboost.sklearn

总结

集成算法思路

  • 如果单个模型由于受噪音数据的影响,导致构建的模型效果不稳定,我们将多个不太稳定的模型的预测结果数据合并,从而降低这个不稳定的情况 ---> Bagging
  • 如果单个模型的预测能力比较弱(至少效果大于0.5),可以考虑串行的构建多个弱学习器,让每个学习器专注于不同的数据样本的区分预测,从而可以保证通过多个模型的融合让最终的预测结果变的更好 --> Boosting

Bagging

思想:基于有放回的重采样构建不同的数据子集,然后使用每个数据子集构建对应的子模型,最终在预测的时候将多个子模型的结果合并即可。

代表算法:随机森林

Boosting
思想:第m个模型的构建,会基于前面m-1个模型的结果来构建;让模型的构建过程是向着让所有样本的预测值和实际值差值更新的方向的变化的。

代表算法:

Adaboost

  • 第m个模型的构建,会基于前面m-1个模型的结果来构建;让第m个模型的构建过程更加的注重前面m-1个模型预测失败的样本。通过每次构建完子模型修改样本权重来进行的,如果该样本在当前子模型预测失败,那么该样本在下一个模型构建的时候对应的权重变大,如果该样本在当前子模型预测成功,那么该样本在下一个模型构建的时候对应的权重变小。

GBDT

  • 第m个模型的构建,会基于前面m-1个模型的结果来构建;让第m个模型的构成过程是基于之前模型的损失函数的梯度方向来进行变化,变化的主要目的是让损失函数的值趋近为0,所以每次子模型的构建都是使用上一个模型的损失函数的偏导数作为当前子模型构建的目标属性。
  • GBDT中通过更改y值让模型预测的越来越准确。

Stacking

思想:分为两个阶段,第一个阶段主要负责基于原始数据构建不用的子模型;第二个阶段主要使用第一个阶段的输出值作为特征属性矩阵,构建最终的子模型。

eg:

原始数据:

x1,x2,x3,x4,y

第一阶段:

1. 模型1

a. 使用x1,x2,x3,x4作为特征属性,使用y作为目标属性构建一个子模型m1,

b. 使用m1对所有的训练数据做一个预测,得到一个预测值h1

2. 模型2

a. 使用x1,x2,x3,x4作为特征属性,使用y作为目标属性构建一个子模型m2,

b. 使用m2对所有的训练数据做一个预测,得到一个预测值h2

3. 模型3

a. 使用x1,x2,x3,x4作为特征属性,使用y作为目标属性构建一个子模型m3,

b. 使用m3对所有的训练数据做一个预测,得到一个预测值h3

4. 模型4

a. 使用x1,x2,x3,x4作为特征属性,使用y作为目标属性构建一个子模型m4,

b. 使用m4对所有的训练数据做一个预测,得到一个预测值h4

5. 模型5

a. 使用x1,x2,x3,x4作为特征属性,使用y作为目标属性构建一个子模型m5,

b. 使用m5对所有的训练数据做一个预测,得到一个预测值h5

第二阶段:

a. 使用第一阶段的输出值作为特征属性矩阵(h1,h2,h3,h4,h5),使用y作为目标属性构建一个最终的子模型

预测过程:

首先使用第一阶段的所有子模型对输入的x做预测产生一个预测向量,然后将预测向量输入第二阶段的子模型中产生最终的预测值

XGBoost

相比于GBDT而言:

  • 内部使用损失函数的二阶导函数来构建当前模型,模型的效果会比GBDT的好
  • 内部的目标函数中嵌入了正则化项的部分,用于限制模型复杂度,模型更加不容易出现过拟合的情况
  • XGBoost是并行构建,XGBoost的并行体现在底层选择划分特征的过程是同时计算多个特征信息的,相比于GBDT中的决策树的构建过程,速度要快

 

posted @ 2019-08-20 22:27  码迷-wjz  阅读(829)  评论(0)    收藏  举报