集成算法介绍及实现
1 集成算法基本概念
(1)定义:集成算法是通过建立几个模型来解决单一预测问题。它的工作原理是生成多个分类器/模型,各自独立地学习和做出预测(如图所示)。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRHhCTsV-1606466244867)(attachment:image-2.png)]
(2)用处:让机器学习效果更好
(3)算法有:Bagging、随机森林、Boosting、Stacking
2 集成算法介绍
2.1 Bagging
Bagging(bootstrap aggregation)算法策略/工作流程:是对数据进行自助采样法,对结果进行简单投票法。 对于给定的包含m个样本的数据集,我们随机选择一个样本放入采样集中,再把该样本放回初始数据集(即有放回抽样),使得下次采样仍有可能被选中。我们这样选择的样本有的在采样集里面重复出现,有的则从未出现。我们分类任务使用简单投票法;对分类任务使用简单平均法;若分类投票出现相同的票数情况,则随机选择一个。
公式如下:
学习自:集成算法(Bagging,随机森林)
2.2 随机森林
随机森林(Random Forest,简称RF)是一个包含多棵决策树的分类器,并且输出的类别是由个别树输出的类别的众数而定。它是Bagging的一个扩展变体,在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树的训练过程中映入了随机属性选择。
- 随机表现在:
- 一次随机选出一个样本,有放回的抽样,重复N次(有可能出现重复的样本)
- 随机取出m个特征, m <<M,建立决策树
- 工作流程:
- 从样本集中用Bootstrap采样选出n个样本;
- 从所有属性中随机选择k个属性,选择最佳分割属性作为节点建立CART决策树;
- 重复以上两个步骤m次,即建立了m棵CART决策树
- 这m棵CART决策树形成随机森林,通过投票表决结果,决定数据属于哪一类
- 优势:
- RF可以处理高维度特征的数据,并且不用做特征选择;
- 随机森林在训练完之后,能够给出哪些特征比较重要;
- 容易做成并行化方法,速度比较快;
- 可以进行可视化展示,便于分析。
- 随机森林、Bagging和决策树的联系:
- RF是Bagging的一个扩展变体,在其工作流程中就可以反映;
- 决策树作为随机森林的基本分类器,当然随机森林也可使用其他分类器,如SVM,Logistic回归等分类器,习惯上,这些分类器组成的总分类器,仍叫做随机森林。
2.3 Boosting
-
介绍:Boosting是集成方法里的一类,也是使用多个基分类器,但它不是并列使用的,各个分类器有自己的权值,它重点关注难分类的数据,即Boosting对分类难易程度不同的数据会赋予分类器不同的权值。Boosting的核心是通过赋予基分类器不同的权值来将弱学习机调整为强学习机。简而言之,Boosting随着学习的积累从弱变强。
-
工作流程:
- 从训练集D中以无放回抽样方式随机抽取一个训练子集d1,用于弱学习机C1的训练。
- 从训练集D中以无放回抽样方式随机抽取一个训练子集d2,并将C1中误误分类样本的50%加入到训练集中,训练得到弱学习机C2。
- 从训练集D中抽取C1和C2分类结果不一致的训练样本生成训练样本集d3,用d3来训练第三个弱学习机C3。
- 通过多数投票来组合弱学习机C1、C2和C3。
- 思想:
- 训练:1)赋予每个训练记录一个权值wi(权值反映对元组分类的困难程度);2)迭代地学习k个分类器: 学习得到分类器Mi之后, 更新数据样本的权值, 使得其后的分类器Mi+1 “更关注”被Mi误分类的训练元组;
- 分类:每个基分类器独立地对待分类实例产生类预测,通过基分类器的加权投票产生待分类实例最终的类预测, 其中每个分类器投票的权重是其准确率的函数
- 公式如下
- 举例总结:Boosting算法不是并行计算的,而是现有树1的结果,再在树1的结果上训练2弥补树1的残差,依次往后。这样也可以理解为,将弱分类器,不断地进行调整加强。
- Boosting模型的经典代表有:AdaBoost、Xgboost
- Adaboost介绍:Adaboost有两个权重,一个权重是训练样本的,一个是分类器。
1)训练样本的权重:所有数据样本初始权重都相等,在经过训练才慢慢不同,每次迭代都会更新一次权重,分类正确的样本权重会变小,分类错误的样本权重会变大(也就说:重点关注错分难分的数据,越难分类的样本权重越大)。
图片表示训练样本的权重变化过程:
2)分类器的权重:该权重跟分类器的分类准确性相关,分类准确性越高,权重越大。每次迭代都会加上一个新的基分类器,并且会求出每个基分类器的权重,这跟最终模型相关。
3)两个权重的取值变化都有现成的公式,数据样本的权重会随着每次迭代发生变化,而基分类器则是每次迭代就确定一个基分类器的权重(因为每次迭代就会加多一个基分类器,但数据样本一直都是那些数据,所以一个是新加,一个是更新)。
学习自:
- Xgboost: 后面再单独讲
2.4 Stacking
-
介绍:本质就是聚合多个分类或回归模型得出结果,具体而言:它是先从初始数据集训练出初级学习器,然后“生成”一个新数据集用于训练次级学习器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imeVYui1-1606466429961)(attachment:image-7.png)] -
原理:
- 对于Model1,将训练集D分为k份,对于每一份,用剩余数据集训练模型,然后预测出这一份的结果
- 重复上面步骤,直到每一份都预测出来。得到次级模型的训练集
- 得到k份测试集,平均后得到次级模型的测试集
- 对于Model2、Model3……重复以上情况,得到M维数据
- 选定次级模型,进行训练预测 ,一般这最后一层用的是LR。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6hqfWsXq-1606466429964)(attachment:image-8.png)]
- 优缺点:
- 优点:采用交叉验证方法构造,稳健性强; 可以结合多个模型判断结果,进行次级训练,效果好。
- 缺点:构造复杂,难以得到相应规则,商用上难以解释。
学习自:stacking算法原理及代码
3 随机森林API介绍及实现
3.1 随机森林API实现
- 分类模块:from sklearn.ensemble import RandomForestClassifier
- 回归模块:from sklearn.ensemble import RandomForestRegressor
- 分类模块参数介绍:
RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, random_state=None, min_samples_split=2)
- n_estimators:integer,optional(default = 10)森林里的树木数量120,200,300,500,800,1200;
- Criterion:string,可选(default =“gini”)分割特征的测量方法;
- max_depth:integer或None,可选(默认=无)树的最大深度 5,8,15,25,30;
- max_features="auto”,每个决策树的最大特征数量
• If “auto”, then max_features=sqrt(n_features).
• If “sqrt”, then max_features=sqrt(n_features)(same as “auto”).
• If “log2”, then max_features=log2(n_features).
• If None, then max_features=n_features. - bootstrap:boolean,optional(default = True)是否在构建树时使用放回抽样
- min_samples_split:节点划分最少样本数
- min_samples_leaf:叶子节点的最小样本数
3.2 随机森林分类示例
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
"""
读取数据
构建模型:
- 特征:除cand_pty_affiliation以外的都是特征
- 目标:cand_pty_affiliation
样本数据:100000
特征:10列
无缺失值
"""
df = pd.read_csv("input.csv")
df.info()
df.head()
"""
数据分割 :train_feature,test_feature,train_y,test_y
"""
from sklearn.model_selection import train_test_split
def get_train_test(test_size=0.95,seed=222):
# 构建 目标值
"""
构建 目标值
- REP-->文本-->1
- DEM-->文本-->0
"""
y = 1 * (df.cand_pty_affiliation == "REP")
# 构建 特征值
"""
特征值构建
- 删除cand_pty_affiliation列
- one-hot编码,则出现为1 未出现为0
- 如果说有一列数据无差异,毫无用处 --> 删除
- 数据无差异表现为数据无波动,标准差=0
"""
X = df.drop("cand_pty_affiliation",axis=1)
X = pd.get_dummies(X,sparse=False)
X = X.drop(X.columns[X.std()==0],axis=1)
return train_test_split(X,y,test_size=test_size,random_state=seed)
train_feature,test_feature,train_y,test_y = get_train_test()
from sklearn.ensemble import RandomForestClassifier
"""
随机森林训练分类模型
"""
rf = RandomForestClassifier(
n_estimators=10,
max_features=3,
random_state=222
)
rf.fit(train_feature,train_y)
"""
预测各个类别的概率,每一个测试样本-->预测的概率??
- 每一行表示每一个测试样本数据
- 两列?目标值是有两类的,REP,DEM
"""
rf.predict_proba(test_feature)
"""
到底是选择REP还是DEM来作为模型评价呢??根据数据分布,选择概率大的。
"""
df.cand_pty_affiliation.value_counts(normalize=True).plot(kind="bar")
# 获取索引为1的DEM 这列作为数据
p = rf.predict_proba(test_feature)[:,1]
"""
需要指标来衡量模型
采用ROC和AUC:离1越近,代表预测越好,即所有样本都分对了
"""
from sklearn.metrics import roc_auc_score
print(round(roc_auc_score(test_y,p),3)) # 0.844
"""
对比决策树模型,来比较训练效果
"""
from sklearn.tree import DecisionTreeClassifier
# 实例化
t1 = DecisionTreeClassifier(max_depth=1,random_state=222)
# 训练模型
t1.fit(train_feature,train_y)
# 预测
t1.predict(test_feature)
# 获取索引为1 DEM 的这列作为数据
p = t2.predict_proba(test_feature)[:,1]
# 模型评价
print(round(roc_auc_score(test_y,p),3)) # 结果是0.751, 小于随机森林的 0.844 所以是树多力量大!!!
"""
可以将决策树可视化一下 :决策树可视化函数
"""
from sklearn import tree
import graphviz
def print_graph(clf,feature_names):
dot_data = tree.export_graphviz(clf,
filled = True,
rounded = True,
special_characters = True,
feature_names=feature_names,
class_names={0:"D",1:"R"})
graph = graphviz.Source(dot_data)
graph.render('tatanic')
return graph
"""
在决策树中。占比越大的颜色越往橙色走,占比越小的就往蓝色走。
"""
print_graph(t1,train_feature.columns)
3.2 随机森林回归
下面以温度预测来做为随机森林回归的例子。需求实现:
- 使用随机森林算法完成基本建模任务
- 观察数据量与特征个数对结果影响
- 对比特征对模型的影响(获得特征的重要性)
- 对比特征对时间效率的影响
"""
1.对数据描述性统计
"""
import pandas as pd
"""
时间: year month day
特征:
ws_1 前一天的风速
prcp_1 前一天的降水量
snwd_1 前一天的积雪量
temp_2:前天的最高温度值
temp_1:昨天的最高温度值
average:每年这一天的平均最高温度值
friend
目标值:
actual :实际温度
"""
df = pd.read_csv("temps_extended.csv")
df.info()
df.head()
"""
2.直观的观察时间与各特征之间的变化关系(可视化),目的在于去观察是否有异常特征
"""
"""
绘制图形:
- x:时间
- y:各个特征值
日期为分开的 --> 构建标准的时间格式:2016-01-01
"""
# 构建标准化时间格式
dates = pd.PeriodIndex(year=df['year'],month=df['month'],day=df['day'],freq='D') # 注意:PeriodIndex类型不能之间作为X轴的值
dates = dates.to_timestamp() # DatetimeIndex
dates
import matplotlib.pyplot as plt
fg,ax = plt.subplots(2,3,figsize=(12,8))
# 实际温度
ax[0,0].plot(dates,df['actual'])
ax[0,0].set_xlabel("dates")
ax[0,0].set_ylabel("actual")
# 昨天的温度
ax[0,1].plot(dates,df['temp_1'])
ax[0,1].set_xlabel("dates")
ax[0,1].set_ylabel("temp_1")
# 前天的温度
ax[0,2].plot(dates,df['temp_2'])
ax[0,2].set_xlabel("dates")
ax[0,2].set_ylabel("temp_2")
# 一年平均这一天的温度
ax[1,0].plot(dates,df['average'])
ax[1,0].set_xlabel("dates")
ax[1,0].set_ylabel("average")
# ws_1 前一天的风速
ax[1,1].plot(dates,df['ws_1'])
ax[1,1].set_xlabel("dates")
ax[1,1].set_ylabel("ws_1")
# prcp_1 前一天的降水量
ax[1,2].plot(dates,df['prcp_1'])
ax[1,2].set_xlabel("dates")
ax[1,2].set_ylabel("prcp_1")
"""
3.判断季节与温度的关系
"""
seasons = []
# 输出所有月份
for month in df["month"]:
# 判断month在哪个区间
# 假设季节的所属月份 存在偏差
if month in [12,1,2]:
seasons.append('winter')
elif month in [3,4,5]:
seasons.append('spring')
elif month in [6,7,8]:
seasons.append('summer')
elif month in [9,10,11]:
seasons.append('fall')
reduced_features = df[['temp_1','prcp_1','average','actual']]
reduced_features['season'] = seasons
# 绘制不同季节中各特征与温度的实际关系
import seaborn as sns
sns.pairplot(reduced_features,hue='season',diag_kind='kde')
"""
4.特征工程——特征预处理
"""
# 文本变量转为0-1变量
more_df_2 = pd.get_dummies(df)
more_df_2.head(5)
"""
5.获取标签值与特征值
"""
import numpy as np
# 标签-->ndarray
labels = np.array(more_df_2["actual"])
# 在特征中去掉标签列
df_features = more_df_2.drop("actual",axis=1)
# 获取特征名称列表
feature_name_list = list(df_features.columns)
# 特征-->ndarray
features = np.array(df_features)
"""
6.数据分割
"""
from sklearn.model_selection import train_test_split
# 训练特征值 测试特征值 训练目标值 测试目标值
train_features,test_features,train_labels,test_labels = train_test_split(df_features,labels,test_size=0.25,random_state=42)
print("训练集特征:",train_features.shape)
print("训练集目标:",train_labels.shape)
print("测试集特征:",test_features.shape)
print("测试集目标:",test_labels.shape)
训练集特征: (1643, 17)
训练集目标: (1643,)
测试集特征: (548, 17)
测试集目标: (548,)
"""
7.建立最基础的随机森林模型
"""
from sklearn.ensemble import RandomForestRegressor
# 建立模型
rf = RandomForestRegressor(n_estimators=1000,random_state=42)
# 训练
rf.fit(train_features,train_labels)
"""
8.模型评估
"""
# 预测结果
predictions = rf.predict(test_features)
# Mape 错误率
# 测试预测样本集 - 测试集真实值 差值
errors = abs(predictions - test_labels)
# 误差/测试目标-->错误率
mape = 100*(errors/test_labels)
print("MAPE:",np.mean(mape)) # MAPE: 6.256602487487005
"""
9.展示树
"""
from sklearn import tree
import graphviz
# 取森林里的某一棵树进行可视化
tree_5 = rf.estimators_[5]
dot_data = tree.export_graphviz(tree_5,
filled = True,
rounded = True,
special_characters = True)
graph = graphviz.Source(dot_data)
graph.render('temperature')
graph
print("该树的最大深度为:",tree_5.tree_.max_depth) # 该树的最大深度为: 18
"""
10.查看特征的重要性
"""
# 获取特征重要性
importances = rf.feature_importances_.tolist()
# 保存:(特征名,特征重要性)-->[1,2,3] [4,5,6] [(1,4),(2,5)..]
# list(zip(feature_name_list,importances))
# 需要将 特征重要性 保留两位小数
feature_importances = [(feature,round(importance,2)) for feature,importance in zip(feature_name_list,importances)]
# 排序 排序键:特征重要性
# ('temp_1', 0.79)-->0.79
feature_importances = sorted(feature_importances,key=lambda x:x[1],reverse=True)
feature_importances # 结果是:('temp_1', 0.83),('average', 0.06),('ws_1', 0.02),('temp_2', 0.02),('friend', 0.02),
"""
11.模型优化:选择能够特征结果能够解释95%以上的特征群就行,不用全部选择
"""
# 获取特征值
sorted_importances = [importance[1] for importance in feature_importances]
# 获取重要性
sorted_features = [importance[0] for importance in feature_importances]
# 累计重要性 np.cumsum([1,2,3,4])-->[1,3,6,10]
cumulative_importances = np.cumsum(sorted_importances)
# 绘制折线图
plt.plot(sorted_features,cumulative_importances,"g-")
# 画一条红色虚线,在0.95那儿
plt.hlines(y=0.95,xmin=0,xmax=len(sorted_importances),color="r",linestyles="dashed")
# x轴刻度旋转
plt.xticks(rotation=90)
# Y轴和名字
plt.xlabel("features")
plt.xlabel("cumulative importance")
plt.title("cumulative importance")
plt.grid()
"所以选择到friend就行"
# 训练随机森林模型,为1000棵树
rf_most_important = RandomForestRegressor(n_estimators=1000,random_state=42)
# 获取该5个特征的训练数据集
names = ["temp_1","average","ws_1","temp_2","friend"]
important_train_features = train_features.loc[:,names]
important_test_features = test_features.loc[:,names]
# 输出数据形状
print("重要特征训练集形状:",important_train_features.shape)
print("重要特征测试集形状:",important_test_features.shape)
# 重新训练模型:训练集的特征,训练集的目标
rf_most_important.fit(important_train_features,train_labels)
# 预测结果:测试集特征
predictions = rf_most_important.predict(important_test_features)
# 使用mape评估结果
errors = abs(predictions - test_labels)
mape = np.mean(100 * (errors / test_labels))
print('mape:', mape) # mape: 6.548844579568184 之前MAPE是: 6.256602487487005 效果变差,但可能效率更高?比较一下
"""
12.比较两个模型的效率
"""
import time
# 使用全部特征的模型
li = []
for i in range(3):
# 初始时间
start_time = time.time()
# 训练模型
rf.fit(train_features,train_labels)
# 进行预测
predictions = rf.predict(test_features)
# 结束时间
end_time = time.time()
li.append(end_time - start_time)
print("使用全部特征所消耗的平均时间:",np.mean(li)) # 使用全部特征所消耗的平均时间: 6.3248123327891035
# 使用重要特征的模型
li = []
for i in range(3):
# 初始时间
start_time = time.time()
# 重新训练模型:训练集的特征,训练集的目标
rf_most_important.fit(important_train_features,train_labels)
# 预测结果:测试集特征
predictions = rf_most_important.predict(important_test_features)
# 结束时间
end_time = time.time()
li.append(end_time - start_time)
print("使用重要特征所消耗的平均时间:",np.mean(li)) # 使用重要特征所消耗的平均时间: 4.191339731216431
3.3 随机森林优化调参
-
介绍:预测参数与真实最优解之间还是有差距的。优化方式可以先通过随机选择确定最优解的大概范围,再在该解的上下进行浮动,进行地毯式(网格)的搜索,得到最优解。
-
使用模块1:
from sklearn.model_selection import RandomizedSearchCV
参数使用如下:
- Estimator:RandomizedSearchCV这个方法是一个通用的,并不是专为随机森林设计的,所以我们需要指定选择的算法模型是什么。
- Distributions:参数的候选空间,我们之间已经用字典格式给出了所需的参数分布。
- n_iter:随机寻找参数组合的个数,比如在这里我们赋值了100代表接下来要随机找100组参数的组合,在其中找到最好的一个。
- Scoring:评估方法,按照该方法去找到最好的参数组合。
- Cv:交叉验证。
- Verbose:打印信息的数量,看自己的需求了。
- random_state:随机种子,为了使得咱们的结果能够一致,排除掉随机成分的干扰,一般我们都会指定成一个值,用你自己的幸运数字就好。
- n_jobs:多线程来跑这个程序,一般不做这个,电脑容易卡。
-
使用模块2:from sklearn.model_selection import GridSearchCV
这个是网格搜索模块,前面有文章介绍了这个模块的使用啦 -
优化思想:给出参数的选择范围,通过随机选择参数来优化,得到大致最优解,再在大致优化解的范围,采用网格搜索,得到优化解
anyway,我们还是基于上述重要特征的模型继续示例~
from sklearn.model_selection import RandomizedSearchCV
# 构建树的个数
n_estimators = np.arange(100,1100,100).tolist()
# 构建树的最大深度
max_depth = np.arange(1,11).tolist()
# 叶子节点最小样本树
min_samples_leaf = [2,4,8]
random_grid = {
'n_estimators':n_estimators,
'max_depth':max_depth,
'min_samples_leaf':min_samples_leaf
}
rf = RandomForestRegressor()
rf_random = RandomizedSearchCV(estimator=rf,param_distributions=random_grid,n_iter=10)
# rf_random.fit(train_features,train_labels) # 全部特征
rf_random.fit(important_train_features,train_labels) # 重要特征
# 输出最好的参数
rf_random.best_params_ # {'n_estimators': 600, 'min_samples_leaf': 4, 'max_depth': 6}
"""
最好的参数错误率的大小
"""
predictions = rf_random.predict(important_test_features)
errors = abs(predictions-test_labels)
mape = 100 * np.mean(errors / test_labels)
print("new_errors:",mape) # new_errors: 6.22611475797937 之前的是 mape: 6.548844579568184 得到优化~
接着我们还在随机缩小最优解范围后.进行上下浮动网格搜索得到最优解
# 基于刚刚得到的最好参数 {'n_estimators': 600, 'min_samples_leaf': 4, 'max_depth': 6}
# 构建树的个数
n_estimators = [550,600,650,700]
# 构建树的最大深度
max_depth = [4,5,6,7,8]
# 叶子节点最小样本树
min_samples_leaf = [2,3,4,5,6]
param_grid = {
'n_estimators':n_estimators,
'max_depth':max_depth,
'min_samples_leaf':min_samples_leaf
}
from sklearn.model_selection import GridSearchCV
rf = RandomForestRegressor()
# 网格搜索
grid_search = GridSearchCV(estimator=rf,param_grid=param_grid)
# 训练
grid_search.fit(important_train_features,train_labels)
# 得到最优参数
grid_search.best_params_
predictions = grid_search.predict(important_test_features)
errors = abs(predictions-test_labels)
mape = 100 * np.mean(errors / test_labels)
print("new_errors:",mape)