根据用户行为数据创建ALS模型并召回商品

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


二 根据用户行为数据创建ALS模型并召回商品

2.0 用户行为数据拆分

  • 方便练习可以对数据做拆分处理

    • pandas的数据分批读取 chunk 厚厚的一块 相当大的数量或部分
    import pandas as pd
    reader = pd.read_csv('behavior_log.csv',chunksize=100,iterator=True)
    count = 0;
    for chunk in reader:
        count += 1
        if count ==1:
            chunk.to_csv('test4.csv',index = False)
        elif count>1 and count<1000:
            chunk.to_csv('test4.csv',index = False, mode = 'a',header = False)
        else:
            break
    pd.read_csv('test4.csv')
    

2.1 预处理behavior_log数据集

  • 创建spark session
import os
# 配置spark driver和pyspark运行时,所使用的python解释器路径
PYSPARK_PYTHON = "/miniconda2/envs/py365/bin/python"
JAVA_HOME='/root/bigdata/jdk'
SPARK_HOME = "/root/bigdata/spark"
# 当存在多个版本时,不指定很可能会导致出错
os.environ["PYSPARK_PYTHON"] = PYSPARK_PYTHON
os.environ["PYSPARK_DRIVER_PYTHON"] = PYSPARK_PYTHON
os.environ['JAVA_HOME']=JAVA_HOME
os.environ["SPARK_HOME"] = SPARK_HOME
# spark配置信息
from pyspark import SparkConf
from pyspark.sql import SparkSession

SPARK_APP_NAME = "preprocessingBehaviorLog"
SPARK_URL = "spark://192.168.19.137:7077"

conf = SparkConf()    # 创建spark config对象
config = (
    ("spark.app.name", SPARK_APP_NAME),    # 设置启动的spark的app名称,没有提供,将随机产生一个名称
    ("spark.executor.memory", "6g"),    # 设置该app启动时占用的内存用量,默认1g
    ("spark.master", SPARK_URL),    # spark master的地址
    ("spark.executor.cores", "4"),    # 设置spark executor使用的CPU核心数
    # 以下三项配置,可以控制执行器数量
#     ("spark.dynamicAllocation.enabled", True),
#     ("spark.dynamicAllocation.initialExecutors", 1),    # 1个执行器
#     ("spark.shuffle.service.enabled", True)
#     ('spark.sql.pivotMaxValues', '99999'),  # 当需要pivot DF,且值很多时,需要修改,默认是10000
)
# 查看更详细配置及说明:https://spark.apache.org/docs/latest/configuration.html

conf.setAll(config)

# 利用config对象,创建spark session
spark = SparkSession.builder.config(conf=conf).getOrCreate()
  • 从hdfs中加载csv文件为DataFrame
# 从hdfs加载CSV文件为DataFrame
df = spark.read.csv("hdfs://localhost:9000/data/behavior_log.csv", header=True)
df.show()    # 查看dataframe,默认显示前20条
# 大致查看一下数据类型
df.printSchema()    # 打印当前dataframe的结构

显示结果:

+------+----------+----+-----+------+
|  user|time_stamp|btag| cate| brand|
+------+----------+----+-----+------+
|558157|1493741625|  pv| 6250| 91286|
|558157|1493741626|  pv| 6250| 91286|
|558157|1493741627|  pv| 6250| 91286|
|728690|1493776998|  pv|11800| 62353|
|332634|1493809895|  pv| 1101|365477|
|857237|1493816945|  pv| 1043|110616|
|619381|1493774638|  pv|  385|428950|
|467042|1493772641|  pv| 8237|301299|
|467042|1493772644|  pv| 8237|301299|
|991528|1493780710|  pv| 7270|274795|
|991528|1493780712|  pv| 7270|274795|
|991528|1493780712|  pv| 7270|274795|
|991528|1493780712|  pv| 7270|274795|
|991528|1493780714|  pv| 7270|274795|
|991528|1493780765|  pv| 7270|274795|
|991528|1493780714|  pv| 7270|274795|
|991528|1493780765|  pv| 7270|274795|
|991528|1493780764|  pv| 7270|274795|
|991528|1493780633|  pv| 7270|274795|
|991528|1493780764|  pv| 7270|274795|
+------+----------+----+-----+------+
only showing top 20 rows

root
 |-- user: string (nullable = true)
 |-- time_stamp: string (nullable = true)
 |-- btag: string (nullable = true)
 |-- cate: string (nullable = true)
 |-- brand: string (nullable = true)
  • 从hdfs加载数据为dataframe,并设置结构
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, LongType
# 构建结构对象
schema = StructType([
    StructField("userId", IntegerType()),
    StructField("timestamp", LongType()),
    StructField("btag", StringType()),
    StructField("cateId", IntegerType()),
    StructField("brandId", IntegerType())
])
# 从hdfs加载数据为dataframe,并设置结构
behavior_log_df = spark.read.csv("hdfs://localhost:9000/data/behavior_log.csv", header=True, schema=schema)
behavior_log_df.show()
behavior_log_df.count()

显示结果:

+------+----------+----+------+-------+
|userId| timestamp|btag|cateId|brandId|
+------+----------+----+------+-------+
|558157|1493741625|  pv|  6250|  91286|
|558157|1493741626|  pv|  6250|  91286|
|558157|1493741627|  pv|  6250|  91286|
|728690|1493776998|  pv| 11800|  62353|
|332634|1493809895|  pv|  1101| 365477|
|857237|1493816945|  pv|  1043| 110616|
|619381|1493774638|  pv|   385| 428950|
|467042|1493772641|  pv|  8237| 301299|
|467042|1493772644|  pv|  8237| 301299|
|991528|1493780710|  pv|  7270| 274795|
|991528|1493780712|  pv|  7270| 274795|
|991528|1493780712|  pv|  7270| 274795|
|991528|1493780712|  pv|  7270| 274795|
|991528|1493780714|  pv|  7270| 274795|
|991528|1493780765|  pv|  7270| 274795|
|991528|1493780714|  pv|  7270| 274795|
|991528|1493780765|  pv|  7270| 274795|
|991528|1493780764|  pv|  7270| 274795|
|991528|1493780633|  pv|  7270| 274795|
|991528|1493780764|  pv|  7270| 274795|
+------+----------+----+------+-------+
only showing top 20 rows

root
 |-- userId: integer (nullable = true)
 |-- timestamp: long (nullable = true)
 |-- btag: string (nullable = true)
 |-- cateId: integer (nullable = true)
 |-- brandId: integer (nullable = true)
  • 分析数据集字段的类型和格式
    • 查看是否有空值
    • 查看每列数据的类型
    • 查看每列数据的类别情况
print("查看userId的数据情况:", behavior_log_df.groupBy("userId").count().count())
# 约113w用户
#注意:behavior_log_df.groupBy("userId").count()  返回的是一个dataframe,这里的count计算的是每一个分组的个数,但当前还没有进行计算
# 当调用df.count()时才开始进行计算,这里的count计算的是dataframe的条目数,也就是共有多少个分组
查看user的数据情况: 1136340
print("查看btag的数据情况:", behavior_log_df.groupBy("btag").count().collect())    # collect会把计算结果全部加载到内存,谨慎使用
# 只有四种类型数据:pv、fav、cart、buy
# 这里由于类型只有四个,所以直接使用collect,把数据全部加载出来
查看btag的数据情况: [Row(btag='buy', count=9115919), Row(btag='fav', count=9301837), Row(btag='cart', count=15946033), Row(btag='pv', count=688904345)]
print("查看cateId的数据情况:", behavior_log_df.groupBy("cateId").count().count())
# 约12968类别id
查看cateId的数据情况: 12968
print("查看brandId的数据情况:", behavior_log_df.groupBy("brandId").count().count())
# 约460561品牌id
查看brandId的数据情况: 460561
print("判断数据是否有空值:", behavior_log_df.count(), behavior_log_df.dropna().count())
# 约7亿条目723268134 723268134
# 本数据集无空值条目,可放心处理
判断数据是否有空值: 723268134 723268134
  • pivot透视操作,把某列里的字段值转换成行并进行聚合运算(pyspark.sql.GroupedData.pivot)
    • 如果透视的字段中的不同属性值超过10000个,则需要设置spark.sql.pivotMaxValues,否则计算过程中会出现错误。文档介绍
# 统计每个用户对各类商品的pv、fav、cart、buy数量
cate_count_df = behavior_log_df.groupBy(behavior_log_df.userId, behavior_log_df.cateId).pivot("btag",["pv","fav","cart","buy"]).count()
cate_count_df.printSchema()    # 此时还没有开始计算

显示效果:

root
 |-- userId: integer (nullable = true)
 |-- cateId: integer (nullable = true)
 |-- pv: long (nullable = true)
 |-- fav: long (nullable = true)
 |-- cart: long (nullable = true)
 |-- buy: long (nullable = true)
  • 统计每个用户对各个品牌的pv、fav、cart、buy数量并保存结果
# 统计每个用户对各个品牌的pv、fav、cart、buy数量
brand_count_df = behavior_log_df.groupBy(behavior_log_df.userId, behavior_log_df.brandId).pivot("btag",["pv","fav","cart","buy"]).count()
# brand_count_df.show()    # 同上
# 113w * 46w
# 由于运算时间比较长,所以这里先将结果存储起来,供后续其他操作使用
# 写入数据时才开始计算
cate_count_df.write.csv("hdfs://localhost:9000/preprocessing_dataset/cate_count.csv", header=True)
brand_count_df.write.csv("hdfs://localhost:9000/preprocessing_dataset/brand_count.csv", header=True)

2.2 根据用户对类目偏好打分训练ALS模型

  • 根据您统计的次数 + 打分规则 ==> 偏好打分数据集 ==> ALS模型
# spark ml的模型训练是基于内存的,如果数据过大,内存空间小,迭代次数过多的化,可能会造成内存溢出,报错
# 设置Checkpoint的话,会把所有数据落盘,这样如果异常退出,下次重启后,可以接着上次的训练节点继续运行
# 但该方法其实指标不治本,因为无法防止内存溢出,所以还是会报错
# 如果数据量大,应考虑的是增加内存、或限制迭代次数和训练数据量级等
spark.sparkContext.setCheckpointDir("hdfs://localhost:9000/checkPoint/")
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, LongType, FloatType

# 构建结构对象
schema = StructType([
    StructField("userId", IntegerType()),
    StructField("cateId", IntegerType()),
    StructField("pv", IntegerType()),
    StructField("fav", IntegerType()),
    StructField("cart", IntegerType()),
    StructField("buy", IntegerType())
])

# 从hdfs加载CSV文件
cate_count_df = spark.read.csv("hdfs://localhost:9000/preprocessing_dataset/cate_count.csv", header=True, schema=schema)
cate_count_df.printSchema()
cate_count_df.first()    # 第一行数据

显示结果:

root
 |-- userId: integer (nullable = true)
 |-- cateId: integer (nullable = true)
 |-- pv: integer (nullable = true)
 |-- fav: integer (nullable = true)
 |-- cart: integer (nullable = true)
 |-- buy: integer (nullable = true)

Row(userId=1061650, cateId=4520, pv=2326, fav=None, cart=53, buy=None)
  • 处理每一行数据:r表示row对象
def process_row(r):
    # 处理每一行数据:r表示row对象

    # 偏好评分规则:
    #     m: 用户对应的行为次数
    #     该偏好权重比例,次数上限仅供参考,具体数值应根据产品业务场景权衡
    #     pv: if m<=20: score=0.2*m; else score=4
    #     fav: if m<=20: score=0.4*m; else score=8
    #     cart: if m<=20: score=0.6*m; else score=12
    #     buy: if m<=20: score=1*m; else score=20

    # 注意这里要全部设为浮点数,spark运算时对类型比较敏感,要保持数据类型都一致
    pv_count = r.pv if r.pv else 0.0
    fav_count = r.fav if r.fav else 0.0
    cart_count = r.cart if r.cart else 0.0
    buy_count = r.buy if r.buy else 0.0

    pv_score = 0.2*pv_count if pv_count<=20 else 4.0
    fav_score = 0.4*fav_count if fav_count<=20 else 8.0
    cart_score = 0.6*cart_count if cart_count<=20 else 12.0
    buy_score = 1.0*buy_count if buy_count<=20 else 20.0

    rating = pv_score + fav_score + cart_score + buy_score
    # 返回用户ID、分类ID、用户对分类的偏好打分
    return r.userId, r.cateId, rating
  • 返回一个PythonRDD类型
# 返回一个PythonRDD类型,此时还没开始计算
cate_count_df.rdd.map(process_row).toDF(["userId", "cateId", "rating"])

显示结果:

DataFrame[userId: bigint, cateId: bigint, rating: double]
  • 用户对商品类别的打分数据
# 用户对商品类别的打分数据
# map返回的结果是rdd类型,需要调用toDF方法转换为Dataframe
cate_rating_df = cate_count_df.rdd.map(process_row).toDF(["userId", "cateId", "rating"])
# 注意:toDF不是每个rdd都有的方法,仅局限于此处的rdd
# 可通过该方法获得 user-cate-matrix
# 但由于cateId字段过多,这里运算量比很大,机器内存要求很高才能执行,否则无法完成任务
# 请谨慎使用

# 但好在我们训练ALS模型时,不需要转换为user-cate-matrix,所以这里可以不用运行
# cate_rating_df.groupBy("userId").povit("cateId").min("rating")
# 用户对类别的偏好打分数据
cate_rating_df

显示结果:

DataFrame[userId: bigint, cateId: bigint, rating: double]
  • 通常如果USER-ITEM打分数据应该是通过一下方式进行处理转换为USER-ITEM-MATRIX

但这里我们将使用的Spark的ALS模型进行CF推荐,因此注意这里数据输入不需要提前转换为矩阵,直接是 USER-ITEM-RATE的数据

  • 基于Spark的ALS隐因子模型进行CF评分预测

    • ALS的意思是交替最小二乘法(Alternating Least Squares),是Spark2.*中加入的进行基于模型的协同过滤(model-based CF)的推荐系统算法。

      同SVD,它也是一种矩阵分解技术,对数据进行降维处理。

    • 详细使用方法:pyspark.ml.recommendation.ALS

    • 注意:由于数据量巨大,因此这里也不考虑基于内存的CF算法

      参考:为什么Spark中只有ALS

# 使用pyspark中的ALS矩阵分解方法实现CF评分预测
# 文档地址:https://spark.apache.org/docs/2.2.2/api/python/pyspark.ml.html?highlight=vectors#module-pyspark.ml.recommendation
from pyspark.ml.recommendation import ALS   # ml:dataframe, mllib:rdd

# 利用打分数据,训练ALS模型
als = ALS(userCol='userId', itemCol='cateId', ratingCol='rating', checkpointInterval=5)

# 此处训练时间较长
model = als.fit(cate_rating_df)
# model.recommendForAllUsers(N) 给所有用户推荐TOP-N个物品
ret = model.recommendForAllUsers(3)
# 由于是给所有用户进行推荐,此处运算时间也较长
ret.show()
# 推荐结果存放在recommendations列中,
ret.select("recommendations").show()

显示结果:

+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|   148|[[3347, 12.547271...|
|   463|[[1610, 9.250818]...|
|   471|[[1610, 10.246621...|
|   496|[[1610, 5.162216]...|
|   833|[[5607, 9.065482]...|
|  1088|[[104, 6.886987],...|
|  1238|[[5631, 14.51981]...|
|  1342|[[5720, 10.89842]...|
|  1580|[[5731, 8.466453]...|
|  1591|[[1610, 12.835257...|
|  1645|[[1610, 11.968531...|
|  1829|[[1610, 17.576496...|
|  1959|[[1610, 8.353473]...|
|  2122|[[1610, 12.652732...|
|  2142|[[1610, 12.48068]...|
|  2366|[[1610, 11.904813...|
|  2659|[[5607, 11.699315...|
|  2866|[[1610, 7.752719]...|
|  3175|[[3347, 2.3429515...|
|  3749|[[1610, 3.641833]...|
+------+--------------------+
only showing top 20 rows

+--------------------+
|     recommendations|
+--------------------+
|[[3347, 12.547271...|
|[[1610, 9.250818]...|
|[[1610, 10.246621...|
|[[1610, 5.162216]...|
|[[5607, 9.065482]...|
|[[104, 6.886987],...|
|[[5631, 14.51981]...|
|[[5720, 10.89842]...|
|[[5731, 8.466453]...|
|[[1610, 12.835257...|
|[[1610, 11.968531...|
|[[1610, 17.576496...|
|[[1610, 8.353473]...|
|[[1610, 12.652732...|
|[[1610, 12.48068]...|
|[[1610, 11.904813...|
|[[5607, 11.699315...|
|[[1610, 7.752719]...|
|[[3347, 2.3429515...|
|[[1610, 3.641833]...|
+--------------------+
only showing top 20 rows
  • model.recommendForUserSubset 给部分用户推荐TOP-N个物品
# 注意:recommendForUserSubset API,2.2.2版本中无法使用
dataset = spark.createDataFrame([[1],[2],[3]])
dataset = dataset.withColumnRenamed("_1", "userId")
ret = model.recommendForUserSubset(dataset, 3)

# 只给部分用推荐,运算时间短
ret.show()
ret.collect()    # 注意: collect会将所有数据加载到内存,慎用

显示结果:

+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|     1|[[1610, 25.4989],...|
|     3|[[5607, 13.665942...|
|     2|[[5579, 5.9051886...|
+------+--------------------+

[Row(userId=1, recommendations=[Row(cateId=1610, rating=25.498899459838867), Row(cateId=5737, rating=24.901548385620117), Row(cateId=3347, rating=20.736785888671875)]),
 Row(userId=3, recommendations=[Row(cateId=5607, rating=13.665942192077637), Row(cateId=1610, rating=11.770171165466309), Row(cateId=3347, rating=10.35690689086914)]),
 Row(userId=2, recommendations=[Row(cateId=5579, rating=5.90518856048584), Row(cateId=2447, rating=5.624575138092041), Row(cateId=5690, rating=5.2555742263793945)])]
  • transform中提供userId和cateId可以对打分进行预测,利用打分结果排序后
# transform中提供userId和cateId可以对打分进行预测,利用打分结果排序后,同样可以实现TOP-N的推荐
model.transform
# 将模型进行存储
model.save("hdfs://localhost:9000/models/userCateRatingALSModel.obj")
# 测试存储的模型
from pyspark.ml.recommendation import ALSModel
# 从hdfs加载之前存储的模型
als_model = ALSModel.load("hdfs://localhost:9000/models/userCateRatingALSModel.obj")
# model.recommendForAllUsers(N) 给用户推荐TOP-N个物品
result = als_model.recommendForAllUsers(3)
result.show()

显示结果:

+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|   148|[[3347, 12.547271...|
|   463|[[1610, 9.250818]...|
|   471|[[1610, 10.246621...|
|   496|[[1610, 5.162216]...|
|   833|[[5607, 9.065482]...|
|  1088|[[104, 6.886987],...|
|  1238|[[5631, 14.51981]...|
|  1342|[[5720, 10.89842]...|
|  1580|[[5731, 8.466453]...|
|  1591|[[1610, 12.835257...|
|  1645|[[1610, 11.968531...|
|  1829|[[1610, 17.576496...|
|  1959|[[1610, 8.353473]...|
|  2122|[[1610, 12.652732...|
|  2142|[[1610, 12.48068]...|
|  2366|[[1610, 11.904813...|
|  2659|[[5607, 11.699315...|
|  2866|[[1610, 7.752719]...|
|  3175|[[3347, 2.3429515...|
|  3749|[[1610, 3.641833]...|
+------+--------------------+
only showing top 20 rows
  • 召回到redis
import redis
host = "192.168.19.137"
port = 6379    
# 召回到redis
def recall_cate_by_cf(partition):
    # 建立redis 连接池
    pool = redis.ConnectionPool(host=host, port=port)
    # 建立redis客户端
    client = redis.Redis(connection_pool=pool)
    for row in partition:
        client.hset("recall_cate", row.userId, [i.cateId for i in row.recommendations])
# 对每个分片的数据进行处理 #mapPartitions Transformation   map
# foreachPartition Action操作             foreachRDD
result.foreachPartition(recall_cate_by_cf)

# 注意:这里这是召回的是用户最感兴趣的n个类别
# 总的条目数,查看redis中总的条目数是否一致
result.count()

显示结果:

1136340

2.3 根据用户对品牌偏好打分训练ALS模型

from pyspark.sql.types import StructType, StructField, StringType, IntegerType

schema = StructType([
    StructField("userId", IntegerType()),
    StructField("brandId", IntegerType()),
    StructField("pv", IntegerType()),
    StructField("fav", IntegerType()),
    StructField("cart", IntegerType()),
    StructField("buy", IntegerType())
])
# 从hdfs加载预处理好的品牌的统计数据
brand_count_df = spark.read.csv("hdfs://localhost:9000/preprocessing_dataset/brand_count.csv", header=True, schema=schema)
# brand_count_df.show()
def process_row(r):
    # 处理每一行数据:r表示row对象

    # 偏好评分规则:
    #     m: 用户对应的行为次数
    #     该偏好权重比例,次数上限仅供参考,具体数值应根据产品业务场景权衡
    #     pv: if m<=20: score=0.2*m; else score=4
    #     fav: if m<=20: score=0.4*m; else score=8
    #     cart: if m<=20: score=0.6*m; else score=12
    #     buy: if m<=20: score=1*m; else score=20

    # 注意这里要全部设为浮点数,spark运算时对类型比较敏感,要保持数据类型都一致
    pv_count = r.pv if r.pv else 0.0
    fav_count = r.fav if r.fav else 0.0
    cart_count = r.cart if r.cart else 0.0
    buy_count = r.buy if r.buy else 0.0

    pv_score = 0.2*pv_count if pv_count<=20 else 4.0
    fav_score = 0.4*fav_count if fav_count<=20 else 8.0
    cart_score = 0.6*cart_count if cart_count<=20 else 12.0
    buy_score = 1.0*buy_count if buy_count<=20 else 20.0

    rating = pv_score + fav_score + cart_score + buy_score
    # 返回用户ID、品牌ID、用户对品牌的偏好打分
    return r.userId, r.brandId, rating
# 用户对品牌的打分数据
brand_rating_df = brand_count_df.rdd.map(process_row).toDF(["userId", "brandId", "rating"])
# brand_rating_df.show()
  • 基于Spark的ALS隐因子模型进行CF评分预测

    • ALS的意思是交替最小二乘法(Alternating Least Squares),是Spark中进行基于模型的协同过滤(model-based CF)的推荐系统算法,也是目前Spark内唯一一个推荐算法。

      同SVD,它也是一种矩阵分解技术,但理论上,ALS在海量数据的处理上要优于SVD。

      更多了解:pyspark.ml.recommendation.ALS

      注意:由于数据量巨大,因此这里不考虑基于内存的CF算法

      参考:为什么Spark中只有ALS

  • 使用pyspark中的ALS矩阵分解方法实现CF评分预测

# 使用pyspark中的ALS矩阵分解方法实现CF评分预测
# 文档地址:https://spark.apache.org/docs/latest/api/python/pyspark.ml.html?highlight=vectors#module-pyspark.ml.recommendation
from pyspark.ml.recommendation import ALS

als = ALS(userCol='userId', itemCol='brandId', ratingCol='rating', checkpointInterval=2)
# 利用打分数据,训练ALS模型
# 此处训练时间较长
model = als.fit(brand_rating_df)
# model.recommendForAllUsers(N) 给用户推荐TOP-N个物品
model.recommendForAllUsers(3).show()
# 将模型进行存储
model.save("hdfs://localhost:9000/models/userBrandRatingModel.obj")
# 测试存储的模型
from pyspark.ml.recommendation import ALSModel
# 从hdfs加载模型
my_model = ALSModel.load("hdfs://localhost:9000/models/userBrandRatingModel.obj")
my_model
# model.recommendForAllUsers(N) 给用户推荐TOP-N个物品
my_model.recommendForAllUsers(3).first()

用户行为准备数据:预处理behavior_log数据集

#第一个count()的作用:计算出groupBy每个分组下有多少数据,也即是每个用户ID的分组下有多少数据
#第二个count()的作用:因为现在已经知道有多少个分组(用户ID)了,即一个分组(用户ID)就是一行,那么第二个count()就是计算有多少行即有多少个分组(用户ID)
print("查看userId的数据情况:", behavior_log_df.groupBy("userId").count().count())
# 约113w用户
#注意:behavior_log_df.groupBy("userId").count()  返回的是一个dataframe,这里的count计算的是每一个分组的个数,但当前还没有进行计算
# 当调用df.count()时才开始进行计算,这里的count计算的是dataframe的条目数,也就是共有多少个分组

#count()的作用:计算出groupBy每个分组下有多少数据
#collect()的作用:数据封装为Row对象进行返回
print("查看btag的数据情况:", behavior_log_df.groupBy("btag").count().collect())    # collect会把计算结果全部加载到内存,谨慎使用
# 只有四种类型数据:pv、fav、cart、buy
# 这里由于类型只有四个,所以直接使用collect,把数据全部加载出来
# 查看btag的数据情况: [Row(btag='buy', count=9115919), Row(btag='fav', count=9301837), Row(btag='cart', count=15946033), Row(btag='pv', count=688904345)]

#第一个count()的作用:计算出groupBy每个分组下有多少数据
#第二个count()的作用:因为现在已经知道有多少个分组了,即一个分组就是一行,那么第二个count()就是计算有多少行即有多少个分组
print("查看cateId的数据情况:", behavior_log_df.groupBy("cateId").count().count())
# 约12968类别id
# 查看cateId的数据情况: 12968

print("查看brandId的数据情况:", behavior_log_df.groupBy("brandId").count().count())
# 约460561品牌id
# 查看brandId的数据情况: 460561

# 一比较两者的count()结果是否不相同,就知道数据中是否存在空值了
print("判断数据是否有空值:", behavior_log_df.count(), behavior_log_df.dropna().count())
# 约7亿条目723268134 723268134
# 本数据集无空值条目,可放心处理
# 判断数据是否有空值: 723268134 723268134
#pivot透视操作,把某列里的字段值转换成行并进行聚合运算(pyspark.sql.GroupedData.pivot)
#如果透视的字段中的不同属性值超过10000个,则需要设置spark.sql.pivotMaxValues,否则计算过程中会出现错误。

# 1.统计每个用户对各类商品的pv、fav、cart、buy数量,也即是每个用户对每个商品品类的喜好程度。
# 2.标签btag:如下["pv"浏览,"fav"收藏,"cart"购物车,"buy"购买]
# 3.groupBy以用户userId和商品类别cateId进行一起分组,就是统计每个用户下对应的各类商品类别的统计数据。
# 4.pivot("btag",["pv","fav","cart","buy"]):变成透视表,也就是构成一个矩阵,行名为分组的用户userId,列名为分组的商品类别cateId,
#   表中值也即矩阵中值为标签btag(["pv","fav","cart","buy"]),
#   得出每个用户userId对每个商品品类cateId下的每个标签btag(["pv","fav","cart","buy"])的行为统计数据(喜好程度)
cate_count_df = behavior_log_df.groupBy(behavior_log_df.userId, behavior_log_df.cateId).pivot("btag",["pv","fav","cart","buy"]).count()
cate_count_df.printSchema()    # 此时还没有开始计算

显示效果:
root
 |-- userId: integer (nullable = true)
 |-- cateId: integer (nullable = true)
 |-- pv: long (nullable = true)
 |-- fav: long (nullable = true)
 |-- cart: long (nullable = true)
 |-- buy: long (nullable = true)
统计每个用户对各个品牌的pv、fav、cart、buy数量并保存结果


# 统计每个用户对各个品牌的pv、fav、cart、buy数量,也即是每个用户对每个品牌的喜好程度。
brand_count_df = behavior_log_df.groupBy(behavior_log_df.userId, behavior_log_df.brandId).pivot("btag",["pv","fav","cart","buy"]).count()
# brand_count_df.show()    # 同上
# 113w * 46w

# 由于运算时间比较长,所以这里先将结果存储起来,供后续其他操作使用
# 写入数据时才开始计算
cate_count_df.write.csv("hdfs://localhost:9000/preprocessing_dataset/cate_count.csv", header=True)
brand_count_df.write.csv("hdfs://localhost:9000/preprocessing_dataset/brand_count.csv", header=True)


根据用户对类目偏好打分训练ALS模型

# 根据您统计的次数 + 打分规则 ==> 偏好打分数据集 ==> ALS模型
# setCheckpointDir设置检查点的作用:如果spark ml在训练模型的时候程序崩了的话,那么下次启动重新计算时,可以重新读取之前落盘的数据继续上一次的地方开始计算。
# spark ml的模型训练是基于内存的,如果数据过大,内存空间小,迭代次数过多的化,可能会造成内存溢出,报错
# 设置Checkpoint的话,会把所有数据落盘,这样如果异常退出,下次重启后,可以接着上次的训练节点继续运行
# 但该方法其实指标不治本,因为无法防止内存溢出,所以还是会报错
# 如果数据量大,应考虑的是增加内存、或限制迭代次数和训练数据量级等
spark.sparkContext.setCheckpointDir("hdfs://localhost:9000/checkPoint/")
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, LongType, FloatType

# 构建结构对象
schema = StructType([
    StructField("userId", IntegerType()),
    StructField("cateId", IntegerType()),
    StructField("pv", IntegerType()),
    StructField("fav", IntegerType()),
    StructField("cart", IntegerType()),
    StructField("buy", IntegerType())
])

# 从hdfs加载CSV文件
cate_count_df = spark.read.csv("hdfs://localhost:9000/preprocessing_dataset/cate_count.csv", header=True, schema=schema)
cate_count_df.printSchema()
显示结果:
root
 |-- userId: integer (nullable = true)
 |-- cateId: integer (nullable = true)
 |-- pv: integer (nullable = true)
 |-- fav: integer (nullable = true)
 |-- cart: integer (nullable = true)
 |-- buy: integer (nullable = true)

cate_count_df.first()    # 第一行数据:Row(userId=1061650, cateId=4520, pv=2326, fav=None, cart=53, buy=None)

前面已经统计出来了每个用户userId对每个商品品类cateId下的每个标签btag(["pv"浏览,"fav"收藏,"cart"购物车,"buy"购买])的行为统计数据(喜好程度),
因此当前需要构建出每个标签btag(["pv"浏览,"fav"收藏,"cart"购物车,"buy"购买])中每个行为数据的打分规则。
规则比如根据下面的第一行pv为13次数进行对应的打分,那么"buy"购买行为理应要比"pv"浏览行为的偏好程度要高,即比如"pv"浏览行为值为1则打1分,
"buy"购买行为值为1则打10分,同时"pv"浏览行为应该有打分上限,即比如"pv"浏览行为值达到上限值则不再加分。
从而最终构建出所有用户对所有商品品类的每个标签行为(["pv"浏览,"fav"收藏,"cart"购物车,"buy"购买])的偏好程度打分数据集。

#处理每一行数据:r表示row对象(比如 Row(userId=1061650, cateId=4520, pv=2326, fav=None, cart=53, buy=None))
def process_row(r):
    # 处理每一行数据:r表示row对象

    # 偏好评分规则:
    #     m: 用户对应的行为次数
    #     行为次数的最大值为20
    #     该偏好权重比例,次数上限仅供参考,具体数值应根据产品业务场景权衡
    #     pv: if m<=20: score=0.2*m; else score=4(行为次数的最大值为20,最高得4分)
    #     fav: if m<=20: score=0.4*m; else score=8
    #     cart: if m<=20: score=0.6*m; else score=12
    #     buy: if m<=20: score=1*m; else score=20

    # 注意这里要全部设为浮点数,spark运算时对类型比较敏感,要保持数据类型都一致
    # r即为 Row对象(userId=1061650, cateId=4520, pv=2326, fav=None, cart=53, buy=None),那么r.pv即为2326
    pv_count = r.pv if r.pv else 0.0
    fav_count = r.fav if r.fav else 0.0
    cart_count = r.cart if r.cart else 0.0
    buy_count = r.buy if r.buy else 0.0

    pv_score = 0.2*pv_count if pv_count<=20 else 4.0
    fav_score = 0.4*fav_count if fav_count<=20 else 8.0
    cart_score = 0.6*cart_count if cart_count<=20 else 12.0
    buy_score = 1.0*buy_count if buy_count<=20 else 20.0

    rating = pv_score + fav_score + cart_score + buy_score
    # 返回用户ID、分类ID、用户对分类的偏好打分
    return r.userId, r.cateId, rating
# 返回一个PythonRDD类型,此时还没开始计算
# cate_count_df中的每一行数据均为Row对象,比如第一行:Row(userId=1061650, cateId=4520, pv=2326, fav=None, cart=53, buy=None)
# map(自定义函数process_row):对每一行Row对象中每个用户行为数据进行打分
# 自定义函数process_row 返回的用户ID、分类ID、用户对分类的偏好打分 构建为 toDF(["userId", "cateId", "rating"])
cate_count_df.rdd.map(process_row).toDF(["userId", "cateId", "rating"])
#显示结果: DataFrame[userId: bigint, cateId: bigint, rating: double]

用户对商品类别的打分数据
# 用户对商品类别的打分数据
# map返回的结果是rdd类型,需要调用toDF方法转换为Dataframe
cate_rating_df = cate_count_df.rdd.map(process_row).toDF(["userId", "cateId", "rating"])
# 注意:toDF不是每个rdd都有的方法,仅局限于此处的rdd
# 可通过该方法获得 user-cate-matrix
# 但由于cateId字段过多,这里运算量比很大,机器内存要求很高才能执行,否则无法完成任务
# 请谨慎使用

# 但好在我们训练ALS模型时,不需要转换为user-cate-matrix,所以这里可以不用运行
# cate_rating_df.groupBy("userId").povit("cateId").min("rating")
# 用户对类别的偏好打分数据
cate_rating_df
#显示结果: DataFrame[userId: bigint, cateId: bigint, rating: double]

# 使用pyspark中的ALS矩阵分解方法实现CF评分预测
# 文档地址:https://spark.apache.org/docs/2.2.2/api/python/pyspark.ml.html?highlight=vectors#module-pyspark.ml.recommendation
from pyspark.ml.recommendation import ALS   # ml:dataframe, mllib:rdd

# 利用打分数据,训练ALS模型。告诉ALS模型用户数据为'userId'列,物品数据为'cateId'列,评分数据为'rating'列。
# checkpointInterval=5:模型训练的时候每迭代5次缓存一次到本地检查点,设置的数值越大模型训练的耗时越短。
# 如果'userId'和''cateId''的原列值的位数都不长的话,也可以直接传入ALS模型训练,
# 但是如果userCol和itemCol的输入字段列值的位数非常长的话,那么便需要把原列值先进行StringIndexer方式转换为索引值再输入进行训练。
als = ALS(userCol='userId', itemCol='cateId', ratingCol='rating', checkpointInterval=5)

# 此处训练时间较长
model = als.fit(cate_rating_df)

# 模型训练好后,调用方法进行使用,具体API查看
# model.recommendForAllUsers(N) 给所有用户推荐TOP-N个物品
ret = model.recommendForAllUsers(3)
# 由于是给所有用户进行推荐,此处运算时间也较长
ret.show()
# 推荐结果存放在recommendations列中
ret.select("recommendations").show()

显示结果:
recommendations为二维列表,其中里面每个一维列表里面有两个值,第一个值是'cateId'列值,第二个值是'rating'预测评分值
+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|   148|[[3347, 12.547271...|
|   463|[[1610, 9.250818]...|
。。。。。。。。
|  3175|[[3347, 2.3429515...|
|  3749|[[1610, 3.641833]...|
+------+--------------------+
only showing top 20 rows

recommendations为二维列表,其中里面每个一维列表里面有两个值,第一个值是'cateId'列值,第二个值是'rating'预测评分值
+--------------------+
|     recommendations|
+--------------------+
|[[3347, 12.547271...|
|[[1610, 9.250818]...|
。。。。。。。。
|[[1610, 3.641833]...|
+--------------------+
only showing top 20 rows

# model.recommendForUserSubset 给部分用户推荐TOP-N个物品
# 注意:recommendForUserSubset API,2.2.2版本中无法使用
dataset = spark.createDataFrame([[1],[2],[3]]) 
dataset = dataset.withColumnRenamed("_1", "userId")
#表示给3个用户(用户ID列表[[1],[2],[3]])推荐3个结果
ret = model.recommendForUserSubset(dataset, 3)

# 只给部分用推荐,运算时间短
ret.show()
ret.collect()    # 注意: collect会将所有数据加载到内存,慎用
显示结果:
recommendations为二维列表,其中里面每个一维列表里面有两个值,第一个值是'cateId'列值,第二个值是'rating'预测评分值
+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|     1|[[1610, 25.4989],...|
|     3|[[5607, 13.665942...|
|     2|[[5579, 5.9051886...|
+------+--------------------+

recommendations中每个Row对象就是一个预测结果,其中里面每个预测结果里面有两个值,第一个值是'cateId'列值,第二个值是'rating'预测评分值
[Row(userId=1, recommendations=[Row(cateId=1610, rating=25.498899459838867), Row(cateId=5737, rating=24.901548385620117), Row(cateId=3347, rating=20.736785888671875)]),
 Row(userId=3, recommendations=[Row(cateId=5607, rating=13.665942192077637), Row(cateId=1610, rating=11.770171165466309), Row(cateId=3347, rating=10.35690689086914)]),
 Row(userId=2, recommendations=[Row(cateId=5579, rating=5.90518856048584), Row(cateId=2447, rating=5.624575138092041), Row(cateId=5690, rating=5.2555742263793945)])]

# transform中提供userId和cateId可以对打分进行预测,利用打分结果排序后
# transform中提供userId和cateId可以对打分进行预测,利用打分结果排序后,同样可以实现TOP-N的推荐
model.transform
# 将模型进行存储
model.save("hdfs://localhost:9000/models/userCateRatingALSModel.obj")
# 测试存储的模型
from pyspark.ml.recommendation import ALSModel
# 从hdfs加载之前存储的模型
als_model = ALSModel.load("hdfs://localhost:9000/models/userCateRatingALSModel.obj")
# model.recommendForAllUsers(N) 给用户推荐TOP-N个物品
result = als_model.recommendForAllUsers(3)
result.show()
显示结果:
+------+--------------------+
|userId|     recommendations|
+------+--------------------+
|   148|[[3347, 12.547271...|
|   463|[[1610, 9.250818]...|
。。。。。。。。。。。
|  3175|[[3347, 2.3429515...|
|  3749|[[1610, 3.641833]...|
+------+--------------------+
only showing top 20 rows
# 召回到redis
import redis
host = "192.168.19.137"
port = 6379    
# 召回到redis:遍历推荐结果数据result中的每个partition分区数据到自定义函数中处理,一个分区中就是一大块数据,其中是一行行的row对象数据
def recall_cate_by_cf(partition):
    # 建立redis 连接池
    pool = redis.ConnectionPool(host=host, port=port)
    # 建立redis客户端
    client = redis.Redis(connection_pool=pool)
    for row in partition:
        #一个用户(row.userId)喜好的若干个商品类别(i.cateId),即把推荐给用户商品类别都存储到redis
        client.hset("recall_cate", row.userId, [i.cateId for i in row.recommendations])

#把推荐结果数据result 通过调用foreachPartition(自定义函数recall_cate_by_cf) 执行 recall_cate_by_cf(partition) 遍历每个partition分区数据到自定义函数中处理
# 对每个分片的数据进行处理 mapPartitions Transformation map
# foreachPartition Action操作 foreachRDD
result.foreachPartition(recall_cate_by_cf)

# 注意:这里这是召回的是用户最感兴趣的n个类别
# 总的条目数,查看redis中总的条目数是否一致
result.count()
显示结果: 1136340

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个具体的例子,我会尽可能详细地解释,希望对你有帮助。 首先,我们需要导入必要的库: ```python from pyspark.ml.recommendation import ALS from pyspark.ml.evaluation import RegressionEvaluator ``` 然后,我们需要读入菜品数据和评分数据: ```python menu_df = spark.read.csv("menu.csv", header=True, inferSchema=True) rating_df = spark.read.csv("rating.csv", header=True, inferSchema=True) ``` 接下来,我们需要将菜品数据和评分数据进行合并: ```python menu_rating_df = menu_df.join(rating_df, ["menu_id"]) ``` 然后,我们需要将用户 ID 和菜品 ID 转换为整数类型: ```python menu_rating_df = menu_rating_df.select(menu_rating_df["user_id"].cast("integer"), menu_rating_df["menu_id"].cast("integer"), menu_rating_df["rating"]) ``` 接着,我们需要使用 ALS 模型进行训练,设置参数并训练模型: ```python als = ALS(maxIter=5, regParam=0.01, userCol="user_id", itemCol="menu_id", ratingCol="rating") model = als.fit(menu_rating_df) ``` 然后,我们需要选择一个用户进行推荐,这里我们选择用户 ID 为 1: ```python user_id = 1 user_df = menu_df.select("menu_id").distinct().withColumn("user_id", lit(user_id)) recommendations = model.transform(user_df).orderBy("prediction", ascending=False).limit(10) ``` 最后,我们需要将推荐结果转换为 Pandas DataFrame,以便展示: ```python recommendations_pandas_df = recommendations.toPandas() ``` 我们可以使用 Pandas 库来输出推荐结果: ```python for i in range(10): print("Recommendation {} : {}".format(i+1, recommendations_pandas_df["menu_id"][i])) ``` 这样,我们就完成了对菜品和评分数据预处理后,使用 ALS 模型评价后对某用户推荐10道新菜品的代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值