集成学习综述与代码实现

1. 集成学习

定义

顾名思义,就是将多个单一模型进行组合,最后形成一个更好的模型的过程。之所以组合多个单一学习器,是因为很多时候单一学习器的效果不够理想,多个模型组合可以互帮互助,各取所长,从而能够更好的完成任务。集成学习一般的结构是先学习单一的学习器,之后通过某种策略将其组合在一起。

条件

(a)首先应该保证分类器之间的差异性,如果分类器都相同,那么组合的出来的结果是不会有变化的。

(b)每个个体分类器的精度必须大于0.5,如果个体分类器的精度低于0.5,那集成之后的精度低于规模的增大而降低。但如果精度是大于0.5的,最后的分类结果会趋于1。

分类

根据个体学习器的生成方式,目前的集成学习方法大致可以分成两类:第一类是单个学习器之间有着很强的依赖关系,需要以串行的序列化的方式生成,代表方法:Boosting。Boosting方法中也有很多分类:Adaboost、GBDT等等。第二类就是个体学习器之间不存在很强的依赖关系,学习器可以并行生成,代表方法:Bagging 和Random Forest。

2. Bagging和Boosting

2.1 Bagging

Bagging又称为套袋法:

每次从样本集合中有放回地抽取n个值,一共抽取k轮,形成k个训练集合,训练集合之间彼此独立。对于分类问题,k个训练器可以训练出k个模型,从而产生k个结果,对于回归问题,取均值作为预测结果。

2.2 Boosting

每次从样本集合中有放回地抽取n个值,一共抽取k轮,形成k个训练集合,训练集合之间彼此独立。对于分类问题,k个训练器可以训练出k个模型,从而产生k个结果,对于回归问题,取均值作为预测结果。

梯度提升的Boosting方法则是使用代价函数对上一轮训练的损失函数f的偏导数来拟合残差。

2.3 Bagging和Boosting的区别

训练数据集

Bagging是每次从样本集中有放回地抽取n个样本,抽取k次,形成k个样本集合,样本集合之间相互独立;

Boosting则是训练集合保持不变,每次变化的是训练集中样例的权重。

样例权重

Bagging使用的是均匀采样,每个样例权重相等;

Boosting根据上一轮结果调整权重,错误率越大权重越大。

预测函数

Bagging所有的预测函数权重相同;

Boosting预测函数权重不同,预测误差小的权重越大。

并行计算

Bagging各个预测函数可以并行生成;

Boosting只能顺序生成,因为后一个预测要用到上一个结果。

2.4 样本权值和模型权值

Boosting会减小在上一轮正确样本的权重,增大错误样本的权重(因为正确样本残差小,错误样本残差大。组合模型的时候会将错误率高的模型赋予低的权重,将错误率低的模型赋予高的权重,有助于增加组合后模型准确率。

3. 组合模型方式

3.1 平均法

(a)简单平均

将所有的个体学习器的数值型输出求平均。

(b)加权平均

各个学习器有自身的权重,根据权重来进行加权平均,代表算法:Adaboost。

3.2 学习法

当训练数据很多时,更加强大的结合策略就是学习法,也就是通过另一个学习器来进行组合。典型代表是Stacking。把个体学习器称之为初级学习器,用于结合的学习器称为次级学习器或者元学习器。

Stacking先从初始训练集训练出初级学习器,然后生成一个新的数据集来训练次级学习器。新的数据集是以初级学习器的输出作为样例,初始样本的标记被当做样例标记。

4. AdaBoost算法

4.1 核心思想

权值的更新取决于上一轮训练结果,增大误分类的样本的权值,减小正确分类的样本权值,在下一轮训练的时候误分类样本会得到更多的关注,正确分类的样本得到的关注变少。因为每次挑选的是误差最小的分类器,增大误分类的样本的权重相当于增大当前分类器的误差,错误率高的分类器对应小权重,错误率低的分类器对应大权重。

4.2 前向分步算法

简单来说就是从前向后,每次只学习一个基函数,最后通过基函数的线性组合,去逼近优化目标函数。

4.3 算法步骤









AdaBoost的Python实现(iris数据集)

import os
import numpy as np
import pandas as pd
from sklearn.cross_validation import train_test_split
from sklearn.datasets import load_iris
def load_data():
iris = load_iris()#导入数据
#转换为dataframe
df = pd.DataFrame(iris.data,columns=iris.feature_names)
df['label'] = iris.target#导入标签
#把0和1类拿出来
df.columns = ['sepal length','sepal width','petal length','petal width','label']#重命名列名
data = np.array(df.iloc[:100,[0,1,-1]])#前100行为分类为0和1两类
#把为0的变成-1
for i in range(len(data)):
if data[i,-1]==0:#把0类的转换为-1类
data[i,-1] = -1
return data[:,0:2],data[:,-1]
X,y = load_data()#导入数据
train_x,test_x,train_x,train_y = train_test_split(X,y,test_size=0.2,random_state=2333)#划分训练集和测试集
#定义Adaboost类
class Adaboost(object):
def __init__(self,n_estimators=1,learning_rate = 0):
'''
n_estimators 分类器个数
learning_rate 步长
'''
self.n_estimate = n_estimators
self.learning_rate = learning_rate
def init_data(self,X,y):
'''
X:属性


y:标签
'''
self.M,self.N = X.shape#属性一共有M行N列
self.X = X#训练属性
self.y = y#标签数据
self.w = [1.0/self.M]*self.M#初始化权重为1/M
self.alpha = []
self.clf_set = []#模型参数集合
def _alpha(self,error):#误差越大 alpha值越小
return 0.5*np.log((1-error)/error)
def _Z(self, weights, a, clf):
'''
a:各个分类器的权重
weights:权重
clf:compare_array
'''
return sum([weights[i]*np.exp(-1*a*self.y[i]*clf[i]) for i in range(self.M)])
def _w(self, a, clf, Z):#相当于给权重w做了一层softmax 归一化
'''
a:各个分类器的权重
weights:权重
clf:compare_array
误分类self.y[i]*clf[i]=1权重增大 正确分类self.y[i]*clf[i]=-1权重变小
'''
for i in range(self.M):
self.w[i] = self.w[i]*np.exp(-1*a*self.y[i]*clf[i])/ Z
#具体算法
def G(self,feature,label,weights):#每次更新的是w
#求出属性列中的最大值和最小值
f_min = min(feature)
f_max = max(feature)
#在属性列上迭代的次数
n_step = (f_max-f_min+self.learning_rate)//self.learning_rate
error = 10000.0#定义为无穷大
best_v = 0.0#表示属性中最好分类点的值
direct,compare_array = None,None
#direct表示优化方向

#compare_array表示该方向对应的c_o_positive or c_o_negative数组
for i in range(1,int(n_step)):
v = f_min+self.learning_rate*i#每次增加的步长为learning_rate
if v not in feature:#如果v不存在属性列中
c_o_positive = np.array([1 if feature[k] > v else -1 for k in range(len(feature))])#大于v的label置为1 小于v的置为-1
#print([weights[k] for k in range(len(feature)) if c_o_positive[k]!=label[k]])
error_positive = sum([weights[k] for k in range(len(feature)) if c_o_positive[k]!=label[k]])#记录不等于label的weight并求和
c_o_negative = np.array([-1 if feature[k] > v else 1 for k in range(len(feature))])#同上 但反向
error_negative = sum([weights[k] for k in range(len(feature)) if c_o_negative[k]!=label[k]])
if error_positive < error_negative:#如果正向误差小于反向误差
weight_error = error_positive#记录误差
_compare_array = c_o_positive
direct = 'positive'
else:
weight_error = error_negative
_compare_array = c_o_negative
direct = 'negative'
if weight_error < error:
error = weight_error#记录误差
best_v = v#记录最佳分类值
compare_array = _compare_array#记录label
return best_v,error,compare_array,direct
def _G(self,x,v,direct):
if direct=='positive':
return 1 if x > v else -1
else:
return -1 if x>x else -1
def fit(self,X,y):
self.init_data(X,y)#初始化类内参数
for i in range(self.n_estimate):#迭代次数
best_clf_error,best_v,clf_result = 100000,None,None
#单个属性
for j in range(self.N):#在属性列上迭代
feature = self.X[:,j]#
v,error,compare,direct = self.G(feature,self.y,self.w)
if error<best> best_clf_error = error
best_v = v
final_direct = direct
clf_result = compare
axis = j

if best_clf_error == 0:
break
a = self._alpha(best_clf_error)#计算alpha 0.5*np.log((1-error)/error)
self.alpha.append(a)#记录alpha
z = self._Z(self.w,a,clf_result)
self.clf_set.append((axis,best_v,final_direct))#clf_set集合
self._w(a,clf_result,z)#修改self.w
def predict(self,feature):#预测函数(比较简单)
result = 0.0
for i in range(len(self.clf_set)):
axis,best_v,final_direct = self.clf_set[i]
f = feature[axis]
result += self.alpha[i] * self._G(f,best_v,final_direct)
return 1 if result >0 else -1
def score(self,X_test,y_test):#打分函数(比较简单)
count = 0
for i in range(len(X_test)):
feature = X_test[i]
if self.predict(feature) == y_test[i]:
count += 1
return count/len(X_test)
/<best>