import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error
import lightgbm as lgb
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import KFold, RepeatedKFold
from scipy import sparse
#显示所有列
pd.set_option('display.max_columns', None)
#显示所有行
pd.set_option('display.max_rows', None)
from datetime import datetime
这一步主要是导入项目中的工具包。
#导入数据
train_abbr=pd.read_csv("../happiness_train_abbr.csv",encoding='ISO-8859-1')
train=pd.read_csv("../happiness_train_complete.csv",encoding='ISO-8859-1')
test_abbr=pd.read_csv("../happiness_test_abbr.csv",encoding='ISO-8859-1')
test=pd.read_csv("../happiness_test_complete.csv",encoding='ISO-8859-1')
test_sub=pd.read_csv("../happiness_submit.csv",encoding='ISO-8859-1')
这一步是导入数据,导入数据后,可以查看数据shape观察数据大小。我们发现test.shape(2968, 139), test_sub.shape(2968, 2), train.shape(8000, 140)。
接着运行命令train.info(verbose=True, null_counts=True)
verbose: 字面意思是冗长的,也就是说如果DataFrame有很多列,是否显示所有列的信息,如果为否,那么会省略一部分;
null_counts: 默认为True, 是否统计NaN值的个数。
#查看label分布
y_train_=train["happiness"]
y_train_.value_counts()
这一步可以查看label的分布。同时我们发现happiness存在值为-8的情况,不符合要求,因此需要处理,这里将其转换为3, 如下所示;
#将-8换成3
y_train_=y_train_.map(lambda x:3 if x==-8 else x)
#让label从0开始
y_train_=y_train_.map(lambda x:x-1)
#train和test连在一起
data = pd.concat([train,test],axis=0,ignore_index=True)
axis=0代表跨行,axis=1代表跨列。ignore_index=True指连接后重新赋值index。
#全部数据大小
data.shape
这时data.shape变为(10968, 140)
#处理时间特征
data['survey_time'] = pd.to_datetime(data['survey_time'],format='%Y-%m-%d %H:%M:%S')
data["weekday"]=data["survey_time"].dt.weekday
data["year"]=data["survey_time"].dt.year
data["quarter"]=data["survey_time"].dt.quarter
data["hour"]=data["survey_time"].dt.hour
data["month"]=data["survey_time"].dt.month
to_datetime将日期格式处理为"%Y-%m-%d %H:%M:%S"
#把一天的时间分段
def hour_cut(x):
if 0<=x<6:
return 0
elif 6<=x<8:
return 1
elif 8<=x<12:
return 2
elif 12<=x<14:
return 3
elif 14<=x<18:
return 4
elif 18<=x<21:
return 5
elif 21<=x<24:
return 6
data["hour_cut"]=data["hour"].map(hour_cut)
这里将一天的时间进行分段。.map()函数可以将数据的特定列应用自定义函数进行值的修改。这里函数将一天中的时间划分为几个时间段从而将时间格式处理为数字形式。
#做问卷时候的年龄
data["survey_age"]=data["year"]-data["birth"]
#让label从0开始
data["happiness"]=data["happiness"].map(lambda x:x-1)
#去掉三个缺失值很多的
data=data.drop(["edu_other"], axis=1)
data=data.drop(["happiness"], axis=1)
data=data.drop(["survey_time"], axis=1)
#是否入党
data["join_party"]=data["join_party"].map(lambda x:0 if pd.isnull(x) else 1)
join_party列中存在空值现象,这里将空值数据处理为0,非空值为1.
#出生的年代
def birth_split(x):
if 1920<=x<=1930:
return 0
elif 1930<x<=1940:
return 1
elif 1940<x<=1950:
return 2
elif 1950<x<=1960:
return 3
elif 1960<x<=1970:
return 4
elif 1970<x<=1980:
return 5
elif 1980<x<=1990:
return 6
elif 1990<x<=2000:
return 7
data["birth_s"]=data["birth"].map(birth_split)
与上面对时间段划分类似,将出生年代分段转换为数值的形式,方便进行数据的训练。
#收入分组
def income_cut(x):
if x<0:
return 0
elif 0<=x<1200:
return 1
elif 1200<x<=10000:
return 2
elif 10000<x<24000:
return 3
elif 24000<x<40000:
return 4
elif 40000<=x:
return 5
data["income_cut"]=data["income"].map(income_cut)
作用与上面相似。
#填充数据
data["edu_status"]=data["edu_status"].fillna(5)
data["edu_yr"]=data["edu_yr"].fillna(-2)
data["property_other"]=data["property_other"].map(lambda x:0 if pd.isnull(x) else 1)
data["hukou_loc"]=data["hukou_loc"].fillna(1)
data["social_neighbor"]=data["social_neighbor"].fillna(8)
data["social_friend"]=data["social_friend"].fillna(8)
data["work_status"]=data["work_status"].fillna(0)
data["work_yr"]=data["work_yr"].fillna(0)
data["work_type"]=data["work_type"].fillna(0)
data["work_manage"]=data["work_manage"].fillna(0)
data["family_income"]=data["family_income"].fillna(-2)
data["invest_other"]=data["invest_other"].map(lambda x:0 if pd.isnull(x) else 1)
data["minor_child"]=data["minor_child"].fillna(0)
data["marital_1st"]=data["marital_1st"].fillna(0)
data["s_birth"]=data["s_birth"].fillna(0)
data["marital_now"]=data["marital_now"].fillna(0)
data["s_edu"]=data["s_edu"].fillna(0)
data["s_political"]=data["s_political"].fillna(0)
data["s_hukou"]=data["s_hukou"].fillna(0)
data["s_income"]=data["s_income"].fillna(0)
data["s_work_exper"]=data["s_work_exper"].fillna(0)
data["s_work_status"]=data["s_work_status"].fillna(0)
data["s_work_type"]=data["s_work_type"].fillna(0)
这一步是在进行数据的填充,将原始数据中对应数据项为空的值设置为自定义的值。
X_train_ = data[:train.shape[0]]
X_test_ = data[train.shape[0]:]
之前合并过一次,这里继续将其拆分
target_column = 'happiness'
feature_columns=list(X_test_.columns)
feature_columns
target_column是label,feature_columns是特征集。
X_train = np.array(X_train_)
y_train = np.array(y_train_)
X_test = np.array(X_test_)
np.array:将列表list或元组tuple转换为ndarray数组。
#自定义评价函数
def myFeval(preds, xgbtrain):
label = xgbtrain.get_label()
score = mean_squared_error(label,preds)
return 'myFeval',score
##### xgb
xgb_params = {"booster":'gbtree','eta': 0.005, 'max_depth': 5, 'subsample': 0.7,
'colsample_bytree': 0.8, 'objective': 'reg:linear', 'eval_metric': 'rmse', 'silent': True, 'nthread': 8}
folds = KFold(n_splits=5, shuffle=True, random_state=2018)
oof_xgb = np.zeros(len(train))
predictions_xgb = np.zeros(len(test))
for fold_, (trn_idx, val_idx) in enumerate(folds.split(X_train, y_train)):
print("fold n°{}".format(fold_+1))
trn_data = xgb.DMatrix(X_train[trn_idx], y_train[trn_idx])
val_data = xgb.DMatrix(X_train[val_idx], y_train[val_idx])
watchlist = [(trn_data, 'train'), (val_data, 'valid_data')]
clf = xgb.train(dtrain=trn_data, num_boost_round=20000, evals=watchlist, early_stopping_rounds=200, verbose_eval=100, params=xgb_params,feval = myFeval)
oof_xgb[val_idx] = clf.predict(xgb.DMatrix(X_train[val_idx]), ntree_limit=clf.best_ntree_limit)
predictions_xgb += clf.predict(xgb.DMatrix(X_test), ntree_limit=clf.best_ntree_limit) / folds.n_splits
print("CV score: {:<8.8f}".format(mean_squared_error(oof_xgb, y_train_)))
xgb.train中参数解释:
params:这是一个字典,里面包含着训练中的参数关键字和对应的值。
dtrain:训练的数据
evals:这是一个列表,用于对训练过程中进行评估列表中的元素。形式是evals=[(dtrain, ‘train’),(dval, ‘val’)]或者是evals=[(dtrain, ‘train’)],对于第一种情况,它使得我们可以在训练过程中观察验证集的效果。
feval:自定义评估函数
early_stopping_rounds:早期停止次数,假设为100,验证集的误差迭代到一定程度在100次内不能再继续降低,就停止迭代。这要求evals里至少有一个元素,如果有多个,按最后一个去执行。返回的是最后的迭代次数(不是最好的)。如果early_stopping_rounds 存在,则模型会生成三个属性,bst.best_score,bst.best_iteration,和bst.best_ntree_limit
verbose_eval:可以输入布尔型或数值型。也要求evals里至少有一个元素。如果为True,则对evals中元素的评估效果会输出在结果中;如果输入数字,假设为5,则每隔5个迭代输出一次。
num_boost_round:控制迭代次数
enumerate:用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中。
seq = [‘one’, ‘two’, ‘three’]
for i, element in enumerate(seq):
… print i, element
…
0 one
1 two
2 three
KFold:k折交叉验证的使用。
n_split:要划分的折数
shuffle:每次都要进行shuffle,测试集中折数的总和就是训练集的个数。
random_state:随机状态
##### lgb
param = {'boosting_type': 'gbdt',
'num_leaves': 20,
'min_data_in_leaf': 20,
'objective':'regression',
'max_depth':6,
'learning_rate': 0.01,
"min_child_samples": 30,
"feature_fraction": 0.8,
"bagging_freq": 1,
"bagging_fraction": 0.8 ,
"bagging_seed": 11,
"metric": 'mse',
"lambda_l1": 0.1,
"verbosity": -1}
folds = KFold(n_splits=5, shuffle=True, random_state=2018)
oof_lgb = np.zeros(len(X_train_))
predictions_lgb = np.zeros(len(X_test_))
for fold_, (trn_idx, val_idx) in enumerate(folds.split(X_train, y_train)):
print("fold n°{}".format(fold_+1))
# print(trn_idx)
# print(".............x_train.........")
# print(X_train[trn_idx])
# print(".............y_train.........")
# print(y_train[trn_idx])
trn_data = lgb.Dataset(X_train[trn_idx], y_train[trn_idx])
val_data = lgb.Dataset(X_train[val_idx], y_train[val_idx])
num_round = 10000
clf = lgb.train(param, trn_data, num_round, valid_sets = [trn_data, val_data], verbose_eval=200, early_stopping_rounds = 100)
oof_lgb[val_idx] = clf.predict(X_train[val_idx], num_iteration=clf.best_iteration)
predictions_lgb += clf.predict(X_test, num_iteration=clf.best_iteration) / folds.n_splits
print("CV score: {:<8.8f}".format(mean_squared_error(oof_lgb, y_train_)))
xgboost的不足之处主要有:
1.每轮迭代时,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复的读写数据又会消耗非常大的时间。
2.预排序方法的时间和空间的消耗都很大。
lightgbm的优势:
1.更快的训练效率
2.更低内存使用
3.更高的准确率
4.支持并行化学习
5.可以处理大规模数据
params(关键参数):
提高准确率
learning_rate:学习率,最开始可以设置大一些,如0.1。调整完其他参数之后再将此参数调小。
max_depth:树模型深度。默认值-1,取值范围为3~8(不超过10)。
num_leaves:叶子节点数,树模型复杂度。默认值31,可以设置为2的n次幂,要大于分类的类别数。
降低过拟合
min_bin:工具箱数(叶子节点数+非叶子节点数)
min_data_in_leaf:一个叶子上数据的最小数量,可以用来处理过拟合。默认值20,调整策略搜索,不要太大。
feature_fraction:每次迭代中随机选择特征的比例。默认值1.0, 0.5~0.9之间调节,可以用来加速训练,也可以用来处理过拟合。
bagging_fraction:不进行重采样情况下随机选择部分数据。默认值1.0,调整策略0.5~0.9之间调节,可以用来加速训练,也可以用来处理过拟合。
bagging_frep:bagging的次数。0表示禁用bagging,非零值表示执行k次bagging。默认值为0,一般去3~5。
lambda1:L1正则
lambda2:L2正则
min_split_gain:执行切分的最小增益,默认值为0.1。
from catboost import Pool, CatBoostRegressor
# cat_features=[0,2,3,10,11,13,15,16,17,18,19]
from sklearn.model_selection import train_test_split
#X_train_s, X_test_s, y_train_s, y_test_s = train_test_split(X_train_, y_train_, test_size=0.3, random_state=2019)
# train_pool = Pool(X_train_s, y_train_s,cat_features=[0,2,3,10,11,13,15,16,17,18,19])
# val_pool = Pool(X_test_s, y_test_s,cat_features=[0,2,3,10,11,13,15,16,17,18,19])
# test_pool = Pool(X_test_ ,cat_features=[0,2,3,10,11,13,15,16,17,18,19])
kfolder = KFold(n_splits=5, shuffle=True, random_state=2019)
oof_cb = np.zeros(len(X_train_))
predictions_cb = np.zeros(len(X_test_))
kfold = kfolder.split(X_train_, y_train_)
fold_=0
#X_train_s, X_test_s, y_train_s, y_test_s = train_test_split(X_train, y_train, test_size=0.3, random_state=2019)
for train_index, vali_index in kfold:
print("fold n°{}".format(fold_))
fold_=fold_+1
k_x_train = X_train[train_index]
k_y_train = y_train[train_index]
k_x_vali = X_train[vali_index]
k_y_vali = y_train[vali_index]
cb_params = {
'n_estimators': 100000,
'loss_function': 'RMSE',
'eval_metric':'RMSE',
'learning_rate': 0.05,
'depth': 5,
'use_best_model': True,
'subsample': 0.6,
'bootstrap_type': 'Bernoulli',
'reg_lambda': 3
}
model_cb = CatBoostRegressor(**cb_params)
#train the model
model_cb.fit(k_x_train, k_y_train,eval_set=[(k_x_vali, k_y_vali)],verbose=100,early_stopping_rounds=50)
oof_cb[vali_index] = model_cb.predict(k_x_vali, ntree_end=model_cb.best_iteration_)
predictions_cb += model_cb.predict(X_test_, ntree_end=model_cb.best_iteration_) / kfolder.n_splits
print("CV score: {:<8.8f}".format(mean_squared_error(oof_cb, y_train_)))
catboost优点:
- 它自动采用特殊的方式处理类别型特征。首先用categorical features做一些统计,计算某个 类别特征(category)出现的概率,之后加上超参数,生成新的数值型特征。有了catboost再也不用手动处理类别型特征了。
- catboost还使用了组合类别特征,可以利用到特征之间的联系,这极大的丰富了特征维度。
- catboost的基模型采用的是对称树,同时计算leaf-value方式和传统的boosting算法也不一样。传统的boosting算法计算的是平均数,而catboost在这方面做了优化采用了其他算法,这些改进都能防止模型过拟合。
通用参数
learning_rate=automatically
depth(max_depth)=6: 树的深度
l2_leaf_reg(reg_lambda)=3: L2正则化系数
n_estimators=1000: 解决问题的树的最大数量
one_hot_max_size=2: 对于某些变量进行one-hot编码
loss_function=‘Logloss’
custom_metric=None
from sklearn import linear_model
# 将lgb和xgb和ctb的结果进行stacking
train_stack = np.vstack([oof_lgb,oof_xgb,oof_cb]).transpose()
test_stack = np.vstack([predictions_lgb, predictions_xgb,predictions_cb]).transpose()
folds_stack = RepeatedKFold(n_splits=5, n_repeats=2, random_state=2018)
oof_stack = np.zeros(train_stack.shape[0])
predictions = np.zeros(test_stack.shape[0])
for fold_, (trn_idx, val_idx) in enumerate(folds_stack.split(train_stack,y_train)):
print("fold {}".format(fold_))
trn_data, trn_y = train_stack[trn_idx], y_train[trn_idx]
val_data, val_y = train_stack[val_idx], y_train[val_idx]
clf_3 = linear_model.BayesianRidge()
#clf_3 =linear_model.Ridge()
clf_3.fit(trn_data, trn_y)
oof_stack[val_idx] = clf_3.predict(val_data)
predictions += clf_3.predict(test_stack) / 10
print("CV score: {:<8.8f}".format(mean_squared_error(oof_stack, y_train_)))
np.vstack:按垂直方向(行顺序)堆叠数组构成一个新的数组
np.hstack:按水平方向(列顺序)堆叠数组构成一个新的数组
np.transpose:对于二维ndarray,transpose在不指定参数的时候默认是矩阵转置。如果指定参数如x.transpose((0,1)),则不发生变化,反之为转置。
RepeatedKFold(n_splits=5,n_repeats=2,random_state =0)
重复n次k折交叉验证。