简介:这份资源是基于Hadoop框架构建的电影推荐系统完整项目包,面向具备Java基础、希望实践大数据分布式计算与个性化推荐算法的开发者与学习者。项目以HDFS存储用户行为与评分数据,通过MapReduce完成数据清洗、相似度计算与推荐生成,并可能结合协同过滤、内容过滤等策略,是理解大数据推荐链路的典型实例。压缩包共1117个文件,约40.21MB,涵盖379个php、169个html、157个png、122个js、60个css等前端与页面资源,以及22个py脚本、sql、xml、json、yaml等配置与数据文件,另含docx、pdf、mpp等文档,便于梳理项目结构与部署流程。目前已有279人学习下载。读者可从中获取完整的推荐系统源码、Hadoop任务实现、前后端交互页面及配置参考,适合用于课程设计、毕业设计或大数据入门实战,帮助快速搭建可运行的电影推荐环境并理解分布式计算与推荐算法的结合方式。
1. 基于 Hadoop 的电影推荐系统:从零搭建到跑通协同过滤
如果你手头正好有一个「基于 Hadoop 电影推荐系统.zip」这样的课程设计或练手项目,大概率会经历三个阶段:解压后一脸懵、照着 README 跑不起来、跑起来也不知道推荐结果是怎么算出来的。这个标题背后其实是一条很典型的大数据入门链路——用 Hadoop 的 HDFS 存电影评分数据,用 MapReduce 或 Spark 做协同过滤计算,最后把推荐结果落库或输出成文件。它解决的核心问题是:当用户-物品评分矩阵大到单机内存放不下时,怎么用分布式的方式算出「你可能还喜欢」。适合谁?适合正在做 Hadoop 课程设计的学生、想从单机推荐算法过渡到分布式实现的工程师,以及需要一套能写进简历的完整大数据项目的人。热搜里 hadoop 伪分布式搭建、hadoop 安装与配置、hadoop 集群搭建这些词,说明大部分人卡在环境这一关,所以这篇会先把环境讲透,再讲算法落地。
2. 环境选型与 Hadoop 伪分布式搭建:单机也能跑通分布式逻辑
2.1 为什么课程设计优先选伪分布式而不是全分布式
很多人一上来就想搭三台虚拟机做全分布式,结果光 SSH 免密和网络配置就耗掉两天,最后算法一行没写。我的建议很明确:课程设计和本地开发阶段,一律先用伪分布式。伪分布式是在一台机器上启动 NameNode、DataNode、ResourceManager、NodeManager 全部守护进程,数据照样走 HDFS,任务照样走 YARN,分布式该有的逻辑一个不少,只是物理上在一台机器。等你把推荐算法跑通了,再迁移到全分布式,只需要改几个配置文件里的主机名,代码一行不用动。
选型上还有几个现实考量。Hadoop 版本建议用 3.x,因为 2.x 在很多新系统上编译兼容性越来越差,而 3.x 对 Java 版本要求是 JDK 8 或 11,别用 JDK 17 以上,否则会遇到一堆反射相关的报错。操作系统用 Ubuntu 20.04 或 CentOS 7 都行,Windows 下用 IDEA 搭建 Hadoop 开发环境也可以,但本地库需要额外处理 winutils,新手容易在这里翻车,所以能上 Linux 就上 Linux。
2.2 伪分布式搭建的完整命令与配置
下面这套步骤是我在干净 Ubuntu 上反复验证过的,按顺序执行即可。先准备 Java 环境:
# 安装 JDK 8,Hadoop 3.x 对 JDK 8 兼容性最稳 sudo apt update sudo apt install openjdk-8-jdk -y java -version # 输出应类似 openjdk version "1.8.0_xxx"接着下载并解压 Hadoop。注意不要用太新的小版本,3.3.x 系列足够稳定:
# 下载 Hadoop 3.3.6(官网归档地址,按需替换镜像) wget https://archive.apache.org/dist/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz tar -zxvf hadoop-3.3.6.tar.gz -C /opt mv /opt/hadoop-3.3.6 /opt/hadoop配置环境变量,编辑~/.bashrc,追加以下内容:
export HADOOP_HOME=/opt/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export HDFS_NAMENODE_USER=root export HDFS_DATANODE_USER=root export HDFS_SECONDARYNAMENODE_USER=root export YARN_RESOURCEMANAGER_USER=root export YARN_NODEMANAGER_USER=root执行source ~/.bashrc生效。然后修改$HADOOP_HOME/etc/hadoop/下的几个核心文件。core-site.xml指定 HDFS 的默认文件系统和临时目录:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop/data/tmp</value> </property> </configuration>hdfs-site.xml设置副本数为 1,因为伪分布式只有一个 DataNode:
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/opt/hadoop/data/datanode</value> </property> </configuration>mapred-site.xml指定 MapReduce 跑在 YARN 上,yarn-site.xml配置 ResourceManager 主机和 NodeManager 的辅助服务:
<!-- mapred-site.xml --> <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration> <!-- yarn-site.xml --> <configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>localhost</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration>还需要在hadoop-env.sh里显式指定 JAVA_HOME,否则启动时可能找不到 Java:
echo 'export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64' >> $HADOOP_HOME/etc/hadoop/hadoop-env.sh格式化 HDFS 并启动:
hdfs namenode -format start-dfs.sh start-yarn.sh jpsjps应该看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 五个进程。少任何一个都说明配置有问题,先去看$HADOOP_HOME/logs/下对应的日志。
2.3 验证 HDFS 和 YARN 是否真的可用
光看进程不够,要实际跑一个任务。先建目录、传文件:
hdfs dfs -mkdir -p /user/root/input echo "hello hadoop hello movie" > test.txt hdfs dfs -put test.txt /user/root/input/ hdfs dfs -cat /user/root/input/test.txt然后跑一个自带的 wordcount 示例,验证 YARN 调度正常:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /user/root/input /user/root/output hdfs dfs -cat /user/root/output/part-r-00000能输出词频统计结果,说明 HDFS 读写和 MapReduce on YARN 这条链路全通了。这一步别跳过,后面推荐系统跑不起来时,你至少能确定不是环境问题。
3. 电影评分数据准备与 HDFS 存储:把 MovieLens 灌进去
3.1 数据集选择与字段说明
电影推荐系统最常用的公开数据集是 MovieLens,常见的有 ml-latest-small(约 10 万条评分)和 ml-1m(约 100 万条评分)。课程设计用 ml-latest-small 就够了,数据量小、跑得快,算法逻辑完全一样。核心文件是ratings.csv,字段为 userId、movieId、rating、timestamp,还有movies.csv包含 movieId、title、genres。
选这个数据集的原因是它天然就是「用户-物品-评分」三元组,协同过滤直接能用。别自己造数据,造出来的分布不真实,算出来的推荐结果没有参考价值。
3.2 上传数据到 HDFS 并做初步清洗
先把 ratings.csv 传到 HDFS:
hdfs dfs -mkdir -p /movie/input hdfs dfs -put ratings.csv /movie/input/ hdfs dfs -ls /movie/input/实际数据里可能有缺失评分或重复记录,用 MapReduce 或 Spark 做一次清洗。这里给一个 Spark 清洗的写法,因为后面推荐算法也建议用 Spark,比裸写 MapReduce 省事很多:
from pyspark.sql import SparkSession from pyspark.sql.functions import col spark = SparkSession.builder \ .appName("MovieDataClean") \ .master("yarn") \ .getOrCreate() # 读取 HDFS 上的 CSV,带表头 df = spark.read.csv("hdfs://localhost:9000/movie/input/ratings.csv", header=True, inferSchema=True) # 去掉评分为空、超出 0.5-5.0 范围的记录,并去重 clean = df.filter((col("rating") >= 0.5) & (col("rating") <= 5.0)) \ .dropDuplicates(["userId", "movieId"]) # 写回 HDFS,parquet 格式读取更快 clean.write.mode("overwrite").parquet("hdfs://localhost:9000/movie/clean") clean.show(5) spark.stop()这段代码的逻辑是:读原始 CSV,过滤掉异常评分,按用户和电影去重,最后以 parquet 格式写回。parquet 是列式存储,后面做矩阵计算时读取效率比 CSV 高很多。参数上注意master("yarn")表示提交到 YARN,本地调试时可以改成local[*]。
3.3 数据分布检查:别急着跑算法
清洗完先看一眼数据分布,这一步很多人跳过,结果算法跑出来推荐全是烂片还不知道为什么:
# 统计每个用户的评分数量分布 user_counts = clean.groupBy("userId").count() user_counts.describe().show() # 统计每部电影的评分数量 movie_counts = clean.groupBy("movieId").count() movie_counts.orderBy(col("count").desc()).show(10)如果发现某些用户只有一两条评分,或者某些电影只有一个人评过,这些数据在协同过滤里基本是噪声。常见做法是设置阈值,比如只保留评分次数大于 20 的电影和评分次数大于 50 的用户。阈值怎么定没有标准答案,数据量大就设高一点,数据量小就设低一点,核心是保证用户-物品矩阵有足够的重叠。
4. 协同过滤算法实现:ALS 矩阵分解在 Spark 上怎么跑
4.1 为什么选 ALS 而不是 UserCF/ItemCF
协同过滤分两大类:基于邻域的方法(UserCF、ItemCF)和基于模型的方法(矩阵分解)。UserCF 和 ItemCF 在数据量小的时候效果不错,但计算相似度矩阵的复杂度是 O(n²),用户或物品一多就扛不住。ALS(交替最小二乘)是矩阵分解的代表算法,把用户-物品评分矩阵分解成两个低维矩阵的乘积,通过交替固定一个矩阵优化另一个来逼近原始评分。Spark MLlib 内置了 ALS 实现,能直接跑在 YARN 上,分布式训练,这是课程设计里最省事也最能体现「分布式」价值的方案。
选 ALS 还有一个现实原因:它天然支持隐式反馈和正则化,能缓解过拟合。你不需要自己手写梯度下降,调几个参数就能出结果,对新手友好。
4.2 ALS 训练的完整代码与参数解释
下面这段代码是推荐系统的核心,直接可以在 Spark 上跑:
from pyspark.sql import SparkSession from pyspark.ml.recommendation import ALS from pyspark.ml.evaluation import RegressionEvaluator spark = SparkSession.builder \ .appName("MovieALSRecommender") \ .master("yarn") \ .getOrCreate() # 读取清洗后的数据 ratings = spark.read.parquet("hdfs://localhost:9000/movie/clean") # 按 8:2 划分训练集和测试集 (training, test) = ratings.randomSplit([0.8, 0.2], seed=42) # 构建 ALS 模型 als = ALS( maxIter=10, # 迭代次数,一般 10-20 够用 regParam=0.1, # 正则化系数,防止过拟合 userCol="userId", itemCol="movieId", ratingCol="rating", coldStartStrategy="drop", # 丢弃冷启动用户/物品的预测 nonnegative=True, # 评分非负,加约束更合理 rank=10 # 隐因子维度,10-200 之间调 ) model = als.fit(training) # 在测试集上预测 predictions = model.transform(test) # 用 RMSE 评估 evaluator = RegressionEvaluator( metricName="rmse", labelCol="rating", predictionCol="prediction" ) rmse = evaluator.evaluate(predictions) print("Root-mean-square error = " + str(rmse)) # 为每个用户生成 Top10 推荐 user_recs = model.recommendForAllUsers(10) user_recs.show(5, truncate=False) # 保存模型和推荐结果 model.save("hdfs://localhost:9000/movie/model") user_recs.write.mode("overwrite").parquet("hdfs://localhost:9000/movie/recs") spark.stop()逻辑说明:先读 parquet 数据,按 8:2 切分,用训练集拟合 ALS 模型,在测试集上算 RMSE 衡量预测误差,最后给所有用户生成 Top10 推荐并保存。参数上,rank是隐因子维度,太小欠拟合、太大过拟合,10 到 200 之间试;regParam控制正则化强度,0.01 到 1 之间调;maxIter一般 10 到 20,再多收益递减。coldStartStrategy="drop"很重要,否则测试集里出现训练集没见过的用户或电影时,预测值会是 NaN,RMSE 直接算不出来。
4.3 用 RMSE 和推荐样例判断模型好坏
RMSE 是回归任务的常用指标,值越小说明预测评分越接近真实评分。MovieLens small 数据集上,ALS 的 RMSE 通常在 0.85 到 1.0 之间。如果 RMSE 大于 1.2,说明模型没学好,优先检查数据清洗是否到位、rank 是否太小、regParam 是否过大。如果 RMSE 小于 0.7,反而要警惕,可能是数据泄漏,比如测试集混进了训练集。
除了 RMSE,还要肉眼看推荐结果。user_recs里每个用户对应一个推荐列表,包含 movieId 和预测评分。把 movieId 关联回 movies.csv,看看推荐的是不是合理。如果给一个爱看动画片的用户推荐了一堆恐怖片,那模型肯定有问题,可能是数据里用户评分太稀疏,或者 rank 设得太低导致表达能力不足。
5. 避坑与排查:那些让推荐系统跑不起来的常见问题
5.1 坑一:HDFS 启动后 DataNode 起不来
现象:jps只看到 NameNode,没有 DataNode,hdfs dfsadmin -report显示没有可用节点。
原因:最常见的是hadoop.tmp.dir配置的目录权限不对,或者多次hdfs namenode -format导致 clusterID 不一致。DataNode 启动时会校验自己的 clusterID 和 NameNode 是否匹配,不匹配就直接退出。
解决:先停掉所有进程stop-dfs.sh,删掉hadoop.tmp.dir下的所有数据,重新格式化一次,再启动。注意格式化只能做一次,除非你清空了所有数据目录。另外确认dfs.datanode.data.dir目录存在且当前用户有写权限。
5.2 坑二:Spark 提交到 YARN 报内存不足
现象:任务提交后卡在 ACCEPTED 状态,或者直接报Container killed by YARN for exceeding memory limits。
原因:YARN 默认给每个容器分配的内存比较小,而 ALS 训练需要一定内存。另外spark.executor.memory和yarn.nodemanager.resource.memory-mb不匹配也会导致容器申请不到资源。
解决:提交时显式指定资源,比如--executor-memory 2g --num-executors 2 --executor-cores 2,同时确认yarn-site.xml里yarn.nodemanager.resource.memory-mb至少是 executor 内存的两倍。伪分布式下资源有限,别把 executor 内存设得比物理内存还大。
5.3 坑三:ALS 预测结果全是 NaN
现象:predictions里 prediction 列全是 NaN,RMSE 算出来也是 NaN。
原因:测试集里出现了训练集没有的用户或电影,ALS 无法为这些冷启动对象生成隐因子,默认返回 NaN。
解决:构建 ALS 时加coldStartStrategy="drop",让 Spark 自动丢弃这些无法预测的记录。如果不想丢,可以改用coldStartStrategy="nan"然后手动填充,但课程设计里直接 drop 最简单。根本解决办法是划分数据集时保证每个用户和电影至少在训练集里出现一次,可以用randomSplit后检查一下。
5.4 坑四:推荐结果全是同一个电影
现象:给所有用户生成的 Top10 推荐里,排名第一的都是同一部电影。
原因:通常是数据分布极度不均,某部电影被大量用户评了高分,ALS 在训练时把它的隐因子学得特别「通用」,导致对谁都推荐它。另外 regParam 太小、rank 太大也会加剧这个问题。
解决:先检查数据分布,把评分次数过少的电影过滤掉,同时考虑对热门电影做降权。参数上适当增大 regParam,比如从 0.1 调到 0.2,减小 rank,比如从 50 降到 20。还可以在推荐时加多样性约束,但课程设计里调参就够了。
5.5 坑五:Windows 下 IDEA 跑 Hadoop 报 winutils 错误
现象:在 Windows 上用 IDEA 跑 Spark 或 MapReduce,报Could not locate executable null\bin\winutils.exe。
原因:Hadoop 的 Windows 原生库缺失,Linux 下不需要,Windows 下必须有 winutils.exe 和 hadoop.dll。
解决:下载对应 Hadoop 版本的 winutils,放到HADOOP_HOME\bin下,并在代码里设置System.setProperty("hadoop.home.dir", "你的HADOOP_HOME路径")。但说实话,Windows 下跑 Hadoop 问题多,能换 Linux 就换,省下来的时间够你把算法调三轮。
6. 从跑通到能演示:推荐结果落库与 Top-N 调优技巧
跑通 ALS 只是第一步,课程设计要演示、要写报告,你得把推荐结果变得「能看」。最直接的做法是把user_recs里的 movieId 关联回电影名,输出成 CSV 或写进 MySQL。关联这一步用 Spark 的 join 就行:
movies = spark.read.csv("hdfs://localhost:9000/movie/input/movies.csv", header=True, inferSchema=True) # user_recs 里 recommendations 是数组结构,先 explode 展开 from pyspark.sql.functions import explode, col recs_flat = user_recs.select("userId", explode("recommendations").alias("rec")) \ .select("userId", col("rec.movieId").alias("movieId"), col("rec.rating").alias("score")) # 关联电影名 final = recs_flat.join(movies, on="movieId", how="left") \ .select("userId", "title", "score") \ .orderBy("userId", col("score").desc()) final.write.mode("overwrite").csv("hdfs://localhost:9000/movie/final_recs", header=True)这段代码的关键是explode,因为recommendForAllUsers返回的 recommendations 是一个数组,不展开没法做 join。展开后按 movieId 关联 movies 表,拿到电影名,最后按用户和评分排序输出。参数上how="left"保证即使某些 movieId 在 movies 表里缺失,推荐记录也不会丢。
Top-N 的 N 怎么定?课程设计里 N=10 是惯例,但你可以做一个对比实验:分别取 N=5、10、20,看推荐列表的覆盖率和多样性。覆盖率指推荐过的电影占总电影的比例,多样性指推荐列表里不同 genres 的数量。N 太小,用户选择少;N 太大,尾部推荐质量下降。我的经验是 N=10 到 20 之间比较平衡,具体看数据稀疏程度。
还有一个容易被忽略的技巧:对预测评分做归一化。ALS 输出的预测评分范围可能和真实评分范围不一致,直接展示会让人困惑。可以按用户做 min-max 归一化,把分数映射到 0 到 1 之间,展示时更直观。另外,如果想让推荐结果看起来更「新鲜」,可以在排序时加一个时间衰减因子,优先推荐近期评分多的电影,但这属于进阶操作,课程设计里不做也不影响。
最后说一个我踩过的坑:模型保存到 HDFS 后,下次想加载回来做增量训练,路径一定要写对,而且 Spark 版本要一致,否则反序列化会失败。我一般会在保存模型时同时导出一份推荐结果到本地,演示的时候直接读本地 CSV,不依赖集群,避免现场翻车。这套方案从环境搭建到出结果,熟练的话一天能跑通,剩下的时间用来调参和写报告。希望帮到你。
本文还有配套的精品资源,点击获取