尧图精选

米其林餐厅数据挖掘:机器学习课程设计实战指南

🕒 发布时间:2026/10/2 10:28:10 📁 来源:尧图网络
简介这份资源是面向高校计算机及相关专业学生的机器学习与数据挖掘课程设计完整项目以米其林餐厅数据为分析对象适合用于期末项目开发、课程实践与技能提升。项目在导师指导下完成并获98分涵盖数据预处理、特征工程、模型训练与可视化展示等完整流程可帮助学习者掌握数据清洗、特征选择与分类模型构建等核心技能。压缩包共56个文件约276KB以Java源码、FreeMarker模板、CSS样式、CSV数据集及SQL脚本为主另含项目说明文档与配置文件结构清晰便于按模块查阅。目前已有52人学习。资源提供完整源代码与详细注释并附使用手册读者可借此理解机器学习项目从需求分析到系统实现的完整生命周期结合餐厅评级、地理分布与菜品特色等真实数据深化对数据挖掘理论的理解并提升工程实践能力。1. 从一份课程设计说起米其林餐厅数据挖掘到底在挖什么米其林餐厅数据挖掘系统说白了就是把米其林指南里的餐厅信息星级、菜系、地理位置、价格区间、评审年份抓下来清洗成结构化表格再用机器学习模型做两件事一是预测某家餐厅下一年会不会掉星或升星二是把餐厅按特征聚类找出“三星餐厅的共同画像”。这套东西在课程设计里出现的频率极高因为它同时踩中了机器学习、数据挖掘、源码、课程设计四个热搜词数据量不大、业务含义清晰、可视化效果好看答辩时老师一眼就能看懂你在做什么。适合谁做如果你正在找机器学习课程设计题目或者想用一个完整项目把 pandas、sklearn、matplotlib 串起来这个方向比手写数字识别有意思得多。它不需要 GPU一台普通笔记本就能跑完全流程但中间的数据清洗和特征工程有足够的坑让你写出像样的实验报告。下面我按实际做过的顺序把从数据获取到模型评估的每一步拆开讲。2. 数据获取与清洗米其林指南不是标准数据集2.1 数据从哪来为什么不能直接爬米其林指南官网有反爬机制而且餐厅列表是动态加载的。常见做法是找公开的 Kaggle 数据集搜 “michelin guide restaurants”或者用维基百科的米其林餐厅列表做补充。我一般会先确认数据字段餐厅名、城市、国家、星级、菜系、价格等级、评审年份。如果做课程设计直接用 Kaggle 上的 CSV 最省事但要注意年份覆盖范围——很多数据集只到 2019 年做时序预测时样本量会不够。如果非要自己采集用 requests BeautifulSoup 对静态页面做小规模抓取即可不要上 Selenium课程设计没必要。抓下来的原始数据通常长这样星级字段是 “Three Stars” 这种文本价格是 “€€€€” 符号菜系是 “French, Contemporary” 这种多标签。这些都不能直接喂给模型。2.2 清洗脚本把星级和价格转成数值import pandas as pd import numpy as np # 读取原始数据假设列名为 name, city, country, stars, cuisine, price, year df pd.read_csv(michelin_raw.csv) # 星级映射文本转数值 star_map { Three Stars: 3, Two Stars: 2, One Star: 1, Bib Gourmand: 0, # 必比登推荐算0星但保留 Selected: 0 # 入选但无星 } df[star_num] df[stars].map(star_map) # 价格符号转数值€ 数量代表价格等级 df[price_level] df[price].apply(lambda x: len(str(x)) if pd.notna(x) else np.nan) # 菜系多标签取第一个菜系作为主菜系其余做 one-hot df[main_cuisine] df[cuisine].apply( lambda x: str(x).split(,)[0].strip() if pd.notna(x) else Unknown ) # 删除缺失关键字段的行 df df.dropna(subset[star_num, price_level, main_cuisine]) # 保存清洗后数据 df.to_csv(michelin_clean.csv, indexFalse) print(df.shape) print(df[star_num].value_counts())这段代码的逻辑很直接星级文本必须转成有序数值因为掉星/升星本质是序数分类问题价格符号长度对应价格等级€ 越多越贵菜系字段是多标签课程设计里先取主菜系降低维度后面聚类时再考虑多标签展开。参数上注意dropna的 subset 只删关键字段缺失的行不要全列删除否则样本量会掉得厉害。清洗完先看一眼star_num的分布。如果三星样本只有几十条后面做分类时就要考虑类别不平衡不能直接上 accuracy 当指标。2.3 特征工程年份和地理位置的坑评审年份这个字段很关键。如果你要做“预测下一年是否掉星”需要构造标签对每家餐厅用第 t 年的特征预测第 t1 年的星级变化。常见做法是构造二分类标签drop如果 t1 年星级低于 t 年则为 1否则为 0。注意不是所有餐厅每年都被评审有些餐厅隔年才出现这种样本要么插值要么丢弃。地理位置不能直接用城市名做 one-hot维度太高。我一般用国家 城市规模等级按餐厅数量分箱来代替。另外菜系和价格等级的交互特征有时比单独特征更有用比如 “French 价格等级4” 这种组合可以用pd.crosstab快速看分布。# 构造掉星标签需要按餐厅名和年份排序 df df.sort_values([name, year]) df[next_star] df.groupby(name)[star_num].shift(-1) df[drop] ((df[next_star] df[star_num]) df[next_star].notna()).astype(int) # 只保留有下一年记录的样本 model_df df.dropna(subset[next_star]).copy() print(model_df[drop].value_counts())这里shift(-1)是按餐厅分组后取下一年的星级drop标签只在有下一年记录时才有意义。如果掉星样本占比低于 5%后面建模要用class_weightbalanced或者 SMOTE 过采样。3. 模型选型与训练为什么我最终用了随机森林和 KMeans3.1 分类模型逻辑回归、XGBoost 和随机森林的对比课程设计里常见的选择是逻辑回归、决策树、随机森林、XGBoost。我的建议是先跑逻辑回归当 baseline再用随机森林看特征重要性XGBoost 作为提分选项但不要一上来就调参。米其林数据样本量通常几千条特征维度几十维随机森林在这种规模下表现稳定且不容易过拟合解释性也比 XGBoost 好写报告。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, roc_auc_score from sklearn.preprocessing import StandardScaler # 特征列价格等级、主菜系 one-hot、国家 one-hot、年份 feature_cols [price_level, year] X pd.get_dummies(model_df[feature_cols [main_cuisine, country]], columns[main_cuisine, country], drop_firstTrue) y model_df[drop] # 划分训练测试集注意 stratify 保证类别比例 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 逻辑回归需要标准化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) lr LogisticRegression(class_weightbalanced, max_iter1000) lr.fit(X_train_scaled, y_train) lr_pred lr.predict(X_test_scaled) print(Logistic Regression:) print(classification_report(y_test, lr_pred)) print(AUC:, roc_auc_score(y_test, lr.predict_proba(X_test_scaled)[:, 1])) # 随机森林不需要标准化 rf RandomForestClassifier( n_estimators200, max_depth8, class_weightbalanced, random_state42 ) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) print(Random Forest:) print(classification_report(y_test, rf_pred)) print(AUC:, roc_auc_score(y_test, rf.predict_proba(X_test)[:, 1]))参数说明class_weightbalanced是掉星样本少时的后悔药能自动调整权重max_depth8是防止随机森林在少量样本上过拟合我试过不限制深度训练集 AUC 到 0.99 但测试集只有 0.65血泪经验n_estimators200在几千条数据上已经足够再往上收益很小。逻辑回归的max_iter1000是因为标准化后仍可能不收敛加大迭代次数即可。评估时不要只看 accuracy。掉星样本少模型全预测“不掉星”也能有 90% 以上的 accuracy但 AUC 和 recall 才是关键。如果 recall 太低说明模型抓不住掉星餐厅需要检查特征里有没有遗漏重要信号比如评审年份的滞后效应。3.2 聚类模型KMeans 找三星餐厅画像聚类部分用 KMeans 对餐厅做分群特征包括价格等级、星级、菜系 one-hot、国家。注意聚类前必须标准化否则价格等级1-4和 one-hot0-1的量纲差异会让价格主导距离计算。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt # 聚类特征价格、星级、菜系 one-hot cluster_features pd.get_dummies( model_df[[price_level, star_num, main_cuisine]], columns[main_cuisine], drop_firstTrue ) scaler_cluster StandardScaler() X_cluster scaler_cluster.fit_transform(cluster_features) # 手肘法选 K inertia [] for k in range(2, 10): km KMeans(n_clustersk, random_state42, n_init10) km.fit(X_cluster) inertia.append(km.inertia_) plt.plot(range(2, 10), inertia, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.title(Elbow Method) plt.savefig(elbow.png) # 选 K4 跑最终聚类 km_final KMeans(n_clusters4, random_state42, n_init10) model_df[cluster] km_final.fit_predict(X_cluster) # 看每个簇的星级和价格均值 print(model_df.groupby(cluster)[[star_num, price_level]].mean()) print(model_df.groupby(cluster)[main_cuisine].value_counts().head(20))n_init10是显式指定多次初始化取最优sklearn 新版本默认值变了写清楚避免结果不可复现。手肘法看拐点如果曲线平滑没有明显拐点可以结合轮廓系数选 K。聚类结果解读时重点看每个簇的星级均值——如果某个簇全是三星且价格等级高那就是“高端三星画像”如果某个簇星级低但价格高可能是“高价低星”的异常群值得在报告里展开。3.3 特征重要性哪些因素真正影响掉星随机森林训练完后用feature_importances_看特征贡献。我做过几次排前面的通常是价格等级、评审年份、主菜系中的法餐和日料。年份重要性高说明评审标准随时间变化不是餐厅本身的问题。这个结论写进课程设计报告很有价值因为它把“数据挖掘”和“业务解释”连起来了。import pandas as pd import matplotlib.pyplot as plt importances pd.Series(rf.feature_importances_, indexX.columns) importances importances.sort_values(ascendingFalse).head(15) importances.plot(kindbarh) plt.gca().invert_yaxis() plt.title(Top 15 Feature Importances) plt.tight_layout() plt.savefig(feature_importance.png)如果发现某个 one-hot 特征重要性异常高比如某个小国家的餐厅全是不掉星那可能是样本偏差需要在报告里说明并考虑合并稀有类别。4. 避坑与排查课程设计里最容易翻车的五个点4.1 数据泄漏用未来了的信息预测过去现象模型 AUC 高到 0.95 以上但答辩时老师一问特征就发现用了下一年的星级。原因构造标签时不小心把next_star也放进了特征列。解决特征列必须严格限定在预测时间点之前的信息next_star和drop只能做标签不能进 X。每次train_test_split前检查一遍列名。4.2 类别不平衡全预测不掉星也有 90% 准确率现象accuracy 很高但 recall 接近 0。原因掉星样本占比太低模型学会了偷懒。解决用class_weightbalanced或者用imblearn的 SMOTE 过采样但注意 SMOTE 只能在训练集上做测试集保持原始分布。评估指标换成 AUC、F1、recall。4.3 聚类结果不可复现每次跑出来簇的编号不一样现象两次运行 KMeans簇 0 的含义变了。原因KMeans 初始中心随机且簇编号本身没有固定顺序。解决固定random_state和n_init并且在解释结果时不要依赖簇编号而是看每个簇的统计特征。如果要做对比用cluster_centers_排序后重新映射编号。4.4 年份字段处理错误把年份当连续变量直接回归现象模型认为 2020 年比 2019 年“大”但掉星概率不是线性增长的。原因年份是时间变量直接当数值特征会引入错误的线性假设。解决把年份做分箱比如每 3 年一箱或者提取“评审周期”特征。如果样本年份跨度小可以直接 one-hot。4.5 可视化图表没有业务含义现象画了 PCA 散点图但看不出任何结论。原因聚类特征里 one-hot 太多PCA 降维后主成分解释性差。解决聚类后用雷达图或分组柱状图展示每个簇的星级、价格、菜系分布比 PCA 散点图更直观。课程设计答辩时老师更想看“你发现了什么”而不是“你画了什么”。5. 从课程设计到可复现项目三个让报告加分的技巧5.1 用管道把预处理和模型串起来课程设计里常见的问题是预处理代码和模型代码混在一起换一份数据就跑不通。用sklearn.pipeline.Pipeline把标准化、编码、模型串成一条流水线既能避免数据泄漏又方便交叉验证。from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score # 数值列和类别列分开处理 num_cols [price_level, year] cat_cols [main_cuisine, country] preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) pipe Pipeline([ (prep, preprocessor), (clf, RandomForestClassifier( n_estimators200, max_depth8, class_weightbalanced, random_state42 )) ]) # 5折交叉验证scoring 用 roc_auc scores cross_val_score(pipe, model_df[num_cols cat_cols], model_df[drop], cv5, scoringroc_auc) print(CV AUC:, scores.mean(), /-, scores.std())handle_unknownignore是防止测试集出现训练集没见过的菜系时报错课程设计数据量小这个参数很关键。交叉验证的 AUC 均值和标准差比单次划分更可靠写进报告里也更有说服力。5.2 把模型保存下来做一个最小预测接口课程设计答辩时如果能现场演示“输入一家餐厅的信息输出掉星概率”效果会好很多。用joblib保存管道再写一个简单的预测函数。import joblib import pandas as pd # 保存管道 joblib.dump(pipe, michelin_drop_pipe.pkl) # 加载并预测新样本 pipe_loaded joblib.load(michelin_drop_pipe.pkl) new_restaurant pd.DataFrame([{ price_level: 4, year: 2023, main_cuisine: French, country: France }]) prob pipe_loaded.predict_proba(new_restaurant)[0, 1] print(f掉星概率: {prob:.2%})注意新样本的列名和顺序必须和训练时一致ColumnTransformer会按列名匹配所以用 DataFrame 而不是 numpy 数组传入。这个接口可以包成 Flask 或 Streamlit但课程设计里命令行演示已经够用。5.3 报告里必须有的三张表第一张是数据清洗前后的样本量和字段对比证明你做了清洗第二张是不同模型的 AUC、F1、recall 对比证明你做了选型第三张是聚类后每个簇的星级、价格、菜系分布证明你做了业务解读。这三张表比任何文字描述都有力。我一般还会加一张特征重要性排序图放在附录里。最后说一个我自己的习惯每次跑完模型先把random_state固定然后把完整输出保存到日志文件包括数据形状、类别分布、模型参数、评估指标。课程设计代码会被反复修改没有日志的话改到后面根本记不清哪次结果是有效的。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →