測實戰(zhàn):DeepSeek輔助POS數(shù)據(jù)特征工程與LightGBM建模)
簡介面向餐飲連鎖行業(yè)的數(shù)據(jù)分析與AI應(yīng)用人群這份PDF以“餐飲連鎖實戰(zhàn)”為主線完整講解如何基于DeepSeek構(gòu)建銷量預(yù)測模型并重點梳理POS數(shù)據(jù)訓(xùn)練過程中的高頻踩坑點。內(nèi)容覆蓋從業(yè)務(wù)痛點、DeepSeek模型原理到數(shù)據(jù)收集清洗、特征工程、模型訓(xùn)練與驗證再到過擬合、數(shù)據(jù)不平衡、梯度消失等問題的應(yīng)對策略同時給出MSE、RMSE、MAE等評估指標(biāo)和部署應(yīng)用思路適合算法工程師、餐飲數(shù)字化運營人員以及入門大模型實戰(zhàn)的學(xué)習(xí)者參考。資源包共1個文件類型為PDF大小1.93MB共25頁目錄層級清晰正文包含代碼示例與步驟拆解便于按需查閱。目前已有60人學(xué)習(xí)下載適合在實際銷量預(yù)測項目啟動前或訓(xùn)練遇到瓶頸時作為避坑手冊對照使用。1. 餐飲連鎖用 DeepSeek 做銷量預(yù)測先認(rèn)清它的邊界餐飲連鎖做銷量預(yù)測最怕的不是算法不夠新而是把 DeepSeek 銷量預(yù)測模型當(dāng)成一個萬能黑匣子POS 數(shù)據(jù)灌進去明天備貨數(shù)就出來。實際項目里直接讓大模型回歸連續(xù)值樣本內(nèi)數(shù)值像模像樣一換門店就翻車的事我見得太多了。這篇實戰(zhàn)筆記要講的是圍繞 POS 數(shù)據(jù)訓(xùn)練這條主線DeepSeek 到底該放在哪個環(huán)節(jié)幫我們生成清洗代碼、特征方案、訓(xùn)練基線和誤差診斷數(shù)值預(yù)測本身用回歸模型落地。適合連鎖餐飲的數(shù)據(jù)分析師、算法工程師和信息化負(fù)責(zé)人照著可以搭出一套能復(fù)現(xiàn)的預(yù)測管線。2. 從 POS 數(shù)據(jù)到訓(xùn)練集清洗、聚合與特征工程2.1 POS 數(shù)據(jù)里哪些字段能用哪些是臟數(shù)據(jù)連鎖餐飲的 POS 數(shù)據(jù)表面看起來字段齊全門店編號、菜品 ID、下單時間、數(shù)量、原價、實收、折扣、渠道、會員 ID。真拿來做訓(xùn)練第一件事不是建模而是對字段做“成分篩查”。銷量預(yù)測模型的輸入是“過去賣了多少”如果這個“賣了多少”里混著退款單、測試單、員工餐、贈菜模型學(xué)的就是假規(guī)律。我一般會先做規(guī)則清洗刪除實收金額為 0 或負(fù)數(shù)的訂單行剔除內(nèi)部測試菜品 ID過濾掉營業(yè)時間之外的下單記錄。特別要留意“免單”“掛賬”財務(wù)系統(tǒng)里它們是真實流水但在銷量預(yù)測里是噪聲。清洗時保留原始表另建一張 clean_pos 表不要在原表上直接改否則后面排查特征問題沒有后悔藥。用 pandas 做首輪清洗import pandas as pd # pos_raw 為門店上傳或中臺導(dǎo)出的訂單明細(xì) df pd.read_parquet(pos_raw.parquet) # 去掉退款、取消單避免負(fù)銷量 df df[df[order_status] 完成] # 去掉內(nèi)部測試菜品和 0 數(shù)量 df df[df[dish_id].isin(internal_dishes) False] df df[df[item_qty] 0] # 近免費贈菜折扣比例超過 0.95 的單獨標(biāo)記 df[is_free_dish] df[discount_ratio] 0.95 df df[df[is_free_dish] False]邏輯說明order_status字段必須確認(rèn)每個值的含義有的門店上報的是“已支付”有的是“已完成”統(tǒng)一前不要直接等值過濾discount_ratio是優(yōu)惠金額占原價的比例不是實付比例閾值 0.95 代表“基本等于贈送”。這組過濾解決的是“銷量不等于真實需求”的問題買一送一產(chǎn)生的 2 件貨算銷量沒錯但贈品件數(shù)不該和正價件數(shù)一模一樣地進入序列。參數(shù)說明internal_dishes要從菜品主數(shù)據(jù)里拿通常以“員工餐”“測試”“贈品”為前綴。營業(yè)時間過濾要看門店類型早餐店和夜宵店的營業(yè)時間窗不同不能全局寫死“06:00-23:59”。2.2 按“門店×菜品×小時/天”聚合先定預(yù)測粒度POS 是訂單明細(xì)訓(xùn)練需要的是序列。如果做門店第二天的總營業(yè)額預(yù)測就按“門店天”聚合如果要指導(dǎo)單品備貨就按“門店菜品天”聚合如果要排班還得拆到小時。粒度定錯后面的特征和模型維護成本都會失控。我一般先做“門店菜品天”的寬表再按需上卷成門店總銷量。用 SQL 聚合最直接CREATE TABLE daily_sales AS SELECT store_id, dish_id, DATE(order_time) AS biz_date, SUM(item_qty) AS total_qty, SUM(orig_amount) AS orig_amount, AVG(discount_ratio) AS avg_discount, COUNT(DISTINCT pos_order_id) AS order_cnt FROM clean_pos GROUP BY store_id, dish_id, DATE(order_time);參數(shù)說明biz_date用下單時間不要用支付時間否則線上支付單會跑到第二天如果有門店跨時區(qū)統(tǒng)一在提取層轉(zhuǎn)成總部時區(qū)不要混用門店本地時間。orig_amount是原始金額不含折扣用來反映“按牌價計的規(guī)?!庇?xùn)練時如果目標(biāo)選營業(yè)額這個字段比實收金額穩(wěn)定得多。avg_discount不要取整個門店平均按菜品平均才有意義因為不同菜品的折扣策略完全不同。聚合之后要再檢查一遍空值沒有賣出去的“門店×菜品×天”要不要補 0我建議補 0但只在“菜品在該門店生命周期內(nèi)”才補否則會把沒上架的菜品算成銷量 0模型會學(xué)到“這個店不賣這道菜”的錯誤信息。補 0 的窗口照常寫成 SQL 或 pandas 的 fillna 邏輯記住補 0 的語義是“已上架但無人買”不是“不存在”。2.3 特征工程節(jié)假日、天氣、促銷與門店生命周期銷量預(yù)測需要的特征不是越多越好。餐飲連鎖最有效的特征永遠(yuǎn)是日類型、節(jié)假日、天氣、促銷以及門店生命周期。構(gòu)造特征時我要求團隊把每個特征都做成“預(yù)測日當(dāng)天已經(jīng)知道的值”而不是“未來的值”否則驗證時就會穿越。節(jié)假日表本來就是公開日歷可以用來構(gòu)造“節(jié)前第幾天”“節(jié)后第幾天”促銷計劃來自運營排期表也是預(yù)測日之前就能確定的信息只有天氣要取預(yù)報值而不是實測值。用 Python 構(gòu)造節(jié)假日和促銷特征df[is_weekend] df[biz_date].dt.weekday 4 df[is_holiday] df[biz_date].isin(holiday_list).astype(int) # 節(jié)前 1 天、節(jié)前 2 天節(jié)假日表提前已知不構(gòu)成未來穿越 for offset in [1, 2]: df[fholiday_before_{offset}] ( df[biz_date] pd.Timedelta(daysoffset) ).isin(holiday_list).astype(int) # 節(jié)后 1 天、節(jié)后 2 天 for offset in [1, 2]: df[fholiday_after_{offset}] ( df[biz_date] - pd.Timedelta(daysoffset) ).isin(holiday_list).astype(int) # 促銷計劃是已知字段直接用排期表 join df df.merge(promo_plan, on[store_id, biz_date], howleft) df[is_promo] df[promo_type].notna().astype(int)邏輯說明holiday_before_1表示“明天是不是節(jié)假日”holiday_after_1表示“昨天是不是節(jié)假日”。這些都是預(yù)測日當(dāng)天能拿到的日歷信息所以不會穿越。加這兩個特征能抓到節(jié)前鎖客和節(jié)后疲軟比單純一個 is_holiday 有用得多。promo_plan必須來自運營提前給定的排期不要用實付折扣反推促銷因為反推發(fā)生在銷售之后在線預(yù)測時拿不到。再加上天氣與門店生命周期# 天氣特征用前一天發(fā)布的氣象預(yù)報不用當(dāng)天實測 weather weather_forecast.set_index([store_id, biz_date])[[temp_max, precip]] df df.join(weather, on[store_id, biz_date]) # 門店生命周期 df df.merge(store_open_date, onstore_id, howleft) df[days_since_open] (df[biz_date] - df[open_date]).dt.days df[is_new_store] (df[days_since_open] 90).astype(int)參數(shù)說明precip用降水量級0/1/2 表示無雨/小雨/大雨不要讓模型自己學(xué)一個連續(xù)毫米數(shù)餐飲銷量對雨量的響應(yīng)是非線性的days_since_open要參與訓(xùn)練但新店樣本建議單獨處理。這里能看出 DeepSeek 的作用就算團隊里缺少有經(jīng)驗的特征工程師也可以把 daily_sales 的表結(jié)構(gòu)和字段說明作為 prompt讓 DeepSeek 生成同樣的特征構(gòu)造代碼然后再人工核對有沒有用到未來信息。我習(xí)慣把“特征方案評審”放到 AI 輔助流程里模型只能做統(tǒng)計關(guān)聯(lián)業(yè)務(wù)規(guī)則必須由人把關(guān)。還有一個容易漏掉的特征是滯后銷量。用過去 7 天、14 天的滾動均值作為特征可以幫模型理解當(dāng)前趨勢。但構(gòu)造時要注意shift()的邊界預(yù)測明天的銷量只能用今天為止的歷史所以訓(xùn)練集里每一行的 lag 特征必須基于該行日期之前的數(shù)據(jù)不能在整條序列上直接 rolling否則會把包含當(dāng)天未來的窗口帶進來。正確做法是先用sort_values(biz_date)再用groupby([store_id,dish_id])[total_qty].transform(lambda x: x.shift(1).rolling(7).mean())這樣既能表達(dá)最近一周的水平又不會穿越。3. DeepSeek 在銷量預(yù)測訓(xùn)練里的真實角色API 調(diào)用與本地部署選型3.1 為什么不讓 DeepSeek 直接回歸連續(xù)值DeepSeek 是預(yù)訓(xùn)練語言模型它的強項是理解文字、生成代碼和做邏輯推理不是做數(shù)值回歸。讓它直接回答“明天這個店賣多少”它在訓(xùn)練數(shù)據(jù)里看過無數(shù)類似問答能生成一個讀數(shù)但這個讀數(shù)沒有概率意義更沒法告訴你置信區(qū)間。對餐飲連鎖來說預(yù)測誤差直接影響備貨和損耗一個沒有誤差上界的數(shù)字就是“猜”。所以我在項目里的分工是數(shù)值預(yù)測交給回歸模型DeepSeek 負(fù)責(zé)生成清洗代碼、特征方案、超參建議和誤差診斷。這個邊界很多團隊一開始不接受覺得“AI 都這么強了怎么還要用傳統(tǒng)模型”。拿一個具體現(xiàn)象說DeepSeek 生成的“明天賣 300 份”往往是對歷史均值的記憶重建門店做了一次大型促銷銷量翻倍讓大模型直接回歸它會按文本聯(lián)想把數(shù)字抬得很高缺少邊界約束而 LightGBM 這類模型能通過輸入特征顯式控制促銷力度預(yù)測結(jié)果還能用 SHAP 拆解。所以“DeepSeek 銷量預(yù)測模型”的正確打開方式是把它放在訓(xùn)練流水線的輔助位置而不是預(yù)測公式本身。3.2 用 DeepSeek API 生成特征工程與排錯建議最小可調(diào)用示例DeepSeek 的 API 是 OpenAI 兼容格式調(diào)用方式和 GPT 接口幾乎一致。最常用的環(huán)境變量是DEEPSEEK_API_KEY模型名deepseek-chat或deepseek-reasoner。這里給一個最小可調(diào)用示例用來讓 DeepSeek 根據(jù)表結(jié)構(gòu)生成特征代碼from openai import OpenAI import os client OpenAI( api_keyos.environ[DEEPSEEK_API_KEY], base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, temperature0.0, max_tokens1024, messages[ {role: system, content: 你是連鎖餐飲 AI 咨詢專家只輸出可直接運行的 Python 代碼。}, {role: user, content: ( 我有一張 daily_sales 表字段store_id,dish_id,biz_date, total_qty,orig_amount,avg_discount。 請生成1節(jié)假日前后偏移特征2門店營業(yè)天數(shù)特征 3促銷標(biāo)記特征。字段名保持原樣不要假設(shè)其他字段存在。 )} ] ) code_text resp.choices[0].message.content print(code_text)邏輯說明temperature0.0是在代碼生成任務(wù)里必須調(diào)的參數(shù)否則同一個 prompt 兩次生成的代碼可能不一樣排錯會變得很痛苦max_tokens1024限制輸出長度防止模型寫一套完整系統(tǒng)繞開你的問題base_url是 DeepSeek 兼容接口的入口不同環(huán)境會變建議寫成配置項而不是硬編碼。生產(chǎn)環(huán)境調(diào)用時不要把門店真實銷量、會員 ID 塞進 prompt先用dataframe.describe()和字段名概括數(shù)據(jù)否則業(yè)務(wù)敏感信息會隨著 API 請求流出。實踐中的常見做法是把這段請求封裝成一個ask_deepseek(task, table_schema)函數(shù)讓特征工程、SQL 審核、代碼糾錯都能復(fù)用。返回來代碼后先在小樣本上跑一遍確認(rèn)字段名和業(yè)務(wù)定義一致再并入主訓(xùn)練腳本。DeepSeek 生成的代碼不能直接信它可能會把avg_discount當(dāng)成“促銷力度”但沒告訴你這個字段缺失率 40%這種坑要靠數(shù)據(jù)校驗。還有一次讓我印象很深的調(diào)用翻車prompt 里沒有寫清楚字段類型DeepSeek 把avg_discount當(dāng)成了字符串生成代碼里做字符串拼接結(jié)果在 pandas 上直接報錯。后來我在 prompt 里固定附加一行字段類型說明avg_discount float缺失表示無優(yōu)惠生成結(jié)果的可用性就高了很多。這說明 API 調(diào)用的關(guān)鍵是 prompt 里有沒有把數(shù)據(jù)字典寫完整而不是模型能力不夠。3.3 本地部署 DeepSeek 做離線批處理vLLM 與量化選型如果 POS 數(shù)據(jù)敏感性高、門店數(shù)量大或者需要批量分析菜品評論、排班文本API 調(diào)用可能不是最佳選擇。常見做法是內(nèi)網(wǎng)部署一個量化版 DeepSeek用 vLLM 提供兼容接口讓訓(xùn)練流水線直接訪問本地模型。部署命令大致是vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --quantization awq \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --served-model-name local-deepseek參數(shù)說明quantization awq是把模型壓到 4-bit 左右顯存占用會低很多普通 24G 顯卡就能跑 7B 級別模型max-model-len 8192是上下文長度特征生成任務(wù)不需要太長設(shè)短一點能降低顯存壓力gpu-memory-utilization 0.85不是越高越好要留出推理時 KV cache 的空間否則并發(fā)一高就 OOMserved-model-name換成local-deepseek后代碼里的model字段要同步改連接地址指向本機端口。本地部署的價值在離線批處理每天凌晨把全量門店的銷量序列、促銷計劃、節(jié)假日表整理成標(biāo)準(zhǔn) prompt調(diào)用本地模型生成特征代碼和異常診斷再傳給訓(xùn)練任務(wù)。這樣既不用把核心數(shù)據(jù)送出去也不會因為外部接口限流耽誤窗口。但要注意本地部署不等于免費硬件和運維成本要算進去如果只有一兩個建模師用直接用 API 更省心。部署完可以用和 API 相同的OpenAI客戶端把base_url改成http://localhost:8000/v1就能調(diào)用這是 OpenAI 兼容生態(tài)的一個紅利。選本地部署還是 API建議先看月度調(diào)用量如果每天只有幾十次特征生成和診斷請求用 API 就行如果要在門店端實時回答銷量問答再把量化模型推到內(nèi)網(wǎng)服務(wù)器。DeepSeek 的 API 價格按 token 計費代碼生成這類任務(wù)通常幾百 token 一次量少的時候成本可以忽略量大了再評估部署成本也不遲。模型導(dǎo)出方面vLLM 直接加載開源權(quán)重不需要額外導(dǎo)出格式如果用 Ollama 之類工具導(dǎo)入更像是打包鏡像。部署好后用curl /v1/models確認(rèn)服務(wù)可用。4. 訓(xùn)練銷量預(yù)測模型DeepSeek 輔助生成回歸基線再融合4.1 基線模型選型為什么不先上深度模型餐飲連鎖的門店×菜品級別數(shù)據(jù)單店單品的日銷量序列通常只有 1~3 年業(yè)務(wù)上還夾雜節(jié)假日、裝修、換菜單等突變。這種規(guī)模下深度模型比如 LSTM、Transformer 很容易過擬合而且調(diào)參成本高換一個門店就要重新驗證。相比之下LightGBM 這類梯度提升樹是絕大多數(shù)實戰(zhàn)項目的首選能處理類別特征、對缺失值不敏感、訓(xùn)練速度快還能輸出特征重要度。我一般會先用簡單均值模型或嶺回歸做一個 baseline再用 LightGBM最后根據(jù)誤差分布決定要不要分店訓(xùn)練。DeepSeek 在這個環(huán)節(jié)的用處是把你整理好的特征說明和目標(biāo)定義喂給它讓它生成一份完整的 LightGBM 訓(xùn)練腳本做起點。它寫的版本可能不是最優(yōu)但能省去從空白文件開始的時間。真正要花時間的是調(diào)時序驗證的參數(shù)和監(jiān)控過擬合。4.2 訓(xùn)練代碼時序交叉驗證與 LightGBM 回歸銷量預(yù)測不能用隨機劃分必須按時間切分。下面這段代碼是基于 sklearn 的 TimeSeriesSplitimport lightgbm as lgb import numpy as np from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error X features.sort_values(biz_date) # 特征表已按時間排序 y X.pop(total_qty) params { objective: regression, metric: mae, learning_rate: 0.05, num_leaves: 31, min_child_samples: 20, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbosity: -1, } tscv TimeSeriesSplit(n_splits5, gap7) mae_list [] for fold, (tr_idx, va_idx) in enumerate(tscv.split(X)): dtrain lgb.Dataset(X.iloc[tr_idx], y.iloc[tr_idx]) dvalid lgb.Dataset(X.iloc[va_idx], y.iloc[va_idx]) model lgb.train( params, dtrain, num_boost_round2000, valid_sets[dvalid], callbacks[lgb.early_stopping(100), lgb.log_evaluation(50)], ) preds model.predict(X.iloc[va_idx]) mae_list.append(mean_absolute_error(y.iloc[va_idx], preds)) print(CV MAE:, np.mean(mae_list))邏輯說明gap7是時序切分的間隔目的是讓訓(xùn)練集和驗證集之間留出至少 7 天避免相鄰天自相關(guān)導(dǎo)致的虛假高精度。如果沒有這個間隔模型在驗證集上可能看起來誤差 6%上線后一到新數(shù)據(jù)就變成 20% 以上。metricmae而不是 MAPE是因為銷量為 0 的日子會把 MAPE 撐到無限大數(shù)量級不統(tǒng)一時再用分店別 MAE 評價。early_stopping(100)設(shè)太大容易過擬合設(shè)太小又可能欠擬合100 輪在餐飲日粒度數(shù)據(jù)上通常夠用。參數(shù)調(diào)優(yōu)時我一般讓 DeepSeek 先給一組候選參數(shù)再用網(wǎng)格或 Optuna 小范圍搜。注意 LightGBM 的num_leaves和min_child_samples要一起動單純調(diào)大num_leaves會讓樹看到太多局部噪聲。還有一個容易忽略的點categorical_feature參數(shù)要顯式傳入門店 ID、菜品 ID 等類別列而不是讓 LightGBM 自己猜否則類別編碼方式可能不一致。訓(xùn)練完要保存模型和特征順序供上線復(fù)用。用 joblib 保存import joblib joblib.dump(model, lgb_daily_sales_model.pkl) joblib.dump(list(X.columns), feature_columns.pkl)這里特別說明特征順序文件是后悔藥。上線時如果新數(shù)據(jù)列順序和訓(xùn)練時不一致LightGBM 不會報錯但它會靜默地把特征名對應(yīng)錯位預(yù)測結(jié)果看著正常實則全錯。每次重訓(xùn)都重新保存特征列表。4.3 分門店×SKU訓(xùn)練還是統(tǒng)一模型模型維護成本的權(quán)衡全部門店一個模型還是每個門店一個模型是餐飲預(yù)測項目里最常被問的問題。我的判斷標(biāo)準(zhǔn)是樣本量和業(yè)務(wù)差異。方案優(yōu)點缺點適用場景統(tǒng)一模型樣本多、泛化好、冷啟動容易門店和菜品差異被平均店型差異小、SKU 標(biāo)準(zhǔn)化高的連鎖分店/分SKU模型擬合強、能抓單店特征新店無模型、維護幾十上百個模型店型差異大、只做核心SKU分層模型兼顧泛化和個性化分層邏輯需要穩(wěn)定按區(qū)域/店型/商圈分層我一般先訓(xùn)練統(tǒng)一模型然后用按門店的殘差分析決定要不要分層。如果某些店型的預(yù)測殘差系統(tǒng)性偏高就給這些店單獨建一個分店模型或者在模型里加入store_type作為強特征。不要一開始就搞幾百個小模型訓(xùn)練管線會很脆。DeepSeek 在這個環(huán)節(jié)可以幫忙做分層建議把門店維度匯總數(shù)據(jù)店型、商圈、有無食堂、營業(yè)時長給它讓它輸出分層方案。但最終是否采取要看分層后驗證集 MAE 有沒有真的下降不能憑感覺。5. 避坑手冊POS 數(shù)據(jù)訓(xùn)練最常見的 5 個坑這 5 個坑是同一個項目里反復(fù)踩過的血淚經(jīng)驗每一條都讓我重寫過訓(xùn)練集。現(xiàn)象、原因和解決方式分開寫方便你對照排查。5.1 坑一把“實付金額”當(dāng)銷量模型被折扣帶偏現(xiàn)象雨天預(yù)測營業(yè)額總是偏高最后發(fā)現(xiàn)門店雨天在沖業(yè)績做滿減活動實付金額大幅下跌但實際出貨件數(shù)并沒有跌。模型像被“打折”牽著走把折扣波動當(dāng)成了需求波動。原因訓(xùn)練目標(biāo)選的是實付金額這個字段直接受促銷強度影響而且營業(yè)數(shù)據(jù)里的折扣往往是銷售后回填的訓(xùn)練時是“已知未來”上線時卻拿不到當(dāng)天精確折扣特征分布完全不一致。解決預(yù)測目標(biāo)一律改成item_qty或“標(biāo)準(zhǔn)牌價營業(yè)額”折扣信息作為特征avg_discount參與訓(xùn)練而不是目標(biāo)。如果業(yè)務(wù)一定要預(yù)測營業(yè)額就按“擬合件數(shù) × 標(biāo)準(zhǔn)牌價”拆兩步最后再用折扣比例做校準(zhǔn)。5.2 坑二隨機切分訓(xùn)練集和測試集導(dǎo)致預(yù)測“穿越”現(xiàn)象驗證集 MAPE 只有 6%門店反饋“一點都不準(zhǔn)”上線后周誤差到 20% 以上。原因隨機切分把后 30 天的數(shù)據(jù)和前 30 天的數(shù)據(jù)混在訓(xùn)練集里模型直接“見過”與驗證日期相鄰的日子靠記憶拿高分。銷量預(yù)測是時間序列不是普通分類任務(wù)。解決強制按時間切分做 TimeSeriesSplit并且設(shè)置gap。我一般至少留 7 天間隔再配合第 4 章的 CV 代碼。還要檢查驗證集是否落在訓(xùn)練集時間范圍之后這是最基本的上線前檢查。5.3 坑三節(jié)假日特征只寫“是不是節(jié)假日”忽略提前備貨現(xiàn)象五一前兩天的預(yù)測總是低估五一當(dāng)天反而高估。原因是門店提前接團餐、鎖客需求峰值發(fā)生在節(jié)前節(jié)日當(dāng)天客流反而減少。原因單一is_holiday特征把節(jié)前、節(jié)中、節(jié)后混為一談模型分不清“節(jié)日前備貨”和“節(jié)日當(dāng)天銷售”。解決增加節(jié)前第 1/2 天、節(jié)后第 1/2 天、連續(xù)假期第幾天等特征。節(jié)假日表是公開日歷預(yù)測日就能拿到不存在穿越。這也是 2.3 節(jié)里那組偏移特征的主要動機。5.4 坑四新店/閉店樣本混進訓(xùn)練預(yù)測被污染現(xiàn)象新店上線時模型預(yù)測極低運營抱怨“新店是翻倍的客流怎么給這么少”。原因訓(xùn)練數(shù)據(jù)里包含新店爬坡期樣本和閉店前甩賣樣本。新店銷量從 0 爬升閉店銷量異常下滑統(tǒng)一模型學(xué)到的是“低銷量”模式對新店自然壓低。解決訓(xùn)練前過濾營業(yè)不滿 90 天的樣本新店單獨走規(guī)則或單獨訓(xùn)練模型閉店門店直接不參與訓(xùn)練和預(yù)測。把days_since_open當(dāng)特征還不夠必須顯式剔除生命周期兩端的樣本否則模型會把開業(yè)爬坡當(dāng)成正常的周度波動。5.5 坑五DeepSeek 生成的代碼“看起來對”運行卻報錯現(xiàn)象讓 DeepSeek 生成的清洗腳本在本地跑通訓(xùn)練出來的模型上線后一天比一天誤差大。后來核對發(fā)現(xiàn)它把order_time當(dāng)成字符串直接取hour沒做時區(qū)轉(zhuǎn)換跨夜門店的數(shù)據(jù)整體錯位。原因語言模型不懂 POS 數(shù)據(jù)的權(quán)限、時區(qū)和狀態(tài)字段含義會默認(rèn)按常識解釋字段名。它生成的代碼語法沒有錯但業(yè)務(wù)語義錯得離譜這正是“AI 代碼助手”最容易翻車的地方。解決讓 DeepSeek 生成任何代碼之前先給它字段清單和業(yè)務(wù)規(guī)則比如“order_time 是 UTC 時間轉(zhuǎn)換到門店時區(qū)再取小時”“退款單 order_status2 已過濾”。生成后用小樣本跑一遍對比統(tǒng)計量是否符合常識。核心原則LLM 是加速器不是負(fù)責(zé)驗證的免檢章。6. 模型上線與滾動訓(xùn)練驗證 MAE 是否可用的一個技巧6.1 用“預(yù)測區(qū)間”代替“單一數(shù)字”與門店溝通上線之后我發(fā)現(xiàn)門店店長最在意的不是“你預(yù)測 300 份”而是“備多少夠賣”。只給單一數(shù)字一旦賣超后廚斷貨店長就會說模型不準(zhǔn)。后來我把歷史 90 天的預(yù)測誤差取 90% 分位數(shù)作為區(qū)間半徑輸出[預(yù)測-半徑, 預(yù)測半徑]門店按區(qū)間上沿備貨斷貨投訴立刻少了。import numpy as np # y_true / y_pred 為最近 90 天實際值和預(yù)測值 radius np.quantile(np.abs(np.array(y_true) - np.array(y_pred)), 0.9) y_hat model.predict(new_data) print(f預(yù)測 {y_hat:.0f}建議備貨區(qū)間 [{y_hat - radius:.0f}, {y_hat radius:.0f}])6.2 每周滾動回測模型退化提醒腳本模型不是訓(xùn)練完就結(jié)束了。菜單調(diào)整、商圈變化、天氣異常都會讓誤差退化。我做了一個每周自動滾動回測的腳本按周切分計算最近 4 周的 MAE并與上周對比。一旦連續(xù)兩周 MAE 上漲超過 15%就會觸發(fā)重訓(xùn)提醒而不是等門店來投訴。# 偽代碼示意每周一執(zhí)行 for week_end in range(30, len(features), 7): tr features[:week_end] va features[week_end:week_end 7] model.fit(tr) mae mean_absolute_error(va.y, model.predict(va)) weekly_mae.append(mae)這段代碼的關(guān)鍵是固定訓(xùn)練窗口長度不要無限增長老數(shù)據(jù)會稀釋最近的需求變化。我現(xiàn)在養(yǎng)成的習(xí)慣是每次重訓(xùn)前先跑一遍最近一天的數(shù)據(jù)做 sanity check如果預(yù)測值和上周同門店誤差超過訓(xùn)練時 MAE 的 3 倍先查數(shù)據(jù)管道再查模型。這個習(xí)慣救了我好多次。希望幫到你。本文還有配套的精品資源點擊獲取