简介:面向农业产量预测场景的机器学习项目包,集成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', encoding='utf-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, columns=cat_cols, drop_first=True) # 时序样本先按年份排序,保证滞后特征不透支未来 if 'year' in df.columns: df = df.sort_values('year').reset_index(drop=True) # 生成滞后特征:把上一年的降雨、气温作为当前样本的特征 for col in ['rainfall', 'temperature']: df[col + '_prev'] = df[col].shift(1) df = df.dropna().reset_index(drop=True) # 标签和特征分离,year和唯一ID不参与建模 y = df['yield_per_ha'] X = df.drop(columns=['yield_per_ha', 'year', 'plot_id'], errors='ignore') print('X shape:', X.shape) print('y head:', y.head())填充策略上,中位数比均值更抗异常,农业气象数据经常有极端暴雨或高温记录,均值会被拉高。类别列用众数填充是省事,如果缺失比例超过20%,我的建议是单独把“缺失”当成一个类别,而不是盲填众数。One-hot编码之后特征维度会膨胀,region有三十个省份会多出二十九列,对随机森林和SVR影响有限,但对BP网络会增加迭代压力。
滞后特征 shift(1) 会把第一行变成NaN,dropna 之后样本量少一行,这是正常现象。注意如果你没有按时间排序就直接shift,等于把未来数据提前,会造成数据泄漏。到这里你已经拿到了干净的X和y,接下来可以进入三个模型的横向对比。
2.3 快速排除异常与冗余:先看describe和相关矩阵
进入建模前,我还会快速做一轮数据体检。先用 describe 看量纲,再用相关矩阵找冗余特征。这一段代码十秒钟出结果,能省掉后面大量调参时间。
print(X.describe().T) # 找与标签相关性最高的特征 corr_target = X.corrwith(y).abs().sort_values(ascending=False) print(corr_target.head(10)) # 找特征之间相关性过高的对,用于去重 corr_matrix = X.corr().abs() upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).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_size=0.2, random_state=42 ) from sklearn.ensemble import RandomForestRegressor rf = RandomForestRegressor( n_estimators=300, max_depth=None, min_samples_leaf=2, max_features='sqrt', n_jobs=-1, random_state=42 ) rf.fit(X_train, y_train) print('RF R2:', rf.score(X_test, y_test)) print('Top features:', rf.feature_importances_)参数含义:n_estimators=300 是森林里决策树的数量,树不是越多越好,超过300收益递减,训练时间线性增长。如果特征几百、样本几万,先设100跑通,再用OOB误差观察增加树数的收益。max_depth=None 让树自由生长,配合 min_samples_leaf=2 控制叶子最小样本量,这是防过拟合的第一道防线。max_features='sqrt' 适合特征维度中等的表格数据,回归任务也可以用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(kernel='rbf', C=100, epsilon=0.1, gamma='scale') ) svr.fit(X_train, y_train) print('SVR R2:', svr.score(X_test, y_test))把 StandardScaler 和 SVR 放进同一个 Pipeline,fit 时只用在训练数据上,predict 时也会用同一套均值和方差,避免单独切分造成的泄漏。C 控制正则化强度,C 越大越拟合训练集,C 越小越平滑。粮食产量数据噪声大,C 取10到200之间比较稳妥,100是常见起点。epsilon 是回归带宽度,值越小越追求精确拟合,但容易过拟合,先设0.1,再根据RMSE量级调整。gamma='scale' 会让 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), activation='relu', solver='adam', learning_rate_init=0.001, batch_size=32, max_iter=800, early_stopping=True, validation_fraction=0.15, n_iter_no_change=20, random_state=42 ) ) 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_init=0.001 是 Adam 的默认步长,配合 max_iter=800 通常能收敛。想要更稳,可以降到0.0001。early_stopping=True 会在训练集里切出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_splits=5, shuffle=True, random_state=42) cv_scores = cross_val_score(rf, X, y, cv=kfold, scoring='r2') 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', compress=3) # 加载并预测 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 对象,加载大数组更快。compress=3 能减小文件体积,代价是保存和加载变慢一点。生产环境里加载 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', encoding='utf-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_splits=5) 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 或地块编号。原因:这些列在训练集和测试集之间不重合,模型却依赖它们硬记住了训练样本。解决:清洗时用 errors='ignore' 的 drop 去掉ID。另一个隐蔽泄漏是面积列:如果标签已经是 yield_per_ha(单产),特征里还放 area 面积,二者强相关,预测时面积未知就会失效。
5.3 坑三:SVR和BP没有归一化,负R²翻车
现象:随机森林R²=0.7,SVR和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(axis=1) # 按验证集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的坑会反复出现,但踩过一次之后,你会把归一化和特征检查变成肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取