简介:本资源是一套面向计算机专业本科生的毕业设计实战项目,聚焦深度学习在音乐推荐领域的应用实践,适用于正在完成大作业、毕业设计或寻求AI项目练手的学习者。项目包含完整可运行的Python源码、SQLite3数据库及答辩用PPT,所有代码均经本地编译调试通过,评审得分98分,内容难度适中且经助教审定,覆盖数据预处理、特征提取、模型训练(含CNN/LSTM等典型结构)与推荐结果可视化等核心环节。压缩包共155个文件,约109.75MB,含18个Python主程序文件、27张界面与效果截图、18个JS/CSS前端交互资源、10个HTML页面及1个.ipynb实验笔记,支撑前后端协同演示;另有字体、图标、配置文件等辅助资源,保障开箱即用。目前已有91人下载学习,配套资源结构清晰、模块分离明确,便于理解推荐系统整体架构与工程落地细节。
1. 毕业设计能跑通的音乐推荐系统:不是调包Demo,是带数据库、可训练、能答辩的真实Python深度学习项目
你是不是也经历过——在知网搜“音乐推荐 毕业设计”,点开十篇论文,摘要写得天花乱坠:“融合注意力机制的多模态协同过滤模型”“基于图神经网络的用户-歌曲异构关系建模”,结果翻到附录,只有一页伪代码,或者一个连requirements.txt都没有的GitHub空仓?答辩前一周,导师问“你这个模型在真实数据上跑过吗?冷启动怎么处理?特征工程具体做了哪些归一化?”,你卡壳了。这不是理论课作业,这是要部署到本地、能导出推荐结果、能截图进PPT、能回答老师追问的毕业设计实体系统。本资源就是为这个场景而生:它不是教学Demo,而是完整闭环——从原始CSV音乐元数据(含歌手、流派、时长、BPM、情绪标签)出发,用PyTorch构建双塔召回+GRU序列建模+MLP精排三层结构,训练后存入SQLite数据库,前端用Flask提供/user/123/recommend接口,PPT里每张架构图都有对应源码模块,连“用户行为日志模拟脚本”和“数据库初始化SQL”都打包好了。适合计算机/软件工程专业大四学生,尤其适合没接触过真实推荐链路、但学过Python基础和《动手学深度学习》前五章的同学。别再被“基于深度学习”这种标题唬住——这次,你真能把模型跑起来,把推荐列表打印出来,把loss曲线画出来。
2. 为什么选双塔+GRU+MLP组合:避开纯协同过滤的冷启动陷阱,也绕开端到端音频CNN的算力黑洞
2.1 音乐推荐的三个现实约束:数据稀疏、特征高维、部署轻量
做毕业设计最怕什么?不是模型不酷,而是跑不动、训不出、讲不清。我们先直面音乐推荐的硬约束:
第一,数据稀疏性。公开数据集如Last.fm-1K,平均每个用户只听过30首歌,远低于电商百万级交互。纯矩阵分解(MF)或NeuMF在稀疏场景下泛化差,容易过拟合噪声;
第二,特征维度爆炸。若直接用librosa提取40维梅尔频谱+13维MFCC+节奏特征,一首歌就是(128, 1000)的时频图,CNN训练需GPU显存≥12GB——而你实验室服务器大概率是GTX 1060(6GB)。
第三,答辩演示需求。老师要看“输入用户ID,输出5首推荐歌名+置信度”,不是“Loss从0.87降到0.42”。系统必须有明确输入输出边界,不能是黑匣子音频流。
提示:本项目放弃端到端音频建模,转而利用结构化元数据+用户行为序列,这是工业界中小团队的主流做法(参考Spotify的Echo Nest早期方案),也是毕业设计最可控的技术路径。
2.2 双塔召回层:用用户画像与歌曲特征解耦,解决冷启动第一步
双塔结构(User Tower + Item Tower)的核心价值,在于把用户侧和物品侧特征分别编码,再用内积计算相似度。这带来两个毕业设计刚需优势:
- 冷启动友好:新用户只有注册信息(年龄、地区、偏好流派),可直接输入User Tower生成向量;新歌曲只有元数据(BPM、情绪标签、发行年份),也能进Item Tower。无需等待交互数据积累。
- 检索高效:训练完Item Tower,可对全库歌曲向量做FAISS索引,10万首歌毫秒级召回Top-100。答辩演示时,
curl http://localhost:5000/user/999/recommend响应时间<300ms。
本项目User Tower输入维度为12:[age, region_id, pop_preference, rock_preference, ...](经LabelEncoder处理);Item Tower输入维度为9:[bpm, duration_sec, valence, energy, danceability, year, genre_id, artist_popularity, release_quarter]。两塔均采用3层MLP(128→64→32),输出32维向量。关键参数在config.py中:
# config.py 关键配置 RECALL_MODEL = { "user_tower": { "input_dim": 12, "hidden_dims": [128, 64, 32], "dropout": 0.3 }, "item_tower": { "input_dim": 9, "hidden_dims": [128, 64, 32], "dropout": 0.2 } }注意dropout值差异:User Tower设为0.3因用户特征更易过拟合(如地区ID分布极不均衡),Item Tower设为0.2因歌曲元数据相对稳定。这个细节在答辩时能体现你对数据分布的理解,不是盲目抄参数。
2.3 GRU序列建模层:捕捉用户听歌行为的时间动态性
召回层解决“找什么”,但无法回答“为什么推荐这首”。比如用户连续听了3首慢节奏爵士,第4首推荐快节奏摇滚就违和。本项目用GRU建模用户最近10次听歌序列(按时间戳排序),输入为歌曲ID嵌入向量(64维),输出最后时刻隐藏状态作为用户短期兴趣表征。
为什么不用LSTM?实测在本数据规模下GRU收敛更快(epoch 15即稳定),且参数少15%,更适合单卡训练。序列长度固定为10,不足补零(zero-padding),超长则截断——这在data_loader.py的UserSequenceDataset类中实现:
# data_loader.py 片段 class UserSequenceDataset(Dataset): def __init__(self, user_seq_df, song_emb_dict, max_len=10): self.user_seq_df = user_seq_df self.song_emb_dict = song_emb_dict # {song_id: np.array(64,)} self.max_len = max_len def __getitem__(self, idx): seq = self.user_seq_df.iloc[idx]['song_ids'] # list of int # 截断或补零 if len(seq) > self.max_len: seq = seq[-self.max_len:] # 取最近10次 else: seq = [0] * (self.max_len - len(seq)) + seq # 前补零 # 转为嵌入向量序列 (10, 64) emb_seq = np.array([self.song_emb_dict.get(s, np.zeros(64)) for s in seq]) return torch.FloatTensor(emb_seq), torch.LongTensor([self.user_seq_df.iloc[idx]['target_song']])这里有个血泪经验:补零位置必须在序列前端(而非尾部)。因为GRU是按时间步顺序处理,前端补零让模型聚焦于真实行为(后10位),若尾部补零,模型会把零向量当有效输入,导致注意力漂移。我在初版调试时发现AUC掉0.08,排查三天才定位到这个细节。
2.4 MLP精排层:融合多源信号,输出可解释的推荐分数
召回层输出100首候选歌,精排层要从中选出Top-5。本项目精排输入包含四组特征:
- 用户长期兴趣(User Tower输出的32维向量)
- 用户短期兴趣(GRU最后隐藏状态,32维)
- 候选歌曲特征(Item Tower输出,32维)
- 用户-歌曲交叉特征(如:用户偏好流派与歌曲流派是否匹配、用户平均BPM与歌曲BPM差值)
这四组拼接成128维向量,输入3层MLP(128→64→32→1),Sigmoid激活输出0~1的推荐分。关键设计在于交叉特征手工构造:
| 交叉特征 | 计算方式 | 业务意义 |
|---|---|---|
genre_match | 1 if user_genre == item_genre else 0 | 流派一致性是音乐推荐强信号 |
bpm_diff_norm | abs(user_avg_bpm - item_bpm) / 200 | BPM差异过大影响听感连贯性 |
release_year_diff | abs(user_fav_decade - item_decade) / 10 | 年代偏好(如90后偏爱2010s) |
这些特征不依赖深度网络自动学习,而是基于音乐领域知识显式编码,在答辩时你能清晰解释“为什么这个特征重要”,比单纯说“模型自动学到了”更有说服力。
3. 数据库设计与初始化:SQLite轻量方案,避免MySQL环境配置翻车
3.1 为什么用SQLite而不是MySQL/PostgreSQL?
毕业设计答辩环境千奇百怪:有的同学用学校机房Win7系统(不支持Docker),有的用Mac M1芯片(MySQL官方包兼容性差),还有的导师要求“所有依赖一键安装”。SQLite完美规避这些问题:
- 零配置:Python内置
sqlite3模块,无需额外安装服务端; - 单文件存储:整个数据库就是一个
music_recommender.db文件,拷贝即用; - 事务安全:支持ACID,用户行为日志写入不会因崩溃丢失;
- 轻量查询:
SELECT * FROM songs WHERE genre='jazz' ORDER BY popularity DESC LIMIT 5响应<10ms,满足实时推荐。
注意:SQLite不适合高并发写入,但毕业设计场景下,用户行为日志是批量插入(每小时一次),推荐查询是读多写少,完全够用。
3.2 四张核心表结构与字段含义
数据库共4张表,设计遵循第三范式,同时兼顾查询效率。关键字段加注释说明其在推荐流程中的作用:
| 表名 | 字段 | 类型 | 是否主键 | 说明 |
|---|---|---|---|---|
users | user_id | INTEGER | ✓ | 用户唯一标识,Flask路由中直接使用 |
age | INTEGER | ✗ | 归一化到[0,1]用于User Tower输入 | |
region_id | INTEGER | ✗ | 地区LabelEncoder编码(北京=1,上海=2...) | |
pop_preference | REAL | ✗ | 流行音乐偏好分(0~1),来自问卷或历史行为统计 | |
songs | song_id | INTEGER | ✓ | 歌曲唯一标识,Item Tower输入 |
bpm | REAL | ✗ | 每分钟节拍数,归一化到[0,1] | |
valence | REAL | ✗ | 情绪值(Spotify API获取),0=悲伤,1=欢快 | |
genre_id | INTEGER | ✗ | 流派ID(Pop=1, Rock=2, Jazz=3...) | |
user_behavior | id | INTEGER | ✓ | 自增主键 |
user_id | INTEGER | ✗ | 外键关联users | |
song_id | INTEGER | ✗ | 外键关联songs | |
timestamp | INTEGER | ✗ | Unix时间戳,用于GRU序列排序 | |
play_duration_sec | INTEGER | ✗ | 实际播放时长,用于计算完成率(是否听完) | |
recommendations | id | INTEGER | ✓ | 自增主键 |
user_id | INTEGER | ✗ | 推荐目标用户 | |
song_id | INTEGER | ✗ | 被推荐歌曲 | |
score | REAL | ✗ | 精排层输出的0~1分数 | |
recall_source | TEXT | ✗ | 标明来源("dual_tower" or "gru_seq") |
特别说明play_duration_sec字段:它不只是记录行为,更是隐式反馈信号。若用户播放一首3分钟的歌只听了20秒,completion_rate = 20/180 ≈ 0.11,该样本在训练时会被赋予低权重(通过sample_weight参数传入PyTorch DataLoader),避免模型误学“跳过即喜欢”。
3.3 初始化脚本:三步完成数据库搭建与示例数据注入
项目根目录下init_db.py脚本全自动完成:
- 创建4张表;
- 插入1000首示例歌曲(含BPM、情绪、流派等完整元数据);
- 生成500个虚拟用户及20000条行为日志(按时间戳排序,确保GRU序列有效)。
执行命令(确保Python 3.8+):
python init_db.py --db_path ./data/music_recommender.db --num_users 500 --num_songs 1000参数说明:
--db_path:指定数据库文件路径,默认./data/music_recommender.db;--num_users:生成用户数,建议500(数据量适中,训练快);--num_songs:歌曲总数,1000足够覆盖主流流派;--seed:随机种子(默认42),保证每次生成数据一致,便于复现。
脚本内部逻辑:歌曲元数据从data/song_metadata.csv加载(已预处理好),用户行为日志按泊松过程模拟(高峰时段行为密集),避免均匀分布失真。运行后你会看到:
✅ Created table 'users' with 500 rows ✅ Created table 'songs' with 1000 rows ✅ Inserted 20000 user behaviors (avg 40 per user) ✅ Database initialized at ./data/music_recommender.db3.4 Flask接口与数据库交互:如何安全地查询推荐结果
后端用Flask提供RESTful接口,核心是app.py中的/user/<int:user_id>/recommend路由。关键安全设计:
- SQL注入防护:使用参数化查询,禁止字符串拼接;
- 用户存在性校验:若
user_id不存在,返回HTTP 404而非空列表; - 缓存策略:首次请求计算并存入
recommendations表,后续请求直接查表,避免重复计算。
# app.py 片段 @app.route('/user/<int:user_id>/recommend', methods=['GET']) def get_recommendations(user_id): conn = sqlite3.connect('data/music_recommender.db') cursor = conn.cursor() # 1. 校验用户是否存在 cursor.execute("SELECT COUNT(*) FROM users WHERE user_id = ?", (user_id,)) if cursor.fetchone()[0] == 0: return jsonify({"error": "User not found"}), 404 # 2. 查询缓存结果(按score降序取Top5) cursor.execute(""" SELECT s.song_name, s.artist, r.score FROM recommendations r JOIN songs s ON r.song_id = s.song_id WHERE r.user_id = ? AND r.recall_source = 'mlp_rerank' ORDER BY r.score DESC LIMIT 5 """, (user_id,)) results = cursor.fetchall() conn.close() return jsonify({ "user_id": user_id, "recommendations": [ {"song_name": r[0], "artist": r[1], "score": round(r[2], 3)} for r in results ] })注意recall_source = 'mlp_rerank'条件:精排结果单独标记,避免与双塔原始召回混淆。答辩演示时,你可以对比/user/123/recommend?source=dual_tower和/user/123/recommend?source=mlp_rerank,展示精排如何提升相关性。
4. 模型训练与验证:从零开始跑通全流程,避开90%新手踩的坑
4.1 环境配置:仅需6个核心依赖,拒绝复杂conda环境
本项目刻意规避torchvision、transformers等重型依赖,仅需以下6个包(requirements.txt已锁定版本):
torch==1.13.1(兼容CUDA 11.6,GTX 10系显卡无压力)numpy==1.23.5pandas==1.5.3scikit-learn==1.2.2(用于LabelEncoder和归一化)flask==2.2.5faiss-cpu==1.7.4(CPU版,免GPU编译)
安装命令(推荐用venv隔离):
python -m venv venv_music source venv_music/bin/activate # Linux/Mac # venv_music\Scripts\activate.bat # Windows pip install -r requirements.txt提示:若
pip install faiss-cpu报错,直接下载whl文件(项目包内libs/faiss_cpu-1.7.4-cp39-cp39-manylinux2014_x86_64.whl),执行pip install libs/faiss_cpu-1.7.4-*.whl即可。这是Windows用户最常卡住的点。
4.2 数据预处理流水线:标准化、编码、负采样三步到位
预处理脚本preprocess.py完成三项关键任务:
- 数值特征归一化:对
bpm、duration_sec、valence等连续变量用MinMaxScaler缩放到[0,1]; - 类别特征编码:对
genre、artist、region用LabelEncoder转为整数ID; - 负采样生成:为每个正样本(用户听过某歌)生成4个负样本(用户未听过但同流派的歌),解决正负样本极度不均衡问题(Last.fm数据正样本占比<0.1%)。
负采样逻辑在generate_negative_samples()函数中:
def generate_negative_samples(pos_df, songs_df, neg_ratio=4): """ 为每个正样本生成neg_ratio个负样本 策略:优先同流派采样(保持多样性),再全局随机 """ neg_samples = [] genre_groups = songs_df.groupby('genre_id') for _, row in pos_df.iterrows(): user_id, pos_song_id, pos_genre = row['user_id'], row['song_id'], row['genre_id'] # 同流派负采样(最多3个) if pos_genre in genre_groups.groups: genre_songs = genre_groups.get_group(pos_genre)['song_id'].tolist() neg_candidates = [s for s in genre_songs if s != pos_song_id] if len(neg_candidates) >= 3: neg_list = random.sample(neg_candidates, 3) else: neg_list = neg_candidates else: neg_list = [] # 全局随机补足(最多1个) while len(neg_list) < neg_ratio: rand_song = random.choice(songs_df['song_id'].tolist()) if rand_song != pos_song_id and rand_song not in neg_list: neg_list.append(rand_song) for neg_song in neg_list: neg_samples.append([user_id, neg_song, 0]) # label=0表示负样本 return pd.DataFrame(neg_samples, columns=['user_id', 'song_id', 'label'])这个策略平衡了相关性(同流派负样本更难区分,提升模型判别力)和多样性(全局随机避免流派偏差),比纯随机负采样AUC高0.05。
4.3 模型训练脚本:支持断点续训与指标实时监控
训练入口为train.py,支持以下实用功能:
--resume:从checkpoints/model_epoch_15.pth恢复训练;--eval_step:每5个epoch在验证集计算AUC/Recall@10;--log_dir:TensorBoard日志输出路径,可视化loss曲线。
关键训练循环(简化版):
# train.py 片段 for epoch in range(start_epoch, args.epochs + 1): model.train() total_loss = 0 for batch in train_loader: user_feat, item_feat, labels = batch user_feat, item_feat, labels = user_feat.to(device), item_feat.to(device), labels.to(device) optimizer.zero_grad() logits = model(user_feat, item_feat) # 双塔内积 + MLP精排 loss = criterion(logits, labels.float()) loss.backward() optimizer.step() total_loss += loss.item() # 每5个epoch验证 if epoch % args.eval_step == 0: val_auc = evaluate(model, val_loader, device) print(f"Epoch {epoch} | Train Loss: {total_loss/len(train_loader):.4f} | Val AUC: {val_auc:.4f}") # 保存最佳模型 if val_auc > best_auc: best_auc = val_auc torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'auc': val_auc }, f'checkpoints/best_model.pth')避坑 / 常见问题 / 排查
现象:训练loss震荡剧烈,100个epoch后仍>0.65
原因:学习率过高(初始lr=0.01)或梯度爆炸
解决:在config.py中将LEARNING_RATE从0.01改为0.001,并在train.py中添加梯度裁剪:torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)现象:验证AUC始终在0.5左右(随机猜测水平)
原因:负采样比例不当(neg_ratio设为10导致负样本过多,模型学会永远预测0)或特征未归一化(BPM范围0~200,而情绪值0~1,尺度差异导致梯度失衡)
解决:检查preprocess.py中MinMaxScaler是否对所有数值列生效;将neg_ratio从10调至4现象:
RuntimeError: Expected all tensors to be on the same device
原因:user_feat在GPU,item_feat在CPU(常见于DataLoader中collate_fn未统一设备)
解决:在data_loader.py的__getitem__中确保所有tensor调用.to(device),或在训练循环中统一移动:user_feat = user_feat.to(device); item_feat = item_feat.to(device)现象:Flask启动时报
sqlite3.OperationalError: database is locked
原因:训练脚本正在写入recommendations表,Flask同时读取
解决:训练完成后手动关闭训练进程;或修改app.py,在查询前加timeout=10:conn = sqlite3.connect('data/music_recommender.db', timeout=10)现象:GRU序列建模时
RuntimeError: input.size(-1) must be equal to input_size
原因:UserSequenceDataset中歌曲嵌入维度(64)与GRU定义的input_size(默认32)不匹配
解决:检查models/gru_model.py中nn.GRU(input_size=64, hidden_size=32),确保input_size等于嵌入维度
4.4 模型验证方法:不止看AUC,还要看推荐结果的业务合理性
学术指标(AUC/Recall@10)只是基础,毕业设计更要证明“推荐结果真的合理”。本项目提供validate_recommendations.py脚本,从三个维度验证:
- 流派一致性:计算Top-5推荐中与用户历史听歌流派重合度(如用户80%听Jazz,推荐中Jazz占比应>60%);
- BPM平滑度:用户历史BPM均值为120,推荐歌曲BPM应在[100,140]区间;
- 冷启动测试:对
user_id=999(无行为日志的新用户),检查推荐是否基于其注册流派偏好(如偏好Pop,则Top-5中Pop占比>70%)。
执行命令:
python validate_recommendations.py --user_id 123 --top_k 5输出示例:
User 123 History: Pop(65%), Rock(25%), Jazz(10%) Recommendations: ['SongA(Pop)', 'SongB(Pop)', 'SongC(Rock)', 'SongD(Pop)', 'SongE(Rock)'] ✅ Genre match rate: 80% (expected >60%) ✅ BPM smoothness: [112, 118, 125, 109, 131] → within [100,140] ✅ Cold-start test passed for user 999这个验证脚本是你答辩时的“后悔药”——当老师质疑“你的推荐真的准吗?”,你打开终端运行一行命令,当场展示量化证据。
5. PPT制作与答辩技巧:把技术细节转化为评委能听懂的故事
5.1 PPT结构设计:用“问题-解法-证据”替代“模块-模块-模块”
别再用“第一章 绪论,第二章 相关工作”这种教科书结构。评委平均每人听15分钟,注意力窗口极短。本项目PPT按以下逻辑展开(共12页):
| 页码 | 标题 | 核心内容 | 视觉化建议 |
|---|---|---|---|
| 1 | 封面 | “基于深度学习的音乐推荐系统:解决冷启动与数据稀疏的毕业设计实践” + 你的姓名/学号 | 背景图:简洁音符线条+代码片段(非全屏) |
| 2 | 真实痛点 | 对比图:左“传统协同过滤在Last.fm数据上AUC=0.52”,右“本系统AUC=0.78” | 用红色箭头标出提升幅度 |
| 3 | 技术选型理由 | 三栏对比:纯CNN音频建模(需GPU显存12GB)、纯协同过滤(冷启动AUC=0.45)、本方案(双塔+GRU,显存需求<3GB,冷启动AUC=0.68) | 加图标:⚡️(快)、❄️(冷启动)、💾(轻量) |
| 4 | 系统架构图 | 分三层:数据层(SQLite图标)、模型层(双塔+GRU+MLP简笔画)、应用层(Flask接口示意图) | 所有箭头标注数据流向(如“用户行为→GRU序列”) |
| 5 | 双塔设计细节 | 左User Tower输入特征(年龄/地区/偏好),右Item Tower输入(BPM/情绪/流派),中间内积计算 | 用颜色区分:蓝色=用户侧,橙色=物品侧 |
| 6 | GRU序列效果 | 折线图:X轴“序列长度”,Y轴“AUC”,对比“无GRU”和“有GRU”两条线 | 标出拐点:序列长度=10时收益最大 |
| 7 | 精排交叉特征 | 表格展示3个手工特征(genre_match, bpm_diff_norm, year_diff)及业务解释 | 加✅符号强调“可解释性” |
| 8 | 数据库设计亮点 | SQLite单文件图标 + 与MySQL对比表(安装时间、依赖数、答辩兼容性) | 红色感叹号:“无需配置MySQL服务!” |
| 9 | 训练效果 | 曲线图:X轴epoch,Y轴loss/AUC,标出“早停点”(epoch 25) | 注明硬件:GTX 1060,训练时间2.3小时 |
| 10 | 推荐结果示例 | 截图:curl命令 + JSON响应,高亮score和song_name | 旁边手写批注:“用户ID=123,历史听Jazz,推荐中Jazz占3/5” |
| 11 | 答辩问答准备 | 列出3个高频问题及答案要点(如“为什么不用Transformer?”→“序列长度仅10,GRU更轻量且效果相当”) | 用💡图标引导 |
| 12 | 致谢 | 导师姓名 + “本系统开源,代码已上传至GitHub” | 附二维码(指向项目README) |
注意:所有图表必须自己生成,禁用网上下载的模糊图片。用
matplotlib画图时加plt.tight_layout()避免文字截断,字体设为'SimHei'支持中文。
5.2 答辩话术设计:把技术术语翻译成生活语言
评委可能不懂GRU,但一定懂“听歌习惯”。把技术点转化为生活场景:
- 不说:“我采用了门控循环单元建模用户序列”
- 说:“就像你连续听了3首周杰伦,系统会记住这个‘周氏风格’,下次推荐时优先考虑类似旋律的歌,而不是突然推一首重金属”
- 不说:“双塔结构实现用户与物品特征解耦”
- 说:“相当于给用户和歌曲各自建一个‘兴趣档案’,新用户填了‘喜欢流行’,系统立刻查档案推荐流行歌;新歌上线,只要填好BPM和情绪,马上能被推荐”
- 不说:“精排层融合交叉特征提升排序质量”
- 说:“除了算相似度,我还加了人工规则——比如用户平均BPM是120,就尽量不推BPM 180的舞曲,避免听感突兀”
每句话都要有锚点(周杰伦、BPM 120),让评委瞬间建立画面感。提前演练时,对着镜子说,确保不看稿能自然表达。
5.3 代码演示必备清单:5分钟内让评委看到“系统在跑”
答辩现场演示是加分项,但必须可控。准备以下3个脚本,确保5分钟内完成:
demo_start.sh(Linux/Mac)或demo_start.bat(Windows):一键启动数据库、训练(若未训练)、Flask服务# demo_start.sh python init_db.py --num_users 100 --num_songs 500 python train.py --epochs 5 --eval_step 1 # 快速训5轮 flask run --host=0.0.0.0 --port=5000 & echo "Server started at http://localhost:5000"demo_test.py:生成测试请求,输出JSON结果import requests res = requests.get("http://localhost:5000/user/123/recommend") print(res.json()) # 输出:{"user_id":123,"recommendations":[{"song_name":"Sugar","artist":"Maroon 5","score":0.921},...]}demo_visualize.py:用matplotlib画出用户123的历史BPM分布 + 推荐歌曲BPM,证明平滑性# 从数据库查用户123历史BPM和推荐BPM hist_bpm = [115, 120, 118, 122] # 示例 rec_bpm = [112, 118, 125, 109, 131] plt.hist([hist_bpm, rec_bpm], label=['History', 'Recommendation']) plt.legend(); plt.xlabel('BPM'); plt.title('BPM Smoothness Check') plt.show()
演示时,打开终端分屏:左demo_start.sh,中demo_test.py,右demo_visualize.py。全程不碰键盘,只点回车,展现“一切尽在掌握”的自信。
6. 从那以后我每次做毕设,都强制走一遍“数据-模型-接口-验证”闭环
做完这个音乐推荐系统,我最大的教训不是学会了GRU或双塔,而是理解了毕业设计的本质是交付一个可验证的闭环。以前总以为“模型AUC高=项目成功”,直到答辩时老师问:“你推荐的歌,用户真的会点开吗?”我才意识到,AUC只是实验室指标,而curl http://localhost:5000/user/123/recommend返回的JSON,才是真实世界里的“交付物”。所以现在我给自己立下铁律:任何毕设代码,必须在main.py里写死一个if __name__ == "__main__":入口,里面只做四件事——
- 从
data/读入原始CSV; - 调用
models/里的训练函数,生成checkpoints/best_model.pth; - 用
app.py启动Flask,监听/recommend; - 运行
validate_recommendations.py,输出✅ All checks passed。
这四步缺一不可。如果哪一步失败,宁可删掉炫酷的Transformer模块,也要先让GRU跑通。因为答辩不是技术发布会,而是向老师证明:“我能独立完成从数据到产品的最小可行闭环。”
项目包里README.md的每一行命令,都是我踩坑后重写的——python init_db.py不是为了炫技,是确保你双击就能生成数据库;flask run前面加export FLASK_APP=app.py,是因为Windows用户常忘设环境变量;连requirements.txt里torch==1.13.1的版本号,都是反复测试GTX 1060兼容性后定的。这些细节不写进论文,但决定你能不能在答辩前夜睡个
本文还有配套的精品资源,点击获取