目录
1. 普通学习 vs 增量学习
1.1 普通学习
1.2 增量学习
2. 增量学习在Kaggle数据上的应用
sklearn作为早期开源的机器学习算法库,并未开放接入GPU进行运算的接口,即sklearn中的所有算法都不支持接入更多计算资源。因此当我们想要使用随机森林在巨量数据上进行运算时,很可能会遭遇计算资源短缺的情况。幸运的是,我们有两种方式解决这个问题:
使用其它可以接入GPU的机器学习算法库实现随机森林,比如xgboost。
继续使用sklearn进行训练,但使用增量学习(incremental learning)。
增量学习是机器学习中非常常见的方法,在有监督和无监督学习当中都普遍存在。增量学习允许算法不断接入新数据来拓展当前的模型,即允许巨量数据被分成若干个子集,分别输入模型进行训练。
1. 普通学习 vs 增量学习
1.1 普通学习
通常来说,当一个模型经过一次训练之后,如果再使用新数据对模型进行训练,原始数据训练出的模型会被替代掉。举个例子,现导入两个数据集,一个是加利福尼亚房价数据集,一个是kaggle房价数据集。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.tree import DecisionTreeRegressor as DTR
from sklearn.model_selection import cross_validate,KFold
from sklearn.datasets import fetch_california_housing
from sklearn.metrics import mean_squared_error
# 加利福尼亚房价数据集
data=pd.read_csv('F:\\Jupyter Files\\机器学习进阶\\集成学习\\datasets\\House Price\\train_encode.csv',encoding='utf-8')
data.drop('Unnamed: 0', axis=1, inplace=True)
x=data.iloc[:,:-1]
y=data.iloc[:,-1]
x.shape #(1460, 80)
# 加利福尼亚房价数据集
X_fc = fetch_california_housing().data
y_fc = fetch_california_housing().target
X_fc.shape #(20640, 8)
在加利福尼亚房价数据集上进行训练:
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
查看森林中所有树的情况,可以看到每一棵树的随机数种子:
model1.estimators_
[DecisionTreeRegressor(max_features='auto', random_state=1785210460), DecisionTreeRegressor(max_features='auto', random_state=121562514), DecisionTreeRegressor(max_features='auto', random_state=1271073231)]
让model1继续在kaggle房价数据集x,y上进行训练:
model1 = model1.fit(x.iloc[:,:8],y)
#注意!! x有80个特征,X_fc只有8个特征,输入同一个模型的数据必须结构一致
model1.estimators_
[DecisionTreeRegressor(max_features='auto', random_state=349555903), DecisionTreeRegressor(max_features='auto', random_state=1253222501), DecisionTreeRegressor(max_features='auto', random_state=2145441582)]
model1中原始的树消失了,新的树替代了原始的树。
再让model1对加利福尼亚房价数据集进行训练:
(mean_squared_error(y_fc,model1.predict(X_fc)))**0.5
235232.2375340384
RMSE异常巨大,模型现在已经不具备任何预测y_fc的能力了。非常明显,model1中原始的树消失了,基于kaggle数据集训练的树覆盖了原始的树,因此model1不再对本来见过的加利福尼亚房价数据报有记忆。
sklearn的这一覆盖规则是交叉验证可以进行的基础,正因为每次训练都不会受到上次训练的影响,我们才可以使用模型进行交叉验证,否则就会存在数据泄露的情况。但在增量学习中,原始数据训练的树不会被替代掉,模型会一致记得之前训练过的数据。
1.2 增量学习
在加利福尼亚房价数据集上进行训练:
# 增量学习
model = RFR(n_estimators=3, warm_start=True) #支持增量学习
model2 = model.fit(X_fc,y_fc)
model2.estimators_
[DecisionTreeRegressor(max_features=1.0, random_state=1192338237), DecisionTreeRegressor(max_features=1.0, random_state=506683268), DecisionTreeRegressor(max_features=1.0, random_state=654939120)]
(mean_squared_error(y_fc,model2.predict(X_fc)))**0.5
0.29385313927085455
让model2继续在kaggle房价数据集x,y上进行训练:
model2 = model2.fit(x.iloc[:,:8],y)
model2.estimators_
[DecisionTreeRegressor(max_features=1.0, random_state=1192338237), DecisionTreeRegressor(max_features=1.0, random_state=506683268), DecisionTreeRegressor(max_features=1.0, random_state=654939120)]
在增量学习当中,树没有发生变化
再让model2对加利福尼亚房价数据集进行训练:
(mean_squared_error(y_fc,model2.predict(X_fc)))**0.5
0.29385313927085455
即便已经对x和y进行了训练,但是model2中对加利福尼亚房价数据集的记忆还在,因此在对X_fc与y_fc进行预测时,依然能够取得不错的分数。
在增量学习当中,树没有发生变化,已经训练过的结果会被保留。对于随机森林这样的Bagging模型来说,这意味着之前的数据训练出的树会被保留,新数据会训练出新的树,新旧树互不影响。
不过,这里存在一个问题:虽然原来的树没有变化,但增量学习看起来并没有增加新的树,事实上,对于随机森林而言,需要手动增加新的树:
#调用模型的参数,可以通过这种方式修改模型的参数,而不需要重新实例化模型
model2.n_estimators += 2 #增加2棵树,用于增量学习
model2.fit(x.iloc[:,:8],y)
model2.estimators_ #原来的树还是没有变化,新增的树是基于新输入的数据进行训练的
[DecisionTreeRegressor(max_features=1.0, random_state=1192338237), DecisionTreeRegressor(max_features=1.0, random_state=506683268), DecisionTreeRegressor(max_features=1.0, random_state=654939120), DecisionTreeRegressor(max_features=1.0, random_state=1440840641), DecisionTreeRegressor(max_features=1.0, random_state=1050229920)]
2. 增量学习在Kaggle数据上的应用
在面对大型数据时,我们采用循环模式分批读取巨大csv或数据库文件中的内容,并将数据分批进行预处理、再增量学习到一个模型当中。
STEP1:定义训练和测试数据地址
trainpath = r"F:\Jupyter Files\机器学习进阶\集成学习\datasets\Big data\bigdata_train.csv"
testpath = r"F:\Jupyter Files\机器学习进阶\集成学习\datasets\Big data\bigdata_test.csv"
STEP2:设法找出csv中的总数据量
当我们决定使用增量学习时,数据应该是巨大到不可能直接打开查看、不可能直接训练、甚至不可能直接导入的(比如,超过20个G)。但如果我们需要对数据进行循环导入,就必须知道真实的数据量大概有多少,因此我们可以从以下途径获得无法打开的csv中的数据量:
- 如果是比赛数据集,一般可以在比赛页面找到相应的说明
- 如果是数据库数据集,则可以在数据库中进行统计
- 如果无法找到相应的说明,可以使用deque库导入csv文件的最后几行,查看索引
- 如果数据没有索引,就只能够靠pandas尝试找出大致的数据范围了
方式一:数据集存在索引
#使用deque与StringIO辅助,导入csv文件最后的n行
from collections import deque #deque:双向队列
from io import StringIO
with open(trainpath, 'r') as data:
q = deque(data, 5)
pd.read_csv(StringIO(''.join(q)), header=None)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | |
0 | 995029 | 3.0 | 3.0 | 5.0 | 5.0 | 2.0 | 3.0 | 2.0 | 5.0 | 5.0 | ... | 291658.0 | 666.0 | 469.0 | 37.0 | 1954.0 | 33.0 | 0.0 | 41.0 | 865.0 | -70.6503 |
1 | 995030 | 2.0 | 4.0 | 4.0 | 2.0 | 4.0 | 2.0 | 4.0 | 4.0 | 4.0 | ... | 968800.0 | 666.0 | 469.0 | 6.0 | 208.0 | 30.0 | 0.0 | 208.0 | 19838.0 | -123.0867 |
2 | 995031 | 2.0 | 1.0 | 3.0 | 2.0 | 5.0 | 1.0 | 5.0 | 4.0 | 4.0 | ... | 567037.0 | 93.0 | 541.0 | 596.0 | 2892.0 | 1602.0 | 0.0 | 144.0 | 2745.0 | 112.5000 |
3 | 995032 | 1.0 | 4.0 | 1.0 | 5.0 | 2.0 | 2.0 | 1.0 | 5.0 | 2.0 | ... | 989963.0 | 57.0 | 441.0 | 13.0 | 520.0 | 29.0 | 0.0 | 208.0 | 10546.0 | -97.0000 |
4 | 995033 | 3.0 | 2.0 | 4.0 | 3.0 | 4.0 | 2.0 | 4.0 | 3.0 | 4.0 | ... | 443675.0 | 36.0 | 272.0 | 3.0 | 285.0 | 15.0 | 0.0 | 208.0 | 9322.0 | -76.3729 |
可以看到最后一行的索引是995033,因此训练集中有99w条数据。
方式二:数据集没有索引
如果数据没有索引,则使用pandas中的skiprows与nrows进行尝试。skiprows: 本次导入跳过前skiprows行。nrows:本次导入只导入nrows行。例如,当skiprows=1000, nrows=1000时,pandas会导入1001~2000行。当skiprows超出数据量时,就会报空值错误EmptyDataError。
for i in range(0,10**7,100000):
df = pd.read_csv(trainpath,skiprows=i, nrows=1)
print(i)
0100000 200000 300000 400000 500000 600000 700000 800000 900000
---------------------------------------------------------------------------EmptyDataError Traceback (most recent call last)
可以看到90w顺利导入了,但是100w报错了,所以数据量在90-100w之间。还可以继续精确数据量的具体范围,但通常来说我们只要确认10w以内的区域就可以了。
STEP3:确认数据量后,准备循环范围
looprange = range(0,10**6,50000)
STEP4:建立增量学习使用的模型,定义测试集
reg = RFR(n_estimators=10
,random_state=1412
,warm_start=True
,verbose=True #增量学习的过程总是很漫长的,你可以选择展示学习过程
)
#定义测试集
test = pd.read_csv(testpath,header="infer",index_col=0)
Xtest = test.iloc[:,:-1]
Ytest = test.iloc[:,-1]
STEP5:开始循环导入与增量学习
注:当skiprows+nrows超出数据量的时候,会导出全部剩下的数据。
for line in looprange:
if line == 0:
#首次读取时,保留列名,并且不增加树的数量
header = "infer"
newtree = 0
else:
#非首次读取时,不要列名,每次增加10棵树
header = None
newtree = 10
trainsubset = pd.read_csv(trainpath, header = header, index_col=0, skiprows=line, nrows=50000)
Xtrain = trainsubset.iloc[:,:-1]
Ytrain = trainsubset.iloc[:,-1]
reg.n_estimators += newtree
reg = reg.fit(Xtrain,Ytrain)
print("DONE",line+50000)
#当训练集的数据量小于50000时,打断循环
if Xtrain.shape[0] < 50000:
break
全部的数据都已经训练完毕后,在测试集上进行测试:
reg.score(Xtest,Ytest)
0.9903482355083931
当使用增量学习时,如果需要调参,我们则需要将增量学习循环打包成一个评估器或函数,以便在调参过程中不断调用,这个过程所需的计算量是异常大的,不过至少我们拥有了在CPU上训练巨大数据的方法。