1. 项目概述:为什么一个印尼菜谱生成器值得花三个月反复调试

去年夏天,我在雅加达一家小餐馆吃完一碗热腾腾的 Soto Ayam 后,顺手翻了翻手机里收藏的印尼食谱App——结果发现,超过60%的“新菜式推荐”要么是重复的,要么配料表错得离谱:把 kemiri(肉豆蔻) 写成 kemiri nut(坚果) ,把 daun salam(月桂叶) 标成 bay leaf(西式月桂) ,连基础单位都混用,一会儿用“sendok makan(汤匙)”,一会儿跳成“gram(克)”。那一刻我意识到,不是印尼人不想做菜,而是没有真正懂本地厨房语境的AI在帮忙。这直接催生了我这个 印尼菜谱生成器 项目——它不追求炫技的多模态或3D建模,而是死磕一件事:让模型真正理解“ resep tradisional Betawi ”和“ masak cepat ala anak kos ”之间的本质区别。核心关键词就三个: Deep Learning Indonesian language recipe generation 。这不是一个玩具项目,而是一次对低资源语言NLP落地边界的实测:当你的训练数据只有不到2万条真实印尼家庭菜谱,当Hugging Face上标着“Indonesian”的预训练模型实际只在维基百科片段上跑过几轮,你该怎么让模型学会区分 sambal matah (巴厘岛生辣酱)里必须用青柠汁而非醋,而 sambal ulek (爪哇捣碎辣酱)则必须用石臼现舂?我试过用纯Transformer从零训练,结果生成的菜谱连“ garam secukupnya(盐适量) ”都写成“ garam 500 gram(盐500克) ”——这根本不是AI,这是厨房恐怖片。所以这次我彻底转向 fine-tuning 路线,锁定了T5、BART、GPT-2三大架构,但关键不是“用哪个”,而是“怎么用才不翻车”。比如T5的prompt设计,我前后改了17版:最初照搬英文模板写“translate English to Indonesian: ...”,生成的菜谱全是直译腔;后来改成“resep untuk: ...”,又太笼统;最终定稿的“ resep masak cepat ala rumahan: [nama makanan] ”,才让模型真正抓住印尼家庭厨房的节奏感。这个项目适合三类人:正在做东南亚本地化AI的产品经理、想练手低资源语言NLP的学生、以及所有被错误菜谱坑过的印尼家庭主妇——它不教你怎么调参,而是告诉你,当数据少、算力紧、语言特殊时, Deep Learning 的每一步选择背后,都是对现实厨房的妥协与尊重。

2. 模型选型与底层逻辑:为什么T5/BART/GPT-2不是简单并列,而是三套解题思路

2.1 BART:用“打乱-修复”思维重建印尼厨房语义网

BART的核心预训练逻辑是 denoising autoencoding ——它不是单纯预测下一个词,而是先故意打乱句子(比如把“ bawang merah, bawang putih, cabai merah ”随机重排成“ cabai merah, bawang merah, bawang putih ”),再让模型还原原始顺序。这个设计对菜谱生成有天然优势:印尼菜谱的食材列表从来不是随意堆砌,而是暗含处理逻辑链。比如 Rendang 的“ kelapa parut, santan, daun jeruk ”,打乱后模型若要还原,就必须理解“ kelapa parut ”要先炒香,“ santan ”需分次加入防糊,“ daun jeruk ”最后放提香——这种隐性工序依赖,恰恰是BART通过“打乱-修复”被迫学出来的。我选用的 IndoBART 并非简单翻译版,它的预训练数据包含大量印尼本土论坛的烹饪讨论帖,比如“ cara bikin sambal yang nggak pecah ”(怎么做不油水分离的辣酱)这类带强烈口语和实操细节的文本。这解释了为什么它在BLEU分数上碾压其他模型:不是因为它更“聪明”,而是它的预训练语料里,早就有无数印尼主妇在教它什么叫“ api kecil banget ”(最小火)和“ sampai berminyak ”(熬到出油)。实操中我刻意没动它的输入格式,直接喂“ Ayam Bakar ”→“ bumbu: kecap manis, air asam jawa, gula merah... ”,因为BART的编码器天生擅长从混乱中提取结构,强行加prompt反而干扰它对印尼菜谱固有节奏的感知。这里有个血泪教训:早期我给BART加了“ generate recipe for: ”前缀,结果生成的步骤全变成英语式被动语态(“ The chicken is marinated... ”),删掉后立刻回归印尼语主动态(“ Ayam direndam... ”)——模型比我们更懂母语的呼吸感。

2.2 T5:把菜谱生成降维成“填空游戏”,但填空规则必须本地化

T5的预训练本质是 text-to-text transfer ,它把所有NLP任务都统一成“输入一段文字,输出另一段文字”。对菜谱生成,这看似完美:输入菜名,输出配方。但陷阱在于,T5的预训练任务库(翻译、问答、摘要)和印尼厨房完全脱节。我最初用Hugging Face上标着“Indonesian”的T5-small,输入“ resep: Gado-Gado ”,输出却是“ Gado-Gado adalah makanan khas Jakarta... ”(定义式回答),因为它在预训练时被喂过太多百科类任务。破局点在于 prompt engineering的本地化重构 。我放弃通用模板,直接复刻印尼食谱网站的真实搜索框逻辑:用户搜“ resep ayam bakar ”,网站返回的是完整步骤,而非食物介绍。于是我把输入强制规范为“ resep masak cepat: [nama makanan] ”,其中“ masak cepat ”(快手菜)是印尼年轻群体最常用的筛选标签,相当于告诉模型:“别给我百科,我要能马上开火的方案”。更关键的是输出约束——我要求模型生成时必须以“ bahan-bahan: ”(食材)和“ cara membuat: ”(做法)严格分段,且“ cara membuat: ”下必须包含“ panaskan wajan ”(烧热锅)、“ tumis bumbu sampai harum ”(炒香调料)等高频动词短语。这其实是在用规则强行校准T5的生成方向。参数上,我设学习率为1e-4,但发现T5对batch size极度敏感:用16会频繁OOM,降到8后loss震荡剧烈,最终锁定为12——这个数字来自对GPU显存的实测:A100 40GB下,12是显存占用(87%)和梯度稳定性(loss标准差<0.03)的黄金平衡点。有趣的是,T5在生成长菜谱时明显乏力,比如输入“ resep rendang padang ”,它常在第三步就漏掉“ masak dengan api kecil selama 4 jam ”(小火炖4小时)这个核心,转而编造“ tambahkan susu kental manis ”(加炼乳)这种完全违背传统的操作——这暴露了T5的致命短板:它擅长短程模式匹配,但对印尼菜谱中“时间-火候-状态”的长程依赖束手无策。

2.3 GPT-2:用“自回归”模拟印尼主妇的唠叨式教学法

GPT-2是三者中唯一 autoregressive (自回归)模型,它不靠编码器-解码器结构,而是像老主妇手把手教徒弟一样,逐字生成:“ Ambil ayam... lalu cuci... kemudian baluri dengan... ”。这种特性让它在生成步骤描述时天然流畅,但代价是输入设计必须重构。原始GPT-2输入是单序列,而菜谱有明确的“菜名→食材→步骤”三段式。我的解法是用特殊分隔符“ >>> ”硬编码结构:“ <FOOD: Ayam Bakar> >>> <INGREDIENTS: kecap manis, air asam jawa...> ”,让模型把“>>>”当作不可分割的语义断点。实测发现,这个符号的选择直接影响生成质量:试过“ | ”、“ :: ”,模型总把它当成普通标点忽略;换成“ >>> ”后,生成的步骤开头90%都带“ lalu ”(然后)、“ setelah itu ”(之后)等连接词,说明它真把“>>>”当作了流程切换信号。IndoGPT的预训练数据虽与IndoBART同源,但因自回归特性,它对口语化表达更敏感。比如输入“ resep sambal matah ”,T5可能输出标准书面语“ iris bawang merah tipis-tipis ”(切薄薄的红葱),而IndoGPT会生成“ bawang merah diiris tipis banget, jangan tebel-tebel ya! ”(红葱切超薄,别切厚啊!),末尾的“ ya! ”正是印尼网络食谱常见的亲切语气词。但GPT-2的硬伤是 无法控制生成长度 。我曾设max_length=512,结果它为凑字数,在“ cara membuat ”末尾硬加“ selamat mencoba, jangan lupa share ke temanmu! ”(祝你好运,别忘了分享给朋友!)——这在技术文档里是灾难,但在面向家庭主妇的App里,反而是加分项。所以我的策略是:用GPT-2专攻“步骤描述”子模块,再用BART生成“食材列表”,最后人工拼接——不是模型不够好,而是不同模型本就该各司其职。

3. 数据工程与预处理:2万条菜谱如何榨出最大价值

3.1 数据清洗:从“脏数据”里抢救印尼厨房的烟火气

原始数据来自印尼本地美食社区爬取的21,347条菜谱,表面看很丰富,但打开CSV第一眼就头皮发麻:32%的条目“ bahan-bahan ”字段是空的;18%的“ cara membuat ”里混着广告链接(“ klik di sini untuk beli bumbu instan ”);最致命的是单位混乱——同一道 Sop Buntut ,有的写“ 2 sendok makan kecap manis ”,有的写“ 2 sdm kecap manis ”,还有的直接写“ kecap manis secukupnya ”(酱油适量)。我的清洗策略不是追求“标准化”,而是保留 印尼厨房的真实表达多样性 。具体操作分三步:
第一步:单位映射而非替换 。我建立了一个动态映射表,把“ sdm ”、“ sendok makan ”、“ sendok ”全部指向“ tablespoon ”这个统一概念,但保留原始字符串。这样模型既能学“ 2 sdm ”的简洁,也能学“ 2 sendok makan ”的完整,还能理解“ secukupnya ”的模糊性——毕竟现实中没人真用量勺称“适量”。
第二步:广告过滤的语义锚定 。我不用正则粗暴删链接,而是训练一个轻量级分类器,专门识别“ klik di sini ”、“ promo bulan ini ”这类营销短语。关键锚点是动词:“ beli ”(买)、“ dapatkan ”(获得)、“ diskon ”(折扣)在菜谱步骤中本就不该出现,一旦检测到立即截断后续内容。
第三步:模糊实体归一化 。印尼菜谱里“ bawang ”(葱)可能指红葱、干葱、大葱,全靠上下文。我用spaCy的Indonesian模型做NER,但发现它对“ bawang bombay ”(洋葱)和“ bawang merah ”(红葱)区分不准。最终方案是构建 菜谱知识图谱 :统计每道菜高频共现词,比如“ rendang ”总和“ kelapa parut ”、“ asam kandis ”一起出现,就把这些词绑定为“ rendang-context ”;当遇到“ bawang ”时,查它在当前菜名context下的共现频率,自动补全为“ bawang merah ”。这步让食材召回准确率从68%提升到91%,代价是预处理时间增加47分钟——但比起生成一锅失败的 Rendang ,这点时间算什么?

3.2 输入构造:让模型一眼看懂“这是道印尼菜”

预训练模型的输入格式决定上限。我测试过三种构造方式:
方式A(纯文本拼接) :“ Ayam Bakar: bumbu: kecap manis, air asam jawa, gula merah. cara: panaskan wajan...
→ BLEU 22.3,问题:模型分不清“bumbu”和“cara”的边界,常把“gula merah”生成进步骤里。
方式B(结构化JSON) {"food": "Ayam Bakar", "ingredients": ["kecap manis", "air asam jawa"], "steps": ["panaskan wajan"...]}
→ BLEU 18.7,问题:模型把JSON符号当噪声,生成结果带大量“{”、“}”、“\”等乱码。
方式C(本地化Prompt+分隔符) resep masak cepat: Ayam Bakar >>> bahan-bahan: kecap manis, air asam jawa, gula merah >>> cara membuat: panaskan wajan...
→ BLEU 34.1,胜出。关键在“ resep masak cepat ”这个前缀——它不是功能描述,而是 文化信号 。印尼年轻人搜菜谱时,92%会加“ masak cepat ”、“ praktis ”(方便)、“ untuk pemula ”(新手)等标签,这相当于告诉模型:“用户要的是可执行方案,不是美食论文”。而“ >>> ”分隔符的威力在于,它强制模型在三个语义块间建立注意力权重:当生成“ cara membuat ”时,它必须回溯“ bahan-bahan ”里的“ kecap manis ”,从而避免生成“ tambahkan saus tomat ”(加番茄酱)这种错误。实测中,去掉“ >>> ”改用换行符,BLEU暴跌至26.5——证明模型需要明确的结构锚点,而非隐式分段。

3.3 数据增强:用“厨房常识”对抗小样本诅咒

2万条数据对英文足够,对印尼语远远不够。我的增强策略拒绝简单同义词替换(印尼语同义词极少),而是基于 印尼厨房物理规律 注入伪数据:
火候增强 :对所有含“ tumis ”(炒)的步骤,按规则插入火候描述。规则库来自印尼烹饪教材:

  • tumis bumbu ” → “ tumis bumbu dengan api sedang sampai harum ”(中火炒香)
  • tumis cabai ” → “ tumis cabai dengan api kecil agar tidak gosong ”(小火炒防焦)
    时间增强 :对炖煮类菜( rendang , opor ),按肉类类型插入时间:“ rendang daging sapi: masak 4 jam ”,“ rendang ayam: masak 1.5 jam ”。
    替代增强 :针对常见缺货食材,生成合理替代方案。比如“ daun salam ”缺失时,模型可生成“ jika tidak ada daun salam, bisa diganti dengan daun jeruk purut (1 lembar) ”(如无月桂叶,可用1片香橙叶替代)。这步不是凭空编造,而是基于印尼农业部发布的《传统香料替代指南》构建规则。最终数据量扩至34,218条,但BLEU提升仅1.2——证明真正的瓶颈不在数量,而在 数据质量的深度 。后来我发现,把原始2万条中“ cara membuat ”字段里所有“ sampai... ”(直到...)结构提取出来(如“ sampai bumbu meresap ”/“ sampai kuah mengental ”),单独构建成一个“状态判断”子数据集,再微调模型,BLEU暴涨5.7。原来模型最缺的不是菜谱,而是对“ mengental ”(变浓稠)、“ meresap ”(入味)这些抽象状态的理解——这才是印尼厨房的终极密码。

4. 训练工程与超参实战:在A100上跑出稳定结果的硬核细节

4.1 框架选择:为什么PyTorch Lightning是救命稻草

不用PyTorch原生API,是因为印尼菜谱生成有三大魔鬼细节:
第一,梯度累积的精度陷阱 。A100单卡跑batch_size=12时,梯度更新极不稳定,loss在0.8~1.5间狂跳。我尝试梯度裁剪(clip_grad_norm_=1.0),效果甚微。Lightning的 Automatic Gradient Accumulation 救了我:设accumulate_grad_batches=2,它自动把两次forward的梯度累加再update,等效batch_size=24,但显存占用不变。关键是,Lightning在accumulation过程中保持FP16精度,而原生PyTorch手动accumulation易因精度丢失导致NaN。
第二,Early Stopping的验证逻辑 。菜谱生成不能只看loss,更要监控BLEU。Lightning的 Trainer 允许我自定义 val_check_interval (每多少step验证一次)和 check_val_every_n_epoch ,我设为每500步验证,因为观察到loss plateau通常出现在500步内。更重要的是,我重写了 validation_step ,让它同时计算loss和BLEU,并用 self.log('val_bleu', bleu_score, sync_dist=True) 同步到所有GPU——没有Lightning,跨GPU的指标同步会让我疯掉。
第三,Checkpoint的智能覆盖 。我设 save_top_k=3 ,但要求只保存 val_bleu 最高的模型。Lightning的 ModelCheckpoint 支持 monitor='val_bleu' mode='max' ,比自己写 torch.save() 可靠十倍。实测中,某次训练因电源波动中断,重启后Lightning自动从最新checkpoint恢复,连learning rate scheduler的状态都完美继承——这省下的3天重训时间,够我优化两轮prompt。

4.2 超参调优:学习率不是调出来的,是“算”出来的

学习率选择绝非玄学。我用 Learning Rate Finder (Lightning内置工具)对每个模型扫描:

  • IndoBART :扫描范围1e-6~1e-3,loss最低点在3.2e-5,但此时梯度爆炸。最终选1e-5,因它在loss下降斜率(-0.042)和梯度范数(1.8)间取得最佳平衡。
  • T5 :扫描显示1e-4时loss下降最快,但验证BLEU在第12 epoch后停滞。分析梯度流发现,encoder层梯度方差过大。解决方案:对encoder层用1e-5,decoder层用1e-4——Lightning的 Layer-wise Learning Rate 支持此操作。
  • IndoGPT :自回归模型对学习率更敏感。扫描发现1e-5时收敛慢,1e-4时early stopping触发过早。最终采用 cosine annealing :初始1e-4,终值1e-6,周期20 epoch。这模仿了印尼主妇“先大火快炒,再小火慢炖”的节奏,让模型前期快速捕捉模式,后期精细调整。
    Batch Size的物理意义 :很多人以为越大越好。我实测发现,对IndoBART,batch_size=12时,每个batch内“ sambal ”类菜谱占比约37%,而batch_size=24时,因数据shuffle,某些batch里“ sambal ”占比骤降至12%。这导致模型对辣酱类菜谱的泛化能力下降。所以batch_size不是越大越好,而是要保证 每个batch内关键菜系分布稳定 ——我最终用stratified sampling确保每类菜谱在batch中占比浮动<±5%。

4.3 混合精度与显存优化:在40GB显存里塞下三个大模型

AMP(Automatic Mixed Precision) 是A100的标配,但T5是个例外。当我对T5启用AMP时,训练第3 epoch就报错:“ RuntimeError: expected scalar type Half but found Float ”。根源在T5的 relative position bias 层,它内部用float32计算相对位置,与FP16冲突。解决方案:用 torch.cuda.amp.autocast(enabled=False) 局部禁用AMP,仅对FFN层启用。但这牺牲了速度。我的折中方案是:T5训练全程用FP32,但用 Gradient Checkpointing (Lightning的 gradient_checkpointing=True )节省显存。实测显存占用从38.2GB降至29.7GB,训练速度仅降18%。
模型并行的实战取舍 :IndoBART-base有140M参数,单卡A100能跑,但IndoBART-large(400M)必OOM。我试过 torch.nn.DataParallel ,但多卡间通信开销让速度暴跌40%。最终采用 FSDP (Fully Sharded Data Parallel),它把模型参数、梯度、优化器状态分片到多卡。配置关键点: sharding_strategy=ShardingStrategy.FULL_SHARD cpu_offload=True (把部分状态卸载到CPU)。虽然首次初始化慢2分钟,但训练速度比DataParallel快2.3倍。代价是代码复杂度上升——但为了跑通IndoBART-large,这200行额外代码值得。

5. 评估体系与问题排查:BLEU之外,厨师怎么看?

5.1 BLEU的局限性:为什么34.1分的模型仍会生成毒菜谱

BLEU只衡量n-gram重叠率,对印尼菜谱是严重误导。举个真实案例:模型生成“ resep: Soto Ayam ”的输出是:

bahan-bahan : ayam, beras, kunyit, jahe, serai, daun salam, garam
cara membuat : rebus ayam dengan beras sampai empuk
(用米和鸡肉一起煮至软烂)
BLEU得分高达38.2——因为“ayam”、“beras”、“rebus”全在参考答案里。但任何印尼人都知道, Soto Ayam的汤底绝不放米 ,米是配饭用的!这暴露BLEU的致命缺陷:它奖励词汇匹配,却惩罚 文化正确性 。为此,我构建了三层评估体系:
第一层:语法与事实核查 。用spaCy-ID检查动词变位(如“ direbus ”是否误为“ direbuskan ”),用规则引擎验证单位(“ 2 sendok teh garam ”是否合理,排除“ 2 kg garam ”)。
第二层:厨房物理验证 。对所有步骤,运行轻量级物理模拟:

  • rebus dalam air mendidih selama 30 menit ”(沸水煮30分钟)→ 检查食材是否耐煮(鸡肉可行,豆腐会散)
  • goreng dalam minyak panas sampai kecoklatan ”(热油炸至褐色)→ 检查油温是否合理(“panas”对应160-180°C,低于150°C会吸油)
    第三层:真人盲测 。邀请12位印尼家庭主妇(6位雅加达,6位泗水),对生成菜谱打分:
  • 可执行性 (1-5分):能否按步骤做出可食菜肴?
  • 地道性 (1-5分):是否符合本地口味习惯?
  • 安全性 (1-5分):有无危险操作(如生熟混用)?
    结果震惊:BLEU最高(34.1)的IndoBART,在“地道性”上仅得2.8分,因它过度依赖训练数据中的高频词,生成“ tambahkan kecap manis ”(加甜酱油)到所有菜里,哪怕 Sayur Asem (酸辣蔬菜汤)本不该放。而BLEU仅28.7的T5,在“可执行性”上得4.3分——因它的prompt“ resep masak cepat ”强制模型优先考虑操作可行性。

5.2 常见问题速查表:那些让你凌晨三点抓狂的Bug

问题现象 根本原因 实战解决方案 预防措施
生成结果突然变英文 IndoBART tokenizer未正确加载印尼语special tokens,fallback到英文vocab tokenizer.add_special_tokens({'additional_special_tokens': ['<FOOD>', '<INGREDIENTS>']}) 显式添加,并 resize_token_embeddings setup_tokenizer 函数末尾加 assert tokenizer.vocab_size > 50000 断言
T5训练loss为NaN T5的relative position bias在FP16下溢出 禁用AMP,或用 torch.set_default_dtype(torch.float32) 全局设为FP32 在T5训练脚本开头加 torch.backends.cuda.matmul.allow_tf32 = False
GPT-2生成无限循环 (如“lalu... lalu... lalu...”) 自回归生成未设置 eos_token_id ,模型找不到结束符 model.generate(..., eos_token_id=tokenizer.eos_token_id, max_length=512) 在tokenizer初始化时,用 tokenizer.pad_token = tokenizer.eos_token 确保一致
BLEU分数虚高 sacrebleu默认tokenize为"13a"(针对英文),对印尼语分词错误 sacrebleu --tokenize indonesian 或用 tokenizer.encode 后计算 将评估脚本封装为 evaluate_indo(recipe_pred, recipe_ref) 函数,内置印尼语分词
Early Stopping不触发 验证集BLEU计算耗时过长(>30秒/epoch),trainer误判为“未完成验证” torch.no_grad() 包裹BLEU计算,并用 concurrent.futures.ThreadPoolExecutor 并行化 validation_step 中,只对batch前10条计算BLEU,其余用loss代理

5.3 实操心得:那些文档里不会写的“厨房潜规则”

  • 不要迷信“larger model, better result” :我训过IndoBART-large,参数是base版3倍,但BLEU只高0.9,训练时间却长4.2倍。在印尼语这种低资源场景,base版的泛化能力反而更强——就像老厨师不用满桌调料,一把盐就能调出灵魂。
  • prompt不是越长越好 :曾试过“ resep masak cepat ala rumahan untuk pemula tanpa bumbu instan: [nama makanan] ”,模型反而困惑,生成结果带大量“ tanpa bumbu instan ”的强调句。最终精简为“ resep masak cepat: [nama makanan] ”,干净利落。
  • 验证集必须含“边缘菜谱” :我的验证集特意加入10%的冷门菜(如 Sate Maranggi Lontong Cap Go Meh ),否则模型在主流菜上BLEU虚高,一遇生僻菜就崩。这就像考驾照,光练直路不行,必须考窄路掉头。
  • 生成时temperature别设0.0 :设为0.0(greedy decoding)看似稳定,但生成结果死板。设为0.7后,“ tumis bumbu ”有时变“ tumis bumbu sampai harum ”,有时变“ tumis bumbu dengan api sedang ”,多样性提升,且无事实错误——这证明适度随机性反而是鲁棒性的来源。

6. 部署与实用技巧:让模型走出Jupyter,走进真实厨房

6.1 Hugging Face Space部署:从Demo到产品的最后一公里

Hugging Face Space不是玩具,而是真实的MVP验证场。我的Space(https://huggingface.co/spaces/haryoa/id-recipe-generator)做了三件关键事:
第一,输入防呆设计 。用户输入框预置提示:“ Contoh: Nasi Goreng, Soto Ayam, Rendang ”,并用JavaScript实时过滤emoji和URL——因为实测23%的用户会输“ Nasi Goreng 🍳 ”或粘贴网页链接。
第二,生成结果结构化渲染 。不直接输出raw text,而是用HTML解析“ bahan-bahan: ”和“ cara membuat: ”分段,并为食材列表加✅图标,步骤加🔢序号。更关键的是,对所有单位( sendok makan , gram , siung )加tooltip,鼠标悬停显示换算(“ 1 sendok makan = 15 ml ”)——这解决了印尼家庭厨房单位混乱的痛点。
第三,用户反馈闭环 。每条生成结果下方有“✅ Bagus!” / “❌ Kurang tepat”按钮。点击后弹出选项:“ Salah bahan ”(食材错)、“ Langkah tidak jelas ”(步骤不清)、“ Tidak enak ”(口味不对)。这些反馈数据每天自动存入Google Sheet,成为下一轮微调的黄金数据。上线两周,收集到412条有效反馈,其中“ Salah bahan ”占67%,直指模型对替代食材(如 kemiri vs kacang mete )的混淆——这比任何BLEU分数都真实。

6.2 本地化推理优化:让老手机也能跑菜谱生成

不是所有印尼用户都有5G。我用ONNX Runtime把IndoBART-base转为ONNX模型,体积从1.2GB压缩到380MB,推理速度提升3.7倍。关键优化点:

  • Dynamic Quantization :对权重进行int8量化,精度损失<0.3 BLEU,但内存占用减半。
  • Session Options调优 intra_op_num_threads=2 , inter_op_num_threads=1 ,适配低端手机双核CPU。
  • 缓存机制 :对高频菜名( Nasi Goreng , Soto Ayam )预生成结果并存入SQLite,响应时间从1.2秒降至0.08秒。
    实测在三星Galaxy A10(2GB RAM)上,ONNX模型稳定运行,而原始PyTorch模型直接OOM。这印证了我的信念: Deep Learning 的价值不在参数量,而在能否在真实约束下解决问题。

6.3 后续可扩展方向:从菜谱生成到厨房智能体

这个项目不是终点,而是起点。我已规划三个务实扩展:
第一,多模态菜谱生成 。接入印尼菜谱图片数据集,用CLIP提取图像特征,与文本特征融合。目标不是生成图片,而是让模型理解“ kuah bening ”(清汤)和“ kuah kental ”(浓汤)的视觉差异,从而生成更精准的步骤。
第二,个性化口味适配 。基于用户历史反馈(如总点“❌ Kurang tepat”针对“ terlalu pedas ”),构建轻量级偏好模型,生成时动态调整“ cabai ”(辣椒)用量。
第三,语音交互厨房助手 。用Wav2Vec2-ID做印尼语ASR,将“ buatkan resep ayam bakar ”转文本,再调用生成模型,最后用Indonesian Tacotron合成语音。硬件只需树莓派+USB麦克风,成本<$50。
这些都不是空中楼阁。上周,我已在泗水一家小餐馆试点:店主用旧手机扫二维码进入Space,语音说“ resep ikan bakar buat 10 orang ”,模型生成10人份烤鱼菜谱,店主照着做,当天卖出37份——没有API,没有云服务,只有一个Hugging Face Space和一部能上网的手机。这就是 Deep Learning 该有的样子:不炫技,不烧钱,只解决厨房里真实冒烟的问题。

我在实际部署中发现,模型对“ untuk pemula ”(新手)标签的响应最稳定——只要输入里有这个词,生成的步骤必然包含“ pastikan wajan benar-benar panas sebelum masukkan bumbu ”(确保锅完全烧热再下调料)这类防错提示。这提醒我:与其追求通用强大,不如深耕一个真实场景的极致体验。现在每次吃印尼菜,我都会下意识看配料表,心里默默给模型打分:这道菜,它能生成吗?如果不能,就是我的下一个迭代目标。

更多推荐