
简介涵盖WordCount、PageRank、Apriori关系挖掘、K-Means聚类与推荐系统五大经典实验的大数据分析实验资源包适合正在学习MapReduce并行计算、图算法、关联规则、无监督学习及协同过滤的高校学生与自学者参考。资源共56个文件以Python源码、CSV实验数据集、docx任务书、txt运行结果为主压缩包约115.26MB目录按lab1至lab5分模块组织结构清晰便于对照学习。其中WordCount实验提供9个模拟分布式节点的源文件与多线程Map-Reduce实现包含map与reduce输出结果PageRank、Apriori、K-Means及推荐系统实验均附带可直接运行的算法脚本、预处理后的数据集及最终输出文件。每项实验配有详细的任务书文档帮助理解实验目的、流程与关键步骤。目前已有501人学习适合需要完整实验方案、可运行代码与规范结果文件的大数据课程学习者。1. 为什么把这五个实验放在一起先交代一下背景。这个项目是我在实际带大数据分析课程时整理的一套实验组合五个子实验分别是wordCount、PageRank、关系挖掘、k-means聚类、推荐系统。单看每个实验都不算难但把它们串起来之后覆盖的恰恰是大数据分析和机器学习中最核心的几类问题分布式批处理、图计算、频繁模式挖掘、无监督聚类、个性化推荐。如果你正在自学大数据或者学校里正好开了类似的课这套实验顺序其实就是一条很合理的进阶路径。第一个实验wordCount是“Hello World”让你跑通分布式计算的基本流程理解Map和Reduce到底在做什么。第二个PageRank把视角从“统计词频”拉升到“图结构上的迭代计算”你需要理解迭代式算法在分布式框架里是怎么收敛的。第三个关系挖掘我用的关联规则方向开始处理“事务型数据”核心是从大量记录里挖出“如果买了A大概率也会买B”这种隐藏规律。第四个k-means进入无监督学习算法本身简单到可以用几行代码实现但真正用在大规模数据上时初始中心点怎么选、K值怎么定、数据怎么标准化这些细节才是拉开差距的地方。第五个推荐系统则是把前面学到的所有东西综合起来——你要处理用户行为数据、计算相似度、做排序、评估效果是一个完整的闭环。为什么建议大家按这个顺序走因为每个实验都在前一个的基础上增加一个新的复杂性维度wordCount让你熟悉工具PageRank让你理解迭代关联规则让你接触非结构化的事务数据k-means让你面对“没有标准答案”的无监督问题推荐系统则把工程和算法揉在一起。这五个实验做完你基本就摸清了大数据分析的主干。2. 环境准备与工具选型这套实验对环境的依赖不算高但有几个坑值得提前说清楚免得浪费时间。2.1 Hadoop与Spark怎么选wordCount和PageRank这两个实验用Hadoop MapReduce或者Spark都能做但我的建议是wordCount用Hadoop跑一遍原生MapReducePageRank改用Spark实现。为什么这么组合wordCount的核心目的是理解MapReduce的计算模型用Hadoop原生的Java接口写一遍你才能真切体会到“Map完还要ShuffleShuffle完才进Reduce”这个过程。如果一上来就用Spark的reduceByKey一行代码就结束了你根本感受不到数据在分布式环境里是怎么流动的。PageRank则不一样。它的每次迭代都涉及多轮MapReduce作业如果用Hadoop写你要维护多个MR作业之间的状态传递代码量大而且容易出错。Spark的RDD可以缓存中间结果迭代计算天然占优势——这也是当时Spark之所以能取代Hadoop成为主流图计算平台的核心原因。所以我推荐Hadoop加Spark混着用各有侧重。2.2 关系挖掘与推荐系统直接用Python关系挖掘实验Apriori算法和推荐系统实验我建议直接跳出Hadoop/Spark用Python实现核心算法只在数据量特别大的时候才考虑用Spark MLlib。原因很简单这两个算法的实现逻辑复杂度远高于计算量需求。Apriori的核心是“逐层搜索剪枝”几千条事务数据用纯Python跑起来都很快没必要引入分布式框架增加理解成本。推荐系统里的协同过滤算法也是一样核心是相似度计算和排序用Python的pandas加numpy就能完成。学习阶段应该把注意力放在算法本身而不是被分布式框架分心。等哪天真遇到十亿级用户数据再上Spark也不迟。2.3 实验环境配置一览我用的是三台虚拟机构成的集群配置如下节点角色CPU内存磁盘masterNameNode / ResourceManager4核8GB50GBslave1DataNode / NodeManager4核4GB50GBslave2DataNode / NodeManager4核4GB50GB软件版本方面Hadoop 3.3.4Spark 3.4.0Python 3.9JDK 8。这套组合是经过验证比较稳定的Hadoop 3.x和Spark 3.x的兼容性很好不需要额外配置什么。记得把JAVA_HOME和HADOOP_HOME环境变量配好这是新手最常见的卡壳点。3. 子实验一wordCount——分布式计算的地基3.1 实验目标与数据准备这个实验的目标很简单给定一批文本文件统计每个单词出现的次数。但它背后承载的意义不简单——你要理解的是“数据分片是怎么分发的”“Map阶段每个分片做了什么”“Shuffle过程发生了什么”“Reduce阶段怎么汇总结果”。测试数据不需要太大我建议从Gutenberg项目下载几本英文原著或者直接用Linux系统自带的/etc/words文件组合成多个输入分片。关键是要把输入数据分成多个文件或者用一个大于HDFS块大小默认128MB的文件这样才能看到多Map任务的并行效果。我当时准备了三个文件每个大约5MB放在HDFS的/input目录下。文件内容刻意选择了不同风格的文本——新闻、技术文档、小说这样单词分布差异大能明显感受到Reduce端的热点倾斜问题后面会详细讲。3.2 原生MapReduce实现要点Hadoop的wordCount代码网上非常多但这里有几个被人忽略的细节第一Combiner的使用。Combiner是Map端的“预Reducer”它会在数据从Map端输出后、进入Shuffle之前先做一次本地的合并。wordCount是Combiner的经典使用场景因为求和操作天然满足结合律。加了Combiner之后Map端输出的数据量会大幅减少Shuffle的压力小很多。实际测试中加不加Combiner作业完成时间能差30%以上。第二Partitioner的作用。默认的HashPartitioner会根据key的哈希值把数据分配到不同的Reduce任务。这个机制在wordCount里看起来“无感”但如果你想控制某个单词一定要到某个Reduce任务里比如按首字母分桶就需要自定义Partitioner。理解这一点后续学Join优化、数据倾斜处理时会有很大帮助。第三输出文件数的控制。Reduce任务的数量决定了最终输出文件的数量。默认情况下是1个Reduce但如果你想看到多个输出文件可以设置setNumReduceTasks(3)。有一种很经典的“反模式”是设置大量Reduce任务来处理小数据每个Reduce都写一个文件造成大量小文件堆积在HDFS上——这个问题在真实生产环境里非常常见这里提前体验一下是好的。3.3 常见报错与排查WordCount跑不通90%的情况是环境问题。我遇到过最典型的几个Error: java.lang.ClassNotFoundException运行hadoop jar时类路径没配对。解决办法是用hadoop classpath命令拿到完整类路径在运行时拼上。输出目录已存在Hadoop不允许覆盖已有的输出目录。如果你重跑作业需要先删除输出目录或者换一个新的输出路径。内存溢出默认的Map/Reduce内存上限可能是1GB如果单词量特别大或者Combiner设置不当容易OOM。可以在mapred-site.xml里调大mapreduce.map.memory.mb和mapreduce.reduce.memory.mb。4. 子实验二PageRank——图计算的第一课4.1 算法核心与工程实现差异PageRank的数学原理不复杂把网页间的链接关系看作一个有向图通过迭代计算每个节点的权重。每个网页的排名等于所有指向它的网页的排名贡献之和再乘以衰减因子通常取0.85。但数学上的简洁和工程上的笨重往往是两回事。用Spark实现PageRank最核心的操作就是两个RDD之间的Join和聚合val links sc.parallelize(Array( (A, Array(B, C)), (B, Array(C, D)), (C, Array(A)), (D, Array(A, B)) )).mapValues(v v.toSeq).cache() var ranks links.mapValues(_ 1.0) for (i - 1 to 10) { val contribs links.join(ranks).flatMap { case (url, (urls, rank)) val size urls.size urls.map(u (u, rank / size)) } ranks contribs.reduceByKey(_ _).mapValues(0.15 0.85 * _) }这个代码里有一个关键细节links必须用.cache()缓存。为什么因为每次迭代都要用到完整的链接关系如果不缓存Spark每次都要从磁盘重新读取并重新计算十次迭代就是十次完整的重算性能会慢到无法接受。这是Spark迭代式计算的核心优化点在很多生产作业里正确使用缓存vs不使用缓存性能差距可以达到几十倍。4.2 收敛判断与迭代次数PageRank是个迭代算法理论上要跑很多轮才能收敛。实际工程中不会无止境地跑下去而是设置两个条件最大迭代次数比如20次或者看两次迭代之间排名变化是否小于某个阈值比如0.001。这里有个经验值值得记住衰减因子d取0.85时收敛速度会比较合理。这个0.85是PageRank论文作者实验得出的推荐值含义是“用户有85%的概率点击当前页面上的链接继续浏览15%的概率直接跳到任意一个随机页面”。如果你把这个值调大比如0.95算法会更贴近真实浏览行为但收敛会变慢调小则收敛快但结果偏离实际。在实际测试中我跑一个只有几百个节点的图设置10次迭代和20次迭代的结果差异已经非常小说明收敛速度比想象中快得多。原因不难理解——PageRank的传播过程是指数衰减的经过几轮迭代后从某个节点出发的“能量”已经分散到大量节点上了对整体排名的影响微乎其微。4.3 一个容易被忽略的问题悬挂节点真实网页不可能都是“正常”的——总有些页面没有任何出链。在PageRank算法里这些悬挂节点会“吞掉”排名导致所有能量最后都流向这些节点。解决办法是在每次迭代时把悬挂节点的排名均匀分给所有节点。代码里可以这样处理val danglingRank ranks.filter(_._2 0.0).values.sum() val totalNodes ranks.count() val extraRank danglingRank / totalNodes ranks contribs.reduceByKey(_ _) .mapValues(_ extraRank) .mapValues(0.15 0.85 * _)这个细节看起来不起眼但如果没有它你的PageRank结果会非常难看——所有悬挂节点排名虚高正常节点的排名被挤压。这也是为什么教科书上的算法和工程实现之间总是存在差距很多“理论上正确”的算法拿到真实数据上要打很多补丁才能用。5. 子实验三关系挖掘——从交易记录中找规律5.1 关联规则与Apriori原理关系挖掘也叫关联规则挖掘的核心场景是购物篮分析给你一堆交易记录每笔交易包含若干商品ID找出“买了啤酒的人大概率也会买尿布”这样的规律。Apriori算法的核心思想非常朴素如果一个项集是频繁的那么它的所有子集也必须是频繁的。反过来如果一个项集不是频繁的那么任何包含它的超集也不可能频繁——这个性质叫“先验性质”apriori。基于这个性质算法可以逐层生成候选项集然后剪掉那些不可能是频繁项集的候选大幅减少计算量。5.2 Python实现与支持度/置信度的坑Apriori的Python实现分三步生成频繁一项集、迭代生成频繁K项集、从频繁项集中提取关联规则。第三步提取规则时有两个指标需要理解清楚支持度SupportP(A∩B)表示A和B同时出现的概率。支持度太低说明这个规则没有代表性可能是偶然现象。置信度Confidence)P(B|A)表示买了A的情况下买B的概率。置信度太低说明A对B没有太强的预测力。这里有一个很容易踩的坑置信度高的规则不一定是好规则。举个例子如果99%的交易里都有“手机壳”这个商品那么“A → 手机壳”的置信度会非常高但这条规则毫无价值——因为不买A的人也会买手机壳。这时候需要用**提升度Lift**来衡量Lift P(A∩B) / (P(A) * P(B))。Lift大于1说明A对B有正向促进作用小于1说明是负相关等于1说明两者独立。我实验时设置的支持度阈值是0.02置信度阈值是0.5在约5万条交易记录上跑出了几条典型的强规则。然后故意拿一条高置信度但低提升度的规则出来对比分析帮学生直观理解三个指标的含义。如果你做实验报告建议一定要展示这个对比它比单纯输出规则列表有价值得多。5.3 数据预处理事务数据的格式化关系挖掘的实验数据一般不直接长成“一行是一笔交易”的样子需要从结构化表中进行转换。比如原始数据里每行是一个订单的商品明细多个商品对应多个订单号需要先按订单号做分组再把商品列表整理成事务格式。这一步看起来简单但在Python里处理大数据集时有个性能陷阱如果用逐行groupby再apply几百万行的数据可能要跑很久。正确的做法是先用pandas的groupby聚合再转换成集合类型。另外事务数据里的商品ID如果有极端高频的商品比如每个订单都有的赠品应该在挖掘前先过滤掉否则它会主导所有规则成为“噪声”。6. 子实验四k-means聚类——无监督学习的代表6.1 算法流程与代码骨架k-means应该是所有聚类算法里最简单的一个随机选择K个中心点把每个样本分配到离它最近的中心点然后重新计算每个簇的中心点重复直到中心点不再变化。这个算法写起来非常短def kmeans(data, k, max_iter100): centers data[np.random.permutation(len(data))[:k]] for _ in range(max_iter): distances np.linalg.norm(data[:, np.newaxis] - centers, axis2) labels np.argmin(distances, axis1) new_centers np.array([data[labels i].mean(axis0) for i in range(k)]) if np.allclose(new_centers, centers): break centers new_centers return centers, labels代码本身没什么值得解释的但k-means真正的难点在于初始化、K值选择和数据标准化。这几个问题在学术界被研究了很久每个都有坑。6.2 K值怎么定肘部法则的实际操作选K值最常用的方法是“肘部法则”画一张聚类数K对应SSE簇内误差平方和的折线图看看哪个K值之后SSE的下降速度明显变缓那个拐点就是合适的K。听起来很简单但实际做的时候会发现折线图往往没有明显的“肘部”而是平滑下降。这时候我会采用一个辅助判断看每个簇的样本数量是否均衡。如果K3时出现一个簇有8000个样本、另外两个簇各只有1000个说明聚类结果不太合理可能需要调大K或者调整初始化方式。另外还有一个经验K值本身没有绝对正确答案它取决于你希望划分的粒度。比如用户分群你想看宏观的3类还是精细的10类取决于业务需求。实验报告里最好把K2到K8的SSE曲线都画出来再结合业务场景说明你为什么选这个K值。6.3 初始化K-Means为什么效果更好标准k-means是随机选初始中心点运气不好时会收敛到局部最优解。K-Means的改进思路是第一个中心点随机选之后每个中心点距离已有的中心点越远被选中的概率越大。这样初始中心点能尽量分散避免一开始就聚在一起。在scikit-learn里KMeans默认就用K-Means初始化参数initk-means。但如果你是自己实现k-means实验课通常要求手写建议把K-Means也实现一遍然后对比两种初始化方式在同一个数据集上的聚类结果。我实际跑下来随机初始化在8次试验里有2次明显陷入局部最优SSE偏高而K-Means几乎没有这个问题。这就是为什么说初始化看似不起眼但对结果影响巨大。6.4 数据标准化最容易被忽略的步骤k-means是基于距离的算法特征之间的量纲差异会直接影响聚类结果。比如用户数据里“登录次数”可能是几百“消费金额”可能是几万“停留时长”可能是几秒如果不做标准化聚类结果基本被“消费金额”这一个特征主导。处理方式一般是Z-score标准化(x - mean) / std或Min-Max缩放。这个步骤要在算距离之前做而且要在数据分割前对全部数据做标准化而不是分别对训练集和测试集做——否则你得到的是两套不同的分布后续评估就没意义了。7. 子实验五推荐系统——从协同过滤到完整评估7.1 数据集与评估方案推荐系统实验我用的是MovieLens 100K数据集包含100,000条评分记录、1,682部电影和943个用户。这个数据集是推荐系统研究的标准数据集规模不大但足够展示算法效果而且格式干净适合实验。评估方案用的是留一法Leave-One-Out的变体把每个用户的最后一条评分作为测试集其余作为训练集。然后用RMSE均方根误差和MAE平均绝对误差两个指标评估预测的准确性。此外还要算一个在真实推荐场景中非常重要的指标覆盖率。覆盖率指的是推荐物品占总物品的比例。如果你的推荐结果总是集中在少数热门电影上RMSE可能很好看但实际上用户并不会觉得推荐有多“懂你”——因为推荐的永远是那些大家都看过的热门片。覆盖率低是所有协同过滤算法在冷启动阶段面临的通病。7.2 基于用户的协同过滤实现与参数选择基于用户的协同过滤核心就是三步计算用户相似度矩阵、找到目标用户的K个近邻、根据近邻的评分预测目标用户对未看物品的评分。相似度计算有几种选择余弦相似度、皮尔逊相关系数、调整余弦相似度。我做实验时对比了三种方法的RMSE发现在MovieLens数据集上皮尔逊相关系数略微优于普通余弦相似度原因是皮尔逊相关系数做了用户评分均值中心化能够消除不同用户评分习惯差异有人喜欢打高分有人喜欢打低分的影响。K近邻的K值选择也需要调参。K太小预测噪声大K太大近邻里混入不相似的用户预测准确率下降。我实验时K从5到50每5取一个值画了一条RMSE-K曲线发现K20左右是拐点。这个调参过程强烈建议写进实验报告它体现了“算法不是黑盒参数需要针对性调整”的工程思维。7.3 冷启动问题与混合推荐思路协同过滤有一个天然的局限新用户没有任何评分记录系统无法计算相似度也就无法做推荐。这就是“冷启动”问题。实验阶段可以通过调整策略缓解比如在用户没有足够评分数据时退回用“热门榜”做推荐。这是推荐系统实验里非常值得深入探讨的一个环节。你可以顺手实现一个简单的混合策略当用户评分数量小于5条时直接推荐全站评分最高的Top 10电影当评分数量大于5条时再用协同过滤。这样虽然牺牲了一部分个性化程度但至少保证了推荐的可用性。真实工业界的推荐系统本质上也都是在个性化、准确性、多样性和实时性之间做权衡没有一个算法能在所有指标上全面胜利。7.4 实验结果展示框架实验报告里建议用表格把结果汇总清楚算法RMSEMAE覆盖率UserCF (K20)0.980.7862%ItemCF0.940.7358%热门推荐基线1.120.865%这里对比才有意义热门推荐基线的RMSE虽然只有1.12不算离谱但覆盖率只有5%——意味着系统只会推荐几十部热门电影中的一部分。这种对比能直观说明“预测准确”不等于“推荐效果好”覆盖率、多样性这些指标在推荐系统里同等重要。8. 常见问题与排查技巧速查这五个实验做下来有一批高频问题值得提前记录我整理成了速查表问题现象可能原因解决思路Hadoop作业卡在Running状态内存不够、节点通信故障检查ResourceManager/NodeManager日志确认DataNode都存活Spark程序跑得很慢缺少缓存、分区数不合适检查RDD血缘关系给重复使用的RDD加cache增大分区数提升并行度Apriori跑出大量无意义规则置信度阈值过低、高频项未过滤提高置信度阈值用Lift过滤规则预处理时去掉垃圾商品k-means聚类结果每次不同随机初始化导致局部最优固定随机种子使用K-Means多次运行取最优UserCF推荐结果集中在热门电影用户评分稀疏、长尾效应增加相似度阈值过滤尝试ItemCF考虑覆盖率指标环境变量导致命令行找不到命令JAVA_HOME或HADOOP_HOME配置问题统一在/etc/profile里配好重开终端验证还有两个调试经验值得单独讲一下。第一个是关于数据规模的检验。我一开始用很小的数据集几千条跑通逻辑后直接切到全量数据结果程序跑了一小时还没结束排查了很久发现是Shuffle阶段数据倾斜——某个Reduce任务处理了90%的数据。后来先分析了一下key的分布发现某个商品ID出现频率异常高处理方式是加盐打散后二次聚合。这类问题在教科书上不会讲但真实项目里几乎一定会碰到。第二个是关于可复现性。所有的随机数都要固定种子包括k-means的初始化、协同过滤的训练集划分、Spark任务的随机分区。这样别人复现你的实验时结果才一致。我见过太多人实验报告里写“得到最好结果”但代码里没有固定随机种子别人跑出来的结果完全不同。9. 几个关键经验总结最后说一点我自己的体会。这套实验设计得巧妙的地方在于它用五个经典算法串起了一条完整的学习路径。但很多人在做实验时只关注“跑通代码”和“得出结果”忽略了实验背后真正重要的东西——理解每个算法为什么这样设计、在什么场景下适用、有什么局限性。我的建议是每个实验做完后都问自己三个问题这个算法解决什么问题如果数据量扩大100倍我的实现还能跑吗如果数据换成另一种形态算法需要做什么调整我当时带学生做这套实验时还额外让他们每个人给自己选的算法写一个“缺点清单”。比如Apriori的缺点是什么会产生大量候选项集数据量大时内存扛不住。k-means的缺点是什么对初始值敏感、需要预先指定K、对异常值敏感。PageRank的缺点是什么有堆积效应、偏重旧页面、容易被搜索引擎优化利用。写完之后再想想有哪些改进算法——FP-Growth解决Apriori的候选集爆炸、K-Means解决初始化问题、TrustRank解决Web垃圾页面问题——这套思路一旦建立起来你对算法的理解深度会远超那些只会调库的人。这也是为什么我坚持推荐大家把这五个实验全部手写一遍至少在核心算法部分要自己实现不要完全依赖现成库。手写一次踩过的坑比读十遍文档都值。本文还有配套的精品资源点击获取