
1. 項目概述從一道賽題到一套完整的供應鏈優化方案如果你關注過近幾年的數學建模競賽尤其是像MathorCup、國賽這類含金量高的比賽會發現一個明顯的趨勢賽題越來越“接地氣”越來越貼近真實的產業痛點。2023年MathorCup大數據挑戰賽的B題——“電商零售商家需求預測及庫存優化問題”就是一個絕佳的例子。它不再是一個純粹的、理想化的數學問題而是將我們直接拋入了一個電商運營者每天都要面對的、充滿不確定性的現實戰場如何根據歷史銷售數據精準預測未來需求并以此為依據制定最優的庫存策略從而在避免缺貨損失和減少庫存積壓之間找到那個微妙的平衡點這道題的價值遠不止于贏得比賽。它本質上是一套完整的、數據驅動的供應鏈管理核心方法論。對于電商從業者、供應鏈分析師、數據科學入門者甚至是相關專業的學生來說深入理解并實踐這道題的解題思路就相當于掌握了一套從數據清洗、特征工程、模型構建到策略優化的標準工作流。本文將基于這道賽題結合我多年在數據分析和供應鏈領域的實戰經驗為你完整拆解從問題理解到代碼實現的每一個環節并提供大量在標準答案之外才會提及的“踩坑”心得和優化技巧。我們的目標不是復現一篇論文而是打造一個你未來可以隨時套用、調整的實戰工具箱。2. 核心問題拆解需求預測與庫存優化的雙螺旋面對這樣一個綜合性問題第一步也是最重要的一步就是進行清晰的問題拆解。我們不能一上來就埋頭搞算法必須先把業務邏輯理清楚。這道題可以看作兩個相互耦合的核心子問題它們像DNA雙螺旋一樣緊密交織。2.1 需求預測洞察未來的銷售脈搏需求預測是整個優化體系的基石。預測不準后續的所有庫存決策都是空中樓閣。題目通常會提供歷史銷售數據可能包含商品SKU、日期、銷售量、銷售額、是否有促銷活動等信息。我們的目標是建立一個模型能夠根據歷史規律預測未來一段時間比如接下來4-8周每個SKU的每周需求量。這里的關鍵在于理解影響需求的“特征”。除了時間序列本身趨勢、季節性我們必須深入業務層面尋找解釋變量時間特征這是基礎。包括年、月、周、日、是否為周末、節假日包括節前節后的影響期。對于電商而言“購物節”效應如618、雙11必須單獨建模其銷量可能是平日的數十倍。商品特征商品類別、價格、生命周期是新品、成熟品還是清倉品。不同品類的銷售模式天差地別比如快消品和耐用消費品。營銷特征是否有促銷活動、促銷力度折扣率、廣告投放量。這是人為干預銷量最直接的因素。外部特征天氣尤其對服裝、飲品等品類、宏觀經濟指數、競爭對手動態等。這部分數據可能需要額外獲取但價值巨大。注意很多新手會犯一個錯誤——急于應用復雜的LSTM、Transformer等深度學習模型。但在實際業務中尤其是在數據量并非極度龐大、且需要強解釋性的場景下梯度提升樹模型如XGBoost、LightGBM往往是更穩健、更高效的首選。它們能很好地處理表格型數據、缺失值并給出特征重要性幫助我們理解到底是什么在驅動銷量變化。2.2 庫存優化在成本與服務的鋼絲上舞蹈有了需求預測我們進入第二環節庫存優化。這里的核心是建立一個成本模型并在滿足一定服務水平的前提下使其最小化。主要成本通常包括持有成本資金占用、倉儲費、損耗、保險費等。通常表示為單位商品單位時間的持有費用。訂貨成本每次下單產生固定費用如物流、行政成本。缺貨成本因庫存不足導致銷售損失帶來的利潤損失以及可能產生的商譽損害。這部分最難量化但至關重要。庫存優化的經典策略是(s, S)策略或定期盤點(R, S)策略。對于本題更可能探討的是安全庫存的設置。安全庫存是為了應對需求的不確定性預測誤差和供應的不確定性提前期波動而設置的緩沖庫存。其核心公式為安全庫存 Z * σ_L。 其中Z是基于目標服務水平如95%從標準正態分布表中查得的系數服務水平95%對應Z約1.65。σ_L是提前期內的需求標準差。這里就體現了預測的重要性——σ_L的估算嚴重依賴于我們對需求波動性即預測誤差分布的準確衡量。實操心得在實際電商庫存管理中“服務水平”不是一個僵化的數字。對于高利潤、高影響力的核心SKU我們可能追求99%的服務水平對于低值、長尾商品95%甚至更低也可能是可接受的成本權衡。在建模時可以嘗試建立分層的服務水平策略為不同重要度的商品設置不同的Z值這樣能顯著降低整體庫存成本。3. 數據預處理與特征工程實戰拿到數據后80%的時間和價值在于預處理和特征工程。這一步做得好簡單的模型也能出好效果這一步做得差再復雜的模型也是徒勞。3.1 數據清洗識別并處理“臟數據”競賽提供的數據通常已相對規整但我們必須以實戰的嚴謹態度對待。缺失值處理對于銷售數據某天銷量為0不一定是缺失可能是真沒賣出。但如果是關鍵特征如價格缺失則需要根據業務判斷是向前/向后填充還是用同類商品均值填充或直接剔除該樣本。異常值檢測與處理促銷日的銷量暴增不是異常值而是需要建模的關鍵點。真正的異常值可能是數據錄入錯誤如多寫一個零。可以使用IQR四分位距法或3σ原則進行統計識別但處理前務必結合業務邏輯判斷。對于明確的錯誤應予修正或剔除對于無法解釋的極端值穩健的做法是將其縮尾處理而非直接刪除以避免損失信息。數據一致性檢查確保商品ID、類別等編碼唯一且一致時間格式統一。3.2 特征構建打造模型的“營養餐”這是將原始數據轉化為模型可理解、可學習信息的關鍵步驟。我們圍繞需求預測構建特征。1. 時間特征import pandas as pd # 假設 df 有 ‘date’ 列 df[date] pd.to_datetime(df[date]) df[year] df[date].dt.year df[month] df[date].dt.month df[week_of_year] df[date].dt.isocalendar().week df[day_of_week] df[date].dt.dayofweek # 周一0 周日6 df[is_weekend] df[day_of_week].apply(lambda x: 1 if x 5 else 0) df[is_holiday] ... # 需要自定義節假日列表進行標記 df[days_to_major_sale] ... # 計算距離最近大促如雙11的天數這是一個強特征2. 滯后特征與滑動窗口特征這是時間序列預測的核心。用過去的數據預測未來。for lag in [1, 7, 14, 21, 28]: # 滯后1天、1周、2周等 df[fsales_lag_{lag}] df.groupby(sku_id)[sales].shift(lag) for window in [7, 14, 30]: # 過去7天、14天、30天的均值/標準差 df[fsales_rollmean_{window}] df.groupby(sku_id)[sales].transform( lambda x: x.rolling(windowwindow, min_periods1).mean() ) df[fsales_rollstd_{window}] df.groupby(sku_id)[sales].transform( lambda x: x.rolling(windowwindow, min_periods1).std() )3. 統計特征商品級別的統計信息如歷史平均銷量、銷量中位數、變異系數標準差/均值衡量波動性等。sku_stats df.groupby(sku_id)[sales].agg([mean, median, std, max, min]).reset_index() sku_stats.columns [sku_id, sku_mean_sales, sku_median_sales, sku_std_sales, sku_max_sales, sku_min_sales] df df.merge(sku_stats, onsku_id, howleft) df[sku_cv] df[sku_std_sales] / (df[sku_mean_sales] 1e-5) # 避免除零4. 交互特征例如“是否周末”與“是否有促銷”的交互項可能揭示周末促銷的額外效應。踩坑提醒特征不是越多越好。過多的特征尤其是高度相關的特征會導致模型過擬合、訓練慢、可解釋性變差。一定要進行特征篩選。可以使用LightGBM/XGBoost訓練后查看特征重要性剔除重要性極低的特征也可以計算特征之間的相關性矩陣剔除高度共線性的特征。在構建滯后特征時需特別注意避免數據泄露——絕不能使用未來的信息來預測過去。4. 需求預測模型構建與評估特征工程完成后我們進入模型構建階段。我將以一個基于LightGBM的回歸模型為例展示完整流程。4.1 模型選擇與訓練為什么選LightGBM因為它速度快、精度高、能處理缺失值、自帶特征重要性評估且對異常值相對穩健。import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, mean_squared_error # 1. 準備數據假設X是特征DataFramey是目標銷量或對數變換后的銷量 # 注意需要先劃分時間序列不能隨機shuffle。 # 假設數據按時間排序我們取前80%時間作為訓練集后20%作為測試集。 split_point int(len(df) * 0.8) X_train, X_test X.iloc[:split_point], X.iloc[split_point:] y_train, y_test y.iloc[:split_point], y.iloc[split_point:] # 2. 創建并訓練模型 lgb_train lgb.Dataset(X_train, y_train) lgb_eval lgb.Dataset(X_test, y_test, referencelgb_train) params { boosting_type: gbdt, objective: regression, # 回歸任務 metric: {l2, l1}, # 評估指標均方誤差和平均絕對誤差 num_leaves: 31, # 控制模型復雜度值越大越容易過擬合 learning_rate: 0.05, feature_fraction: 0.9, # 每次迭代隨機選擇90%的特征防止過擬合 bagging_fraction: 0.8, # 每次迭代隨機選擇80%的數據防止過擬合 bagging_freq: 5, verbose: 0, force_col_wise: True # 對于特征較多的情況可加速訓練 } gbm lgb.train(params, lgb_train, num_boost_round1000, # 迭代輪數 valid_setslgb_eval, callbacks[lgb.early_stopping(stopping_rounds50)]) # 早停法防止過擬合 # 3. 預測 y_pred gbm.predict(X_test, num_iterationgbm.best_iteration)4.2 預測結果評估與誤差分析預測不能只看一個總的MAE或RMSE必須進行深入的誤差分析。import numpy as np import matplotlib.pyplot as plt # 計算誤差指標 mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) mape np.mean(np.abs((y_test - y_pred) / (y_test 1e-5))) * 100 # 平均絕對百分比誤差 print(fMAE: {mae:.2f}) print(fRMSE: {rmse:.2f}) print(fMAPE: {mape:.2f}%) # 誤差分析1按商品類別分析 test_df X_test.copy() test_df[true_sales] y_test.values test_df[pred_sales] y_pred test_df[abs_error] np.abs(test_df[true_sales] - test_df[pred_sales]) error_by_category test_df.groupby(category)[abs_error].mean().sort_values(ascendingFalse) print(各品類平均絕對誤差) print(error_by_category.head(10)) # 查看誤差最大的品類 # 誤差分析2按銷量水平分析 test_df[sales_level] pd.qcut(test_df[true_sales], q5, labels[很低,低,中,高,很高]) error_by_level test_df.groupby(sales_level)[abs_error].mean() print(\n不同銷量水平的平均絕對誤差) print(error_by_level) # 誤差分析3可視化預測 vs 實際 plt.figure(figsize(12,6)) plt.plot(test_df[true_sales].values[:100], labelTrue, alpha0.7) plt.plot(test_df[pred_sales].values[:100], labelPredicted, alpha0.7) plt.legend() plt.title(Sample of Predictions vs True Values) plt.xlabel(Time Index) plt.ylabel(Sales) plt.show()關鍵點解讀MAPE的陷阱當真實值很小時MAPE會異常放大失去參考意義。對于存在零值或接近零值的數據建議使用對稱平均絕對百分比誤差sMAPE或關注MAE/RMSE。誤差分析的價值它告訴我們模型在哪里表現不好。是長尾商品預測不準還是促銷日的峰值抓不住或者是新品冷啟動問題針對不同的問題需要不同的優化策略。例如對于促銷日預測不準可以嘗試將促銷數據單獨建模或引入更復雜的促銷衰減效應特征。4.3 模型集成與序列預測對于重要商品或預測難度大的場景可以嘗試模型集成如將LightGBM、XGBoost和線性回歸的結果加權平均。對于明顯具有自相關性的純時間序列可以先用Prophet或ARIMA捕捉趨勢和季節項將其殘差再用LightGBM進行建模捕捉其他特征的影響這是一種有效的混合模型思路。個人經驗在電商需求預測中分層次、分顆粒度建模往往比一個“大而全”的模型效果更好。例如先預測品類總銷量再按歷史占比分解到SKU或對暢銷品、普通品、滯銷品分別建立不同的模型采用不同的特征集和參數。這更符合業務管理邏輯也更容易控制誤差。5. 庫存優化模型建立與求解獲得未來周期的需求預測均值μ和標準差σ后我們進入庫存優化環節。這里我們聚焦于經典的周期盤點庫存模型并考慮安全庫存。5.1 確定成本參數與服務目標首先我們需要與業務方在比賽中則是根據題目假設確定幾個核心參數h: 單位產品單位時間的持有成本元/件/天。K: 每次訂貨的固定成本元/次。p: 單位產品的缺貨損失成本元/件。這通常大于產品毛利因為它包含了利潤損失和商譽損失。L: 訂貨提前期天即從下單到貨物入庫的時間。Service Level (SL): 目標服務水平如95%表示一個補貨周期內需求被滿足的概率。5.2 計算經濟訂貨批量與再訂貨點對于需求相對穩定、采用連續盤點或定期盤點的場景經濟訂貨批量模型仍有指導意義。經濟訂貨批量EOQ sqrt( (2 * D * K) / h )其中D是年預測總需求。這個公式給出了在理想條件下總成本最低的每次訂貨量。但在需求波動大的電商環境EOQ更多是參考。再訂貨點這是庫存優化的核心。ROP 提前期內的平均需求 安全庫存。提前期內平均需求 L * μμ為日預測需求均值。安全庫存 Z * σ_L。σ_L是提前期L內的需求標準差。如果日需求獨立同分布則σ_L σ_daily * sqrt(L)。其中σ_daily可以從預測模型的誤差分布中估算。關鍵計算示例 假設我們預測某個SKU未來日需求均值μ100件日需求標準差σ20件。提前期L7天目標服務水平95%對應Z1.65。提前期內平均需求 100 * 7 700件。提前期內需求標準差 σ_L 20 * sqrt(7) ≈ 52.9件。安全庫存 1.65 * 52.9 ≈ 87.3件。再訂貨點 ROP 700 87.3 ≈ 787.3件向上取整為788件。這意味著當庫存水平下降到788件時就應該立即啟動補貨流程訂購一定數量如EOQ或根據未來周期需求計算出的量的貨物以期在庫存耗盡前新貨能送達。5.3 模擬仿真與策略評估紙上計算終覺淺絕知此事要“仿真”。建立成本模型后最好的驗證方法是用歷史數據或模擬需求數據進行滾動仿真。import numpy as np def inventory_simulation(demands, lead_time, initial_inventory, reorder_point, order_quantity, h, K, p): 模擬一個庫存策略的執行過程。 :param demands: 列表每日實際需求 :param lead_time: 提前期天 :param initial_inventory: 期初庫存 :param reorder_point: 再訂貨點 :param order_quantity: 固定訂貨量 :param h: 單位持有成本 :param K: 單次訂貨成本 :param p: 單位缺貨成本 :return: 總成本平均庫存水平缺貨次數 inventory initial_inventory on_order 0 # 在途庫存 order_arrival_day [] # 記錄在途訂單的到達日期和數量 total_holding_cost 0 total_ordering_cost 0 total_shortage_cost 0 daily_inventory [] for day, demand in enumerate(demands): # 1. 接收在途訂單如果今天有到貨 arrivals [q for d, q in order_arrival_day if d day] if arrivals: inventory sum(arrivals) order_arrival_day [(d, q) for d, q in order_arrival_day if d ! day] # 2. 滿足當日需求 if inventory demand: inventory - demand shortage 0 else: shortage demand - inventory inventory 0 # 全部售罄 total_shortage_cost shortage * p # 3. 計算當日持有成本基于當日結束時的庫存 total_holding_cost inventory * h daily_inventory.append(inventory) # 4. 檢查是否需要訂貨假設每日盤點一次 if inventory on_order reorder_point: # 發出訂單 total_ordering_cost K on_order order_quantity order_arrival_day.append((day lead_time, order_quantity)) total_cost total_holding_cost total_ordering_cost total_shortage_cost avg_inventory np.mean(daily_inventory) service_level 1 - (sum([1 for d in demands if d 0]) - sum([1 for i in daily_inventory if i 0])) / sum([1 for d in demands if d 0]) # 簡化計算 return total_cost, avg_inventory, service_level # 使用預測的需求和實際需求或模擬需求進行仿真 simulated_demands np.random.normal(loc100, scale20, size365) # 模擬一年需求 simulated_demands np.maximum(simulated_demands, 0).astype(int) # 需求非負 total_cost, avg_inv, sl inventory_simulation( demandssimulated_demands, lead_time7, initial_inventory800, reorder_point788, # 我們之前計算的ROP order_quantity500, # 假設的訂貨量 h0.1, # 每件每天0.1元 K50, # 每次訂貨50元 p20 # 每缺貨一件損失20元含利潤 ) print(f仿真總成本{total_cost:.2f}元平均庫存{avg_inv:.2f}件服務水平{sl:.2%})通過調整reorder_point和order_quantity運行多次仿真我們可以找到使總成本最小化的參數組合。這就是仿真優化的思路。核心技巧安全庫存公式Z * σ_L中的σ_L強烈建議使用預測誤差的標準差而非歷史需求的標準差。因為安全庫存是為了應對“預測不準”而不是需求本身的波動。你可以用訓練好的模型在驗證集上做預測計算預測值與真實值的誤差然后分析這些誤差在提前期L內的分布和標準差。這樣計算出的安全庫存更具魯棒性。6. 完整建模流程串聯與代碼框架將以上步驟串聯起來形成一個端到端的解決方案。以下是核心流程的偽代碼框架# 1. 數據加載與探索 data pd.read_csv(sales_data.csv) print(data.info()) print(data.describe()) # 可視化銷量時間序列、分布、與促銷的關系等 # 2. 數據預處理與特征工程 df_clean data_cleaning(data) df_featured feature_engineering(df_clean) # 3. 劃分訓練集與測試集按時間順序 X df_featured.drop(columns[sales, date, sku_id]) # 特征 y df_featured[sales] # 目標 # 時間序列劃分 X_train, X_test, y_train, y_test time_based_split(X, y, split_ratio0.8) # 4. 訓練需求預測模型 model train_lgb_model(X_train, y_train, X_test, y_test) # 評估模型 evaluate_model(model, X_test, y_test) # 分析特征重要性 plot_feature_importance(model) # 5. 對未來周期進行預測 future_features generate_future_features(last_date, future_periods) # 生成未來時間的特征 future_demand_mean model.predict(future_features) # 預測未來需求均值 future_demand_std estimate_forecast_error(model, X_test, y_test) # 估算預測誤差標準差 # 6. 庫存優化計算 holding_cost 0.1 ordering_cost 50 shortage_cost 20 lead_time 7 service_level 0.95 # 計算每個SKU的再訂貨點和建議訂貨量 inventory_policy_df calculate_inventory_policy( future_demand_mean, future_demand_std, lead_time, service_level, holding_cost, ordering_cost ) # inventory_policy_df 包含 columns: [sku_id, reorder_point, order_quantity, safety_stock] # 7. 可選策略仿真與驗證 simulation_results run_inventory_simulation( historical_demands, # 可用測試集數據或生成數據 inventory_policy_df, lead_time, holding_cost, ordering_cost, shortage_cost ) print(simulation_results[[total_cost, service_level_achieved]])7. 參賽進階思路與常見問題排雷如果你是為MathorCup或類似比賽準備除了上述標準流程以下幾點能讓你脫穎而出1. 考慮現實約束倉儲容量限制總庫存不能超過倉庫容量。這需要在優化模型中添加約束條件可能用到線性/整數規劃。資金約束采購總金額有限制。供應商最小起訂量訂貨量必須是MOQ的整數倍。多級庫存考慮中央倉、區域倉、前置倉的多級補貨策略。2. 需求預測的進階處理新品/冷啟動問題對于沒有歷史數據的新品可以采用相似品類的銷售模式、市場調研數據、或基于商品屬性的回歸模型進行預測。促銷峰值的后效應大促后常伴隨銷量低谷需求前置需要在特征中體現。不確定性量化不僅預測需求的均值還預測其概率分布如分位數回歸為魯棒的庫存決策提供更多信息。3. 庫存模型的優化求解對于更復雜的策略如(s, S)或帶有多種約束的模型解析解可能難以求得。此時可以借助優化庫進行求解。from scipy.optimize import minimize def total_cost_function(params, demand_mean, demand_std, lead_time, h, K, p, service_level_target): 定義總成本函數用于優化求解s和S s, S params # s是再訂貨點S是最大庫存水平 # 此處需要嵌入一個庫存成本仿真函數來計算給定(s,S)下的期望總成本 # 可以使用公式近似也可以用小規模蒙特卡洛模擬 expected_cost ... # 可以添加懲罰項如果服務水平不達標 service_level_actual ... penalty 1000 * max(0, service_level_target - service_level_actual) # 懲罰系數 return expected_cost penalty # 初始猜測和邊界 initial_guess [100, 300] bounds [(0, 500), (200, 1000)] # s和S的邊界 result minimize(total_cost_function, initial_guess, args(demand_mean, demand_std, lead_time, h, K, p, 0.95), boundsbounds, methodL-BFGS-B) optimal_s, optimal_S result.x4. 論文寫作與可視化問題重述要清晰用自己的話提煉問題本質。模型假設要合理且明確說明在什么條件下你的模型成立。求解過程要詳細特別是關鍵公式的推導和解釋。結果分析要深入不僅展示數字還要解釋為什么這個結果好/不好反映了什么業務洞察。靈敏度分析是亮點分析關鍵參數如持有成本、缺貨成本、提前期變化對最優策略和總成本的影響。這能體現你對模型魯棒性的思考。可視化要專業使用清晰的圖表展示銷量預測效果、庫存水平變化、成本構成、靈敏度分析結果等。避免花哨但無意義的圖表。常見問題排雷表問題現象可能原因排查與解決思路預測誤差極大尤其是峰值未識別或處理好促銷、節假日等特殊事件構建專門的節日/促銷標志特征或對特殊日期樣本單獨加權訓練。模型在訓練集表現好測試集差過擬合特征過多、模型過于復雜、或存在數據泄露1. 進行特征篩選。2. 增加正則化參數如num_leaves調小min_data_in_leaf調大。3. 嚴格檢查時間序列劃分確保沒有未來信息混入訓練集。安全庫存計算后缺貨依然頻繁1. 預測誤差標準差低估。2. 提前期不穩定。3. 需求分布非正態。1. 使用更保守的Z值如對應更高服務水平。2. 考慮提前期波動σ_L sqrt(L * σ_demand^2 μ_demand^2 * σ_leadtime^2)。3. 使用更穩健的誤差估計方法如Bootstrapping。庫存周轉率過低持有成本高安全庫存設置過高或訂貨批量EOQ過大。1. 重新評估服務水平目標是否過高。2. 考慮采用更頻繁的小批量補貨但會增加訂貨成本K。3. 實施ABC分類對A類商品精細管理C類商品采用簡單策略。求解速度慢無法得到最優解模型過于復雜或仿真次數太多。1. 對于復雜模型先嘗試簡化如忽略次要約束。2. 使用啟發式算法如遺傳算法、模擬退火替代精確求解。3. 減少蒙特卡洛仿真的迭代次數或使用方差縮減技術。這道賽題是一個完美的橋梁連接了數據科學理論與供應鏈管理實踐。從數據清洗到特征工程從機器學習建模到運籌優化最后通過仿真驗證它完整地再現了一個數據驅動決策的閉環。真正掌握它意味著你不僅擁有解決一道賽題的能力更擁有了應對現實世界中不確定性挑戰的一套核心方法論。在實際工作中這些模型和參數需要與業務系統持續集成并隨著市場變化而不斷迭代更新。記住沒有一勞永逸的預測也沒有放之四海而皆準的庫存策略唯有持續學習、持續優化才能在動態的商業環境中保持競爭力。