
1. 项目概述一次完整的数模竞赛实战复盘又到了一年一度的数学建模竞赛季看着学弟学妹们开始组队、找资料、刷题我不禁想起了去年带队参加2023年国赛C题的经历。那是一场关于“蔬菜类商品的自动定价与补货决策”的硬仗题目背景非常贴近实际零售场景但数据量庞大、变量关系复杂对建模能力和编程实现都是不小的考验。最终我们队拿到了不错的成绩这个过程里踩过的坑、总结的经验远比最后那张证书来得珍贵。今天我就以这道C题为蓝本结合我们当时的解题过程进行一次深度的复盘与拆解。这篇文章不仅适合正在备赛的数模新手了解一场竞赛从破题到提交的全流程也适合有一定基础的队员看看在模型构建、编程实现和论文写作中有哪些可以优化的细节和容易忽略的“魔鬼”。我们的核心任务是面对一个多阶段、多目标的动态决策问题如何根据历史销售数据、批发价格和损耗率为未来一周的蔬菜商品制定每日补货量和定价策略以实现商超利润最大化。这听起来像是一个经典的运筹优化问题但难点在于数据并非“干净”的理想状态它包含了销售波动、商品关联、库存成本、变质损耗等多重现实约束。复盘的价值就在于跳出赛后“上帝视角”重新梳理我们当时在有限时间三天三夜内是如何一步步抽丝剥茧将实际问题转化为数学模型并用代码将其实现和求解的。2. 赛题核心与破题思路解析2.1 题目背景与问题重述2023年C题给出了一个非常具体的场景一家生鲜商超需要对其销售的蔬菜商品进行自动化管理。题目提供了过去四年内数百种单品蔬菜的每日销售记录、批发价格以及商品的损耗率数据。要求参赛者建立数学模型解决两个核心问题第一基于历史数据预测未来一周2023年7月1日至7日各单品蔬菜的日补货量第二在补货决策的基础上同时制定未来一周的日定价策略使得商超在这段时间内的预期利润最大化。这里有几个关键约束条件需要吃透销售与定价关系销售量并非独立它受到当日定价的显著影响。题目暗示了需求函数的存在即价格越高销量可能越低但具体函数形式未知需要我们从数据中挖掘或合理假设。库存与损耗蔬菜有保质期当日未售出的商品会按一定损耗率变质无法在次日继续销售。这意味着补货量不是越多越好过量补货会导致高额损耗成本补少了又会损失销售机会。数据特性历史数据包含了节假日、季节性波动、突发天气可能隐含在销售波动中等复杂因素。同时不同蔬菜品类如叶菜、根茎、菌菇的销售模式和损耗特性差异巨大。决策耦合补货量和定价策略必须联合决策因为定价影响销量销量又决定了库存和损耗最终共同影响利润。这是一个典型的“决策变量相互影响”的动态优化问题。注意很多队伍一开始会犯“分步优化”的错误即先预测“不受价格影响”的销量来决定补货再根据补货量来定价。这忽略了定价对需求的调节作用与题目要求的“利润最大化”目标本质上是相悖的。正确的思路必须是“联合优化”。2.2 核心思路与模型框架选择面对这样一个复杂问题在有限时间内不可能建立一个完美无缺的“终极模型”。我们的策略是先搭建一个主体清晰、逻辑自洽的核心模型框架再针对各个环节的难点采用务实、可实现的算法进行填充和求解。我们最终确定的整体框架是一个两阶段模型但这不是简单的分步而是有迭代反馈的耦合结构第一阶段需求预测与关系建模这个阶段的目标不是直接预测未来一周的具体销量因为销量受未知的未来定价影响而是从历史数据中提炼出关键规律基线销量预测剔除价格影响预测在“常规”定价下未来每天各商品的销量趋势。这里需要考虑周期性星期几效应、季节性、趋势性以及节假日效应。我们采用了时间序列分解STL结合LightGBM回归模型的方法。STL可以稳健地分解出趋势、季节和残差项而LightGBM则用来拟合残差项与天气、节假日标识等协变量之间的关系。价格-需求弹性估计这是本题的灵魂。我们需要量化价格变动对销量的影响程度。我们采用了面板数据模型。将每个单品的历史日度数据视为一个“个体”构建如下的对数-线性回归模型log(Sales_it) α_i β * log(Price_it) γ * X_it ε_it其中Sales_it是商品i在第t天的销量Price_it是其价格X_it是控制变量如星期几、是否节假日α_i是商品固定效应捕捉商品特异性β就是我们需要的需求价格弹性系数。这个系数告诉我们价格每变化1%销量会反向变化大约|β|%。第二阶段联合优化决策在获得基线销量预测(BaseDemand)和价格弹性(β)后我们建立了一个以未来七天总利润最大化为目标的非线性规划模型。决策变量未来每天每个商品的补货量Q_it和零售定价P_it。目标函数Max Σ_i Σ_t [ P_it * Min(ActualSales_it, Q_it) - Cost_it * Q_it - HoldingCost * Max(Q_it - ActualSales_it, 0) ]其中ActualSales_it是由定价决策影响后的实际销量它通过需求函数与P_it关联ActualSales_it BaseDemand_it * (P_it / ReferencePrice)^β。Cost_it是批发成本HoldingCost是单位损耗成本与损耗率相关。约束条件包括补货量非负、定价范围约束如不能低于成本价或高于某个倍数、以及可能的库存容量约束等。这个模型本质上是一个带约束的非线性优化问题。我们使用差分进化算法进行求解因为它对目标函数的形态要求不高能较好地处理非线性、多峰值问题适合这种决策变量较多商品数×天数×2的场景。3. 数据处理、特征工程与关键算法实现3.1 数据清洗与预处理实战题目提供的数据通常“暗藏玄机”。我们拿到的是CSV格式的销售明细、成本表和商品信息表。第一步不是急着建模而是花上几个小时彻底“摸清”数据。缺失值与异常值处理销售为零的日子需要区分是“真没卖”如门店关门、商品缺货还是“数据缺失”。我们结合了批发价格记录和前后日期的销售情况来判断。对于短期内的零星零值采用前后天的均值插补对于长期连续零值且无成本记录的判断为该商品在该时段未经营在对应时间段的预测中将其需求视为0。价格异常波动发现某些商品个别日期价格极高或极低可能是促销或标错。我们使用箱线图结合业务逻辑进行过滤对于单价超过上下四分位数3倍IQR的值参考同期市场批发价进行修正或剔除该日数据。损耗率数据这是关键成本参数。题目给出的损耗率是常数但实际中可能与季节、库存时间有关。我们做了一组敏感性分析在模型中将损耗率设为可上下浮动10%的参数观察策略稳健性。时间特征构建基础特征年、月、日、星期几、季度、是否月末/月初。关键特征——节假日与事件我们手动标注了数据时间范围内的所有国家法定节假日、调休日、以及周末。并创造了一个“节前效应”和“节后效应”特征例如将国庆节前3天标记为“节前高峰”节后第1天标记为“节后低谷”。连续假期长度对于春节、国庆等长假其每一天的销售模式可能都不同我们将其单独编码为一个分类变量。商品关联性分析我们计算了主要蔬菜品类之间的销售相关系数矩阵。发现例如“菠菜”和“小油菜”的销量呈正相关同为叶菜可能受同一类菜谱驱动而“土豆”和“绿叶菜”的关联性较弱。这启示我们在预测时可以将高关联的商品编为一组引入“同类商品销量”作为预测特征提升了基线预测的准确性。3.2 需求预测模型LightGBM的调优细节我们选择LightGBM而非简单的ARIMA或Prophet是因为我们的特征不仅仅是时间序列还有丰富的协变量节假日、天气推测、关联商品销量等。LightGBM能很好地处理这种混合特征。实操要点与调参心得数据划分绝对不能随机划分必须按时间顺序划分训练集和验证集。我们使用前3年的数据做训练最后4个月的数据做验证模拟“用历史预测未来”的场景。目标变量预测的是经过平滑处理后的日销量对数log(1sales)这样可以减少极端值的影响使模型更稳定。核心参数调优# 示例参数设置基于Optuna超参数优化框架的结果 params { objective: regression, metric: rmse, boosting_type: gbdt, num_leaves: 31, # 控制模型复杂度不宜过大防过拟合 learning_rate: 0.05, feature_fraction: 0.8, # 每次迭代随机选择80%的特征增加随机性 bagging_fraction: 0.8, # 每次迭代随机选择80%的数据相当于子采样 bagging_freq: 5, verbose: -1, min_data_in_leaf: 20, # 防止过拟合的关键参数 reg_alpha: 0.1, # L1正则化 reg_lambda: 0.2, # L2正则化 }特征重要性分析训练完成后输出特征重要性图。我们发现“星期几”、“是否节假日”、“该商品上月同期销量”是最重要的三个特征。这验证了我们的特征工程方向是正确的也让我们在后续弹性估计时能更好地控制这些混杂因素。踩坑记录最初我们使用了未做平滑处理的原始销量并且num_leaves参数设置过大127导致模型在训练集上表现极好但在验证集上波动很大过拟合。通过观察验证集预测残差图发现模型对销量突增突降的日子预测很差。后来我们改用对数变换平滑目标变量并加大正则化强度min_data_in_leaf,reg_lambda同时增加了“节前N天”这样的滞后特征才使模型泛化能力显著提升。3.3 价格弹性估计的面板模型实现这是连接预测与优化的桥梁。我们使用Python的linearmodels库中的PanelOLS来拟合固定效应模型。import pandas as pd import statsmodels.api as sm from linearmodels import PanelOLS # 假设 df 是一个多层索引的 DataFrame索引为 [‘product_id’, ‘date’] df[log_sales] np.log1p(df[sales]) # 因变量销量对数 df[log_price] np.log(df[price]) # 核心自变量价格对数 # 添加控制变量星期几、是否节假日的虚拟变量 df pd.get_dummies(df, columns[weekday, is_holiday], drop_firstTrue) # 设置面板数据结构 df df.set_index([product_id, date]) # 定义因变量和自变量 y df[log_sales] X df[[log_price, weekday_1, weekday_2, ..., is_holiday_1]] X sm.add_constant(X) # 添加常数项但固定效应模型会吸收个体效应 # 拟合个体固定效应模型 model PanelOLS(y, X, entity_effectsTrue) # entity_effectsTrue 表示控制商品固定效应 results model.fit(cov_typeclustered, cluster_entityTrue) # 聚类稳健标准误 print(results.summary)关键解读log_price的系数就是我们需要的弹性系数β。如果β -1.5意味着价格每上涨1%销量平均下降1.5%。固定效应 (entity_effects)它吸收了每个商品不随时间变化的特质如品牌效应、固定摆放位置等让我们能更纯净地估计价格变动的影响。聚类稳健标准误 (cluster_entity)由于同一个商品不同日期的误差项可能相关自相关使用聚类标准误可以使估计更可靠。我们为每个大类商品叶菜、茄果、根茎等分别估计了弹性系数因为不同品类的需求对价格的敏感度显然不同。例如生活必需的“土豆”弹性较小价格变化销量变化不大而“精品有机蔬菜”的弹性则较大。4. 优化模型构建与求解策略4.1 非线性规划模型的具体化将第二阶段的概念模型具体化。对于单个商品i未来一天t的利润函数可以写为Profit_it P_it * S_it(P_it) - C_it * Q_it - θ * C_it * max(Q_it - S_it(P_it), 0)其中S_it(P_it) D_it * (P_it / P0)^β是需求函数。D_it是第一阶段预测的基线需求当价格为P0例如历史均价时β是弹性系数。θ是损耗率θ * C_it * max(...)代表损耗成本。决策变量是P_it(价格) 和Q_it(补货量)。约束包括P_min P_it P_max价格上下限Q_it 0S_it(P_it) Q_it I_(t-1)销量不能超过当日可用库存本题中假设每日清库故I_(t-1)0但模型可扩展目标Max Σ_i Σ_t Profit_it这是一个非凸的非线性规划问题因为需求函数S_it(P_it)是指数形式当β不为1时利润函数关于价格P_it是非线性的。传统的梯度下降法容易陷入局部最优。4.2 差分进化算法求解实战我们选用scipy.optimize库中的differential_evolution求解器。它的优势在于不需要梯度信息全局搜索能力强。决策变量编码我们将未来7天所有商品的价格和补货量平铺成一个一维向量。假设有N个商品则变量总长度为2 * N * 7。前N*7个是价格后N*7个是补货量。边界设置价格的下界P_min设为成本价的1.1倍保证微利上界P_max设为成本价的3倍基于历史数据最高溢价。补货量的下界为0上界设为该商品历史最高日销量的2倍一个合理的物理上限。目标函数实现import numpy as np from scipy.optimize import differential_evolution def objective_function(x, baseline_demand, cost_prices, elasticity, spoilage_rate): x: 决策变量向量 [prices, quantities] baseline_demand: 形状为 (N, 7) 的基线需求矩阵 cost_prices: 形状为 (N, 7) 的成本矩阵 elasticity: 形状为 (N,) 的弹性系数向量 spoilage_rate: 标量或形状为 (N,) 的损耗率 num_products baseline_demand.shape[0] days 7 # 重塑变量 prices x[:num_products * days].reshape(num_products, days) quantities x[num_products * days:].reshape(num_products, days) total_profit 0.0 for i in range(num_products): for t in range(days): P prices[i, t] Q quantities[i, t] D baseline_demand[i, t] C cost_prices[i, t] beta elasticity[i] # 参考价格 P0 取历史均价 P0 historical_avg_price[i] # 计算实际需求 actual_demand D * ((P / P0) ** beta) # 实际销量不能超过补货量 actual_sales min(actual_demand, Q) # 计算损耗 waste max(Q - actual_sales, 0) # 单日利润 daily_profit P * actual_sales - C * Q - spoilage_rate * C * waste total_profit daily_profit # 优化器默认求最小化所以返回负利润 return -total_profit # 定义边界 bounds [] # 价格边界 for _ in range(num_products * days): bounds.append((P_min, P_max)) # 补货量边界 for _ in range(num_products * days): bounds.append((0, Q_max)) # 执行优化 result differential_evolution( objective_function, bounds, args(baseline_demand, cost_prices, elasticity, spoilage_rate), maxiter1000, # 最大迭代次数 popsize15, # 种群大小一般为变量数5-10倍 dispTrue, # 显示迭代过程 workers-1, # 使用所有CPU核心并行计算 updatingdeferred, seed42 # 固定随机种子以便复现 ) optimal_solution result.x optimal_profit -result.fun参数设置心得popsize种群大小不宜过小否则搜索空间覆盖不足也不宜过大否则计算太慢。我们设置为变量数的5倍左右在计算资源和效果间取得平衡。maxiter最大迭代次数需要观察收敛曲线。我们设置1000并在回调函数中记录每代最优值发现通常在500代左右已收敛。workers-1启用并行计算能大幅缩短运行时间对于这种计算密集型目标函数至关重要。约束处理我们的边界约束比较简单变量独立边界。如果存在更复杂的线性约束如总补货量不超过卡车容量需要使用惩罚函数法将其融入目标函数。5. 模型检验、灵敏度分析与论文写作点睛5.1 模型检验与稳健性分析一个只给出结果而不检验的模型是缺乏说服力的。我们主要做了以下检验历史数据回测将模型应用于过去一段已知真实销量和价格的历史时期例如2023年3月。用我们模型生成的补货和定价策略计算出“模拟利润”再与商超该时段的实际利润进行对比。我们的模型模拟利润比实际利润高出约8%这证明了模型策略的有效性。同时我们分析了差异来源主要是模型假设了完全按策略执行而实际运营中存在执行偏差和突发情况。关键参数灵敏度分析需求弹性β我们让β在估计值±20%的范围内波动重新运行优化模型。发现利润变化在±5%以内说明模型对弹性系数不算极度敏感这增强了我们使用估计值的信心。损耗率θ将损耗率提高20%观察补货策略的变化。结果显示高损耗率商品的补货量会显著减少定价会略有提高以弥补单位利润总体逻辑符合商业直觉。成本波动模拟批发成本突然上涨10%的情景。模型给出的策略是对弹性低的必需品如土豆适度提价并小幅减少补货对弹性高的非必需品如精品蔬菜则大幅提价并显著减少补货以维持利润。这体现了模型的动态调整能力。5.2 论文写作的核心要点与避坑指南数模竞赛七分做三分写。论文是呈现你们所有工作的唯一窗口。摘要重中之重。必须用精炼的语言不超过一页讲清楚针对什么问题、用了什么方法、建立了什么模型、设计了什么算法、得到了什么结果、有何特色与检验。我们采用“问题-方法-模型-算法-结果-检验”六段式结构确保评委在最短时间内抓住所有亮点。模型假设要合理且必要。我们明确列出了几条关键假设如“未来一周市场需求模式与历史同期相似”、“不同商品间的需求相互独立虽有关联性分析但优化时暂未考虑联合补货的捆绑效应”、“损耗只发生在当日未售出商品”等。每一条假设都说明了其合理性以及对模型可能带来的简化影响。模型建立与求解逻辑清晰图文并茂。我们用一张技术路线图Visio绘制概括了从数据到预测再到优化的全流程让评委一目了然。在描述LightGBM和面板模型时不仅给出公式还附上了特征重要性柱状图和弹性系数分布箱线图使结论可视化。描述差分进化算法时我们画了一个简单的算法流程图并说明了参数设置的理由。结果展示直观、对比、有深度。我们提供了未来一周所有商品的补货与定价建议总表放在附录。在正文中我们挑选了3个代表性商品高弹性、低弹性、高损耗绘制了其未来一周的补货量-定价-预测销量趋势对比图。图中三条曲线并列清晰展示了模型如何根据商品特性制定不同策略低弹性商品价格稳定、补货平稳高弹性商品采用动态定价试探需求。将我们的优化策略与两种基准策略进行对比1)经验策略按历史同期均值补货按成本加成法定价2)仅预测补货固定利润率定价。用表格展示了三种策略下的模拟利润对比我们的模型利润分别高出15%和22%有力证明了联合优化的价值。模型评价与推广体现思考深度。我们客观分析了模型的不足一是未考虑商品间的替代互补效应如西红柿涨价可能带动黄瓜销量二是将损耗率简化为常数三是假设需求函数为恒定弹性形式可能在高价格区间失真。提出了改进方向可以引入联合需求模型如多元逻辑回归来刻画商品间关系可以将损耗率建模为库存时间的函数可以尝试更灵活的需求函数形式。推广部分我们指出该模型框架稍作修改更换需求预测模块和成本结构即可应用于其他生鲜品类水果、肉类甚至快消品的自动补货定价系统体现了模型的通用性。写作避坑指南切忌罗列代码论文是展示思想和结果的不是程序说明书。核心算法只需给出伪代码或流程图关键参数说明即可。图表规范所有图表必须有编号和标题并在正文中引用。图表中的文字要清晰可读避免使用过于花哨的颜色。符号说明在模型建立章节前集中列出文中所有重要符号及其含义显得专业且便于阅读。参考文献引用关键的算法原理、模型方法的经典文献或权威资料如差分进化的原始论文、LightGBM官方文档增加论文的理论基础。语言表达使用“我们建立了...”、“本文采用...”等客观陈述句避免“我认为...”等主观表述。多用“如图1所示”、“由表2可知”等承上启下的句子。6. 团队协作、时间管理与工具链三天三夜的竞赛是对体力和脑力的双重考验合理的分工与流程至关重要。角色分工三人队建模手负责整体框架设计、数学模型推导、公式撰写、论文核心部分问题分析、模型建立、求解的写作。需要深厚的数学和运筹学功底。编程手负责数据清洗、特征工程、算法实现预测模型、优化求解、结果可视化。需要熟练使用PythonPandas, NumPy, Scikit-learn, LightGBM, SciPy等库。写作手负责论文的摘要、问题重述、模型假设、结果分析、模型检验、摘要翻译等部分的撰写和全文的润色、排版。需要极强的逻辑归纳和文字表达能力。关键分工不分家。建模手要懂编程的大致逻辑编程手要理解模型的经济含义写作手要全程参与讨论理解每一步。我们每天早晚各开一次短会同步进度调整方向。时间管理我们的节奏第一天上午所有人一起读题、讨论、查资料确定大方向。下午建模手细化模型框架编程手开始数据探索性分析EDA写作手整理问题重述和文献。第一天晚上至第二天中午编程手完成数据清洗和基线预测模型建模手完成优化模型的数学表述写作手撰写模型假设和符号说明。第二天下午至第三天凌晨编程手实现弹性估计和优化求解产出第一版结果建模手分析结果合理性调整模型参数写作手开始撰写模型建立与求解部分。第三天全天编程手进行灵敏度分析和模型检验生成最终图表建模手与写作手共同撰写结果分析、模型评价与推广写作手统稿、润色、排版。第三夜至提交前三人共同通读全文检查逻辑、公式、图表、错别字。编程手确保所有代码和结果可复现。最后2小时完成摘要精修和最终PDF生成、上传。工具链推荐协作Overleaf在线LaTeX编辑器实时协作版本历史清晰排版精美。编程Jupyter Notebook / VS Code。Notebook适合探索性分析和展示VS Code适合大型脚本开发。使用Git进行代码版本管理。绘图Matplotlib / Seaborn 用于统计图表Visio或Draw.io用于绘制技术路线图和流程图。公式LaTeX语法Overleaf原生支持。文献管理Zotero可以快速插入参考文献。复盘2023年C题的整个过程最大的收获不是某个具体的算法而是一套解决复杂现实问题的系统化思维方式从理解问题、简化假设、数据驱动、模型构建、算法求解到结果验证与表达。每一个环节都需要严谨务实又需要创造性思考。对于准备参赛的队伍我的建议是早做准备多练真题不仅要会“做”更要学会“讲”把你们精彩的思考过程清晰、有力、美观地呈现在那篇最终的论文里。