Aggregation Model : Blending , Bagging , Boosting

⑴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。和我們之前學的一樣,選擇應該最好的模型做預測即可。


10624272-e12faddee7b46e0e.png

2.mix their opinion from all your friends uniformly

如果你的friends的水平都差不多,那麼不如混合一下他們的想法求平均,把所有的預測結果加載一起進行平均求解。


10624272-347d2e92bf0669ae.png

3.mix their opinion from all your friends non-uniformly

如果你的friends水平參差不齊,那麼就可以給予不同的重視程度,比如有專業搞股票的,那就給多點投票權給他,不是就減少點。


10624272-6931b34999e4e881.png

4.combine the opinion conditionally

第四種方法與第三種方法類似,但是權重不是固定的,根據不同的條件,給予不同的權重。和第三個相比,不同之處就在於它是動態的。比如如果是傳統行業的股票,那麼給這方面比較厲害的朋友較高的投票權重,如果是服務行業,那麼就給這方面比較厲害的朋友較高的投票權重。


10624272-43d610a303f8f36c.png

但是第一種方法只是從衆多的模型中選擇一個最好的,aggregation就是融合,我們需要的是集體的智慧。看一下爲什麼aggregation可以比普通的模型work better。

10624272-012c76446cf2010c.png

一條一條線就是剛剛的validation,aggregation做的就是融合,比如上部分中間的圓圈的點,叉叉一票,圈圈是兩票,所以就是圈圈,所以aggregation是可以做到feature transform的。所以是起到了特徵轉換的效果:
10624272-1b1886aca05c302a.png

再從另一個方面來看:比如我們使用perceptron learning algorithm:
10624272-e76a33129dcca376.png

而如果綜合一下投票選擇,平均一下是可以得到中間那條最好的直線,這和SVM的目標是一樣的。所以aggregation也起到了regularization的效果。綜合一下:

The advantages of aggregation model

①feature transform
②regularization

⑶Uniform Blending

對應着第三種方法。我們已經選擇好了一些比較好的gt,其實就是model,我們已經從friends裏面選擇幾個很好的朋友出來做aggregation了。uniform意味着就是平均,分類公式:

10624272-4e796031bd4bf53b.png

這種方法對應三種情況:第一種情況是每個候選的矩gt都完全一樣,這跟選其中任意一個gt效果相同;第二種情況是每個候選的矩gt都有一些差別,這是最常遇到的,大都可以通過投票的形式使多數意見修正少數意見,從而得到很好的模型,如下圖所示;第三種情況是多分類問題,選擇投票數最多的那一類即可。
10624272-70a8ce3fd68b3c1c.png

小的差別是可以被大的方向所correct。如果是迴歸問題:
10624272-d40a423732d33b15.png

第一種情況:如果gt都是一樣的,那麼沒有任何區別
第二種情況:如果是都不一樣都有差別,那麼求平均其實可以減弱這些差別的影響。

10624272-9064468ea0bc9072.png

對於uniform blending for regression的可行性

10624272-d4d5adcbee56db70.png

所以有:
10624272-72c07262d7053d77.png

所以從平均來說,求gt的平均值G比單一的gt要效果更好,可能對於左邊單一gt-lost function求平均會混淆單一的gt效果,求平均只是簡化計算,配合右邊的而已,如果不要平均還是一樣的結果,分別求lost相加求平均和先平均再求lost其實就是單一gt和平均G的lost對比。
我們已經知道G是數目爲T的gt的平均值。令包含N個數據的樣本D獨立同分佈於PN,每次從新的Dt中學習得到新的gt,在對gt求平均得到G,當做無限多次,即T趨向於無窮大的時候:
10624272-079da35dadb4f660.png

10624272-436bdddd7c1c025d.png

所以當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不能完全消除。
10624272-a4e4e571649eb2b9.png

⑷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

最後畫柱形圖:


10624272-110086ca721e2fbc.png

平均下來的還是很高的,符合上面的avg(Eout(gt)) > E(avg(gt))的結果。

⑸Linear Blending and Any Blending

之前講的是平均的思想,先要提的是linear Blending,對應的就是 non-uniformly:


10624272-d98e83b88955949c.png

那麼主要是怎麼找到最好的α,可以應用之前的誤差最小化的思想:


10624272-290ca572e4c3fb9a.png

和之前的線性模型差不多,可以用梯度下降或者牛頓法求解,但是區別就是α有存在限制。所以linear blending = 線性模型 + feature transform + 限制
事實上對於α這個值的正反其實可以不理會:
10624272-10783688040f7dc9.png

當α < 0,有|α|(-g(x)),意思就是讓我們反着用,對的當錯的,錯的當對的。α > 0就一切照常使用。
對於Any Blending,其實就是stacking,堆疊模型,一層一層用不同的模型進行訓練。


10624272-b13d88c02d60ea1d.png

⑹Bagging(Boostrap Aggregation)

對於Blending,無論是Uniform的還是Non-Uniform或者是conditional,都是先得到gt,也就是先用不同的數據訓練好gt之後在進行的aggregate,那麼我們能否一邊做aggregation一邊做getting gt呢?

10624272-2efed391c64ea77b.png

問題來了,如何得到不同的g。在blending中是使用不同的model來得到不同的g,這是一種方法。①使用不同的model。②同一model不同參數。③algorithm本身就帶有隨機性,比如kmean,EM這種對於初值很敏感的算法,PLA本身這種比較隨機的。④數據隨機,不同的數據訓練模型,可以相同模型也可以不同模型。
10624272-d1ecce07815112c3.png

回顧一下上一節講到的一個演算法可以有兩部分構成:
10624272-b4c4ceba837a0bdc.png

那如何利用已有的一份數據集來構造出不同的gt呢?首先,我們回顧一下之前介紹的bias-variance,即一個演算法的平均表現可以被拆成兩項,一個是所有gt的共識(bias),一個是不同gt之間的差距是多少(variance)。其中每個gt都是需要新的數據集的。只有一份數據集的情況下,如何構造新的數據集?
其中g杆是在T趨於infinite的時候不同gt計算得到的平均值,爲了得到g杆,我們需要構造兩個近似條件:
①有限的T
②由已有數據集D構造出Dt PN,獨立同分布

第一個條件是可以完成的,第二個條件使用boostrap做法。假設我們有N筆資料,選出一個樣本,記錄下,再放回去,再選擇一個樣本,再放回去,重複N次。這樣我們就得到了新的一筆資料,這筆資料裏面有可能有重複的也有可能沒有原數據裏面的某些樣本。而抽取放回操作不一定是要N次。

10624272-1567921906108808.png

有一個實際的例子:
下面舉個實際中Bagging Pocket算法的例子。如下圖所示,先通過bootstrapping得到25個不同樣本集,再使用pocket算法得到25個不同的gt,每個pocket算法迭代1000次。最後,再利用blending,將所有的gt融合起來,得到最終的分類線,如圖中黑線所示。可以看出,雖然bootstrapping會得到差別很大的分類線(灰線),但是經過blending後,得到的分類線效果是不錯的,則bagging通常能得到最佳的分類模型。


10624272-499eb6454a18b4f3.png

⑺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()

效果對比:


10624272-af982127fce56747.png

紅色的線是boostrap採樣,藍色的線是下采樣,其實也不是下采樣,因爲剛剛忘記設置rate了,所以默認是1.......。總體效果還是比blending要好的。
GitHub上代碼:

https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/AggregationModel/Bagging

⑻Adaptive Boosting

繼續Aggregation Model,最後一個集成算法——Adaptive Boosting,和前面一樣都是監督式的學習。舉一個簡單的例子:老師要求學生來識別一些蘋果,上面的是蘋果下面的是其他東西。一個學生說,圓形的是蘋果,於是成功的識別了一些,但是也有錯誤的。於是有了下圖:

10624272-1b976ba18e45b500.png

既然有正確分類的了,那麼應該要把注意力放在錯誤的地方上,其他算法也是這樣,logistics linear SVM都是minimize lost function。於是另一個學生又提出來了,紅色的纔是蘋果,於是有:
10624272-06fd38898e0063be.png

但是錯誤的幾個裏面,綠色的也可以是蘋果,於是另一個學生又說綠色的是蘋果,然後又有:
10624272-b704c2b2de822d6c.png

還是有錯,於是另一個同學又說有梗的纔是蘋果,然後:
10624272-b4d090b3996fb4ec.png

這下就正確了,經過這幾個同學的推論,蘋果就是園的,紅色或者綠色,有梗,雖然可能不是非常準確,但是要比單一的條件要好得多。也就是說把所有學生對蘋果的定義融合起來,最終得到一個比較好的對蘋果的總體定義。這個過程就是boosting,一開始的單個分類器,也就是一個同學是弱分類器,然後boosting主要就是集中多個弱分類器把它變成強的分類器。
在上面的例子裏面,老師就像是演算法,學生就像hypothesis 的g,蘋果的總定義就是G,老師的作用有兩個,一個是選擇學生,第二個就是指導方向,使得它們的注意力集中在錯誤上面。
10624272-ba3e18c0523b0156.png

①Diversity by Re-weighting

介紹這個algorithm之前先來看一下之前的bagging,bagging的抽樣方法是boostrap抽樣得到一個和原始數據類似的數據D1,然後訓練gt,之後進行aggregation操作。假設我們現在有D個樣本,進行了一次boostrap抽樣操作:

10624272-79b06c528cb33f9c.png

那麼對於一個新的抽樣得到的數據D1,把他交給一個算法,讓他計算:
10624272-aa318612aa345b86.png

可以看到抽樣出來x1有兩個,既然是兩個的x1的,那就說明x1只要犯了錯誤那麼就相當於是兩個x1犯了錯誤,說明它做錯的程度要更深一些,於是改進一下,增加一個權值:
10624272-0fa04f676c100d26.png

所以當D1裏面出現次數越多的樣本,他對應的u就是越大,也就是說他的懲罰會越厲害,其實就是通過bootstrap的方式,來得到這些ui值,作爲犯錯樣本的權重因子,再用 algorithn最小化包含ui的error function,得到不同的gt。這個error function被稱爲bootstrap-weighted error。
10624272-e9d9eea0a98f021e.png

這種算法形式就和之前的SVM差不多,在最後的soft margin裏面,我們轉換過ξ,他其實就是犯錯誤的多少,所以:
10624272-fa073d96cd139e19.png

和之前的SVM不一樣的是,這裏的u相當於每個犯錯的樣本的懲罰因子,並會反映到α的範圍限定上。
同樣在logistics中也是一樣的:
10624272-9ac58ce7429fc6fe.png

所以,知道了u的概念之後,我們是可以通過u來選擇不同的gt的,意味我們是可以在相同的模型中通過參數不同來選擇不同的gt。結果是要使得gt互相之間都很不相同,因爲之前講過越不同的gt想要找到他們的consensus就越難,不同的gt也會涉及不同的分類方式,所以效果也會越好,相當於是regularization和feature transform了。

問題來了,如何得到不同的g(t)和g(t+1)?按照上面的講述:


10624272-5e55b2d33d03ccae.png

g(t)是u(t+1)產生的,而g(t+1)是u(t+1)產生的,所以如果當g(t)用u(t+1)的時候效果很差時,就表明g(t)和g(t+1)是很差的了。於是我們利用隨機效果,比如一枚硬幣,隨機的時候兩面都是0.5,如果有:


10624272-3e3cefd561e2467f.png

那麼就表明相差很大,因爲gt對於預測分類是沒有什麼影響的了,已經是隨機了,所以這個時候差異是最大的。
對於上面那個式子不太好求解,變換一下:
10624272-6148681110ec5a11.png

犯錯誤的點和沒有犯錯誤的點用橙色和綠色表示,要讓這個fraction是0.5,錯誤和正確的一樣就可以了,所以,錯誤的都乘上正確的數量,正確的都乘上錯誤的數量,600張是對的,200張錯的,那麼錯的都乘600對的都乘200就可以了。或者我們可以乘上分數,3/4和1/4。所以計算u(t+1)就可以乘上1-ε和ε了。


10624272-255477b37427af0e.png

②Adaptive Boosting Algorithm

現在進入了真正的Adaboost。對於1-ε和ε這些還是有點麻煩,引入一個新的尺度:

10624272-2e6cfae53fde3e2d.png

對於正確的類別將會除以◇t,對於錯誤的將會乘上◇t。效果是一樣的,之所以這樣做是因爲它代表了更多的physical significance;當ε < 1/2,◇t > 1,所以正確的除去◇t,就是在削弱正確類別的重要性,錯誤類別乘上就是增加它的權值,增加它的重要性,對於minimize影響這麼大algorithm肯定會重視,於是就會花更多的力氣來處理權值大的,這就是像剛剛的例子裏面,老師指導方向的效果了。
10624272-e25a6efbb2080201.png

從這裏開始,我們就可以得到一個初步的演算法了:
10624272-42f736446302e27a.png

初始值暫時不知道,①通過u(t)得到gt②利用gt更新u(t)到u(t+1)。
對於u的初始值,一開始可以都是平均的,或者可以用剛剛的boostrap抽樣來決定。G(x)可以使用之前的linear blending算法,設置一個參數α作爲gt的權重:
10624272-70bfc6417e124264.png

對於α,構造一個和上面◇t 相關的一個式子:
10624272-e9f6b612967e7c46.png

於是算法完整了:
10624272-47832301c6a7393c.png

α這樣取值是有物理意義的,例如當ϵ=1/2時,error很大,跟擲骰子這樣的隨機過程沒什麼兩樣,此時對應的⋄t=1,α=0,即此gt對G沒有什麼貢獻,權重應該設爲零。而當ϵt=0時,沒有error,表示該gt預測非常準,此時對應的⋄t=∞,α=∞,即此gt對G貢獻非常大,權重應該設爲無窮大。
10624272-e141dfc2c0131c94.png

由三部分構成:base learning algorithm A,re-weighting factor ⋄t和linear aggregation αt。這三部分分別對應於我們在本節課開始介紹的例子中的Student,Teacher和Class。
10624272-6236f1adf5e00548.png

base algorithm:對應基礎算法模型,比如決策樹,SVM,logistics這些。
re-weighting factor :對應的就是u的更新了。
linear aggregation:就是最後的G,綜合gt的意見。
完整的算法流程:
10624272-a1ba2beebabab4ec.png


③對於AdaBoost可行性的理解:

10624272-b29a498761cbd50e.png

對這個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的理解。
10624272-bfc6f8011e23e93e.png

所以只要每一次ϵ ∈(ϵt , 1/2),也就是每一次都可以比隨機好一點點,那麼多次迭代就可以得到很好的結果了。Ein很小,Eout也不會差到哪裏去。

⑼Adaptive Boosting in Action

看一下實際上的效果,待會會有代碼的實現。
首先拿一些樣本做切割:


10624272-518078b73a56c013.png

10624272-9f0d00374e26b102.png

10624272-a541364cb3a54230.png

10624272-cd5cc44db4a8d196.png

迭代多幾次之後就可以分開了。

⑽AdaBoost代碼實現

⒈實現弱分類器decision-stump

實現Adaboost需要用到弱分類器,這個弱分類器使用的是decision-stump。這是一個單分類器,也叫單決策樹,它會遍歷所有數據的維度,然後找到最小的Ein在哪個維度上,那麼哪個維度的那個線就是最好的分類。和上面的幾個圖是一樣的,只能是垂直或者是豎直分,是一直弱分類器。它主要的過程就是遍歷所有的數據維度,然後尋找一個合適的thresh作爲分割,再看看是左邊的作爲positive好還是右邊的作爲positive好。從實際意義來看,decision stump 根據一個屬性的一個判斷就決定了最終的分類結果,比如根據水果是否是圓形判斷水果是否爲蘋果,這體現的是單一簡單的規則(或叫特徵)在起作用。顯然 decision stump 僅可作爲一個 weak base learning algorithm(它會比瞎猜 1/2 稍好一點點,但好的程度十分有限),常用作集成學習中的 base algorithm,而不會單獨作爲分類器。
所以要優化的function:

10624272-29fb7b95b0e7cc83.png

所以要先實現一個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得到一個弱分類器,α的更新公式:

10624272-ab9a4e6ff531728e.png

把1/2提出來也是一樣的。
③這個分類器算是完成了,把它加入到集合裏面。
④對於u的更新:
10624272-4589f72d3864675c.png

整合一下,其實就是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個點:


10624272-0aac2575b6719103.png
4.效果對比

講道理,這東西應該是可以做到正確率百分之100的。但是:

10624272-402100e4a8aa9529.png

看看效果:
10624272-8fc75c2cb2c63b58.png
一個弱分類器的時候

10624272-b72806af6d1b2567.png
10個的時候

10624272-a0856e63c42c01ad.png
20個的時候

10624272-7b7c8f06b3c44351.png
30個的時候

我後面又迭代了幾次,講道理,這我就有點虛了。
後來我又找了一下原因,numSize = 10,是不是這個decision stump分類能力太弱了,Adaboost雖然點名要弱的,但是這個散打比賽太弱了。於是改到40,然後就行了......
起最後的效果圖(事實上改到40還是不行,到99.75%就停了,改到60之後就OK了):
10624272-c51e4366672df705.png

10624272-1bd1870622f54982.png

這樣效果就有100%了,當然實際上這樣會過擬合,但是這只是測試而已。
aggregation model完結!

所有代碼在GitHub上:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/AggregationModel/Adaboost

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章