实战|W餐饮平台智能化菜品推荐方案(含Spark实操+算法选型+完整流程)

实战|W餐饮平台智能化菜品推荐方案(含Spark实操+算法选型+完整流程)

在餐饮外卖赛道竞争日趋激烈的当下,"用户留存"成为平台突围的核心关键。W餐饮外卖平台近期面临老用户下单率下滑的困境------调研显示,老用户对热门菜品失去新鲜感,又难以发现符合自身口味的新菜品,最终导致活跃度下降。

基于此,我们针对W平台的核心痛点,设计了一套基于Spark的智能化菜品推荐方案,通过用户历史评分数据挖掘口味偏好,结合协同过滤算法实现个性化推荐,兼顾"偏爱菜品召回"与"新菜品探索",最终助力平台提升老用户复购率。本文将从项目背景、数据探索、算法选型、流程设计到Spark实操,完整拆解方案落地全流程,附代码案例可直接复用。

一、项目背景与核心痛点拆解

1. 业务现状

  • 用户群体:以都市上班族为主,习惯外卖订餐,对菜品选择效率、口味匹配度要求高;
  • 平台基础:W平台具备完善的用户评分体系(1-5分),用户下单后可对菜品评价,后台以JSON格式存储评分数据;
  • 核心问题:老用户下单率下滑,核心原因是"推荐同质化"------现有热门菜品排行榜模式,无法满足个性化口味需求,且长尾菜品(非热门但有针对性需求)难以触达目标用户。

2. 项目目标

设计并实现菜品智能推荐系统,作为原有平台的扩展模块,核心目标:

  1. 个性化推荐:基于用户历史评分,精准推送偏爱菜品及适配口味的新菜品;
  2. 技术落地:基于Spark生态实现数据处理、模型训练与推荐结果生成,适配平台海量用户数据;
  3. 业务价值:提升老用户下单率,激活长尾菜品流量,增强用户粘性。

二、用户数据分析:推荐系统的核心基石

推荐系统的效果,本质依赖数据的质量与针对性。我们首先对W平台的用户评分数据进行探索分析,明确数据特征与可用价值。

1. 数据集说明

核心数据源为用户评分数据集(JSON格式),已做脱敏处理,每条记录包含5个关键属性,同时关联MySQL中的菜品基础数据集(本文重点聚焦评分数据处理)。

属性名称 属性说明 数据示例
UserID 用户唯一标识(脱敏) A2WOH395IHGS0T
MealID 菜品唯一标识 B0040HNZTW
Rating 用户评分(1-5分,越高越偏好) 5.0
Review 用户评价内容(辅助分析口味偏好) 风味独特,真的不错!
ReviewTime 评分时间戳(秒级) 1483202656

2. 数据筛选原则

用户口味虽可能变化,但短期内(2个月内)相对固定。因此,我们筛选最近两个月的用户评分数据作为建模数据源,避免历史数据(长期前)对当前口味偏好的干扰,提升推荐准确性。

3. Spark实操:数据加载与探索分析(附完整代码)

由于评分数据为JSON格式,结构固定,我们采用Spark SQL加载数据并生成DataFrame,便于后续统计分析与预处理。以下为本地IDE(IntelliJ IDEA)+ Spark 3.3.0实操代码,可直接复制运行。

步骤1:环境准备(pom.xml依赖)
xml <groupId>org.apache.spark</groupId> <artifactId>spark-core_2</artifactId><version>3</version</dependency> <!-- Spark SQL依赖(用于加载JSON数据、数据查询<dependency><groupId>org.apache</groupId> <artifactId>spark-sql_2.12</artifactId> <version>3.3.0</version> <!-- JSON解析依赖(可选,增强JSON<dependency<groupId></groupId><artifactId>fast</artifactId> <version>1.2</version></dependency>
步骤2:加载JSON格式评分数据
scala 复制代码
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._

object DishRecommendationDataExploration {
  def main(args: Array[String]): Unit = {
    // 1. 初始化SparkSession(本地模式,用于测试;生产环境改为集群模式)
    val spark = SparkSession.builder()
      .appName("DishRecommendationDataExploration")
      .master("local[*]") // 本地多线程运行
      .getOrCreate()
    
    // 2. 加载JSON格式的用户评分数据(本地文件路径,生产环境改为HDFS路径)
    val ratingDF = spark.read
      .option("multiline", "true") // 支持多行JSON(单条JSON占一行可省略)
      .json("src/main/resources/user_rating.json")
    
    // 3. 查看数据结构(Schema)
    println("=== 数据Schema ===")
    ratingDF.printSchema()
    
    // 4. 查看前20条数据
    println("\n=== 前20条评分数据 ===")
    ratingDF.select("UserID", "MealID", "Rating", "Review", "ReviewTime")
      .show(20, truncate = false) // truncate=false:不截断长文本(如Review)
    
    // 5. 数据类型转换(ReviewTime为时间戳,转为可阅读时间格式)
    val ratingDFWithTime = ratingDF.withColumn(
      "ReviewDateTime",
      from_unixtime(col("ReviewTime"), "yyyy-MM-dd HH:mm:ss")
    )
    
    println("\n=== 转换时间格式后的数据 ===")
    ratingDFWithTime.select("UserID", "MealID", "Rating", "ReviewDateTime")
      .show(10, truncate = false)
步骤3:数据探索与统计分析

通过Spark SQL统计数据分布,挖掘数据特征(如评分分布、用户活跃度、菜品热度等),为后续算法选型和预处理提供依据,继续补充代码:

scala 复制代码
    // 6. 数据统计分析
    // 6.1 评分分布统计(查看用户评分偏好,是否集中在高分)
    println("\n=== 评分分布统计 ===")
    ratingDF.groupBy("Rating")
      .count()
      .orderBy("Rating")
      .show()
    
    // 6.2 用户活跃度统计(每个用户的评分次数,筛选活跃用户)
    println("\n=== 用户活跃度统计(前10名) ===")
    val userActiveDF = ratingDF.groupBy("UserID")
      .count()
      .withColumnRenamed("count", "RatingCount")
      .orderBy(desc("RatingCount"))
    
    userActiveDF.show(10)
    
    // 6.3 菜品热度统计(每个菜品的评分次数,筛选热门/长尾菜品)
    println("\n=== 菜品热度统计(前10名热门菜品) ===")
    val mealHotDF = ratingDF.groupBy("MealID")
      .count()
      .withColumnRenamed("count", "RatingCount")
      .orderBy(desc("RatingCount"))
    
    mealHotDF.show(10)
    
    // 6.4 数据缺失值统计(检查是否有缺失值,需预处理)
    println("\n=== 数据缺失值统计 ===")
    val missingValues = ratingDF.columns.map { colName =>
      (colName, ratingDF.filter(col(colName).isNull).count())
    }.toList
    
    missingValues.foreach { case (colName, count) =>
      println(s"$colName: $count 条缺失值")
    }
    
    // 7. 筛选最近两个月的数据(时间戳转换:假设当前时间戳为1499200000,两个月≈5256000秒)
    val twoMonthsSeconds = 60 * 60 * 24 * 60 // 60天(简化计算,实际按自然月调整)
    val currentTimestamp = 1499200000L // 模拟当前时间戳
    val recentRatingDF = ratingDF.filter(col("ReviewTime") >= (currentTimestamp - twoMonthsSeconds))
    
    println(s"\n=== 最近两个月的评分数据量:${recentRatingDF.count()} 条 ===")
    
    // 8. 关闭SparkSession
    spark.stop()
  }
}
步骤4:运行结果与分析(关键截图解读)

以下为核心运行结果示例(贴合项目数据特征),用于指导后续预处理和算法设计:

  1. 评分分布:5分占比45%,4分占比30%,1-2分占比仅8%,说明用户评分整体偏高,需避免"高分同质化"干扰;
  2. 用户活跃度:约20%的用户贡献了80%的评分(符合帕累托法则),需重点关注活跃老用户的偏好;
  3. 菜品热度:前10名热门菜品占总评分次数的35%,剩余80%以上菜品为长尾菜品,需通过推荐激活;
  4. 缺失值:无缺失值,无需额外填充;最近两个月数据量约8000条(模拟),满足建模需求。

三、推荐算法选型:5大算法对比+最优方案确定

推荐算法是系统核心,直接决定推荐效果。我们对比了当前主流的5种推荐算法,结合项目"个性化+新兴趣发现"的核心需求,最终选定协同过滤推荐算法。

1. 5大推荐算法优缺点对比(完整版表格)

推荐方法 核心优点 核心缺点 适配性(W平台)
基于内容的推荐方法 1. 无冷启动、数据稀疏问题;2. 可推荐长尾/新菜品;3. 能解释推荐原因(如"推荐XX,因你喜欢XX口味");4. 无需其他用户数据,仅依赖用户历史行为。 1. 需提取菜品内容特征(如口味、食材),特征工程复杂;2. 推荐同质化严重,难以发现新兴趣;3. 无法捕捉用户隐性偏好(如用户评价未提及的口味)。 ★★★☆☆(可作为辅助,但无法满足"新兴趣发现"需求)
协同过滤推荐方法 1. 无需菜品内容特征,可处理非结构化数据;2. 能发现用户潜在兴趣(如"和你口味相似的人喜欢XX");3. 推荐多样性好,可兼顾偏爱菜品与新菜品;4. 学习速度快,依赖用户评分数据即可建模。 1. 存在数据稀疏问题(部分用户/菜品评分少);2. 有冷启动问题(新用户/新菜品无评分无法推荐);3. 可扩展性需优化(海量用户数据下计算压力大)。 ★★★★★(贴合核心需求,稀疏问题可通过数据预处理解决)
基于规则的推荐方法(关联规则) 1. 逻辑直观,易理解(如"点牛奶的用户常点面包");2. 可离线挖掘规则,减轻在线计算压力;3. 适合搭配推荐(如"你点的XX,常搭配XX")。 1. 规则挖掘耗时,且需频繁更新;2. 无法实现个性化,仅能做群体搭配推荐;3. 难以处理长尾菜品,推荐多样性差。 ★★☆☆☆(可作为搭配推荐辅助,无法单独作为核心算法)
基于效用的推荐方法 1. 可融入非菜品属性(如配送速度、商家可靠性);2. 能精准计算用户效用值,推荐优先级清晰。 1. 效用函数设计复杂,需结合大量业务规则;2. 依赖用户显式反馈,隐性偏好捕捉不足;3. 建模成本高,不适合快速落地。 ★★☆☆☆(业务复杂度高,超出当前项目范围)
基于知识的推荐方法 1. 无需用户历史数据,可解决冷启动问题;2. 推理逻辑清晰,能精准匹配用户明确需求。 1. 需构建完善的知识图谱(如菜品-口味-用户需求映射),成本高;2. 无法捕捉用户隐性偏好,推荐灵活性差;3. 不适合大规模用户场景。 ★☆☆☆☆(不适合当前项目,知识图谱构建成本超出预算)

2. 最优算法确定:协同过滤推荐算法

结合W平台的核心需求(个性化推荐+新兴趣发现)和数据特点(有充足的用户评分数据,无菜品内容特征),最终选定协同过滤推荐算法作为核心算法,理由如下:

  • 贴合业务痛点:能发现用户潜在口味偏好,解决"热门菜品同质化"问题,同时推送适配的新菜品;
  • 技术落地性强:仅依赖用户评分数据,无需复杂的特征工程,可基于Spark MLlib快速实现;
  • 可优化性强:数据稀疏问题可通过"用户/菜品过滤""评分填充"解决;冷启动问题可后续结合热门菜品推荐补充。

注:本项目采用"基于用户的协同过滤(User-Based CF)",核心逻辑:找到与目标用户口味相似的用户群体,将该群体高分评价且目标用户未尝试的菜品,推荐给目标用户。

四、推荐系统完整流程设计(含Spark落地)

推荐系统作为W餐饮平台的扩展模块,需与原有平台无缝衔接,整体采用"离线处理+定时推送"的模式,避免在线计算压力,保证推荐响应速度。流程架构如下(附模块详解+技术实现):

1. 整体流程架构图(简化版)

原有餐饮平台 → 用户下单/评分 → 生成JSON评分数据 → Flume传输至HDFS → Spark处理(预处理+模型训练) → 生成推荐结果 → Flume传输至平台MySQL → 用户登录时推送推荐菜品

2. 各模块详细说明(含技术实现)

模块1:用户评分数据生成与传输
  • 生成:用户登录W平台,完成下单并评分后,后台自动生成JSON格式的评分记录,存储在平台应用服务器本地;
  • 传输:采用Flume采集评分数据,配置Flume Agent,将JSON文件实时/定时传输至HDFS(分布式文件系统),用于后续Spark离线处理;
  • 调度:使用Oozie配置工作流,定时触发Flume采集任务(如每小时采集一次),避免数据堆积。
模块2:数据预处理(Spark实操)

基于Spark SQL对HDFS上的评分数据进行预处理,解决数据稀疏、异常值等问题,生成可供建模的干净数据集。核心步骤(延续前文Spark代码,补充预处理逻辑):

scala 复制代码
// 补充数据预处理代码(承接前文数据探索代码)
// 1. 异常值处理(过滤评分不在1-5分的异常数据)
val validRatingDF = recentRatingDF.filter(col("Rating").between(1.0, 5.0))

// 2. 解决数据稀疏问题(筛选评分次数≥3的用户、评分次数≥2的菜品)
val activeUserIDs = userActiveDF.filter(col("RatingCount") >= 3).select("UserID").collect().map(_.getString(0))
val hotMealIDs = mealHotDF.filter(col("RatingCount") >= 2).select("MealID").collect().map(_.getString(0))

val denseRatingDF = validRatingDF
  .filter(col("UserID").isin(activeUserIDs: _*))
  .filter(col("MealID").isin(hotMealIDs: _*))

// 3. 数据格式转换(将UserID、MealID转换为数值型,适配Spark MLlib模型)
import org.apache.spark.ml.feature.StringIndexer

// 用户ID编码
val userIndexer = new StringIndexer()
  .setInputCol("UserID")
  .setOutputCol("UserIndex")
  .fit(denseRatingDF)

// 菜品ID编码
val mealIndexer = new StringIndexer()
  .setInputCol("MealID")
  .setOutputCol("MealIndex")
  .fit(denseRatingDF)

// 应用编码,生成建模数据集
val modelDF = userIndexer.transform(denseRatingDF)
val finalModelDF = mealIndexer.transform(modelDF)
  .select("UserIndex", "MealIndex", "Rating")

println(s"=== 预处理后建模数据集量:${finalModelDF.count()} 条 ===")
finalModelDF.show(10)
模块3:协同过滤模型训练(Spark MLlib)

使用Spark MLlib中的ALS(交替最小二乘法)算法,实现基于用户的协同过滤模型训练,调整参数获取最优模型。核心代码:

scala 复制代码
// 协同过滤模型训练代码
import org.apache.spark.ml.recommendation.ALS
import org.apache.spark.ml.evaluation.RegressionEvaluator

// 1. 划分训练集(80%)和测试集(20%)
val Array(trainingDF, testDF) = finalModelDF.randomSplit(Array(0.8, 0.2), seed = 42)

// 2. 初始化ALS算法(核心参数调整)
val als = new ALS()
  .setUserCol("UserIndex") // 用户编码列
  .setItemCol("MealIndex") // 菜品编码列
  .setRatingCol("Rating") // 评分列
  .setRank(10) // 特征维度(可调整,默认10)
  .setMaxIter(10) // 迭代次数(可调整,默认10)
  .setRegParam(0.01) // 正则化参数(防止过拟合,可调整)
  .setColdStartStrategy("drop") // 冷启动策略(丢弃无评分的用户/菜品)

// 3. 训练模型
val alsModel = als.fit(trainingDF)

// 4. 模型评估(使用RMSE指标,越小越好)
val predictionsDF = alsModel.transform(testDF)
val evaluator = new RegressionEvaluator()
  .setMetricName("rmse")
  .setLabelCol("Rating")
  .setPredictionCol("prediction")

val rmse = evaluator.evaluate(predictionsDF)
println(s"=== 模型RMSE值:$rmse ===") // 理想值≤0.8,本文示例RMSE≈0.72

// 5. 生成推荐结果(为每个用户推荐前10个菜品)
val userRecsDF = alsModel.recommendForAllUsers(10) // 每个用户推荐10个菜品
println("\n=== 每个用户的推荐菜品(编码格式) ===")
userRecsDF.show(5, truncate = false)
模块4:推荐结果处理与推送
  • 结果转换:将模型输出的"菜品编码(MealIndex)"转换为原始"菜品ID(MealID)",结合MySQL中的菜品信息,补充菜品名称、图片等详情;
  • 结果存储:将推荐结果(UserID + 推荐菜品列表)存储至HDFS,再通过Flume传输至W平台的MySQL数据库;
  • 用户推送:当用户登录平台时,后台从MySQL中查询该用户的推荐列表,优先展示偏爱菜品(高相似度用户高分菜品),再展示新菜品(用户未尝试过的),完成推荐。
模块5:流程调度与维护

使用Oozie配置完整工作流,定时触发"数据采集→预处理→模型训练→结果推送"全流程(如每天凌晨2点执行,避免影响平台正常运行);同时定期监控模型RMSE指标,每两周调整一次参数,保证推荐效果。

五、拓展任务:Spark大数据技术的行业应用(5+项)

本项目核心采用Spark离线计算(数据处理、模型训练),结合Spark生态的其他组件,可实现更多场景落地。以下为Spark大数据技术的5+项典型行业应用:

  1. 电商行业:用户个性化推荐(如淘宝、京东的商品推荐),基于Spark MLlib协同过滤算法,挖掘用户浏览、下单、收藏数据,实现精准推送;
  2. 金融行业:风险控制(如信用卡欺诈检测),基于Spark Streaming实时处理交易数据,结合机器学习算法,实时识别异常交易;
  3. 物流行业:路径优化与运力调度(如顺丰、京东物流),基于Spark SQL分析历史物流数据,结合图计算(Spark GraphX)优化配送路径;
  4. 媒体行业:内容推荐(如抖音、今日头条),基于Spark Streaming实时处理用户观看、点赞、评论数据,结合协同过滤、基于内容的推荐,实现个性化内容推送;
  5. 医疗行业:疾病预测与诊断辅助,基于Spark MLlib处理海量医疗数据(如病历、检查报告),训练分类模型,辅助医生进行疾病预测;
  6. 交通行业:交通流量预测,基于Spark SQL分析历史交通数据(如车流量、拥堵时长),结合时序模型,预测未来交通流量,辅助交通调度。

六、项目总结与优化方向

1. 项目总结

本文围绕W餐饮外卖平台老用户留存痛点,设计并实现了一套基于Spark的智能化菜品推荐方案,核心成果:

  • 完成用户评分数据探索与预处理,解决数据稀疏问题,生成高质量建模数据集;
  • 对比5大推荐算法,选定协同过滤(ALS算法)作为核心,基于Spark MLlib实现模型训练与评估(RMSE≈0.72,推荐效果良好);
  • 设计完整的推荐流程,实现"数据采集→模型训练→结果推送"全自动化,可无缝对接原有平台。

2. 后续优化方向

  • 解决冷启动问题:新用户采用"热门菜品+口味问卷"推荐,新菜品采用"基于内容的推荐",结合协同过滤实现混合推荐;
  • 提升实时性:引入Spark Streaming,实时处理用户评分数据,实现"实时更新推荐列表",提升用户体验;
  • 优化推荐多样性:融入关联规则推荐,为用户推送"搭配菜品",避免推荐同质化;
  • 模型迭代:结合用户点击、下单等隐性反馈数据,优化ALS算法参数,提升推荐准确率。
相关推荐
草莓熊Lotso6 小时前
Qt 主窗口核心组件实战:菜单栏、工具栏、状态栏、浮动窗口全攻略
运维·开发语言·人工智能·python·qt·ui
飞哥数智坊6 小时前
TRAE Friends@济南第3场圆满落幕,一次技术平权的具象化冲击
ai编程·trae
aiguangyuan6 小时前
基于BiLSTM-CRF的命名实体识别模型:原理剖析与实现详解
人工智能·python·nlp
禹凕6 小时前
Python编程——进阶知识(MYSQL引导入门)
开发语言·python·mysql
阿钱真强道6 小时前
13 JetLinks MQTT:网关设备与网关子设备 - 温控设备场景
python·网络协议·harmonyos
我的xiaodoujiao6 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 47--设置Selenium以无头模式运行代码
python·学习·selenium·测试工具·pytest
寻星探路12 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
ValhallaCoder15 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
猫头虎16 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven