天池新人实战赛之[离线赛]之商品推荐

赛题介绍

竞赛题目
在真实的业务场景下,我们往往需要对所有商品的一个子集构建个性化推荐模型。在完成这件任务的过程中,我们不仅需要利用用户在这个商品子集上的行为数据,往往还需要利用更丰富的用户行为数据。定义如下符号:
U -- 用户集合
I -- 商品全集
P -- 商品子集,  P \sqsubseteq I 
D -- 用户对商品全集的行为数据集合
那么我们的目标是利用D来构造U中用户对P中商品的推荐模型。

数据说明
本场比赛提供20000用户的完整行为数据以及百万级的商品信息。竞赛数据包含两个部分。
第一部分是用户在商品全集上的移动端行为数据(D),表名为tianchi_fresh_comp_train_user_2w,包含如下字段:

字段字段说明提取说明
user_id用户标识抽样 & 字段脱敏
item_id商品标识字段脱敏
behavior_type用户对商品的行为类型包括浏览、收藏、加购物车、购买,对应取值分别是1、2、3、4.
user_geohash用户位置的空间标识,可以为空由经纬度通过保密算法生成
item_category商品分类标识字段脱敏
time行为时间精确到小时级别

第二部分是商品子集(P),表名是tianchi_fresh_comp_train_item_2w,包含如下字段:

字段字段说明提取说明
item_id商品标识抽样 & 字段脱敏
item_geohash商品位置的空间标识,可以为空由经纬度通过保密的算法生成
item_category商品分类标识字段脱敏

训练数据包含了抽样出来的一定量用户在一个月时间(11.18~12.18)之内的移动端行为数据(D),评分数据是这些用户在这个一个月之后的一天(12.19)对商品子集(P)的购买数据。参赛者要使用训练数据建立推荐模型,并输出用户在接下来一天对商品子集购买行为的预测结果。

评分数据格式
具体计算公式如下:参赛者完成用户对商品子集的购买预测之后,需要将结果放入指定格式的数据表(非分区表)中,要求结果表名为:tianchi_mobile_recommendation_predict.csv,且以utf-8格式编码;包含user_id和item_id两列(均为string类型),要求去除重复。

评估指标
比赛采用经典的精确度(precision)、召回率(recall)和F1值作为评估指标。具体计算公式如下:
Precision = \frac{\left | \cap (PredictionSet, ReferenceSet) \right |}{\left | PredictionSet \right |}

Recall =\frac{\left | \cap (PredictionSet, ReferenceSet) \right |}{\left | ReferenceSet \right |}

F1= \frac{2 \times Precision \times Recall}{Precision + Recall}

其中PredictionSet为算法预测的购买数据集合,ReferenceSet为真实的答案购买数据集合。我们以F1值作为最终的唯一评测标准。

 

解决方案

als
这里采用交替最小二乘法,通过用户对商品的打分矩阵,提取出用户和商品在隐式因子(Latent Factor,可以这样理解,一系列兴趣因子,用户、商品在这些兴趣因子上分别有一系列的打分取值)上的取值向量,通过用户与商品特征向量的内积得出用户对该商品的兴趣度,从大到小排序筛选出用户可能购买的商品。
pyspark.ml.recommendation下集成了该预测器,通过ParamGrid来设置超参数组合,再通过交叉验证来得到各个超参数组合下的模型,最后通过训练模型并评估指标得出最优的模型,具体的代码如下:

def getModel(scores_rdd1_ds):
    n = 0.001
    l = []
    while n < 50:
        l.append(n)
        n = n * 8

    from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
    from pyspark.ml.evaluation import RegressionEvaluator
    from pyspark.ml.recommendation import ALS
    als = ALS(userCol="user", itemCol="product", ratingCol="rating")
    paramGrid = ParamGridBuilder().addGrid(als.rank, range(10, 101, 10)) \
        .addGrid(als.maxIter, range(10, 71, 10)) \
        .addGrid(als.regParam, l) \
        .addGrid(als.implicitPrefs, [True, False]).build()
    evaluator = RegressionEvaluator(predictionCol="prediction", labelCol="rating")
    cross_val = CrossValidator(estimator=als, estimatorParamMaps=paramGrid, evaluator=evaluator, parallelism=4,
                               numFolds=2, collectSubModels=True)
    cvModel = cross_val.fit(scores_rdd1_ds)
    cross_val.getCollectSubModels()
    return cvModel.bestModel

这里通过对用户对指定商品的动作类型之和来计算得分:

users=spark.read.parquet('hdfs://master:8020/tmp.db/tianchi_fresh_comp_train_user-parquet')
users.createOrReplaceTempView('users')
scores_rdd=spark.sql('select user_id,item_id,sum(behavior_type) as score from users group by user_id,item_id').rdd
import pyspark.mllib.recommendation as rd
scores_rdd1=scores_rdd.map(lambda r:rd.Rating(r.user_id,r.item_id,r.score))
sc.setCheckpointDir("hdfs://master:8020/tmp.db/als")
scores_rdd1.checkpoint()

scores_rdd1_ds = sqlContext.createDataFrame(scores_rdd1)
bestModel = getModel(scores_rdd1_ds)
bestModel.save(hadoop_preffix+'/models/bestModel.model')

预测用户对商品子集的推荐结果的代码如下:

def predict():
    items = spark.read.parquet('hdfs://master:8020/tmp.db/tianchi_fresh_comp_train_item-parquet')
    item_item_ids = items.select('item_id').withColumnRenamed('item_id', 'product')
    item_item_ids.checkpoint() # 拿到item_ids列表
    result = bestModel.recommendForItemSubset(item_item_ids, 20) # 对每个商品分别推荐20个用户

    from pyspark.sql.functions import explode_outer
    rr = result.select("product", explode_outer('recommendations')).select('product', 'col.user', 'col.rating')
    rr = rr.orderBy(rr.rating.desc()).distinct() # 将商品-推荐用户的数据展开
    rr.checkpoint()

    cc = rr.count()
    rr = rr.limit(int(cc * 0.6))
    rr.select('user', 'product').withColumnRenamed('user','user_id').withColumnRenamed('product','item_id').write.csv('hdfs://master:8020/tmp.db/recommend0108_2.csv') # 推荐数据的前60%
    return rr

通过采样评估预测的误差度:

# 计算实际得分与预测得分之间误差的 均方根
def calc_err(test,model):
    pre_data = test.map(lambda r: ((r.user, r.product), r.rating))
    predicted_data = model.predictAll(pre_data.keys())
    predicted_data = predicted_data.map(lambda r: ((r.user, r.product), r.rating))
    err = predicted_data.join(pre_data).values()\
        .map(lambda x: (x[0] - x[1]) ** 2).reduce(lambda a,b:a+b)
    import math
    err = math.sqrt(err / test.count())
    return err


test=scores_rdd1.sample(False,0.2)
calc_err(test, bestModel)

这些方法运行的时间比较长,为了让程序在出现意外后可以重启在原来的基础上继续运行,使用了StreamingContext,完整代码如下:

from pyspark.streaming import StreamingContext
from pyspark.sql.context import SQLContext
from pyspark.sql.session import SparkSession


def setupFunc():
    streamingContext = StreamingContext(sc, batchDuration=20)
    sc = streamingContext.sparkContext
    spark = SparkSession(sc)
    sqlContext = SQLContext(sc, spark)

    users = spark.read.parquet('hdfs://master:8020/tmp.db/tianchi_fresh_comp_train_user-parquet')
    users.createOrReplaceTempView('users')
    scores_rdd = spark.sql('select user_id,item_id,sum(behavior_type) as score from users group by user_id,item_id').rdd
    import pyspark.mllib.recommendation as rd
    scores_rdd1 = scores_rdd.map(lambda r: rd.Rating(r.user_id, r.item_id, r.score))
    sc.setCheckpointDir("hdfs://master:8020/tmp.db/als")
    scores_rdd1.checkpoint()

    # model=rd.ALS.train(scores_rdd1,50,50,0.01)
    #     # model.predict(10001082,214252945)
    # model.save(sc,"hdfs://master:8020/tmp.db/als.model")
    # model.userFeatures()

    def getModel(scores_rdd1_ds):
        n = 0.001
        l = []
        while n < 50:
            l.append(n)
            n = n * 8

        from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
        from pyspark.ml.evaluation import RegressionEvaluator
        from pyspark.ml.recommendation import ALS
        als = ALS(userCol="user", itemCol="product", ratingCol="rating")
        paramGrid = ParamGridBuilder().addGrid(als.rank, range(10, 101, 10)) \
            .addGrid(als.maxIter, range(10, 71, 10)) \
            .addGrid(als.regParam, l) \
            .addGrid(als.implicitPrefs, [True, False]).build()
        evaluator = RegressionEvaluator(predictionCol="prediction", labelCol="rating")
        cross_val = CrossValidator(estimator=als, estimatorParamMaps=paramGrid, evaluator=evaluator, parallelism=4,
                                   numFolds=2, collectSubModels=True)
        cvModel = cross_val.fit(scores_rdd1_ds)
        cross_val.getCollectSubModels()
        return cvModel.bestModel

    scores_rdd1_ds = sqlContext.createDataFrame(scores_rdd1)
    bestModel = getModel(scores_rdd1_ds)

    def predict():
        items = spark.read.parquet('hdfs://master:8020/tmp.db/tianchi_fresh_comp_train_item-parquet')
        item_item_ids = items.select('item_id').withColumnRenamed('item_id', 'product')
        item_item_ids.checkpoint()
        result = bestModel.recommendForItemSubset(item_item_ids, 20)

        from pyspark.sql.functions import explode_outer
        rr = result.select("product", explode_outer('recommendations')).select('product', 'col.user', 'col.rating')
        rr = rr.orderBy(rr.rating.desc()).distinct()
        rr.checkpoint()
        cc = rr.count()
        rr = rr.limit(int(cc * 0.6))
        rr.select('user', 'product').withColumnRenamed('user', 'user_id').withColumnRenamed('product',
                                                                                            'item_id').write.csv(
            'hdfs://master:8020/tmp.db/recommend0108_2.csv')
        return rr

    rr = predict()

    # 计算实际得分与预测得分之间误差的 均方根
    def calc_err(test, model):
        pre_data = test.map(lambda r: ((r.user, r.product), r.rating))
        predicted_data = model.predictAll(pre_data.keys())
        predicted_data = predicted_data.map(lambda r: ((r.user, r.product), r.rating))
        err = predicted_data.join(pre_data).values() \
            .map(lambda x: (x[0] - x[1]) ** 2).reduce(lambda a, b: a + b)
        import math
        err = math.sqrt(err / test.count())
        return err

    test = scores_rdd1.sample(False, 0.2)
    calc_err(test, bestModel)

    return streamingContext


hadoop_preffix="hdfs://master:8020/tmp.db"
streamingContext=StreamingContext.getOrCreate(hadoop_preffix+'/als',setupFunc)
streamingContext.start()
streamingContext.awaitTermination()

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞是提高专业知识和技能水平的有效途径。通过参与竞,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞培养了学生的团队合作精神。许多竞项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞是提高学生综合能力的一种途径。竞项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参者具备全面的素质。在竞过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞可以为学生提供展示自我、树立信心的机会。通过比的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞对于个人职业发展具有积极的助推作用。在竞中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值