简介:这份教案面向大数据技术类相关专业师生,围绕《Hadoop大数据开发基础》第6章项目案例展开,帮助读者在真实场景中掌握KNN分类算法与MapReduce分布式编程的结合应用。内容涵盖KNN算法原理与实现步骤、MapReduce编程逻辑、分类算法评价指标,以及电影网站用户性别预测的完整项目流程,包括数据预处理、模型建立、结果评价与K值寻优,并配有引导性、探究性和拓展性问题供课堂讨论。资源包为1个PDF文件,约24KB,结构紧凑,适合作为48学时课程中9学时的实验教学参考。目前已有1078人学习下载,读者可借此理清MapReduce连接多份数据、清洗缺失值与异常值、划分训练验证测试集、实现KNN分类模型及评价分类效果的关键思路,适合需要将理论落地为项目实践的大数据学习者。
1. 电影网站用户性别预测:从 Hadoop 伪分布式到离线特征工程的一条完整链路
电影网站的用户画像里,性别是最常被拿来做推荐冷启动的字段之一。注册表单里那一栏很多人不填,或者随手选一个,于是运营侧就会问:能不能靠行为日志把性别补出来?这个标题讲的正是这件事——用 Hadoop 生态做一套离线批处理,把电影网站的用户行为日志清洗成特征,再跑一个性别预测模型。它解决的不是"模型多准",而是"数据从哪来、怎么在集群上跑通、特征怎么落盘"。
适合谁看:正在做 hadoop 课程设计、需要交一份能跑通的电影网站数据分析作业的学生;以及刚接手离线数仓、要拿一个真实场景练手的初中级大数据开发。整套东西在单机伪分布式上就能跑,不需要真集群,但流程和集群版一致,后面换到多节点只是改配置。下面按"环境 → 数据 → 特征 → 模型 → 避坑"的顺序拆开讲。
2. 伪分布式环境与电影网站日志的落地准备
2.1 为什么先搭伪分布式而不是直接上集群
很多人一上来就想搞 hadoop 集群搭建,三台虚拟机、免密、HA 全配一遍,结果卡在 DataNode 起不来,三天没写一行分析代码。我的建议是先用伪分布式把逻辑跑通:NameNode、DataNode、ResourceManager、NodeManager 全在一台机器上,HDFS 和 YARN 的行为和真集群几乎一致,唯一区别是副本数和并发度。等代码稳定了,再把core-site.xml、hdfs-site.xml里的地址换成真集群的,业务代码一行不用改。
伪分布式对内存的要求不高,4GB 内存的虚拟机就能跑,但要把 YARN 的容器内存调小,否则一个任务申请 2GB 直接卡死。下面这套配置是我在 4GB 虚拟机上反复验证过的。
2.2 最小可用的伪分布式配置
先确认 JDK 和 SSH 本机免密,这两步几乎所有 hadoop 安装与配置教程都会讲,这里只给关键命令:
# 本机免密,伪分布式也需要,否则 start-dfs.sh 会反复要密码 ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost "echo ok" # 能打印 ok 才算通core-site.xml只改两处,fs.defaultFS指向本机 9000 端口,hadoop.tmp.dir指到一个磁盘空间够的目录:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/module/hadoop/data/tmp</value> </property> </configuration>hdfs-site.xml把副本数设成 1,伪分布式只有一个 DataNode,设 3 会一直报副本不足:
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration>yarn-site.xml里最关键的是把容器内存压下来,否则 4GB 机器跑不动 MapReduce:
<configuration> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>2048</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>1024</value> </property> </configuration>参数说明:resource-memory-mb是 NodeManager 能分给容器的总内存,maximum-allocation-mb是单个容器上限。两个值要满足"单容器上限 ≤ 总内存",否则提交任务时直接抛InvalidResourceRequestException。格式化并启动:
hdfs namenode -format # 只在第一次执行,重复格式化会丢数据 start-dfs.sh start-yarn.sh jps # 应看到 NameNode/DataNode/ResourceManager/NodeManagerjps里少进程是新手最常见的翻车点,先看日志目录logs/下对应.log文件的最后几十行,八成是端口占用或内存不足。
2.3 电影网站日志的字段设计
电影网站的行为日志一般有三类:用户表(user_id、注册性别、年龄)、评分表(user_id、movie_id、rating、timestamp)、浏览表(user_id、movie_id、停留时长、点击次数)。性别预测的标签来自用户表里"填了性别"的那部分人,特征来自评分和浏览行为。把这三份数据放到 HDFS 上:
hdfs dfs -mkdir -p /movie/input hdfs dfs -put users.csv /movie/input/ hdfs dfs -put ratings.csv /movie/input/ hdfs dfs -put views.csv /movie/input/ hdfs dfs -ls /movie/input常见做法是把原始 CSV 先落到 HDFS,再用 Hive 建外部表指向它,这样清洗逻辑用 SQL 写,比手写 MapReduce 快得多。如果你的环境里还没装 Hive,也可以先用 MapReduce 或 Spark 读 CSV,但特征聚合这一步用 SQL 会省很多事。
3. 用 Hive 做行为特征聚合:把日志变成性别可用的向量
3.1 特征到底该取哪些
性别预测不是靠单条记录,而是靠"这个用户长期偏好什么"。经验上区分度最高的几类特征:评分均值(女性用户打分普遍偏高)、评分方差(男性用户两极分化更明显)、高分电影的类型分布(爱情/动作占比)、活跃时段(深夜活跃比例)、浏览停留时长。这些都能从评分表和浏览表聚合出来,按 user_id 分组,一行一个用户。
标签处理上有个坑:用户表里性别字段可能是"男/女/M/F/1/0/未知"混着来,必须先归一化,把未知的整行丢掉,否则训练集里混进噪声标签,模型学出来的东西没法解释。
3.2 建外部表并清洗标签
-- 用户表:把性别归一化成 0/1,未知的过滤掉 CREATE EXTERNAL TABLE ods_users ( user_id STRING, gender STRING, age INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE LOCATION '/movie/input/users.csv'; -- 清洗后落到明细表,只保留性别明确的用户 CREATE TABLE dwd_user_label AS SELECT user_id, CASE WHEN lower(trim(gender)) IN ('m','male','男','1') THEN 1 WHEN lower(trim(gender)) IN ('f','female','女','0') THEN 0 ELSE NULL END AS label FROM ods_users WHERE lower(trim(gender)) IN ('m','male','男','1','f','female','女','0');逻辑说明:CASE WHEN把各种写法统一成 1(男)和 0(女),WHERE子句把无法识别的行直接排除。参数上注意trim和lower要一起用,日志里经常有" Male "这种带空格和大小写混杂的值,只做其中一个会漏。
3.3 聚合评分与浏览特征
-- 评分特征:均值、方差、评分数 CREATE TABLE dwd_rating_feat AS SELECT user_id, count(*) AS rating_cnt, avg(rating) AS rating_avg, variance(rating) AS rating_var, max(rating) - min(rating) AS rating_range FROM ods_ratings GROUP BY user_id; -- 浏览特征:总停留、平均停留、深夜活跃比例 CREATE TABLE dwd_view_feat AS SELECT user_id, sum(duration) AS view_total, avg(duration) AS view_avg, sum(CASE WHEN hour(ts) >= 23 OR hour(ts) < 5 THEN 1 ELSE 0 END) / count(*) AS night_ratio FROM ods_views GROUP BY user_id;参数说明:variance是 Hive 内置聚合函数,直接给方差,不用自己算平方和。night_ratio用整数除法在部分 Hive 版本里会截断成 0,稳妥写法是乘 1.0 转浮点:sum(...) * 1.0 / count(*)。这一步跑完,把三张表按 user_id join 起来就是训练宽表:
CREATE TABLE dws_user_feature AS SELECT l.user_id, l.label, r.rating_cnt, r.rating_avg, r.rating_var, r.rating_range, v.view_total, v.view_avg, v.night_ratio FROM dwd_user_label l LEFT JOIN dwd_rating_feat r ON l.user_id = r.user_id LEFT JOIN dwd_view_feat v ON l.user_id = v.user_id;用LEFT JOIN而不是JOIN,是因为有些用户只浏览不评分,内连接会把他们丢掉,样本量白白少一截。join 完把结果导出到本地或 HDFS,供后面建模用:
hive -e "SELECT * FROM dws_user_feature" > user_feature.tsv4. 训练性别预测模型:从宽表到可解释的基线
4.1 为什么先做逻辑回归而不是直接上深度模型
性别预测这种二分类,特征就十来个,样本量通常几万到几十万,逻辑回归的准确率往往和复杂模型差不了几个点,但可解释性强——你能直接看系数说"评分均值越高越可能是女性"。先跑一个基线,知道数据本身能到多少分,再决定要不要上 XGBoost 或神经网络。很多课程设计翻车就翻在直接上深度模型,调参调到怀疑人生,最后连基线都没跑出来。
4.2 用 pandas + sklearn 跑通基线
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report # 读 Hive 导出的宽表,缺失值用 0 填充(未评分用户) df = pd.read_csv("user_feature.tsv", sep="\t") df = df.fillna(0) feat_cols = ["rating_cnt", "rating_avg", "rating_var", "rating_range", "view_total", "view_avg", "night_ratio"] X = df[feat_cols].values y = df["label"].values # 标准化:逻辑回归对量纲敏感,view_total 动辄上万,不缩放会压过其他特征 scaler = StandardScaler() X = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y) clf = LogisticRegression(max_iter=1000, class_weight="balanced") clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))逻辑说明:fillna(0)处理未评分/未浏览用户,StandardScaler做标准化,stratify=y保证训练测试集里男女比例一致,class_weight="balanced"应对性别样本不均衡。参数上max_iter默认 100 在特征多时经常不收敛,调到 1000 稳妥。跑完看classification_report,如果某一类 recall 特别低,多半是样本不均衡,先回去查数据分布,别急着换模型。
4.3 特征重要性的快速验证
逻辑回归的系数可以直接看方向:
for name, coef in zip(feat_cols, clf.coef_[0]): print(f"{name:15s} {coef:+.3f}")系数为正表示该特征越大越偏向 label=1。如果night_ratio系数接近 0,说明深夜活跃对性别没区分度,可以从特征里删掉,减少过拟合。这一步是很多人跳过的,但它能帮你判断特征工程到底有没有用。
5. 避坑与排查:伪分布式跑电影日志最容易翻的五个地方
5.1 现象:任务卡在 ACCEPTED 不动
原因:YARN 容器内存配置超过 NodeManager 可用内存,或者yarn.nodemanager.resource.memory-mb设得比物理内存还大。解决:把maximum-allocation-mb调到 1024 以下,resource-memory-mb不超过物理内存的 70%,重启 YARN 再提交。
5.2 现象:Hive 查询报错 "Vertex failed" 或内存溢出
原因:默认 MapReduce 任务内存不够,join 大表时尤其明显。解决:提交前设置set mapreduce.map.memory.mb=1024; set mapreduce.reduce.memory.mb=1024;,或者改用hive.auto.convert.join=true让小表走 MapJoin。
5.3 现象:性别标签归一化后样本只剩一半
原因:原始日志里性别字段大量为空或写成"保密""未知",WHERE过滤把这些人全丢了。解决:先统计各取值的数量再决定过滤规则,如果明确标签太少,考虑用半监督或只对有标签用户建模,别硬凑。
5.4 现象:模型准确率 90% 但预测全是同一类
原因:性别样本严重不均衡,模型学会了"全猜多数类"。解决:看classification_report里少数类的 recall,用class_weight="balanced"或对少数类过采样,别只看 accuracy。
5.5 现象:重复格式化 NameNode 后数据全没了
原因:hdfs namenode -format会重建元数据,原 DataNode 上的块对不上。解决:格式化只在首次安装时做一次,之后要重置就先把dfs.namenode.name.dir和dfs.datanode.data.dir目录清空再格式化,别在跑着数据的集群上随手执行。
6. 把单机流程搬到集群:几个真正省时间的技巧
伪分布式跑通之后,往真集群迁移时,业务代码基本不用动,要改的是配置和资源申请。我一般会先把core-site.xml里的fs.defaultFS换成集群 NameNode 地址,hdfs-site.xml副本数改回 3,然后重点调 YARN 的资源队列。集群上最容易忽略的是数据本地性:如果 HDFS 块和计算节点不在同一台机器,任务会走网络拉数据,速度差好几倍。提交任务时用mapreduce.job.reduce.slowstart.completedmaps=0.8让 reduce 早点启动,能省不少等待时间。
验证迁移是否成功,不要只看任务成功,要对比单机和集群跑出来的dws_user_feature行数和几个聚合值是否一致。我吃过一次亏:集群上 Hive 的variance因为版本差异返回了不同精度,导致特征分布偏移,模型准确率掉了三个点,查了半天才发现是函数行为不一致。所以跨环境一定要做数据对账,别信"任务成功就是对的"。
最后一个习惯:每次改完配置或 SQL,先把中间表count(*)和几个关键字段的min/max/avg打出来存一份,出问题时能快速定位是哪一步开始偏的。这套流程不复杂,难的是每一步都留下可对比的痕迹。希望帮到你。
本文还有配套的精品资源,点击获取