拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DeepSeek实战:从千万条餐饮评论到菜单优化决策

DeepSeek实战:从千万条餐饮评论到菜单优化决策

简介:以餐饮业为场景的完整实战案例文档,面向餐饮从业者、数据分析师及大模型应用学习者,展示如何借力DeepSeek处理千万条用户评论,并据此优化菜品菜单。资源为单个PDF文件,共26页,压缩包大小约1.86MB,排版与图表显示正常,阅读顺畅。案例从餐饮行业的数据驱动必要性切入,系统讲解评论数据收集与预处理、DeepSeek模型原理与集成、情感分析、主题挖掘、关联分析,再落到具体菜单调整策略,同时提供关键代码实现与优化前后业务指标对比,结构完整、可复用性强。读者既能理解大模型落地餐饮业务的方法论,也能参考其分析流程与决策思路,掌握从数据清洗、特征提取到套餐设计、成本利润平衡的完整链路,适合需要从数据中挖掘用户偏好、提升菜品销量的实际业务场景。已有100人学习下载。

1. 千万条评论堆在后台,菜单却还是老板拍脑袋定的

DeepSeek这个名字最近在餐饮圈子里出现的频率越来越高,但多数人的认知停留在“它是个能聊天的AI”这一层。这份26页的案例文档讲的是一条更硬核的路径:从外卖平台、点评网站抓取千万条真实评论,做清洗、分词、情感分析、主题挖掘和关联规则分析,最后把结论直接落回菜单——哪个菜该留,哪个菜该降权,哪个新菜值得试。我读完最大的感受是:这不是一篇科普,而是一条可以照着复现的技术流水线。适合三类人看:手里握着大量评论数据但不知道怎么用的餐饮运营,想给客户做数据化菜单咨询的服务商,以及想拿真实业务场景练手大模型微调的算法工程师。

2. 数据收集管道:四个来源与采集方式的关键取舍

2.1 四种评论来源的特点与适用场景

评论数据不是越多越好,来源结构决定了后续分析能回答什么问题。案例里把数据来源拆成四类:外卖平台(美团、饿了么)、餐厅官方网站和社交媒体账号(微信公众号、微博、抖音)、点评类网站(大众点评)、在线旅游平台(携程、去哪儿)。这四类数据在分析价值上有明显分工。

外卖平台的评论集中在菜品口味、包装、配送速度上,适合回答“出餐体验”类问题;社交媒体上的评论更分散,但包含消费者对品牌形象、新品话题的讨论;点评类网站的评论结构化程度最高,有评分、有文字、有消费场景标签,是做情感分析的主力数据源;在线旅游平台的评论对景区周边餐厅尤其关键,游客更爱提“特色”“当地人推荐”这类词,是挖掘地方菜创新的富矿。

我自己的经验是:第一步先别急着写爬虫,先盘点手上能合法拿到哪些数据。很多连锁餐饮其实已经有了外卖平台的后台导出权限,但一直没往下游做过分析。文档里建议的优先级是:先走API,再考虑爬虫,最后才补人工收集。这个顺序是对的,API拿到的数据字段规整、带时间戳,省掉大量清洗工作。

2.2 网络爬虫与API调用的落地写法

文档里给出了一段基于requests和BeautifulSoup的爬虫示例,这属于入门级写法,但思路值得保留。我一般会在这个基础上加三样东西:请求头伪装、随机延时、异常重试。

import requests from bs4 import BeautifulSoup import time import random def fetch_reviews(url, max_retries=3): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } for attempt in range(max_retries): try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: soup = BeautifulSoup(response.text, 'html.parser') reviews = soup.find_all('div', class_='review') return [r.text.strip() for r in reviews] else: print(f'请求失败,状态码:{response.status_code}') except Exception as e: print(f'第{attempt + 1}次请求异常:{e}') time.sleep(random.uniform(1, 3)) return [] reviews = fetch_reviews('https://example.com/reviews') print(f'抓取到{len(reviews)}条评论')

这里的关键参数是timeout设为10秒,防止某个页面卡死拖垮整个抓取进程;random.uniform(1, 3)是每次请求之间的随机延时,用来降低被平台风控的概率。max_retries控制异常重试次数,网络抖动时能自动恢复。另外一个容易忽略的点:爬虫抓到的HTML文本里经常混着CSS类名和隐藏节点,BeautifulSoup的选择器要提前用浏览器开发者工具确认,不然抓回来一堆空值。

2.3 数据预处理:从清洗到分词的固定动作

无论是API还是爬虫拿到的数据,进模型前都要过一遍清洗流水线。文档里按顺序列了四步:去HTML标签和特殊字符、数据归一化(统一大小写和日期格式)、缺失值处理、中文分词。这四步的顺序有讲究——先做规则清洗,再做归一化,最后才分词,顺序反了会导致分词结果里残留无意义字符。

import re import jieba def clean_review(text): text = re.sub(r'<.*?>', '', text) # 去HTML标签 text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', text) # 保留中文、字母、数字 text = re.sub(r'http\S+', '', text) # 去链接 return text.strip() def tokenize_review(text): return jieba.lcut(text) sample = '<p>这道菜的口味非常不错!下次还会再点~</p>' cleaned = clean_review(sample) tokens = tokenize_review(cleaned) print(tokens)

正则表达式里我特意改成了保留中文字符集:[^\u4e00-\u9fa5a-zA-Z0-9\s]。原始文档用的是[^\w\s],这个写法在纯英文场景没问题,但中文评论里会把“好吃!”的感叹号去掉后留下一个空字符,影响后续分词的连贯性。分词用jieba.lcut而不是jieba.cut,前者直接返回列表,省一行转换代码。对于餐饮评论,我建议加载jieba的自定义词典,把菜名、品牌名、菜品简称加进去,不然“夫妻肺片”“毛血旺”这类词容易被切碎。

3. 特征提取的三层递进:从词频到词嵌入再到降维

3.1 词频统计与TF-IDF:先看大家在聊什么

特征提取是整个分析流程里最容易被低估的一步。很多新手拿到清洗好的数据就直接丢给模型,结果训练出来的情感分类器只能识别“好吃”“难吃”两个词,换个说法就失灵。原因是评论文本里大量信息藏在低频词里,单纯的词频统计会把“不错”“还行”这类高频泛化词顶上榜首,而这些词对区分菜品好坏几乎没有贡献。

from collections import Counter from sklearn.feature_extraction.text import TfidfVectorizer # 词频统计:快速感知整体话题 all_words = [] for review in data['review_content'].tolist(): all_words.extend(jieba.lcut(review)) word_freq = Counter(all_words) print(word_freq.most_common(20)) # TF-IDF:定位有区分度的关键词 vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2)) tfidf_matrix = vectorizer.fit_transform(data['review_content']) feature_names = vectorizer.get_feature_names_out()

TF-IDF的两个参数值得展开说。max_features=5000是控制特征维度的上限,千万条评论的原始词表规模可能到几十万,全量做TF-IDF矩阵会让内存直接爆掉,而且大量生僻词对模型是噪声。我一般会先用词频统计跑一遍,把出现次数低于5次的词直接过滤掉,再做TF-IDF。ngram_range=(1, 2)是同时保留单字词和双字词组,像“不新鲜”这种三字词组需要调到(2, 3),但维度会指数增长,要权衡。

3.2 词嵌入与句嵌入:让模型理解语义

词频和TF-IDF解决的是“词有没有出现”的问题,解决不了“词和词在语义上是否相似”。比如“咸了”“太咸”“盐放多了”在字面上几乎没有重合,但表达的是同一个意思。这一步要靠词嵌入来兜底。

from gensim.models import Word2Vec import numpy as np sentences = [jieba.lcut(review) for review in data['review_content']] model = Word2Vec(sentences, vector_size=128, window=5, min_count=3, workers=4) def get_review_vector(review): vectors = [] for word in jieba.lcut(review): if word in model.wv: vectors.append(model.wv[word]) if vectors: return np.mean(vectors, axis=0) return np.zeros(model.vector_size) data['review_vector'] = data['review_content'].apply(get_review_vector)

Word2Vec的参数需要根据语料规模调。vector_size=128是对千万级评论比较折中的维度选择,再大效果提升有限但内存开销翻倍。min_count=3意味着出现次数少于3次的词直接丢弃,这些低频词多半是错别字或一次性表达,训练进去只会拉低向量质量。window=5控制上下文窗口,即一个词跟前后5个词之间的关联强度,对餐饮短评来说这个值够用,窗口太大反而会把不相关的词扯到一起。

3.3 PCA降维与特征选择:控制计算成本

词嵌入得到的评论向量维度通常是128维,如果特征工程做得更细,把TF-IDF特征和词嵌入特征拼接起来,维度可能到几千。直接拿去训练模型不是不行,但训练时间和过拟合风险都会上去。文档里给的方案是用PCA降到50维,这个思路在工程上是标准的。

from sklearn.decomposition import PCA review_vectors = np.array(data['review_vector'].tolist()) pca = PCA(n_components=50, random_state=42) reduced_vectors = pca.fit_transform(review_vectors) data['reduced_vector'] = list(reduced_vectors)

PCA之前最好做一步标准化,不然数值范围大的特征会主导主成分的计算。可以先用StandardScaler对review_vectors做标准化再喂给PCA。另外random_state=42必须固定,否则每次运行生成的降维结果不一样,后续模型训练的可复现性就无法保证。

提示:降维不是越多越好。PCA会损失信息,降到多少维可以通过累计解释方差比来判断——一般保留累计解释方差90%以上的维度数。

4. DeepSeek微调实战:情感分析、主题挖掘与关联规则

4.1 预训练模型选型与微调参数

文档里建议用bert-base-chinese作为中文评论分析的底座模型,理由是它在长文本语义捕捉上比传统词向量模型强一个量级。这里需要澄清一个容易混淆的点:DeepSeek本身是通用大模型,但在这种任务里更常见的做法是借用Hugging Face生态加载一个中文预训练模型来做迁移学习,而不是直接调用DeepSeek的对话接口。

from transformers import AutoModelForSequenceClassification, AutoTokenizer model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

num_labels=2意味着二分类——正面评论和负面评论。如果你的分析需要更细的粒度,比如分成“满意、一般、不满意”三档或“口味、服务、环境、价格”四类,就改成对应的数字。分类粒度越细,需要标注的训练样本就越多,这一点在准备数据之前就要想清楚。

4.2 评论数据集的封装与训练流程

微调预训练模型最关键的是把原始文本转成模型能吃的格式。文档里的ReviewDataset类封装了这一过程,我在此基础上补充了训练集与验证集的切分逻辑。

import torch from torch.utils.data import DataLoader, Dataset from transformers import AdamW class ReviewDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_length=128): self.encodings = tokenizer( texts, truncation=True, padding='max_length', max_length=max_length, return_tensors='pt' ) self.labels = torch.tensor(labels, dtype=torch.long) def __len__(self): return len(self.labels) def __getitem__(self, idx): return { 'input_ids': self.encodings['input_ids'][idx], 'attention_mask': self.encodings['attention_mask'][idx], 'labels': self.labels[idx] } # 按8:2切分训练验证集 split_idx = int(len(texts) * 0.8) train_texts, val_texts = texts[:split_idx], texts[split_idx:] train_labels, val_labels = labels[:split_idx], labels[split_idx:] train_dataset = ReviewDataset(train_texts, train_labels, tokenizer) val_dataset = ReviewDataset(val_texts, val_labels, tokenizer) train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=16)

max_length=128是个工程上的平衡点。餐饮评论文本大多在几十个字以内,128个token足够覆盖绝大多数样本,同时把计算量控制住。batch_size=16对应的是12GB左右显存的入门级显卡,显存更大的话可以开到32。学习率用2e-5,这个值是BERT系列微调的经验最优,调大容易发散,调小收敛太慢。训练轮数建议3个epoch起步,观察验证集loss不再下降就提前停止。

4.3 情感分析、主题挖掘与关联分析的联动

情感分析结束后,手上有的是每条评论的正面/负面标签。但光知道“负面评论占了30%”没用,得知道负面集中在哪个维度。这时候就要做主题挖掘,把负面评论再按主题聚类。文档里关联规则挖掘这个点容易被忽略,但其实价值很高——它能回答“哪个菜和哪个菜经常被一起表扬或一起吐槽”。

主题挖掘常见做法是LDA,但LDA对短文本效果不稳定。我踩过几次坑之后,反而更推荐一个土办法:拿情感分类结果里的负面评论做高频词统计,人工看一下集中在哪些菜品词上。把贬义形容词和菜品词做个共现矩阵,比LDA更直观,也更容易给餐饮老板讲明白。关联规则挖掘可以用Apriori算法,评分最低的菜品和配送慢这类服务负面评价之间的关联规则,往往能指向真正需要改的运营环节。

5. 避坑指南:从数据到菜单的五个高频翻车点

5.1 分词把菜品名切碎了

现象:jieba分词后,“辣子鸡丁”变成了“辣子”“鸡丁”,“水煮鱼”变成了“水煮”“鱼”,词频统计结果完全没法指向具体菜品。

原因:jieba的默认词典没有包含餐饮领域的菜品名实体,对专有名词的识别能力偏弱。

解决:加载自定义词典。把餐厅在售菜单的菜品名整理成txt文件,每行一个菜名加词频权重,用jieba.load_userdict('dishes.txt')加载。菜品名越全,后续情感分析绑定到具体菜品的准确率越高。

5.2 重复评论没去干净,热门菜品频次虚高

现象:某道菜的正向评论数量是其他菜品的几十倍,运营觉得这道菜是爆款,结果一看销量平平。

原因:不同平台之间同一段文案反复出现,或者同一用户多次提交同样评价。用drop_duplicates只做内容去重,没考虑语义相同但文字略有差异的情况。

解决:第一层用subset=['review_content']去重;第二层用embedding向量做相似度过滤,两条评论向量余弦相似度超过0.95的只保留一条。这一步对千万级数据是必要的,否则词频和情感统计都会失真。

5.3 情感标注不一致导致模型评估虚高

现象:验证集上准确率95%以上,但上线后对真实评论的判断明显不对。

原因:训练数据的情感标签标注标准不一致——有人认为“一个人来的”是中性描述,有人标成负面(因为没人陪)。标注的主观性直接传导给模型。

解决:标注规范要写死。我用的规则是:明确出现褒义/贬义关键词的才标正/负,描述性内容标中性,三分类在训练时用num_labels=3。标注完成后再随机抽200条做一致性检查,两个人标注结果的Kappa系数低于0.7就退回重标。

5.4 模型在GPU上训练时显存溢出

现象:CUDA out of memory,程序直接崩掉。

原因:max_length=128、batch_size=16是BET-base的资源预算,如果你的数据集中有大量超长评论被截断后信息丢失,或者显卡只有6GB显存,原来的配置就跑不动。

解决:第一步batch_size降到8,第二步max_length降到96,第三步开启梯度累积,每两步更新一次梯度。如果还溢出,就换用albert-base-chinese这类参数更少的模型,效果损失在可接受范围内。

5.5 菜单优化后短期销量涨了但客单价降了

现象:砍掉差评菜、主推好评菜后,总销量上去半个月,月底一算利润反而缩水了。

原因:情感分析优化的是“顾客满意度”而不是“利润”。得分最高的菜往往是价格偏低的引流品,砍掉高毛利但口碑平稳的菜,客单价自然往下掉。

解决:菜单优化不能只看情感得分这一个维度。要把毛利率、食材成本、出餐复杂度加进来做个综合评分,再决定菜品的去留。情感得分是方向盘,毛利率是油门,两个指标要一起看。

6. 效果评估落地:从业务指标对比到菜单复盘的操作框架

6.1 三类业务指标的对比框架

优化菜单的最终价值要落到业务数字上,不然分析做得再漂亮也只是PPT。文档里给的评估维度有销售额、客流量、菜品销量、顾客满意度,这个结构可以直接实操。

指标类别具体指标对比维度数据来源
销售额总销售额、时段销售额优化前30天 vs 优化后30天收银系统
客流量总客流、新老客占比优化前30天 vs 优化后30天会员系统
菜品销量单品销量、退菜率优化前30天 vs 优化后30天菜单点单记录
满意度星级评分、负面评论占比优化前30天 vs 优化后30天评论平台

时间窗口的选择要认真一点。餐饮有天然的周期波动,工作日和周末的客群结构完全不同,直接拿优化后一周和优化前一周比,数据里混着太多噪声。我习惯取30天作为对比窗口,横跨至少两个完整周末。

6.2 用Pandas做优化前后的对比实操

业务指标对比用Pandas就能完成,不需要额外工具。下面这段代码是拿来即用的对比模板。

import pandas as pd # 加载优化前和优化后的日度经营数据 before_df = pd.read_csv('business_before.csv', parse_dates=['date']) after_df = pd.read_csv('business_after.csv', parse_dates=['date']) # 计算核心指标 metrics = ['total_sales', 'customer_count', 'avg_order_value'] summary = pd.DataFrame({ '优化前均值': before_df[metrics].mean(), '优化后均值': after_df[metrics].mean(), }) # 计算变化幅度 summary['变化幅度'] = (summary['优化后均值'] / summary['优化前均值'] - 1) * 100 print(summary.round(2)) # 单菜品销量对比:找出优化后真正跑出来的菜品 dish_sales = pd.merge( pd.read_csv('dish_before.csv'), pd.read_csv('dish_after.csv'), on='dish_name', suffixes=('_前', '_后') ) dish_sales['变化率'] = (dish_sales['销量_后'] / dish_sales['销量_前'] - 1) * 100 top_gainers = dish_sales.nlargest(10, '变化率')[['dish_name', '销量_前', '销量_后', '变化率']] print(top_gainers)

对比的陷阱在于很容易忽略季节性因素。如果优化后的30天恰好包含节假日,销售额的上涨可能根本不是菜单调整的功劳。我的习惯是再拉去年同期数据做参照,三组数据放在一起看,才能把真实影响和自然波动区分开。

数值波动大不代表结论有问题,先看数据口径是否统一、有没有异常值,比如某一天的缺货记录把单品销量打到0,这类脏数据要清理掉再下结论。做完定量对比之后,再把评论情感分析的结果拿来对照——如果某道菜销量涨了但负面评论也涨了,说明是促销拉动的短期热度,不是菜品本身真正赢得了顾客。

从那以后我每次做完菜单优化项目,都会强制自己走一遍“先洗评论、再标标签、再调模型、再对业务指标”的完整循环,缺一步都不算结束。分析做得再细致,最后落到菜单上的动作无效,那就是白干。这份案例文档的价值就在于把从数据到决策的每个环节都串了起来,照着走一遍,能省掉不少自己摸索的时间。希望帮到你。

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

返回列表