ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SWAT模型全局敏感性分析:Sobol与PAWN对比及Matlab实现

SWAT模型全局敏感性分析:Sobol与PAWN对比及Matlab实现 第一次用SWAT去率定一个200多平方公里的流域我心里确实是发毛的。参数太多而且很多参数在物理意义上是重叠的你改这个和改那个模拟结果可能差不多根本分不清是谁在起作用。手动试错试了三天算出的NSE也上不了0.6。后来我才想明白光靠经验调参是治标不治本真正该做的第一步是先搞清楚在这个模型里到底哪些参数值得你花时间去率定。这件事就是项目标题里说的——全局敏感性分析。简单讲就是把几十个参数全部放进合理的取值范围内用系统化的抽样方法跑模型定量判断每个参数对模型输出比如径流NSE的影响程度。项目把Sobol和PAWN两种全局敏感性分析方法放在同一个高参数化SWAT模型上做了对比并用Matlab实现完整流程既出参数重要度排名也分析方法之间的差异。这篇文章我就把整个项目的设计思路、原理对比、Matlab代码实现细节以及我踩过的坑全部写出来。内容适合正在做SWAT率定或水文模拟的研究生、水环境工程师也适合刚接触敏感性分析、想在Matlab里落地一套代码的同学。1. 项目背景与整体设计思路1.1 SWAT模型为什么“高参数化”SWATSoil and Water Assessment Tool土壤和水评估工具是一个基于物理机制的半分布式水文模型被广泛用于流域径流、泥沙、营养物质模拟。它把流域按水文响应单元拆分每个单元又涉及地表径流、土壤水、地下水、河道汇流等过程。正因为过程拆得细SWAT的参数数量非常可观。仅跟径流直接相关的就至少有二十多个——CN2径流曲线数、SOL_AWC土壤有效含水量、SOL_K饱和导水率、ESCO土壤蒸发补偿系数、ALPHA_BF基流消退系数、GWQMN浅层地下水径流系数、CH_N2主河道曼宁系数等等。如果把泥沙、水质参数也算进去轻松超过60个。高参数化的直接后果就是模型率定困难。参数多了目标函数对单个参数的响应被其他参数“稀释”加上参数之间存在不同程度的敏感性交互常规的试错法很难定位到真正的敏感参数。这也是为什么SWAT相关研究里论文几乎都会先做敏感性筛选再只率定排名靠前的参数。项目第一步要解决的就是这个筛选问题。1.2 从局部敏感性走向全局敏感性很多人最初接触的是局部敏感性分析在某一个基准参数点逐次只扰动一个参数观察输出变化。这种OAT一次一个变量方法操作简单但是有个致命弱点——只反映基准点附近的局部行为完全忽略参数之间的交互效应。在SWAT这类非线性模型里参数响应往往不是单调的。CN2在湿润年份的敏感度和干旱年份完全不同某个参数单独看可能不敏感但和另一个参数联合变化时影响却很大。局部方法看不到这些所以局部方法筛出来的结果很容易“自欺欺人”。全局敏感性分析则是把参数空间当作一个整体用统计抽样方法让所有参数同时变化然后通过多次模型模拟计算参数对输出的影响指标。这类方法不依赖基准点能捕获交互效应筛选结果也更可靠。常见方法里有基于方差分解的Sobol、基于回归的Morris筛选、基于分布距离的PAWN等。项目选择对比Sobol和PAWN正好对应了两种思路完全不同的家族。1.3 为什么偏偏是PAWN对比Sobol先说Sobol方法。它是目前公认的标准全局敏感性分析方法几乎成了各类敏感性分析论文里的“对照组”。学界对它的理论性格研究得最透彻各类工具也最齐全。但Sobol有个实际痛点计算开销大。对一个k参数模型即使采用Saltelli采样也需要N×(k2)次模型运行。假设参数数20、样本基量500就是11000次SWAT模拟。在部分SWAT项目里单次运行耗时可能以分钟计这个成本很多人接受不了。PAWN方法由Pianosi和Wagener在2015年前后提出思路不是分解方差而是比较“参数取全体范围内的输出分布”和“参数固定在某个窄带区间时的输出分布”之间的差异。它不依赖输出的一阶矩和二阶矩假设对重尾分布、非均匀响应更稳健而且在同等精度下往往能用更少的模型运行次数尤其适合参数特别多的模型。我把这两个方法放在同一个模型上跑目的很直接看看在SWAT这种高参数化场景下PAWN能不能以更低计算成本得到与Sobol一致的参数排序。如果一致后面就可以放心用PAWN做快速筛选如果差异明显我们就需要知道差异发生在哪、为什么会差。2. Sobol与PAWN方法原理拆解2.1 Sobol方差分解的思路Sobol方法的核心是把模型输出的总方差分解为各参数及参数组合的方差贡献。先定义模型输出为Y输入参数为X1、X2、……、Xk。若参数相互独立则Y的方差可以分解为Var(Y) ΣVi ΣΣVij …… V12…k其中Vi表示只有第i个参数变化时单独贡献的方差Vij表示第i和第j个参数交互作用贡献的方差。基于这个分解就有了两个经典指数一阶效应指数Si Vi / Var(Y)表示第i个参数单独对输出方差的贡献比例。总效应指数STi 1 - V~i / Var(Y)其中V~i是除第i个参数之外所有参数贡献的方差。总效应不仅含Si还包括所有与第i个参数相关的交互作用。实际计算通常用Saltelli采样。构造两个独立的N×k采样矩阵A和B再把A的第i列换成B的第i列生成新矩阵ABi相应地把B的第i列换成A的第i列生成BAi。分别把A、B、ABi、BAi代入模型得到对应的输出向量就能估算Si和STi。这种设计的优势是理论严谨能区分单参数主效应和交互效应。代价是模型运行次数多A和B各运行N次每个参数还要运行N次总计N×(k2)次运行。需要注意的是当两个参数之间存在强相关时Sobol的方差分解前提会被破坏Si和STi的解释会失真这也是实操里必须警惕的地方。2.2 PAWN基于CDF距离的思路PAWN走了另一条路。它不去分解方差而是观察“输出累积分布函数CDF”的变化。模型输出Y在全体参数变化下的无条件分布记为F(y)。然后固定第i个参数在它的某个窄区间内让其他参数继续随机变化得到条件分布F(y|Xi∈区间)。如果第i个参数对输出影响大那么固定它的值以后输出分布会和无条件分布产生明显差异如果第i个参数不重要固定不固定都一样。怎么量化这个差异用Kolmogorov-SmirnovKS距离即两条CDF曲线的最大垂直差。PAWN会先把参数Xi的取值范围分成几十个区间在每个区间里采样并得到对应的条件CDF计算出所有区间的KS距离后取最小的那个作为该参数的整体敏感性指标。这里有个容易理解错的地方为什么取区间中最小值而不是最大值或平均值因为PAWN想找的是Xi的“最大影响”——如果一个参数在任意区间下都能明显改变输出分布那它就是稳定的敏感参数。当然也有论文在讨论用平均值或加权策略具体项目里可以按自己的需求调整但标准实现取的是最小值。PAWN不依赖方差分解所以对输出分布的形状不敏感。哪怕模型输出是偏态分布、甚至存在极端值CDF的KS距离依然稳定。这个特点对SWAT非常合适因为SWAT模拟的产流量经常是强偏态分布的小流量时候密集、大洪水时候稀疏。2.3 方法对比与选择建议我做一个快速对照表方便你在选型时一眼看明白。对比维度SobolPAWN理论基础输出方差分解输出条件CDF与无条件CDF的距离典型采样方式Saltelli序列采样随机/Latin超立方采样条件区间采样模型运行次数估算N×(k2)k越大越贵通常数倍于参数数×样本区间数普遍低于Sobol交互效应识别一阶与总效应分开能识别交互能反映总体影响但交互分解能力较弱对偏态/重尾输出方差估计易受极端值影响基于CDF更稳健收敛速度一阶效应收敛较快总效应和交互项较慢对大多数实际模型收敛较快工具成熟度很高各类软件均有实现相对较新实现版本存在差异选型建议很简单如果你关心参数交互效应的精细结构预算又充足用Sobol如果参数很多、模型单次运行又慢或者输出分布明显很偏优先考虑PAWN。实际工程项目里我常把两者结合——先用PAWN快速筛掉不敏感参数再用Sobol对留下的参数精细化分析。3. Matlab代码实现全过程3.1 整体框架设计整个项目的实现可以拆成四个模块采样模块、模型调用模块、指数计算模块、结果可视化模块。我是按这四部分分别写成独立函数再在主脚本里串起来的。主脚本的顺序大概是定义参数名和取值范围选择样本量N生成Sobol和PAWN所需的采样矩阵把每一组参数写入SWAT输入文件调用SWAT执行文件批量读取输出文件中的径流值计算NSE等目标函数计算两种方法的敏感性指数绘制排名条形图并对比。这里有个设计层面和大家经常忽略的关键点敏感性分析要把“参数样本矩阵”和“模型目标函数”解耦。先在采样阶段生成全部参数组合并保存好再去循环调用模型。不要把采样和模型调用混在一个循环里写不然中途断掉前面所有跑过的模型就浪费了。我后面加了一个断点续跑机制每次循环先把当前参数组合保存成CSV跑完一条再追加结果这样哪怕电脑中途死机也能接着上次继续。3.2 Saltelli采样矩阵生成生成Sobol方法需要的高维采样矩阵是第一步。Matlab自带的sobolset可以生成Sobol低差异序列这个序列比纯随机数覆盖更均匀能让方差估计的收敛速度更快。% 参数数量 k 15; N 500; % 每组的样本数 % 生成 Sobol 低差异序列 p sobolset(k, Skip, 1000, Leap, 100); u net(p, 2 * N); % 分成A和B两个基底矩阵 A_u u(1:N, :); B_u u(N1:2*N, :); % 构建ABi和BAi矩阵组 AB_u zeros(N, k, k); BA_u zeros(N, k, k); for i 1:k AB_u(:, :, i) A_u; BA_u(:, :, i) B_u; AB_u(:, i, i) B_u(:, i); % A的第i列换成B的第i列 BA_u(:, i, i) A_u(:, i); % B的第i列换成A的第i列 end生成的是在[0,1]区间的均匀采样还需要映射到每个参数的真实取值范围。SWAT参数有连续型也有离散型比如CN2一般设5到95而ALPHA_BF通常0到1。映射时我习惯写个统一的转换函数function param map_uniform_to_range(u, low, high) param low u .* (high - low); end如果某个SWAT参数是离散等级比如管理措施编号就要在映射后再取整。这个看似不起眼的处理经常是导致模型静默报错的原因——参数写到文件里变成了非法字符或越界数字SWAT直接中断运行。3.3 PAWN采样与条件区间抽样PAWN的采样比Sobol稍微复杂一点。我采用的流程是先对全体参数做一次Latin超立方采样得到N个无条件样本跑模型得到无条件输出的CDF。这一步可以用lhsdesign实现u_all lhsdesign(N, k);然后针对每个参数Xi把它的取值范围分成M个区间我常用M15。对每个区间再抽取Rm个条件样本让Xi的取值范围锁定在该区间内其余k-1个参数继续在整个参数空间内随机变化。这些条件样本也要全部跑模型得到条件CDF。PAWN的总体模型运行次数大约是T N k × M × Rm设N500k15M15Rm20则总运行次数约5000次比Sobol的N×(k2)8500次少了四成。如果SWAT单次运行要30秒这个差距就直接体现在一个上午和一下午的差别上。生成条件区间样本时有一个细节区间边界不能直接把min和max当作闭区间端点否则边界处的参数值可能触发SWAT初始化报错。我给边界做了2%的收缩确保所有样本点都落在合法范围内。3.4 与SWAT模型的耦合调用这是整个项目里最容易被低估的一步。敏感性分析跟SWAT自带的自动率定工具不同我们得自己控制每次模拟的参数。我用的方案是把SWAT的参数全部预写在一个基础配置文件集里然后通过Matlab读取这些文本文件替换关键参数值再调用SWAT的可执行程序。假设你已经完成CALIBRATION或SWAT-CUP里的参数初始化得到一个完整的SWAT项目目录。需要修改的参数通常分散在不同文件里。我写了一个通用函数专门做文本替换function replace_swat_param(FilePath, ParamName, ParamValue) txt fileread(FilePath); pattern [\b ParamName \s([\d\.\-Ee\])]; [tokens, ~] regexp(txt, pattern, tokens, match); if ~isempty(tokens) newStr regexprep(txt, pattern, [ParamName num2str(ParamValue, %.6E)]); fid fopen(FilePath, w); fprintf(fid, %s, newStr); fclose(fid); else error(未找到参数 %s 的赋值位置, ParamName); end end调用SWAT执行文件的方式在Windows和Linux下略有区别。Windows下直接system(SWAT_64bit.exe)前提是SWAT项目目录已经设置为当前工作目录。Linux下要加上wine或者直接用编译好的Linux版SWAT。这里必须强调每次运行SWAT前务必清空SWAT项目文件夹里的output.*文件否则旧的输出不覆盖你读取到的结果可能是上一次运行的。读取输出文件我主要分析output.rst或者output.sub用importdata或者textscan按列读取。如果你只看出口断面径流只要读主河道最后一行、最后一个节点的流量值即可。然后把模拟径流和实测径流一起代入NSE函数function nse calc_nse(sim, obs) % 计算纳什效率系数 NSE nse 1 - sum((obs - sim).^2) / sum((obs - mean(obs)).^2); endNSE对极端洪水很敏感如果你关心枯水期建议改用KGEKling-Gupta效率或对流量先做对数变换再计算NSE。我在项目里同时算了三种目标函数最后报告以NSE中的结果为主。3.5 指数计算与可视化Sobol指数的计算实现如下假设我们已经跑完A、B、AB、BA四组模型得到了对应的目标函数向量Y_A、Y_B、Y_AB、Y_BAfunction [Si, STi] calc_sobol_indices(Y_A, Y_B, Y_AB, Y_BA, N) mu_A mean(Y_A); mu_B mean(Y_B); varY var([Y_A(:); Y_B(:)]); k size(Y_AB, 2); Si zeros(k, 1); STi zeros(k, 1); for i 1:k Si(i) (mean(Y_A .* Y_BA(:, i)) - mu_A * mu_B) / varY; STi(i) 1 - (mean(Y_B .* Y_AB(:, i)) - mu_A * mu_B) / varY; end endPAWN指数计算稍微绕一些。先基于无条件样本计算基准CDF再对每个区间计算条件CDF并求两者的KS距离function Sk calc_pawn_index(Y_uncond, Y_cond_cell, M) % 基准CDF [F_base, y_grid] ecdf(Y_uncond); Sk zeros(M, 1); for m 1:M [F_cond, ~] ecdf(Y_cond_cell{m}); % 插值到相同网格 F_cond_interp interp1(sort(Y_cond_cell{m}), F_cond, y_grid, previous, extrap); Sk(m) max(abs(F_base - F_cond_interp)); end stat min(Sk); end注意ecdf返回的F是阶梯函数直接对两个长度不同的CDF做KS距离计算会因网格不一致而失真。上面代码里我用了插值到统一网格再取最大差值的办法这个细节能让结果稳定很多。可视化我用barh画横向条形图把Sobol的一阶效应、总效应和PAWN指数画成三个子图排序一致就说明两个方法结论相符。排序不一致的地方单独高亮再返回去看该参数在SWAT里的物理意义判断是否值得针对性率定。3.6 样本量怎么定样本量的经验判断我个人总结成三句话先跑单次试验看耗时再估算总预算最后用Bootstrap重采样验证稳定性。如果SWAT单次运行需要2秒N500、k15的Sobol完整运行约8500次耗时约4.7小时勉强能接受。如果单次运行需要30秒8500次就是70个小时以上这时候建议把N降到200或者直接主力用PAWN约3000次25小时。也可以先做一次小样本预跑N100看看敏感性排名结果是不是已经能区分出明显的“头尾”。如果排名前3的参数和排名倒数的参数之间指数差了一个数量级小样本筛选就够如果参数之间指数非常接近说明模型对这些参数的敏感性差异不大再加大样本量也未必能稳定排序。4. 结果解读、常见问题与实操心得4.1 敏感性排序怎么读以我做过的一个半干旱农业流域为例Sobol总效应排名前三经常是CN2、SOL_AWC、ALPHA_BF。这三个参数分别控制产流能力、土壤持水能力和基流消退在物理机制上正好对应地表径流、土壤储水、地下水补给的三大环节。PAWN排名虽然前三名顺序可能略有变化但认定“这三个是主要敏感参数”的结论一般是一致的。真正需要关注的是排名中段和末段的参数。如果一个参数Sobol的一阶效应Si很低但总效应STi很高说明它的作用主要体现在与其他参数交互上。这种参数单独率定时效果不明显但在全局优化时必须纳入。PAWN对这类参数通常也会给一个中等偏上的分数但不会告诉你交互细节这也是PAWN相对短板的地方。反过来如果某个参数在两个方法中排名差异特别大我建议先检查它有没有触发数值瓶颈——比如参数值导致SWAT模块里某个变量被除零或被设为负值模型还在跑但物理过程已经失真。这种情况下所有敏感性分析结果都会失真不能直接比较方法优劣。4.2 收敛性与稳定性判断判断指数是否稳定我常用的手段是Bootstrap重采样。把已经跑出来的样本集合当成总体有放回地反复抽取子集比如抽样500次每次重新算一遍敏感性指数看指数均值和置信区间。如果Sobol里某个STi的95%置信区间宽度超过指数本身的50%说明样本量严重不足排名会随随机波动大幅变化。此时要加大N而不是去调整采样方法。PAWN这边M区间数和Rm区间内采样数是两大控制因素。M太大区间太窄条件样本量被稀释CDF距离估算方差变大M太小区间太宽固定参数的作用被平均掉PAWN指数会偏低。我用下来M10到20之间比较合适每个区间内最少要有30个条件样本。4.3 常见问题速查表我把项目里踩过的典型问题整理成表直接在表里给排查方向。现象可能原因处理办法Sobol指数出现负值样本量不足方差估计不稳定增大N或用bootstrap检查置信区间不同批次的指数排名大变采样矩阵随机种子未固定模型非线性过强固定随机种子增加重复采样验证SWAT中途无任何报错却不出结果输出文件被旧结果占用exe未等待完成每次运行前清空output.*用系统返回值检查运行状态参数替换后SWAT直接崩溃参数值越界或文本格式不符合自由格式要求检查参数取值范围替换后立即读文件确认格式PAWN指数普遍偏低区间数M太多条件样本量不足减少M增加Rm确保每个区间有足够样本NSE计算结果为NaN模拟值出现了缺失或负流量清理SWAT模拟失败的子流域或改用月份均值部分参数两个方法排序完全相反参数之间存在强相关或模型存在死区对相关参数做独立性诊断考虑因子固定分组试验这里的第7条最容易迷惑。有一次我发现某参数的PAWN指数很高但Sobol总效应几乎为零。后来检查发现该参数和另一个参数存在强共线性两个参数在参数空间里沿对角线方向联动方差分解无法区分它们各自的贡献而PAWN基于条件区间采样反而能捕捉到“固定其中一个时输出分布会改变”的信号。出现这种情况别急着替方法下结论先去查参数相关性矩阵。4.4 我踩过的几个坑和最终建议第一次写Sobol实现时我直接把sobolset生成的序列通通当成均匀分布参数结果有个参数一直取到边界外SWAT跑到一半就崩溃浪费了一天时间。后来我强制在每个映射函数里加范围检查任何超界的值直接报错而不是静默截断这才让整个流程可靠下来。PAWN这边我刚开始套用论文里默认的M40发现条件样本被分得太碎每个区间只有一个样本CDF距离估算完全失真。后来改成M15每个区间重新采样30次效果立刻稳定。不要迷信论文参数一定要结合自己模型的运行成本和输出分布特点去折衷。还有一件事容易被忽略敏感性分析的目标函数选错分析结果可能完全没用。如果你只关心洪水过程却用NSE做目标函数那基流参数会被低估反之如果你主要关心枯季基流应该用对低流量敏感的指标比如月最低流量NSE或对数流量NSE。我在项目里最后选择了NSE配合多站点平均同时用KGE做交叉验证。最后再分享一个经验。全局敏感性分析不是一锤子买卖它是迭代的。第一轮用PAWN筛掉20个参数里的8个不敏感参数剩下12个再用Sobol做精细化分析所需计算量比一次跑20个参数的Sobol可以节省一大半。实践中我强烈建议先做一次快速筛选再来做高精度分析。项目里全程跑完后我对这十几个参数的敏感性排序已经有了明确认知后续率定不再盲目目标函数提升也快了很多。这才是敏感性分析真正能带来的价值。
返回列表