LESSON 9.5 随机森林在巨量数据上的增量学习
五 隨機森林在巨量數(shù)據(jù)上的增量學習
集成學習是工業(yè)領(lǐng)域中應(yīng)用最廣泛的機器學習算法。實際工業(yè)環(huán)境下的數(shù)據(jù)量往往十分巨大,一個訓練好的集成算法的復(fù)雜程度與訓練數(shù)據(jù)量高度相關(guān),因此企業(yè)在應(yīng)用機器學習時通常會提供強大的計算資源作為支持,也因此當代的大部分集成算法都是支持GPU運算的(相對的,如果你發(fā)現(xiàn)一個算法在任何機器學習庫中,都沒有接入GPU運算的選項,這可能說明該算法在工業(yè)應(yīng)用中基本不會被使用)。
sklearn作為早期開源的機器學習算法庫,難以預(yù)料到如今人工智能技術(shù)走進千家萬戶的應(yīng)用狀況,因此并未開放接入GPU進行運算的接口,即sklearn中的所有算法都不支持接入更多計算資源。因此當我們想要使用隨機森林在巨量數(shù)據(jù)上進行運算時,很可能會遭遇計算資源短缺的情況。幸運的是,我們有兩種方式解決這個問題:
- 使用其他可以接入GPU的機器學習算法庫實現(xiàn)隨機森林,比如xgboost。
- 繼續(xù)使用sklearn進行訓練,但使用增量學習(incremental learning)。
增量學習是機器學習中非常常見的方法,在有監(jiān)督和無監(jiān)督學習當中都普遍存在。增量學習允許算法不斷接入新數(shù)據(jù)來拓展當前的模型,即允許巨量數(shù)據(jù)被分成若干個子集,分別輸入模型進行訓練。
1 普通學習 vs 增量學習
- 普通學習
通常來說,當一個模型經(jīng)過一次訓練之后,如果再使用新數(shù)據(jù)對模型進行訓練,原始數(shù)據(jù)訓練出的模型會被替代掉。舉個例子,我們原本的數(shù)據(jù)集X與y是kaggle房價數(shù)據(jù)集,結(jié)構(gòu)為:
X.shape #(1460, 80)y.shape #(1460,)現(xiàn)在,我們導(dǎo)入sklearn中非常常用的另一個數(shù)據(jù)集,加利福尼亞房價數(shù)據(jù)集:
from sklearn.datasets import fetch_california_housing from sklearn.metrics import mean_squared_errorX_fc = fetch_california_housing().data y_fc = fetch_california_housing().targetX_fc.shape #可以看到,加利福尼亞房價數(shù)據(jù)集的特征量為8 #(20640, 8)建模,并在X_,y_基礎(chǔ)上進行訓練:
model = RFR(n_estimators=3, warm_start=False) #不支持增量學習的 model1 = model.fit(X_fc,y_fc)#RMSE (mean_squared_error(y_fc,model1.predict(X_fc)))**0.5 #0.30123985583215596 #使用.estimators_查看森林中所有樹的情況,可以看到每一棵樹的隨機數(shù)種子 model1.estimators_ #[DecisionTreeRegressor(max_features='auto', random_state=1785210460), # DecisionTreeRegressor(max_features='auto', random_state=121562514), # DecisionTreeRegressor(max_features='auto', random_state=1271073231)]此時,如果讓model1繼續(xù)在kaggle房價數(shù)據(jù)集X,y上進行訓練:
model1 = model1.fit(X.iloc[:,:8],y) #注意,X有80個特征,X_fc只有8個特征,輸入同一個模型的數(shù)據(jù)必須結(jié)構(gòu)一致model1.estimators_ #你發(fā)現(xiàn)了嗎?model1中原始的樹消失了,新的樹替代了原始的樹 #[DecisionTreeRegressor(max_features='auto', random_state=349555903), # DecisionTreeRegressor(max_features='auto', random_state=1253222501), # DecisionTreeRegressor(max_features='auto', random_state=2145441582)]再讓model1對加利福尼亞房價數(shù)據(jù)集進行訓練,會發(fā)生什么呢?別忘了model1之前訓練過加利福尼亞房價數(shù)據(jù)集:
#RMSE (mean_squared_error(y_fc,model1.predict(X_fc)))**0.5 #188517.0427626784RMSE異常巨大,模型現(xiàn)在已經(jīng)不具備任何預(yù)測y_fc的能力了。非常明顯,model1中原始的樹消失了,基于kaggle數(shù)據(jù)集訓練的樹覆蓋了原始的樹,因此model1不再對本來見過的加利福尼亞房價數(shù)據(jù)報有記憶。
sklearn的這一覆蓋規(guī)則是交叉驗證可以進行的基礎(chǔ),正因為每次訓練都不會受到上次訓練的影響,我們才可以使用模型進行交叉驗證,否則就會存在數(shù)據(jù)泄露的情況。但在增量學習中,原始數(shù)據(jù)訓練的樹不會被替代掉,模型會一致記得之前訓練過的數(shù)據(jù),我們來看看詳細情況:
- 增量學習
我們還是可以使用X,y以及X_fc,y_fc作為例子,這一次,我們讓warm_start參數(shù)取值為True,允許隨機森林進行增量學習:
model = RFR(n_estimators=3, warm_start=True) #支持增量學習 model2 = model.fit(X_fc,y_fc) (mean_squared_error(y_fc,model2.predict(X_fc)))**0.5 #0.30099931130927154 model2.estimators_ #[DecisionTreeRegressor(max_features='auto', random_state=338470642), # DecisionTreeRegressor(max_features='auto', random_state=1545812511), # DecisionTreeRegressor(max_features='auto', random_state=740599321)]讓X和y在model2上繼續(xù)進行訓練:
model2 = model2.fit(X.iloc[:,:8],y) (mean_squared_error(y_fc,model2.predict(X_fc)))**0.5 #0.30099931130927154你發(fā)現(xiàn)了嗎?即便已經(jīng)對X和y進行了訓練,但是model2中對加利福尼亞房價數(shù)據(jù)集的記憶還在,因此在對X_fc與y_fc進行預(yù)測時,依然能夠取得不錯的分數(shù)。
model2.estimators_ #在增量學習當中,樹沒有發(fā)生變化 #[DecisionTreeRegressor(max_features='auto', random_state=338470642), # DecisionTreeRegressor(max_features='auto', random_state=1545812511), # DecisionTreeRegressor(max_features='auto', random_state=740599321)]所以在增量學習當中,已經(jīng)訓練過的結(jié)果會被保留。對于隨機森林這樣的Bagging模型來說,這意味著之前的數(shù)據(jù)訓練出的樹會被保留,新數(shù)據(jù)會訓練出新的樹,新舊樹互不影響。對于邏輯回歸、神經(jīng)網(wǎng)絡(luò)這樣不斷迭代以求解權(quán)重𝑤的算法來說,新數(shù)據(jù)訓練時w的起點是之前的數(shù)據(jù)訓練完畢之后的w。
不過,這里存在一個問題:雖然原來的樹沒有變化,但增量學習看起來并沒有增加新的樹——事實上,對于隨機森林而言,我們需要手動增加新的樹:
model2.estimators_ #屬性,反映訓練完畢的模型的一些特點、一些客觀存在的性質(zhì) #調(diào)用模型的參數(shù),可以通過這種方式修改模型的參數(shù),而不需要重新實例化模型 model2.n_estimators += 2 #增加2棵樹,用于增量學習 model2 #RandomForestRegressor(n_estimators=5, warm_start=True) model2.fit(X.iloc[:,:8],y) #RandomForestRegressor(n_estimators=5, warm_start=True) model2.estimators_ #原來的樹還是沒有變化,新增的樹是基于新輸入的數(shù)據(jù)進行訓練的 #[DecisionTreeRegressor(max_features='auto', random_state=338470642), # DecisionTreeRegressor(max_features='auto', random_state=1545812511), # DecisionTreeRegressor(max_features='auto', random_state=740599321), # DecisionTreeRegressor(max_features='auto', random_state=1633155700), # DecisionTreeRegressor(max_features='auto', random_state=623929223)]2 增量學習在Kaggle數(shù)據(jù)上的應(yīng)用
- 實際應(yīng)用
現(xiàn)在我們使用一個385MB的csv文件作為例子,進行巨量數(shù)據(jù)的導(dǎo)入和訓練(當然,在實際中csv文件往往是5G以上,基本不可能使用excel打開進行簡單分析或觀察)。該數(shù)據(jù)是來自Kaggle的五大人格心理特質(zhì)回歸數(shù)據(jù)集。五大人格心理特質(zhì)是心理學當中常見的人格分類法,也稱為FFM模型或OCEAN模型。這種人格分類法是通過給與被調(diào)查者一些描述性格方面的句子,讓被調(diào)查者選擇自己符合的項目,例如:
- 考試前我總是提前準備好一切,盡全力避免出錯
- 考試前我會花幾天時間準備
- 我在考試前臨時抱佛腳
- 我考試前從不準備
- 我不在意考試,甚至不記得考試的時間
你從中選擇最像你的選項,和最不像你的選項,選擇結(jié)果最終被用于性格分類。該數(shù)據(jù)集通過收集100w人群在大約80個問題當中的選項,得出最終性格分數(shù)和分類。訓練集大約有一百萬樣本,測試集則有2w樣本,更詳細的狀況可以查看:Big Five Personality Test | Kaggle
在面對大型數(shù)據(jù)時,我們采用循環(huán)模式分批讀取巨大csv或數(shù)據(jù)庫文件中的內(nèi)容,并將數(shù)據(jù)分批進行預(yù)處理、再增量學習到一個模型當中。在我們的例子中,由于學習的重點是增量學習,因此課堂上使用的數(shù)據(jù)是經(jīng)過我處理完畢、可以直接被隨機森林處理的數(shù)據(jù),在實際業(yè)務(wù)中,我們往往需要寫好一個可以預(yù)處理所有數(shù)據(jù)的pipeline,然后在循環(huán)的過程中不斷調(diào)用改pipeline。
現(xiàn)在,我們在干凈的數(shù)據(jù)上來看看增量學習具體的步驟吧:
1、定義訓練和測試數(shù)據(jù)地址
trainpath = r"D:\Pythonwork\2021ML\PART 2 Ensembles\datasets\Big data\bigdata_train.csv" testpath = r"D:\Pythonwork\2021ML\PART 2 Ensembles\datasets\Big data\bigdata_test.csv"2、設(shè)法找出csv中的總數(shù)據(jù)量
當我們決定使用增量學習時,數(shù)據(jù)應(yīng)該是巨大到不可能直接打開查看、不可能直接訓練、甚至不可能直接導(dǎo)入的(比如,超過20個G)。但如果我們需要對數(shù)據(jù)進行循環(huán)導(dǎo)入,就必須知道真實的數(shù)據(jù)量大概有多少,因此我們可以從以下途徑獲得無法打開的csv中的數(shù)據(jù)量:
- 如果是比賽數(shù)據(jù)集,一般可以在比賽頁面找到相應(yīng)的說明
- 如果是數(shù)據(jù)庫數(shù)據(jù)集,則可以在數(shù)據(jù)庫中進行統(tǒng)計
- 如果無法找到相應(yīng)的說明,可以使用deque庫導(dǎo)入csv文件的最后幾行,查看索引
- 如果數(shù)據(jù)沒有索引,就只能夠靠pandas嘗試找出大致的數(shù)據(jù)范圍了
可以看到最后一行的索引是995033,因此訓練集中有99w條數(shù)據(jù)。?
#如果數(shù)據(jù)沒有索引,則使用pandas中的skiprows與nrows進行嘗試 #skiprows: 本次導(dǎo)入跳過前skiprows行 #nrows:本次導(dǎo)入只導(dǎo)入nrows行 #例如,當skiprows=1000, nrows=1000時,pandas會導(dǎo)入1001~2000行 #當skiprows超出數(shù)據(jù)量時,就會報空值錯誤EmptyDataErrorfor i in range(0,10**7,100000):df = pd.read_csv(trainpath,skiprows=i, nrows=1)print(i) #0 #100000 #200000 #300000 #400000 #500000 #600000 #700000 #800000 #900000可以看到90w順利導(dǎo)入了,但是100w報錯了,所以數(shù)據(jù)量在90-100w之間。如果我們想,我們可以繼續(xù)精確數(shù)據(jù)量的具體范圍,但通常來說我們只要確認10w以內(nèi)的區(qū)域就可以了。
3、確認數(shù)據(jù)量后,準備循環(huán)范圍
[*range(0,10**6,50000)] #[0, # 50000, # 100000, # 150000, # 200000, # 250000, # 300000, # 350000, # 400000, # 450000, # 500000, # 550000, # 600000, # 650000, # 700000, # 750000, # 800000, # 850000, # 900000, # 950000] looprange = range(0,10**6,50000)4、建立增量學習使用的模型,定義測試集
reg = RFR(n_estimators=10,random_state=1412,warm_start=True,verbose=True #增量學習的過程總是很漫長的,你可以選擇展示學習過程,n_jobs=-1 #調(diào)用你全部的資源進行訓練)#定義測試集 test = pd.read_csv(testpath,header="infer",index_col=0) Xtest = test.iloc[:,:-1] Ytest = test.iloc[:,-1]Xtest.head()5、開始循環(huán)導(dǎo)入與增量學習
#當skiprows+nrows超出數(shù)據(jù)量的時候,會發(fā)生什么? trainsubset = pd.read_csv(trainpath, header=None, index_col=0, skiprows=950000, nrows=50000) trainsubset.tail(5) #會導(dǎo)出全部剩下的數(shù)據(jù),即便不足200w trainsubset = pd.read_csv(trainpath, header=None, index_col=0, skiprows=950000, nrows=50000)trainsubset.tail(5) #會導(dǎo)出全部剩下的數(shù)據(jù),即便不足200w trainsubset.shape #(45035, 110)for line in looprange:if line == 0:#首次讀取時,保留列名,并且不增加樹的數(shù)量header = "infer"newtree = 0else:#非首次讀取時,不要列名,每次增加10棵樹header = Nonenewtree = 10trainsubset = pd.read_csv(trainpath, header = header, index_col=0, skiprows=line, nrows=50000)Xtrain = trainsubset.iloc[:,:-1]Ytrain = trainsubset.iloc[:,-1]reg.n_estimators += newtreereg = reg.fit(Xtrain,Ytrain)print("DONE",line+50000)#當訓練集的數(shù)據(jù)量小于50000時,打斷循環(huán)if Xtrain.shape[0] < 50000:break # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.4s finished # DONE 50000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.3s finished # DONE 100000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.2s finished # DONE 150000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.3s finished # DONE 200000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.4s finished # DONE 250000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.3s finished # DONE 300000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.0s finished # DONE 350000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.1s finished # DONE 400000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.1s finished # DONE 450000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.2s finished # DONE 500000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.4s finished # DONE 550000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.1s finished # DONE 600000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.4s finished # DONE 650000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.2s finished # DONE 700000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.3s finished # DONE 750000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.1s finished # DONE 800000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.2s finished # DONE 850000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.1s finished # DONE 900000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 4.3s finished # DONE 950000 # [Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 12 concurrent workers. # DONE 1000000 # [Parallel(n_jobs=-1)]: Done 10 out of 10 | elapsed: 3.8s finished?現(xiàn)在,全部的99w數(shù)據(jù)都已經(jīng)訓練完畢了,我們可以在測試集上進行測試:
reg.score(Xtest,Ytest) #R2 99%,這可能與測試集上的數(shù)據(jù)太少有關(guān) #0.9903482355083931當使用增量學習時,如果需要調(diào)參,我們則需要將增量學習循環(huán)打包成一個評估器或函數(shù),以便在調(diào)參過程中不斷調(diào)用,這個過程所需的計算量是異常大的,不過至少我們擁有了在CPU上訓練巨大數(shù)據(jù)的方法。在后續(xù)的課程當中,我們將會講解如何將隨機森林或其他集成算法接入GPU進行訓練,進一步提升我們可以訓練的數(shù)據(jù)體量、進一步減少我們所需的訓練時間。
總結(jié)
以上是生活随笔為你收集整理的LESSON 9.5 随机森林在巨量数据上的增量学习的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LESSON 9.4 集成算法的参数空间
- 下一篇: LESSON 10.410.510.6