⑴Motivation of Aggregation
比如现在有一支股票,你不知道是跌还是涨。你有T个friends,每一个friend对应的建议分别是g1,g2,g3...gn,那么你应该怎么选择建议?
⑵Blending
1.Select the most trust-worthy friend
这其实就是对应validation,我们在所有的friend里面做kfold或者vfoldvalidation,然后比较Ein,小的将拿出来作为你的most trust-worthy friend。和我们之前学的一样,选择应该最好的模型做预测即可。
2.mix their opinion from all your friends uniformly
如果你的friends的水平都差不多,那么不如混合一下他们的想法求平均,把所有的预测结果加载一起进行平均求解。
3.mix their opinion from all your friends non-uniformly
如果你的friends水平参差不齐,那么就可以给予不同的重视程度,比如有专业搞股票的,那就给多点投票权给他,不是就减少点。
4.combine the opinion conditionally
第四种方法与第三种方法类似,但是权重不是固定的,根据不同的条件,给予不同的权重。和第三个相比,不同之处就在于它是动态的。比如如果是传统行业的股票,那么给这方面比较厉害的朋友较高的投票权重,如果是服务行业,那么就给这方面比较厉害的朋友较高的投票权重。
但是第一种方法只是从众多的模型中选择一个最好的,aggregation就是融合,我们需要的是集体的智慧。看一下为什么aggregation可以比普通的模型work better。
一条一条线就是刚刚的validation,aggregation做的就是融合,比如上部分中间的圆圈的点,叉叉一票,圈圈是两票,所以就是圈圈,所以aggregation是可以做到feature transform的。所以是起到了特征转换的效果:
再从另一个方面来看:比如我们使用perceptron learning algorithm:
而如果综合一下投票选择,平均一下是可以得到中间那条最好的直线,这和SVM的目标是一样的。所以aggregation也起到了regularization的效果。综合一下:
The advantages of aggregation model
①feature transform
②regularization
⑶Uniform Blending
对应着第三种方法。我们已经选择好了一些比较好的gt,其实就是model,我们已经从friends里面选择几个很好的朋友出来做aggregation了。uniform意味着就是平均,分类公式:
这种方法对应三种情况:第一种情况是每个候选的矩gt都完全一样,这跟选其中任意一个gt效果相同;第二种情况是每个候选的矩gt都有一些差别,这是最常遇到的,大都可以通过投票的形式使多数意见修正少数意见,从而得到很好的模型,如下图所示;第三种情况是多分类问题,选择投票数最多的那一类即可。
小的差别是可以被大的方向所correct。如果是回归问题:
第一种情况:如果gt都是一样的,那么没有任何区别
第二种情况:如果是都不一样都有差别,那么求平均其实可以减弱这些差别的影响。
对于uniform blending for regression的可行性
所以有:
所以从平均来说,求gt的平均值G比单一的gt要效果更好,可能对于左边单一gt-lost function求平均会混淆单一的gt效果,求平均只是简化计算,配合右边的而已,如果不要平均还是一样的结果,分别求lost相加求平均和先平均再求lost其实就是单一gt和平均G的lost对比。
我们已经知道G是数目为T的gt的平均值。令包含N个数据的样本D独立同分布于PN,每次从新的Dt中学习得到新的gt,在对gt求平均得到G,当做无限多次,即T趋向于无穷大的时候:
所以当T是infinite的时候,得到的g杆就是把T个训练好的朋友的结果综合起来,代替G。这T个gt是通过不同的D来训练的,把他们扔进A(Dt)进行训练,最后得到结果。
所以对应上面等式可以得到:avg(Eout(gt))就是演算法A期望的表现(expected performance of A),因为就是通过A(Dt)演算法来挑选出合适的gt来进行平均的,avg(ε(gt - G)^2)就是不同的gt和他们达成的共识相差了多少(expected deviation to consensus,called variance),Eout(G)就是达成的共识(performance of consensus),而blending在求平均的过程中就有减弱上述variance的作用,只是削弱,而不是消除。因为只有T趋于无限的时候才有可能用g杆完全替代G,因为G是所有gt的consensus,而达成共识是要求大方向把小方向给correct过来,如果大方向不够大,那么是没有能力correct的,所以平时我们使用的是近似于G,而不是真正意义上的G,所以上面的variance不能完全消除。
⑷uniform-blending的代码实现
导包准备和读取数据文件:
import numpy as np
import pandas as pd
def read_file(filename):
'''
use to read file which you open
:param filename:
:return:dataset
'''
data = pd.read_csv(filename)
return data
pass
def load_data():
print('Loading data......')
train = read_file('../../Data/train.csv')
test = read_file('../../Data/test.csv')
y_train = train.iloc[: , 0]
x_train = train.iloc[: , 1:]
y_test = test.iloc[: , 0]
x_test = test.iloc[: , 1:]
return x_train , y_train , x_test , y_test
pass
if __name__ == '__main__':
x_train , y_train , x_test , y_test = load_data()
print('x_train : ',x_train)
print('y_train : ',y_train)
又导包:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import MachineLearning.AggregationModel.Blending.load_data as load_data
from sklearn.model_selection import StratifiedKFold
from sklearn.ensemble import RandomForestClassifier , ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
主函数部分:
if __name__ == '__main__':
np.random.seed(0) # seed to shuffle the train set
accuracys = []
verbose = True
shuffle = False
X, y, X_test , y_test = load_data.load_data()
if shuffle:
idx = np.random.permutation(y.size) #random a sequence
X = X[idx]
y = y[idx]
clfs = [RandomForestClassifier(n_estimators=100, n_jobs=-1, criterion='gini'),
RandomForestClassifier(n_estimators=100, n_jobs=-1, criterion='entropy'),
ExtraTreesClassifier(n_estimators=100, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=100, n_jobs=-1, criterion='entropy'),
GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=50)]
print('Building the model......')
dataset_blend_train = np.zeros((X.shape[0], len(clfs)))
dataset_blend_test = np.zeros((X_test.shape[0], len(clfs)))
test_matrix = np.zeros((len(y_test) , 5))
for j, clf in enumerate(clfs):
print (j, clf)
clf.fit(X[j*700 : (j+1)*700] , y[j*700 : (j+1)*700])
predict = clf.predict_proba(X_test)[: , 1]
predict1 = []
for i in range(len(predict)):
if predict[i] > 0.5:
predict1.append(1)
else:
predict1.append(0)
pass
accuracys.append(calculate_accuracy(predict1 , y_test))
test_matrix[: , j] = predict
predictions = []
for i in range(len(test_matrix)):
if test_matrix[i].mean() > 0.5:
predictions.append(1)
else:
predictions.append(0)
accuracys.append(calculate_accuracy(predictions , y_test))
indexs_name = ['RandomForest','RandomForest','ExtraTrees_gini','ExtraTrees_entropy' , 'GradientBoosting','Average']
draw(accuracys , indexs_name)
选择的model分别是randomforest,extraTrees和FradientBoosting,这就是选择gt的步骤,之后就是用不同的训练集去训练他们,5个model那就把数据分成5类,不同的数据训练不同模型,综合结果。
工具函数:
def calculate_accuracy(predictions , y_test):
sum = 0
for i in range(len(y_test)):
if predictions[i] == y_test.tolist()[i]:
sum += 1
print("accuracy :",(sum/len(y_test))*100 , '%')
return (sum/len(y_test))*100
def draw(accuracys , indexs_name):
fig, ax = plt.subplots()
colors = ['#B0C4DE','#6495ED','#0000FF','#0000CD','#000080','#191970']
ax.bar(range(len(accuracys)), accuracys, color=colors, tick_label=indexs_name)
plt.xlabel('ModelName')
plt.ylabel('Accuracy')
ax.set_xticklabels(indexs_name,rotation=30)
plt.show()
pass
最后画柱形图:
平均下来的还是很高的,符合上面的avg(Eout(gt)) > E(avg(gt))的结果。
⑸Linear Blending and Any Blending
之前讲的是平均的思想,先要提的是linear Blending,对应的就是 non-uniformly:
那么主要是怎么找到最好的α,可以应用之前的误差最小化的思想:
和之前的线性模型差不多,可以用梯度下降或者牛顿法求解,但是区别就是α有存在限制。所以linear blending = 线性模型 + feature transform + 限制
事实上对于α这个值的正反其实可以不理会:
当α < 0,有|α|(-g(x)),意思就是让我们反着用,对的当错的,错的当对的。α > 0就一切照常使用。
对于Any Blending,其实就是stacking,堆叠模型,一层一层用不同的模型进行训练。
⑹Bagging(Boostrap Aggregation)
对于Blending,无论是Uniform的还是Non-Uniform或者是conditional,都是先得到gt,也就是先用不同的数据训练好gt之后在进行的aggregate,那么我们能否一边做aggregation一边做getting gt呢?
问题来了,如何得到不同的g。在blending中是使用不同的model来得到不同的g,这是一种方法。①使用不同的model。②同一model不同参数。③algorithm本身就带有随机性,比如kmean,EM这种对于初值很敏感的算法,PLA本身这种比较随机的。④数据随机,不同的数据训练模型,可以相同模型也可以不同模型。
回顾一下上一节讲到的一个演算法可以有两部分构成:
那如何利用已有的一份数据集来构造出不同的gt呢?首先,我们回顾一下之前介绍的bias-variance,即一个演算法的平均表现可以被拆成两项,一个是所有gt的共识(bias),一个是不同gt之间的差距是多少(variance)。其中每个gt都是需要新的数据集的。只有一份数据集的情况下,如何构造新的数据集?
其中g杆是在T趋于infinite的时候不同gt计算得到的平均值,为了得到g杆,我们需要构造两个近似条件:
①有限的T
②由已有数据集D构造出Dt PN,独立同分布
第一个条件是可以完成的,第二个条件使用boostrap做法。假设我们有N笔资料,选出一个样本,记录下,再放回去,再选择一个样本,再放回去,重复N次。这样我们就得到了新的一笔资料,这笔资料里面有可能有重复的也有可能没有原数据里面的某些样本。而抽取放回操作不一定是要N次。
有一个实际的例子:
下面举个实际中Bagging Pocket算法的例子。如下图所示,先通过bootstrapping得到25个不同样本集,再使用pocket算法得到25个不同的gt,每个pocket算法迭代1000次。最后,再利用blending,将所有的gt融合起来,得到最终的分类线,如图中黑线所示。可以看出,虽然bootstrapping会得到差别很大的分类线(灰线),但是经过blending后,得到的分类线效果是不错的,则bagging通常能得到最佳的分类模型。
⑺Bagging的代码实现
实现主要的Bagging包:
就是一个类:
class Bagging(object):
所有有关于Bagging的方法都会在这里。
首先是初始化方法:
def __init__(self ,n_estimators , estimator , rate = 1.0):
self.n_estimators = n_estimators
self.estimator = estimator
self.rate = rate
pass
有多少个基础模型,模型是哪几个,如果是下采样那么采样率是多少。然后就是投票函数了,因为得到的结果是横着的,横坐标是model纵坐标是prediction,所有要transpose一下,取出每一行看看是1多还是0多,然后取最多的返回即可。
def Voting(self , data):
term = np.transpose(data)
result = list()
def Vote(df):
store = defaultdict()
for kw in df:
store.setdefault(kw , 0)
store[kw] += 1
return max(store , key=store.get)
result = map(Vote , term)
return result
下采样,如果是用下采样的话就可以调用这个函数:
def UnderSampling(self,data):
data = np.array(data)
np.random.shuffle(data)
newdata = data[0:int(data.shape[0]*self.rate),:]
return newdata
pass
刚刚采样的rate就有用了。
训练预测,这些别人已经写好了,不用多写:
重心是在于Bagging模型本身,这些算法在后面讲到的时候会详细说到。
def TrainPredict(self , train , test):
clf = self.estimator.fit(train[: , 0:-1] , train[: , -1])
result = clf.predict(test[: , 0:-1])
return result
pass
然后就是boostrap采样了:
def RepetitionRandomSampling(self , data , number):
samples = []
for i in range(int(self.rate * number)):
samples.append(data[random.randint(0, len(data) - 1)])
pass
return samples
pass
这个就是一直随机基于好了。
def Metrics(self, predict_data , test):
score = predict_data
pre = np.matrix(test[: , -1])
score = list(score)
score = np.matrix(score)
recall = recall_score(pre.T , score.T, average=None)
precision = accuracy_score(pre.T, score.T)
return recall , precision
pass
估计模型效果,recall,metric,F1,accuracy,precision这些以后会有详细的篇章会讲到。
def MutModel_clf(self , train , test , sample_type = 'RepetitionRandomSampling'):
result = list()
sample_function = self.RepetitionRandomSampling
num_estimators = len(self.estimator)
if sample_type == "RepetitionRandomSampling":
print('Sample type : ' , sample_type)
elif sample_type == "UnderSampling":
print('Sample type : ' , sample_type)
sample_function = self.UnderSampling
print ("sampling frequency : ",self.rate)
for estimator in self.estimator:
print (estimator)
for i in range(int(self.n_estimators/num_estimators)):
sample=np.array(sample_function(train,len(train)))
clf = estimator.fit(sample[:,0:-1],sample[:,-1])
result.append(clf.predict(test[:,0:-1]))
score = self.Voting(result)
recall,precosoion = self.Metrics(score,test)
return recall,precosoion
只会就是主函数了。
之后就到了数据处理了,因为使用的数据还是上一次blending使用的数据,所以要把label放到最后,做一下数据处理:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
def MoveActivity(fileName , location , saveName):
dataframe = pd.read_csv(location + fileName)
activity = dataframe['Activity']
dataframe.drop(labels=['Activity'], axis=1,inplace = True)
dataframe.insert(len(dataframe.columns.values) , 'Activity', activity)
dataframe.to_csv(location + saveName,index=False)
pass
if __name__ == '__main__':
MoveActivity(fileName='train.csv' , location='../../Data/' , saveName='newtrain.csv')
MoveActivity(file
生成一个新的表格CSV。
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import MachineLearning.AggregationModel.Bagging.bagging as bagging
from sklearn.model_selection import StratifiedKFold
from sklearn.ensemble import RandomForestClassifier , ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
def loadData():
train = pd.read_csv('../../Data/newtrain.csv')
test = pd.read_csv('../../Data/newtest.csv')
train = train.as_matrix()
test = test.as_matrix()
return train , test
pass
def Running():
train , test = loadData()
clfs = [RandomForestClassifier(n_estimators=100, n_jobs=-1, criterion='gini'),
RandomForestClassifier(n_estimators=100, n_jobs=-1, criterion='entropy'),
ExtraTreesClassifier(n_estimators=100, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=100, n_jobs=-1, criterion='entropy'),
GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=50)]
bag = bagging.Bagging(n_estimators=5 , estimator=clfs)
recall , precision = bag.MutModel_clf(train , test)
print(recall , precision)
pass
if __name__ == '__main__':
Running()
使用的模型和上一个blending的是一样的,便于对比。最后做了一下对比:
if __name__ == '__main__':
pres1 = []
pres2 = []
for i in range(10):
pre = Running(sample='UnderSampling')
pres2.append(pre)
for i in range(10):
pre = Running()
pres1.append(pre)
plt.plot([x for x in range(10)] , pres1 , c = 'r')
plt.plot([x for x in range(10)] , pres2 , c = 'b')
plt.show()
效果对比:
红色的线是boostrap采样,蓝色的线是下采样,其实也不是下采样,因为刚刚忘记设置rate了,所以默认是1.......。总体效果还是比blending要好的。
GitHub上代码:
⑻Adaptive Boosting
继续Aggregation Model,最后一个集成算法——Adaptive Boosting,和前面一样都是监督式的学习。举一个简单的例子:老师要求学生来识别一些苹果,上面的是苹果下面的是其他东西。一个学生说,圆形的是苹果,于是成功的识别了一些,但是也有错误的。于是有了下图:
既然有正确分类的了,那么应该要把注意力放在错误的地方上,其他算法也是这样,logistics linear SVM都是minimize lost function。于是另一个学生又提出来了,红色的才是苹果,于是有:
但是错误的几个里面,绿色的也可以是苹果,于是另一个学生又说绿色的是苹果,然后又有:
还是有错,于是另一个同学又说有梗的才是苹果,然后:
这下就正确了,经过这几个同学的推论,苹果就是园的,红色或者绿色,有梗,虽然可能不是非常准确,但是要比单一的条件要好得多。也就是说把所有学生对苹果的定义融合起来,最终得到一个比较好的对苹果的总体定义。这个过程就是boosting,一开始的单个分类器,也就是一个同学是弱分类器,然后boosting主要就是集中多个弱分类器把它变成强的分类器。
在上面的例子里面,老师就像是演算法,学生就像hypothesis 的g,苹果的总定义就是G,老师的作用有两个,一个是选择学生,第二个就是指导方向,使得它们的注意力集中在错误上面。
①Diversity by Re-weighting
介绍这个algorithm之前先来看一下之前的bagging,bagging的抽样方法是boostrap抽样得到一个和原始数据类似的数据D1,然后训练gt,之后进行aggregation操作。假设我们现在有D个样本,进行了一次boostrap抽样操作:
那么对于一个新的抽样得到的数据D1,把他交给一个算法,让他计算:
可以看到抽样出来x1有两个,既然是两个的x1的,那就说明x1只要犯了错误那么就相当于是两个x1犯了错误,说明它做错的程度要更深一些,于是改进一下,增加一个权值:
所以当D1里面出现次数越多的样本,他对应的u就是越大,也就是说他的惩罚会越厉害,其实就是通过bootstrap的方式,来得到这些ui值,作为犯错样本的权重因子,再用 algorithn最小化包含ui的error function,得到不同的gt。这个error function被称为bootstrap-weighted error。
这种算法形式就和之前的SVM差不多,在最后的soft margin里面,我们转换过ξ,他其实就是犯错误的多少,所以:
和之前的SVM不一样的是,这里的u相当于每个犯错的样本的惩罚因子,并会反映到α的范围限定上。
同样在logistics中也是一样的:
所以,知道了u的概念之后,我们是可以通过u来选择不同的gt的,意味我们是可以在相同的模型中通过参数不同来选择不同的gt。结果是要使得gt互相之间都很不相同,因为之前讲过越不同的gt想要找到他们的consensus就越难,不同的gt也会涉及不同的分类方式,所以效果也会越好,相当于是regularization和feature transform了。
问题来了,如何得到不同的g(t)和g(t+1)?按照上面的讲述:
g(t)是u(t+1)产生的,而g(t+1)是u(t+1)产生的,所以如果当g(t)用u(t+1)的时候效果很差时,就表明g(t)和g(t+1)是很差的了。于是我们利用随机效果,比如一枚硬币,随机的时候两面都是0.5,如果有:
那么就表明相差很大,因为gt对于预测分类是没有什么影响的了,已经是随机了,所以这个时候差异是最大的。
对于上面那个式子不太好求解,变换一下:
犯错误的点和没有犯错误的点用橙色和绿色表示,要让这个fraction是0.5,错误和正确的一样就可以了,所以,错误的都乘上正确的数量,正确的都乘上错误的数量,600张是对的,200张错的,那么错的都乘600对的都乘200就可以了。或者我们可以乘上分数,3/4和1/4。所以计算u(t+1)就可以乘上1-ε和ε了。
②Adaptive Boosting Algorithm
现在进入了真正的Adaboost。对于1-ε和ε这些还是有点麻烦,引入一个新的尺度:
对于正确的类别将会除以◇t,对于错误的将会乘上◇t。效果是一样的,之所以这样做是因为它代表了更多的physical significance;当ε < 1/2,◇t > 1,所以正确的除去◇t,就是在削弱正确类别的重要性,错误类别乘上就是增加它的权值,增加它的重要性,对于minimize影响这么大algorithm肯定会重视,于是就会花更多的力气来处理权值大的,这就是像刚刚的例子里面,老师指导方向的效果了。
从这里开始,我们就可以得到一个初步的演算法了:
初始值暂时不知道,①通过u(t)得到gt②利用gt更新u(t)到u(t+1)。
对于u的初始值,一开始可以都是平均的,或者可以用刚刚的boostrap抽样来决定。G(x)可以使用之前的linear blending算法,设置一个参数α作为gt的权重:
对于α,构造一个和上面◇t 相关的一个式子:
于是算法完整了:
α这样取值是有物理意义的,例如当ϵ=1/2时,error很大,跟掷骰子这样的随机过程没什么两样,此时对应的⋄t=1,α=0,即此gt对G没有什么贡献,权重应该设为零。而当ϵt=0时,没有error,表示该gt预测非常准,此时对应的⋄t=∞,α=∞,即此gt对G贡献非常大,权重应该设为无穷大。
由三部分构成:base learning algorithm A,re-weighting factor ⋄t和linear aggregation αt。这三部分分别对应于我们在本节课开始介绍的例子中的Student,Teacher和Class。
base algorithm:对应基础算法模型,比如决策树,SVM,logistics这些。
re-weighting factor :对应的就是u的更新了。
linear aggregation:就是最后的G,综合gt的意见。
完整的算法流程:
③对于AdaBoost可行性的理解:
对这个VC bound中的第一项Ein(G)来说,有一个很好的性质:如果满足ϵt ≤ ϵ < 12,则经过T=O(log N)次迭代之后,Ein(G)能减小到 = 0的程度。而当N很大的时候,其中第二项也能变得很小。因为这两项都能变得很小,那么整个Eout(G)就能被限定在一个有限的上界中。dvc(H)这部分是base algorithm的复杂度,自然是不会太大了,基础算法就是弱分类器就行了,boost要做正是把弱分类器变强。T(logT)是迭代次数,这个是实践的时候得到的结论。logN/N就是样本数目了。迭代次数通常不会太久,数据大小也可以控制,所以综上来看,AdaBoost可以做到很好的效果缺不会太过拟合,这也就印证了之前regularization和feature transform的理解。
所以只要每一次ϵ ∈(ϵt , 1/2),也就是每一次都可以比随机好一点点,那么多次迭代就可以得到很好的结果了。Ein很小,Eout也不会差到哪里去。
⑼Adaptive Boosting in Action
看一下实际上的效果,待会会有代码的实现。
首先拿一些样本做切割:
迭代多几次之后就可以分开了。
⑽AdaBoost代码实现
⒈实现弱分类器decision-stump
实现Adaboost需要用到弱分类器,这个弱分类器使用的是decision-stump。这是一个单分类器,也叫单决策树,它会遍历所有数据的维度,然后找到最小的Ein在哪个维度上,那么哪个维度的那个线就是最好的分类。和上面的几个图是一样的,只能是垂直或者是竖直分,是一直弱分类器。它主要的过程就是遍历所有的数据维度,然后寻找一个合适的thresh作为分割,再看看是左边的作为positive好还是右边的作为positive好。从实际意义来看,decision stump 根据一个属性的一个判断就决定了最终的分类结果,比如根据水果是否是圆形判断水果是否为苹果,这体现的是单一简单的规则(或叫特征)在起作用。显然 decision stump 仅可作为一个 weak base learning algorithm(它会比瞎猜 1/2 稍好一点点,但好的程度十分有限),常用作集成学习中的 base algorithm,而不会单独作为分类器。
所以要优化的function:
所以要先实现一个decision-stump:
def decision_stump(dataMat , Labels , u):
dataMat = np.mat(dataMat)
Labels = np.mat(Labels).T
n , d = dataMat.shape
numSteps = 10.0
bestSump = {}
bestClasEst = np.mat(np.zeros((n , 1)))
minError = np.inf
for i in range(d):
rangeMin = dataMat[: , i].min()
rangeMax = dataMat[: , i].max()
stepSize = (rangeMax - rangeMin) / numSteps
for j in range(-1 , int(numSteps) + 1):
for inequal in ['lt' , 'gt']:
threshVal = (rangeMin + np.float32(j) * stepSize)
predictedVals = stumpClassify(dataMat , i , threshVal , inequal)
errArr = np.mat(np.ones((n , 1)))
errArr[predictedVals == Labels] = 0
weightedError = u.T * errArr
if weightedError < minError:
minError = weightedError
bestClasEst = predictedVals.copy()
bestSump['dim'] = i
bestSump['thresh'] = threshVal
bestSump['inseq'] = inequal
return bestSump, minError, bestClasEst
传入u参数是为了后面的兼容Adaboost的weightError,错误需要增加权重。按照上面的function解释一下功能:
①确定thresh,也就是确定阈值的范围,这里设置的是10个阈值,也就是每一个维度我按照10个来划分,迭代12次,找到最好的一个阈值。12次是因为最左和最右边各有一次。
②迭代dimension,开始寻找每一个维度是否是最小的Ein。
③迭代thresh,实际上就是迭代每一个维度上的12个thresh值,看看哪一个达到最小。
④如果thresh的positive和negative是反过来的也有可能造成很大的错误,所以看看哪边是positive或者是negative可以达到最好的效果,所以迭代lt和gt。
⑤最后得到是所有分类的1,-1标签,乘上每一个样本的权值u,就得到最终的error了。
thresh的decision-stump分类function:
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq): # just classify the data
retArray = np.ones((np.shape(dataMatrix)[0], 1))
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
其实就是调换一下positive和negative的表示方式。
⒉实现Adaboost
接下来就是主要的running function,Adaboost了:
def Running(Number , numIt):
dataX , dataY = getData.get_Data(Number)
weakClassArr = []
n = dataX.shape[0]
u = np.mat(np.ones((n , 1)) / n)
aggClassEst = np.mat(np.zeros((n , 1)))
getData.draw(dataX , dataY , Number , [])
for i in range(numIt):
bestSump, error, classEst = decision_stump(dataX, dataY, u)
alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))
bestSump['alpha'] = alpha
weakClassArr.append(bestSump)
expon = np.multiply(-1 * alpha * np.mat(dataY).T, classEst)
u = np.multiply(u , np.exp(expon)) / np.sum(u) #if miss the normalization,the u will be bigger than bigger , and the thresh is unusable.
aggClassEst += alpha * classEst
aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(dataY).T, np.ones((n, 1)))
errorRate = aggErrors.sum() / n
precision = np.multiply(np.sign(aggClassEst) == np.mat(dataY).T, np.ones((n, 1)))
precision = (sum(precision) / n) * 100
if i % 10 == 0:
if precision == 100.0:
break
print('precision : ',precision)
getData.draw(dataX , np.sign(aggClassEst) ,200, weakClassArr)
return weakClassArr, aggClassEst
其实就是按照上面所讲的不找来写的。
实现步骤:
①首先是得到数据了,初始化一些参数等等,比如u,n还有多个弱分类器分类结果相加得到的aggClassEst。
②迭代,每一个迭代就会产生一个弱分类器,首先先用刚刚写的decision-stump得到一个弱分类器,α的更新公式:
把1/2提出来也是一样的。
③这个分类器算是完成了,把它加入到集合里面。
④对于u的更新:
整合一下,其实就是ue^(-αf(x)h(x)),上面的公式是可以化简成这样的。最后还有进行一个u的归一化,林轩宇老师上面的是没有进行归一化的,如果不进行归一化也可以,但是可能错误的权值会增加的很快,正确的权值就会增加的很慢,因为后面的分类结果是要multiple u然后相加的,这样就是会导致,可能你这个点在上一次分类是正确的,u有点小,但是这一次错了,一乘上结果u就很大了,一加起来就很大,会导致有些个别的点波动幅度会很大,最后不一定用-1 / 1这些阈值可以分开了,分类效果很受影响。归一化的一个好处就是可以把错误都限制在-1 / 1之间,最后还是可以用1.-1区分。
⑤最后就是一些错误的相加了,计算准确率和错误率。
⒊最后一个就是数据的准备了
生成一个sin的数据,就是要生成这种百分之80都是对的,看看剩下那20能不能分对,做到feature transform的效果。
import numpy as np
import random
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
def get_Data(Number):
x = np.zeros((2*Number , 2),dtype = np.float32)
y = np.zeros(2*Number , dtype = np.float32)
t = np.linspace(0 , np.pi * 2 , Number)
for i in range(Number):
x[i] = np.c_[t[i] , np.sin(t[i]) - np.random.random() * 3]
y[i] = 1
pass
for i in range(Number):
x[Number+i] = np.c_[t[i] , np.sin(t[i]) + np.random.random() * 3]
y[Number+i] = -1
return x , y
pass
def draw(X, Y, Number , line):
for i in range(2*Number):
if Y[i] == 1:
plt.scatter(X[i,0], X[i, 1], color='r', marker='x')
else:
plt.scatter(X[i, 0], X[i, 1], color='b', marker='<')
pass
for l in line:
if l['dim'] == 0:
plt.plot(8*[l['thresh']] , np.linspace(0, 8, 8) , color = 'gray' , alpha = 0.5)
else:
plt.plot(np.linspace(0, 6.5, 6), 6 * [l['thresh']], color = 'gray', alpha=0.5)
pass
plt.title('DataSet')
plt.xlabel('First Dimension')
plt.ylabel('Second Dimension')
plt.show()
pass
if __name__ == '__main__':
x , y = get_Data(200)
draw(x, y, 200 , [])
效果200个点:
4.效果对比
讲道理,这东西应该是可以做到正确率百分之100的。但是:
看看效果:
我后面又迭代了几次,讲道理,这我就有点虚了。
后来我又找了一下原因,numSize = 10,是不是这个decision stump分类能力太弱了,Adaboost虽然点名要弱的,但是这个散打比赛太弱了。于是改到40,然后就行了......
起最后的效果图(事实上改到40还是不行,到99.75%就停了,改到60之后就OK了):
这样效果就有100%了,当然实际上这样会过拟合,但是这只是测试而已。
aggregation model完结!
所有代码在GitHub上:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/AggregationModel/Adaboost