简介:面向需要复现深度学习作物产量预测研究的开发者,AAAI 2017最佳学生论文奖配套代码覆盖从遥感数据获取到模型训练与结果分析的完整链路。包内共54个文件,以38个Python脚本为核心,分别实现Google Earth Engine数据下载、图像切片与三维直方图清理、CNN/LSTM与高斯过程建模、批量训练及半监督实验;另有csv标签数据、npy/npz结果矩阵、svg可视化图和mat变化函数文件辅助分析,压缩包仅1.25MB。目前已吸引1539人浏览/学习,适合有一定Python和深度学习基础、希望了解遥感时序建模或复现论文实验的研究者,也可用作课程设计与科研入门的基线参考。通过目录模块可快速定位1下载数据、2干净数据、3模型、4 model_batch、5 model_semi_supervised等环节,拿到完整可运行代码、结果分析工具和半监督扩展思路,省去从GEE批量导出影像和调参的重复工作。
1. 产量预测为什么难:在动手写模型之前,先回答这四个问题
我最初做 crop_yield_prediction 这个项目时,和大家一样直接跳到模型搭建,想着尽快把神经网络跑起来、把精度刷上去。结果试了两周模型,效果始终不理想。回头复盘才发现,问题根本不在模型,而在"产量预测"这四个字本身太模糊了。深度学习技术再强,也得先回答清楚:你要预测什么、用谁的数据、在哪个时间点预测、服务什么决策。这四个问题不解决,后面所有工作都是在沙地上盖楼。
先说空间尺度。产量数据的粒度直接决定建模上限。国内公开的统计产量大多到县区一级,样本量够但内部差异大;美国 USDA-NASS 的县级数据则相对规范,也是国外论文里最常用的训练集。田块级数据精度最高,但人工标注成本很高,普通个人项目很难负担。我的项目选择了县级尺度的玉米产量预测,好处是数据公开且易复现,坏处是模型天然忽略田间的灌溉、施肥差异。你要清楚自己做的属于哪一种,别期望县级模型给出田块级建议。
其次是预测时间点。播种前、营养生长期、收获前30天,这三个时间点能做预测的特征完全不同。播种前只有气候统计量和土壤属性,准确率天花板就摆在那里;等到生长季中后期,遥感影像可以把叶面积指数、冠层温度、叶绿素含量都记录下来,这些和最终产量高度相关。我最终把预测窗口放在生长季后期,既保证了精度,也能为粮食收购、保险定损留出操作时间。你在做类似项目时,建议先确定"决策时点",再回过头选特征。
第三是标签对齐。县级产量是一个行政区域内整季作物的平均单产,而遥感影像的一个像素只有30米分辨率,两者尺度根本不在一个层级。如果直接把县平均产量当作每一个像素的监督标签,模型就会学到大量噪声,区域内部的空间差异会被抹平。我的处理方式是把一个县范围内所有有效像元的特征做空间聚合,再和县级标签配对。这个操作很基础,但对最终模型性能影响极大。
最后想清楚你要的是绝对产量还是相对丰欠。很多政府决策只需要知道今年比常年增产还是减产,这时候预测相对变化更稳定;但保险定价、期货交易则需要绝对数值。我最后同时训练了两个输出头,分别回归绝对单产和年际差分,发现差分目标在异常年份的鲁棒性明显更好。这也是传统统计模型和树模型很难处理的点,因为它们把产量当作一组静态特征的回归问题,忽略了时空动态;深度学习恰好能通过卷积、循环网络和注意力机制把这种动态结构建模出来。
2. 数据工程:crop_yield_prediction 项目里最花时间的环节
2.1 数据源选型和组合策略
很多人以为产量预测模型的核心在算法,实际把项目完整走一遍就会发现,数据工程至少占掉70%的时间。我梳理过一轮公开数据源,最终锁定四类:遥感影像、气象再分析数据、土壤属性数据、官方产量统计。
| 数据类别 | 推荐来源 | 空间/时间分辨率 | 在模型中扮演的角色 |
|---|---|---|---|
| 遥感植被指数 | MODIS NDVI/EVI、Sentinel-2 | 250m/5天、10m/5天 | 直接反映作物长势,是最强特征 |
| 气象数据 | ERA5、PRISM | 0.25°/逐小时、4km/逐日 | 温度、降水、辐射的时序变化 |
| 土壤属性 | SoilGrids | 250m | 质地、有机碳、阳离子交换量 |
| 产量统计 | USDA-NASS / 中国统计年鉴 | 县/年 | 监督标签 |
这几种数据在项目里承担的职责完全不同。遥感植被指数是产量的直接影像化表达,尤其是生长季累计的 NDVI 峰值和积分值,几乎和最终产量线性相关;气象数据描述的是环境胁迫,像热浪、干旱这些极端事件,遥感影像可能有滞后,气象序列能及时捕捉;土壤数据是静态的,却决定了产量的基础水平和潜在上限,许多模型忽略这个变量,导致跨区域泛化很差。
2.2 样本构造:时间窗口怎么滑、标签怎么对齐
原始数据处理完之后,下一步是造样本。这一步看似机械,实际决定模型能不能学到因果信息,而不是把时序关系弄混。我用的做法是,把整个生长季按天划分,用10天一期的合成影像生成NDVI曲线,再以每3个月为一个输入窗口、步长为10天滑动截取,构造出多段重叠的时序样本。每段样本的标签对应窗口结束后最终的县级产量。
这里有一个关键细节:特征时间必须严格早于预测时间点,否则就引入了未来信息泄漏。比如我要在当地收获前30天出预测,那么输入窗口就必须截止到那一天,后面所有数据一概不能进入模型。我知道有人为了提升指标,把整个生长季的影像全部输入模型,这当然精度高,但实际部署时根本没法用,因为你手里没有未来的数据。做这类项目一定要先明确"预测截止日",然后按这个截止日构造训练和测试样本。
2.3 预处理里那些很容易踩的坑
数据预处理看起来都是琐碎活,但每一项都在悄悄影响精度。我踩过的坑至少有三个,这里直接说出来。
第一个坑是归一化参数混用。我一开始直接对整个数据集做了全局 z-score 归一化,后来用按年份划分的验证集评估时发现指标虚高。原因是全局归一化已经把验证集的均值和标准差带入训练过程,形成了隐性泄漏。正确做法是只在训练集上计算均值和标准差,然后原样套用到验证集和测试集。这个错误在表格类数据里不致命,在深度学习里却可能让你对泛化能力产生严重错觉。
第二个坑是云层和无效像元。遥感影像不是每一帧都干净,云层遮挡会让 NDVI 值骤降。我第一次做的时候直接用原始像元均值,结果某些多雨地区出现大量异常峰值。后面换了 Savitzky-Golay 滤波配合云掩膜(cloud mask)数据,把受污染的时序点剔除后再做合成,才算把噪声压下去。
第三个坑是数据缓存。几十个县、十多年、多个数据源叠加,原始数据量很容易超过几十 GB。每迭代一次预处理就全部重跑一遍,时间成本完全不能接受。我后来把所有预处理结果缓存成 NumPy 数组或 Parquet 文件,并且给每个版本打上哈希标识,这样调模型时可以秒级加载特征,不必反复读原始影像。
3. 模型选型实战:CNN、LSTM、Transformer 各自的适用边界
3.1 三种模型到底在解决什么问题
模型选型不是越先进越好,关键是匹配数据形态。产量预测输入本质是一个带空间位置标记的时间序列,各特征之间的相互作用很复杂。围绕这个数据形态,我对比过三类架构:
一维 CNN 的优势在于参数少、训练快,能有效提取局部时序模式,比如连续两个月的气温高温累积对后期灌浆的影响。缺点是感受野有限,单纯堆很多层才能捕捉更长期的依赖,容易带来优化困难。LSTM 天然适合时序建模,理论上可以记忆几个月前的关键事件,比如花期低温对授粉的影响,但训练比较慢,对初始化和学习率更敏感,调参成本高。Transformer 用自注意力机制直接建模任意时间步之间的依赖,长程信息传递很高效,但需要足够多的数据,否则容易过拟合。
在实际项目中,我并没有在三者之间做非此即彼的选择,而是把它们组合起来:先用 CNN 提取局部特征,再送入 LSTM 和 Transformer 编码器的混合结构,让模型同时具备局部敏感性、长程记忆和全局关联能力。这不是炫技,而是产量数据本身包含多尺度信息,单一结构很难全部覆盖。
3.2 一种可复现的基线结构
我的模型输入张量形状是(batch, time_steps, num_features),其中time_steps是滑动窗口长度,num_features是遥感、气象、土壤三类特征拼接后的维度。用 PyTorch 实现时,核心结构大致是这样的:
class CropYieldModel(nn.Module): def __init__(self, time_steps=36, num_features=12, d_model=128): super().__init__() # 先用一维卷积提取每个特征自身的局部模式 self.cnn = nn.Sequential( nn.Conv1d(num_features, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(), ) # LSTM 负责捕捉跨时间的动态变化 self.lstm = nn.LSTM(64, 128, num_layers=2, batch_first=True) # Transformer 编码器补充全局依赖 self.transformer = nn.TransformerEncoder( nn.TransformerEncoderLayer(d_model=128, nhead=4, batch_first=True), num_layers=2 ) # 回归头输出绝对产量和年际差分 self.head = nn.Sequential( nn.Linear(128, 64), nn.GELU(), nn.Dropout(0.2), nn.Linear(64, 2) ) def forward(self, x): # x: (batch, time_steps, num_features) x = x.transpose(1, 2) x = self.cnn(x) x = x.transpose(1, 2) x, _ = self.lstm(x) x = self.transformer(x) x = x.mean(dim=1) return self.head(x)这个结构在多种遥感产量预测论文里都有类似影子,属于稳妥的基线。如果你刚入门,建议先把这个模型跑通,再逐步替换模块做对比,不要一上来就堆大模型。
3.3 消融实验里最让我意外的结论
消融实验是这类项目里必须做的,否则你不知道精度到底来自哪里。我的实验中出现了几个反直觉的结果。
第一个是,把气象特征全部去掉之后,模型精度只下降了不到百分之十,反倒是去掉遥感 NDVI 特征后,测试集误差急转直下,短期内心脏骤停那种。这告诉我,植被指数在整个产量形成预测里占绝对主导地位。但这不代表气象数据没用:在极端年份,比如2020年的高温干旱,加入气象胁迫特征能让模型的绝对误差显著降低。也就是说,遥感是常规年份的主力,气象是异常年份的保险。
第二个是,Transformer 部分在数据量不足时会拖后腿。我训练集只有不到一千个县年样本,直接把 Transformer 层加到三层以上,训练损失能降,验证集却明显过拟合。最后我把层数压到两层、嵌入维度降到128,各方面表现才正常。这说明在中小规模数据集上,Transformer 不是白给的,数据量不够时反而要优先保证 CNN 和 LSTM 的稳定性。
4. 训练和评估中容易翻车的几个细节
4.1 损失函数、激活函数与优化器选择
产量预测是回归任务,最常见的损失函数是 MSE,但它对异常年份的惩罚特别大。一年极端干旱造成大面积减产,MSE 会把模型往这个离群点方向猛拉,牺牲平均表现。我实验下来,Huber 损失比纯 MSE 稳得多,它结合了 MAE 和 MSE 的优点,在残差较小时梯度变化平缓,残差很大时又不会彻底被离群值带跑。如果你更关心相对误差,还可以尝试对预测值和标签取对数后再算回归损失,这样在小产区和高产区之间做损失平衡效果更好。
激活函数的选择也是影响模型能否训动的重要因素。隐藏层里我尽量避免用饱和激活函数,比如 sigmoid 和 tanh,因为层数一深梯度容易消失;ReLU 系列在实测中表现最好,尤其是 GELU,在 Transformer 和 MLP 结构里都比 ReLU 稳定。输出层则不加任何激活,直接输出实数值,千万不要为了"归一化"而加 sigmoid,那会把预测限制在0到1之间,完全错乱。
优化器方面,如果你直接踩进深度学习新手的经典坑——随便用默认学习率开跑,会发现损失很容易爆炸。我用的是一段式余弦退火学习率调度,初始学习率设在1e-4,配合 AdamW 优化器和 20 个 epoch 的 warmup,整体训练稳定许多。环境配置上,我当初装深度学习框架时也折腾过不少时间,国内镜像源加 conda 虚拟环境是解决依赖冲突最省心的组合,PyTorch 的 CUDA 版本和显卡驱动一定要核对,否则模型能跑 CPU 版但速度惨不忍睹。
4.2 验证集划分:按年份切和按地区切结果差别很大
许多做表格回归习惯用随机抽样划分训练集和验证集,在产量预测里这是大忌。同一个县同一年的地块特征高度相似,随机划分会把相邻时间的样本同时放进训练和验证,指标虚高到失真。
我试过两种合理划分,效果差异非常明显。第一种是按年份划分,把最后两年作为验证集,前十年数据训练,模拟的是"用历史预测未来"的真实场景,模型要面对气候波动和品种更新,挑战最大;第二种是按地区划分,留出一整个州的产量数据不参与训练,检验的是模型能不能迁移到没见过的地理区域。两种划分方式得到的误差可以相差百分之二三十。如果你的模型只在随机划分下表现好,按年份划分后误差大幅上升,基本可以断定模型记住了时间相关的模式,并没有学到真正的因果特征。
在最终评估时,我同时报告按年份和按地区划分的结果,并单独挑出极端气候年份做压力测试。模型在正常年份误差很低,但一到极端年份误差放大不少,这也暴露了当前方法的天花板——训练数据里如果没有足够的极端样本,模型很难凭空学会应对未知灾害。
4.3 评估指标组合
R² 是最常见也最容易被误用的产量预测指标,原因是当数据方差很大时,哪怕模型只是简单预测历史均值,R² 也能超过0.8,看似很好,实际已经失去区分度。我的习惯是至少同时看四个指标:RMSE、MAE、MAPE、R²,各有各的侧重。
| 指标 | 公式 | 说明 |
|---|---|---|
| RMSE | √(Σ(yi-ŷi)²/n) | 对大误差敏感,适合风险控制场景 |
| MAE | Σ | yi-ŷi |
| MAPE | Σ | (yi-ŷi)/yi |
| R² | 1 - SSres/SStot | 解释方差比例,需要结合数据方差看 |
实际部署时我会主看 RMSE 和 MAPE:RMSE 告诉农民或保险公司最坏情况下能差多少吨,MAPE 方便在不同县之间对比预测质量。R² 只作为参考,不单独作为考核指标。
5. 从实验到落地:产量模型真正能用的最后一公里
5.1 跨区域、跨年份泛化:模型在新地盘上还灵不灵
实验室指标好看,不等于模型在真实世界能用。我拿训好的模型去预测一个完全没参与训练的州时,误差比本地验证集高出一截。仔细分析后发现,核心原因是训练集里这个州的地块很少,模型没见过当地特有的种植制度和土壤条件。要改善这一点,最有效的办法不是调模型结构,而是把土壤属性特征和气候分区标签显式加入输入。加上之后,跨区域误差明显回落。
跨年份泛化同样棘手。产量预测面对的是不断变化的农业系统,新品种、新农药、种植结构调整都在悄悄改变产量与特征之间的关系。我做过一个最直接的压力测试:用2005到2015年的数据训练,去预测2016到2020年的产量,发现极端年份误差被明显放大。这意味着模型部署到生产环境后必须定期用最近年份数据重新微调,否则预测能力会逐年衰减。
5.2 可解释性:农业专家需要你解释为什么预测这个数
模型上线后,我发现自己陷入了另一个困境:模型精度够了,但没有业务方敢用它。农技站的人会问,你预测这块地亩产600公斤,依据是什么?如果只甩出一句"深度学习算出来的",对方大概率直接不采用。可解释性不是加分项,而是落地的前提。
我后来给模型加了特征重要度分析,用类似 SHAP 的方式计算每个输入特征对预测结果的贡献,并按县域输出可读报表。比如"本县今年7月 NDVI 偏低,叠加8月高温天数增加,模型因此下调产量估值"。经过这样的形式,基层人员能理解模型的判断依据,才真正愿意拿模型结果做参考。
5.3 上线部署时容易被忽视的更新策略
深度学习模型的部署和传统软件不一样,不是训练完永久上线就完事。产量预测模型强烈依赖当年的气象和遥感数据,数据本身的延迟和缺失会直接影响推理结果。我的生产系统里设置了两条更新路径:一条是快速路径,生长季内每天拉取最新遥感影像和气象预报数据,跑增量推理刷新预测;另一条是慢速路径,每年生产周期结束后用当年完整数据重新训练一遍模型,替换旧版本。
推理延迟在这个场景里不是什么大问题,一个县的预测用 GPU 跑毫秒级就能完成,真正限制业务更新频率的是数据获取和预处理链路。遥感数据清洗、云掩膜、特征计算这一套流程,平时要提前建好流水线,等数据一到就能滚动更新。很多项目模型本身做得不错,却因为数据管道搭建不及时,错过最佳预测窗口,最终没有发挥实际价值。
我个人在跑完这套 crop_yield_prediction 项目后最大的感受是,深度学习在农业遥感领域的潜力很大,但做好产量预测的瓶颈从来不只是算法本身。数据治理、问题定义、评估设计、业务解释,每一个环节都能让最终效果天差地别。如果你刚接触这类项目,建议先别急着堆模型,多花点时间把数据和评估方案理清楚,后面自然水到渠成。
本文还有配套的精品资源,点击获取