ARTICLE DETAIL

资讯详情

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

数学建模竞赛:基于需求预测与优化模型的蔬菜定价补货决策

数学建模竞赛:基于需求预测与优化模型的蔬菜定价补货决策 1. 项目概述从超市货架到数学模型的挑战每次走进超市的生鲜区看到那些码放整齐、色泽鲜亮的蔬菜你有没有想过这些菜的价格是怎么定出来的为什么今天西红柿贵了五毛明天黄瓜又打折了货架上快空了为什么不是马上补满而是要等一等这背后远不是“看心情”或者“凭经验”那么简单尤其是在大型连锁商超每天面对海量的销售数据、波动的采购成本和挑剔的顾客一个决策失误带来的可能就是成吨的腐烂损耗或是错失的销售机会。2023年高教社杯数学建模竞赛的C题正是将我们带入了这个充满烟火气又极具挑战的现实场景蔬菜类商品的自动定价与补货决策。这道题目的核心是要求我们建立一个数学模型帮助商超解决两个最头疼的问题第一明天每种蔬菜该卖多少钱自动定价第二明天每种蔬菜该进多少货补货决策。题目会提供过去一段时间内各种蔬菜的销售流水、损耗情况、批发市场价格等信息我们的任务就是从中挖掘规律构建一个能够同时优化商超利润或综合收益并控制风险的决策系统。这不仅仅是一道数学题它融合了数据分析、时间序列预测、库存管理、价格弹性理论以及运筹优化等多学科知识是对参赛者解决复杂系统工程问题能力的一次全面检验。对于参赛队伍来说无论你是擅长编程的数据高手还是精通优化理论的建模达人亦或是能将复杂问题清晰表述的写作能手这道题都提供了充分的发挥空间。它要求我们既要有扎实的数学模型功底又要有将模型与现实业务逻辑紧密结合的洞察力。接下来我将以一名多次参与并指导此类赛事的“老手”视角为你层层拆解这道题的解题思路、核心技术选型以及那些容易踩坑的细节。2. 核心需求解析与问题拆解面对“自动定价与补货决策”这个复合问题第一步也是最关键的一步就是进行清晰的问题拆解。我们不能试图用一个“大而全”的模型一口吃下所有问题而应该将其分解为几个逻辑连贯的子模块。2.1 定价与补货的内在联系与决策顺序首先要理解定价和补货这两个决策是如何相互咬合的。定价直接影响需求价格定高了销量可能锐减导致商品滞销最终腐烂损耗价格定低了销量可能上升但毛利变薄如果补货不足还会导致缺货损失销售机会。补货决策则决定了供给的上限补多了面临损耗风险和资金占用补少了无法满足需求损失收入和顾客好感。因此一个合理的决策框架应该是先预测再优化。具体来说可以遵循以下逻辑顺序需求预测基于历史数据预测未来一天或决策周期内在各种可能价格下的蔬菜需求量。这是所有后续决策的基石。联合决策优化在需求预测的基础上建立一个优化模型。这个模型的目标函数通常是最大化预期利润或考虑损耗成本后的净收益决策变量就是每种蔬菜的售价和补货量。优化过程需要同时考虑成本、售价、预测需求、损耗率、库存容量等多个约束条件。2.2 题目数据深度剖析竞赛题目通常会提供类似以下维度的数据我们需要像侦探一样从中提取有效信息销售流水包含日期、商品编码、销量、售价。这是分析历史需求模式的核心。损耗记录包含日期、商品编码、损耗量/损耗率。这是计算成本、约束补货的关键。批发市场价格或成本价包含日期、商品编码、批发价。这是计算利润的基础。商品分类信息如蔬菜的品类叶菜、果菜、根茎类。同类商品可能存在需求关联或价格弹性相似性。需要特别注意的数据问题数据清洗销售数据中可能存在节假日、促销活动带来的异常峰值需要进行识别和平滑处理。损耗数据可能不完整需要合理估算。稀疏性问题某些高端或小众蔬菜销售频率低数据稀疏直接建模困难可能需要借助品类共性进行信息借用。销量为零的记录要区分是“有货但没卖出”需求为零还是“缺货”需求未被满足。这通常需要结合当日库存结余或损耗数据来判断是建模中的一个难点。2.3 评价指标的理解题目可能会要求优化“利润”、“收益”或“综合效益”。我们需要明确其计算方式利润 销售收入 - 采购成本 - 损耗成本 - 其他成本可简化。损耗成本的处理至关重要。可以将损耗的商品按其成本价计入损失也可以将其视为一种效率惩罚项。综合效益有时题目会引入缺货惩罚机会成本、库存周转率奖励等需要将这些因素量化并融入目标函数。理解清楚“我们要优化什么”是构建模型目标的根本。3. 核心技术路线与模型选型基于以上拆解我们可以规划出一条从预测到优化的完整技术路线。这里没有唯一的“标准答案”但有几条经过验证的、效果不错的路径。3.1 需求预测模型选型需求预测是第一步也是最影响后续优化结果的一步。蔬菜需求通常具有以下特征短期波动性、可能的周期性每周模式、受价格显著影响、受季节性影响。常用的模型有传统时间序列模型如ARIMA自回归积分滑动平均模型。适用于有一定规律性的序列但难以直接纳入价格作为解释变量。对于蔬菜数据通常需要先进行平稳性检验和差分处理。机器学习回归模型线性回归/岭回归/Lasso回归将历史销量、历史价格、星期几、是否节假日等作为特征预测未来销量。Lasso回归还能进行特征选择防止过拟合。这是基础且有效的方法。树模型XGBoost, LightGBM这类模型能自动捕捉特征间的非线性关系对异常值不敏感在数据量足够的情况下往往表现优于线性模型。例如价格与销量的关系可能不是简单的线性而是存在“阈值效应”价格超过某个点销量断崖式下跌树模型能更好地拟合这种模式。考虑价格弹性的需求函数拟合这是更贴近经济学原理的方法。我们可以为每种蔬菜拟合一个需求函数其形式可以是线性的Q a - b*P或更常用的恒定弹性形式Q A * P^(-E)。其中Q是需求量P是价格E是价格弹性系数通常为正值表示价格上升1%需求下降E%。通过历史数据回归出参数a, b或A, E就可以直接建立销量与价格的数学关系为后续优化提供便利。实操心得在比赛中推荐采用“需求函数拟合”与“机器学习模型”相结合的策略。对于销售规律相对明显、数据量充足的大品类蔬菜可以尝试拟合需求函数因为它物理意义清晰便于接入优化模型。对于长尾稀疏商品则使用基于品类特征的机器学习模型如LightGBM进行预测稳定性更好。不要只用一个模型对付所有商品。3.2 定价-补货联合优化模型构建在获得需求预测模型即知道了销量 f(价格, 其他因素)之后我们就可以构建核心的优化模型了。决策变量p_i: 第i种蔬菜的销售单价。q_i: 第i种蔬菜的补货量或订单量。有时也可将预期销量d_i作为中间变量由p_i通过需求函数决定。目标函数最大化总利润。Maximize Σ [ p_i * d_i(p_i) - c_i * q_i - h_i * I_i ]其中d_i(p_i)是由定价决策p_i通过需求函数预测出的销量。c_i是第i种蔬菜的单位采购成本来自批发价数据。h_i是第i种蔬菜的单位损耗成本或持有成本。I_i是预期期末库存通常I_i 期初库存 q_i - d_i。如果I_i 0则可能产生损耗。约束条件需求约束预期销量不能超过预测需求即d_i D_i(p_i)其中D_i(p_i)是需求函数给出的最大可能需求。库存平衡I_i 期初库存 q_i - d_i。损耗约束根据历史数据期末库存I_i可能会按一定比例θ_i损耗。可以表示为损耗量 θ_i * I_i当I_i0并将损耗成本计入目标函数。更精细的模型可以将损耗视为一个随机事件。业务约束价格范围约束p_i_min p_i p_i_max可能来自市场监督或公司政策。补货量非负且可能有上限0 q_i q_i_max受仓储空间或供应商限制。资金约束Σ c_i * q_i 总采购预算。逻辑约束销量不能超过可用库存即d_i 期初库存 q_i。这个约束与需求约束共同决定了实际销量d_i min(预测需求, 可用库存)这是一个关键的非线性点需要巧妙线性化或使用优化求解器处理。模型求解如果需求函数d_i(p_i)是线性的且将min()函数线性化处理那么整个模型可以转化为一个**线性规划LP或二次规划QP**问题可以使用PuLPPython、Gurobi、CPLEX等优化求解器高效求解。如果需求函数是非线性的如指数形式或者问题规模不大也可以使用非线性规划NLP求解器或采用启发式算法如遗传算法、模拟退火进行求解。在比赛时间有限的情况下将问题合理简化至线性/二次规划是更稳妥的策略。4. 分步实现与关键细节处理有了理论框架我们来看看如何用代码和数据进行具体实现。这里以Python生态为例展示一个核心的实现流程。4.1 数据预处理与探索性分析import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler # 1. 加载数据 sales_df pd.read_csv(sales.csv) # 日期、商品、销量、售价 loss_df pd.read_csv(loss.csv) # 日期、商品、损耗量 cost_df pd.read_csv(cost.csv) # 日期、商品、批发价 # 2. 数据合并与清洗 # 按日期和商品编码将销售、损耗、成本数据关联起来 df pd.merge(sales_df, cost_df, on[date, prod_id], howleft) df pd.merge(df, loss_df, on[date, prod_id], howleft) # 处理缺失值损耗数据可能缺失假设缺失即为0损耗 df[loss_qty].fillna(0, inplaceTrue) # 计算关键衍生字段 df[profit] df[sales_qty] * (df[sale_price] - df[cost_price]) df[loss_rate] df[loss_qty] / (df[sales_qty] df[loss_qty]).replace(0, np.nan) # 3. 探索性分析以单个商品为例 sample_prod PROD001 prod_data df[df[prod_id] sample_prod].copy() prod_data.set_index(date, inplaceTrue) fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(prod_data[sale_price]) axes[0, 0].set_title(Price Trend) axes[0, 1].scatter(prod_data[sale_price], prod_data[sales_qty], alpha0.5) axes[0, 1].set_xlabel(Price) axes[0, 1].set_ylabel(Sales Qty) axes[0, 1].set_title(Price vs Sales) axes[1, 0].plot(prod_data[sales_qty]) axes[1, 0].set_title(Sales Qty Trend) axes[1, 1].boxplot([prod_data[prod_data[day_of_week]i][sales_qty] for i in range(7)]) axes[1, 1].set_title(Sales by Day of Week) plt.tight_layout() plt.show()通过可视化我们可以直观看到价格趋势、价格与销量的散点关系初步判断价格弹性、销量时间序列模式以及周内效应。4.2 需求预测模型实现示例这里展示使用LightGBM和拟合需求函数两种方法。方法一基于LightGBM的预测import lightgbm as lgb from sklearn.model_selection import train_test_split # 为每个商品构建训练数据集 def prepare_features(df, prod_id): prod_df df[df[prod_id]prod_id].copy() prod_df[date] pd.to_datetime(prod_df[date]) prod_df[day_of_week] prod_df[date].dt.dayofweek prod_df[month] prod_df[date].dt.month prod_df[lag1_sales] prod_df[sales_qty].shift(1) # 昨日销量 prod_df[lag7_sales] prod_df[sales_qty].shift(7) # 上周同期销量 prod_df[rolling_avg_price_7] prod_df[sale_price].rolling(7).mean() # ... 可以加入更多特征如节假日标识、天气数据如果提供等 prod_df.dropna(inplaceTrue) return prod_df # 训练模型 def train_lgb_model(feature_df): X feature_df[[sale_price, day_of_week, month, lag1_sales, lag7_sales, rolling_avg_price_7, cost_price]] y feature_df[sales_qty] X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, shuffleFalse) train_data lgb.Dataset(X_train, labely_train) val_data lgb.Dataset(X_val, labely_val, referencetrain_data) params { objective: regression, metric: rmse, boosting_type: gbdt, num_leaves: 31, learning_rate: 0.05, feature_fraction: 0.9 } model lgb.train(params, train_data, valid_sets[val_data], num_boost_round1000, early_stopping_rounds50, verbose_evalFalse) return model, X.columns.tolist() # 预测明日销量需要提供明日特征如计划价格、星期几等 def predict_with_lgb(model, features, tomorrow_features): # tomorrow_features是一个字典包含模型所需的所有特征值 input_df pd.DataFrame([tomorrow_features]) return model.predict(input_df[features])[0]方法二拟合常弹性需求函数from scipy.optimize import curve_fit def constant_elasticity_demand(P, A, E): 需求函数 Q A * P^(-E) return A * (P ** (-E)) def fit_demand_curve(price_data, sales_data): 拟合参数A和E # 过滤掉价格为0或销量为0的数据点 valid_mask (price_data 0) (sales_data 0) p price_data[valid_mask] q sales_data[valid_mask] try: # 提供初始猜测值 [A_guess, E_guess] popt, pcov curve_fit(constant_elasticity_demand, p, q, p0[np.mean(q)*np.mean(p), 1.5], maxfev5000) A_opt, E_opt popt return A_opt, E_opt except Exception as e: print(fFitting failed: {e}) return None, None # 对单个商品进行拟合 prod_price prod_data[sale_price].values prod_sales prod_data[sales_qty].values A, E fit_demand_curve(prod_price, prod_sales) if A and E: print(fFitted demand function: Q {A:.2f} * P^(-{E:.2f})) # 预测给定价格p需求为 constant_elasticity_demand(p, A, E)4.3 优化模型构建与求解使用PuLP假设我们采用线性需求函数d_i a_i - b_i * p_i并简化处理销量与库存的关系构建一个线性规划问题。import pulp # 假设有n种商品 n len(product_list) products product_list # 商品ID列表 # 创建问题实例最大化利润 prob pulp.LpProblem(Pricing_and_Replenishment, pulp.LpMaximize) # 定义决策变量 p pulp.LpVariable.dicts(Price, products, lowBound0) # 售价 q pulp.LpVariable.dicts(Order, products, lowBound0) # 补货量 d pulp.LpVariable.dicts(Sales, products, lowBound0) # 预期销量 # 假设我们已经从历史数据中拟合出每种商品的需求函数参数 a[i], b[i] # 以及获取了其他参数 initial_inventory {...} # 期初库存字典 cost_price {...} # 采购单价字典 loss_rate {...} # 损耗率字典 max_inventory {...} # 最大库存容量字典 budget 10000 # 总采购预算 # 设置目标函数总利润 销售收入 - 采购成本 - 损耗成本 # 简化损耗成本 损耗率 * (期初库存补货量-销量) * 成本价 profit_expr pulp.lpSum([d[i] * p[i] - cost_price[i] * q[i] - loss_rate[i] * (initial_inventory[i] q[i] - d[i]) * cost_price[i] for i in products]) prob profit_expr # 添加约束条件 for i in products: # 1. 需求函数约束销量 最大可能需求 (a - b*p) prob d[i] demand_a[i] - demand_b[i] * p[i], fDemand_Func_{i} # 2. 库存平衡与销量逻辑约束销量 可用库存 prob d[i] initial_inventory[i] q[i], fSales_Logic_{i} # 3. 期末库存非负简化实际可能允许负损耗 prob initial_inventory[i] q[i] - d[i] 0, fEnding_Inv_NonNeg_{i} # 4. 库存容量约束 prob initial_inventory[i] q[i] max_inventory[i], fMax_Inventory_{i} # 5. 价格上下限约束示例 prob p[i] cost_price[i] * 1.2, fMin_Price_{i} # 最低加价20% prob p[i] cost_price[i] * 3.0, fMax_Price_{i} # 最高加价200% # 总预算约束 prob pulp.lpSum([cost_price[i] * q[i] for i in products]) budget, Total_Budget # 求解问题 solver pulp.PULP_CBC_CMD(msgFalse) # 使用CBC求解器 prob.solve(solver) # 输出结果 print(pulp.LpStatus[prob.status]) for i in products: if q[i].varValue 0 or p[i].varValue 0: # 只输出有决策的商品 print(fProduct {i}: Price${p[i].varValue:.2f}, Order{q[i].varValue:.1f}, Expected Sales{d[i].varValue:.1f}) print(fTotal Expected Profit: ${pulp.value(prob.objective):.2f})5. 进阶考量与模型优化点基础的线性规划模型可能过于理想化。要让模型更贴近现实、在竞赛中脱颖而出需要考虑以下进阶问题5.1 处理不确定性随机规划或鲁棒优化前面的模型假设需求预测是绝对准确的这显然不现实。更高级的模型会考虑需求的不确定性。随机规划假设需求服从某种概率分布如正态分布均值为预测值标准差通过历史误差估计。然后优化期望利润。这需要更复杂的建模和求解技术如场景法。鲁棒优化不假设具体的分布只设定需求在一个不确定集合内波动例如预测值±20%。然后优化在最坏情况下的利润最大化最小值。这种方法结果更保守但能防范极端风险。简化实践在比赛中一个实用的方法是进行敏感性分析。在求得最优解后轻微扰动需求预测参数a_i, b_i观察最优定价和补货量如何变化并分析利润的稳定性。这能体现你对不确定性的思考。5.2 商品关联性分析蔬菜之间可能存在替代或互补关系。例如西红柿涨价顾客可能多买黄瓜替代生姜涨价可能不影响生菜独立。忽略这种关联性单独为每种商品定价可能会损失整体利润。分析方法可以计算不同商品销量之间的相关系数或者利用关联规则挖掘如Apriori算法发现经常被同时购买的商品组合。模型整合在需求预测阶段可以将相关商品的价格作为特征加入模型。在优化阶段可以引入交叉价格弹性但这会大大增加模型复杂度。一个折中方案是对强关联的商品组进行整体优化或是在目标函数中加入惩罚项防止关联商品价格设置得过于不合理。5.3 损耗模型的精细化损耗不是简单的线性比例。它可能与库存时间、商品特性、季节密切相关。动态损耗率可以建立损耗率与库存天数的关系。例如采用新鲜度衰减模型假设商品价值或可售性随时间指数衰减。这样优化模型不仅要决定补多少还要决定以什么顺序销售先进先出。随机损耗将损耗视为一个随机变量用随机规划来建模。5.4 多目标优化商超可能不只追求利润最大化还希望提高客户满意度减少缺货率。可以在目标函数中加入缺货惩罚项或将其作为约束如缺货率低于5%。加快库存周转可以在目标函数中加入对期末库存的惩罚项鼓励少进货、快销售。价格形象稳定避免价格剧烈波动。可以在目标函数中加入对价格变化幅度的惩罚项(p_t - p_{t-1})^2。处理多目标的一个常用方法是加权求和法将多个目标按重要程度赋予权重合并成一个综合目标函数。6. 论文写作要点与常见陷阱数学建模竞赛模型和求解占一半清晰的表达也占一半。在论文写作中要注意6.1 模型假设的清晰表述必须明确列出所有关键假设例如“假设单一销售周期内需求函数形式保持不变。”“假设损耗只发生在期末且与期末库存量成正比。”“假设采购提前期为0即当日订货当日到达。”“假设不同蔬菜之间的需求相互独立。”如果未考虑关联性 清晰的假设能让评委理解你的模型边界也体现了你的严谨性。6.2 灵敏度分析不可或缺这是体现模型稳健性和你思考深度的关键部分。至少要做关键参数灵敏度改变需求弹性系数b_i、损耗率θ_i、采购成本c_i等观察最优决策和利润的变化。用图表展示结果。约束条件灵敏度分析采购预算、库存容量等约束的“影子价格”说明哪个约束对利润限制最大。模型对比可以将你的联合优化模型与“经验定价简单补货如历史均值”策略进行对比用数据展示你模型的优越性。6.3 常见陷阱与避坑指南忽视数据预处理直接使用原始数据建模会被异常值如促销日带偏。务必进行清洗、平滑、处理缺失值。预测与优化脱节用时间序列模型预测出一个固定的“明日销量”然后基于这个固定值去优化定价这在逻辑上是矛盾的。因为定价会影响销量。必须使用将价格作为输入的需求预测模型。模型过于复杂无法求解在有限时间内追求完美模型可能导致无法求解或结果不可解释。先搭建一个简洁可解的基线模型确保它能运行出结果然后再逐步增加复杂性。忽略业务常识模型给出的价格不能低于成本价补货量不能为负。这些看似简单的约束编程时一旦遗漏就会产生荒谬的结果。论文只有模型没有分析不要只罗列公式和代码。要用文字解释每一步的意图用图表展示中间结果和最终结果用灵敏度分析证明模型的可靠性。6.4 可视化呈现一图胜千言。在你的论文中应包含关键商品的历史价格-销量散点图展示价格弹性。需求预测结果与实际销量的对比图。优化前后利润或关键指标的对比柱状图。灵敏度分析的热力图或折线图。最终决策方案建议定价与补货量表。这道“蔬菜定价与补货”赛题是一个经典的运营管理问题它完美地将学术理论与商业实践结合在一起。解决它不仅需要数学和编程技能更需要一种系统思维和业务直觉。从理解数据背后的故事开始到构建一个自洽的数学模型再到谨慎地解释和推广你的结果每一步都是挑战也都是乐趣。最深刻的体会是没有一个模型是万能的但一个清晰的、可解释的、考虑到主要矛盾的模型远比一个复杂但黑箱的模型更有价值。在比赛的最后阶段不妨跳出代码以一个超市经理的身份审视一下你的模型给出的方案这些价格看起来合理吗这些订货量会不会导致后天的货架空置这种思考往往能帮你发现模型中最后的盲点。
返回列表