第三课 Rossman销售预测
加载数据
处理缺失数据
发现只有测试数据的Open
有十一个空的(训练数据没有)。由于很少,我们直接将其找出,如下
cond = test['Open'].isnull()
test[cond]
输出结果如下
然后我们去找出训练数据中商店\(622\)在2015年9月左右的营业情况,发现除了一些周末全部都是开着的,于是我们将非周末的缺失数据变成1
注意这道题目还多了一个数据,是商店的数据,于是还要处理这个数据,如下
进行变量命名方便后续处理
v1 = 'CompetitionDistance'
v2 = 'CompetitionOpenSinceMonth'
v3 = 'CompetitionOpenSinceYear'
v4 = 'Promo2SinceWeek'
v5 = 'Promo2SinceYear'
v6 = 'PromoInterval'
感觉v2
和v3
,v4
,v5
和v6
是同时缺失的,我们可以用如下代码查看一下
这验证了我们的猜想。那么接下来就是如何填充这个缺失值。显然直接用中位数或者均值却填写有失偏颇,然后视频就用\(0\)去填写,代表这是刚开业的,我有点搞不懂这个操作
划分训练集和验证集
之所以把这个单独作为一个标题,是因为这个视频的操作比较新。他先提取出销售额大于\(0\)的样本(为\(0\)的话表示关门了,只不过我觉得这一步做不做都无所谓),然后在这些提取出的样本中,选择一个商店(我觉得可以直接画所有商店),画出时间与销售额的关系,然后再去查看测试数据中一共有哪些日期(使用unique()
即可),发现全部都是八九月份,然后从先前的关系发现六七月份跟八九月份的销售额很类似,所以划分验证集的时候选择六七月份的数据作为验证集
合并数据集
这里因为多了一个商店的数据,所以将这个数据合并到训练集和测试集中去,如下
cond = train['Sales'] > 0
train = train[cond] # 过滤了销售额小于0的数据
train = pd.merge(train, store, on='Store', how='left')
test = pd.merge(test, store, on='Store', how='left')
特征工程
创造特征(是否在促销活动期间),哑变量转换(下面的代码要先将字符串转换成数字,我觉得没必要可以直接哑变量转换)
for data in [train, test]:
# 修改时间
data['year'] = data['Date'].apply(lambda x: x.split('-')[0]).astype(int)
data['month'] = data['Date'].apply(lambda x: x.split('-')[1]).astype(int)
data['day'] = data['Date'].apply(lambda x: x.split('-')[2]).astype(int)
# 店铺有一个字段:PromoInterval,string类型,无法进行建模
# IsPromoMonth 是否进行了促销
month2str = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep',
10: 'Oct', 11: 'Nov', 12: 'Dec'}
data['monthstr'] = data['month'].map(month2str)
# convert 是转换函数
convert = lambda x: 0 if x['PromoInterval'] == 0 else 1 if x['monthstr'] in x['PromoInterval'] else 0
# 这个月是否为促销月
data['IsPromoMonth'] = data.apply(convert, axis=1)
# 将存在字符串类型转换成数字:StoreType, Assortment, StateHoliday
mappings = {'0': 0, 'a': 1, 'b': 2, 'c': 3, 'd': 4}
data['StoreType'].replace(mappings, inplace=True)
data['Assortment'].replace(mappings, inplace=True)
data['StateHoliday'].replace(mappings, inplace=True)
然后删除一些特征,代码如下
display(train.shape,test.shape)
df_train = train.drop(['Date','monthstr','PromoInterval','Customers','Open'],axis = 1)
df_test = test.drop(['Date','monthstr','PromoInterval','Open','Id'],axis = 1)
display(df_train.shape,df_test.shape)
对训练集删除Customers
这个特征的原因是测试集没有这个特征,所以我们建模的时候不能用这个特征
然后查看一下标签的分布,发现是正态分布但是有偏态,所以直接取对数即可
构建模型
定义损失函数
使用均方根百分比误差,定义如下
代码如下
def rmspe(y, yhat):
return np.sqrt(np.mean((1 - yhat/y)**2))
但是注意,这里我们对标签取了对数的(数据太小不利于梯度下降,速度太慢),所以要用下面的函数
def rmspe_xg(y, yhat): # 放大镜
y = np.expm1(y)
yhat = np.expm1(yhat.get_label()) # DMatrix数据类型,get_label获取数据
return 'rmspe', rmspe(y, yhat)
Kaggle上面很多回归问题最终的score是0.多少,就是用的上面的公式
模型训练
代码见下
params = {'objective':'reg:linear',
'booster':'gbtree',
'eta':0.03,
'max_depth':10,
'subsample':0.9,
'colsample_bytree':0.7,
'silent':1,
'seed':10}
num_boost_round = 6000
dtrain = xgb.DMatrix(X_train, y_train)
dtest = xgb.DMatrix(X_test, y_test) # 保留的验证数据
print('模型训练开始……')
evals = [(dtrain,'train'), (dtest,'validation')]
xgb.train(params, # 模型参数
dtrain, # 训练数据
num_boost_round, # 轮次,决策树的个数
evals=evals, # 验证,评估的数据
early_stopping_rounds=100,
feval=rmspe_xg, # 模型评估的函数
verbose_eval=True) # 打印输出log日志,每次训练详情
对feval
的定义如下
输出如下
模型优化
这里是一个trick,不是调参,而是将预测值与真实值相比较,发现大部分预测值偏高,于是将预测值微调,即将预测值乘以一个\(w\)(其中\(w\)比\(1\)略小,具体为多少可以画学习曲线找出最佳值)。如果要更细致的话,可以对每一个商店找出对应的\(w\)。这个优化非常棒,经过上面的过程,没有调参的话能进前百分之十,调了参直接第三了