汽车租赁推荐系统毕业设计:协同过滤与Django实现
记得当时选毕业设计题目我把各个平台上能搜到的“推荐系统毕业设计”翻了个遍清一色是电影推荐、图书推荐、商品推荐。我偏偏换了个赛道汽车推荐系统具体场景切在汽车租赁上。原因很现实——这个题目能把数据分析、可视化、协同过滤、Django Web开发以及现在很热的“大数据”“新能源汽车”话题串在一条完整的业务链路里做出来不是一个玩具Demo而是一个能演示、能写论文、能拿去跟面试官讲清楚的完整作品。这篇文章就是把我做这个项目时的完整思路、代码逻辑和踩坑过程原样写出来。打算做类似毕业设计或者单纯想搞懂推荐系统怎么从算法变成Web应用的同学可以直接照搬。这套链路对新人很友好算法核心是协同过滤工程部分是标准Django三板斧真正的难度在细节——而细节恰恰是我下面重点展开的部分。1. 选题阶段为什么不做电影推荐非要做汽车租赁推荐1.1 推荐系统不该只出现在电商和视频网站很多人一提推荐系统第一反应就是电商平台“猜你喜欢”或者视频平台的“你可能想看”。这些场景当然典型但正因为太典型反而成了毕业设计里的红海数据公开、实现方案满天飞、老师也看腻了。汽车租赁这个场景的优势在于它既有推荐系统的通用结构又有自己的业务特殊性。用户在平台上看车、选车、租车会产生真实的选择行为车型有品牌、能源类型纯电/混动/燃油、座位数、日租金、续航里程这些结构化特征用户也有年龄、城市、会员等级、历史租车偏好这些人格化属性。两者像积木一样既能搭出协同过滤所需的历史行为矩阵又能搭出基于内容特征做筛选的逻辑。更关键的是汽车租赁场景里“推荐”不是锦上添花的营销功能而是实打实影响转化的核心模块。车型几十上百款用户翻半天找不到合适的车下单转化率自然就低。一个把“合适”放在第一位的推荐系统天然有说服力。再加上新能源车在租赁市场里的渗透率逐年上升把能源类型作为推荐维度既贴合产业趋势也让项目有了“时代感”。1.2 租赁数据里藏着的“算法友好结构”推荐算法最怕什么最怕数据之间没有任何联系。比如你只有一张用户表没有任何行为记录再牛的算法也无从下手。而租赁订单天然就是强行为数据每个订单都记录着谁租了哪辆车、租了几天、花了多少钱、事后给了几分。这构成了算法最喜欢的三元组结构用户-物品-评分。我把数据拆成三块来看用户侧性别、年龄、所在城市、会员等级、注册时长车辆侧车型名、品牌、车辆类别轿车/SUV/MPV、能源类型、日租金、座位数、续航里程行为侧订单金额、租赁天数、是否续租、订单评分、下单时间这三块拼起来既能做协同过滤的评分矩阵又能做可视化的分析维度还能支撑Django页面上的筛选和展示需求。一套数据喂饱了算法、图表、Web三个模块这在毕设项目的完整性上非常加分。我后来在论文里写数据模型章节时基本上就是把这个结构展开描述根本不用硬凑字数。1.3 这套组合在毕业设计和求职里的双重价值从评分角度这套题目覆盖了“数据构造-数据清洗-数据分析-可视化-算法设计-系统实现”的全过程每一章都有可写的内容。从面试角度推荐系统的问题在算法岗和开发岗都会被问到而你能端出一个亲手搭到Web端的完整项目比只会背协同过滤公式的候选人要有说服力得多。我当时的定位是“基于混合推荐的汽车租赁平台”——论文题目听起来像样演示系统和算法有清晰对应答辩时评委一眼就能知道你做了什么。后来和一些做技术面试的朋友聊他们也说这种“数据算法系统”三位一体的项目是最容易在简历上被挑出来细问的类型。2. 一夜生成8万条订单数据构造与清洗才算真正入门2.1 三张核心表的结构设计真实企业级数据拿不到也不建议在毕设里用非正规手段去“爬”数据合规风险太大。更合理的做法是按业务逻辑自己构造模拟数据。这是毕设项目里常见且被认可的做法只要造数逻辑贴近真实场景分析结论一样有参考价值。我的表结构设计如下表名关键字段作用useruser_id, gender, age, city, member_level, register_date用户特征carcar_id, model_name, brand, category, energy_type, price_per_day, seat_num, range_km车辆特征rentalorder_id, user_id, car_id, rent_start, rent_end, total_amount, rating行为数据这里有个设计细节值得说明rating字段虽然叫“用户评分”但真实租赁平台没有多少用户会认真打分所以我在造数时把它当成“行为评分的合成结果”来构造。同样是5分有的人是因为续租/长租才给的高分有的人是新客短租随手给的。在算法阶段这个字段还会继续加工不会直接拿原始值进场。2.2 造数时的业务逻辑而不是纯随机有些同学造数据就一句random.randint(1, 100)造出来的订单既不符合逻辑、也没有任何规律后面所有分析都会变得毫无意义。造数阶段就要考虑业务合理性否则后面每一步都是错的。我当时的造数思路是这样的用户规模5000人年龄覆盖18到65岁城市按一线、新一线、二线城市等级随机抽取车型规模200款包含纯电、混动、燃油三类能源价格从80元/天到800元/天分布订单量8万条左右时间跨度一年关键规律年轻用户更偏新能源和低价车商务型用户更偏SUV和品牌车热门车型订单多且评分偏高冷门车型订单稀疏、用来模拟长尾。这个长尾结构是对协同过滤的真正考验也是论文里一个不错的分析点。代码示意如下import random import pandas as pd from datetime import datetime, timedelta random.seed(42) users [] for uid in range(1, 5001): users.append({ user_id: uid, gender: random.choice([男, 女]), age: random.randint(18, 65), city_level: random.choice([一线, 新一线, 二线, 三线]), member_level: random.randint(1, 5), }) cars [] for cid in range(1, 201): cars.append({ car_id: cid, model_name: f模拟车型{cid}, energy_type: random.choice([纯电, 混动, 燃油]), price_per_day: random.randint(80, 800), }) orders [] for oid in range(1, 80001): user_id random.randint(1, 5000) car_id random.randint(1, 200) days random.randint(1, 14) orders.append({ order_id: oid, user_id: user_id, car_id: car_id, days: days, total_amount: round(days * 300 random.randint(-20, 20), 2), rating: random.choice([4.0, 4.5, 5.0, 3.5, 2.0]), }) rental_df pd.DataFrame(orders)注意示例里的total_amount没有和具体车型价格关联这是简化逻辑。真正跑实验时我会先从car表读取price_per_day再乘上天数生成金额保证金额和车型强相关后面的价格偏好分析才靠谱。造数阶段把“价格×天数金额”这种基本约束做对后面所有的分析和可视化才有逻辑基础。2.3 清洗阶段要处理的三类脏数据模拟数据同样会产生脏数据而且恰恰是“随机造数”最容易埋雷。我整理了三类必须处理的问题第一类是缺失值和异常值。部分订单的rating会是空值部分车型缺少range_km。燃油车没有续航里程是正常的但如果你想统一分析要给它一个业务上说得通的占位值并单独标记。第二类是时间倒挂。租赁开始时间晚于结束时间或者租期超过一个月这类数据要么删除、要么标记为无效。我是在造数时故意留了一部分这种脏数据用来演示清洗流程。第三类是逻辑矛盾。比如total_amount 0或者同一个用户在同一时间段重复租了同一款车往往说明造数时字段没对齐需要回到源头修正。清洗代码并不复杂但要有意做成一个可维护的函数后面跑实验要反复用def clean_rental_data(df): df df.dropna(subset[user_id, car_id, rating]) df df[df[total_amount] 0] df df[df[rent_end] df[rent_start]] df df.drop_duplicates(subset[order_id]) return df.reset_index(dropTrue)2.4 特征工程把“生数据”变成“算法食材”清洗完只是第一步真正决定推荐质量的是特征工程。协同过滤只需要评分矩阵但要处理冷启动、要上混合推荐就必须把车和人的特征用起来。我给车型做的特征包括能源类型、日租金分档经济型/舒适型/豪华型、座位数档位、品牌热度分位、续航里程分位。给用户做的特征包括年龄分组、城市等级、会员等级、历史平均订单金额、历史订单数。这些特征用 Pandas 的groupby加merge就能完成但对后面的冷启动替换和内容相似度计算至关重要。比如我后面用到的“内容相似度”就是把车映射成一个向量[能源类型独热, 价格分档, 座位数, 续航分档]再和其他车做余弦相似度。这不是什么高级操作但非常好用——新车没有订单数据时靠它能找到最相近的在租车型。3. 可视化不是画给老师看是画给自己理思路3.1 我为什么把可视化放在算法之前算法实现前先做可视化这一步很容易被低估。我当时的习惯是每建一批特征先画图看看分布再决定算法参数。比如价格分档怎么划、哪些车算热门车、用户群体怎么分组——不看数据靠拍脑袋很容易定错方向。到论文阶段可视化也是撑门面的东西。一份毕业设计论文里如果只有表格和公式评委很难有耐心读下去但配合几张“租赁热度分布”“车型偏好分组图”项目说服力完全不一样。3.2 六张图把数据故事讲清楚我做过的图表里最有用的六张是图表类型回答的问题各能源类型订单占比饼图/环形图新能源车在租赁市场的真实接受度热门车型 Top 15横向柱状图头部车型有哪些长尾有多长不同年龄段的车型偏好堆叠柱状图年龄特征能否作为推荐信号一周内订单量的时间分布折线图工作日/周末租车需求的潮汐变化租金区间与订单量关系柱状图/箱线图价格带与受欢迎度的非线性关系用户评分分布直方图评分字段是否可用、是否偏斜画图工具我用的是 pandas 自带的绘图接口加 pyplot简单图表已经够用。重点是得出结论不是追求酷炫效果import matplotlib.pyplot as plt # 各能源类型订单占比 merged rental_df.merge(car_df, oncar_id) energy_count merged[energy_type].value_counts() energy_count.plot(kindpie, autopct%.1f%%) plt.title(各能源类型订单占比) plt.show() # 热门车型 Top 15 hot_car rental_df[car_id].value_counts().head(15) hot_car.plot(kindbarh) plt.title(热门车型 Top 15) plt.show()如果想在答辩演示时做交互效果可以再上 pyecharts 把静态图搬到 Web 页面里。但算法验证阶段用静态图就够不要把大量时间耗在调整图表配色上。3.3 从图表里反推推荐策略我当时的几个关键发现直接改变了算法设计第一新能源车订单占比接近40%。这说明推荐不能按“传统燃油车为主”的思路来续航和能源类型要作为重要信号。第二热门车型集中度很高Top 20 车型贡献了超过一半的订单尾部大量车型订单稀疏。这个长尾结构正好是协同过滤能发挥价值的地方——头部可以用热榜尾部长尾才需要个性化去捞。第三年龄段和车型类别有明显对应关系。年轻用户偏纯电轿车中年用户偏舒适型SUV。这意味着基于用户的协同过滤有先天的数据基础也意味着内容特征能源、类别、价格一定不能浪费。这些结论全部来自可视化的观察不是拍脑袋。换句话说可视化不是给论文凑图的是真的在给推荐算法定方向。4. 协同过滤的工程实现从共现矩阵到Top-N推荐4.1 基于用户还是基于物品我选了后者协同过滤分两大类基于用户UserCF和基于物品ItemCF。基于用户的逻辑是“找到和你喜好相似的人把那些人喜欢而你还没见过的车推荐给你”。听起来合理但放在汽车租赁场景里有一个致命问题用户租车行为非常稀疏。一个人一年可能只租三五次车几百款车型里很难找到真正“相似”的用户。更何况用户的租车需求是变化的——这周商务出行租SUV下周家庭出游租MPV把用户当成稳定偏好实体来建模稳定性很差。基于物品的逻辑是“找到你已经租过车的相似车型推荐给你”。这个思路更适合租赁场景租过纯电轿车的用户大概率会对另一款纯电轿车感兴趣租过7座SUV的用户下一单很可能还是7座SUV。车型是相对稳定的实体类别、价格、能源类型这些特征不会频繁变化所以 ItemCF 在租赁场景里的准确度和可解释性都更好。我在毕设里最终选择 ItemCF 作为核心算法。4.2 核心代码矩阵、相似度、推荐逻辑基于物品的协同过滤分两步先计算车型之间的相似度再根据用户的历史租车记录做加权汇总得到候选车型的得分列表取 Top-N。先构建用户-车型评分矩阵。注意评分不要直接用订单里的rating我做了一个综合分订单评分×0.7 是否续租×0.2 订单金额分档×0.1合成0到5的偏好分。这一步对推荐效果影响很大值得多花时间调权重。import pandas as pd import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 构造用户-车型评分矩阵 user_item_matrix rental_df.pivot_table( indexuser_id, columnscar_id, valuesscore, fill_value0 ) # 计算车型相似度矩阵 item_sim cosine_similarity(user_item_matrix.T) item_sim_df pd.DataFrame( item_sim, indexuser_item_matrix.columns, columnsuser_item_matrix.columns ) # 对指定用户做推荐 def recommend_itemcf(user_id, ui_matrixNone, sim_dfNone, top_n10): if user_id not in ui_matrix.index: return [] user_rated ui_matrix.loc[user_id] user_rated user_rated[user_rated 0] scores {} for car_id, rating in user_rated.items(): sim_row sim_df[car_id] * rating for cand_id, s in sim_row.items(): if cand_id ! car_id and user_rated.get(cand_id, 0) 0: scores[cand_id] scores.get(cand_id, 0) s top_items sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return [cid for cid, _ in top_items]这段代码把推荐过程拆得很直观外层循环用户租过的车内层循环相似车累加“相似度×评分”。虽然暴力但对8万条订单的数据量完全够用。正式实验时如果想更快可以换成矩阵运算但对毕设来说先跑通、能解释清楚每一步在干什么比盲目追求性能更重要。4.3 冷启动新用户、新车型分别怎么处理协同过滤最经典的缺陷就是冷启动没有历史行为的用户、没有历史订单的新车都推不出来。我当时做了两个兜底方案对新用户直接用热门车型榜加新用户画像的内容筛选。比如新用户选了“纯电、经济型、轿车”就按这个画像从车型库里筛出最符合的若干款展示同时混入一定比例的全局热门车。对新车型用内容相似度代替协同过滤。每款车映射成一个特征向量求它和现有热门车型的余弦相似度把最接近车型的推荐位分给新车型。这两个方案都不需要训练上线就能用。答辩时评委对这部分特别感兴趣因为冷启动是推荐系统在工业界最常被追问的问题。4.4 效果验证不能只说跑通了还要有指标毕设里不能只说“推荐出来了”要有评价指标。工业界常用 RMSE、MAE 评价评分预测用 PrecisionK、RecallK 评价推荐列表质量。我选的是后者因为毕设场景是“给用户推荐一列车型”顺序和覆盖率比精确预测一个分数更有意义。实现思路是时间切分把前8个月的订单当训练集后4个月的订单当测试集。对测试集里每个用户看推荐列表里出现了多少辆用户真正租过的车计算 Precision10 和 Recall10。这种评估方式和论文里常写的时序验证逻辑一致也能避免后面会详细说的数据泄漏问题。5. Django把算法打包成可演示的Web系统5.1 项目结构与数据模型算法做出来只完成了一半毕业设计要的是“系统”所以我还用Django搭了一个可运行的Web应用。项目结构采用标准的单应用模式car_rental/ ├── manage.py ├── recommend/ │ ├── views.py │ ├── models.py │ ├── urls.py │ └── utils.py └── templates/ ├── index.html ├── detail.html └── analysis.htmlDjango 数据模型直接对应前面的三张表# recommend/models.py from django.db import models class User(models.Model): gender models.CharField(max_length10) age models.IntegerField() city_level models.CharField(max_length20) member_level models.IntegerField(default1) class Car(models.Model): model_name models.CharField(max_length100) brand models.CharField(max_length50) category models.CharField(max_length20, default轿车) energy_type models.CharField(max_length10) price_per_day models.IntegerField() seat_num models.IntegerField(default5) range_km models.IntegerField(nullTrue, blankTrue) class Rental(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) car models.ForeignKey(Car, on_deletemodels.CASCADE) rating models.FloatField(nullTrue) rent_start models.DateTimeField() rent_end models.DateTimeField()这里的字段比算法阶段更完整因为 Web 页面要展示车型列表、筛选条件、详情页内容没有足够字段撑不起来。5.2 推荐视图与页面交互推荐功能的核心逻辑放在视图里。流程是拿到当前用户ID调用utils.py里的推荐函数得到车型ID列表再从数据库取出车型详情传给模板渲染。# recommend/views.py from django.shortcuts import render from .models import User, Car, Rental from .utils import load_model, recommend_itemcf def home(request, user_id1): model_data load_model() # 读取预先训练好的矩阵和相似度表 rec_ids recommend_itemcf(user_id, model_data[ui_matrix], model_data[sim_df]) rec_cars Car.objects.filter(car_id__inrec_ids) return render(request, index.html, {cars: rec_cars})这里有一个很重要的工程细节用户-车型矩阵和车型相似度矩阵不需要每次请求都重算。我提前训练好并序列化保存视图启动时加载一次响应时间能从秒级降到毫秒级。这个“离线计算推荐结果、在线读取结果”的思路是工业推荐系统的经典做法写进论文是亮点。5.3 数据量大了怎么办8万条订单在数据库里不算大但如果有同学想把规模做到50万甚至上百万有几个点要提前处理给rental表的user_id和car_id加数据库索引否则按用户查询订单会越来越慢相似度矩阵不要每次请求都加载导出成.npy文件或放进缓存表订单量超过百万时离线计算可以考虑用 Dask、Spark 做并行但毕设没必要一开始就上先保证正确跑通再说。Django 端的性能优化对毕业设计属于加分项不是必需项。真正影响答辩印象的是系统能正常运行、推荐结果有依据、架构逻辑能讲清楚。5.4 演示系统的三个加分思路搭完基础功能后我加了三个小功能都不是重活但对演示效果帮助很大切换用户功能展示不同历史行为用户推荐结果差异明显比只演示一个固定用户更能证明算法有效推荐理由展示每条推荐下方显示一句“因为您租过某车型而某车型和它相似度高”可解释性直接拉满数据可视化页面把第3章的图放进 analysis 页面让评委知道你做了完整的数据分析闭环。6. 复盘我踩过的坑和答辩时怎么讲6.1 数据泄漏最容易犯又最难察觉的错误我第一次做评估时犯了一个典型错误——用全部订单数据生成相似度矩阵然后去评价测试集结果。这意味着模型在“训练”阶段已经看到了测试期的订单指标虚高Precision10 一度“好”到0.4以上。后来检查才发现相似度矩阵必须只用训练集订单构建测试集只能做验证。改过来之后指标立刻回落到合理水平但也真实反映了模型的水平。这个坑非常隐蔽因为程序不报错结果还特别漂亮。如果答辩时指标异常高而你答不出原因很容易被问住。统一规范是评估阶段的数据切分要在矩阵构建之前完成不是先建全量矩阵再按时间切分。6.2 指标选择不要只看一个数我一开始只看 Precision10后来发现不同用户群体之间差异巨大。有10次以上历史行为的用户推荐效果好新用户 Precision 几乎为零。只算平均值会掩盖这种分化。后来我按用户历史行为数量分组计算指标并单独统计冷启动用户的覆盖率评估报告一下子扎实了很多。6.3 答辩时最可能被追问的三个问题根据我的经验这类项目评委大概率会问以下三个问题提前准备口径“为什么不用深度学习或者大模型”我的回答是协同过滤在稀疏数据和可解释性上表现稳定适合租赁场景深度学习和大模型可以作为扩展方向我在论文里讨论了大模型做冷启动和自然语言交互的可行性但核心链路先用可解释、低成本的算法跑通。这个回答既承认边界又体现出你对新技术的了解。“你这个数据量算大数据吗”我的回答是项目沉淀的是大数据处理的方法思路包括离线训练、矩阵压缩、增量更新而不是单纯比拼数据规模。8万条订单能跑通完整链路换成更大数据量只需替换计算引擎架构不变。“冷启动怎么解决”按 4.3 小节里的两个兜底方案讲清楚逻辑清晰就能站住。分享一个我自己最深的体会做这种项目别一上来就啃算法论文先把数据和场景摸透。协同过滤本身公式量不大真正吃时间的是把数据、算法、工程串起来。等这条链路完整走通后面想换深度学习、或者接大模型做对话式选车都只是替换其中一个模块的问题整个系统骨架已经稳稳在你手里了。