news 2026/10/20 15:08:58

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于机器学习的米其林餐厅数据挖掘:从特征工程到关联规则实战

简介:这份资源是面向高校计算机及相关专业学生的机器学习与数据挖掘课程设计完整项目,以米其林餐厅数据为分析对象,帮助学习者通过真实案例掌握数据清洗、特征选择与分类模型构建等核心技能。项目在导师指导下完成并获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", encoding="utf-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(ascending=False) print(missing_rate[missing_rate > 0.6])

这段代码的逻辑是先做类型分离,再对类别字段看取值数量和头部分布,最后看缺失率。参数上,encoding要根据实际文件调整,常见是utf-8或latin-1;nunique()返回唯一值数量,如果某个类别字段有上百个取值,说明需要做类别合并或目标编码;缺失率阈值 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_unknown="ignore"), ["city_grouped", "cuisine"]), ("text", TfidfVectorizer(max_features=500), "review_text"), ] )

这段代码做了三件事:价格清洗、文本长度构造、高基数类别合并,最后用ColumnTransformer把三条处理管线并行组织。参数上,nlargest(20)里的 20 可以根据数据量调整,数据量大可以放到 50;TfidfVectorizer的max_features=500是控制文本特征维度的常用值,太大容易过拟合,太小会丢信息;handle_unknown="ignore"保证预测时遇到训练集没见过的类别不会报错。整个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_iter=1000, multi_class="multinomial")) ]) # 树模型:随机森林,能输出特征重要性 rf = Pipeline([ ("prep", preprocessor), ("clf", RandomForestClassifier(n_estimators=200, random_state=42)) ]) # 分层交叉验证,保证每折里各类别比例一致 cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for name, model in [("LR", baseline), ("RF", rf)]: scores = cross_val_score(model, df.drop(columns=["star"]), df["star"], cv=cv, scoring="f1_macro") print(name, scores.mean(), scores.std())

这段代码搭了两个Pipeline,用StratifiedKFold做五折交叉验证,评估指标选f1_macro。为什么用 macro F1 而不是准确率?因为米其林星级分布通常不均衡,三星餐厅远少于一星,准确率会被多数类带偏。max_iter=1000是逻辑回归防止不收敛的常用设置;n_estimators=200是随机森林的树数量,再多收益递减;random_state=42保证结果可复现,课程设计报告里写「随机种子固定为 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-learn==1.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, annot=True, fmt="d", cmap="Blues", xticklabels=rf.classes_, yticklabels=rf.classes_) plt.xlabel("Predicted") plt.ylabel("Actual") plt.title("Confusion Matrix") plt.savefig("docs/confusion_matrix.png", dpi=150)

classification_report会输出每个类别的 precision、recall、f1-score,比整体准确率细得多;confusion_matrix配合seaborn热力图,一眼看出模型在哪个类别上翻车。dpi=150保证图片放进 Word 文档不糊。这些图直接贴进课程设计报告,比纯文字描述强。

4. 避坑与排查:那些让课程设计卡三天的常见问题

4.1 中文路径和编码问题导致读取失败

现象:pd.read_csv报UnicodeDecodeError或文件明明存在却提示FileNotFoundError。原因:Windows 下中文路径或 CSV 文件本身是gbk编码,而 pandas 默认用utf-8。解决:读取时显式指定encoding="gbk"或encoding="latin-1",路径尽量用英文;如果必须用中文路径,在 Python 里用pathlib.Path处理,避免字符串拼接。

4.2 训练集和测试集预处理不一致

现象:交叉验证分数很高,但用predict.py单独预测时结果离谱。原因:训练时用了StandardScaler标准化,预测时忘了对新数据做同样的变换。解决:把所有预处理步骤封进Pipeline,训练和预测用同一个Pipeline对象,joblib.dump保存整个 Pipeline 而不是只保存分类器。

4.3 类别不平衡导致模型只预测多数类

现象:准确率看着不错,但混淆矩阵显示所有样本都被预测成一星。原因:三星餐厅样本太少,模型学到「全猜一星」就能拿高准确率。解决:用class_weight="balanced"让模型关注少数类,或者用 SMOTE 做过采样,评估指标换成 macro F1 而不是准确率。

4.4 文本特征维度爆炸拖慢训练

现象:加入评语 TF-IDF 后,训练时间从几秒变成几分钟,内存飙升。原因:TfidfVectorizer默认保留所有词,几万条评语能产生几十万维特征。解决:设置max_features=500到2000,加min_df=5过滤低频词,用stop_words="english"去掉无意义词。如果还慢,考虑用HashingVectorizer替代。

4.5 随机种子不固定导致结果无法复现

现象:每次跑代码准确率都不一样,报告里写的数据对不上。原因:train_test_split、随机森林、XGBoost 都有随机性,没设种子。解决:所有涉及随机的函数都传random_state=42,包括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 = [f"cuisine_{row['cuisine']}", f"city_{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(f"star_{row['star']}") transactions.append(items) te = TransactionEncoder() te_array = te.fit(transactions).transform(transactions) df_trans = pd.DataFrame(te_array, columns=te.columns_) # 找频繁项集,最小支持度 0.05 freq = apriori(df_trans, min_support=0.05, use_colnames=True) rules = association_rules(freq, metric="lift", min_threshold=1.2) print(rules[["antecedents", "consequents", "support", "confidence", "lift"]].head(10))

这段代码先把每条餐厅记录转成标签列表,再用TransactionEncoder转成布尔矩阵,apriori找频繁项集,association_rules生成规则。参数上,min_support=0.05表示项集至少在 5% 的记录中出现,太低会产出大量无意义规则,太高可能什么都找不到;metric="lift"和min_threshold=1.2表示只保留提升度大于 1.2 的规则,lift 大于 1 说明前件和后件正相关。输出里的support是支持度,confidence是置信度,lift是提升度,三个指标一起看才能判断规则有没有业务价值。

我一般会重点看 lift 最高的几条规则,比如「cuisine_French + price_high → star_3」的 lift 是 2.5,说明法餐加高价和三星的关联比随机情况强 2.5 倍。这种规则写进课程设计报告,比单纯说「随机森林准确率 85%」更有数据挖掘的味道。注意关联规则挖掘对离散化方式很敏感,价格阈值设 150 还是 200,结果可能完全不同,报告里要说明阈值选择依据,比如「按中位数分档」或「按业务常识分档」。

从那以后我每次做数据挖掘课程设计,都强制自己先跑一遍关联规则,哪怕主任务是分类——因为它经常能挖出分类模型看不到的组合模式,答辩时老师一问「你有什么额外发现」,这就是现成答案。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 4:34:35

CMake报错nmake找不到?Windows工具链环境排查与解决

如果你在 Windows 上跑 CMake,大概率迟早会撞见这么一条报错:CMake Error: Running nmake -? failed with: no such file or directory我第一次碰到它时直接被绕懵了:机器上 VS 装得好好的,C 桌面开发组件也勾了,怎么…

作者头像 李华
网站建设 2026/10/21 4:35:13

社区网络规划设计方案:VLAN划分、交换机配置与IP地址分配实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 9:59:13

一专多长:普通人如何科学规划年度学习方向与技能成长

1. 为什么说“一专多长”是普通人最值得下注的成长策略每年年初,朋友圈都会被各种学习Flag刷屏。有人一口气报了五个课程,有人列了长达三页的书单,但到了三月份再看,大部分都已经静悄悄。我见过太多这样的案例,也踩过同…

作者头像 李华
网站建设 2026/10/19 23:38:18

PHQ9抑郁自评去哪做

PHQ-9 抑郁症状自评去哪做 想在网上做 PHQ-9 抑郁自评,要的是量表本身正规、答案不被收集、结果边界写明。明白健康(https://healviews.com/)的测评中心里有 PHQ-9 抑郁症状自评 https://healviews.com/assessments/phq9 :9 题、约…

作者头像 李华
网站建设 2026/10/17 13:40:02

基于PLC的污水处理组态王动画仿真系统设计

1. 这套系统到底在做一件什么事接触过工控项目的人应该都有感受:PLC程序好写,画面也能画,但真正把“设备控制逻辑”和“上位机监控画面”打通,让泵该起的时候起、该停的时候停,画面上所有状态和趋势跟现场一走——这个…

作者头像 李华