ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

2024国赛C题种植策略:PSO粒子群优化与Python完整复现

2024国赛C题种植策略:PSO粒子群优化与Python完整复现 简介面向备战2024年全国大学生数学建模竞赛C题的参赛者这份资源围绕农作物种植策略优化问题给出了从数据分析、模型构建到结果输出的全套方案。压缩包共24个文件以8个Python脚本、12个Excel数据表为主体另有2个PDF论文与2个说明文档整体仅2.21MB便于快速下载。代码按问题一至三拆分覆盖线性/非线性回归、皮尔森相关性检验、KMeans聚类以及粒子群优化算法每问对应独立脚本可直接运行得到结果Excel数据经整理包含地块信息、作物信息、预期销售量、种植成本与销售单价等并附聚类后的三类相关性数据以及各题运行结果。论文对建模思路、算法原理与结果分析进行了详细阐述有助于理解完整求解流程并迁移到类似赛题。目前已有253人学习适合需要快速复用代码、参考高分论文结构的参赛队伍。1. 2024国赛C题种植策略一份从数据表到PSO模型的完整拆解“2024国赛C题”把华北某乡村的农作物种植策略做成了一道典型的约束优化题地块类型五花八门作物有二十多种还得同时处理轮作、豆科占比、销售预期量这些硬约束。这份资源和市面上那些只放论文不放代码的“思路贴”不一样它给了完整可跑的Python源码、全套xlsx数据表、五个结果文件外加一篇讲清楚建模逻辑的高分论文。九个脚本按问题编号切分Q1_1、Q1_2、Q2、Q3_cluster、Q3_ABCD_EF各管一问数据从地块信息、作物信息、相关性信息几张表流入跑完自动吐出对应的result文件。它适合三类人正在备赛、想照着一套高分方案复刻完整流程的参赛队被“多约束种植调度”这类问题卡住的研究者以及只想看粒子群优化、kmeans聚类、皮尔森检验在同一个项目里怎么协作的代码党。论文和代码能对上数据又是现成的拿来做复现比从零搭省太多事。2. 代码与数据文件九个脚本和六张表的上下游关系整套源码按问题编号拆成独立脚本而不是一个大main.py。这样设计的好处很实在每个小问可以单独跑、单独出结果文件改一问不影响其它问比赛最后一天赶论文时不用重跑全流程。理解了这一层看文件清单就不会晕。2.1 九个Python脚本谁负责哪一问先把脚本和用途对齐下面的表格是按项目内文件说明整理的脚本职责Q1_1.py问题一第一问基础种植策略求解Q1_2.py问题一第二问调整约束后的种植策略Q2.py问题二加入销售预期量的粒子群优化求解Q3_cluster.py问题三kmeans聚类版求解Q3_ABCD_EF.py问题三手动按地块-大棚分组版求解pearson.py问题三的皮尔森相关性检验判断能否用线性回归Linear_Regression.py问题三预期销售量线性回归nonlinear_regression.py问题三预期销售量非线性回归Q1_1.py和Q1_2.py分别对应问题一的两个小问输出result1_1.xlsx和result1_2.xlsxQ2.py输出result2.xlsx。第三问给了两套解法Q3_cluster.py走kmeans聚类Q3_ABCD_EF.py则是按地块编号里的ABCD普通地块和EF大棚手动分成两组再求解两个脚本分别输出result3聚类.xlsx和result3地块-大棚.xlsx。pearson.py、Linear_Regression.py、nonlinear_regression.py没有独立结果表它们是第三问的中间环节先验证线性相关性再决定用哪种回归。2.2 六张数据表输入输出与依赖顺序数据文件同样是分层设计的我按读取关系整理成一张表表内容被谁读取地块信息.xlsx地块编号、面积、类型Q1/Q2/Q3主脚本作物信息.xlsx作物名称、亩产量、种植成本、销售单价全脚本相关性信息.xlsx所有作物的预期销售量、种植成本、销售单价均值回归前的汇总依据相关性信息_类别1/2/3.xlsx第三问kmeans聚类后各类别的处理结果问题三回归与主求解汇总补充信息.xlsx2023年全年的所有可用数据pearson与回归脚本开始跑之前我习惯先把两张核心表读进来做一次健康检查import pandas as pd df_land pd.read_excel(地块信息.xlsx, engineopenpyxl) df_crop pd.read_excel(作物信息.xlsx, engineopenpyxl) print(df_land.shape, df_crop.shape) print(df_land.columns.tolist()) print(df_crop.columns.tolist())这段代码的作用是确认表头和行列数。地块维度和作物维度决定了决策变量的长度如果表头里混进了空格、换行符后面reshape时直接报错。先跑一遍省得在PSO里排查半天才发现是读表的问题。2.3 README与论文PDF先读哪一份再动手README.md说明运行顺序和依赖库论文PDF基于粒子群优化算法的华北农作物种植策略模型.pdf把模型公式、变量定义、约束条件写全了C题.pdf是赛题原文。建议固定顺序先读C题.pdf理解题目要什么再读论文PDF看作者怎么拆解最后对照README跑代码。直接跳进代码的人通常会卡在一个问题上——搞不清每个脚本凭什么这么设计。3. 问题一与问题二从线性规划到粒子群优化的切换理由种植策略本质上是一个资源分配问题前两问的区别在于约束的复杂程度。第一问相对干净第二问把销售预期量塞进来后目标函数开始出现不可导、带条件的片段这是整个项目选择粒子群优化PSO而不是传统线性规划的直接原因。3.1 问题一建模变量、约束与目标函数决策变量是一个矩阵X(i,j)表示第i块地种植第j种作物的面积地块数m和作物数n相乘后拉平就是一维向量PSO里的维度dim就等于m×n。这个细节决定后面所有代码的结构先把向量化讲清楚import numpy as np m len(df_land) # 地块数 n len(df_crop) # 作物数 dim m * n lb np.zeros(dim) # 下界面积不为负 ub np.ones(dim) # 先归一化再乘各地块面积上限第一问需要满足的基本约束包括每块地种植总面积不超过地块面积上限部分作物不能种在某些地块类型上豆科作物有轮作或占比要求。目标函数是总收益最大化即所有地块上“产量×单价−成本”的加总。Q1_1.py和Q1_2.py的差异就在这些约束的组合方式上第二问通常会增加更细的限制条件比如同一地块不能连续种植同一作物。3.2 问题二换用PSO非线性约束下的选择第二问引入了销售预期量的限制种得再多市场消化不了也白搭。目标函数里会出现“min(产量, 预期销售量)”这类分段项或者“如果产量超过预期销量就按预期销量计收益”的条件判断。这类函数在非连续点上不可导梯度类算法写起来很别扭。启发式算法不要求目标函数可导PSO这种群智能方法天然适合。这是这个项目从问题一开始就用PSO而不是线性规划求解器的原因之一——统一套路避免每个小问换求解器比赛期间的时间成本最低。sko库scikit-opt提供了现成的PSO实现pip install scikit-opt就能装。3.3 sko的PSO接口与参数含义以这套项目里Q2的求解逻辑为例核心写法通常是这样的from sko.PSO import PSO def total_profit(x): # 把一维向量还原成地块×作物矩阵 area_mat x.reshape(m, n) # 收益 面积 * 亩产量 * 单价 - 面积 * 成本 profit (area_mat * yield_per_mu * price).sum() - (area_mat * cost).sum() # 罚函数超过地块面积上限就加重惩罚 penalty 0.0 over area_mat.sum(axis1) - land_area penalty np.maximum(over, 0.0).sum() * 1e6 return -(profit - penalty) # PSO求最小值目标取负 pso PSO(functotal_profit, dimdim, lblb, ubub, pop40, max_iter100, w0.8, c10.5, c20.5) pso.run() best_x pso.gbest_x best_profit -pso.gbest_y目标函数里先把决策向量还原成矩阵再按矩阵计算收益和罚项。PSO默认求最小值所以目标函数返回负收益罚函数则直接加在收益上。这里的罚函数权重1e6不是随便定的它要比最大可能收益高一个数量级否则超面积约束会被搜索过程无视。PSO的五个关键参数按经验整理成表后面调参可以直接对照参数含义典型取值什么时候调pop粒子数量30~50维度变大时加到80以上max_iter最大迭代次数100~300收敛曲线没走平就加大w惯性权重0.5~0.9全局搜索不足时加大c1个体学习系数0.5~2群体过早收敛时调大c2全局学习系数0.5~2收敛太慢时调大3.4 约束进PSO罚函数是这个模型的灵魂PSO本身是个黑匣子它不认约束条件只认目标函数数值。所有的约束——地块面积上限、豆科占比、轮作限制——都得靠罚函数变成目标函数里的惩罚项。罚函数权重是血泪经验最多的位置调小了约束形同虚设产出种植面积超过地块上限调大了搜索空间被压死粒子全挤在一个角落结果反而不如不调。我复现这类项目时通常这样加约束先不加任何罚函数让PSO裸跑一遍确认基本流程通再加面积罚函数确认总种植面积不超过地块上限最后逐个加轮作和占比约束。一次全堆上去出问题根本定位不到是哪条约束写错了。4. 问题三kmeans聚类、皮尔森检验与两类回归的组合第三问的目标从“怎么种”变成了“先把未来卖多少估出来再重新优化种植方案”。这就得依赖2023年的历史销售数据也就是汇总补充信息.xlsx里的内容。前两问的优化思路到第三问继续用但前面多铺了两层数据聚类和回归拟合。4.1 kmeans聚类为什么地块要先分类再回归不同地块类型、不同大棚条件下的作物销售规律差异很大。如果所有地块放一起算相关系数、做回归混合样本会把规律抹平皮尔森系数被拉低回归也不准。Q3_cluster.py的思路是先对地块特征做kmeans聚类把数据拆成三个类别分别输出相关性信息_类别1/2/3.xlsx再对每类单独走相关性检验和回归。运行环境只装了numpy/pandas/sko/matplotlib没有sklearn所以聚类多半是手写的。常见的手写版长这样import numpy as np def kmeans(X, k, max_iter100, seed0): rng np.random.default_rng(seed) centroids X[rng.choice(len(X), k, replaceFalse)] for _ in range(max_iter): # 所有样本到所有中心点的距离 d ((X[:, None, :] - centroids[None, :, :]) ** 2).sum(axis2) labels d.argmin(axis1) # 重新计算每个簇的中心 new_centroids np.array([X[labels i].mean(axis0) for i in range(k)]) if np.allclose(centroids, new_centroids): break centroids new_centroids return labels, centroids注意聚类前先做标准化地块面积是几百上千的数值价格和成本是几十的数值量纲不统一时距离计算全被面积主导聚类结果基本等于按面积分箱。另外这段代码有个隐患某个簇为空时X[labels i]为空数组mean会返回nan。遇到这种情况就减小k或者换个随机种子重新初始化。4.2 皮尔森检验线性回归之前先过一道关口pearson.py这套项目里专门用来判断“能不能用皮尔森相关”。皮尔森相关系数衡量的是两个变量之间的线性相关强度如果数据本身是非线性关系r值会被严重低估后续线性回归的拟合质量也不会好。pearson.py做的工作就是计算预期销售量与候选因子种植成本、销售单价、历史销量的相关系数并输出结果供人判断。核心计算用numpy就能做import numpy as np # sales和cost是两个等长的一维数组 r np.corrcoef(sales, cost)[0, 1] n len(sales) # 粗略判断样本量够且 |r| 0.6 才适合继续线性拟合 if abs(r) 0.6 and n 30: print(f可用线性回归r{r:.3f}, n{n}) else: print(f相关性偏弱或样本不足尝试非线性回归r{r:.3f})这里0.6是经验阈值不是统计学课本里的硬标准。样本量小于30时r的波动很大就算数值高也不可信。如果数据里有离群值r会被单个点带偏写论文时可以补一张散点图说明。4.3 线性与非线性回归预期销售量的两种拟合Linear_Regression.py先试线性回归nonlinear_regression.py再试非线性两套都跑再对比R²选拟合质量高的进主模型。数据集是2023年全年的作物级别记录样本量不大几十到上百个点用多项式拟合就够用没必要上复杂模型。numpy的polyfit可以直接做import numpy as np # 一阶线性拟合 coef_linear np.polyfit(x, y, deg1) y_hat_linear np.polyval(coef_linear, x) ss_res ((y - y_hat_linear) ** 2).sum() ss_tot ((y - y.mean()) ** 2).sum() r2_linear 1 - ss_res / ss_tot # 二阶多项式拟合注意deg2 coef_quad np.polyfit(x, y, deg2) y_hat_quad np.polyval(coef_quad, x) r2_quad 1 - ((y - y_hat_quad) ** 2).sum() / ss_tot print(线性R²:, round(r2_linear, 4), 二次R²:, round(r2_quad, 4))deg1就是直线deg2是抛物线。二次拟合的R²通常不会低于线性但如果二次项系数接近0说明曲线没有明显弯曲线性就够了。非线性回归脚本里常见的做法是把二次、对数、指数都试一遍选R²最高且系数符号合理的那一个。4.4 手动分组与聚类结果的双重验证Q3_ABCD_EF.py的做法是绕开kmeans直接按地块编号前缀把数据分成ABCD普通地块和EF大棚两类分别做回归再求解。这样做的意义是稳健性验证如果聚类版和手动分组版的最优种植方案高度接近说明结论不依赖具体聚类参数如果差异大说明分类方式对结果影响显著论文里就得把这个差异讲清楚。两个版本的结果分别输出到result3聚类.xlsx和result3地块-大棚.xlsx直接放在一起对比就行。做竞赛的话这个“双版本互证”的细节在评阅时比多跑十个模型都加分。5. 复现避坑跑这份代码最容易翻车的五个点代码能跑通和代码能复现是两回事。以下五个坑是我按这套项目的运行环境Python 3.11 pandas/numpy/sko/matplotlib逐一排查过的每条都是现象、原因、解决三段写。5.1 现象pip install sko之后import直接报错具体报错是AttributeError: module numpy has no attribute float。原因sko的PSO部分代码里用了np.float这类旧别名NumPy 1.24开始移除了这些别名Python 3.11环境下默认装的是新版NumPy一跑就崩。解决把NumPy锁到1.23.x命令是pip install numpy1.23.5或者手动把报错代码里的np.float改成floatnp.int改成int改完再跑。5.2 现象read_excel读出来数字列全变成文本地块信息表读出来后面积列dtype是object而不是float64PSO里一算就类型报错。原因xlsx里这些列混入了空格、单位字符或者全角数字Excel自动按文本存了。解决读表后加一步强制转换pd.to_numeric(df_land[面积], errorscoerce)转换失败的变成NaN再统一处理。跑任何脚本之前先df.dtypes扫一遍这步能省半小时。5.3 现象pearson.py输出的相关系数全是nan原因数据里有缺失值或者某一列所有值都相同标准差为零相关系数公式里分母除零就出nan。解决办法分两步先df.isna().sum()查缺失用dropna或fillna处理再检查每列的std()标准差为零的列直接剔除不参与相关性计算。5.4 现象result1_1和result1_2的结果混在一起项目里Q1_1.py和Q1_2.py的输出文件名只差最后一个数字跑完第二次时如果不小心前一个结果就被覆盖了。解决运行前先清理输出目录或者给输出文件加上带时间戳的后缀再或者在脚本末尾强制print(输出完成:, output_path)至少能确认写到了哪个文件。5.5 现象Q2.py连续跑三次三次最优方案都不一样原因PSO初始化粒子位置是随机的没固定随机种子每次搜索路径完全不一样。这个在数学建模竞赛里很致命评阅人拿到三次不同的答案会直接怀疑模型稳定性。解决脚本开头加np.random.seed(42)同时把PSO的状态参数固定论文里把种子值写清楚说明在当前随机种子下复现了结果。6. 进阶验证三个技巧让这套模型结果更可信6.1 技巧一固定随机种子后做灵敏度分析把销售单价和种植成本分别上下浮动10%重新跑Q2.py看最优种植面积变化幅度。如果单价涨10%就大面积改种说明模型对价格敏感论文讨论部分得写如果方案基本不变说明解是稳的。这个分析结果做成一张表放论文附录评阅人直接能看到模型的边界。6.2 技巧二用2023年数据做闭环回测把2023年当作已过去的年份用问题三的回归加优化流程跑一遍生成一个“如果当时按这个模型种”的方案再和2023年实际数据对比误差。R²高不高在真实数据面前才算数。这套项目里的汇总补充信息.xlsx就是这个用途别只用它做回归训练集。6.3 技巧三把三问结果合并成一张对比总表五个结果文件分散看很费劲评阅人也不喜欢来回点Excel。我一般会在最后写一段合并脚本把Q1_1、Q1_2、Q2、Q3的结果按地块对齐生成一张总表import pandas as pd r1 pd.read_excel(result1_1.xlsx) r2 pd.read_excel(result1_2.xlsx) rq2 pd.read_excel(result2.xlsx) merged pd.concat( [r1, r2, rq2], keys[Q1_1, Q1_2, Q2], axis1 ) merged.to_excel(merged_result.xlsx, indexFalse)concat的keys参数会自动给每列加前缀哪个结果来自哪一问一眼就能分辨。我复现这类竞赛项目时吃过不少亏第一次跑Q2忘了固定随机种子三次结果差异大到没法写进论文后来养成了习惯每个脚本开头先固定seed输出文件名带问号编号跑完先把运行日志拷一份存档。代码能跑通只是第一步能被稳定复现才是真完成。这套项目最值钱的不是某段代码而是把数据分析、统计检验、回归预测、优化求解串成一条完整答题链的思路下载后按README从Q1_1.py开始跑把五个结果文件都生成出来再对照论文看每个步骤的设计理由会比只看代码收获多得多。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进