ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hadoop电影推荐系统源码实战:从环境搭建到协同过滤算法

Hadoop电影推荐系统源码实战:从环境搭建到协同过滤算法 简介这是一套面向计算机、电子信息工程、数学等专业大学生的课程设计与毕业设计参考资料围绕Hadoop平台上的电影推荐系统展开帮助读者掌握分布式文件操作与数据处理的核心技能。资源包共10个文件以Python源码、CSV数据集、u.item与u.data等MovieLens原始数据文件及Markdown说明文档为主压缩包约2.49MB代码参数化设计、注释清晰并附有运行结果便于二次修改与调试。项目基于Windows 10、Hadoop 2.8.3、Python 3.x、VSCode与MySQL 8.0环境搭建通过mrjob实现多轮MapReduce任务完成评分数据清洗、用户与电影信息关联及推荐结果输出覆盖从数据预处理到推荐计算的全流程。目前已有231人学习下载适合需要完整项目方案、排错思路与目录结构参考的读者可据此快速复现实验并理解Hadoop与Python协同开发的工程组织方式。1. 从一份能跑通的 Hadoop 电影推荐源码说起很多同学做课程设计或毕设时最头疼的不是写不出代码而是环境跑不起来、数据对不上、结果出不来。这份Implementation-of-a-hadoop-based-movie-recommendation-system-main.zip就是冲着这个痛点来的它用 Python 写 MapReduce 任务跑在 Hadoop 2.8.3 上配套 MovieLens 的u.data、u.item、u.user三份原始数据最终产出result.csv和ratings.csv。整个流程从数据清洗到协同过滤推荐一条龙代码里注释清楚、参数可改作者声称都实测跑通过。适合谁计算机、电子信息、数学专业需要交课程设计或大作业的本科生以及想快速摸清 Hadoop 上跑推荐系统完整链路、不想在环境上耗三天的新手。下面我按自己拆包复现的顺序把这份资源讲透。2. 拆开压缩包文件清单与数据流走向2.1 每个文件到底干什么用先把包解开根目录下是这些文件文件作用mr1.py第一个 MapReduce 任务清洗原始评分数据mr2.py第二个 MapReduce 任务计算物品相似度或生成推荐mrjobTemp.pymrjob 框架的模板文件方便本地调试run.py主入口脚本串联整个执行流程u.dataMovieLens 原始评分数据格式为 用户ID::电影ID::评分::时间戳u.item电影元数据含电影ID、标题、类型等u.user用户元数据含用户ID、年龄、性别、职业result.csv最终推荐结果输出ratings.csv中间处理后的评分数据README.md运行说明和依赖描述这个结构很典型原始数据 → 清洗 → 计算 → 输出。mr1.py和mr2.py是核心run.py负责调度mrjobTemp.py是给你改参数用的模板。数据流是单向的不绕弯对新手友好。2.2 数据格式先对齐不然后面全白干MovieLens 的u.data用的是双冒号::分隔不是逗号也不是制表符。很多人在这一步翻车——直接拿 pandas 的read_csv默认逗号去读结果整个 DataFrame 只有一列。正确做法是显式指定分隔符import pandas as pd # 注意u.data 用双冒号分隔不是逗号 df pd.read_csv(u.data, sep::, enginepython, names[user_id, movie_id, rating, timestamp]) print(df.head()) print(df.shape)sep::是关键enginepython是因为默认的 C 引擎不支持多字符分隔符。names手动指定列名因为原始文件没有表头。跑完这一步你应该看到 100000 行左右的数据具体取决于你用的 MovieLens 版本四列类型分别是 int、int、float、int。如果行数差太多先检查分隔符再检查文件编码。提示u.item里电影标题可能含逗号用 pandas 读的时候同样要小心建议也用sep|并指定encodinglatin-1否则会报 UnicodeDecodeError。3. 环境搭建Hadoop 2.8.3 伪分布式 Python 3 联调3.1 Hadoop 伪分布式最小配置这份代码跑在 Hadoop 2.8.3 上Windows 10 环境。伪分布式是课程设计最常用的模式一台机器模拟整个集群。核心改两个文件core-site.xmlconfiguration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configurationhdfs-site.xmlconfiguration property namedfs.replication/name value1/value /property /configurationfs.defaultFS指定 NameNode 地址伪分布式下就是 localhost。dfs.replication设成 1因为只有一个 DataNode设成 3 会一直报副本不足。改完执行hdfs namenode -format格式化再start-dfs.sh启动。用jps检查应该看到 NameNode、DataNode、SecondaryNameNode 三个进程。少一个就去翻logs目录下的日志常见原因是端口被占或权限不对。3.2 Python 端依赖与 mrjob 配置代码用 mrjob 来写 MapReduce 任务它能把 Python 脚本直接提交到 Hadoop 集群。先装依赖pip install mrjob pandas numpymrjob 默认用本地模式跑要提交到 Hadoop 需要加-r hadoop参数。在mrjobTemp.py里通常能看到这样的配置段from mrjob.job import MRJob class MRRatingClean(MRJob): def configure_args(self): super(MRRatingClean, self).configure_args() self.add_passthru_arg(--min-rating, default3.0, typefloat, help最低评分阈值低于此值的记录被过滤) def mapper(self, _, line): fields line.strip().split(::) if len(fields) 4: user_id, movie_id, rating, _ fields if float(rating) self.options.min_rating: yield movie_id, (user_id, float(rating)) def reducer(self, movie_id, values): for user_id, rating in values: yield movie_id, (user_id, rating) if __name__ __main__: MRRatingClean.run()add_passthru_arg定义了一个可调参数--min-rating默认 3.0。这意味着评分低于 3 的记录会被过滤掉减少噪声。mapper 里按::切分reducer 直接透传。这个模板的好处是参数化——你想改阈值命令行加--min-rating 4.0就行不用动代码。提交命令python mrjobTemp.py -r hadoop hdfs://localhost:9000/input/u.data \ --min-rating 3.5 -o hdfs://localhost:9000/output/cleaned-r hadoop指定运行模式-o指定输出路径。跑之前确认u.data已经上传到 HDFS 的/input目录用hdfs dfs -put u.data /input/搞定。3.3 把数据喂进 HDFS 并跑通第一个任务完整的上传和执行流程# 在 HDFS 建输入目录 hdfs dfs -mkdir -p /input # 上传三份原始数据 hdfs dfs -put u.data /input/ hdfs dfs -put u.item /input/ hdfs dfs -put u.user /input/ # 确认上传成功 hdfs dfs -ls /input/ # 执行第一个 MapReduce 任务 python mr1.py -r hadoop hdfs://localhost:9000/input/u.data \ -o hdfs://localhost:9000/output/mr1 # 查看输出 hdfs dfs -cat /output/mr1/part-00000 | head -20-mkdir -p递归建目录-put上传本地文件到 HDFS。mr1.py的输出在/output/mr1下通常是一个part-00000文件。用-cat加head看前 20 行确认格式对不对。如果输出为空八成是 mapper 里的过滤条件太严或者输入路径写错了。这时候把--min-rating调低再试。4. 推荐算法核心从评分矩阵到推荐结果4.1 协同过滤在 MapReduce 里怎么拆这份代码用的是基于物品的协同过滤Item-Based CF。核心思路如果两个电影被同一批用户相似地评分它们就相似用户对看过的电影评分高就推荐相似的高分电影。在 MapReduce 里拆成两个阶段第一阶段mr1.py清洗评分数据输出电影ID → (用户ID, 评分)的键值对。这一步把原始数据整理成按电影聚合的格式方便后续计算相似度。第二阶段mr2.py对每部电影计算它与其他电影的相似度然后按相似度加权预测用户对未看电影的评分取 Top-N 作为推荐。相似度用余弦相似度import math def cosine_similarity(ratings_a, ratings_b): # ratings_a, ratings_b 是 dict: {user_id: rating} common_users set(ratings_a.keys()) set(ratings_b.keys()) if not common_users: return 0.0 dot_product sum(ratings_a[u] * ratings_b[u] for u in common_users) norm_a math.sqrt(sum(ratings_a[u] ** 2 for u in common_users)) norm_b math.sqrt(sum(ratings_b[u] ** 2 for u in common_users)) if norm_a 0 or norm_b 0: return 0.0 return dot_product / (norm_a * norm_b)common_users是两部电影共同被评分的用户集合。dot_product是评分向量的点积norm_a和norm_b是各自的模长。分母为零说明某部电影没有有效评分直接返回 0 避免除零错误。这个函数在 reducer 里对每一对电影调用一次计算量取决于电影数量和共同评分用户数。4.2 参数怎么调相似度阈值与推荐数量mr2.py里通常有几个关键参数参数含义建议值影响--sim-threshold相似度最低阈值0.3~0.5太低引入噪声太高推荐太少--top-n每用户推荐数量10~20太多不精准太少没参考价值--min-common最少共同评分用户数5~10太少相似度不可靠调参逻辑先用默认值跑一遍看result.csv里推荐结果的数量和分布。如果很多用户没有推荐降低--sim-threshold或--min-common如果推荐明显不相关提高阈值。我一般会跑三组对比保守0.5/10/10、中等0.4/15/5、宽松0.3/20/3看哪组在覆盖率和准确率之间平衡最好。4.3 跑通完整流程并验证输出完整执行# 第二阶段计算相似度并生成推荐 python mr2.py -r hadoop hdfs://localhost:9000/output/mr1 \ --sim-threshold 0.4 --top-n 15 --min-common 5 \ -o hdfs://localhost:9000/output/mr2 # 把结果拉回本地 hdfs dfs -get /output/mr2/part-00000 result.csv # 用 pandas 检查结果 python -c import pandas as pd df pd.read_csv(result.csv, sep\t, headerNone, names[user_id, recommendations]) print(df.head(10)) print(总用户数:, len(df)) print(有推荐的用户数:, df[recommendations].notna().sum()) -get把 HDFS 上的结果下载到本地。result.csv通常是制表符分隔两列用户ID 和推荐电影列表。用 pandas 读进来检查总用户数和有推荐的用户数差多少。如果差太多说明阈值设高了回去调--sim-threshold。这一步是验证整个链路是否通畅的关键别跳过。注意result.csv里的推荐列表可能是字符串形式的列表比如[1, 5, 12]后续要用eval或ast.literal_eval解析。直接当列表用会报错。5. 避坑排查环境、数据、算法三层翻车点5.1 Hadoop 启动报错NameNode 反复格式化现象每次重启都提示NameNode is not formatted或者jps看不到 NameNode 进程。原因hdfs namenode -format执行多次导致dfs.namenode.name.dir下的 clusterID 不一致或者core-site.xml里fs.defaultFS的端口和实际启动的端口对不上。解决停掉所有进程stop-dfs.sh删掉data和logs目录下的所有内容重新格式化一次然后只启动一次。格式化只能做一次之后重启直接start-dfs.sh不要再格式化。5.2 Python 脚本提交后卡在 map 0% reduce 0%现象mrjob 提交任务后一直卡在 0%日志里没有明显报错。原因Hadoop 集群资源不够或者 mapper 里死循环。常见的是u.data文件太大mapper 处理超时或者split(::)后字段数不对代码里没做长度检查导致异常被吞掉。解决先在本地用-r local跑一遍确认逻辑没问题。本地能跑通再提交到 Hadoop。mapper 里加if len(fields) ! 4: return做防御。如果数据量大用-D mapreduce.map.memory.mb2048增加内存。5.3 推荐结果全是同一部电影现象result.csv里每个用户的推荐列表几乎一样或者都是最热门的电影。原因相似度计算时没有排除用户已经看过的电影或者热门电影的相似度普遍偏高导致推荐被热门电影霸榜。解决在生成推荐前先拿到用户已评分的电影集合从候选推荐里剔除。另外对相似度做归一化或者用 TF-IDF 加权降低热门电影权重。代码里加一行过滤# 在 reducer 生成推荐前排除用户已看过的电影 watched set(user_ratings.keys()) recommendations [m for m in candidate_movies if m not in watched]5.4 中文路径或空格导致 HDFS 上传失败现象hdfs dfs -put报No such file or directory但本地文件明明存在。原因Windows 下路径含中文或空格HDFS 命令解析出错。解决把数据文件放到纯英文、无空格的路径下比如D:\hadoop\data\。上传时用绝对路径别用相对路径。如果路径必须含空格用引号包起来hdfs dfs -put D:\my data\u.data /input/。5.5 输出目录已存在导致任务失败现象第二次跑同一个任务时报Output directory already exists。原因Hadoop 不允许输出目录已存在防止覆盖数据。解决每次跑之前删掉旧输出hdfs dfs -rm -r /output/mr1。或者用-o指定一个带时间戳的新目录比如/output/mr1_$(date %s)。6. 进阶技巧用 mrjob 本地模式快速迭代与结果验证跑通全流程后最耗时的不是写代码而是每次改参数都要提交到 Hadoop 等半天。我的习惯是先用 mrjob 的本地模式快速验证逻辑确认没问题再上集群。本地模式命令python mr2.py -r local --sim-threshold 0.4 --top-n 15 \ --min-common 5 u.data local_result.csv-r local让 mrjob 在本地用多进程模拟 MapReduce速度比 Hadoop 快一个数量级。输入用重定向 u.data输出直接写文件。本地跑出来的结果和 Hadoop 上跑出来的应该一致前提是数据一样如果不一致八成是 mapper 或 reducer 里有依赖集群环境的代码比如读 HDFS 路径。验证推荐质量我一般看两个指标覆盖率和平均相似度。覆盖率是有推荐结果的用户占比平均相似度是推荐列表里电影与用户已看电影的平均相似度。用 pandas 算import pandas as pd import ast df pd.read_csv(result.csv, sep\t, headerNone, names[user_id, recs]) df[recs] df[recs].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else []) # 覆盖率 coverage (df[recs].apply(len) 0).mean() print(f覆盖率: {coverage:.2%}) # 推荐数量分布 df[rec_count] df[recs].apply(len) print(df[rec_count].describe())ast.literal_eval安全地把字符串列表转成 Python 列表。覆盖率低于 60% 就说明阈值太严高于 95% 可能太松。推荐数量分布看中位数和均值差太多说明有异常用户。还有一个技巧把u.item里的电影标题关联到推荐结果上这样输出的是电影名而不是 ID答辩或演示时直观得多。用 pandas mergeitems pd.read_csv(u.item, sep|, encodinglatin-1, headerNone, names[movie_id, title] [fgenre_{i} for i in range(18)]) items items[[movie_id, title]] # 展开推荐列表并关联标题 df_exploded df.explode(recs).dropna(subset[recs]) df_exploded[recs] df_exploded[recs].astype(int) merged df_exploded.merge(items, left_onrecs, right_onmovie_id, howleft) print(merged[[user_id, title]].head(20))explode把列表列展开成多行merge关联电影标题。这样每个用户推荐了哪些电影一目了然。从那以后我每次交课程设计前都强制走一遍本地模式验证 标题关联确认推荐结果能看懂、能解释再打包提交。希望帮到你。本文还有配套的精品资源点击获取
返回列表