ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Hadoop MapReduce实现电影用户性别预测:从数据清洗到模型应用

Hadoop MapReduce实现电影用户性别预测:从数据清洗到模型应用 简介本资源是一套基于Hadoop生态实现的电影网站用户性别预测项目源码面向大数据初学者与高校课程实践者聚焦生活娱乐场景下的用户画像建模问题适用于MapReduce编程、数据清洗与分类算法如KNN的综合训练。压缩包共60个文件含26个Java源文件覆盖数据预处理、MapReduce作业、Join关联、特征分割等核心模块、28个编译后class文件、2个配置文件log4j.properties与数据库/集群连接参数相关properties、以及.project、.classpath等Eclipse工程元数据文件整体仅81KB轻量但结构完整。已有2117人学习下载虽需使用者自行补充原始数据集并适配本地Hadoop环境如修改IP、版本号或数据库配置但其清晰的模块划分如demo01demo03、datajoin、knn_split_data等目录与典型业务流程封装为理解分布式数据处理链路提供了可拆解、可调试的参考范例。 提到Hadoop很多人第一反应是“大数据平台、TB级处理、上千台节点”但真正动手做过课程设计或者入门项目的人都知道第一步往往是先用单机伪分布式把流程跑通。前段时间我正好把一个电影网站用户性别预测的案例完整做了一遍从环境搭建到MapReduce代码编写再到结果调优整个过程踩了不少坑。这篇文章就把整个项目原原本本拆开来讲包括数据集怎么选、三个MapReduce作业怎么设计、代码怎么写、哪些参数需要调、以及容易翻车的地方。适合正在做Hadoop课程设计、准备大数据面试、或者想用MapReduce练手的朋友拿过去可以直接参考复现。1. 项目到底在做什么需求拆解与目标设定1.1 一句话说清业务目标这个项目的核心任务很简单给定一个电影评分网站的历史数据包括用户信息、电影信息和评分记录然后根据用户看过的电影以及打分情况预测这个用户的性别是男还是女。为什么要做这个预测放到真实的互联网场景里性别是用户画像里非常重要的一环。网站做个性化推荐、广告投放、内容运营都需要知道访问者的大致性别。但很多情况下用户不会主动填写性别或者填了也不一定真实。这时候就可以通过行为数据来推断比如一个人看了大量动作片、科幻片打分风格偏向硬核那大概率是男性一个人对爱情片、文艺片的评分参与度更高可能就更倾向于女性。当然这个逻辑不可能百分之百准确但它能作为一个概率输出给业务方一个参考维度。从Hadoop课程设计的角度看这个需求非常适合用来练习MapReduce因为它天然可以被拆解成多个阶段先做数据清洗和用户过滤再统计每个电影在不同性别群体下的评分分布最后基于这些统计结果做预测。每一步都是一个独立的MapReduce作业非常适合展示MapReduce“分而治之”的思想。1.2 为什么用Hadoop而不是直接用Python跑很多人会问这个数据量看起来也不大直接用pandas加载到内存里算不就完了为什么要用Hadoop这个问题的答案要分两面说。从纯工程效率角度如果数据量只有几万条、几十万条用Pandas确实更快代码也更短。但Hadoop项目案例的价值不在于“最快解决这个问题”而在于“用分布式思维解决一类问题”。当数据量到了几亿条、几十亿条单机内存装不下或者计算时间超过了可接受的阈值MapReduce模型就能派上用场。它把计算逻辑拆成Map和Reduce两个阶段Map阶段可以并行处理海量数据Reduce阶段做汇总合并理论上数据量再大只要集群规模够都能在有限时间内算完。另外课程设计和面试场景里考官想看到的是你对大数据处理框架的理解而不是单纯的机器学习能力。所以这个项目需要展示的是你会不会用HDFS存数据、会不会写MapReduce作业、能不能处理数据倾斜、懂不懂Combiner优化——这些才是Hadoop项目的核心考点。至于模型本身用朴素贝叶斯就够了因为它的计算逻辑简单能非常自然地拆成Map和Reduce两个阶段不需要搞复杂的迭代计算。1.3 方案与模型选型为什么是朴素贝叶斯我最终选的分类模型是朴素贝叶斯而且是用MapReduce自己实现而不是调用现成的机器学习库。原因有三点。第一朴素贝叶斯的训练过程本质上就是统计频次。对每个电影、每个性别组合统计“喜欢”和“不喜欢”的人数这个统计逻辑用MapReduce写起来非常顺手Mapper负责解析评分并输出键值对Reducer负责累加计数。第二朴素贝叶斯的预测过程也可以做成一个独立的MapReduce作业把待预测用户的评分记录作为输入把训练阶段得到的条件概率表作为辅助数据在Reduce阶段算出每个用户属于男性和女性的后验概率取较大者作为预测结果。第三朴素贝叶斯对缺失数据和不平衡数据有一定的容忍度而且可解释性很好。预测完你能明确说出是哪些电影的评分把概率推向了男性或女性这对写实验报告和答辩都很有利。相比之下SVM、逻辑回归这些模型要实现分布式版本复杂度高得多不适合作为Hadoop课程设计。2. 环境准备与数据集获取2.1 运行环境与Hadoop版本组合这个项目我是在一台8G内存的笔记本上跑的系统是CentOS 7Hadoop用的是3.5.0版本Java用的JDK 8。Hadoop选择3.x而不是2.x原因很简单3.x是当前主流HDFS支持纠删码、支持more than 1 NameNode性能比2.x有提升而且新版的yarn调度器更稳定。有一点要特别提醒第一次搭环境的朋友JDK版本和Hadoop版本必须匹配。我最初装的是JDK 11结果Hadoop 3.5.0启动后NameNode一直报错查了半天才发现是JDK版本兼容性问题。换回JDK 8之后一次就过了。具体版本对应关系可以查Hadoop官方文档3.5.0对应的稳定JDK是8或11但实测8最稳。我这里是伪分布式模式也就是在一个节点上同时跑NameNode、DataNode、ResourceManager、NodeManager。如果你用的是多台机器的完全分布式集群那还涉及ZooKeeper、JournalNode这些组件复杂度会高不少。对于课程设计来说伪分布式完全够用而且方便调试。2.2 数据集选择MovieLens 1M数据集我用的MovieLens 1M这是电影推荐领域最经典的公开数据集之一由美国明尼苏达大学的GroupLens研究组发布。它包含三个文件users.dat、ratings.dat、movies.dat总数据量在百万级非常适合做Hadoop入门演示。三个文件的字段格式需要提前说清楚因为后面写MapReduce的时候要按字段位置解析。users.dat的格式是用户ID::性别::年龄::职业::邮编其中性别字段只有M和F两个值。age是一个分类编号1表示18岁以下18表示18-24岁25表示25-34岁以此类推。ratings.dat的格式是用户ID::电影ID::评分::时间戳评分范围是1到5的整数。movies.dat的格式是电影ID::电影名::电影类型列表其中电影类型用竖线分隔比如Action|Crime|Drama。这个数据集的好处是字段干净、格式统一不需要做过多的ETL可以把精力集中在MapReduce逻辑上。如果你想自己造数据也可以但真实数据集的分布更能反映现实中遇到的情况比如热门电影被评分次数极高、冷门电影可能只有几个人评过这种长尾分布正好可以用来讨论数据倾斜问题。2.3 数据预处理与上传HDFS拿到原始数据后不能直接扔给Hadoop跑因为三个文件分散在本地目录而MapReduce作业读取的是HDFS上的路径。所以第一步是把数据传到HDFS上。我建了一个目录/movie/input存放原始数据执行命令如下hdfs dfs -mkdir -p /movie/input hdfs dfs -put users.dat /movie/input/ hdfs dfs -put ratings.dat /movie/input/ hdfs dfs -put movies.dat /movie/input/ hdfs dfs -ls /movie/input/上传之后可以确认一下文件块分布。用hdfs fsck命令看文件被分成了几个block这能帮你直观理解HDFS的分块机制。比如ratings.dat有200多万行文件大小约23M默认块大小128M的话它只占一个块但实际生产环境里的文件远不止这个规模分块和并行计算的逻辑是一样的。需要说明的是原始数据集的分隔符是双冒号::在MapReduce里解析时按“::”拆分即可。但是有一个坑Java里的split方法接受的参数是正则表达式而::不是正则特殊字符直接拆分没问题。不过如果某个字段里包含了|这样的字符处理时就要小心了这个后面在代码里会说。3. 三个MapReduce作业的完整实现3.1 作业一统计用户评分次数过滤冷启动用户第一个作业要解决的是冷启动和低活跃用户的问题。有些用户只评过一两部电影凭这么少的信息去预测性别结果基本靠蒙。所以我们需要先统计每个用户的评分次数然后过滤掉评分次数少于阈值的用户。这个作业的逻辑非常基础就是一个经典的WordCount变体。Mapper读入ratings.dat每一行按照“::”拆分取第0个字段作为用户ID输出用户ID, 1。Reducer累加得到每个用户的总评分次数。关键代码片段如下public class RatingCountMapper extends MapperObject, Text, Text, IntWritable { private final static IntWritable one new IntWritable(1); private Text userId new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] fields value.toString().split(::); // ratings.dat: UserID::MovieID::Rating::Timestamp userId.set(fields[0]); context.write(userId, one); } } public class RatingCountReducer extends ReducerText, IntWritable, Text, IntWritable { private IntWritable result new IntWritable(); public void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable val : values) { sum val.get(); } result.set(sum); context.write(key, result); } }跑完作业一后输出目录/movie/rating_count里的每一行是一个用户ID加评分次数。我在Driver里设置了一个过滤条件评分次数少于10次的用户不进入后续模型。这个阈值不是拍脑袋定的你可以先跑一遍作业一看看数据分布情况再决定。MovieLens 1M里大多数用户的评分次数都在20到200之间低于10的属于极端低活跃用户过滤掉之后能明显提升预测准确率。过滤的实现方式有两种一种是在Driver里读取作业一输出筛选后写入另一个目录另一种是对作业一的输出再做一次小MapReduce。我推荐后者虽然多一个作业但思路清晰而且方便你在命令行里单独验证每一步的输入输出。后面还会讲到数据量和噪音的权衡要放在真实场景里理解过滤太狠会导致训练样本不足过滤太松又会让模型被低质量数据带偏。3.2 作业二训练性别偏好模型第二个作业是整个项目的核心它的目的是统计出每个电影在不同性别中的评分偏好。为了把“喜欢”和“不喜欢”转成可计算的条件概率我做了一个中性化处理评分4分视为喜欢评分2分视为不喜欢评分等于3分直接忽略。这么做是因为3分代表了“一般、没感觉”语义模糊放进模型里反而会引入噪音。这个作业的输入是经过作业一过滤后的评分数据以及users.dat里的性别信息。因为评分数据在ratings.dat里性别信息在users.dat里所以需要做一次数据关联。MapReduce里做数据关联通常有两种方式Map Side Join和Reduce Side Join。这里users.dat很小可以直接放到DistributedCache里在Mapper的setup阶段加载到内存这样在map阶段就能把用户ID映射成性别不需要额外做Reduce Side Join。Mapper的逻辑分三步从DistributedCache里加载性别映射表存成一个HashMap。读入评分数据解析出用户ID、电影ID、评分。通过性别映射表查到该用户性别再判断该评分是喜欢还是不喜欢输出复合键电影ID::性别值为1或0。这里的复合键需要重新解释一下我们想让Reducer按电影ID和性别分组同时分别统计喜欢和不喜欢的人数。一个直接的做法是输出的key是“电影ID::性别”value里带上喜欢标记。但更简洁的方式是输出两个不同的计数标记比如电影ID::性别, 1表示喜欢电影ID::性别, 0表示不喜欢。这样就需要在自定义键上实现分组逻辑或者在Mapper阶段就拆成两个输出。我当时的做法是因为“喜欢”和“不喜欢”是互斥的所以可以在同一个Mapper里输出两个key一个是电影ID::性别_LIKE一个是电影ID::性别_DISLIKEvalue都为1。这样Reducer不需要判断value里的正负只需要对1做累加。这个方案的思路是把一个“多分类计数”问题拆成两个独立的计数作业。虽然代码看起来重复但逻辑更直观也更容易扩展如果后面加入“性别未知”的情况只需要增加一个新的key后缀就行。下面是核心代码public class PrefModelMapper extends MapperObject, Text, Text, IntWritable { private final static IntWritable one new IntWritable(1); private MapString, String userGenderMap new HashMap(); private Text outKey new Text(); Override protected void setup(Context context) throws IOException, InterruptedException { // 从DistributedCache加载用户性别映射 Path[] cacheFiles context.getLocalCacheFiles(); if (cacheFiles ! null cacheFiles.length 0) { BufferedReader br new BufferedReader(new FileReader(cacheFiles[0].toString())); String line; while ((line br.readLine()) ! null) { String[] fields line.split(::); // users.dat: UserID::Gender::Age::Occupation::Zip-code userGenderMap.put(fields[0], fields[1]); } br.close(); } } public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] fields value.toString().split(::); String userId fields[0]; String movieId fields[1]; int rating Integer.parseInt(fields[2]); String gender userGenderMap.get(userId); if (gender null) { return; // 没有性别信息的用户直接跳过 } if (rating 4) { outKey.set(movieId :: gender _LIKE); context.write(outKey, one); } else if (rating 2) { outKey.set(movieId :: gender _DISLIKE); context.write(outKey, one); } // rating 3 直接忽略 } }Reducer就是一个标准的累加器。这一步生成的模型文件大概长这样1::F_LIKE 98 1::F_DISLIKE 12 1::M_LIKE 45 1::M_DISLIKE 60 2::F_LIKE 120 ...意思是电影1女性用户中有98人打了4分以上12人打了2分以下男性用户中45人喜欢60人不喜欢。这个表就是后续性别预测的依据。3.3 作业三性别预测与结果输出第三个作业负责预测。输入是待预测用户的评分数据辅助数据是作业二生成的偏好模型。目标是对每一个待预测用户输出一个性别判断以及对应的置信度。这里用的概率公式是朴素贝叶斯。假设我们已知男性用户在所有用户中的占比P(男性)对一部电影m用户打了高分喜欢时P(喜欢|男性)可以用训练数据里的M_LIKE次数除以男性评价该电影的总次数得到。同理可以得到P(不喜欢|男性)、P(喜欢|女性)、P(不喜欢|女性)。对于用户的每一条评分记录我们根据实际评分找出对应的条件概率把所有条件概率相乘再乘以先验概率P(性别)得到的就是该用户属于该性别的后验概率。实际计算时因为概率连乘会越乘越小甚至出现下溢出所以通常取对数再累加。我在代码里用Math.log操作计算对数概率然后用logP_M和logP_F的大小比较来判定。Mapper端处理逻辑读入评分记录解析出用户ID、电影ID、评分输出以用户ID为key的记录。这里不做计数只是把同一个用户的评分数据收集到一起方便Reducer端统一计算。Reducer端拿到一个用户的所有评分后遍历每一条评分从全局加载的模型表里查对应电影和性别的概率值累加对数概率最后比较两个概率大小。代码里我在setup阶段把整个模型表加载成一个HashMap键是“电影ID::性别_标签”值是对数概率。这里有性能上的取舍。模型表如果很大全量加载到每个Mapper和Reducer的内存里会有压力。但MovieLens 1M的模型表只有几千个电影再乘以4种组合内存占用非常小。如果换成千万级电影的数据集就不能再这么干了需要改成Map Side Join时按输入分片过滤模型子集或者用数据库存储模型参数这属于工程优化话题这里先不展开。下面是Reducer端的核心计算逻辑public class PredictReducer extends ReducerText, Text, Text, Text { private MapString, Double logProbMap new HashMap(); private double pMalePrior 0.0; private double pFemalePrior 0.0; Override protected void setup(Context context) throws IOException, InterruptedException { // 加载模型表计算先验概率 // 加载逻辑从缓存文件中读取并填充logProbMap } public void reduce(Text key, IterableText values, Context context) throws IOException, InterruptedException { double logPMale Math.log(pMalePrior); double logPFemale Math.log(pFemalePrior); for (Text val : values) { String[] parts val.toString().split(::); String movieId parts[0]; int rating Integer.parseInt(parts[1]); String prefLabel (rating 4) ? LIKE : ((rating 2) ? DISLIKE : null); if (prefLabel null) continue; // 查询该电影下男性/女性的log概率并累加 Double mProb logProbMap.get(movieId ::M_ prefLabel); Double fProb logProbMap.get(movieId ::F_ prefLabel); if (mProb ! null) logPMale mProb; if (fProb ! null) logPFemale fProb; } String predictGender (logPMale logPFemale) ? M : F; double confidence Math.exp(Math.max(logPMale, logPFemale) - logSumExp(logPMale, logPFemale)); context.write(key, new Text(predictGender \t confidence)); } }注意最后一个logSumExp是为了将两个对数概率归一化成置信度避免出现置信度大于1或者负数的情况。预测结果输出格式是“用户ID 性别 置信度”。比如196 M 0.87 186 F 0.92 22 M 0.65拿到这个结果后可以和users.dat里的真实性别做对比算准确率。我用一个简单的Python脚本统计了一下在过滤掉评分次数少于10的用户之后整体准确率在73%左右。对于只用朴素贝叶斯和评分数据、没用任何内容特征比如电影类型、年龄的模型来说这个效果是可以接受的。如果你还希望继续提高准确率后面我会在优化那一节给出可落地的思路。4. 关键参数与优化细节4.1 用Combiner减少Shuffle数据量作业二的Mapper会输出大量键值对比如一个用户评了100部电影就会产生100条甚至200条输出。这些数据在Shuffle阶段要经过排序、分组、网络传输到Reducer如果数据规模再放大几倍这个开销会非常可观。Hadoop提供的Combiner机制可以在Map端先做一次局部合并减少要传输的数据量。作业二里的Combiner和Reducer逻辑其实一模一样都是对所有值做求和所以我在Driver里直接设置了job.setCombinerClass(PrefModelReducer.class);这一个操作能把Shuffle阶段的数据量减少一个量级实测下来作业耗时能缩短30%到40%。需要注意的是Combiner不是任何场景都能直接套用Reducer逻辑它必须满足交换律和结合律。像我们这种纯求和的操作没问题但如果是求平均值就不能直接这么用否则结果会出错。这是个高频面试题值得记住。4.2 数据倾斜的处理思路电影评分数据有一个天然的长尾效应《指环王》《星球大战》这种热门电影可能被上万人评分而一些冷门独立电影可能只有几个人评分。这种数据分布会让某些Reducer收到的数据量远大于其他Reducer形成数据倾斜。作业二里我们是用电影ID做分组键的一部分热门电影对应的Reducer会明显更慢。我当时的优化思路有两条第一设置合理的Combiner让Map端的局部聚合先把热门电影的统计做掉一部分减轻Reduce端压力。这个改动效果最直接。第二如果倾斜特别严重可以考虑把热门电影单独拆分出来处理或者用自定义Partitioner把可能的热点键分散到多个Reducer上最后再汇总。但课程设计阶段没有必要搞这么复杂了解原理、能说清楚处理方案就够了。4.3 自定义计数器统计先验概率朴素贝叶斯计算里需要先验概率P(男性)和P(女性)也就是全部有效评分用户中男性和女性的占比。这个数据可以在作业二里顺便统计出来不需要单独跑一个作业。Hadoop的Counter机制可以做到这一点。在Mapper里每处理一个用户ID就根据性别增加对应的CounterReducer阶段结束时读取计数器的值写入一个配置文件。这样作业二的输出目录里既有模型表又有先验概率文件作业三加载的时候一起读进来就行。代码大致是这样enum GenderCounter { MALE_TOTAL, FEMALE_TOTAL } // 在Mapper里 if (M.equals(gender)) { context.getCounter(GenderCounter.MALE_TOTAL).increment(1); } else if (F.equals(gender)) { context.getCounter(GenderCounter.FEMALE_TOTAL).increment(1); }这个做法的好处是不额外占用计算资源而且和主流程解耦。真正生产环境里这种“跑任务的过程中顺便收集元信息”的思路也很常见比如统计日志总量、异常条数等。5. 常见问题与排查实录5.1 环境与启动阶段的典型问题先整理一个排查速查表这些都是我实际踩过的坑每条都有代表性问题现象可能原因解决方案NameNode启动后DataNode自动退出data目录权限不对或/tmp目录被清理清空dfs.name.dir和dfs.data.dir目录重新format运行作业时报ExitCode: 1日志无详细错误代码里空指针或解析异常被吞掉打开日志的DEBUG级别或自己加System.err打印内存溢出OOMMap/Reduce的堆内存太小mapreduce.map.memory.mb调大同步调整容器内存输出目录已存在导致作业失败HDFS输出目录不能重复每次运行前删除输出目录或代码里自动判断删除中文电影名乱码文件编码不是UTF-8上传到HDFS前先转码或者统一用GBK读入有几个细节要特别说明。伪分布式模式下NameNode和DataNode的数据目录默认放在/tmp下而Linux系统重启时会自动清理/tmp里的文件这就导致重启后DataNode因为找不到数据目录而启动失败。解决办法是把dfs.name.dir和dfs.data.dir改到/opt/hadoop/data这样的持久化目录下。另外作业失败后不会自动覆盖已有的输出目录会直接报FileAlreadyExistsException。最简单的做法在Driver里加几行代码先判断输出路径是否存在存在就递归删除Path outputPath new Path(args[1]); FileSystem fs FileSystem.get(conf); if (fs.exists(outputPath)) { fs.delete(outputPath, true); }这个看似琐碎的问题实际跑实验的时候几乎每个人都会遇到。5.2 代码逻辑与数据关联的坑第二个容易出问题的地方是数据关联。作业二里用了DistributedCache加载users.dat的性别映射表但如果映射表里的用户ID和ratings.dat里的用户ID对不上或者加载的路径错误就会出现大量用户被跳过的情况而任务还不会报错。我一开始没加校验跑出来的模型文件特别稀疏后来仔细检查才发现是users.dat的文件路径没有加对导致setup阶段读到了空文件。排查思路是在Mapper的setup里加一个计数打印出实际加载了多少条用户记录如果映射表只有0条数据那一定是缓存文件的问题。这个习惯很重要不要只盯着Reduce的结果看要在每个阶段都输出日志来确认数据流是否正常。还有一个常见问题是split方法用错了。ratings.dat的分隔符是“::”但在Java中split方法的参数是正则表达式如果分割符号有特殊含义就需要转义。这里用“::”没有这个风险。但如果你的数据集是用竖线分隔的就必须用split(\|)这是个高发错误很多初学者在这里卡很久。5.3 模型效果不理想的调整方向如果你跑出来的准确率明显偏低比如低于60%大概率不是代码bug而是数据处理策略的问题。可以按下面几个方向排查。第一评分中性化的阈值是否合理。有同学觉得3分也应该纳入有效范围但我的实验表明把3分当作“喜欢”会让模型把一堆情感模糊的评分当成正向信号准确率反而下降。你可以在预处理阶段把3分单独剔除或者把3分视为“不喜欢”然后对比效果。第二低活跃用户过滤得不够狠。评分次数少于10次的用户行为噪声太大如果不过滤这些数据会严重干扰统计。你想啊一个只看过一部电影的人你靠一部电影的评分去猜性别跟抛硬币没区别。第三不同电影的评分人数差异巨大直接用频次作为条件概率的估计会让样本数少的电影产生很飘的估计值。一个电影只有5个人评分4个男性喜欢得出P(喜欢|男性)0.8但这个估计的置信区间非常宽。工程上可以用拉普拉斯平滑来缓解给每个频次都加上一个平滑系数α避免某些电影的条件概率变成极端的0或1。这个在代码里很好实现统计完成后加一个很小的值就行。第四如果还想进一步优化可以把电影类型特征加进来。movies.dat里的类型字段提供了Action、Comedy、Drama等类别信息完全可以作为额外的朴素贝叶斯特征。这时候需要把作业二的key从“电影ID::性别_标签”扩展成“电影ID::组合类型::性别_标签”或者单独跑一个统计电影类型和性别分布模型的作业。我当时加了电影类型特征后准确率提升了约5个百分点。写在最后我对这个项目的几点体会把整个项目做完之后我的体会是Hadoop课程设计的关键不在于模型多高级而在于能不能把“分而治之”的思想真正落地。你要能说清楚每个MapReduce作业是从哪份数据出发、通过什么转换得到什么结果并解释每一步为什么要这样设计。性别预测这个题目的妙处就在于它的业务逻辑足够简单让技术的展示空间很大你能从容地把数据清洗、关联、统计、模型训练、模型应用完整走一遍而且每一步都能用MapReduce实现。最后分享一个项目扩展的小技巧。如果你答辩时想体现更多思考可以在现有代码基础上加一个“按年龄段预测性别”的实验因为users.dat里有年龄字段。你可以对比一下不同年龄段里性别预测的准确率有没有明显差异然后分析原因这个分析过程往往比模型本身更能给你加分。数据都在同一个数据集里代码改动也很小但表达出来的深度完全不一样。本文还有配套的精品资源点击获取
返回列表