☰
毕业设计可用的Python音乐推荐系统:双塔+GRU+SQLite闭环实现
2026/10/3 9:41:33 网站建设 项目流程

简介:本资源是一套面向计算机专业本科生的毕业设计实战项目,聚焦深度学习在音乐推荐领域的应用实践,适用于正在完成大作业、毕业设计或寻求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_match1 if user_genre == item_genre else 0流派一致性是音乐推荐强信号
bpm_diff_normabs(user_avg_bpm - item_bpm) / 200BPM差异过大影响听感连贯性
release_year_diffabs(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张表,设计遵循第三范式,同时兼顾查询效率。关键字段加注释说明其在推荐流程中的作用:

表名字段类型是否主键说明
usersuser_idINTEGER✓用户唯一标识,Flask路由中直接使用
ageINTEGER✗归一化到[0,1]用于User Tower输入
region_idINTEGER✗地区LabelEncoder编码(北京=1,上海=2...)
pop_preferenceREAL✗流行音乐偏好分(0~1),来自问卷或历史行为统计
songssong_idINTEGER✓歌曲唯一标识,Item Tower输入
bpmREAL✗每分钟节拍数,归一化到[0,1]
valenceREAL✗情绪值(Spotify API获取),0=悲伤,1=欢快
genre_idINTEGER✗流派ID(Pop=1, Rock=2, Jazz=3...)
user_behavioridINTEGER✓自增主键
user_idINTEGER✗外键关联users
song_idINTEGER✗外键关联songs
timestampINTEGER✗Unix时间戳,用于GRU序列排序
play_duration_secINTEGER✗实际播放时长,用于计算完成率(是否听完)
recommendationsidINTEGER✓自增主键
user_idINTEGER✗推荐目标用户
song_idINTEGER✗被推荐歌曲
scoreREAL✗精排层输出的0~1分数
recall_sourceTEXT✗标明来源("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脚本全自动完成:

  1. 创建4张表;
  2. 插入1000首示例歌曲(含BPM、情绪、流派等完整元数据);
  3. 生成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.db

3.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.5
  • pandas==1.5.3
  • scikit-learn==1.2.2(用于LabelEncoder和归一化)
  • flask==2.2.5
  • faiss-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完成三项关键任务:

  1. 数值特征归一化:对bpm、duration_sec、valence等连续变量用MinMaxScaler缩放到[0,1];
  2. 类别特征编码:对genre、artist、region用LabelEncoder转为整数ID;
  3. 负采样生成:为每个正样本(用户听过某歌)生成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')

避坑 / 常见问题 / 排查

  1. 现象:训练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)

  2. 现象:验证AUC始终在0.5左右(随机猜测水平)
    原因:负采样比例不当(neg_ratio设为10导致负样本过多,模型学会永远预测0)或特征未归一化(BPM范围0~200,而情绪值0~1,尺度差异导致梯度失衡)
    解决:检查preprocess.py中MinMaxScaler是否对所有数值列生效;将neg_ratio从10调至4

  3. 现象: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)

  4. 现象:Flask启动时报sqlite3.OperationalError: database is locked
    原因:训练脚本正在写入recommendations表,Flask同时读取
    解决:训练完成后手动关闭训练进程;或修改app.py,在查询前加timeout=10:conn = sqlite3.connect('data/music_recommender.db', timeout=10)

  5. 现象: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/情绪/流派),中间内积计算用颜色区分:蓝色=用户侧,橙色=物品侧
6GRU序列效果折线图: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分钟内完成:

  1. 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"
  2. 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},...]}
  3. 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__":入口,里面只做四件事——

  1. 从data/读入原始CSV;
  2. 调用models/里的训练函数,生成checkpoints/best_model.pth;
  3. 用app.py启动Flask,监听/recommend;
  4. 运行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兼容性后定的。这些细节不写进论文,但决定你能不能在答辩前夜睡个

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询