2026/10/10 5:00:44

基于机器学习的米其林餐厅数据挖掘:从特征工程到关联规则实战

基于机器学习的米其林餐厅数据挖掘:从特征工程到关联规则实战 简介这份资源是面向高校计算机及相关专业学生的机器学习与数据挖掘课程设计完整项目以米其林餐厅数据为分析对象帮助学习者通过真实案例掌握数据清洗、特征选择与分类模型构建等核心技能。项目在导师指导下完成并获98分适合课程设计、期末项目或实践能力提升场景。压缩包共56个文件约276KB以Java源码、FreeMarker模板、CSS样式、CSV数据集、SQL脚本及配置文件为主涵盖数据预处理、特征工程、模型训练与可视化展示的模块化流程另附使用手册与代码注释。目前已有53人学习下载。读者可获得一套可直接运行的完整项目方案包括多份米其林餐厅评级数据、数据库建表脚本、前端展示页面与备份文件便于理解机器学习项目从需求分析到系统实现的完整生命周期并对照文档快速复现与二次开发。1. 从一份课程设计说起米其林餐厅数据挖掘到底在挖什么前阵子帮一个学弟看他的课程设计题目是「基于机器学习的米其林餐厅数据挖掘系统」。他一开始以为就是爬点餐厅名单、画几张图交差结果导师一句「要有模型、要有评估、要有可复现的流程」直接把他整不会了。这份源码包和配套文档解决的正是这个场景它把米其林餐厅数据从采集、清洗、特征工程到机器学习建模、结果可视化串成一条完整链路适合正在做数据挖掘或机器学习课程设计的学生也适合想拿一个真实业务数据集练手的入门工程师。米其林餐厅数据本身很有意思——星级、价位、菜系、地理位置、评语文本混在一起既有结构化字段又有非结构化文本天然适合做分类、聚类和关联分析。你拿到的不只是一堆代码而是一套能跑通、能改参数、能写进报告的方法论。下面我按「这是什么、怎么用、坑在哪」的顺序把这份资源拆开讲清楚。2. 数据挖掘流程拆解从原始字段到可训练特征矩阵2.1 先搞清楚数据里有什么再决定怎么挖拿到任何数据挖掘项目第一步永远不是写模型而是把数据字段摸清楚。米其林餐厅数据常见字段包括餐厅名称、所在城市与国家、星级一星/二星/三星/必比登推荐、人均价格、菜系分类、地址经纬度、评语文本、获奖年份等。这些字段里星级是典型的多分类标签价格和经纬度是数值特征菜系和城市是类别特征评语文本则需要走自然语言处理路线。很多人一上来就pd.read_csv然后直接fit结果模型准确率上不去回头一看是标签编码把「三星」编成了 2、「一星」编成了 0模型学到的序关系根本不存在。正确做法是先做字段类型盘点把字段分成数值型、类别型、文本型三类再分别设计处理策略。import pandas as pd # 读取原始数据注意编码米其林数据常含法语/英语特殊字符 df pd.read_csv(michelin_restaurants.csv, encodingutf-8) # 字段类型盘点数值、类别、文本分开看 num_cols df.select_dtypes(include[int64, float64]).columns.tolist() cat_cols df.select_dtypes(include[object]).columns.tolist() # 打印每个类别的取值分布判断是否需要合并稀有类别 for col in cat_cols: print(col, df[col].nunique(), df[col].value_counts().head(5).to_dict()) # 检查缺失率超过 60% 的字段考虑直接丢弃 missing_rate df.isnull().mean().sort_values(ascendingFalse) print(missing_rate[missing_rate 0.6])这段代码的逻辑是先做类型分离再对类别字段看取值数量和头部分布最后看缺失率。参数上encoding要根据实际文件调整常见是utf-8或latin-1nunique()返回唯一值数量如果某个类别字段有上百个取值说明需要做类别合并或目标编码缺失率阈值 60% 是我一般会用的经验值低于这个值的字段可以用填充保留高于这个值的字段强行填充反而引入噪声。2.2 特征工程把餐厅的「身份」翻译成模型能读的数字特征工程是数据挖掘里最见功力的环节。米其林餐厅数据里星级是标签但价格、菜系、城市、评语长度、获奖次数都可以做成特征。数值特征做标准化或归一化类别特征做独热编码或目标编码文本特征做 TF-IDF 或词向量。这里有个容易翻车的地方价格字段经常带货币符号和千分位逗号直接astype(float)会报错。我一般会先写一个清洗函数把非数字字符去掉再转数值。另外城市和菜系这类高基数类别特征独热编码会让特征维度爆炸常见做法是保留出现频率最高的前 N 个类别其余归为「其他」。import re import numpy as np from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.feature_extraction.text import TfidfVectorizer # 清洗价格字段去掉货币符号、逗号、空格再转 float def clean_price(x): if pd.isna(x): return np.nan return float(re.sub(r[^\d.], , str(x))) df[price_clean] df[price].apply(clean_price) # 构造文本长度特征评语长度往往和餐厅热度相关 df[review_len] df[review_text].fillna().apply(len) # 高基数类别只保留 Top 20其余归为 Other top_cities df[city].value_counts().nlargest(20).index df[city_grouped] df[city].where(df[city].isin(top_cities), Other) # 用 ColumnTransformer 把数值、类别、文本三条管线拼起来 preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), [price_clean, review_len]), (cat, OneHotEncoder(handle_unknownignore), [city_grouped, cuisine]), (text, TfidfVectorizer(max_features500), review_text), ] )这段代码做了三件事价格清洗、文本长度构造、高基数类别合并最后用ColumnTransformer把三条处理管线并行组织。参数上nlargest(20)里的 20 可以根据数据量调整数据量大可以放到 50TfidfVectorizer的max_features500是控制文本特征维度的常用值太大容易过拟合太小会丢信息handle_unknownignore保证预测时遇到训练集没见过的类别不会报错。整个preprocessor可以直接塞进Pipeline和后面的分类器串在一起避免训练和预测阶段预处理不一致——这是很多人手写预处理时最容易踩的坑。2.3 模型选型分类、聚类还是关联规则别拿锤子找钉子米其林餐厅数据挖掘能做的任务不止一个。如果目标是预测星级那是多分类问题常用逻辑回归、随机森林、XGBoost如果目标是发现餐厅分组那是聚类问题K-Means 或 DBSCAN 都行如果目标是找「什么菜系和什么价位经常一起出现」那是关联规则挖掘Apriori 算法可以上场。课程设计里最常见的是分类任务因为评估指标明确报告好写。我一般会先用逻辑回归跑一个基线再用随机森林看特征重要性最后用 XGBoost 冲一下指标。不要一上来就上深度学习几百条到几千条数据用树模型足够神经网络反而容易过拟合。from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score, StratifiedKFold from sklearn.pipeline import Pipeline # 基线模型逻辑回归适合看线性可分性 baseline Pipeline([ (prep, preprocessor), (clf, LogisticRegression(max_iter1000, multi_classmultinomial)) ]) # 树模型随机森林能输出特征重要性 rf Pipeline([ (prep, preprocessor), (clf, RandomForestClassifier(n_estimators200, random_state42)) ]) # 分层交叉验证保证每折里各类别比例一致 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) for name, model in [(LR, baseline), (RF, rf)]: scores cross_val_score(model, df.drop(columns[star]), df[star], cvcv, scoringf1_macro) print(name, scores.mean(), scores.std())这段代码搭了两个Pipeline用StratifiedKFold做五折交叉验证评估指标选f1_macro。为什么用 macro F1 而不是准确率因为米其林星级分布通常不均衡三星餐厅远少于一星准确率会被多数类带偏。max_iter1000是逻辑回归防止不收敛的常用设置n_estimators200是随机森林的树数量再多收益递减random_state42保证结果可复现课程设计报告里写「随机种子固定为 42」比写「结果可能略有不同」专业得多。3. 系统落地从 Notebook 到可演示的课程设计系统3.1 把散落的脚本组织成可维护的工程结构很多课程设计源码包打开一看根目录下十几个.ipynb和.py混在一起跑的时候全靠猜哪个先执行。这份资源如果要做成「系统」建议按功能拆目录data/放原始数据和清洗后数据src/放特征工程和模型训练脚本app/放可视化或 Web 演示docs/放课程设计文档。训练脚本用argparse接收参数比如数据路径、模型类型、输出路径这样换数据集不用改代码。我一般还会加一个config.yaml把超参数集中管理调参时不用满文件找数字。# 推荐的目录结构 michelin-mining/ ├── data/ │ ├── raw/ # 原始 CSV │ └── processed/ # 清洗后数据 ├── src/ │ ├── preprocess.py # 清洗与特征工程 │ ├── train.py # 模型训练与评估 │ └── predict.py # 单条预测接口 ├── app/ │ └── dashboard.py # Streamlit 可视化 ├── config.yaml # 超参数配置 └── requirements.txt # 依赖清单这个结构的好处是职责清晰preprocess.py只负责把原始数据变成特征矩阵train.py只负责训练和评估predict.py提供单条推理接口dashboard.py做展示。课程设计答辩时老师问「你的数据清洗在哪」你直接打开preprocess.py比在一堆 Notebook 里翻半天强得多。requirements.txt要写清楚版本号比如scikit-learn1.3.0避免换环境跑不起来。3.2 用 Streamlit 快速搭一个能演示的前端课程设计光有模型不够还得能演示。Streamlit 是最省事的方案几十行代码就能做出交互页面下拉框选城市、滑块调价格区间、输入评语文本实时输出预测星级和置信度。它不需要前端知识pip install streamlit然后streamlit run dashboard.py就能在浏览器打开。注意模型加载要用st.cache_resource缓存否则每次交互都重新加载模型页面卡到没法看。import streamlit as st import joblib import pandas as pd st.cache_resource def load_model(): return joblib.load(models/rf_pipeline.pkl) model load_model() st.title(米其林餐厅星级预测) city st.selectbox(城市, [Paris, Tokyo, New York, Other]) cuisine st.selectbox(菜系, [French, Japanese, Italian, Other]) price st.slider(人均价格, 0, 500, 100) review st.text_area(评语, Great food and service) if st.button(预测): input_df pd.DataFrame([{ city_grouped: city, cuisine: cuisine, price_clean: price, review_text: review, review_len: len(review) }]) pred model.predict(input_df)[0] proba model.predict_proba(input_df).max() st.write(f预测星级{pred}置信度{proba:.2f})这段代码的关键点是st.cache_resource装饰器它保证模型只加载一次输入 DataFrame 的列名必须和训练时ColumnTransformer里定义的列名完全一致否则会报「找不到列」predict_proba返回各类别概率取最大值作为置信度展示。参数上slider的范围根据实际价格分布调整米其林餐厅人均可能到几百欧元范围设太小没意义。这个页面虽然简单但答辩时能现场演示「改价格看预测变化」比只放静态截图有说服力。3.3 评估报告怎么写才不像凑字数课程设计文档里评估部分最容易写成「准确率 85%效果良好」这种废话。合格的评估要包含数据集划分方式训练/验证/测试比例、评估指标准确率、macro F1、混淆矩阵、基线对比逻辑回归 vs 随机森林 vs XGBoost、错误分析哪些类别容易混。混淆矩阵能直观看出「二星被预测成一星」还是「三星被预测成二星」这比一个孤零零的准确率有信息量。我一般还会画特征重要性条形图说明「价格和评语长度是影响星级的关键因素」让报告有业务解释而不是纯技术堆砌。from sklearn.metrics import classification_report, confusion_matrix import matplotlib.pyplot as plt import seaborn as sns # 在测试集上评估 y_pred rf.predict(X_test) print(classification_report(y_test, y_pred)) # 混淆矩阵可视化 cm confusion_matrix(y_test, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsrf.classes_, yticklabelsrf.classes_) plt.xlabel(Predicted) plt.ylabel(Actual) plt.title(Confusion Matrix) plt.savefig(docs/confusion_matrix.png, dpi150)classification_report会输出每个类别的 precision、recall、f1-score比整体准确率细得多confusion_matrix配合seaborn热力图一眼看出模型在哪个类别上翻车。dpi150保证图片放进 Word 文档不糊。这些图直接贴进课程设计报告比纯文字描述强。4. 避坑与排查那些让课程设计卡三天的常见问题4.1 中文路径和编码问题导致读取失败现象pd.read_csv报UnicodeDecodeError或文件明明存在却提示FileNotFoundError。原因Windows 下中文路径或 CSV 文件本身是gbk编码而 pandas 默认用utf-8。解决读取时显式指定encodinggbk或encodinglatin-1路径尽量用英文如果必须用中文路径在 Python 里用pathlib.Path处理避免字符串拼接。4.2 训练集和测试集预处理不一致现象交叉验证分数很高但用predict.py单独预测时结果离谱。原因训练时用了StandardScaler标准化预测时忘了对新数据做同样的变换。解决把所有预处理步骤封进Pipeline训练和预测用同一个Pipeline对象joblib.dump保存整个 Pipeline 而不是只保存分类器。4.3 类别不平衡导致模型只预测多数类现象准确率看着不错但混淆矩阵显示所有样本都被预测成一星。原因三星餐厅样本太少模型学到「全猜一星」就能拿高准确率。解决用class_weightbalanced让模型关注少数类或者用 SMOTE 做过采样评估指标换成 macro F1 而不是准确率。4.4 文本特征维度爆炸拖慢训练现象加入评语 TF-IDF 后训练时间从几秒变成几分钟内存飙升。原因TfidfVectorizer默认保留所有词几万条评语能产生几十万维特征。解决设置max_features500到2000加min_df5过滤低频词用stop_wordsenglish去掉无意义词。如果还慢考虑用HashingVectorizer替代。4.5 随机种子不固定导致结果无法复现现象每次跑代码准确率都不一样报告里写的数据对不上。原因train_test_split、随机森林、XGBoost 都有随机性没设种子。解决所有涉及随机的函数都传random_state42包括train_test_split、StratifiedKFold、RandomForestClassifier。在报告里注明「所有随机过程种子固定为 42」这是可复现性的基本要求。5. 进阶技巧用关联规则挖出「菜系价位」的隐藏组合分类模型告诉你「这家餐厅会不会是三星」但数据挖掘还有一类任务叫关联规则挖掘能回答「哪些特征经常一起出现」。比如「法餐 人均 200 欧以上 评语提到 wine」是不是经常和三星同时出现。Apriori 算法是经典方案mlxtend库可以直接调用。先把数值特征离散化比如价格分低/中/高三档评语长度分短/中/长然后用TransactionEncoder转成布尔矩阵跑apriori找频繁项集再association_rules生成规则。from mlxtend.frequent_patterns import apriori, association_rules from mlxtend.preprocessing import TransactionEncoder # 把每条餐厅记录转成「特征标签」列表 transactions [] for _, row in df.iterrows(): items [fcuisine_{row[cuisine]}, fcity_{row[city_grouped]}] items.append(price_high if row[price_clean] 150 else price_low) items.append(review_long if row[review_len] 200 else review_short) items.append(fstar_{row[star]}) transactions.append(items) te TransactionEncoder() te_array te.fit(transactions).transform(transactions) df_trans pd.DataFrame(te_array, columnste.columns_) # 找频繁项集最小支持度 0.05 freq apriori(df_trans, min_support0.05, use_colnamesTrue) rules association_rules(freq, metriclift, min_threshold1.2) print(rules[[antecedents, consequents, support, confidence, lift]].head(10))这段代码先把每条餐厅记录转成标签列表再用TransactionEncoder转成布尔矩阵apriori找频繁项集association_rules生成规则。参数上min_support0.05表示项集至少在 5% 的记录中出现太低会产出大量无意义规则太高可能什么都找不到metriclift和min_threshold1.2表示只保留提升度大于 1.2 的规则lift 大于 1 说明前件和后件正相关。输出里的support是支持度confidence是置信度lift是提升度三个指标一起看才能判断规则有没有业务价值。我一般会重点看 lift 最高的几条规则比如「cuisine_French price_high → star_3」的 lift 是 2.5说明法餐加高价和三星的关联比随机情况强 2.5 倍。这种规则写进课程设计报告比单纯说「随机森林准确率 85%」更有数据挖掘的味道。注意关联规则挖掘对离散化方式很敏感价格阈值设 150 还是 200结果可能完全不同报告里要说明阈值选择依据比如「按中位数分档」或「按业务常识分档」。从那以后我每次做数据挖掘课程设计都强制自己先跑一遍关联规则哪怕主任务是分类——因为它经常能挖出分类模型看不到的组合模式答辩时老师一问「你有什么额外发现」这就是现成答案。希望帮到你。本文还有配套的精品资源点击获取