这次我们来看一套完整的“AI + CMIP6 数据分析与可视化”全流程技术方案。CMIP6 是第六次国际耦合模式比较计划,也是目前气候变化研究、区域影响评估、极端气候分析中最常使用的全球气候模式数据来源。问题在于,很多人在拿到 CMIP6 数据后卡在第一步:文件太大、变量太多、坐标系统不一致、模式分辨率太粗,不知道怎么跟区域站点观测数据结合,更不知道 AI 方法到底能用在哪个环节。
这篇文章不是单纯讲某个库怎么用,而是把一整条流程拆开讲清楚:从 CMIP6 数据怎么获取、怎么用 Python 处理,到如何把粗分辨率模式数据降尺度到区域尺度,再到如何分析极端气候事件、如何用 AI 模型做预测和回归,最后落到可视化与批量任务落地。整个流程全部基于 Python 生态,重点会覆盖 xarray、CDO 思想(Python 替代方案)、scikit-learn / PyTorch 的轻量应用、Cartopy 绘图和批量脚本设计。
如果你是做气候水文、农业、城市规划、生态风险评估相关方向,或者正在写论文、做课题、接区域气候变化影响评估项目,这篇文章建议收藏备用。下面直接进入正题。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 数据源 | CMIP6 全球气候模式输出,数百个模式与情景组合 |
| 核心任务 | 数据处理、变量提取、日/月尺度转换、统计降尺度、极端气候指数计算、空间可视化 |
| AI 应用环节 | 机器学习降尺度、气候变量回归预测、极端事件分类/识别、模式订正(Bias Correction) |
| 主要 Python 库 | xarray、netCDF4、pandas、numpy、scipy、scikit-learn、statsmodels、Cartopy、matplotlib、esmf/xiso(可选项) |
| 硬件要求 | 数据预处理以 CPU + 内存为主;AI 训练可选 GPU,小样本场景 CPU 完全可跑 |
| 启动方式 | Python 脚本 / Jupyter Notebook,无固定 GUI |
| 接口能力 | 可通过 Python API/脚本化批量处理,也可封装为 FastAPI 服务供其他程序调用 |
| 批量任务 | 支持按模式、情景、时间段、区域切片批量执行 |
| 输出格式 | NetCDF、CSV、GeoTIFF、PNG/PDF 地图 |
| 适合场景 | 气候变化区域影响评估、极端气候分析、降尺度研究、气象数据工程 |
从材料可以看出,这个流程不是单一模型,而是一套方法框架。它最有价值的地方在于把“数据 → 处理 → 降尺度 → AI 建模 → 极端气候分析 → 可视化”串成一条完整链路,比零散调用库要实用得多。
2. 适用场景与使用边界
先讲清楚这套流程能干什么,以及哪些场景不能硬上。
2.1 适合谁用
- 气候变化研究人员:需要处理 CMIP6 多模式数据,分析未来温度、降水、极端事件变化。
- 区域评估项目组:比如某个省、某个流域的未来气候预估,需要把全球模式结果降尺度到几十公里甚至几公里。
- AI 应用开发者:想用机器学习做气候变量预测、模式订正、极端气候识别,但不知道怎么和 CMIP6 数据打通。
- 数据工程 / Python 开发:需要批量下载、清洗、重投影气候数据并输出报表或地图。
2.2 能解决什么问题
- CMIP6 文件是 NetCDF 格式,量大且命名复杂,可以用 xarray 统一读取。
- 全球模式分辨率通常在 100~250 公里,直接用于区域评估太粗,需要用降尺度方法细化。
- 极端气候分析需要从日数据中计算热浪日数、强降水频率、干旱指数等指标,纯手写容易出错。
- 多模式集合统计需要按统一时间轴和网格重采样,Python 脚本可以自动化。
2.3 不适合什么场景
- 需要超高分辨率(公里级以下)动力降尺度并做长期积分模拟,这需要 WRF 等区域气候模式,不是 Python 脚本能替代的。
- 需要业务化实时预报,CMIP6 是气候情景数据,不是天气预报数据,时效性完全不够。
- 对计算性能要求极高的超大数据集,比如全球 0.25° 逐小时数据全变量分析,单纯 Python 单机处理会吃力,建议配合分布式或采样策略。
2.4 合规与数据使用提醒
CMIP6 数据遵循机构数据使用协议,发布成果时一般需要引用数据版本和模式清单。项目中如果涉及站点观测数据、再分析数据、区域地理数据,要确认授权范围。不要把未公开数据直接部署到公网接口服务。涉及具体区域影响结论时,建议使用多模式集合而不是单模式结果,并在论文或报告中注明数据版本和不确定性。
3. 环境准备与前置条件
这套流程以 Python 为主。下面是一份通用环境准备清单,不锁定具体版本号,按你自己的系统环境安装即可。
3.1 操作系统与 Python
- 建议 Windows 10/11、Ubuntu 20.04+ 或 macOS。
- Python 建议 3.9 或更高版本,3.10/3.11 兼容性最好。
- 推荐使用 conda 创建独立环境,避免和其他项目依赖冲突。
conda create -n cmip6 python=3.11 conda activate cmip63.2 核心依赖安装
pip install xarray netCDF4 dask[complete] pandas numpy scipy pip install matplotlib cartopy scikit-learn statsmodels pip install jupyterlab tqdm pyyaml如果数据量很大,可以考虑额外安装 h5netcdf、h5py 和 zarr,用于更高效的文件读写:
pip install h5netcdf h5py zarr3.3 建议安装的扩展库
| 库 | 用途 |
|---|---|
| cdo | 气候数据操作工具,重采样、变量运算方便,但需要单独安装 |
| xesmf | 基于 ESMF 的网格重插值,做降尺度前处理很好用 |
| regionmask | 按区域掩膜统计气候变量 |
| salem | 简化 Cartopy + NetCDF 可视化 |
| cmdstanpy / prophet | 时间序列趋势分析可选 |
如果使用 xesmf,需要确认系统支持 esmpy。安装示例:
pip install xesmf esmpy3.4 硬件与磁盘空间
CMIP6 单文件经常是几百 MB 到几个 GB。建议:
- 至少留 100 GB 空闲磁盘,如果你下载多模式、多情景数据,500 GB 也不嫌多。
- 内存建议 16 GB 以上。处理日尺度全球网格数据时,32 GB 会更从容。
- AI 训练部分:传统机器学习(随机森林、Gradient Boosting、线性回归)CPU 即可。深 learning 模型训练如果有 GPU 可以加速,但多数表格型气候任务不强制 GPU。
3.5 开发环境建议
jupyter lab先在 Jupyter Notebook 里做探索性分析,再把稳定步骤写成 .py 脚本,方便批量执行和版本管理。项目目录建议这样组织:
cmip6_project/ ├── data/ │ ├── raw/ │ ├── processed/ │ └── output/ ├── scripts/ ├── notebooks/ ├── config/ └── logs/4. CMIP6 数据获取与目录组织
数据获取是整个流程的起点。CMIP6 数据分布在全球多个节点,常用入口包括 ESGF 节点、各机构数据门户,以及部分云托管数据集。
4.1 数据选择思路
下载之前先明确你的需求:
- 模式:比如 EC-Earth3、MPI-ESM1-2-HR、CanESM5、CESM2 等。
- 情景(Experiment):historical、ssp126、ssp245、ssp370、ssp585。
- 变量:tas(近地表气温)、pr(降水)、tasmax、tasmin、psl、hurs 等。
- 时间频率:day(日)、mon(月)、Amon/Omon 等。
- 网格:gn(原生网格)、gr(规则网格)。
建议不要一次下载所有模式。先选 3~5 个常用模式,形成多模式集合即可。
4.2 通过 Wget 脚本批量下载
ESGF 节点通常提供“Search → Add to Cart → Download Script”流程,生成一个 wget 脚本。日常操作像这样:
- 进入 ESGF 数据搜索页面。
- 选择 CMIP6、变量 tas/pr、experiment 为 ssp245、frequency 为 day。
- 选中若干模式文件,加入下载列表。
- 下载 wget 脚本到项目 data/raw 目录。
- 在命令行执行。
cd data/raw bash wget-20250201_123456.sh注意:不同节点可能有认证要求,部分数据需要注册 Earth System Grid Federation 账号。用户名和密码通常会写在脚本或提示信息中。
4.3 检查文件完整性
下载完成后,每个文件一般带有 .nc 后缀。部分节点会提供 .md5 或 .sha256sum 校验文件。建议下载后做一次校验:
md5sum *.nc > checksums.txt # 或者 sha256sum *.nc4.4 元数据管理
建议把每个文件的模式、情景、变量、时间范围记录到表格里,后续写批量脚本时可以直接读取,不用每次手动拼路径。
import pandas as pd records = [] # 伪代码:遍历下载目录,从文件名解析元数据 # 示例:tas_day_EC-Earth3_ssp245_r1i1p1f1_gr_20150101-20301231.nc # 规则根据实际文件名调整文件名解析是批量处理的基础。CMIP6 文件名一般遵循 DRS 命名规范,可以用正则提取字段:
import re pattern = r"^(\w+)_(\w+)_(.+?)_(.+?)_(.+?)_(.+?)_(.+)\.nc$"5. CMIP6 数据处理与变量提取
数据下载完成后,进入核心的数据处理环节。以温度变量 tas 为例,一般要经历时间维裁剪、区域裁剪、单位换算、气候态计算、缺失值处理等步骤。
5.1 用 xarray 读取 NetCDF 文件
xarray 是处理多维科学数据最顺手的工具,读 NetCDF 就像读 DataFrame 一样直接。
import xarray as xr # 读取单文件 ds = xr.open_dataset("data/raw/tas_day_EC-Earth3_ssp245_r1i1p1f1_gr_20150101-20301231.nc") # 查看数据结构 print(ds) # 查看变量 print(ds.data_vars) # 查看坐标 print(ds.coords)5.2 时间裁剪与区域裁剪
# 时间裁剪,例如只保留 2020-2040 年 ds_sel = ds.sel(time=slice("2020-01-01", "2040-12-31")) # 区域裁剪,例如中国东部 ds_region = ds_sel.sel(lat=slice(20, 55), lon=slice(100, 135))注意:不同模式经度范围可能是 0~360,也可能是 -180~180。先统一处理:
# 如果经度是 0~360,且想用 -180~180 if ds.lon.max() > 180: ds = ds.assign_coords(lon=(((ds.lon + 180) % 360) - 180)).sortby("lon")5.3 单位换算
CMIP6 变量一般有标准单位。温度通常是 K,降水通常是 kg m-2 s-1:
# 温度从开尔文转摄氏度 ds["tas"] = ds["tas"] - 273.15 ds["tas"].attrs["units"] = "degC" # 降水从 kg m-2 s-1 转为 mm/day seconds_per_day = 86400 ds["pr"] = ds["pr"] * seconds_per_day ds["pr"].attrs["units"] = "mm/day"5.4 日数据转月数据
极端气候分析和趋势分析经常需要月尺度数据。xarray 提供了非常方便的 resample:
# 日平均温度 转 月平均 tas_month = ds["tas"].resample(time="1MS").mean() # 日降水 转 月累积降水 pr_month = ds["pr"].resample(time="1MS").sum()5.5 多文件合并
同一模式同一情景的数据按时间切分多个文件时,可以用 xarray 的 open_mfdataset 合并,同时用 dask 延迟计算减少内存压力:
import xarray as xr ds_combined = xr.open_mfdataset( "data/raw/tas_day_EC-Earth3_ssp245_*.nc", combine="by_coords", chunks={"time": 3650} )5.6 气候态计算与距平
# 基线期 1995-2014 base = ds.sel(time=slice("1995-01-01", "2014-12-31")) # 气候态 clim = base.groupby("time.month").mean(dim="time") # 逐月距平 anomaly = ds.groupby("time.month") - clim5.7 保存处理结果
处理完的数据建议统一保存到 processed 目录:
ds_region.to_netcdf("data/processed/tas_EC-Earth3_ssp245_2020-2040_CNEast.nc")6. AI 方法在 CMIP6 分析中的应用
接下来是这套方案里最值得展开的部分:AI 到底能在 CMIP6 分析里做什么。从实际项目看,常用的切入点有三个:统计降尺度、模式订正和极端事件识别预测。
6.1 用机器学习做统计降尺度
降尺度的目标是让全球模式 100~200 公里的输出,变成区域尺度的空间分布。统计降尺度本质上是建立“大尺度气候因子 → 局地点/区域变量”的映射关系。常用方法包括线性回归、随机森林、梯度提升树,以及简单的神经网络模型。
一个典型方案是:
- 特征 X:CMIP6 模式输出的温度/降水/气压/湿度等大尺度变量(可以是格点值或多点组合)。
- 目标 Y:观测站点温度或降水(来自气象站或区域再分析数据)。
- 训练期:1980-2014 年。
- 预测期:2015-2100 年未来情景。
下面是一个随机森林降尺度示例:
import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score # 假读数据:X 是模式特征,y 是站点观测 # X 可以是站点位置对应的模式变量矩阵 # y 是站点观测温度序列 data = pd.read_csv("data/processed/train_features.csv") X = data[[f"mode_var_{i}" for i in range(1, 11)]] y = data["station_tas"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = RandomForestRegressor( n_estimators=300, max_depth=8, min_samples_leaf=2, n_jobs=-1, random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print("RMSE:", np.sqrt(mean_squared_error(y_test, y_pred))) print("R2:", r2_score(y_test, y_pred))评估模型时重点看 RMSE 和 R2,同时检查预测值和观测值在极端端的表现。气候数据普遍存在“回归到均值”的现象——预测的方差比观测小,极端高值被低估,极端低值被高估。针对这个问题,可以增加分位数映射或分布订正的后处理。
6.2 用神经网络做降尺度
当输入特征变多、区域变大时,随机森林的特征组合能力受限。这时可以考虑简单的 MLP 或一维 CNN。表格型气候数据用 MLP 就足够起步。
import torch import torch.nn as nn class MLPDownscale(nn.Module): def __init__(self, input_dim, hidden_dim=128, output_dim=1): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, output_dim) ) def forward(self, x): return self.net(x)训练时注意标准化特征和目标,否则模型很容易不收敛。
from sklearn.preprocessing import StandardScaler scaler_X = StandardScaler() scaler_y = StandardScaler() X_train_s = scaler_X.fit_transform(X_train) y_train_s = scaler_y.fit_transform(y_train.values.reshape(-1, 1)).ravel()6.3 用机器学习做模式订正(Bias Correction)
模式输出存在系统性偏差,例如温度整体偏高或降水偏多。最简单的订正方法是 delta 方法,即用“未来距平 + 观测气候态”:
# future_anomaly = future_model - historical_model_clim # corrected_future = future_anomaly + observed_clim机器学习方法可以做得更精细。把历史时期模式值作为特征,观测值作为目标,训练回归模型,再应用到未来模式输出上。这与降尺度方法相通,差别在于目标变量和特征空间设计。
一个更鲁棒的做法是分位数映射。训练期计算模式与观测的累积分布函数 CDF,逐分位点调整:
from scipy import stats # 伪代码:按分位数做映射 def quantile_mapping(model_data, obs_data, future_data, n_quantiles=100): quantiles = np.linspace(0, 1, n_quantiles) model_quantiles = np.quantile(model_data, quantiles) obs_quantiles = np.quantile(obs_data, quantiles) # 对未来数据做插值映射 corrected = np.interp(future_data, model_quantiles, obs_quantiles) return corrected6.4 用 AI 做极端气候识别与分类
除了回归任务,分类任务也很常见。比如根据环流型、温度场、湿度场预测某区域是否发生热浪或强降水。这类任务可以构造样本标签,用梯度提升树或随机森林做分类。
from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report clf = GradientBoostingClassifier(n_estimators=200, max_depth=4, random_state=42) clf.fit(X_train, y_train_cls) y_pred_cls = clf.predict(X_test) print(classification_report(y_test_cls, y_pred_cls))分类任务里,正负样本通常不平衡。建议使用 class_weight 参数或在训练前做下采样/过采样处理。
7. 降尺度技术细节与区域影响分析
AI 方法是被滥用的词,但降尺度本身有一套完整的方法论。在实际项目中,经常是“动力降尺度 + 统计降尺度 + 机器学习订正”组合使用。
7.1 常用降尺度方法对比
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Delta 方法 | 直接用未来距平叠加观测气候态 | 简单快速 | 空间分布继承模式误差 |
| 线性回归 | 建立大尺度预报因子与区域变量关系 | 可解释性强 | 对非线性关系捕捉不足 |
| 随机森林/GBDT | 非线性映射 | 精度较高,能捕捉非线性 | 外推能力有限 |
| 深度学习 | 多层非线性网络 | 表达能力强 | 需要更多训练数据,可解释性弱 |
| 动力降尺度(WRF) | 区域模式物理过程模拟 | 物理一致性最好 | 计算成本高,配置复杂 |
7.2 地形因子与站点外推
在山区做降尺度,一定要加入海拔、坡度、坡向等静态变量。温度随海拔递减,降水受地形抬升影响。
# 伪代码:特征中加入站点海拔 X["elevation"] = station_elevation最简单的山地温度订正:温度直减率约 0.6°C / 100m。如果模式格点平均海拔和站点海拔差异大,先做海拔订正再训练模型,效果更稳定。
7.3 区域影响分析的基本流程
完成降尺度后,区域影响分析一般包含:
- 区域平均时间序列和趋势。
- 季节平均和年循环变化。
- 极端气候指数变化。
- 多模式集合的信噪比与一致性判断。
# 区域平均温度未来变化 tas_region_mean = corrected_data.mean(dim=["lat", "lon"]) # 未来期与基线期差 future_period = tas_region_mean.sel(time=slice("2070-01-01", "2099-12-31")) base_period = tas_region_mean.sel(time=slice("1995-01-01", "2014-12-31")) change = future_period.mean() - base_period.mean() print(f"区域平均升温: {change.values:.2f} °C")8. 极端气候指标分析与实现
极端气候分析不能只看平均值的改变,必须用国际通用的极端气候指数。ETCCDI 定义的指数是目前行业标准。这里挑几个最常用的展开。
8.1 热浪相关指数
TXx(日最高气温最大值)和TNn(日最低气温最小值)是最基础的两个。还有TX90p(日最高气温超过基线第 90 百分位数的天数),也就是暖昼日数。
# 假设已计算逐日 tasmax daily_tasmax = ds["tasmax"] # 基线期 1961-1990 或 1995-2014,按项目需要 base = daily_tasmax.sel(time=slice("1995-01-01", "2014-12-31")) threshold = base.quantile(0.9, dim="time") # 超过阈值的天数 warm_days = daily_tasmax.where(daily_tasmax > threshold) warm_day_count = warm_days.groupby("time.year").count(dim="time")8.2 强降水指数
R95p(日降水超过基线第 95 百分位数的总降水量),RX1day(最大 1 日降水量),R20mm(日降水量大于 20 mm 的天数)都是常用指数。
daily_pr = ds["pr"] # 基线降水阈值 base_pr = daily_pr.sel(time=slice("1995-01-01", "2014-12-31")) pr_threshold = base_pr.quantile(0.95, dim="time") # R95p:超阈值降水总量 heavy_pr = daily_pr.where(daily_pr > pr_threshold) r95p = heavy_pr.groupby("time.year").sum(dim="time")8.3 连续干旱天数(CDD)
CDD 的定义是日降水小于 1 mm 的最长连续天数。用 pandas 或 xarray 都可以算,但需要注意纬度维的处理。
def calculate_cdd(pr_series, dry_threshold=1.0): # pr_series: 一维时间序列 dry = (pr_series < dry_threshold).astype(int) # 利用累计求和区分连续段 group = (dry == 0).cumsum() dry_groups = dry.groupby(group) max_streak = dry_groups.sum().max() return max_streak8.4 多指数批量计算
实际项目里不可能只算一个指数。建议写一个统一的 Python 模块,把所有指数计算函数集中管理。
# extreme_indices.py def calc_txx(daily_tasmax): return daily_tasmax.groupby("time.year").max() def calc_tnn(daily_tasmin): return daily_tasmin.groupby("time.year").min() def calc_rx1day(daily_pr): return daily_pr.groupby("time.year").max() def calc_r20mm(daily_pr, threshold=20): days = daily_pr.where(daily_pr >= threshold) return days.groupby("time.year").count(dim="time")8.5 基于极端指数的区域统计
计算完格点指数后,可以按行政区、流域做区域平均:
# 示例:按区域掩膜 import regionmask # 定义研究区 regions = regionmask.defined_regions.ar6.all # 计算区域平均 regional_mean = ds_mean.regionmean(regions)9. 可视化与地图输出
数据分析做完,下一步是把结果变成论文或报告里的图。CMIP6 结果可视化最常用的组合是 Cartopy + matplotlib + xarray。
9.1 空间分布图
import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature # 假设 data 是二维场 (lat, lon) fig, ax = plt.subplots( figsize=(10, 6), subplot_kw={"projection": ccrs.PlateCarree()} ) p = data.plot( ax=ax, transform=ccrs.PlateCarree(), cmap="RdBu_r", cbar_kwargs={"shrink": 0.8, "label": "Temperature change (°C)"} ) ax.add_feature(cfeature.COASTLINE, linewidth=0.8) ax.add_feature(cfeature.BORDERS, linewidth=0.5) ax.set_extent([70, 140, 15, 55]) plt.title("Future Temperature Change (2070-2099 vs 1995-2014)") plt.show()9.2 时间序列图
多模式集合的时序图是区域影响分析中的标准图。典型的做法是画多模式范围阴影 + 集合平均线。
fig, ax = plt.subplots(figsize=(10, 5)) # ensemble_mean 和 ensemble_std 是按时间序列计算出来的 ax.plot(time, ensemble_mean, color="navy", label="MME mean") ax.fill_between( time, ensemble_mean - ensemble_std, ensemble_mean + ensemble_std, color="navy", alpha=0.2, label="±1 std" ) ax.axhline(0, color="black", linestyle="--", linewidth=0.8) ax.legend() ax.set_xlabel("Year") ax.set_ylabel("Temperature anomaly (°C)") plt.show()9.3 极端指数变化图
极端指数常用箱线图或柱状图表示未来不同情景下的变化范围。比如画出 4 个情景下 TXx 的变化范围:
# 伪代码:按情景统计 import pandas as pd data = { "ssp126": txx_ssp126_change, "ssp245": txx_ssp245_change, "ssp370": txx_ssp370_change, "ssp585": txx_ssp585_change } df = pd.DataFrame(data) df.plot.box()9.4 批量保存图片
批量任务中,图片输出命名要有规则,建议包含变量、模式、情景、时间段:
output_name = f"txx_{model}_{scenario}_{start_year}-{end_year}.png" plt.savefig(f"data/output/figures/{output_name}", dpi=300, bbox_inches="tight") plt.close()10. 接口 API 与批量任务设计
很多项目不止跑一次数据,而是需要按月、按季度、按新模式反复运行。这时候必须考虑批量任务和接口封装。
10.1 批量处理脚本
推荐用一个配置文件统一管理输入输出,脚本只读配置。
# config/batch_config.yaml data_dir: ./data/raw output_dir: ./data/output variables: [tas, pr, tasmax, tasmin] scenarios: [historical, ssp245, ssp585] models: [EC-Earth3, CanESM5, MPI-ESM1-2-HR] region: {lat_min: 20, lat_max: 55, lon_min: 100, lon_max: 135} period: {start: 2015, end: 2099} indices: [txx, tnn, rx1day, r20mm]import yaml from pathlib import Path with open("config/batch_config.yaml", "r", encoding="utf-8") as f: config = yaml.safe_load(f) for model in config["models"]: for scenario in config["scenarios"]: for variable in config["variables"]: # 拼文件名、读取数据、处理、计算、保存 pass10.2 并行处理
如果机器核数多,用 concurrent.futures 可以显著加速:
from concurrent.futures import ProcessPoolExecutor, as_completed def process_one_task(task): # task 是 (model, scenario, variable) 组合 return run_pipeline(task) tasks = [ (model, scenario, variable) for model in config["models"] for scenario in config["scenarios"] for variable in config["variables"] ] with ProcessPoolExecutor(max_workers=4) as executor: futures = [executor.submit(process_one_task, t) for t in tasks] for future in as_completed(futures): print(future.result())10.3 API 服务封装
如果团队内部经常需要查询不同区域、不同模式的结果,可以把分析结果封装成 FastAPI 接口。
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class AnalysisRequest(BaseModel): variable: str scenario: str period: str region: dict @app.post("/api/climate_analysis") def analyze(req: AnalysisRequest): # 内部调用分析函数 result = run_analysis(req.variable, req.scenario, req.period, req.region) return resultuvicorn api_server:app --host 127.0.0.1 --port 8000请求示例:
import requests url = "http://127.0.0.1:8000/api/climate_analysis" payload = { "variable": "tas", "scenario": "ssp245", "period": "2070-2099", "region": {"lat_min": 20, "lat_max": 55, "lon_min": 100, "lon_max": 135} } response = requests.post(url, json=payload, timeout=600) print(response.json())注意:API 服务直接暴露在公网有安全风险,建议只绑定在内网或本机,并加访问令牌。
11. 资源占用与性能观察
处理 CMIP6 数据时大家最关心的还是资源占用。这里给出一套观察方法和常见优化思路,具体数字因数据和机器而异,不做绝对化描述。
11.1 内存占用观察
用 xarray 读取大文件时会发现文件读取很快但后续计算很慢,原因很可能是数据全量读入了内存。建议用 dask 分批计算:
ds = xr.open_dataset("large_file.nc", chunks={"time": 3650, "lat": 50, "lon": 50})执行计算时可以用 nvidia-smi(GPU)和 htop/free(内存)实时观察。
11.2 CPU 与 GPU 差异
- 纯数据处理(裁剪、重采样、指数计算)一般 CPU 并行就够了。
- 随机森林等 scikit-learn 模型在 CPU 上表现很好,n_jobs=-1 可加速。
- 深度学习模型在数据量大时上 GPU 收益更大。但在小区域、小样本任务中,GPU 加速效果不明显,反而增加启动和显存开销。
11.3 性能优化技巧
- 先裁剪区域再计算,减少不必要网格。
- 先重采样成月数据再保存,日数据只在需要极端指数时保留。
- 多文件读取用 chunks 避免内存爆炸。
- 中间结果用 zarr 格式保存,比 NetCDF 更适合分块读写。
- 大批量任务时,先跑通一个样本,再横向扩展。
11.4 端口与进程管理
如果启动了 FastAPI 服务,端口被占用会启动失败。排查方式:
# Linux/macOS lsof -i :8000 # Windows netstat -ano | findstr :8000如果调试后进程未释放,强制结束前确认不是生产环境:
kill -9 PID12. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 下载脚本执行失败 | 节点认证失败、网络不稳定 | 查看 wget 脚本日志 | 手动登录节点重新生成脚本;断点续传 |
| NetCDF 文件读取报错 | 文件损坏或缺少依赖库 | 用 ncdump -h 查看文件头 | 重新下载文件;安装 h5netcdf |
| 经纬度坐标范围不一致 | 模式不同坐标系 | 打印 ds.coords 检查 | 统一旋转到 -180~180 或 0~360 |
| 单位换算后数值不合理 | 没注意原始单位 | 查看 variable.units 属性 | 按 CMIP6 标准单位换算 |
| 内存不足 | 直接读取全部数据 | 用 chunks 查看计算图 | 用 dask chunked read;裁剪区域先做 |
| 降尺度预测值方差偏小 | 回归模型固有回归到均值 | 检查预测标准差 | 叠加分位数映射或随机残差 |
| 极端指数计算卡住 | 逐格点循环效率低 | 检查代码是否有 for 循环 | 使用 xarray 向量化操作 |
| 绘图时海岸线不显示 | Cartopy 资源文件未下载 | 检查 network/data 路径 | 安装 cartopy 附加数据或手动下载 |
| API 请求超时 | 数据处理量过大 | 查看日志耗时 | 先预处理结果再启动服务;优化计算流程 |
| GPU 训练显存溢出 | batch_size 过大 | nvidia-smi 观察 | 减小 batch_size 或使用梯度累积 |
13. 最佳实践与工程化建议
这套流程从数据到出图周期很长,工程化程度越高,后期越省心。
13.1 数据版本管理
CMIP6 数据有 v20190603 这类版本标识。你处理后的结果也必须带版本,建议在处理脚本里自动加上模式、情景、变量、处理日期。
import datetime today = datetime.date.today().isoformat() output_name = f"tas_{model}_{scenario}_{today}.nc"13.2 日志记录
批量任务跑十几个小时,中途失败是常事。脚本必须写日志:
import logging logging.basicConfig( filename="logs/pipeline.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s" ) logging.info(f"Processing {model} {scenario} {variable}")13.3 失败重试机制
对于网络下载和多节点依赖,推荐重试机制。
import time from tenacity import retry, stop_after_attempt, wait_fixed @retry(stop=stop_after_attempt(3), wait=wait_fixed(5)) def download_task(task): # 你的下载逻辑 pass13.4 结果复核
极端气候指数计算最容易出错的环节是阈值计算。基线期选择不一致结果差异巨大,务必在方法部分写明基线期。出图前做一次空间一致性检查,避免出现异常极值。
13.5 合规使用
涉及区域影响结论时,不要只用单模式、单情景。合理做法是多模式集合平均,并给出模式间离散度。涉及站点观测数据时确认数据来源。如果有人脸、声音、版权素材相关的工作内容走完全不同的合规通道,这里不展开,但授权边界同样要先确认。
14. 总结
这套 AI + CMIP6 分析流程最值得尝试的点在于,它把气候科学里最琐碎、最容易出错的环节拆成了可复用的 Python 模块。数据下载后从 NetCDF 读取、裁剪、单位换算、日转月、区域平均到极端指数计算,每一步都有明确的技术方案。AI 在其中的作用不是替代气候物理机制,而是提供降尺度、订正和极端事件识别的新工具。
最先要验证的功能是数据链路本身。建议先下载一个小区域、单变量、短时间段的文件,把 xarray 的读取、裁剪、单位换算跑通,再逐步增加模式和情景。最容易踩的坑是纬度经度坐标不统一和单位换算错误,这两点会直接影响所有后续计算结果,务必在代码里加检查逻辑和日志输出。
后续可以扩展的方向很多:把降尺度模型从随机森林换成更复杂的深度网络;把单区域分析扩展到多个流域或行政区;把极端指数计算扩展成一套完整的 ETCCDI 指数库;把离线脚本封装成可请求的 Web API,让团队内部其他系统直接复用。先把一条单链路跑通,再逐步叠加复杂度,这套流程就能真正变成你自己可以长期使用的气候数据分析工具。