毕设推荐系统实战:DeepFM+Hadoop+Spark视频号推荐落地指南
发布时间:2026/9/26 8:19:58来源:尧图网络
简介本资源是一套完整的微信视频号大数据分析与推荐系统毕业设计项目面向计算机、大数据、人工智能方向的本科生及初入推荐系统领域的学习者解决海量用户行为数据下的精准内容分发问题。项目基于Hadoop构建分布式存储底座采用TensorFlow复现PNN与DeepFM模型集成Spark Streaming实时消费Kafka中的用户行为流如点赞、评论、完播时长并实现召回→过滤→精排三级推荐架构支持CTR点击率预估与动态模型更新。压缩包共1225个文件含16个核心Python脚本模型训练/流处理/评估、15张可视化图表特征分布、AUC曲线等、5个CSV样本数据集、多个TensorFlow模型文件.pb/.index/.data及1份详细设计文档.pdf整体大小76.41MB。已有1163人学习下载提供从环境部署、代码调试到效果验证的全流程实践材料特别适合用于课程设计、毕设参考或工业级推荐系统入门实战。1. 为什么微信视频号的推荐不能只靠协同过滤DeepFM Hadoop Spark 是毕业设计里最能“落地”的组合你手里的毕设题目不是“用 PyTorch 复现一篇顶会论文”而是要在一个真实业务场景里——微信视频号——跑通从原始日志采集、清洗、特征工程到模型训练、在线打分、结果回写全链路。协同过滤在小数据集上跑得飞快但一接入视频号级日志单日 PB 级用户行为、千万级视频、亿级用户它立刻暴露出三个硬伤冷启动卡死新视频、长尾内容无法曝光、ID 类特征如用户设备型号、城市等级、WiFi/4G 标识完全被忽略。而 DeepFM 正是为解决这类高维稀疏 ID 特征连续数值特征混合建模而生的——它把 FM 的二阶交叉能力和 DNN 的高阶非线性拟合能力拧在一起既保留可解释性FM 部分能算出“北京男青年 × 短剧”交叉权重又扛得住海量特征百万级 embedding 维度下仍可训。Hadoop 提供稳定可靠的底层存储与批处理调度尤其适合日志归档、宽表构建Spark 则承担特征实时拼接、模型分布式训练、AB 实验分流等对延迟和吞吐双敏感的任务。这不是炫技堆栈而是当前工业界中型推荐系统最主流、文档最全、调试工具最成熟、答辩老师最容易验证的技术路径。如果你的毕设目标是“让老师点开你的 demo 页面输入一个测试用户 ID立刻看到排序合理的 10 条视频推荐”那这套组合就是你唯一该选的、不翻车的基线方案。2. 搭建三件套Hadoop 伪分布式 Spark Standalone DeepFM 训练环境的最小可行配置2.1 为什么选伪分布式而非完全分布式——毕设环境的真实约束很多同学一上来就折腾 3 节点 YARN 集群结果卡在 ZooKeeper 同步失败、NodeManager 启动报错、资源抢占冲突上两周过去连hdfs dfs -ls /都跑不通。毕业设计不是生产部署核心诉求是所有模块能在一台 16GB 内存、500GB SSD 的笔记本或云服务器上稳定跑通且各环节日志可查、命令可复现、错误可定位。伪分布式Pseudo-Distributed Mode完美匹配这一需求HDFS NameNode/DataNode、YARN ResourceManager/NodeManager 全部运行在同一台机器的不同 JVM 进程中避免了多机网络配置、SSH 免密、时钟同步等干扰项Spark Standalone 模式则绕过 YARN 的复杂调度逻辑直接用start-master.sh和start-worker.sh启动主从进程资源分配透明可控DeepFM 训练用 TensorFlow 2.x 或 PyTorch 1.12 torchrec轻量版即可无需 GPU——因为毕设重点是流程闭环不是 SOTA 指标。我带过的 12 届毕设里90% 成功案例都始于这个“单机三件套”它让你把精力聚焦在数据流和特征逻辑上而不是运维玄学。2.2 Hadoop 伪分布式绕过官网坑点的 5 步初始化提示不要下载最新版 Hadoop 3.4.x毕设阶段首选 3.2.4 —— 它与 Spark 3.3.x 兼容性最好且社区文档最全。# 1. 解压并设置 JAVA_HOME必须 JDK8JDK11 在 Hadoop 3.2.x 有 ClassLoader 冲突 tar -xzf hadoop-3.2.4.tar.gz echo export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 ~/.bashrc source ~/.bashrc # 2. 修改 core-site.xml指定默认文件系统为本地 HDFS cat $HADOOP_HOME/etc/hadoop/core-site.xml EOF configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configuration EOF # 3. 修改 hdfs-site.xml关闭权限检查毕设免去 chmod 777 玄学 cat $HADOOP_HOME/etc/hadoop/hdfs-site.xml EOF configuration property namedfs.namenode.name.dir/name valuefile:/usr/local/hadoop/hadoop_data/hdfs/namenode/value /property property namedfs.datanode.data.dir/name valuefile:/usr/local/hadoop/hadoop_data/hdfs/datanode/value /property property namedfs.permissions.enabled/name valuefalse/value /property /configuration EOF # 4. 格式化 NameNode仅首次执行 $HADOOP_HOME/bin/hdfs namenode -format # 5. 启动 HDFS注意顺序先 namenode再 datanode $HADOOP_HOME/sbin/start-dfs.sh # 验证jps 应看到 NameNode、DataNode、SecondaryNameNode 进程关键参数说明dfs.permissions.enabledfalse是毕设救命开关——它禁用 HDFS 用户权限校验避免因 Linux 用户与 HDFS 用户名不一致导致Permission deniednamenode.name.dir和datanode.data.dir必须指向绝对路径且目录需提前mkdir -p创建并赋予当前用户读写权限fs.defaultFS中的localhost:9000是 HDFS RPC 端口Spark 读取 HDFS 数据时必须与此一致。2.3 Spark Standalone跳过 YARN 直接跑通特征工程的关键# 1. 下载 Spark 3.3.2与 Hadoop 3.2.4 二进制兼容 wget https://archive.apache.org/dist/spark/spark-3.3.2/spark-3.3.2-bin-hadoop3.tgz tar -xzf spark-3.3.2-bin-hadoop3.tgz # 2. 配置 workers 文件单机模式只需 localhost echo localhost $SPARK_HOME/conf/workers # 3. 启动 Master 和 Worker注意必须先启 Master $SPARK_HOME/sbin/start-master.sh $SPARK_HOME/sbin/start-worker.sh spark://localhost:7077 # 4. 验证访问 http://localhost:8080应看到 1 个 Alive Workers # 5. 提交一个 WordCount 测试确认 Spark 可读 HDFS $SPARK_HOME/bin/spark-submit \ --master spark://localhost:7077 \ --class org.apache.spark.examples.JavaWordCount \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.3.2.jar \ hdfs://localhost:9000/input/test.txt关键参数说明--master spark://localhost:7077中的7077是 Spark Master 默认端口不可与 HDFS 的9000混淆spark-examplesjar 包路径必须完整且test.txt需提前上传至 HDFShdfs dfs -put /tmp/test.txt /input/若提交后任务卡在ACCEPTED状态大概率是 Worker 内存不足——在$SPARK_HOME/conf/spark-env.sh中添加export SPARK_WORKER_MEMORY4g根据本机内存调整。2.4 DeepFM 环境用 PyTorch torchrec 构建轻量可训模型# requirements.txt精简版避免 CUDA 版本冲突 torch1.12.1cpu torchrec0.3.1 pandas1.5.3 numpy1.23.5 scikit-learn1.2.2 # 安装命令强制 CPU 版本避免显卡驱动问题 pip install torch1.12.1cpu -f https://download.pytorch.org/whl/torch_stable.html pip install torchrec0.3.1为什么不用 TensorFlowTensorFlow 2.x 的tf.keras对 SparseTensor 支持不稳定且tf.distribute在单机多进程下易触发 OOM而 PyTorch 的torchrec是 Meta 开源的推荐系统专用库内置EmbeddingBag高效处理变长 ID 特征、DenseArch/SparseArch分离稠密/稀疏特征通道、InteractionArchFM 交叉层代码行数少于 200 行即可搭出可训 DeepFM且支持torch.compile加速。毕设阶段稳定性 性能可读性 黑匣子。3. 数据流打通从微信视频号模拟日志到 DeepFM 训练样本的四步转换3.1 模拟视频号日志生成符合真实分布的 100 万条行为数据微信视频号核心日志字段包括user_id,video_id,timestamp,duration_ms,play_percent,device_type,city_level,network_type。毕设无法获取真实数据但必须模拟其统计特性play_percent呈长尾分布80% 用户只看前 10%10% 用户看完device_type中 Android 占 65%、iOS 占 35%city_level一线/新一线/二线/其他 20%/30%/25%/25%video_id服从 Zipf 分布头部 1% 视频占 50% 播放量。# generate_log.py import pandas as pd import numpy as np from faker import Faker fake Faker() # 生成 100 万条模拟日志 n_samples 1000000 data { user_id: np.random.randint(1, 1000000, n_samples), video_id: np.random.zipf(1.2, n_samples), # Zipf 分布模拟热门视频 timestamp: pd.date_range(2023-01-01, periodsn_samples, freq10S), duration_ms: np.random.choice([30000, 60000, 120000], n_samples, p[0.5, 0.3, 0.2]), play_percent: np.clip(np.random.beta(1, 3, n_samples) * 100, 0, 100), # Beta 分布模拟播放完成度 device_type: np.random.choice([Android, iOS], n_samples, p[0.65, 0.35]), city_level: np.random.choice([一线, 新一线, 二线, 其他], n_samples, p[0.2, 0.3, 0.25, 0.25]), network_type: np.random.choice([WiFi, 4G, 5G], n_samples, p[0.4, 0.4, 0.2]) } df pd.DataFrame(data) df.to_csv(video_log.csv, indexFalse, encodingutf-8) print(✅ 模拟日志生成完成video_log.csv)逻辑说明np.random.zipf(1.2)控制幂律分布陡峭度1.2 比标准值 1.0 更贴近视频号头部集中现象np.random.beta(1,3)生成左偏分布确保play_percent多数集中在 0–30% 区间符合短视频“划走率高”特性所有字段类型与真实日志对齐为后续 Spark SQL 解析铺路。3.2 Spark 清洗与宽表构建用 DataFrame API 替代 MapReduce# spark_etl.py from pyspark.sql import SparkSession from pyspark.sql.functions import * from pyspark.sql.types import * # 初始化 SparkSession连接本地 HDFS spark SparkSession.builder \ .appName(VideoLogETL) \ .master(spark://localhost:7077) \ .config(spark.sql.adaptive.enabled, true) \ .getOrCreate() # 1. 读取 CSV 日志注意schema 显式声明避免类型推断错误 schema StructType([ StructField(user_id, IntegerType(), True), StructField(video_id, IntegerType(), True), StructField(timestamp, StringType(), True), StructField(duration_ms, IntegerType(), True), StructField(play_percent, DoubleType(), True), StructField(device_type, StringType(), True), StructField(city_level, StringType(), True), StructField(network_type, StringType(), True) ]) log_df spark.read.csv(video_log.csv, headerTrue, schemaschema) # 2. 清洗过滤无效播放播放时长 1s 或完成度 1% clean_df log_df.filter( (col(duration_ms) 1000) (col(play_percent) 1.0) ) # 3. 构建用户-视频宽表聚合用户历史行为最近 3 天内播放次数、平均完成度 window_spec Window.partitionBy(user_id).orderBy(col(timestamp).desc()) user_stats clean_df \ .withColumn(rank, row_number().over(window_spec)) \ .filter(col(rank) 3) \ .groupBy(user_id) \ .agg( count(*).alias(play_count_3d), mean(play_percent).alias(avg_play_percent_3d), collect_list(video_id).alias(recent_videos) ) # 4. 关联视频元信息模拟 video_meta 表 video_meta spark.createDataFrame([ (1, 短剧, 情感, 120), (2, 知识, 科技, 300), (3, 生活, 美食, 180), # ... 实际需生成 10 万条此处简化 ], [video_id, category, tag, duration_sec]) wide_table clean_df.join(video_meta, video_id, left) \ .join(user_stats, user_id, left) \ .select( user_id, video_id, category, tag, device_type, city_level, network_type, play_percent, play_count_3d, avg_play_percent_3d ) # 5. 写入 HDFS 供 DeepFM 训练读取 wide_table.write.mode(overwrite).parquet(hdfs://localhost:9000/data/wide_table) print(✅ 宽表已写入 HDFShdfs://localhost:9000/data/wide_table)参数说明spark.sql.adaptive.enabledtrue启用 Spark 3.2 自适应查询优化自动调整 shuffle partitions避免小文件爆炸collect_list(video_id)生成用户近期观看序列后续可用于构建序列特征如 last_clicked_videoparquet格式比 CSV 节省 70% 存储空间且支持列裁剪DeepFM 训练时只读取所需字段。3.3 DeepFM 特征编码将宽表转为模型可接受的 sparse/dense tensor# feature_engineering.py import torch import pandas as pd from torchrec.datasets.utils import Batch from torchrec.sparse.jagged_tensor import KeyedJaggedTensor # 1. 从 HDFS 读取 Parquet使用 pyarrow s3fs 模拟 HDFS 访问 # 实际部署时替换为 spark.read.parquet(hdfs://...) df pd.read_parquet(data/wide_table, enginepyarrow) # 2. 定义特征映射字典毕设阶段手动构建生产环境用 Spark ML StringIndexer feature_maps {} for col in [device_type, city_level, network_type, category, tag]: unique_vals df[col].dropna().unique() feature_maps[col] {val: idx for idx, val in enumerate(unique_vals)} # 3. 构建 sparse featuresID 类特征 sparse_features [] for col in [device_type, city_level, network_type, category, tag]: indices [feature_maps[col].get(x, 0) for x in df[col].fillna(unknown)] sparse_features.append(torch.tensor(indices, dtypetorch.long)) # 4. 构建 dense features数值类特征 dense_features torch.tensor( df[[play_percent, play_count_3d, avg_play_percent_3d]].fillna(0).values, dtypetorch.float32 ) # 5. 构建 label播放完成度 50% 视为正样本 labels (df[play_percent] 50).astype(int).values # 6. 封装为 KeyedJaggedTensortorchrec 标准输入格式 kjt KeyedJaggedTensor.from_lengths_sync( keys[device_type, city_level, network_type, category, tag], valuestorch.cat(sparse_features), lengthstorch.tensor([len(x) for x in sparse_features] * len(df)) ) print(f✅ 特征编码完成{len(df)} 条样本{kjt._num JaggedDims()} sparse dims)关键逻辑KeyedJaggedTensor是 torchrec 的核心数据结构它把变长 ID 特征如用户历史点击序列压缩成紧凑张量避免 padding 浪费内存feature_maps手动构建是毕设最优解——Spark 的StringIndexer在单机环境下易因内存不足失败且索引映射需保存供线上推理复用play_percent 50作为 label 是业务可解释的强信号比单纯用play_percent回归更稳定避免长尾噪声干扰。4. 模型训练与评估DeepFM 的 3 个必调参数与离线指标陷阱4.1 DeepFM 模型定义用 torchrec 实现不到 150 行的可训架构# deepfm_model.py import torch import torch.nn as nn from torchrec.modules.embedding_configs import EmbeddingBagConfig from torchrec.modules.embedding_modules import EmbeddingBagCollection from torchrec.modules.interaction_modules import DotInteraction, InteractionV2 class DeepFM(nn.Module): def __init__(self, vocab_sizes, embedding_dim16, dense_dim128, num_dense_layers2): super().__init__() # 1. EmbeddingBagCollection处理稀疏特征 self.ebc EmbeddingBagCollection( tables[ EmbeddingBagConfig( namef{name}_emb, embedding_dimembedding_dim, num_embeddingssize, feature_names[name] ) for name, size in vocab_sizes.items() ], devicetorch.device(cpu) ) # 2. FM 交互层计算所有特征两两交叉 self.interaction DotInteraction() # 3. DNN 部分处理高阶非线性 layers [] input_dim len(vocab_sizes) * embedding_dim for _ in range(num_dense_layers): layers.extend([ nn.Linear(input_dim, dense_dim), nn.ReLU(), nn.Dropout(0.2) ]) input_dim dense_dim self.dnn nn.Sequential(*layers) # 4. 输出层 self.prediction nn.Linear(dense_dim len(vocab_sizes) 1, 1) # 1 for bias def forward(self, kjt, dense_features): # Embedding lookup embeddings self.ebc(kjt) # FM part: linear interaction linear_out torch.sum(embeddings.values(), dim1) # sum of embeddings interaction_out self.interaction(embeddings) # pairwise dot product # DNN part dnn_out self.dnn(embeddings.values().flatten(1)) # Concatenate and predict combined torch.cat([linear_out, interaction_out, dnn_out], dim1) return torch.sigmoid(self.prediction(combined)).squeeze(-1)参数说明embedding_dim16ID 类特征 embedding 维度16 是毕设平衡效果与内存的黄金值8 维度损失表达力32 维度单机易 OOMdense_dim128DNN 隐藏层维度128 足够拟合视频号行为模式再大无显著提升num_dense_layers2两层 DNN 已足够捕获高阶交互三层以上易过拟合且训练慢。4.2 训练循环用 DataLoader AdamW 实现稳定收敛# train.py from torch.utils.data import Dataset, DataLoader from sklearn.metrics import roc_auc_score, log_loss class VideoDataset(Dataset): def __init__(self, sparse_features, dense_features, labels): self.sparse_features sparse_features self.dense_features dense_features self.labels labels def __len__(self): return len(self.labels) def __getitem__(self, idx): return ( self.sparse_features[idx], self.dense_features[idx], self.labels[idx] ) # 初始化数据集与 DataLoader dataset VideoDataset(sparse_features, dense_features, labels) dataloader DataLoader(dataset, batch_size1024, shuffleTrue, num_workers2) # 初始化模型与优化器 model DeepFM(vocab_sizes{device_type: 2, city_level: 4, network_type: 3, category: 10, tag: 50}) optimizer torch.optim.AdamW(model.parameters(), lr0.001, weight_decay1e-5) # 训练循环 for epoch in range(10): model.train() total_loss 0 for sparse_batch, dense_batch, label_batch in dataloader: optimizer.zero_grad() pred model(sparse_batch, dense_batch) loss torch.nn.functional.binary_cross_entropy(pred, label_batch.float()) loss.backward() optimizer.step() total_loss loss.item() # 验证 model.eval() with torch.no_grad(): preds [] trues [] for sparse_batch, dense_batch, label_batch in dataloader: pred model(sparse_batch, dense_batch) preds.extend(pred.cpu().numpy()) trues.extend(label_batch.numpy()) auc roc_auc_score(trues, preds) print(fEpoch {epoch1}: Loss{total_loss/len(dataloader):.4f}, AUC{auc:.4f})避坑 / 常见问题 / 排查现象训练 loss 不下降AUC 停在 0.5随机水平原因vocab_sizes中某个特征如tag的类别数远大于实际数据中出现的数量导致 embedding 初始化后未被更新解决打印feature_maps[tag]的长度确保与vocab_sizes[tag]一致或改用len(set(df[tag]))动态计算现象CUDA out of memory即使设置了devicecpu原因PyTorch 默认缓存 GPU 内存即使没用 GPU 也会占用解决在脚本开头添加import os; os.environ[CUDA_VISIBLE_DEVICES] 现象KeyedJaggedTensor构造时报lengths与values长度不匹配原因lengths应为每个样本的特征长度如用户点击了 5 个视频则 lengths[5]而非总长度解决用torch.tensor([len(x) for x in sparse_features])而非torch.tensor([len(sparse_features)] * len(df))现象AUC 在验证集上飙升到 0.99但实际推荐效果差原因label 构造错误——用play_percent 50时未排除play_percent 0的无效样本导致正负样本比例失衡解决清洗时增加filter(col(play_percent) 0)且 label 用(df[play_percent] 50).astype(int)前确保play_percent无 NaN现象模型预测全部输出 0.5原因torch.sigmoid前的self.prediction层未初始化或 learning rate 过小解决检查self.prediction.weight是否全零将lr从0.001提升至0.01并加torch.nn.init.xavier_normal_(self.prediction.weight)5. 线上服务与效果验证用 Flask 搭建最小推荐 API 与 AB 实验设计5.1 Flask 推理服务把训练好的 DeepFM 模型封装成 HTTP 接口# app.py from flask import Flask, request, jsonify import torch import joblib import numpy as np app Flask(__name__) # 加载模型与特征映射 model torch.load(deepfm_model.pth, map_locationtorch.device(cpu)) model.eval() feature_maps joblib.load(feature_maps.pkl) app.route(/recommend, methods[POST]) def recommend(): data request.json user_id data[user_id] candidate_videos data[video_ids] # list of video_id # 构建特征模拟 Spark 宽表查询逻辑 # 实际部署时应从 Redis/HBase 查询用户画像、从 Hive 查询视频元信息 features [] for vid in candidate_videos: # 简化固定填充 device_typeAndroid, city_level一线... sparse_vec [ feature_maps[device_type].get(Android, 0), feature_maps[city_level].get(一线, 0), feature_maps[network_type].get(WiFi, 0), feature_maps[category].get(短剧, 0), feature_maps[tag].get(情感, 0) ] dense_vec [50.0, 3.0, 65.0] # play_percent, play_count_3d, avg_play_percent_3d # 转 tensor sparse_tensor torch.tensor(sparse_vec, dtypetorch.long) dense_tensor torch.tensor(dense_vec, dtypetorch.float32) # 模型预测 with torch.no_grad(): score model(sparse_tensor.unsqueeze(0), dense_tensor.unsqueeze(0)).item() features.append({video_id: vid, score: score}) # 按 score 降序排列 features.sort(keylambda x: x[score], reverseTrue) return jsonify({recommendations: features[:10]}) if __name__ __main__: app.run(host0.0.0.0, port5000)关键设计点map_locationtorch.device(cpu)确保模型在无 GPU 环境下加载model.eval()关闭 dropout 和 batch norm保证推理一致性特征构造逻辑与训练时严格对齐feature_maps必须用同一份 pickle 文件返回 top-10 而非全量排序降低响应延迟。5.2 AB 实验框架用 Spark SQL 实现流量分桶与效果归因微信视频号 AB 实验核心是同一用户在不同实验组看到的推荐结果必须稳定sticky assignment且曝光、点击、完播事件需关联到对应实验组。毕设无需复杂分流系统用 Spark SQL MD5 哈希即可实现-- 1. 为每个 user_id 分配实验组0: control, 1: deepfm SELECT user_id, CAST(ABS(HASH(user_id)) % 100 AS INT) % 2 AS group_id, CASE WHEN CAST(ABS(HASH(user_id)) % 100 AS INT) % 2 0 THEN control ELSE deepfm END AS group_name FROM user_table -- 结果存入 Hive 表 experiment_assignment -- 2. 关联曝光日志与实验组 SELECT e.group_name, COUNT(*) AS impression_cnt, SUM(CASE WHEN l.play_percent 50 THEN 1 ELSE 0 END) AS complete_cnt, SUM(CASE WHEN l.play_percent 50 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS completion_rate FROM exposure_log l JOIN experiment_assignment e ON l.user_id e.user_id GROUP BY e.group_name为什么用HASH(user_id) % 100 % 2HASH(user_id)保证相同 user_id 每次哈希结果一致实现 sticky% 100引入扰动避免user_id末位数字规律导致分组倾斜最终% 2得到 0/1 二分组控制组与实验组流量均等。5.3 效果验证不止看 AUC还要盯住三个业务指标指标计算方式毕设合格线为什么重要完播率提升实验组完播率 / 对照组完播率≥ 1.055%直接反映用户是否愿意看完是视频号核心 KPI人均播放时长实验组总播放时长 / 实验组 UV≥ 对照组 15s避免模型只推“短平快”内容牺牲内容深度新视频曝光占比实验组中发布 24h 的视频曝光量 / 总曝光量≥ 15%检验冷启动能力防止模型只推历史热门注意毕设答辩时老师最常问的是“你的模型到底带来了什么业务价值”——拿出这三张表比展示 AUC0.82 有力十倍。5.4 毕设交付物清单让答辩老师一眼看懂你的工作量文件名格式说明验证方式hadoop_pseudo_setup.mdMarkdownHadoop 伪分布式安装步骤与截图jps命令输出、hdfs dfs -ls /截图spark_etl.pyPython宽表构建完整代码含注释提交到 Spark UI 查看 DAG 图deepfm_train.ipynbJupyter Notebook模型定义、训练、验证全过程运行后显示 AUC 曲线图flask_api.pyPython推理服务代码curl -X POST http://localhost:5000/recommend -d {user_id:1,video_ids:[1,2,3]}返回 JSONab_experiment_report.pdfPDFAB 实验结果截图完播率、人均时长、新视频占比Spark SQL 查询结果导出表格我带毕设十年最深的教训是永远先跑通 end-to-end 流程再优化单点指标。曾有个学生花三周调参把 AUC 从 0.78 提到 0.83结果答辩时发现 Flask 服务根本没启动API 404——老师一句“你这推荐系统连首页都打不开怎么证明有效”直接终结答辩。所以我的习惯是第一天就写好app.py第二天跑通curl请求第三天把 Spark ETL 接进去第四天喂进 DeepFM 训出第一个 AUC……让每一步产出都可演示、可截图、可解释。技术细节可以补但“系统能跑”是毕设生死线。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网