当前位置: 首页 > news >正文

从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战

从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战

文章目录

  • 从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战
    • 一、故事的起点
    • 二、LIKE暴力分类:笨但能跑
    • 三、包厢利用率:把时间轴展开
    • 四、如果现在重做:用LLM做分类
      • 4.1 不需要7B,3B就够了
      • 4.2 处理边界情况
      • 4.3 效率对比
    • 五、教训与反思
    • 六、如果现在从头做

一、故事的起点

接手一个餐饮老系统的数据分析任务。打开数据库一看,流水表里几万条记录,只有商品名称、数量、单价、金额、日期。没有分类字段,没有品类标签。

老板说:帮我看一下,哪个品类贡献了最多营收?哪个单品卖得最好?

几千个不同的商品名称,人工分类?不现实。当时用的办法,说出来有点笨——用SQL的LIKE逐个品类往里套

二、LIKE暴力分类:笨但能跑

看看当时写的SQL(保留原样,包括那些注释掉的条件):

insertintocf_flselectdistinct'海鲜类'fl,namesfromcf_product awherenameslike'%虾%'andnamesnotlike'%虾片%'andnamesnotlike'%龙虾味%'

就这?对,就这。

每一类都是一轮"试错→排除→再试错"的循环。注释里密密麻麻全是被推翻的条件:

-- names like '%蟹%' and names not like '%蟹肉棒%'-- names like '%鸡%' and names not like '%鸡蛋%'-- names like '%牛%' and names not like '%牛油%' and names not like '%牛奶%'

为什么这么多排除条件?因为中餐菜名太坑了:

  • “蚂蚁上树”——没有蚂蚁,是肉末粉条(猪肉类)
  • “夫妻肺片”——没有夫妻,是牛肉牛杂(牛肉类)
  • “鱼香肉丝”——没有鱼,是猪肉类
  • “狮子头”——没有狮子,是猪肉类

LIKE只能做字面匹配,理解不了语义。每遇到一个误分类,就得加一个排除条件。几千条数据,光分类就调了两三天。

最终效果是这样的:逐条INSERT进分类表(cf_fl),两个字段——类别和商品名称。一个品类一个品类地攒出来的。

-- 查某个品类的销售情况selecta.names,count(*)as销售次数,sum(numbers)as销售总量,sum(costs)as总营收,max(price)as最高单价,min(price)as最低单价,round(sum(costs)/sum(numbers),2)as客单价fromcf_product a,cf_fl bwherea.names=b.namesandb.fl='海鲜类'groupbya.namesorderby4desc;

跑出来的结果很有意思:同一个菜,最高单价和最低单价能差出好几倍。比如一道"蒜蓉大虾",最高卖过128,最低卖过68。这不是折扣,是定价混乱——不同时段、不同服务员可能录入的价格不一样。这种问题,不分析数据根本发现不了。

三、包厢利用率:把时间轴展开

分类问题解决了,接下来是包厢分析。

数据长这样:

tablename(包厢号)btime(开台时间)etime(结账时间)billid(账单号)
3012022-01-08 18:302022-01-08 22:15XS-220108-0023
3022022-01-08 19:002022-01-09 01:30XS-220108-0025

老板想知道:每个包厢,每天每个小时,是空闲还是占用?

原始数据只有开台和结账时间,要变成"每小时的状态",得自己算。当时的做法——PL/SQL暴力遍历:

declaren_count number;ctimedate;date1date;hour1 number :=0;begindate1 :=to_date('20220101','yyyymmdd');while(date1<to_date('20220401','yyyymmdd'))loopforrecin(select*frombx)loophour1 :=0;while(hour1<24)loopctime :=date1+hour1/24;selectcount(*)inton_countfrombxlogwheretablename=rec.tablenameandctimebetweenbtimeandetime;ifn_count>0theninsertintobxrq(rq,tablename,chour,kyzt)values(date1,rec.tablename,hour1,'有客');elseinsertintobxrq(rq,tablename,chour,kyzt)values(date1,rec.tablename,hour1,'无客');endif;hour1 :=hour1+1;commit;endloop;endloop;date1 :=date1+1;endloop;end;

三个嵌套循环:90天 × 十几间包厢 × 24小时 = 三万多条记录。跑了好几分钟才出结果。

然后用Oracle的PIVOT展开成矩阵:

select*from(selecttablename,rq,chour,decode(kyzt,'无客',1,0)kyfromBXRQ)pivot(sum(ky)forchourin('0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23'))

1表示空闲,0表示占用。一张热力图就出来了。

再按星期汇总:

selectdecode(to_char(rq,'d'),'7','星期六','1','星期日','2','星期一','3','星期二','4','星期三','5','星期四','6','星期五')as星期,count(distinctrq)as总天数,sum(casewhenhour22=1andhour23=1andhour0=1then1else0end)as"晚10点后全满天数"frombxkygroupbydecode(to_char(rq,'d'),'7','星期六','1','星期日','2','星期一','3','星期二','4','星期三','5','星期四','6','星期五')orderby3;

结论很清晰:周六晚上10点到凌晨,几乎所有包厢都满员。周一到周五的下午,大面积空闲。

这不是什么高深的洞察,但它是真实的。不做数据分析,老板只能凭感觉说"周末忙、平时闲",但到底多忙、多闲,哪个时段最空,不知道。有了数据,决策才有依据——空闲时段推低价套餐引流,高峰时段提高最低消费门槛。

四、如果现在重做:用LLM做分类

时隔几年再看这些SQL,最想改的就是分类那块。

LIKE暴力匹配的痛点很明确:

  1. 维护成本高。每新增一个菜品,都要手动判断归哪类。新品上架了,分类表没更新,分析就漏了
  2. 误分类多。"鱼香肉丝"含"鱼"字但不是鱼类,"鸡蛋灌饼"含"鸡"字但不是禽类。排除条件越加越多,越来越难维护
  3. 无法泛化。换一个餐厅,菜名体系完全不同,所有条件得重写

现在用LLM,事情简单得多。

4.1 不需要7B,3B就够了

菜品分类是短文本分类任务:输入一个菜名(通常2-10个字),输出一个类别标签。这在NLP领域是最基础的任务之一。

方案适用场景本任务是否需要
BERT/TextCNN有标注数据,需训练够用,但要标注
LLM 1.5B-3B少样本,零训练最推荐
LLM 7B更复杂的推理杀鸡用牛刀
LLM 70B+通用任务完全没必要

用Qwen2.5-3B甚至更小的模型,本地部署,几行代码搞定:

importjsonfromopenaiimportOpenAI client=OpenAI(base_url="http://localhost:8080/v1",api_key="empty")categories=["海鲜类","牛肉类","猪肉类","禽类","蔬菜类","主食类","酒水类","饮品甜品类","其他"]defclassify_dish(name):resp=client.chat.completions.create(model="qwen2.5-3b",messages=[{"role":"system","content":f"你是餐饮菜品分类助手。将菜名归入以下类别之一:{', '.join(categories)}。只输出类别名称,不要解释。"},{"role":"user","content":f"菜名:{name}"}],temperature=0)returnresp.choices[0].message.content.strip()dishes=["蒜蓉大虾","蚂蚁上树","夫妻肺片","鱼香肉丝","狮子头","青岛啤酒","芒果布丁"]fordindishes:print(f"{d}->{classify_dish(d)}")

预期输出:

蒜蓉大虾 -> 海鲜类 蚂蚁上树 -> 猪肉类 夫妻肺片 -> 牛肉类 鱼香肉丝 -> 猪肉类 狮子头 -> 猪肉类 青岛啤酒 -> 酒水类 芒果布丁 -> 饮品甜品类

LIKE做不到的事,LLM轻松搞定。"蚂蚁上树"它知道是肉末粉条,不是虫类;"鱼香肉丝"它知道是川菜猪肉,不是鱼类。这是语义理解,不是字面匹配。

4.2 处理边界情况

当然,LLM也不是万能的。遇到本地特色菜、自创菜名,可能也会犯错。比如"九星招牌虾"——如果"九星"是店名,这应该归海鲜类,但LLM可能不确定。

解决方法:少样本提示(Few-shot)。在prompt里给几个例子:

messages=[{"role":"system","content":f"你是餐饮菜品分类助手。将菜名归入以下类别之一:{', '.join(categories)}。只输出类别名称。\n\n示例:\n九星招牌虾 -> 海鲜类\n秘制烤羊排 -> 牛羊肉类\n特色素三鲜 -> 蔬菜类"},{"role":"user","content":f"菜名:{name}"}]

加上几个本店特色菜的例子,准确率能从85%拉到95%以上。剩下5%的异常值,人工过一遍就行。

4.3 效率对比

维度LIKE暴力匹配LLM分类
开发时间2-3天(反复调条件)半天(写prompt+跑批)
新增菜品手动加条件自动分类
误分类率10%-15%(语义陷阱)< 5%(加few-shot后)
可迁移性换店重写改几个few-shot例子
维护成本持续累积排除条件基本无需维护

几千条数据,本地3B模型跑完不到半小时。加上人工抽查,整个过程半天搞定。当年用LIKE,光"虾"这个关键字就调了半天。

五、教训与反思

回头看这次数据分析,最大的教训不是技术选型,而是:遗留系统的数据质量,决定了分析的上限。

  • 菜名不统一:同一个菜,有的写"蒜蓉虾",有的写"蒜蓉大虾",有的写"蒜蓉开背虾"。LIKE和LLM都会把它们当成不同的菜
  • 价格混乱:同菜不同价,可能是时段定价,也可能是录错了
  • 包厢数据有脏数据:有些记录的开台时间晚于结账时间,PL/SQL遍历时会漏算

这些问题不是SQL或LLM能解决的,需要在数据采集端就做好规范化。

第二个体会是:数据分析的价值不在于技术多酷,而在于能不能回答业务问题。

包厢分析那段PL/SQL,写法很丑——三层嵌套循环,逐行INSERT,跑好几分钟。但跑出来的那张热力图,直接告诉老板"周六晚10点以后一房难求,周三下午全空"。这个结论值多少钱?如果据此调整定价策略,一个月多出来的营收可能覆盖整个数据分析的成本。

LIKE分类也很丑。但当年没有更好的工具,它解决了问题。现在有了LLM,同样的任务可以用更优雅的方式做。技术的进步就在于此:不是让不可能变成可能,而是让可能的成本从三天降到半天。

六、如果现在从头做

我会这样设计:

  1. 分类:LLM(3B本地模型),自动分类 + 人工抽查异常值
  2. 包厢分析:不再用PL/SQL暴力遍历,改用SQL窗口函数或Python pandas处理时间区间
  3. 可视化:包厢热力图直接用Python(matplotlib/seaborn),不用在数据库里PIVOT
  4. 数据清洗:先用LLM做菜名标准化("蒜蓉虾"和"蒜蓉大虾"合并为同一道菜),再分类

技术栈从"Oracle + 纯SQL"变成"Python + LLM + 可视化工具",但分析思路是一样的:先解决分类问题,再做聚合统计,最后回答业务问题。

工具换了,方法论没变。这大概就是数据分析的本质。

http://www.jsqmd.com/news/869713/

相关文章:

  • 如何突破60帧限制?原神PC版帧率解锁工具完全指南
  • 【杂谈】-游戏生成数据:人工智能训练中极易被低估的核心资源
  • 软件架构设计师考试——系统安全性与保密性设计知识点全总结(考前冲刺版,超1万字)
  • 避坑指南:在Windows 11上搞定ADSP-21569的SigmaStudio 4.6图形化开发环境
  • 告别笨重设备!聊聊我们如何用FPGA把水下光通信端机做小、做便宜
  • 微信聊天记录永久保存指南:如何用WeChatExporter完整备份你的珍贵对话
  • 551KB的Electron asar文件管理革命:告别命令行的可视化解决方案
  • NHSE存档编辑器:5个实用技巧让动物森友会岛屿建设事半功倍
  • Windows安卓子系统开发指南:从入门到精通
  • 告别混乱信号名!Vivado ILA调试中高效管理探针与触发条件的3个技巧
  • 英雄联盟智能助手Seraphine:如何用Python让游戏数据成为你的制胜法宝?
  • 手把手教你用Zynq-7000 PL端驱动HDMI:从Digilent IP到完整Vivado工程(附源码)
  • 从安全与自动化出发:用Ansible Playbook一键搞定Ubuntu服务器用户管理和SSH访问配置
  • 3分钟掌握本地视频字幕提取神器:Video-subtitle-extractor终极指南
  • 佳易王拼豆店桌球室棋牌室专用计时计费软件,多版本电脑端移动端saas版多版本技术教程解析
  • 决策树 随机森林面试详解|剪枝、过拟合、特征重要性
  • 树莓派4B部署YOLOv8保姆级避坑指南:从PyTorch版本选择到模型推理全流程
  • PX4飞控的‘眼睛’怎么选?深度对比T265、UWB与动捕(MOCAP)的ROS集成方案与实战心得
  • 别再只用手册了!用Modbus Poll/ModScan快速调试你的RS485温度传感器(Windows平台教程)
  • 3步快速清理Windows驱动存储:DriverStore Explorer终极使用指南
  • 别再乱加Buffer了!深入AXI Interconnect内部,聊聊Crossbar与那些‘耦合器’的正确用法
  • 跨平台音乐加密文件解密解决方案:Unlock Music Electron技术实现深度解析
  • 2026年降AI不踩雷:3个网站测评+4招实用技巧+1AI工具,助你论文高效通关 - 降AI实验室
  • Pydantic序列化避坑指南:model_dump vs dict、exclude/include高级用法与SerializeAsAny解析
  • AI写论文大比拼!4款AI论文写作工具,谁能脱颖而出?
  • AI Agent 大模型 面试教程
  • 告别臃肿卡顿!GHelper:华硕笔记本轻量级控制工具终极指南
  • 除了“窑鸡”和加班,网络安全大厂(深信服/天融信/绿盟)的真实工作体验和技术栈是怎样的?
  • 5分钟掌握:免费开源工具Ryzen SDT实现AMD处理器深度调试与精准控制
  • Google I/O 大会 AI 新特性亮点与困惑并存:功能分散、定位模糊、碎片化待解