YAOTU INSIGHTS

农业粮食产量预测实战:BP、随机森林与SVR对比及pkl部署

农业粮食产量预测实战:BP、随机森林与SVR对比及pkl部署
简介面向农业产量预测场景的机器学习项目包集成BP神经网络、随机森林与SVR三种算法以Python实现从数据预处理到模型预测的完整流程适合高校计算机、数据科学相关专业学生用于毕业设计或课程设计也便于开发者二次开发。压缩包共7个文件包含2个Python脚本、2个Excel数据集、2个可复用的模型pkl文件及1份项目说明文档整体仅22KB结构紧凑。说明文档明确了英文路径与运行步骤按文档操作即可复现预测结果pkl模型可直接加载省去重复训练时间。脚本覆盖Xlsx读取、标准化处理、多种模型训练与预测对比等关键环节可帮助学习者理解不同算法在粮食产量回归任务中的表现差异。已有276人学习下载适合具备一定Python基础、希望系统掌握机器学习建模流程的在校学生与研发人员。1. 农业粮食产量预测为什么把BP网络、随机森林和SVR放在同一个方案里在农口做产量预测最常见的组合不是深度学习而是把BP网络、随机森林和SVR三个模型跑一遍选个最好的存成pkl。这套基于多种机器学习算法的农业粮食产量预测python源码核心就是给你一条能直接改的链路pandas清洗数据、三个回归模型训练、joblib保存模型。它适合做农情分析、毕业论文和竞赛的人用最小代价拿到可解释的基线。我先把话放前面随机森林最稳SVR吃归一化BP吃迭代次数。不理解这三个脾气你会跑出一个让所有指标都翻车的黑匣子。下面我按数据准备、建模、评估、排错的顺序把这条链路拆开讲。2. 数据准备与特征工程从原始地块数据到训练矩阵2.1 数据集字段和任务结构产量预测到底需要什么粮食产量预测本质是一个回归任务标签是连续值比如亩产量或总产量。常见表格数据集的特征会包含三块气象特征降雨量、平均气温、日照时数、投入品特征化肥用量、灌溉面积、种子用量、土壤特征pH值、有机质。除此之外往往还有地区、年份、地块编号这些标识列。拿到数据的第一件事不是建模而是把这四类分清楚标识列、特征列、标签列以及可能隐含的时间列。产量预测最隐蔽的问题是时间结构。如果数据是按年份采样的同一块地在相邻年份之间并不是独立样本随机打乱会让模型偷看未来信息。所以做 train_test_split 之前先看有没有年份和地块编号。我的习惯是唯一主键列如 plot_id直接剔除年份列先保留用来排序和切分但不直接当成特征。等把训练矩阵构造好之后再决定是否按年份划分验证集。还要掂量样本量。几百行和几万行的处理方式完全不同几百行用BP网络很容易过拟合优先随机森林加交叉验证上万行才轮到SVR或BP网络发挥空间。特征数量也决定后续维度控制如果 get_dummies 之后特征超过一百个建议先做相关性筛选而不是直接喂给模型。2.2 用pandas做清洗和编码缺失值、类别变量和滞后特征下面这段清洗代码适用大多数农业产量CSV列名按自己的数据替换即可。我一般先打印形状和数据类型再逐列处理缺失值。先跑通最小链路再谈调参。import pandas as pd import numpy as np df pd.read_csv(grain_yield.csv, encodingutf-8) print(df.shape) print(df.dtypes) # 数值列缺失值用中位数填充对异常值不敏感 num_cols [rainfall, temperature, fertilizer, area] for col in num_cols: df[col] df[col].fillna(df[col].median()) # 类别列缺失值用众数填充 cat_cols [crop_type, region] for col in cat_cols: df[col] df[col].fillna(df[col].mode()[0]) # 类别变量做one-hot编码drop_first避免完全共线性 df pd.get_dummies(df, columnscat_cols, drop_firstTrue) # 时序样本先按年份排序保证滞后特征不透支未来 if year in df.columns: df df.sort_values(year).reset_index(dropTrue) # 生成滞后特征把上一年的降雨、气温作为当前样本的特征 for col in [rainfall, temperature]: df[col _prev] df[col].shift(1) df df.dropna().reset_index(dropTrue) # 标签和特征分离year和唯一ID不参与建模 y df[yield_per_ha] X df.drop(columns[yield_per_ha, year, plot_id], errorsignore) print(X shape:, X.shape) print(y head:, y.head())填充策略上中位数比均值更抗异常农业气象数据经常有极端暴雨或高温记录均值会被拉高。类别列用众数填充是省事如果缺失比例超过20%我的建议是单独把“缺失”当成一个类别而不是盲填众数。One-hot编码之后特征维度会膨胀region有三十个省份会多出二十九列对随机森林和SVR影响有限但对BP网络会增加迭代压力。滞后特征 shift(1) 会把第一行变成NaNdropna 之后样本量少一行这是正常现象。注意如果你没有按时间排序就直接shift等于把未来数据提前会造成数据泄漏。到这里你已经拿到了干净的X和y接下来可以进入三个模型的横向对比。2.3 快速排除异常与冗余先看describe和相关矩阵进入建模前我还会快速做一轮数据体检。先用 describe 看量纲再用相关矩阵找冗余特征。这一段代码十秒钟出结果能省掉后面大量调参时间。print(X.describe().T) # 找与标签相关性最高的特征 corr_target X.corrwith(y).abs().sort_values(ascendingFalse) print(corr_target.head(10)) # 找特征之间相关性过高的对用于去重 corr_matrix X.corr().abs() upper corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) high_pairs [(col1, col2) for col1, col2 in zip(*np.where(upper 0.95))] print(high_pairs[:10])先看每列的标准差如果某个特征几乎等于常数它对模型没有贡献可以删除。再看与标签的相关性如果 top 特征全是降雨、化肥这类农学上讲得通的字段说明数据基本正常。最后看特征两两之间相关性超过0.95保留一个即可否则随机森林的特征重要性会在高度相关的特征之间分摊SVR也会因为共线性而变得不稳定。3. 三模型横向建模BP网络、随机森林、SVR的训练脚本3.1 随机森林先跑通再调n_estimators和跑多长时间随机森林回归算法是bagging集成的代表它对量纲不敏感不需要归一化在小样本表格数据上经常是第一个跑出正R²的模型。上千样本、几十个特征默认参数就能在秒级到分钟级跑完这也是它适合做基线的原因。from sklearn.model_selection import train_test_split # 先切分训练集和测试集避免后面的任何操作接触测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators300, max_depthNone, min_samples_leaf2, max_featuressqrt, n_jobs-1, random_state42 ) rf.fit(X_train, y_train) print(RF R2:, rf.score(X_test, y_test)) print(Top features:, rf.feature_importances_)参数含义n_estimators300 是森林里决策树的数量树不是越多越好超过300收益递减训练时间线性增长。如果特征几百、样本几万先设100跑通再用OOB误差观察增加树数的收益。max_depthNone 让树自由生长配合 min_samples_leaf2 控制叶子最小样本量这是防过拟合的第一道防线。max_featuressqrt 适合特征维度中等的表格数据回归任务也可以用1.0。n_jobs-1 让所有CPU参与。“随机森林需要跑多长时间”是新手问得最多的问题。实际经验一万行、四十个特征、三百棵树普通笔记本上大约一两分钟。如果数据维度更高优先调 max_features 而不是死磕 n_estimators。3.2 SVR核函数与归一化的绑定关系SVR的核心是核函数RBF核通过样本间距离衡量相似度。一旦特征量纲差异过大比如降雨量几百、气温二十几距离会被量纲大的列主导模型等于白学。这就是为什么我强调SVR必须配 StandardScaler而且最好放进 Pipeline 里一起管理。from sklearn.svm import SVR from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler svr make_pipeline( StandardScaler(), SVR(kernelrbf, C100, epsilon0.1, gammascale) ) svr.fit(X_train, y_train) print(SVR R2:, svr.score(X_test, y_test))把 StandardScaler 和 SVR 放进同一个 Pipelinefit 时只用在训练数据上predict 时也会用同一套均值和方差避免单独切分造成的泄漏。C 控制正则化强度C 越大越拟合训练集C 越小越平滑。粮食产量数据噪声大C 取10到200之间比较稳妥100是常见起点。epsilon 是回归带宽度值越小越追求精确拟合但容易过拟合先设0.1再根据RMSE量级调整。gammascale 会让 sklearn 按特征方差自动算 gamma特征标准化后用 auto 和 scale 差别不大。SVR 的缺点是样本量过几万后训练明显变慢因为核矩阵复杂度接近O(N²)。如果数据量大改用线性核或直接选择随机森林更实际。3.3 BP网络用MLPRegressor把迭代和早停管起来BP网络在 sklearn 里对应 MLPRegressor它没有 Keras 那么灵活但对表格数据足够用而且和 pkl 落地的方案无缝衔接。神经网络理论上是会把输入标准化适应但实际不归一化时 loss 震荡、收敛慢表现经常不如线性回归。所以我把 BP 也包在 StandardScaler 后面用。from sklearn.neural_network import MLPRegressor from sklearn.pipeline import make_pipeline bp make_pipeline( StandardScaler(), MLPRegressor( hidden_layer_sizes(128, 64), activationrelu, solveradam, learning_rate_init0.001, batch_size32, max_iter800, early_stoppingTrue, validation_fraction0.15, n_iter_no_change20, random_state42 ) ) bp.fit(X_train, y_train) print(BP R2:, bp.score(X_test, y_test))hidden_layer_sizes(128,64) 表示两个隐藏层第一层128个神经元第二层64个。农业产量数据特征通常在几十个量级这个容量足够如果数据量很小减到(64,32)更安全。learning_rate_init0.001 是 Adam 的默认步长配合 max_iter800 通常能收敛。想要更稳可以降到0.0001。early_stoppingTrue 会在训练集里切出15%做验证当验证损失连续20轮不下降就停这是防BP过拟合的最后一道保险。在表格数据上BP网络未必赢过随机森林。如果BP的R²明显低于另外两个先检查数据量是不是太小、特征有没有归一化而不是盲目加深网络。3.4 三个模型的初始参数表速查模型是否依赖归一化最值得调的参数千级样本跑多久随机森林否n_estimators、max_depth、min_samples_leaf秒级到分钟级SVR是C、epsilon、gamma秒级BP/MLP强烈建议hidden_layer_sizes、learning_rate_init、max_iter分钟级这张表是我做产量预测时的起点。先把默认参数全部跑一遍再按表格顺序调参不盲目穷举。三个模型本身够用但离“交付一个可以用pkl模型”还差两步评估和保存。4. 模型评估与pkl落地RMSE之后再存模型4.1 回归指标RMSE、MAE与R²之间怎么选三个模型训练完先用回归指标横向比较。不能只看R²还要看误差绝对量级。如果产量均值是五百公斤每亩RMSE八十公斤相对误差16%这在农业上基本不实用。所以我每次都会把RMSE和均值放在一起看。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import numpy as np # 一次性算三个模型的预测后面集成还能复用 pred_rf rf.predict(X_test) pred_svr svr.predict(X_test) pred_bp bp.predict(X_test) print(RF RMSE:, np.sqrt(mean_squared_error(y_test, pred_rf))) print(RF MAE:, mean_absolute_error(y_test, pred_rf)) print(RF R2:, r2_score(y_test, pred_rf)) print(y mean:, y_test.mean())RMSE 对大误差敏感会放大预测很差的样本的影响MAE 是平均绝对误差更稳健。R² 是相对简单均值模型的提升接近1最好。小测试集下R²可能因为个别样本剧烈波动所以还要做交叉验证。注意交叉验证的折法取决于数据是否有时间结构下面单独说。4.2 交叉验证不要被一次划分骗了一次 train_test_split 的结果带有随机性我会用交叉验证看稳定性。普通回归用 KFold如果样本带年份序列必须换 TimeSeriesSplit。from sklearn.model_selection import cross_val_score, KFold kfold KFold(n_splits5, shuffleTrue, random_state42) cv_scores cross_val_score(rf, X, y, cvkfold, scoringr2) print(RF CV R2 mean:, cv_scores.mean(), std:, cv_scores.std())KFold 适合样本之间没有时间依赖的数据。如果你已经确认有年份列建议直接改 TimeSeriesSplit它按时间顺序把前几个年份分给训练集后几个年份分给验证集不会出现“未来穿越”。交叉验证的均值比一次划分可靠用来判断三个模型谁更适合当前数据。4.3 用joblib把训练好的模型保存成pkl文件训练好的对象可以直接 joblib.dump 保存。pkl 文件里包含模型结构、超参、训练出的权重以及特征名但不包含训练数据。预测新样本时特征列必须和训练时完全一致。import joblib joblib.dump(rf, rf_model.pkl) joblib.dump(svr, svr_model.pkl) joblib.dump(bp, bp_model.pkl) # 需要压缩时第三个参数指定级别 joblib.dump(bp, bp_model_compressed.pkl, compress3) # 加载并预测 loaded_rf joblib.load(rf_model.pkl) print(Loaded RF R2:, loaded_rf.score(X_test, y_test)) print(Predict sample:, loaded_rf.predict(X_test.iloc[:5]))joblib 比 pickle 更适合包含 numpy 数组的 sklearn 对象加载大数组更快。compress3 能减小文件体积代价是保存和加载变慢一点。生产环境里加载 pkl 的 Python 和 sklearn 版本必须和训练时接近否则可能报 AttributeError 或 ValueError。我一般会在训练脚本末尾打印 sklearn.version并和 pkl 放在同一个目录。4.4 预测脚本怎么组织一个标准的产量预测脚本应该是读取新数据、做同样的清洗和特征变换、加载 pkl、predict、输出 DataFrame。特征变换每一步都要和训练时保持一致比如训练时做了 get_dummies新数据也必须包含相同的类别取值。最好把 2.2 里的清洗逻辑封装成一个 preprocess() 函数训练和预测都调用它就不会漏步骤。def preprocess(raw_df): # 这里放2.2的清洗和特征工程代码 return X_new raw_df pd.read_csv(new_year.csv, encodingutf-8) X_new preprocess(raw_df) pred loaded_rf.predict(X_new) print(pred)这个函数化思路值几十行代码但能省掉大把复现时间。你保存的 pkl 模型只认识训练时那个特征顺序如果新数据少了某一列或多了某一列预测结果就会错位。5. 产量预测建模里的5个必踩坑从数据泄漏到pkl版本兼容5.1 坑一随机切分时间序列导致成绩虚高现象测试集R²很高换到下一年的实际数据预测误差暴涨。原因样本里有年份序列随机打乱让模型看到了相邻年份的未来信息。解决先确认年份列再用 TimeSeriesSplit。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(X): X_train, X_test X.iloc[train_idx], X.iloc[test_idx] # 这里重新训练和评估TimeSeriesSplit 不随机洗牌严格按时间顺序切分。这个方法我在自己的产量预测里用过分数会比普通 KFold 低不少但换新年份数据时更可信。5.2 坑二唯一ID和面积列泄漏现象随机森林特征重要性第一名是 plot_id 或地块编号。原因这些列在训练集和测试集之间不重合模型却依赖它们硬记住了训练样本。解决清洗时用 errorsignore 的 drop 去掉ID。另一个隐蔽泄漏是面积列如果标签已经是 yield_per_ha单产特征里还放 area 面积二者强相关预测时面积未知就会失效。5.3 坑三SVR和BP没有归一化负R²翻车现象随机森林R²0.7SVR和BP却输出负值。原因量纲差异让距离度量被量纲大的列主导模型拟合失败。解决用 make_pipeline(StandardScaler(), 模型)并且先fit训练集再transform测试集不能在整个数据集上fit后再切分。这个坑是我在SVR上踩得最久的后来把归一化写进Pipeline才算根治。5.4 坑四BP网络max_iter不够警告不收敛现象训练结束看到 ConvergenceWarning分数比线性回归还低。原因max_iter 太小损失还没降到平稳就强制停止。解决打开 early_stopping把 max_iter 提到 1500如果 loss 仍然震荡把 learning_rate_init 降到 0.0001。BP网络训练时最好打印一下 loss_curve 的前几轮看到 loss 在持续下降就说明步长没问题。5.5 坑五pkl跨版本加载报错现象换台机器加载 pkl报 ModuleNotFoundError 或 ValueError。原因sklearn 估计器的类定义随版本变化旧 pkl 在新版本里反序列化时找不到对应结构。解决保存 pkl 的同时保存 sklearn 版本信息并用同一虚拟环境重建。import joblib, sklearn print(sklearn version:, sklearn.__version__) joblib.dump((rf, sklearn.__version__), rf_with_meta.pkl)预测脚本加载时先检查版本不一致就提示重装环境。最稳的做法是给项目写一个 requirements.txt锁住 sklearn、pandas、joblib 的版本换机器直接复现环境。6. 用简单集成把三个模型揉成一个预测器6.1 平均集成和加权集成的选择三个模型各有脾气简单平均常常比单个最好模型更稳。对产量预测我一般用加权平均而不是等权因为三个模型的得分差异可能很大。import numpy as np preds np.column_stack([pred_rf, pred_svr, pred_bp]) # 等权平均 final_avg preds.mean(axis1) # 按验证集R2权重加权 r2_scores np.array([ r2_score(y_test, pred_rf), r2_score(y_test, pred_svr), r2_score(y_test, pred_bp) ]) weights r2_scores / r2_scores.sum() final_weighted preds weights print(Avg R2:, r2_score(y_test, final_avg)) print(Weighted R2:, r2_score(y_test, final_weighted))如果某个模型过差它的权重会趋近0等权反而被它拖累。注意权重只能基于验证集计算不能在测试集上调权重否则又造成数据泄漏。最稳妥的用法是把训练集再切一小份出来当验证集算好权重再回到全量训练。6.2 验证你的最终预测链路最后一步是用一段真实新数据走完整流程加载 pkl、preprocess、三个模型 predict、按权重输出。如果输出分布明显偏离历史极值优先检查滞后特征有没有正确生成以及新数据的类别特征和训练集是否对齐。这套集成方案不复杂却能把单个模型的随机性摊平尤其在数据量只有几百行的时候效果立竿见影。我从这套流程里学到的教训是产量预测的价值不在选一个多花哨的模型而在于把数据清洗、时间切分、模型集成这一串链路做到可复现。换数据集时随机森林和SVR的坑会反复出现但踩过一次之后你会把归一化和特征检查变成肌肉记忆。希望帮到你。本文还有配套的精品资源点击获取