機器學習:以二元決策樹為基學習器實現隨機森林算法的迴歸分析

機器學習 隨機森林 鏡音雙子 腳本語言 新新物聯網 2017-05-04

聲明:本文是站在迴歸分析角度講的,分類的理解可能跟這有點不一樣。

1.前言

隨機森林也是集成方法的一種,是對Bagging算法的改進。

隨機森林主要有兩步組成:

1)有放回的隨機抽取樣本數據,形成新的樣本集。這部分和Bagging算法一樣,但是有兩點需要注意:

a)新的樣本集的大小和原始樣本集的大小是一樣的。假如原始樣本有1000個數據,那麼新樣本集也要包括1000個數據,只是新樣本集裡面會含有部分重複的數據,這樣可以避免過度擬合的問題。

b)每生成一個決策樹,都需要重新對原始數據進行取樣。假如進行k次訓練(即生成k課樹),那麼就需要重複k次這個動作

2)無放回的隨機抽取屬性列。假如有12個屬性(即12列),從這12個屬性列中隨機抽取無重複的n列(一般建議是總屬性的1/3)進行運算。每次訓練都需要重新抽取

2.算法實現思路

該算法的核心就是如何實現上述兩個步驟,過程如下:

1)有放回的隨機抽取樣本數據

a)定義一個需要抽取的數據的索引列表

b)使用隨機函數隨機生成和數據集同樣大小的數值填充到索引列表中

c)對數據索引列表排序

d)抽取包含在索引列表中的數據重新組成樣本集,並抽取對應的標籤值組成標籤集

2)無放回的隨機抽取屬性列

a)定義一個需要抽取的屬性的索引列表

b)使用隨機函數隨機生成和屬性數量同樣大小的數值填充到索引列表中

c)對屬性索引列表排序

d)抽取包含在屬性索引列表中的屬性重新組成屬性集

3)從新的樣本集中按照新的屬性集抽取樣本數據集及標籤值,然後進行分析

3.實現過程

實驗數據還是使用分析紅酒口感時使用的數據。原始數據來源

1)劃分數據。

把數據劃分成訓練集和測試集,並把數據和標籤拆分開。這裡多說一點,在進行分析時,一定要把數據分成訓練集和測試集,不能在訓練集上訓練出模型後,然後再用訓練集得出的預測值用於後續分析。因為剛開始的時候對隨機森林的理解有點偏差,所以沒有劃分訓練集和測試集,結果反應均方誤差隨模型個數變化的曲線一直是波浪線。

import numpy as np
import matplotlib.pyplot as plt
import os
from sklearn.tree import DecisionTreeRegressor
import random

##運行腳本所在目錄
base_dir=os.getcwd
data=np.loadtxt(base_dir+r"\wine.txt",delimiter=";")

dataLen = len(data)    ##矩陣的長度:行數
dataWid = len(data[0]) ##矩陣的寬度:列數
'''
    第一步:劃分訓練集和測試集
'''
##測試集大小:這裡選擇30%作為測試集,70%作為訓練集
nSample = int(dataLen * 0.30)

##在0~dataLen直接隨機生成nSample個點
idxTest = random.sample(range(dataLen), nSample)
idxTest.sort

#定義訓練集和測試集標籤
xTrain =   #訓練集
xTest =    #測試集
yTrain =   #訓練集標籤
yTest =    #測試集標籤

##劃分數據:每行數據最後一個是標籤值
for i in range(dataLen):
    row = data[i]
    if i not in idxTest:
        xTrain.append(row[0:dataWid-1])
        yTrain.append(row[-1])
    else :
        xTest.append(row[0:dataWid-1])
        yTest.append(row[-1])

2)使用隨機森林算法訓練數據

這裡還是使用sklearn包中的二元決策樹函數DecisionTreeRegressor作為主要的分析函數。

另外還需要說兩個隨機函數(不是numpy裡面的):

random.choice(range(n)):在range形成的列表裡面隨機抽取一個

random.sample(range(n),m):在range形成的列表裡面無重複的隨機抽取m個數

'''
    第二步:使用隨機森林算法訓練數據
'''

modelList =          ##模型列表:決策樹的個數
predList =  ##預測值列表
mse =  ##均方差列表
allPredictions =     ##預測值累加和列表
numTreesMax = 100      ##最大樹數目
treeDepth = 12         ##每個樹的深度
nAttr = 4 ##隨機抽取的屬性數目,建議值:迴歸問題1/3

'''
        隨機森林思路:
        對應每個決策樹:
        1.有放回的隨機抽取和樣本數據大小一樣的數據集
        2.無放回的隨機抽取小於總屬性個數的屬性
'''

'''
      整個循環過程:
      1.外層循環生成模型
      2.在循環內部
        a)有放回的在訓練集上重新生成樣本數據索引列表idxList
        b)根據idxList生成樣本數據
        c)無放回的隨機抽取屬性值索引列表attList
        d)根據idxList、attList生成用戶訓練的數據
        e)進行訓練
        f)在測試集上執行類似步驟,並進行預測
        g)測試集上產生的預測值加入列表待用
'''
for iTrees in range(numTreesMax):
    ##定義決策樹
    modelList.append(DecisionTreeRegressor(max_depth=treeDepth))
    
    ##隨機抽取的樣本數據集和標籤集
    xList = 
    yList = 
    
    ##進行隨機抽取時樣本數據集的索引列表和屬性索引列表
    idxList = 
    attList = 
    
    ##構造隨機樣本數據集的索引列表
    for idx in range(len(xTrain)):
        idxList.append(random.choice(range(len(xTrain))))       
    idxList.sort  ##記得排序
    
    ##構造隨機樣本數據集
    for idx in idxList:
        xList.append(xTrain[idx])
        yList.append(yTrain[idx])

     ##構造隨機屬性列表:dataWid-1,是因為最後一列是標籤值
    attList = random.sample(range(dataWid-1),nAttr)  
    attList.sort ##記得排序

     ##構造測試數據集
    xTrain1 = 
    yTrain1 = 
    
    for i in range(len(xList)):
         ##只讀取抽取到的列
         row = [xList[i][j] for j in attList]
         xTrain1.append(row)
         ##yList每行只有一個標籤值
         yTrain1.append(yList[i])
         
    ##開始訓練
    modelList[-1].fit(xTrain1, yTrain1)
 
    ##獲取預測值 ---測試集需要抽取相同的列進行預測
    xTest1 =  
    for i in range(len(xTest)):
         ##只讀取抽取到的列
         row = [xTest[i][j] for j in attList]
         xTest1.append(row)
         
    latestOutSamplePrediction = modelList[-1].predict(xTest1)
    ##預測值添加到列表
    predList.append(list(latestOutSamplePrediction))

3)通過誤差累加和尋找最佳樹數目

篩選方法:不斷累加模型在測試集上的誤差值,直到這個和值基本保持不變了(即圖像尾部區域直線),此時的模型個數是最優的模型個數。為了方便理解下面循環,用個圖解釋下:

機器學習:以二元決策樹為基學習器實現隨機森林算法的迴歸分析

(圖1)

測試集經過在所有的模型上計算後,會形象一個二維的列表(如上圖1所示)。每一行代表一個模型的預測結果。第一次循環就是n1的數據,第二次循環是n1+n2的值,第三次循環存儲的是n1+n2+n3的值,依次類推,直到把模型列表循環完成。

這裡有一點需要說明:因為是隨機抽取樣本數據和隨機抽取屬性,所以最優解時的模型的數目不是固定的。

##通過累積均方誤差觀察隨機森林性能
for iModels in range(len(modelList)):
    prediction = 
    ##此循環的目的:每個模型都是把前面的所有的模型的對應列的預測值加起來,形成一個新列表
    ##說明:len(xTest) 每個模型的預測都會生成len(xTest)列的一行數據,這裡len(xTest) 和len(yTest)是一樣的
    for iPred in range(len(xTest)):
        prediction.append(sum([predList[i][iPred] for i in range(iModels + 1)]) / (iModels + 1))
    ##添加到列表
    allPredictions.append(prediction)
    ##計算新的離差
    errors = [(yTest[i] - prediction[i]) for i in range(len(yTest))]
    ##均方差:即離差的平方和的平均數
    mse.append(sum([e * e for e in errors]) / len(yTest))

print('Minimum MSE')
print(min(mse))      ##0.372633316412
print(mse.index(min(mse)))  ## 不確定

4)繪圖觀察誤差平方隨模型數目變化的曲線

'''
   第四步:繪圖觀察誤差平方隨模型數目變化的曲線
'''
####模型個個數+1,繪圖用:即模型列表中的從0開始的下標變成從1開始 的編號
nModels = [i + 1 for i in range(len(modelList))]
##繪圖
plt.plot(nModels,mse)
plt.axis('tight')
plt.xlabel('Number of Trees in Ensemble')
plt.ylabel('Mean Squared Error')
plt.ylim((0.0, max(mse)))
plt.show
機器學習:以二元決策樹為基學習器實現隨機森林算法的迴歸分析

此次運行結果大概在90以後曲線區域平緩。

4.使用RandomForestRegressor函數實現上述過程

RandomForestRegressor是sklearn包中提供的實現隨機森林算法的函數。下面還會用到另外一個函數:train_test_split。這兩個函數的其他參數很容易理解,重點是說下參數:random_state,這個值在實際環境中取默認值None即可,但是在開發環境中需要指定一個任意固定值,是讓內部的隨機生成器生成的結果固定,方便研究其他變量引起的變化。

from sklearn.model_selection import train_test_split
from sklearn import ensemble
from sklearn.metrics import mean_squared_error
import numpy as np
import matplotlib.pyplot as plt
import os

##運行腳本所在目錄
base_dir=os.getcwd
data=np.loadtxt(base_dir+r"\wine.txt",delimiter=";")

dataLen = len(data)    ##矩陣的長度:行數
dataWid = len(data[0]) ##矩陣的寬度:列數
'''
    第一步:把訓練數據和標籤數據分開
'''
xList =  ##樣本數據集
yList =  ##標籤集

##劃分數據:樣本數據集和標籤集
for i in range(dataLen):
    row = data[i]   
    xList.append(row[0:dataWid-1])
    yList.append(row[-1])
    
##把列表轉成數組
X = np.array(xList)
Y = np.array(yList)

##取30%的數據作為測試集,70%的數據作為訓練集
##random_state設置成固定值是為了多次運行代碼保持一致的結果,在開發階段調整模型。真實環境設置默認值;None
xTrain, xTest, yTrain, yTest = train_test_split(X, Y, test_size=0.30, random_state=0)

##樹的數目:建議是嘗試值100~500
nTreeList = range(1, 100, 1)   
##均方誤差
mse =          

for iTrees in nTreeList:
    depth =12     ##樹最大深度,為了保持跟上面實驗一致設為12,建議設置:None
    maxFeat  = 4  ##最大屬性值個數
    ##定義模型
    wineRFModel = ensemble.RandomForestRegressor(n_estimators=iTrees, max_depth=depth, max_features=maxFeat,random_state=0)
    ##開始訓練
    wineRFModel.fit(xTrain,yTrain)

    #計算測試集上的預測值
    prediction = wineRFModel.predict(xTest)
    ##計算均方差並加入到列表
    mse.append(mean_squared_error(yTest, prediction))


print('Minimum MSE')
print(min(mse))  #0.378757382093
print(mse.index(min(mse))) #45

#繪製均方誤差隨模型數目的變化曲線
plt.plot(nTreeList, mse)
plt.xlabel('Number of Trees in Ensemble')
plt.ylabel('Mean Squared Error')
plt.ylim([0.0, 1.1*max(mse)])
plt.show
機器學習:以二元決策樹為基學習器實現隨機森林算法的迴歸分析

按照上面的設置每次生成的最佳模型數目都是27,但是random_state改成None後,最優解時的樹的數目也不固定了。

5.總結

上述兩種方法使用的數據一樣,參數也儘可能保持一致,生成的結果就均方誤差而言也基本一致。但是第一種方法最佳模型數目是99時所需要的時間也遠遠少於第二種方法模型數目是27的所需的時間。其時間比大概是1:4的樣子,也就是說後者所需時間是前者所需時間的4倍,所以針對一些可以自己動手寫代碼的部分,並且不會產生太大偏差的時候,還是自己寫代碼比較好。這個例子告訴我們:並不是所有的現成類庫的性能都優於自己寫的代碼性能。

相關推薦

推薦中...