机器学习实践项目(一)- Rossman商店销售预测 - 结果微调

上一篇文章我们提到,按照训练的结果,取平均Ratio的话,是整体略偏高的,预测值大概是真实值的1.001倍,因此需要对预测的结果进行微调。微调的思路有两种,一种是全部门店乘以同一个权重进行调整,另一种是每个门店计算一个单独的权重进行微调,下面我们把两种思路都实现一遍。

1. 全部门店用统一的权重进行微调

运行以下代码计算微调权重。

# 上面是未经过微调的结果,下面开始对所有门店的预测销售金额通过一个统一的微调权重进行微调,并评估微调结果
weights = [(0.99 + (i / 1000)) for i in range(200)] # 创建一个权重的list,共200个
errors = []
for w in weights:
    error = rmspe(np.expm1(y_validate), np.expm1(yhat * w)) # 尝试用每一个生成的权重对预测值进行放大,并计算均方根百分比误差存到errors中
    errors.append(error)

errors = pd.Series(errors, index = weights) # errors转换成Pandas的Series结构

# 设置画图参数准备画出所有权重的均方根百分比误差值,需要挑一个最小的作为最终的权重
plt.figure(figsize = (9, 6))
errors.plot()
plt.xlabel('权重系数', fontsize = 18)
plt.ylabel('均方根百分比误差', fontsize = 18)

# 找到误差最小的位置
best_index = errors.argmin() # argmin()方法返回序列中最小值的索引
best_weight = errors.index[best_index]   # 最佳权重值
best_error = errors.iloc[best_index]     # 最佳权重值对应的最小误差

print(f"最佳的偏差校正权重: {best_weight:.3f}")
print(f"对应的最小均方根百分比误差: {best_error:.6f}")

运行完毕后,我们发现输出结果如下。
用统一权重微调后的RMSPE
这个结果和前面不微调时的RMSPE相比要更小。
没有微调的RMSPE

所有的RMSP的折线图如下,可以看到最小的值大概是在1不到点,就是上图的0.999
全部的RMSPE

因此,我们采用统一权重对预测结果进行微调还是卓有成效的,下面我们试一下对每个门店用单独的参数进行微调。

2. 每个门店用单独的权重进行微调

运行如下代码:

# 上面是对所有门店预测的销售金额用同一个微调权重进行微调,下面对所有门店用单独的权重进行微调并评估效果
shops = np.arange(1, 1116) # 生成1~1115的门店编号
weights_validate = []
weights_test = []

for shop in shops:
    cond = res['Store'] == shop # 筛选出当前门店在 res 里的数据,res保存的是验证集,以及验证集的预测值
    df1 = pd.DataFrame(data = res[cond], columns = col_1) # 从res数据集中生成一个新的DF,取出这个门店基于验证集预测出来的预测结果和真实值

    weights = [(0.98 + (i / 1000)) for i in range(40)] # 同在验证集中的做法一样,这里生成一个适用于测试集的权重列表,里面的每个权重将用来微调预测结果
    errors = []
    for w in weights:
        error = rmspe(np.expm1(df1['Sales']), np.expm1(df1['Prediction'] * w)) # 为当前门店基于weights中的每个权重计算RMSPE
        errors.append(error)

    errors = pd.Series(errors, index = weights)
    # 找到误差最小的位置
    best_index = errors.argmin() # 为当前门店取出RMSPE最小的值对应的索引
    best_weight = np.array(weights[best_index]) # 取出当前门店最小的权重
    weights_validate.extend(best_weight.repeat(len(df1)).tolist()) # 把最佳权重 复制并扩展成和当前门店数据行数一样的数据集
    
    cond2 = df_test['Store'] == shop # 筛选出测试集中的对应门店
    df2 = pd.DataFrame(data = df_test[cond2]) # 生成一个新的DF,从测试集中取出这个门店的数据
    weights_test.extend(best_weight.repeat(len(df2)).tolist()) # 把最佳权重 复制并填充到与该门店所有数据行的权重字段

# 按门店编号排序 X_validate,然后把 weights_validate 填进去。
# 再恢复原本的索引顺序。
# 最后把 weights_validate 提取出来单独存储,同时从 X_validate 删除这个辅助列。
# 这几行代码的目的,其实就是 把按门店计算好的权重序列 weights_validate,正确地对齐回 X_validate 原始数据的顺序
X_validate= X_validate.sort_values(by ='Store')
X_validate['weights_validate'] = weights_validate
X_validate = X_validate.sort_index()
weights_validate = X_validate['weights_validate']
X_validate = X_validate.drop(['weights_validate'], axis = 1)

# 按门店编号排序 df_test,然后把 weights_test 填进去。
# 再恢复原本的索引顺序。
# 最后把 weights_test 提取出来单独存储,同时从 df_test 删除这个辅助列。
df_test= df_test.sort_values(by = 'Store')
df_test['weights_test'] = weights_test
df_test = df_test.sort_index()
weights_test = df_test['weights_test']
df_test = df_test.drop(['weights_test'], axis = 1)

逻辑有点复杂,画一个流程图来看一下。

开始:为每个门店寻找最优微调权重,
并对齐回验证集与测试集
初始化:shops = 1..1115;
weights_validate = [];weights_test = []
进入循环:
遍历每个 shop ∈ shops
① 验证集筛选:df1 = res[Store==shop]
(含真实值与预测值,仅保留 col_1)
② 生成候选权重:
weights = {0.980, 0.981, …, 1.019}(共40个)
③ 逐权重计算误差:error = RMSPE(exp(Sales)-1,
exp(Prediction*w)-1)
④ 选择最小误差对应的 best_weight,
并扩展到 weights_validate
⑤ 筛选测试集对应门店 df2,
并将 best_weight 扩展到 weights_test
是否还有下一个门店?
⑥ 验证集对齐:按 Store 排序 → 填入 weights_validate →
恢复原索引 → 删除辅助列
⑦ 测试集对齐:按 Store 排序 → 填入 weights_test →
恢复原索引 → 删除辅助列
结束:得到与原始行顺序严格对齐的
weights_validate 与 weights_test

运行结束后,评估一下效果。

yhat_new = yhat * weights_validate # 按照每个门店的微调权重,对每个门店的预测值进行微调
error = rmspe(np.expm1(y_validate), np.expm1(yhat_new)) # 计算所有门店的RMSPE
print(f"RMSPE = {error:.6f}  ({error * 100:.4f}%)")

发现RMSPE比按照统一权重微调又好了很多。
每个门店单独微调的RMSPE

我感觉我可以交差了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值