ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

动态优化与需求预测在生鲜商品定价补货决策中的应用

动态优化与需求预测在生鲜商品定价补货决策中的应用 1. 从赛题到实战拆解“蔬菜定价与补货”的核心挑战每年国赛C题一出总能引起一阵热议。2023年的这道“蔬菜类商品的自动定价与补货决策”乍一看像是超市运营的日常问题但真正上手后很多队伍才发现它远不止是套个预测模型那么简单。这道题的精髓在于它把一个看似商业化的场景抽象成了一个典型的动态优化与控制问题并且要求你用数学语言和算法去逼近现实世界的复杂性和不确定性。我辅导过不少队伍发现大家最容易犯的错误就是“一上来就调包”。看到“定价”和“补货”立刻想到时间序列预测ARIMA、LSTM然后预测销量再根据预测结果去“优化”定价和补货量。这个思路本身没错但忽略了题目隐含的几个关键耦合关系价格影响需求需求决定销量销量消耗库存库存水平又反过来影响定价策略和补货时机。这是一个闭环系统。如果你把定价和补货拆成两个独立的模型去跑结果往往会相互矛盾比如定价模型建议涨价但补货模型基于历史销量预测却建议大量补货这显然不合理。所以处理这道题的第一步不是找代码而是厘清逻辑闭环。题目给出的数据通常包括过去一段时间内各种蔬菜的每日销量、成本、售价、损耗率等。我们需要构建的是一个能够模拟“昨日决策定价与补货→ 今日市场表现销量与损耗→ 明日决策调整”这个动态过程的模型。核心目标很明确在满足一定的客户满意度比如不缺货约束下最大化一段时间内的总利润或者最小化综合成本包括采购成本、库存持有成本和损耗成本。这听起来像是一个经典的报童问题Newsvendor Problem的扩展但加入了价格弹性。报童问题只决定订货量而这里我们需要同时决定订货量和销售价格。因此一个可行的整体框架是建立一个以每日利润为目标的优化模型其决策变量是每种蔬菜每天的定价和补货量约束条件包括库存动态平衡、需求函数、容量限制等然后设计算法来求解这个多周期、多商品的优化问题。2. 需求预测价格弹性的量化是第一步也是最大难点任何定价模型的基础都是需求预测而需求预测在这里的核心是建立需求关于价格和其他因素的函数关系也就是需求函数。这是整个项目最富挑战性也最体现建模功力的部分。很多参考代码会直接使用历史销量的移动平均或指数平滑作为未来需求预测这完全忽略了价格的影响是致命的错误。在本题中我们必须假设需求 (D_{it})商品i在第t天的需求量是价格 (P_{it}) 的函数。最常用的形式是线性需求函数(D_{it} a_i - b_i * P_{it} \epsilon)。其中(a_i) 可以理解为理论最大市场需求价格为零时(b_i) 是价格弹性系数(\epsilon) 是随机误差项代表其他未建模因素如天气、节假日的影响。那么如何从历史数据中估计出每个蔬菜的 (a_i) 和 (b_i) 呢这里就需要一点计量经济学的思路了。我们可以利用过去一段时间内该蔬菜价格和销量的每日数据进行线性回归。具体操作时有几点需要特别注意首先数据预处理至关重要。历史销量数据中包含了缺货Stock-out的日子。在这些日子里记录的销量低于实际需求因为需求没有被完全满足。如果直接用这些数据进行回归会导致估计出的价格弹性系数 (b_i) 偏小因为高价时销量低可能部分是因为缺货而非单纯因为价格高。一个常见的处理方法是识别并剔除明显的缺货日。如何识别可以观察销量是否为0或者销量远低于历史平均水平同时库存记录也为0或极低的日子。更严谨的做法是使用 Heckman 两步法等样本选择模型进行纠正但在国赛时间限制内进行合理的筛选和说明即可。其次考虑外部因素。需求不仅受自身价格影响还可能受替代品或互补品价格、周末效应、促销活动等影响。在多元线性回归中可以引入虚拟变量如IsWeekend或其他蔬菜的价格作为解释变量。例如西红柿和黄瓜可能存在替代关系黄瓜涨价可能导致西红柿需求增加。这能提升模型的解释力。最后模型验证。估计出参数后要用一部分数据如最后一周做样本外预测计算平均绝对百分比误差MAPE等指标评估模型预测效果。如果效果不佳可能需要尝试其他形式的函数如对数线性形式 (ln(D) a - b*ln(P))其弹性系数为常数b或者更复杂的模型。下面是一个利用statsmodels库进行需求函数估计的Python示例代码框架。假设我们有一个DataFramedf包含dateproduct_idpricequantity_soldis_weekend等列。import pandas as pd import numpy as np import statsmodels.api as sm import matplotlib.pyplot as plt # 假设 df 是包含历史数据的DataFrame # 步骤1数据清洗剔除异常值如系统错误导致的极高销量和疑似缺货日 # 这里以简单阈值法为例实际应更精细 def clean_demand_data(product_df): Q1 product_df[quantity_sold].quantile(0.25) Q3 product_df[quantity_sold].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR # 同时将销量为0或极低且当日库存告罄的日子视为缺货日剔除 # 假设有‘ending_inventory’列 normal_sales product_df[(product_df[quantity_sold] lower_bound) (product_df[quantity_sold] upper_bound) (product_df[quantity_sold] 0)] # 简单剔除0值 # 更佳做法结合库存判断是否缺货例如normal_sales product_df[product_df[ending_inventory] safety_stock] return normal_sales # 步骤2为单一商品建立需求模型 def estimate_demand_function(product_id, df): product_data df[df[product_id] product_id].copy() product_data_clean clean_demand_data(product_data) # 准备自变量 (X) 和因变量 (y) # 基础模型需求 f(自身价格) X product_data_clean[[price]] X sm.add_constant(X) # 添加常数项对应 a_i y product_data_clean[quantity_sold] # 扩展模型加入周末效应 # X product_data_clean[[price, is_weekend]] # X sm.add_constant(X) model sm.OLS(y, X).fit() print(fProduct {product_id} Demand Function Estimation:) print(model.summary()) # 获取参数 # 对于基础模型const 是 a_i, price的系数是 -b_i a model.params[const] b -model.params[price] # 注意取负号因为我们的公式是 D a - b*P print(fEstimated demand function: D {a:.2f} - {b:.2f} * P) # 可视化拟合效果 plt.scatter(product_data_clean[price], y, alpha0.5, labelActual Data) prices_range np.linspace(product_data_clean[price].min(), product_data_clean[price].max(), 100) predicted_demand a - b * prices_range plt.plot(prices_range, predicted_demand, r-, labelFitted Line) plt.xlabel(Price) plt.ylabel(Demand (Quantity Sold)) plt.title(fDemand Function for Product {product_id}) plt.legend() plt.grid(True) plt.show() return model, a, b # 对每个商品循环处理 demand_params {} for pid in df[product_id].unique(): try: model, a, b estimate_demand_function(pid, df) demand_params[pid] {a: a, b: b, model: model} except Exception as e: print(fFailed to estimate for product {pid}: {e})这段代码提供了从数据清洗到参数估计的完整流程。在实际比赛中你需要根据数据特点调整清洗逻辑并尝试不同的模型设定。得到可靠的需求函数后我们才能进入下一步构建优化模型。3. 模型构建将商业问题转化为数学优化问题有了需求函数我们就可以形式化地描述这个多周期决策问题了。假设我们规划未来 (T) 天例如比赛要求的一周的决策共有 (I) 种蔬菜。决策变量(p_{it}): 商品 (i) 在第 (t) 天的销售单价。(q_{it}): 商品 (i) 在第 (t) 天开始时的补货量订货量。(I_{it}): 商品 (i) 在第 (t) 天结束时的库存量。参数(D_{it}(p_{it}) a_i - b_i \cdot p_{it}): 需求函数为简化先忽略随机误差使用期望需求。(c_i): 商品 (i) 的单位进货成本。(h_i): 商品 (i) 的单位每日库存持有成本可能包含资金占用、仓储费等。(s_i): 商品 (i) 的单位残值当日未售出且未损耗的部分次日可能降价处理或按此价值计算损失。(\theta_i): 商品 (i) 的日损耗率例如每日库存的5%会腐烂损失。(C_{cap}): 仓库总容量约束。(I_{i0}): 商品 (i) 的初始库存。目标函数最大化 (T) 天内的总利润。 [ \text{Maximize} \quad \sum_{t1}^{T} \sum_{i1}^{I} \left[ p_{it} \cdot \min(D_{it}(p_{it}), \text{Available}{it}) - c_i \cdot q{it} - h_i \cdot I_{it} \right] ] 其中(\text{Available}{it} I{i,t-1} \cdot (1-\theta_i) q_{it})是第 (t) 天开始时可用于销售的实际库存昨日库存扣除损耗加上今日补货。注意实际销量不能超过可用库存和需求的最小值即 (\min(D_{it}(p_{it}), \text{Available}_{it}))。这引入了非线性是模型复杂点之一。有时为了简化可以假设需求总能被满足即 Available D但这需要后续用约束保证不太现实。更常见的处理是引入一个辅助变量表示实际销量并添加相应的逻辑约束。约束条件库存动态平衡(I_{it} \text{Available}{it} - \text{Sales}{it})。其中 (\text{Sales}{it} \min(D{it}(p_{it}), \text{Available}_{it}))。需求约束(\text{Sales}{it} \leq D{it}(p_{it}))。实际销量不超过预测需求。供给约束(\text{Sales}{it} \leq \text{Available}{it})。实际销量不超过可用库存。容量约束(\sum_{i1}^{I} I_{it} \leq C_{cap}, \quad \forall t)。每日结束总库存不超过仓库容量。非负与逻辑约束所有决策变量非负。定价可能在一个合理范围内 (p_{it} \in [p_{i}^{min}, p_{i}^{max}])。这个模型是一个**非线性规划NLP**问题因为目标函数和约束中包含了价格 (p) 的二次项如果需求是线性的则 (p \cdot \min(D(p), \text{Available})) 是非线性的。直接求解全局最优解非常困难。因此在实际比赛中我们通常采用分解或启发式方法来求解。一个非常有效的思路是**模型预测控制MPC**框架在每个决策点 (t)每天开始时我们基于当前库存 (I_{i,t-1})对未来 (N) 天一个滚动时域例如 N3进行优化。在这个滚动时域内将随机需求用其期望值代替从而将随机优化问题暂时确定化。求解这个确定性的非线性规划问题得到未来 (N) 天的最优定价和补货计划。只执行第一天的决策(p_{it}^, q_{it}^)。进入下一天 (t1)观察到实际的需求和损耗更新库存状态。重复步骤1-5滚动向前。这样我们将一个复杂的多周期随机优化问题分解为一系列小规模的确定性优化问题大大降低了求解难度。虽然这不是全局最优但在不确定环境下是一种非常鲁棒和实用的近似优化策略。4. 求解策略与代码实现从理论到可运行的算法MPC框架给了我们方向但具体到每一天的优化问题即上面第3步如何求解呢这里提供两种在数学建模中实用且易实现的思路。思路一基于梯度搜索的迭代算法针对简化模型如果我们做一个强假设每日需求都能被完全满足即 Available D那么实际销量就等于预测需求 (D_{it}(p_{it}))。这个假设要求我们的补货量足够大可能会高估利润但可以极大地简化模型。此时目标函数关于 (p_{it}) 是二次的利润 (p*(a-bp) - cq - hI)约束大多是线性的。对于单个商品单周期问题甚至可以直接求导得到最优定价 (p^* (a b*c) / (2b))考虑成本时。对于多商品多周期问题我们可以采用**协调下降Coordinate Descent**的方法固定补货量 (q)优化价格 (p)此时每个商品每天的价格决策相对独立仅通过容量约束耦合可以利用一阶条件快速计算或使用小型优化器求解。固定价格 (p)优化补货量 (q)此时问题变成一个带容量约束的线性/二次规划可以用scipy.optimize.linprog或cvxopt等库求解。交替迭代步骤1和2直到目标函数变化很小或达到迭代次数上限。这种方法的优点是概念清晰实现相对简单。缺点是“需求完全满足”的假设可能不成立且交替迭代可能收敛到局部最优。思路二模拟优化Simulation-Based Optimization这是更贴近现实、也更稳健的方法。我们承认需求是随机的并且实际销量受库存限制。我们设计一个策略函数其输入是当前状态库存水平、历史需求等输出是决策定价和补货量。例如策略可以是一个简单的线性规则(p_{it} \alpha_i - \beta_i * I_{i,t-1})库存高则降价(q_{it} \gamma_i * (S_i - I_{i,t-1})^)库存低于目标水平 (S_i) 则补货。然后我们通过**模拟Simulation**来评估任何一组策略参数(\alpha, \beta, \gamma, S)的好坏。模拟过程就是按照策略函数在计算机中“运行”未来T天每天根据当前库存和策略决定价格和补货然后根据带有随机误差的需求函数例如 (D a - b*p \epsilon, \epsilon \sim N(0, \sigma^2))和损耗率计算实际销量和库存更新最终得到总利润。接下来我们可以使用启发式优化算法如粒子群优化PSO、遗传算法GA来搜索最优的策略参数使得模拟得到的总利润期望值最大。pyswarm或deap库可以帮助实现PSO或GA。下面给出一个模拟优化方法的核心代码框架它更通用也更能体现“决策”的不确定性import numpy as np import pandas as pd from scipy.optimize import differential_evolution import warnings warnings.filterwarnings(ignore) class VegetableStoreSimulator: def __init__(self, products_df, T7, warehouse_capacity1000): 初始化模拟器。 products_df: DataFrame包含每个商品的基础参数a, b, cost, hold_cost, spoil_rate, initial_inv。 T: 模拟天数。 warehouse_capacity: 仓库总容量。 self.products products_df.to_dict(index) # 商品字典 self.product_ids list(self.products.keys()) self.I len(self.product_ids) self.T T self.C warehouse_capacity def policy(self, state, params): 策略函数。根据当前状态和策略参数决定价格和补货量。 这里采用一个简单的线性库存反馈策略作为示例。 state: 字典包含‘inventory’当前库存数组长度I params: 策略参数向量需要定义其结构。例如每商品有4个参数[base_price, price_sensitivity, target_stock, order_intensity] prices np.zeros(self.I) orders np.zeros(self.I) inv state[inventory] # 假设params是一个长度为 (4*I) 的向量 param_per_product params.reshape(self.I, 4) for idx, pid in enumerate(self.product_ids): base_p, price_sens, target_s, order_inten param_per_product[idx] # 定价策略基础价减去库存敏感系数乘以库存库存高则降价 p base_p - price_sens * inv[idx] p max(self.products[pid][min_price], min(p, self.products[pid][max_price])) # 限制在合理范围 prices[idx] p # 补货策略如果库存低于目标水平则订购差额的一部分 if inv[idx] target_s: q order_inten * (target_s - inv[idx]) # 可以加上补货上限约束 q min(q, self.products[pid][max_order]) orders[idx] q else: orders[idx] 0.0 return prices, orders def simulate(self, params, random_seedNone): 执行一次完整的T天模拟返回总利润。 if random_seed is not None: np.random.seed(random_seed) # 初始化状态 inventory np.array([self.products[pid][initial_inv] for pid in self.product_ids]) total_profit 0.0 for day in range(self.T): state {inventory: inventory.copy()} # 1. 根据策略做决策 prices, orders self.policy(state, params) # 2. 计算当日可用库存昨日库存扣除损耗加上今日补货 available inventory * (1 - np.array([self.products[pid][spoil_rate] for pid in self.product_ids])) orders # 3. 计算当日实际需求带随机扰动 demand np.zeros(self.I) actual_sales np.zeros(self.I) for idx, pid in enumerate(self.product_ids): a self.products[pid][a] b self.products[pid][b] p prices[idx] # 期望需求 expected_demand a - b * p # 添加随机噪声例如服从正态分布 noise np.random.normal(0, expected_demand * 0.1) # 噪声标准差为期望需求的10% demand[idx] max(0, expected_demand noise) # 需求非负 # 实际销量受限于可用库存 actual_sales[idx] min(demand[idx], available[idx]) # 4. 计算当日各项成本和收入 revenue np.dot(prices, actual_sales) order_cost np.dot([self.products[pid][cost] for pid in self.product_ids], orders) holding_cost np.dot([self.products[pid][hold_cost] for pid in self.product_ids], inventory) # 损耗成本损耗掉的货物价值。损耗量 inventory * spoil_rate spoil_cost np.dot([self.products[pid][cost] for pid in self.product_ids], inventory * np.array([self.products[pid][spoil_rate] for pid in self.product_ids])) daily_profit revenue - order_cost - holding_cost - spoil_cost total_profit daily_profit # 5. 更新库存状态次日开始时的库存 可用库存 - 实际销量 inventory available - actual_sales # 6. 检查容量约束可以加入惩罚项这里简单处理为硬约束 if inventory.sum() self.C: # 如果超容可以施加一个大的惩罚或者按比例削减库存不现实 total_profit - 1e6 # 施加一个巨大的惩罚 break return total_profit def evaluate_policy(self, params, n_simulations50): 评估策略进行多次模拟取平均利润作为该策略的得分。 profits [] for i in range(n_simulations): profit self.simulate(params, random_seedi) # 使用不同的随机种子 profits.append(profit) return np.mean(profits) # 最大化平均利润 # 主优化部分 if __name__ __main__: # 假设我们已经有了产品参数DataFrame prod_df # prod_df 应包含列a, b, cost, hold_cost, spoil_rate, initial_inv, min_price, max_price, max_order # 这里创建示例数据 prod_data { Tomato: {a: 100, b: 2.5, cost: 1.0, hold_cost: 0.05, spoil_rate: 0.05, initial_inv: 50, min_price: 1.5, max_price: 5.0, max_order: 200}, Cucumber: {a: 80, b: 2.0, cost: 0.8, hold_cost: 0.03, spoil_rate: 0.03, initial_inv: 40, min_price: 1.2, max_price: 4.0, max_order: 150}, } prod_df pd.DataFrame(prod_data).T simulator VegetableStoreSimulator(prod_df, T7, warehouse_capacity500) # 定义优化问题寻找最优策略参数 params # 每个商品有4个参数共 I*4 个参数 num_products len(prod_df) total_params num_products * 4 # 参数边界需要根据经验设定例如价格参数在[成本价 最高限价]附近库存目标在[0, 最大库存]等 bounds [] for pid in prod_df.index: # base_price 边界 bounds.append((prod_df.loc[pid, cost]*1.2, prod_df.loc[pid, max_price])) # price_sensitivity 边界 (非负) bounds.append((0.0, 0.5)) # target_stock 边界 bounds.append((10, 100)) # order_intensity 边界 (0~1) bounds.append((0.5, 1.5)) # 使用差分进化算法寻找最优参数 def objective_function(params): # 差分进化最小化目标所以我们返回负的平均利润 avg_profit simulator.evaluate_policy(params, n_simulations30) # 每次评估用30次模拟平均 return -avg_profit result differential_evolution(objective_function, bounds, maxiter50, popsize15, seed42, dispTrue) best_params result.x best_profit -result.fun print(fOptimization finished.) print(fBest average profit found: {best_profit:.2f}) print(fBest policy parameters (per product):) param_matrix best_params.reshape(num_products, 4) for idx, pid in enumerate(prod_df.index): print(f {pid}: base_price{param_matrix[idx,0]:.2f}, price_sens{param_matrix[idx,1]:.3f}, target_stock{param_matrix[idx,2]:.1f}, order_inten{param_matrix[idx,3]:.2f})这个框架实现了一个完整的模拟优化流程。VegetableStoreSimulator类封装了环境模拟policy函数定义了可调节的决策规则simulate函数运行一个完整的业务周期evaluate_policy通过多次模拟评估策略的稳健性。最后我们使用differential_evolution这个全局优化算法来搜索最优的策略参数。注意模拟优化计算量较大参数搜索空间随商品数量增加而指数增长。在比赛中需要合理设置模拟次数 (n_simulations)、优化算法的迭代次数 (maxiter) 和种群大小 (popsize)在求解质量和计算时间之间取得平衡。对于商品数较多的情况可以考虑先对商品进行分类如高销量/高损耗、低销量/低损耗对每类商品使用同一套策略参数以减少待优化参数数量。5. 关键细节、常见陷阱与论文写作要点有了模型和算法最后能否拿奖就看细节处理和论文表达了。以下是几个极易失分的关键点1. 数据预处理与假设的合理性缺货数据处理如前所述直接使用包含缺货日的数据进行回归会导致需求函数估计有偏。必须在论文中明确指出这一点并说明你的处理方法如剔除、使用 Heckman 模型。这是评委考察你洞察力的重点。损耗的处理损耗是生鲜商品的核心。题目给出的损耗率是日损耗率这意味着库存商品每天都会按比例减少。在库存动态方程中必须准确体现可用库存 昨日库存 * (1 - 损耗率) 今日补货。许多队伍错误地只在期末计算一次损耗。需求随机性你的模型是否考虑了需求的随机波动在模拟优化中我们通过添加随机噪声来体现。在论文中你应该分析随机性对结果的影响例如通过多次模拟给出利润的分布均值、方差、置信区间而不仅仅是一个点估计值。这能体现模型的鲁棒性。2. 模型复杂性与求解可行性的权衡不要盲目追求复杂模型。一个考虑周全、求解稳定的简化模型远胜过一个无法求解或结果荒谬的复杂模型。MPC框架和模拟优化就是很好的折中方案。清晰说明你的求解方法。如果你用了启发式算法如PSO、GA要说明参数设置种群大小、迭代次数、为什么选择它、以及如何避免陷入局部最优如多次运行取最优。敏感性分析是加分项。改变关键参数如需求弹性系数b、损耗率、成本观察最优决策和利润如何变化。这能展示你对模型的理解深度。3. 结果可视化与解释不要只扔出一堆数字。用图表展示你的最优决策方案甘特图或热力图展示未来一周每种蔬菜每天的定价和补货量一目了然。库存路径图展示每种蔬菜在未来一周的库存水平变化检查是否触发了补货点或接近缺货。利润构成图展示总收入、采购成本、持有成本、损耗成本的占比分析利润的主要来源和损耗的主要影响。对你的决策给出业务解释。例如“模型建议在周三对西红柿进行降价促销因为根据历史数据周三的需求价格弹性较高降价能显著提升销量从而在周末大量补货前清理库存降低损耗风险。” 这种解释将数学模型和商业逻辑连接了起来。4. 代码与论文的衔接论文中出现的公式、变量必须与代码中的变量命名有清晰的对应关系。可以在附录中给出核心算法的伪代码。在论文中说明你的代码结构例如“我们构建了Simulator类来模拟商店运营Optimizer类使用差分进化算法调整策略参数。”确保代码可复现。提交的代码应有清晰的注释并包含一个README说明如何运行以及依赖的库numpy,pandas,scipy等。5. 一个容易被忽略的“坑”初始库存与预热期题目通常给出一个初始库存。你的优化是从第一天开始的但第一天决策时这个初始库存是“沉没成本”已经产生的持有成本不应计入未来利润优化。更重要的是这个初始库存可能不是最优状态例如某些商品库存极高某些极低。你的优化模型需要在这个非最优的起点上开始运作。在模拟中前几天可能主要是在“纠偏”将库存调整到理想水平。在分析结果时可以区分“过渡期”和“稳定期”。最后记住数学建模竞赛的本质是用数学工具解决一个实际问题并清晰、有说服力地展示你的解决方案。从准确的问题分析到合理的模型假设再到稳健的求解算法最后到直观的结果呈现每一步都需要逻辑自洽。2023年C题的“蔬菜定价与补货”问题正是检验你这套完整能力的绝佳舞台。避开一上来就套模型的陷阱从理解业务闭环开始一步步构建和求解你的模型你的论文就成功了一半。
返回列表