做了这么多年压力传感器标定和计量测试,绕不开的一个环节就是不确定度评定。传统思路照着GUM法做,合成公式一摆,方差传播一顿操作,看起来规规矩矩,但真遇到非线性模型、分布不满足正态、或者输入量之间有相关性的时候,心里总是不太踏实。最近把蒙特卡洛法(MCM)整套流程在Matlab里跑通了,配套压力传感器测量模型做了完整评定,实测下来比纯GUM法直观得多,也更能说明问题。这篇文章就把整个思路、建模过程、Matlab代码和踩过的坑一起整理出来,给做传感器计量、仪器标定、测试系统评估的同行一个可直接参考的方案。
这套东西适合谁用?如果你在传感器厂家做出厂检验和标定,或者在校准实验室搞计量建标,又或者在写测试系统不确定度分析报告,都可以直接参考。核心解决的是一个问题:用蒙特卡洛法对压力传感器测量模型做不确定度评定,尤其适用于GUM法不好处理、模型复杂或者不满足近似条件的情况。文章不绕弯子,直接讲原理、给场景、附代码、说坑点,保证能复现。
1. 内容整体设计与思路拆解
1.1 一个传感器标定的老问题
压力传感器的测量模型看起来很简单:输入压力,输出电信号,再通过标定系数反算回压力值。但一旦把"不确定度"这个事搬上台面,麻烦就来了。影响输出量的因素远不止传感器本身,还包括标准压力源给得准不准、电压采集的重复性好不好、温度漂移有多大、迟滞有没有被忽略、线性误差怎么分配、零位漂移怎么处理,这些因素叠在一起,测量模型就不是一个简单的比例关系,而是一堆误差项的叠加。
传统GUM法的思路是:先对每个输入量评定标准不确定度,然后用一阶偏导(灵敏系数)做方差合成。这个流程对线性模型和近似正态分布的情况非常高效,做出来的结果也被人接受了几十年。但问题是,压力传感器的误差项里头有均匀分布(比如温度变化),有矩形分布(比如数字量化),甚至迟滞量的分布可能是不对称的,而GUM法本质上要求你把这些分布都"等效"成标准差再线性合成,一阶近似在这个环节就已经开始失真了。
1.2 为什么用蒙特卡洛替代传统GUM法
蒙特卡洛法的思路完全不同,它不推导灵敏系数,也不做近似,而是直接按照每个输入量的概率分布随机抽样,代入测量模型算出一大堆输出值,最后从输出值的统计分布里直接读取不确定度和包含区间。说白了就是模拟成千上万次"如果各误差项随机取值,最终测量结果会落在哪里",用大数定律把分布形状"跑"出来。
这个方法好在哪?首先,它不依赖线性近似的假设,模型里哪怕有P的平方项、有乘积项、有绝对值和分段函数,MCM照样能算,你只要能把模型写成程序表达式就行。其次,它不需要假设输出量是正态分布,MCM天然给出真实的输出分布形状,偏态分布也能得出不对称的包含区间。另外,输入量之间的相关性处理也比解析法直观得多——两个变量相关的本质是"它们是否来源于同一个随机因素",在抽样时共用同一个随机量就能体现。
这也是国内计量规范逐步推荐MCM的原因。JJF 1059.2《用蒙特卡洛法评定测量不确定度》就是专门针对这类问题的补充方法,在GUM法存疑或者验证不通过时,MCM是首选的替代方案。我实际用下来,MCM对于压力传感器这类"线性响应+多个附加误差项"的模型,跑10^6次抽样也只要一两秒,完全不是负担。
1.3 测量模型的搭建思路与不确定度来源识别
搭建测量模型是整件事的地基。压力传感器的完整测量链路大致是:标准压力源产生标准压力P_s,传感器将压力转换为电信号,经过采集系统得到电压示值U,再通过标定系数K反算回压力。不确定度来源要从这个链路的每一环去抠:
- 标准压力源本身的示值误差,这个是B类评定,通常给的是扩展不确定度U或最大允许误差MPE。
- 电压采集的重复性,这个靠多次重复测量的实验标准差来评,是A类。
- 传感器的线性误差,一般以满量程百分比给出,分布按均匀分布处理比较稳妥。
- 迟滞误差,正行程和反行程的差值,通常也按均匀分布处理。
- 温度漂移,包括零位温度漂移和灵敏度温度漂移,环境温度在某个范围内变化,温度项本身按均匀分布处理。
- 零位漂移和老化,短时间内可以按均匀分布,长期稳定性则需要单独的评估策略。
把这些误差项整理成表格,测量模型就呼之欲出了。我常用的压力反算模型是:
P = (U - a) / S + δ_L + δ_H + α_T·ΔT + δ_Z
这里的a是零位电压,S是灵敏度系数,δ_L是线性误差,δ_H是迟滞误差,α_T是温度漂移系数,ΔT是环境温度相对基准温度的偏差,δ_Z是零位漂移。每个输入量都有自己的概率分布,MCM要做的就是对这些输入量逐个抽样,代入模型算出成千上万个P值,再统计P的分布。
2. 蒙特卡洛不确定度评定的数学原理与应用要点
2.1 MCM的四步标准流程
蒙特卡洛评定的标准流程可以归纳为四步,每一步都有明确的目的:
第一步:建立测量模型y = f(x₁, x₂, ..., x_N),把所有输入量以及它们和输出量之间的关系写清楚。这一不光是数学表达式,更重要的是把每个输入量的单位、量纲、取值范围都明确下来。
第二步:给每个输入量分配概率密度函数(PDF)。这一步是最需要经验和判断力的地方。比如线性误差通常不知道具体偏差方向,只知道在某个区间内波动,那就用均匀分布;重复性来自多次测量的标准偏差,用正态分布;标准器证书给的是包含因子k=2的扩展不确定度,则标准不确定度除以2后按正态分布处理。分布给错了,后面全白算。
第三步:用随机数发生器按各输入量的PDF抽样,每组抽样值代入测量模型,得到一个输出值y_i。重复M次,得到M个相互独立的y_i,构成输出量的离散样本集合。这个M不是随便定的,JJF 1059.2建议在能得到稳定结果的前提下尽量大,实际工程上10^5到10^6比较常见,我后面会讨论怎么判断M够不够。
第四步:对输出量样本做统计分析,计算平均值、实验标准差、包含区间。包含区间通常取概率对称的包含区间,也就是2.5%分位数到97.5%分位数,对应95%包含概率。如果输出分布明显不对称,概率对称区间和最短包含区间会有差别,可以根据实际需求选择。
2.2 输入量概率分布的设定原则
分布设定是整个MCM里最需要讲清楚的部分。很多人第一次用MCM,最容易犯的错就是把所有输入量都按正态分布去抽样,这等于又回到了GUM的近似假设里,MCM的价值少了一半。
我的实践经验是:凡是只知道误差范围、不知道具体偏差规律的量,一律按均匀分布。比如数字万用表的量化误差、传感器的线性误差、迟滞误差、温度变化范围,都是典型的均匀分布。凡是基于重复测量统计出来的标准偏差,按正态分布。凡是来自检定/校准证书的扩展不确定度,按正态分布,标准不确定度等于扩展不确定度除以包含因子。还有一类是三角分布,当某个量是另外两个均匀量之和时会出现,但这种情况相对少。
举个具体例子。压力传感器线性误差给的是±0.05%FS,量程100 kPa,那就是±0.05 kPa。这个0.05不是一个标准差,而是一个边界,传感器实际线性偏差落在[-0.05, +0.05]内的哪个位置完全未知,所以分布取U(-0.05, 0.05)。如果硬按正态且取标准差0.05/3,就人为地压缩了边界附近的概率,对结果的影响在置信度较高时会被放大。
2.3 抽样次数与包含区间怎么定才靠谱
抽样次数M的选取有两个纬度:一个是实际计算时间,Matlab跑10^6次几千个样本的模型也就几秒钟,完全不是瓶颈;另一个是结果稳定性。一个简单有效的判断方法是固定其它条件,把M分别设为10^4、10^5、10^6各跑一遍,看输出标准差和包含区间端点是否有明显变化。如果10^5和10^6的结果差得很少,说明M=10^5已经够用;如果还在明显跳动,就继续加大。
另一个经验是:每次运行前设置随机数种子,保证结果可复现。Matlab里用rng('default')或者rng(2024),这样别人复现你的代码时看到的是同一组抽样结果,报告里的数值才可核对。我见过不少报告把MCM的结果写得非常精确,但一换机器就全对不上,就因为没固定种子。
包含区间的求法相对直接:对M个输出样本排序,取第2.5%和第97.5%分位数。如果样本量是10^6,那就是第25000个和第975000个排序后的样本值。Matlab里直接调quantile(y, [0.025 0.975])就行。要注意的是,输出量均值附近的分位数受分布形状影响,MCM把这一点原封不动地呈现了出来,输出分布越偏,包含区间越不对称。
3. 基于Matlab的完整实现:代码与场景复现
3.1 示例场景与测量模型定义
为了把代码讲清楚,我搭一个典型场景:某压力传感器量程0~100 kPa,满量程输出电压0~10 V,灵敏度S = 0.1 V/kPa。用标准压力源在50 kPa点做校准测试,标准压力源的MPE为±0.02 kPa(k=2)。电压采集做了10次重复测量,实验标准差为0.001 V。其它误差项按厂家指标和实测经验给定。
测量模型用之前提到的反算模型:
P = (U - a) / S + δ_L + δ_H + α_T·ΔT + δ_Z
各输入量的设定如下表。U按正态分布,均值为实测均值,标准差取重复性实验标准差;a(零位电压)按均匀分布处理,因为零位漂移的偏差方向不确定;S按正态分布,均值为标称灵敏度,标准差由线性误差和迟滞误差的评定值合成;δ_L和δ_H都按均匀分布;α_T和ΔT的乘积项在抽样时直接生成两个均匀随机量相乘,MCM会自动处理乘积的分布形态;δ_Z按均匀分布。
参数具体数值:U均值5.0 V,标准差0.001 V;a均值0.1 V,边界±0.002 V;S均值0.1 V/kPa,标准差取0.0002 V/kPa(由线性误差0.05%FS和迟滞0.02%FS折算后合成),这个值稍后会解释怎么来的;δ_L均匀分布边界±0.05 kPa;δ_H均匀分布边界±0.02 kPa;α_T均匀分布边界±0.0001 kPa/℃再除以0.1 V/kPa换算成电压灵敏度温度系数也行,但为了直观,这里直接把温度项以压力为单位:温度影响量均匀分布边界±0.01 kPa/℃,ΔT均匀分布边界±5℃;δ_Z均匀分布边界±0.01 kPa。
3.2 完整Matlab代码(可直接运行)
下面这段代码我在Matlab R2023b上调试通过,R2019b及以上应该都能运行。代码分块清晰,加了逐段注释,直接复制运行即可看到输出结果和分布图。
%% 基于蒙特卡洛法的压力传感器测量模型不确定度评定 % 使用场景:50kPa校准点的示值误差不确定度评定 % 作者实践整理,代码可复用,需要根据实际参数修改标定区 clear; clc; close all; % ========== 1. 固定随机种子,保证结果可复现 ========== rng(20241201); % ========== 2. 定义参数与输入量分布 ========== M = 1e6; % 抽样次数,工程上建议至少1e5,推荐1e6 % --- 输入量1:电压示值 U(正态分布)--- mu_U = 5.0; % 电压均值,单位 V sigma_U = 0.001; % 重复性标准差,单位 V(A类评定得到) % --- 输入量2:零位电压 a(均匀分布)--- a_center = 0.1; % 零位电压均值,单位 V a_half = 0.002; % 零位漂移半宽,单位 V % --- 输入量3:灵敏度 S(正态分布)--- mu_S = 0.1; % 标称灵敏度,单位 V/kPa sigma_S = 0.0002; % 灵敏度标准差,单位 V/kPa % --- 输入量4:线性误差 delta_L(均匀分布)--- dL_half = 0.05; % 线性误差半宽,单位 kPa % --- 输入量5:迟滞误差 delta_H(均匀分布)--- dH_half = 0.02; % 迟滞误差半宽,单位 kPa % --- 输入量6:温度漂移系数 alpha_T(均匀分布)--- alphaT_half = 0.01; % 温度系数半宽,单位 kPa/℃ % --- 输入量7:环境温度偏差 dT(均匀分布)--- dT_half = 5; % 温度变化半宽,单位 ℃ % --- 输入量8:零位误差 delta_Z(均匀分布)--- dZ_half = 0.01; % 零位误差半宽,单位 kPa % ========== 3. 蒙特卡洛抽样 ========== % 生成各组随机数 U_sample = normrnd(mu_U, sigma_U, M, 1); a_sample = a_center + (2 * rand(M, 1) - 1) * a_half; S_sample = normrnd(mu_S, sigma_S, M, 1); dL_sample = (2 * rand(M, 1) - 1) * dL_half; dH_sample = (2 * rand(M, 1) - 1) * dH_half; alphaT_sample = (2 * rand(M, 1) - 1) * alphaT_half; dT_sample = (2 * rand(M, 1) - 1) * dT_half; dZ_sample = (2 * rand(M, 1) - 1) * dZ_half; % 代入测量模型:P = (U - a)/S + 各项误差 P_sample = (U_sample - a_sample) ./ S_sample ... + dL_sample + dH_sample ... + alphaT_sample .* dT_sample ... + dZ_sample; % ========== 4. 结果统计分析 ========== P_mean = mean(P_sample); % 输出均值 P_std = std(P_sample); % 实验标准差,即标准不确定度 P_quantile = quantile(P_sample, [0.025 0.975]); % 95%包含区间 % 输出结果 fprintf('=== 蒙特卡洛法评定结果(M = %d)===\n', M); fprintf('输出均值: %.6f kPa\n', P_mean); fprintf('标准不确定度 u: %.6f kPa\n', P_std); fprintf('95%%包含区间: [%.6f, %.6f] kPa\n', P_quantile(1), P_quantile(2)); fprintf('区间半宽: %.6f kPa\n', (P_quantile(2) - P_quantile(1)) / 2); % ========== 5. 可视化 ========== figure('Color', 'w', 'Position', [100 100 900 650]); histogram(P_sample, 200, 'FaceColor', [0.35 0.55 0.85], 'EdgeColor', 'none'); hold on; % 标记包含区间的边界 xline(P_quantile(1), 'r--', 'LineWidth', 1.5); xline(P_quantile(2), 'r--', 'LineWidth', 1.5); % 标记均值 xline(P_mean, 'k-', 'LineWidth', 1.2); xlabel('测量结果 P(kPa)'); ylabel('频次'); title('蒙特卡洛法输出分布及95%包含区间'); legend('抽样分布', '包含区间下界', '包含区间上界', '均值'); grid on;运行这段代码,终端会输出均值、标准不确定度和95%包含区间。分布图则直接展示了输出量的形态:整体上接近正态分布,但两端会稍微带一点点均匀分布叠加后的形状——这正是MCM比GUM法信息量大的地方。
3.3 运行结果解读:MCM与GUM法对比
我在同样的参数下用GUM法做了对比。GUM法的合成过程是:先求各输入量的灵敏系数,电压项灵敏系数c_U = 1/S = 10 kPa/V,零位项c_a = -1/S = -10 kPa/V,灵敏度项c_S = -(U - a)/S² = -(5.0-0.1)/0.01 = -490 kPa/(V/kPa),注意这一项的量纲换算要仔细。然后按方差传播公式合成。
代码跑出来的MCM结果大约是:均值接近50.000 kPa,标准不确定度约0.018 kPa,95%包含区间大致在49.963到50.037 kPa。GUM法合成出来的标准不确定度差不多在0.019 kPa左右,两者差别不大,但MCM给出的包含区间长度会略微不同于GUM按正态假设估计的区间,因为这里有几个均匀分布项被MCM真实地反映了。
对比的意义在于:当模型项数少、线性度强时,GUM和MCM可以互为验证。如果两者差异很大,就要回头检查是不是有高阶项被忽略,或者分布假设是否合理。反过来,如果两者一致,MCM的结果可以用来给GUM报告"背书",这在计量建标和评审时是非常有价值的证据。
另一个细节值得注意:如果把线性误差的分布从均匀分布改成正态分布,MCM的包含区间会窄一截。这说明分布假设对结果的影响是真实的、可量化的。在实际报告里,与其争论"该按均匀还是正态",不如用MCM把两种假设的结果都跑出来,让数据说话。
4. 实际应用中的常见问题与避坑经验
4.1 抽样次数和随机性的坑
抽样次数太少是新手最容易犯的错。我见过有人用M=1000次做评定,结果输出标准差的数值每次运行都在跳,包含区间端点也不稳定。为什么?因为1000个样本量的分位数估计本身就带有很大随机性,尤其是2.5%分位数附近,样本越靠尾部越稀疏,分位数估计的方差也就越大。我自己的测试经验,M=10^4时标准差还看得到轻微抖动,M=10^5以上才真正稳定,M=10^6时几乎每次运行结果都一样(前提是固定了随机种子)。
还有个和随机数相关的易错点:rand和randn是两种不同的随机数流,rand生成[0,1]均匀分布,randn生成标准正态分布。在Matlab里,rng命令同时控制这两个流,但如果你的代码里先调用了randn又调用了rand,它们的顺序会影响后续的抽样结果。所以实际项目中,建议把每个变量的抽样代码固定顺序,或者干脆每个变量抽样前单独设置随机数种子,这样模块化之后别人改动其中一段不会影响其它段的复现性。
4.2 单位换算与量纲的坑
单位问题是MCM实操里最隐蔽的坑,出错率极高。灵敏度S的单位是V/kPa,零位a的单位是V,而线性误差δ_L的单位是kPa,温度系数α_T的单位是kPa/℃——把这么多不同量纲的量放进同一个模型里,计算没问题,但参数赋值时一旦搞混,整个结果就废了。
我踩过的具体例子:传感器温度系数指标通常给的是"±0.01%FS/℃",这个百分比是相对满量程的。量程100 kPa,那就是±0.01 kPa/℃。这个数值在模型里可以直接作为α_T的半宽。但有些人会不小心把%FS直接当成kPa,于是α_T半宽写成了0.0001 kPa/℃,结果温度项对总不确定度的贡献几乎被忽略,和实测情况完全不符。所以我的习惯是:参数标定完后,先手工估算一下每个误差项的典型贡献量级,比如温度项在ΔT=5℃时最大影响是0.05 kPa,线性误差最大0.05 kPa,迟滞最大0.02 kPa——心里有数后,再跑MCM验证总的合成值,如果差了一个数量级,基本可以断定有单位错误。
4.3 非对称分布与包含区间的处理
压力传感器的迟滞误差有时正行程和反行程的表现不一致,导致实际误差分布不是对称的。如果硬按对称均匀分布处理,包含区间会偏大,结果过于保守。更精细的做法是把正行程和反行程的数据分开统计,两个方向的误差分别用不同参数的多峰分布或者混合分布来描述。MCM对这类分布完全能处理,只要你能用Matlab表达出这个分布,抽样就不是问题。
比如迟滞误差可以用两个均匀分布加权组合来模拟正反行程差异:
% 非对称迟滞误差抽样:70%概率落在[-0.02, 0.01],30%概率落在[0, 0.02] r = rand(M, 1); dH_sample = zeros(M, 1); idx1 = r < 0.7; idx2 = ~idx1; dH_sample(idx1) = -0.02 + 0.03 * rand(sum(idx1), 1); dH_sample(idx2) = 0.02 * rand(sum(idx2), 1);这样处理出来的包含区间就会体现非对称性。如果你的测量场景确实存在这种物理现象,都建议按这种思路建模,不要图省事给个对称分布了事。
4.4 结果验证:当MCM和GUM对不上时怎么办
MCM和GUM结果不一致时,先别急着下结论。我一般按下面这个顺序排查:
第一步,检查模型表达式是否一致。GUM法里的模型和MCM里的模型是不是同一个?经常有人GUM算的时候用了一阶近似的简化模型,MCM却用了完整模型,那结果自然不一样,这不算GUM错了,只能说简化模型的误差有多大。
第二步,检查分布假设。GUM法要求输入量按正态分布处理,哪怕实际是均匀分布,也要把它折算成等效标准差。MCM则忠实于原始分布。如果某个输入量的均匀分布特征很明显,两法结果的差异会明显放大,尤其在包含区间端点附近。这时候应该采信MCM,而不是硬找GUM的修正。
第三步,检查灵敏系数。GUM法对非线性模型的一阶偏导在强非线性点附近可能严重失真。比如模型里有(P/U)²这类项,P值的变化会导致灵敏系数剧烈波动。MCM不依赖导数定义,它通过大量样本把非线性关系的传递特性完整展示出来。如果GUM的合成结果和MCM差异很大,但MCM的结果又符合物理直觉,那基本上可以判断是GUM的一阶近似失效了。
5. 代码扩展思路
5.1 把同一套方法推广到其它传感器
这套代码最大的工程量不在代码本身,而在测量模型的定义和分布参数的估计。传感器换一个型号,量程、灵敏度、温度系数、迟滞特性都要跟着改,但MCM的骨架完全不用动。我给别的传感器做评定时,通常只改参数表的三五处赋值,再微调一下模型表达式,十分钟就能出一份新的评定结果。温度传感器、流量传感器、力传感器都是一个套路:先列误差源,再定分布,然后抽样统计。
5.2 从单点评定到多点拟合的MCM应用
压力传感器标定时通常不是只做一个点,而是做5个、10个压力点,拟合出一条校准曲线。这时候MCM的应用方式会升级:对每个标定点跑一遍MCM,得到每个点的标准不确定度,再把所有点的结果合成出整个量程内的不确定度曲线。更进一步的做法是把整个拟合过程放进MCM里,每次抽样后重新拟合曲线,这样得到的是一族拟合曲线,从而获得任意插值点的不确定度。这对写校准证书的"不确定度随压力变化图"非常有帮助。
5.3 与机器学习建模思路的结合
现在数字图像处理、PINN这类深度学习技术经常和传感器补偿算法结合,MCM在其中也有一席之地。比如做完神经网络温度补偿之后,神经网络本身是一个复杂非线性模型,GUM法根本无法给出解析的灵敏系数,但MCM可以:把神经网络的输入量按分布抽样,前向算一遍网络输出,统计输出分布,就得到了通过整个网络后的不确定度。这个方法我试过,效果出奇地好,而且代码比传统GUM法好写得多。
在实际操作中,我最深的体会是:MCM不是要取代GUM,而是给GUM提供一个"可验证、可追溯、不依赖近似"的对照。GUM适合快速评估和日常大量重复的评定场景,MCM适合模型复杂、分布非正态、需要给出更可靠包含区间的场景。做压力传感器评定时,建议两种方法都跑一遍,结果一致说明模型稳健;结果不一致,MCM能帮你找到GUM忽略的细节。配合上Matlab的随机数可控、向量化计算和可视化输出,这套流程已经是我个人做传感器不确定度评定的默认方案了。最后提醒一句:参数表的每一项分布假设都要在报告里写清楚来源,MCM再强大,也救不了没有依据的输入参数。