ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Hadoop的协同过滤视频推荐系统:从环境搭建到Top-N生成

基于Hadoop的协同过滤视频推荐系统:从环境搭建到Top-N生成 简介面向大数据与推荐系统学习者的基于Hadoop的协同过滤视频推荐系统完整项目包解决海量视频场景下传统推荐性能瓶颈问题。资源共383个文件、12.1MB涵盖58个Java源码系统核心算法与MapReduce实现、88个JavaScript与54个CSS前端交互与展示、78个PNG和66个JPG界面素材、13个HTML页面及SQL、YML等配置文件结构完整便于直接部署与二次开发。已有24人学习下载适合高校大数据课程设计、毕业设计或入门实践。包内包含完整的前后端工程、Hadoop相关配置及推荐流程实现可帮助理解基于用户/物品的协同过滤原理与Hadoop处理用户行为数据的实际应用。1. 基于Hadoop的协同过滤视频推荐系统这个zip包解决什么问题如果你在课程设计资料里看到《基于Hadoop的协同过滤视频推荐系统.zip》这种命名大概率是一个典型的大数据毕设交付物Hadoop 负责分布式存储和并行计算协同过滤负责从用户的历史行为里给每个人算出视频推荐最后把 Top-N 结果输出成可查询的文件或接口。这类系统的意义不在算法多新而在于让“用户-视频评分矩阵”这类天然适合分块的大规模运算真正落到集群环境下跑。它适合两类人一是准备 Hadoop 课程设计或毕业设计的学生二是想快速搭一套离线推荐验证环境的技术人员。痛点也很明确单机跑全量相似度计算随着数据量增长会逐渐卡死Hadoop 把矩阵运算拆到多节点并行后规模问题才解耦。下面我从选型拆到实现把配置、参数和踩坑点一次讲清。2. 把系统拆成三层Hadoop管存储计算协同过滤管推荐策略2.1 视频推荐场景为什么绕不开Hadoop视频推荐系统拿到手的数据本质是一张稀疏的大宽表行是用户列是视频单元格里是评分或观看时长。平台一天产生的行为记录轻松上亿条单机推荐算法当然能处理一小批导出数据但一旦全量训练内存里装载用户-物品矩阵的代价会迅速超过单机物理限制。Hadoop 的价值说白了是把“分块存储”和“并行计算”两件事标准化HDFS 存下原始日志MapReduce 在数据所在节点上就地计算。这也是标题里“基于 Hadoop”的真正含义——不是在 Hadoop 上抄一个传统推荐算法而是让算法具备横向扩展能力。常见做法是把整个系统分成三层HDFS 存放原始日志和预处理后的评分数据MapReduce 负责离线计算包括行为统计、相似度计算、Top-N 候选合并最后用 MySQL 或 Redis 保存离线结果供上层 Web 或 App 查询。课程设计里通常实现的是前两层最上层简化成结果文件或一个简单查询接口。这里要注意Hadoop 和 ZooKeeper 整合通常是多节点集群才开始涉及的事伪分布式单机阶段可以先不碰等需要做 NameNode HA 时再引入。伪分布式是性价比最高的学习路径。同一台机器上NameNode、DataNode、ResourceManager、NodeManager 都以独立进程存在配置过程和真集群几乎一致只是把 dfs.replication 降到 1。也有人在 Docker 镜像里跑 Hadoop 做实验但对课程设计和答辩来说原生伪分布式更便于讲清楚配置与排错逻辑。2.2 协同过滤选 user-based 还是 item-based协同过滤最核心的选择是相似度方向。user-based 找“和我胃口相似的用户”把他们的观看记录推给我item-based 找“和我看过视频相似的其他视频”。视频推荐场景下我一般优先选 item-based。原因很朴素视频站点用户量远大于视频量用户画像变化快user-based 每次计算都要重建用户相似关系而视频之间的相似度相对稳定可以离线算好反复复用。更直白地说item-based 把最复杂的计算放到“视频-视频矩阵”上规模比“用户-用户矩阵”可控得多。相似度度量方面课程设计里最常用的是余弦相似度。把两个视频各自收到的评分看作向量余弦相似度就是夹角余弦。公式上大致是“共同打分向量的内积除以两个向量模长的乘积”。皮尔逊相关系数会先减掉用户平均分再接余弦对评分尺度的差异更鲁棒但实现复杂度高一点。答辩时能讲清余弦相似度已经足够支撑一个基于 Hadoop 的推荐选题。需要提醒的是真实评分矩阵是极端稀疏的用户打分的覆盖范围通常不到视频总数的 1%。这时候 item-based 比 user-based 更稳因为“视频一起被看过”的共现关系比“用户口味一致”更容易被捕捉到。另外要区分显式评分和隐式反馈MovieLens 数据集自带 1 到 5 的显式评分而实际视频平台的清洗日志往往只有观看时长。后者需要把完成率映射成分数这个处理放到后面数据准备小节详细说。2.3 一份 zip 交付物的典型工程结构拿到 zip 后先别急着解压先看包内 README 或 docs 目录。常见交付结构是目录/文件作用src/main/java推荐系统源码主要是若干个 MapReduce 作业data/MovieLens 或自造评分数据集input/清洗后待上传 HDFS 的文本output/推荐结果或相似度结果conf/core-site.xml、hdfs-site.xml 等配置说明docs/设计文档、答辩 PPT、数据库建表说明README.md部署命令、环境版本要求检查环境要求是第一优先级。zip 包作者使用的 Hadoop 版本、JDK 版本、Maven 仓库位置决定了你能不能复现。很多学生拿到包就急着改代码最后发现本地 Hadoop 版本和依赖不一致花在环境上的时间反而比写推荐逻辑多。还有一个容易忽略的点Hadoop 2.x 的 mapreduce 包名是 org.apache.hadoop.mapreduceHadoop 3.x 仍兼容但其他组件如 Hive、HBase 则必须匹配特定 Hadoop 版本。如果 zip 项目是 2.x 时代编写的最稳妥就是复用 2.10.x 版本而不是升级到 3.3.x 再回来改代码。3. 伪分布式环境搭建从空机器到跑通一个Hadoop作业3.1 前置依赖安装与 SSH 免密配置Hadoop 伪分布式安装有一个固定的先后顺序JDK、SSH、Hadoop 本体顺序不要打乱。JDK 版本上我建议 JDK8Hadoop 2.x 和 3.x 都用得很稳避免用新版本 JDK 踩模块化兼容问题。下面的命令基于 Ubuntu/CentOS 系系统以 root 用户执行。sudo apt-get update sudo apt-get install -y openssh-server openssh-client vim wget tar ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost命令含义ssh-keygen 生成一对 RSA 密钥-P 表示空密码-f指定文件位置把公钥追加到 authorized_keys 后ssh localhost就不再要求输密码。如果这里一直要求输入密码排查一下 authorized_keys 的权限权限不能是 777否则 SSH 会拒绝读取。Hadoop 本体解压后还需要把环境变量写进 profiletar -zxvf hadoop-2.10.1.tar.gz -C /opt/ echo export HADOOP_HOME/opt/hadoop-2.10.1 export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 /etc/profile.d/hadoop.sh source /etc/profile.d/hadoop.sh hadoop versionJAVA_HOME 路径以实际 JDK 安装位置为准不确定时用readlink -f $(which java)反查。hadoop version能输出版本号说明环境变量生效。不用在 etc/hadoop/hadoop-env.sh 里重复配置 JAVA_HOME保持环境变量文件简洁排错会更快。3.2 五个配置文件的关键参数说明伪分布式配置集中在 Hadoop 安装目录的 etc/hadoop 下。第一次配置建议只动五个文件core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml、hadoop-env.sh。core-site.xml 配置默认文件系统和临时目录configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/opt/hadoop/data/tmp/value /property /configurationhdfs-site.xml 配置副本数和 secondary namenode 地址configuration property namedfs.replication/name value1/value /property property namedfs.namenode.secondary.http-address/name valuelocalhost:50090/value /property /configuration注意hadoop.tmp.dir 不要放在 /tmp 下很多 Linux 发行版定期清空 /tmp会导致格式化记录和元数据全部丢失。dfs.replication 在伪分布式下只能设 1设成 2 或 3 后 DataNode 因副本不足会一直处于等待状态。mapred-site.xml 里指定使用 YARNconfiguration property namemapreduce.framework.name/name valueyarn/value /property /configurationyarn-site.xml 把单机内存限制写清楚configuration property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property property nameyarn.scheduler.minimum-allocation-mb/name value512/value /property property nameyarn.scheduler.maximum-allocation-mb/name value4096/value /property /configuration如果是 2GB 内存的小机器先按物理内存的 70% 估算不要照抄 4096。配置完成后格式化并启动hdfs namenode -format start-dfs.sh start-yarn.sh jps格式化会在 hadoop.tmp.dir 下生成元数据目录启动后jps能看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 五个进程环境就绪。如果jps只有 Jps 自身看 $HADOOP_HOME/logs 下的日志最常见的错误是 JAVA_HOME 没生效或端口占用。3.3 从 zip 包导入项目到编译提交作业进入推荐系统代码环节。先把 zip 包解压到工作目录用 IntelliJ IDEA 打开其中的 maven 工程。IDEA 首次导入时pom.xml 里的依赖会下载到本地仓库网络慢时很容易出现 jar 损坏报错后面避坑章节会讲。注意工程路径不要含中文和空格Hadoop 作业的输入输出目录名也建议只用英文。pom.xml 中需要声明与安装的 Hadoop 版本一致的依赖dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version2.10.1/version scopeprovided/scope /dependency编译打包并提交作业mvn clean package -DskipTests hdfs dfs -mkdir -p /user/root/rec/input hdfs dfs -put data/u.data /user/root/rec/input/ hadoop jar target/recommend-1.0.jar com.example.RecommendDriver \ /user/root/rec/input /user/root/rec/output这里com.example.RecommendDriver是入口类按源码实际包名替换。/user/root/rec/output输出目录必须不存在Hadoop 不允许覆盖已有输出目录。如果数据还在本地先hdfs dfs -put再运行如果数据量很大可以只放抽样文件到 input 目录跑通流程后再全量。实际提交后控制台会输出 map 和 reduce 进度。等作业结束时查看结果hdfs dfs -ls /user/root/rec/output hdfs dfs -cat /user/root/rec/output/part-r-00000part-r-00000是 reducer 输出文件多个 reducer 会生成多个 part 文件。用hdfs dfs -getmerge可以合并到本地一份文件方便导出统计。这一步能正常看到推荐列表说明整条链路已经通了一大半。4. 协同过滤实现细节评分矩阵、相似度计算与Top-N生成4.1 把观看日志整理成可计算的评分矩阵协同过滤算法输入的最基本格式是三列userId、videoId、score。课程设计里最常遇到的现成数据是 MovieLens 的 u.data字段分别是用户ID、电影ID、评分、时间戳分隔符是制表符。视频平台的原始日志一般没有评分只有用户观看时长处理方式是先做映射。常见做法是用播放完成率作为评分依据完成率 0.8 以上记 5 分0.6 到 0.8 记 3 分0.4 到 0.6 记 1 分低于 0.4 视为无效观看丢弃。这个规则不是标准要把它写进项目文档保证答辩时能说清数据来源。假设原始日志 log.csv 的列是 videoId,userId,playPercent用 awk 做字段筛选和映射awk -F, NR1{ if ($30.8) score5; else if ($30.6) score3; else if ($30.4) score1; else next; print $2\t$1\tscore; } log.csv input/ratings.tsv wc -l input/ratings.tsv sort -u input/ratings.tsv -o input/ratings.tsv参数说明awk 的NR1跳过表头$1是 videoId$2是 userId$3是完成率。输出统一改成制表符分隔比逗号更好因为后续 Java 代码用split(\t)处理更不容易被数据里的逗号干扰。wc -l看一眼总行数sort -u去重避免同一用户对同一视频产生多条评分。数据准备好后上传 HDFShdfs dfs -mkdir -p /user/root/rec/input hdfs dfs -put input/ratings.tsv /user/root/rec/input/这一步之后输入数据就不再依赖本地文件系统。后面跑作业时如果输出结果行数和输入行数对不上回到 ratings.tsv 重新检查空行和不合规字段。4.2 一个 MapReduce 实现物品相似度的核心思路基于物品的协同过滤在 MapReduce 里最常见的做法是从用户视角聚合出物品共现关系再计算相似度。第一个作业的任务是“把同一用户看过的视频两两配对统计共现次数”。Mapper 阶段以 userId 作为 key把 userId、videoId 原样输出目的是让同一个用户的数据分到同一个 reducer。public class CoOccurrenceMapper extends MapperLongWritable, Text, Text, Text { Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] parts value.toString().split(\t); String userId parts[0]; String videoId parts[1]; context.write(new Text(userId), new Text(videoId)); } }Reducer 阶段把一个用户看过的所有视频做两两组合输出“视频A:视频B”共现计数public class CoOccurrenceReducer extends ReducerText, Text, Text, IntWritable { Override protected void reduce(Text key, IterableText values, Context context) throws IOException, InterruptedException { ListString items new ArrayList(); for (Text v : values) { items.add(v.toString()); } MapString, Integer pairCount new HashMap(); for (int i 0; i items.size(); i) { for (int j i 1; j items.size(); j) { String pair items.get(i).compareTo(items.get(j)) 0 ? items.get(i) : items.get(j) : items.get(j) : items.get(i); pairCount.put(pair, pairCount.getOrDefault(pair, 0) 1); } } for (Map.EntryString, Integer entry : pairCount.entrySet()) { context.write(new Text(entry.getKey()), new IntWritable(entry.getValue())); } } }参数说明两两组合前要把 key 按字典序固定防止A:B和B:A被当成两对待统计。单个 reducer 里用户看过的视频列表越长双重循环的代价越高所以工程实现里一般会在 mapper 端限制每个用户最多取 200 个视频避免数据倾斜。第二个作业输入第一个作业输出的共现计数按余弦公式算相似度。实现上通常会先把每个物品的向量模长算出来再在 reducer 里用“共现次数除以模长乘积”得到最终相似度。课程设计里直接用共现次数做归一化通常已经能出结果真正要复现完整余弦公式还需要按物品分组多一次 MapReduce。第二个作业的业务逻辑不复杂但 HDFS 上会有一次 shuffle数据量大时是性能大头。4.3 Top-N 推荐生成的三个必调参数相似度表生成后推荐阶段要针对每个用户聚合候选视频。核心逻辑是取用户历史视频的所有相似视频加权汇总排序后截取前 N 个。实现上可以单独跑一个 MapReduce 作业mapper 读相似度表reducer 按用户合并。这里有三个参数最值得调也最容易在答辩时被问到。邻居数 k参与加权的相似视频数量。k 太大会融入大量弱相关候选结果趋同热门k 太小结果波动大。常见取值 20 到 50。相似度阈值 minSimilarity相似度低于阈值的候选直接丢弃推荐质量更干净也能节省 reducer 内存常用 0.1 到 0.3。结果截断 topN最终给用户输出多少条。离线评估常用 10方便算召回率。下面用 Python Hadoop Streaming 做一个简化版 reducer演示聚合和截断逻辑import sys current_user None rec_score {} for line in sys.stdin: # 输入格式userId videoId score uid, vid, score line.strip().split(\t) if current_user is None or uid ! current_user: if current_user is not None: # 输出上一个用户的top10 recs sorted(rec_score.items(), keylambda x: -x[1])[:10] print(current_user \t ,.join([v for v, _ in recs])) current_user uid rec_score {} # 这里假设相似度表已经通过join算出每个候选的加权分数 # 实际工程中用 相似度×用户评分 累加 rec_score[vid] rec_score.get(vid, 0.0) float(score) if current_user is not None: recs sorted(rec_score.items(), keylambda x: -x[1])[:10] print(current_user \t ,.join([v for v, _ in recs]))参数说明[:10]就是 topN。真实代码里“相似度乘用户评分累加”的部分需要 join 相似度表这里用评分直接演示数据结构。运行前要确认 HADOOP_STREAMING_JAR 环境变量已指向对应 jar否则hadoop jar后面必须带上完整 jar 路径。结果写回 HDFS 后用hdfs dfs -cat检查输出看看同一用户输出行里是否包含他历史中已看过的视频若包含说明过滤逻辑没生效。5. 避坑指南zip解压、Hadoop配置和推荐结果的常见翻车点5.1 zip 伪加密或文件损坏导致无法解压现象解压“基于Hadoop的协同过滤视频推荐系统.zip”时提示文件损坏或解压到一半要求输入密码而 README 明明说没有密码。原因部分打包工具把 zip 文件头部的加密标志位设置成伪加密内容并没有真正加密但普通解压器看到标志位就要求密码。更常见的是下载过程中文件不完整破坏了压缩包结尾结构。解决用 7-Zip 或最新版 WinRAR 打开验证先执行“测试”命令。伪加密可以用 7-Zip 的参数尝试解压或用 zip 命令行工具消除加密标志真加密只能找回密码或返回原来源。如果是尾部分段丢失典型报错是 could not find EOCD这种情况重新下载完整文件即可不需要反复修复。5.2 NameNode 起不来Incompatible clusterIDs现象执行 hdfs namenode -format 后再 start-dfs.shjps 看不到 NameNode日志报 Incompatible clusterIDs。原因NameNode 和 DataNode 的 clusterID 不一致。常见于格式化后集群临时目录被清理或者格式化之前没有删除旧目录导致新旧元数据混用。解决按顺序执行 stop-all.sh清空 hadoop.tmp.dir 整个目录再次执行 hdfs namenode -format之后 start-dfs.sh。注意格式化会清掉 HDFS 上已有数据演示环境还没正式数据时这样处理最干净。清空前先备份 input 目录里的原始数据。5.3 作业提交到 YARN 后一直 ACCEPTED不进入 RUNNING现象hadoop jar 提交后控制台停在 Map 0%YARN 管理页面显示应用状态 ACCEPTED迟迟不起容器。原因伪分布式的 NodeManager 可用内存低于默认配置的 yarn.nodemanager.resource.memory-mb导致容器一直无法分配。也可能是 mapreduce.map.memory.mb 比 maximum-allocation-mb 还大资源请求被拒绝。解决将 yarn-site.xml 中 memory 参数下调到物理内存的 60% 到 70%mapred-site.xml 中 map/reduce 内存控制在 512MB 到 1024MB重启 yarn 服务。改完后看 NodeManager 日志确认内存限制是否生效。如果机器内存本身不到 2GB建议先加交换分区或在虚拟机里增加内存否则内存问题会在 shuffle 阶段再次爆发。5.4 推荐结果出现 NaN 或所有用户推荐同一批视频现象输出文件里有大量 NaN或不同用户的前 N 条推荐完全一样。原因评分数据中存在缺失值或 0 分余弦相似度分母为 0重复评分没去重导致共现次数失真排序前没有过滤用户历史视频热门视频占据了 Top-N。解决运行前用awk -F\t $3~/^[0-9](\.[0-9])?$/ {print} input/ratings.tsv清洗非法评分去掉 0 分和缺失值排序前把用户历史列表作为排除集合对 minSimilarity 设置不为 0 的阈值。做完这三件事后一般不会再出现全 NaN 或清一色热门的结果。5.5 IDEA 导入项目时报 Invalid zip archive: could not find EOCD现象在 IntelliJ IDEA 里打开 pom.xml 时IDEA 弹窗报错 Invalid zip archive: could not find EOCD依赖树刷新失败。原因本地 Maven 仓库中的某个 jar 因网络中断下载不完整文件尾部的 EOCDEnd of Central Directory记录丢失。这是依赖下载最常见的翻车点和 Hadoop 本身无关但卡住的概率很高。解决找到 ~/.m2/repository 下对应 artifactId 目录把相关版本目录整体删掉再执行 mvn clean compile 重新下载。建议给 pom.xml 配置阿里云镜像仓库一个镜像能省去大半依赖下载时间。重新编译时观察卡在哪个模块如果每次都卡在同一位置的同一 jar直接手动用下载工具把该 jar 放到本地仓库对应路径。6. 验证推荐效果用离线指标和抽样日志确认系统可用6.1 用召回率给推荐结果打分跑通不等于正确。我一般先按时间把用户历史切分成训练集和测试集用前 80% 训练后 20% 做真实反馈再用推荐结果计算命中率。课程设计里会算单个指标 Precision10、Recall10 就够了。用一个简化的 Python 脚本计算 Recall10def recall_at_k(pred, truth, k10): if not truth: return 0.0 hit len(set(pred[:k]) set(truth)) return hit / len(set(truth)) pred [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10] truth [v3, v9, v21] print(recall_at_k(pred, truth))参数说明pred 是推荐作业输出的视频 id 数组truth 是用户在测试集里真实点击过的视频 id 集合。命中率一般不强求很高离线协同过滤能到 10% 到 30% 就属于正常范围关键是把对比实验做出来比如对比不同邻居数 k 下的 Recall10。6.2 冷启动与输出落库被低估的工程细节新用户没有历史行为协同过滤必然算不出来。常见做法是给新用户回热门视频列表等产生行为后再切换成个性化推荐。另一种是把观看时长映射成隐式评分避免过度依赖显式打分。这两个处理不需要增加 MapReduce 复杂度却能让系统在演示时看起来更完整。离线结果要供前端查询时设计成 user_id、video_id、score、rank 四列导入 MySQL。跑全量数据前我会先用一小部分日志把整个链路过一遍确认每个阶段输出行数符合预期再切全量。这是让我少翻车最多的一条习惯。希望这些拆解和避坑经验帮到你少踩几次环境坑。本文还有配套的精品资源点击获取
返回列表