基于PySpark的销量预测

“ 本文阐述基于PySpark的sql数据读取、特征处理、寻找最优参数、使用最优参数预测未来销量的全过程,重在预测流程和Pyspark相关知识点的讲解,展示可供企业级开发落地的demo。”

1 数据读取与预处理

1.1 数据读取

df = spark.sql("""
    select store_code,goods_code,ds,qty as label
  from xxx.store_sku_sale
  where ds>='2020-05-22' and store_code in ('Z001','Z002')
    """)

1.2 特征生成

1).dayofweek等函数是从from pyspark.sql.functions import *中functions的类而来;
2).数据此时是spark.dataframe格式,用类sql的形式进行操作;
3).withColumn函数为新增一列;
4).为说明问题简化了特征预处理,只是使用是否月末和星期的OneHotEncoder作为特征。

df = df.withColumn('dayofweek', dayofweek('ds'))
df = df.withColumn("dayofweek", df["dayofweek"].cast(StringType()))
# 是否月末编码
df = df.withColumn('day', dayofmonth('ds'))
df = df.withColumn('day', df["day"].cast(StringType()))
df = df.withColumn('month_end', when(df['day'] <= 25, 0).otherwise(1))
# 星期编码--将星期转化为了0-1变量,从周一至周天
dayofweek_ind = StringIndexer(inputCol='dayofweek', outputCol='dayofweek_index')
dayofweek_ind_model = dayofweek_ind.fit(df)
dayofweek_ind_ = dayofweek_ind_model.transform(df)
onehotencoder = OneHotEncoder(inputCol='dayofweek_index',                                           outputCol='dayofweek_Vec')
df = onehotencoder.transform(dayofweek_ind_)

1.3 数据集的划分

此时产生的dayofweek_Vec是一个向量,在时序领域,统计特征非常重要,比如mean,std,这些特征是可以由sql来完成,但one_hot这类特征使用sql可能乏力,于是可以借助pyspark.ml中的特征处理模块,如果还是无法很好的处理特征,便需要借助numpy等使用spark自定义函数udf进行操作。还需留意一点,pyspark.ml中默认的输入特征是转换后名为features的稠密向量(DenseVector),也就是多行row合并在一起,另外,作为有监督学习,和features对应的标签默认名为label。

2 模型构建和调优

2.1 设置参数空间

下面我们以最简单的回归模型作为演示。

线性回归模型最重要的三个参数为:regParam–正则系数;fitIntercept–是否带截距elasticNetParam–弹性网,[0,1]之间,0表示L2;1表示L1;此时我们为每个参数构建一个参数空间。

lr_params = ({'regParam': 0.00}, {'fitIntercept': True}, {'elasticNetParam': 0.5})
lr = LinearRegression(maxIter=100, regParam=lr_params[0]['regParam'], \
                      fitIntercept=lr_params[1]['fitIntercept'], \
                      elasticNetParam=lr_params[2]['elasticNetParam'])

lrParamGrid = ParamGridBuilder() \
    .addGrid(lr.regParam, [0.005, 0.01, 0.1, 0.5]) \
    .addGrid(lr.fitIntercept, [False, True]) \
    .addGrid(lr.elasticNetParam, [0.0, 0.1, 0.5, 1.0]) \
    .build()

2.2 交叉验证

使用五折交叉验证,在备选空间上寻找最优参数,此时的lr_best_params为向量,可以使用type(lr_best_params)查看该对象的数据类型。

cross_valid = CrossValidator(estimator=lr, estimatorParamMaps=lrParamGrid, evaluator=RegressionEvaluator(),
                          numFolds=5)

cvModel = cross_valid.fit(train_data)

best_parameters = [(
    [{key.name: paramValue} for key, paramValue in zip(params.keys(), params.values())], metric) \
    for params, metric in zip(
        cvModel.getEstimatorParamMaps(),
        cvModel.avgMetrics)]

lr_best_params = sorted(best_parameters, key=lambda el: el[1], reverse=True)[0]

以上交叉验证自然是比较费时的,也可以使用sample函数随机抽取一定比例的数据放入模型中。

2.3 dataframe转换

下面借用pd.DataFrame把以上关键参数转换为结构化数据,方便后面直接转换为spark.dataframe。为了检查使用最优参数前面的评价指标,如mape是否有下降,是可以把类似的这些参数一并写入数据库。

pd_best_params = pd.DataFrame({
    'regParam':[lr_best_params[0][0]['regParam']],
    'fitIntercept':[lr_best_params[0][1]['fitIntercept']],
    'elasticNetParam':[lr_best_params[0][2]['elasticNetParam']]
})
pd_best_params['update_date'] = today
pd_best_params['update_time'] = update_time
pd_best_params['model_type'] = 'linear'

2.4 dataframe最优参数保存至数据库

pd.DataFrame–>spark.dataframe,以追加的形式写入hive,得到的最优参数供后续模型预测使用。

spark.createDataFrame(pd_best_params).write.mode("append").format('hive').saveAsTable(
    'temp.regression_model_best_param')

3 读取预测数据集和最佳参数

3.1 生成并读取预测数据集

通过union合并真实销售数据,并使用join on 1=1产生门店/商品/时间维度的笛卡尔集。

df = spark.sql(f"""
    select store_code,goods_code,ds,qty
   from xxx.test_store_sale
   where ds>='{prev28_day}' and ds<'{today}'
    union
    select s.store_code,s.goods_code,d.ds,0 as qty
    from
    (select stat_date as ds from xxl.dim_date where stat_date<'{after7_day}' and   stat_date>='{today}') d
    join
    (select
    distinct
    store_code,goods_code
    from xxx.test_store_sale
    ) s on 1=1""")

3.2 读取最佳参数

仅以regparam参数为例,把从sql中读取出来的数据转化为标量,然后转换为可供模型函数调用的实际参数值,(因表中数据很小所以使用了collect)。

best_param_set=spark.sql(f"select regparam,fitIntercept, elasticNetParam from scmtemp.regression_model_best_param order by update_date desc,update_time desc limit 1 ").collect()
reg_vec=best_param_set.select('regparam')
reg_b= [row.regparam for row in reg_vec][0]
reg_b=float(reg_b)

4 模型预测并写入sql

在上文的交叉验证阶段我们对数据集的划分为形式为random,在预测阶段,需按照指定时间划分。

train_data=df.where(df['ds'] <today)
test_data=df.where(df['ds'] >=today)
train_mod01 = assembler.transform(train_data)
train_mod02 = train_mod01.selectExpr("features","qty as label")

test_mod01 = assembler.transform(test_data)
test_mod02 = test_mod01.select("store_code","goods_code","ds","features")

# build train the model
lr = LinearRegression(maxIter=100,regParam=reg_b, fitIntercept=inter_b,elasticNetParam=elastic_b, solver="normal")
model = lr.fit(train_mod02)
predictions = model.transform(test_mod02)
predictions.select("store_code","goods_code","ds","prediction").show(5)
test_store_predict=predictions.select("store_code","goods_code","ds","prediction").createOrReplaceTempView('test_store_predict')
spark.sql(f"""create table xxx.regression_test_store_predict as select * from test_store_predict""")

在交叉验证阶段使用的是evaluate函数放入测试集进行模型评估,在正式的预测场景使用的是transform来预测,如果预放入模型的特征已经转换为名为features的向量,在transform预测阶段放入的数据是可以带入时间和store_code,sku_code等列,预测输出默认列名为prediction。

结语

以上流程其实是两个阶段,分别为模型交叉验证寻找最优参数与使用最优参数预测训练,其中,寻找最优参数阶段,虽然我们已经用到了spark这个大数据处理利器,但是参数空间和本身放入的数据往往都不小,所以在实际使用过程中,出于计算性能考虑和实际需要,最优参数更新的频率可能低于模型预测周期,比如,因每天产生销售数据,预测未来的模型是每天执行,但是最优参数的更新周期可能是一周执行一次,除了节省资源考虑,还有交叉验证得到的最优参数往往会较为稳定。

这就是本文分享的PySpark销量预测全流程,为书写编辑便利,合并和简化了某些步骤,比如特征处理,只是点到其中关键点,如兴趣可以沿着带过的知识点扩增其他,最后,欢迎交流指正。

(本文涉及到的全部代码请点击

参考:
1.http://spark.apache.org/docs/2.3.0/api/python/pyspark.sql.html#pyspark.sql.functions.pandas_udf>
2.http://spark.apache.org/docs/2.3.0/api/python/pyspark.ml.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值