尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

数学建模实战:基于LightGBM与协同优化的生鲜商品定价补货决策

数学建模实战:基于LightGBM与协同优化的生鲜商品定价补货决策 1. 从赛题到实战一次完整的数学建模竞赛复盘去年带队参加全国大学生数学建模竞赛我们组选的C题“蔬菜类商品的自动定价与补货决策”给我留下了深刻的印象。这道题乍一看是典型的运筹优化问题但深入下去会发现它完美地融合了数据分析、时间序列预测、库存管理和定价策略等多个领域非常考验参赛者的综合建模能力和工程化思维。很多队伍拿到题目后容易一头扎进复杂的算法里却忽略了问题本身的业务逻辑和数据特性导致模型“好看不好用”。今天我就以这道赛题为例抛开那些华丽的算法名词从一线实战的角度复盘一下我们是如何一步步把问题拆解、建模、求解并最终形成一份可落地方案的。我会重点分享我们当时的思考路径、关键的技术选型理由、以及那些在论文里不会写的“踩坑”经验。无论你是准备参加未来竞赛的学生还是对数据驱动决策感兴趣的朋友相信这篇复盘都能给你带来一些实实在在的启发。这道题的核心目标很明确为一家生鲜超市的蔬菜商品设计自动化的定价和补货策略。题目提供了过去四年多、超过2400种商品的详细销售流水、损耗记录和批发价格数据。这听起来像是一个标准的“数据模型”任务但难点在于其动态性和复杂性。定价会影响销量销量又决定了库存和补货需求而补货的时机和数量反过来又会影响未来的定价空间和损耗成本。这是一个典型的闭环系统任何一个环节的模型如果脱离其他环节单独优化结果都可能南辕北辙。我们的策略是先构建一个能够模拟这个闭环系统的“数字孪生”环境再在这个环境中去测试和优化我们的策略。接下来我将分步拆解我们是如何实现这个过程的。2. 数据理解与预处理远比想象中复杂的第一步拿到数据后的第一步不是急着跑模型而是彻底理解数据。我们当时花了将近一天的时间来做数据探索性分析EDA这一步的价值在后期建模时得到了充分体现。2.1 数据字段的深度解读与业务映射题目给出的数据主要包含几个部分销售流水sale、商品信息goods、损耗记录loss和批发价格purchase。每个文件都有其独特的价值与陷阱。销售流水表是核心它记录了每一天、每一个单品SKU的销售情况。关键字段包括销售日期、商品编码、销售单价、销售数量、是否打折。这里第一个坑就出现了销售数量。在生鲜零售中尤其是蔬菜顾客购买时往往是称重的销售流水记录的是“交易笔数”还是“实际重量”通过结合商品信息表中的“规格”字段如“500g/袋”和销售单价分析我们判断它记录的是标准化包装后的“份数”。例如菠菜“500g/袋”销售数量为2意味着卖出了2袋共1公斤。这个理解至关重要它直接决定了后续需求预测和库存计算的单位。损耗记录表是另一个关键但噪声极大的数据源。它记录了每日报损的商品和数量。这里存在严重的数据稀疏性和记录不一致问题。很多商品可能长时间没有损耗记录一旦出现就是一大笔。这并不代表这些商品真的没有损耗更可能的原因是1门店在日常操作中小额损耗被忽略或内部消化了2损耗记录可能只在盘点日或达到某个阈值时才被系统记录。因此直接使用原始损耗数据来计算损耗率会严重失真。我们的处理方法是将损耗数据按商品、按月进行聚合并与同期的销售数据结合计算一个“月度理论损耗率”再根据商品特性如叶菜类损耗高、根茎类损耗低进行平滑和先验修正。批发价格表提供了成本信息。但要注意这是采购成本而我们的销售定价需要覆盖的还包括运营成本、损耗成本、以及期望利润。此外批发价格本身也在波动我们需要从中提取成本趋势。2.2 针对生鲜特性的数据清洗与特征工程基于以上理解我们进行了针对性的清洗和特征构建处理缺失值与异常值对于销售数据中连续多天无销售记录的商品我们并未简单删除。在生鲜场景下这可能是“断货”而非“无需求”。我们结合补货记录需从销售序列中间接推断来区分这两种情况。对于异常高的单日销量我们检查了是否发生在节假日或促销日并参考前后期的销售水平进行平滑处理。构建时间序列特征这是预测的基础。对于每个SKU我们构建了以下特征滞后特征 (Lag Features)过去1天、3天、7天、14天、30天的销量。考虑到每周周期性特别强调了“上周同一天”的销量lag-7。滚动统计特征过去7天、14天的平均销量、销量标准差、最大值、最小值。这有助于捕捉趋势和波动性。时序特征星期几One-Hot编码、是否为月初/月末、是否为法定节假日外部数据、距离特定节日如春节、中秋的天数。生鲜消费与节假日强相关。构建商品静态与动态属性特征静态属性从商品信息表中获取如商品大类叶菜、果菜、根茎、菌菇等、是否易腐我们根据大类自定义了一个0-1的腐败系数、平均规格重量。动态属性当前库存水平需要根据初始库存、销售、损耗、补货进行模拟推算、当前售价、过去一段时间的平均折扣力度、过去一段时间的损耗率估计值。构建市场与成本特征成本特征使用批发价格数据计算商品的当前周平均采购成本以及成本相对于上周的变化率。竞争/替代特征这是一个高阶技巧。我们计算了同一大类内不同商品之间价格的相关系数并构建了一个“替代品价格指数”。例如当菠菜价格飙升时生菜的需求可能会增加。注意特征工程不是一蹴而就的。我们采用了一种迭代式的方法先构建一组基础特征训练一个初始模型然后分析模型的特征重要性剔除不重要的再根据业务理解尝试构建新的交叉特征如此循环。最终我们保留了大约30个核心特征。3. 需求预测模型为什么我们放弃了复杂的LSTM需求预测是整个决策链条的起点。预测不准后面的定价和补货都是空中楼阁。在模型选型上我们经历了一个从“追求复杂”到“回归实用”的过程。最初我们很自然地想到了使用循环神经网络RNN或长短期记忆网络LSTM因为它们能很好地处理序列数据。我们用了大概一天时间搭建了一个LSTM模型但很快就发现了问题数据量问题虽然总数据量很大2400商品 * 1000天但具体到单个SKU其销售序列可能只有几百个有效点很多商品并非每日有售。对于深度模型来说这个数据量不足以训练一个稳健的模型容易过拟合。计算资源与时间限制竞赛时间只有三天。训练2400多个独立的LSTM模型即使使用GPU时间成本也过高且调参过程层数、神经元数、dropout率会异常繁琐。可解释性差LSTM是个黑盒当预测出现偏差时我们很难诊断是哪个因素导致的不利于后续的模型迭代和策略调整。经过讨论我们转向了更轻量、更可解释的LightGBM梯度提升树。事实证明这个选择非常正确。3.1 基于LightGBM的时序预测框架LightGBM本身不是为时序数据设计的但通过巧妙的特征工程它可以表现得非常好。我们的模型架构如下训练-验证-测试划分为了避免数据泄露我们严格按时间顺序划分。例如用前80%的数据做训练中间10%做验证用于早停和调参最后10%做测试。多步预测策略我们需要预测未来1-3天的需求。这里没有采用复杂的Seq2Seq而是使用了直接多步预测Direct Multi-step Forecasting。即我们训练三个独立的模型Model_t1: 特征为截止到第t天的数据预测第t1天的需求。Model_t2: 特征为截止到第t天的数据预测第t2天的需求。Model_t3: 特征为截止到第t天的数据预测第t3天的需求。 这样做的好处是简单直接每个模型可以针对其预测步长进行优化。缺点是忽略了预测步长之间的误差累积但对于短期预测3天内这个问题不显著。目标变量处理销量数据通常是计数数据且可能存在零膨胀很多天销量为0。我们尝试了将销量作为回归目标使用RMSE损失也尝试了将其视为泊松分布或负二项分布LightGBM支持这些目标函数。实测下来对于我们的数据简单的回归配合适当的特征效果已经足够好。3.2 模型训练与评估中的关键细节类别特征处理LightGBM可以原生高效处理类别特征如商品编码、星期几。我们直接将cat_col参数设置为这些字段让算法内部处理比独热编码效率高得多。早停与调参我们使用验证集进行早停early_stopping_rounds50。核心参数如num_leaves控制树复杂度、learning_rate、feature_fraction每次迭代使用的特征比例通过网格搜索进行优化。一个重要的技巧是我们不是对所有商品使用同一套参数而是将商品按销量规模和波动性分成几类如高销量稳定型、低销量间歇型、促销敏感型为每一类寻找最优参数范围。评估指标的选择不使用简单的MAE或MSE。我们采用了加权平均绝对百分比误差WMAPE。因为不同商品的销量量级差异巨大菠菜一天可能卖100公斤而某种香料一天只卖几公斤。WMAPE能更好地衡量整体预测的相对误差。WMAPE Σ|实际 - 预测| / Σ|实际|预测结果的后处理模型的输出是连续值但销量应该是非负整数。我们进行了四舍五入。更重要的是我们加入了一个业务规则层如果预测值小于某个阈值比如0.5且该商品历史上存在大量零销量日则直接将预测值置为0。这有效减少了无意义的小额补货建议。最终我们的需求预测模型在测试集上的WMAPE稳定在18%-25%之间对于生鲜数据来说这是一个可以接受的结果为后续决策提供了可靠的基础。4. 定价与补货的联合优化模型构建有了需求预测接下来就是核心的决策部分定价和补货。最关键的认知是这两个决策必须联合优化而不是分先后进行。如果先定好价再根据预测需求去补货那么补货量可能无法支撑定价策略带来的销量变化如果先决定补多少货再根据库存去定价又可能无法实现利润最大化。我们将其建模为一个随机动态规划Stochastic Dynamic Programming问题但考虑到计算复杂度最终采用了一个模型预测控制Model Predictive Control, MPC的近似框架。简单来说就是在每一个决策点每天我们都对未来一个有限的时间窗口例如3天进行“滚动优化”。4.1 决策模型的目标函数与约束我们定义决策变量为未来第i天对商品j的定价p_ij和补货量q_ij。 目标是在一个规划期H3天内最大化总期望利润。目标函数如下Maximize: Σ_{i1 to H} Σ_{j} [ E(D_ij(p)) * (p_ij - c_j) - h * I_ij - b * L_ij ]其中E(D_ij(p))在价格p下商品j在第i天的期望销量。它来自我们的需求预测模型但这里需求是价格的函数。我们通过历史数据拟合了每个商品的需求-价格弹性曲线。c_j商品j的单位成本采购成本处理成本。h单位库存持有成本资金占用、仓储空间。I_ij第i天开始的库存水平由初始库存、补货、销售和损耗动态决定。b单位损耗成本商品成本处理费用。L_ij第i天的期望损耗量是库存水平和商品腐败系数的函数。约束条件库存平衡约束I_{i1, j} I_{i, j} q_{i, j} - D_{i, j} - L_{i, j}。这是最核心的动力学方程。补货能力约束每天的总补货量体积或重量有上限仓库容量、物流能力。价格约束售价需在合理范围内通常设定为成本价的某个倍数如1.2倍到3倍并参考市场均价。非负约束补货量、库存量非负。服务水平约束可选我们希望缺货概率低于某个值这可以转化为对安全库存的约束。4.2 需求作为价格函数的处理这是定价模型的核心。我们假设需求D(p)符合一个简单的指数形式D(p) A * exp(-k * p)其中A是基准需求k是价格弹性系数。对于每个商品我们利用历史数据中价格与销量的对应关系通过线性回归拟合出ln(D) ln(A) - k * p从而估计出参数A和k。实操心得直接拟合整个历史序列的效果并不好因为需求还受星期、季节等因素影响。我们的做法是先将历史销量通过预测模型中的时序特征进行“去趋势化”得到一个“基准需求”再分析这个“基准需求”与价格波动之间的关系来拟合弹性系数。这样更干净。4.3 损耗模型的量化损耗L(I)我们建模为库存水平I和商品腐败系数s的函数。一个常用的简化模型是L(I) s * I即损耗与库存成正比。更精细的模型可以考虑非线性比如L(I) s * I^2表示库存越多管理越困难损耗加速增长。我们根据商品大类为s赋予了不同的值叶菜类0.1根茎类0.02等并在目标函数中损耗成本b * L(I)起到了类似正则化的作用会自然抑制过高的库存水平。5. 模型求解与策略输出从理论到可执行代码上述优化模型是一个带约束的非线性规划问题决策变量数量是商品数 * 预测天数 * 2对于大规模问题直接求解非常困难。我们采用了以下策略进行简化与求解5.1 问题分解与协同优化我们设计了一个两阶段迭代算法定价阶段固定补货量假设补货计划已知优化模型简化为对每个商品独立定价因为需求函数只依赖自身价格这是一个一维搜索问题可以用黄金分割法等快速求解。目标是最大化单商品单日利润(p - c) * D(p)。补货阶段固定价格假设未来几天的价格已知那么未来需求也已知根据需求预测和价格弹性调整。此时补货问题变成一个带有损耗的确定性动态库存问题可以通过逆向递归或线性规划高效求解目标是平衡采购成本、持有成本和损耗成本。然后我们让这两个阶段迭代进行初始化用成本加成法设定一个初始价格用经典的s, S策略或报童模型设定一个初始补货量。迭代在定价阶段基于当前的补货计划决定了库存水平优化价格在补货阶段基于新的价格决定了新的需求预测优化补货量。终止当价格和补货量的变化小于某个阈值或达到最大迭代次数时停止。这种协同优化Co-optimization的方法虽然不能保证找到全局最优解但能在有限时间内找到一个高质量的可行解且计算效率很高。5.2 代码实现关键模块我们的代码主要分为以下几个模块用Python实现# 1. 数据加载与预处理模块 class DataPreprocessor: def load_and_clean(self, raw_data_path): # 读取CSV处理缺失值解析日期 pass def feature_engineering(self, df): # 构建滞后特征、滚动特征、时序特征等 pass # 2. 需求预测模型模块 class DemandForecaster: def train(self, train_data, categorical_features): # 训练LightGBM模型保存模型文件 import lightgbm as lgb params {objective: regression, metric: mape, ...} self.model lgb.train(params, lgb_dataset) def predict(self, features, horizon1): # 根据预测步长调用对应的模型进行预测 pass def predict_with_price_effect(self, base_demand, new_price, elasticity): # 根据价格弹性调整基础预测需求 adjusted_demand base_demand * np.exp(-elasticity * (new_price - base_price)) return adjusted_demand # 3. 优化求解器模块 class PricingReplenishmentSolver: def __init__(self, cost, holding_cost, spoilage_cost, elasticity): self.cost cost self.h holding_cost self.b spoilage_cost self.k elasticity def optimize_price(self, current_inventory, planned_replenishment): 固定补货量下优化单日价格 # 使用一维搜索如scipy.optimize.minimize_scalar寻找最大化利润的价格 def profit_func(price): demand self.forecaster.predict_with_price_effect(...) sales min(demand, current_inventory) # 考虑库存限制的实际销量 spoilage self.spoilage_rate * current_inventory profit sales * (price - self.cost) - self.h * current_inventory - self.b * spoilage return -profit # 求最小化负利润 result minimize_scalar(profit_func, bounds(min_price, max_price), methodbounded) return result.x def optimize_replenishment(self, prices, demand_forecast, initial_inventory): 固定价格下优化多期补货计划 # 转化为线性规划问题使用PuLP或ortools求解 # 决策变量未来H天的补货量 q[i] # 目标最小化总成本采购持有损耗 # 约束库存平衡方程、容量约束等 pass def coordinate_descent(self, initial_guess, max_iters10): 协同优化主循环 prices initial_guess[prices] replenishment initial_guess[replenishment] for i in range(max_iters): new_prices self.optimize_price(replenishment) new_replenishment self.optimize_replenishment(new_prices) if convergence_check(prices, new_prices, replenishment, new_replenishment): break prices, replenishment new_prices, new_replenishment return prices, replenishment5.3 策略输出与可视化求解完成后我们不仅输出未来几天的建议定价和补货量表格还生成了关键的可视化图表用于决策支持商品画像面板针对重点商品展示其历史销量、价格、预测需求、建议价格与补货量的趋势图。全局概览仪表盘展示所有商品建议补货的总成本、预计总销售额、总利润、以及预计的整体损耗率。敏感度分析模拟当成本上涨10%或需求波动增大时策略的稳健性如何。模拟回溯测试将我们的策略应用到历史某段时间的数据上与门店实际采用的策略可从数据中反推进行对比用实际利润和损耗数据证明我们策略的优越性。这些输出不仅满足了赛题要求更构成了一份有说服力的商业决策报告。6. 参赛实战中的经验教训与避坑指南回顾整个参赛过程有几个关键点决定了最终作品的质量这些在标准教程里往往不会提及。教训一对“自动化”的过度执着与业务规则的平衡。最初我们想建立一个完全数据驱动的、端到端的自动化模型。但在测试时发现模型有时会给出反直觉的建议比如在暴雨天气预测某叶菜需求大增而建议高价和大量补货。这忽略了供应链中断的风险暴雨可能导致无法采购或运输成本激增。后来我们引入了“业务规则覆盖层”。我们建立了一个简单的规则库例如当天气预报显示未来24小时有暴雨/大雪时对所有生鲜商品的补货量上限下调30%当某商品批发价格单日涨幅超过15%时触发人工审核标志。模型提供基础建议规则层进行校准和风险控制这才是工业级“自动决策”系统的常态。教训二评估指标与业务目标的错配。我们最初用WMAPE来评估需求预测用求解器的目标函数值来评估优化结果。这没错但不够。评委或者真实场景中的管理者更关心什么是“赚了多少钱”和“扔了多少货”。因此我们增加了一个核心的模拟评估环节。我们用过去一年的数据作为“测试环境”将我们的策略逐日模拟执行根据当天开始的库存和我们的模型建议决定价格和补货量然后使用真实的次日销量数据注意这里不是预测值来计算当日的实际利润和损耗。累加得到全年模拟总利润和总损耗并与基于历史实际数据反推的“基准策略”进行对比。这个模拟利润的提升百分比才是最有说服力的指标。教训三代码的工程化与可复现性。数学建模竞赛的代码经常是“一次性”的混乱且难以复现。我们这次强制要求1使用配置文件管理所有路径和参数2核心函数必须有详细的文档字符串3设置随机种子确保每次运行结果一致4使用argparse或类似库支持命令行参数方便快速进行不同场景的测试。这虽然花了些时间但在最后调整模型、生成不同版本结果时效率提升巨大也减少了错误。教训四对“损耗”数据的创造性处理。如前所述原始损耗数据噪声极大。我们最终的方案是不使用原始日度损耗数据直接建模。而是将其与销售数据结合在“模拟评估”环节我们构建了一个简化的损耗生成器当日损耗 腐败系数 * 当日闭市库存 * 随机扰动。其中腐败系数来自商品分类随机扰动服从一个均值为1的分布。这样在策略优化时我们基于“期望损耗”做决策在最终模拟评估时我们引入随机性来测试策略的鲁棒性。这个处理方式在论文中我们进行了重点说明并进行了敏感性分析展示了其合理性。最后想说的是数学建模竞赛的魅力在于它无限逼近真实的商业或科学问题。C题不仅仅是一道题它映射了零售行业的核心痛点。通过这次实战我们深刻体会到一个好的模型一定是“理解业务的数据模型”和“驾驭模型的业务逻辑”相结合的产物。代码和算法是工具真正的智慧在于如何用这些工具去定义、分析和解决一个模糊的现实问题。希望这篇超详细的复盘能帮你绕过我们曾经踩过的坑更高效地抓住问题的本质。
返回列表