本次介绍算法我想换一种方式,用实际的实现代码来介绍算法。辅助以必要的公式、表达式。
首先我们初始化权值矩阵D、弱分类器列表G以及弱分类器对应权值列表alpha。权值矩阵D在每次循环中都会用于当前分类器误差计算以及根据误差修改权值矩阵。弱分类器列表G以及弱分类器对应权值列表alpha都是在循环中存储,在最后弱分类集合时遍历。
弱分类器的个数M我们可以自己指定,M也决定了学习的循环次数。我们该实验的弱分类器确定为CART算法生成的分类决策树,我们可以使用sklearn中封装好的函数生成弱分类器,具体方法详见sklearn的指导手册。M的大小确实决定了集成学习的效果,但往往集成学习test_error曲线上升段和下降段都很短,中间有很长的一段平台期,也就意味着M从较小值开始增大时test_error急剧下降,而集成学习很难过拟合也就是M特别特别大时才会适得其反,大部分中间值的波动对集成学习效果影响很小,所以实际应用中并不需要很大的M便可以逼近最佳效果。
我们在这里默认X维度(100,2),y维度(100,1)。我们的损失矩阵error(100,1),y_pred与y进行比对其中预测正确的位置值为0也就是不产生误差,预测错误的位置值为1产生误差。e_m代表每个个体分类器的总体误差,计算方法为权值矩阵与损失矩阵对应位置元素相乘。
我们得到每个个体分类器的总体误差率e_m后,就可以根据这个值计算该个体分类器在最后集成时候的系数alpha以及更新权值矩阵D。所以说集成个体分类器时的投票权重以及每次更新训练数据权重矩阵都与个体分类器的误差率有关。具体公式如下图所示:
下图可以看出当 G m ( x i ) = y i G_m(x_i)=y_i Gm(xi)=yi时也就是分类正确时, G m ( x i ) ∗ y i = 1 G_m(x_i)*y_i=1 Gm(xi)∗yi=1, 0 < e − α m < 1 0
下图表征的是个体分类器误差率e_m与投票权重alpha_m之间的函数关系,可以发现误差率e_m越小其在最后投票过程中所占权重更大,误差率e_m越大其所占权重越小。表明我们的alpha系数表达式可以很好的表征e_m与alpha之间的关系。
最后遍历所有的个体分类器G以及投票权重alpha得出最终的投票结果即可。np.sign(score)函数的意思是,score大于0时返回1,等于0时返回0,小于0时返回-1。加权投票表决的含义就是将所有模型的输出乘上对应系数求和,再根据和值和门限确定分类。
def adaboost(X, y, M, Max_depth=None):
"""
adaboost函数,使用CART作为弱分类器
参数:
X: 训练样本
y: 样本标签, y = {-1, +1}
M: 使用M个弱分类器
Max_depth: 基学习器CART决策树的最大深度
返回:
F: 生成的模型
"""
# 假设X(100,2) num_X=100,num_feature=2
num_X, num_feature = X.shape
### START CODE HERE ###
# 初始化训练数据的权值分布
# 生成一个(100,1)的矩阵,值都为1/num_X
D = (np.ones((num_X,1))/num_X).reshape(-1,1)
G = [] #用于存放多个弱分类器,以待线性组合
alpha = [] #用于存放每个弱分类器的alpha参数
for m in range(M):
# 使用具有权值分布D_m的训练数据集学习,得到基本分类器
# 使用DecisionTreeClassifier,设置树深度为Max_depth
G_m = DecisionTreeClassifier(max_depth = Max_depth)
# 开始训练
model_m = G_m.fit(X,y,sample_weight = D.flatten())#注意加上参数,样本权重sample_weight = D
# 计算G_m在训练数据集上的分类误差率
y_pred = model_m.predict(X).reshape(-1,1)
error = np.where(y_pred == y,0,1) #返回矩阵,相等的地方为0不计算损失,不相等的地方为1计算损失
e_m = np.sum(D * error) #两个(100,1)的矩阵对应元素相乘,求和计算总体损失
#以下两个判断是为了保证np.log内不为0
if e_m == 0:
break
if e_m == 1:
raise ValueError("e_m = {}".format(e_m))
# 计算G_m的系数
alpha_m = (1/2) * np.log((1-e_m)/e_m)
# 更新训练数据集的权值分布
temp = D * np.exp(-alpha_m * y * y_pred)#三个(100,1)的矩阵对应元素相乘
D = temp / np.sum(temp)#更新权值矩阵
# 保存G_m和其系数
G.append(G_m)
alpha.append(alpha_m)
# 构建基本分类器的线性组合
def F(X):
num_G = len(G)
score = 0
for i in range(num_G):
score += alpha[i] * G[i].predict(X).reshape(-1,1)
return np.sign(score).reshape(-1,1)#返回投票表决产生的分类结果
### END CODE HERE ###
return F
def gbdt_classifier(X, y, M, Max_depth=5):
"""
用于分类的GBDT函数
参数:
X: 训练样本
y: 样本标签,y = {0, +1}
M: 使用M个回归树
Max_depth: 基学习器CART决策树的最大深度
返回:
F: 生成的模型
"""
### START CODE HERE ###
y_pred = np.mean(y) #对应算法起始的fo
Models = [] #用于存放所用个体分类器的列表
for m in range(M):
# 根据分类问题交叉熵损失计算余量r
r = y - sigmoid(y_pred)
# 使用DecisionTreeRegressor,设置树深度为5,random_state=0
f_m = DecisionTreeRegressor(max_depth = Max_depth,random_state=0)
# 开始训练
model = f_m.fit(X,r)
r_pred = model.predict(X).reshape(-1,1)
y_pred = y_pred + r_pred #更新下次待学习的余量
# 添加当前个体分类器
Models.append(model)
def F(X):
num_X, _ = X.shape
reg = np.zeros((num_X,1))
# 累加所有模型的计算结果
for model in Models:
y_pred = model.predict(X)
reg += y_pred.reshape(-1,1)
# 分类问题需要使用sigmoid将计算值归一到0-1之间
y_pred_gbdt = sigmoid(reg)
# 以0.5为阈值,得到最终分类结果0或1
one_position = y_pred_gbdt >= 0.5
y_pred_gbdt[one_position] = 1
y_pred_gbdt[~one_position] = 0
return y_pred_gbdt
### END CODE HERE ###
return F
def bagging(X, y, T, size, seed=0, max_depth=None):
"""
Bagging算法,分类器为CART,用于二分类
参数:
X: 训练集
y: 样本标签
T: T组
size: 每组训练集的大小
seed: 随机种子
max_depth: 基学习器CART决策树的最大深度
返回:
F: 生成的模型
"""
classifiers = [] # 存放分类器的列表
m, n = X.shape
### START CODE HERE ###
np.random.seed(seed)
for i in range(T):
# 使用np.random.choice选择size个序号,注意replace参数的设置,以满足有放回的均匀抽样。
index = np.random.choice(a=m,size=size,replace=True)
X_group = X[index]
y_group = y[index]
# 使用tree.DecisionTreeClassifier,设置max_depth=max_depth, min_samples_split=2(生成完全树),random_state=0
t = DecisionTreeClassifier(max_depth=max_depth,min_samples_split=2,random_state=0)
# 开始训练
t = t.fit(X_group,y_group)
classifiers.append(t)
def F(X):
# 计算所有分类器的预测结果
result = []
for t in classifiers:
result.append(t.predict(X).reshape(-1,1))
# 把预测结果组成 num_X * T 的矩阵
pred = np.array(result)
# 计算"0"有多少投票
vote_0 = np.sum(pred == 0)
# 计算"1"有多少投票
vote_1 = np.sum(pred == 1)
# 选择投票数最多的一个标签
pred = 0 if vote_0 > vote_1 else 1
return pred
### END CODE HERE ###
return F