Engee 中模型参数的识别
示例说明
让我们来考虑基于数据的模型参数识别任务的实现。我们必须识别某个模型的几个时变参数,我们事先知道该模型的结构。
在本例中,我们将分析动态模型 Engee 生成的信号。它位于start/examples/data_analysis/parametric_identification
文件夹中。
该模型由两个子系统组成:一个生成分析信号,另一个是参考模型(无噪声)。

在子系统ModelSystem
中,五个信号通过求和的方式进行组合:
- 常数分量:数字 5、
- 时间分量:斜率为 0.01 的直线、
- 第一次谐波:振幅为 1、角频率为 rad/s 的正弦波、
- 不太突出的二次谐波:振幅为 0.6、角频率为 rad/s 的正弦波、
- 白噪声,偏移为零,色散为 0.3。

在子系统ModelBlock
中,我们可以替换已确定的参数。只要在其中设置参考参数并生成参考信号即可。在该子系统中,模型被描述为一个代码:
``朱莉娅
函数 (c::ModelBlock)(t::Real)
T0 = 5
T1 = 0.01
A1、f1、phi1 = 1、0.02、0
A2、f2、phi2 = 0.6、0.008、0
信号 = T0 + T1 * t +
A1 * sin( 2*pi * f1 * t + phi1 ) .+
A2 * sin( 2*pi * f2 * t + phi2 );
返回信号
结束
让我们保存参数的参考值:
Pkg.add(["CSV", "HypothesisTests"])
# "Идеальные" параметры линейной части сигнала (смещение, наклон)
T0m = 5;
T1m = 0.01;
# "Идеальные" параметры первой синусоиды (амплитуда, частота, фаза)
A1m = 1;
f1m = 0.02;
phi1m = 0;
# "Идеальные" параметры второй синусоиды (амплитуда, частота, фаза)
A2m = 0.6;
f2m = 0.008;
phi2m = 0;
运行模型后,我们得到了 500 秒的信号。下面是最后 200 秒的曲线图:

图块ModelBlock
现在的初始化参数与子系统ModelSystem
中的动态模型完全相同,因此from_code
(深蓝色)的图形完全复制了信号from_model
(橙色)的系统部分。
模型存储的数据
两个信号都保存在 CSV 表param_id_data.csv
中,其中包含以下列:
*time
- 时间列、
*m
- 动态模型生成的数据列(参考参数和误差)、
*c
- 参考模型生成的数据列(作为代码执行)。
本示例中的操作顺序
我们需要执行以下步骤:
1.启动动态模型,合成模型数据
2.按照正确的顺序找到所有模型链接的参数(假设信号允许我们猜测模型的拓扑结构,或者我们已经知道了)。
3.检查从输入信号中减去系统成分后的结果是否为正态分布噪声。
4.将参数到 Engee 功能块代码中,并检查模型是否相互匹配。
合成模型数据
运行以下单元格,合成我们要分析的数据:
modelName = "param_identification_model"
#engee.close( modelName, force=true ) # Для повторного запуска модели
model = modelName in [m.name for m in engee.get_all_models()] ? engee.open( modelName ) : engee.load( "$(@__DIR__)/$modelName.engee");
results = engee.run( modelName, verbose=false )
加载库
连接用于数据加载、表格处理、光谱分析的库和多项式回归库。
using CSV, DataFrames, FFTW, Polynomials
下载并连接假设检验库(我们使用正态检验):
#import Pkg; Pkg.add( "HypothesisTests", io=devnull );
using HypothesisTests
加载数据
从这里开始,我们将只使用动态模型生成的信号,但你可以看到这两种方法是可以互换的,如果有必要,这个例子可以在一个脚本中完成。
我们将对模型信号ModelSystem
进行可视化处理,该信号由时间矢量和计算值组成:
t = results["from_model"].time;
signal = results["from_model"].value;
我们还将保留时间信号的采样率(与两个相邻测量值之间的时间距离成反比的值):
sample_rate = 1/(t[2] - t[1])
我们将识别
参考模型ModelBlock
以参考参数值初始化(与模型ModelSystem
中的参数值相同)。

该模型由 8 个参数定义:
- 线性趋势参数:
- 第一个正弦波的振幅、频率和相位:
- 第二个正弦波的振幅、频率和相位:
我们不会对噪声参数感兴趣,尽管它们很容易通过函数mean
和var
来识别。
让我们识别线性分量
我们首先要做的是从信号中识别并消除线性趋势。
trend = Polynomials.fit( t, signal, 1 ) # Последний аргумент: степень полинома
让我们将多项式参数保留为单独的变量
T0, T1 = trend[0], trend[1]
让我们在图表中显示找到的趋势,并展示我们的数据在减去这一趋势后的样子:
plot(
plot( t, [signal trend.(t)], label=["Данные" "Тренд"] ),
plot(t, signal - trend.(t), lc=:darkblue, label="Без тренда"),
layout=(2,1)
)
很好,信号开始看起来像正弦波和噪声的组合。
识别第一个正弦波
为了确定信号中包含的正弦波的参数,我们使用 FFTW 库绘制信号的频谱图。
其离散度与原始信号中的点数成正比。也就是说,如果不对原始信号进行离散化处理,频谱的精度就会很低。为了消除高频区域,我们可以通过降低原始信号的采样频率来减少原始信号中的点数。为此,我们设置变量downsample_factor
。数值 40 表示我们将从原始信号中每隔 40 个点取样一次。
downsample_factor = 40;
# Дальше мы будем работать с сигналом пониженной дискретизации (signal_downsampled)
idx_downsample = 1:downsample_factor:length(t)
t_downsampled = t[idx_downsample]
# Дискретизация сигнала, из которого мы вычли линейный тренд
signal_downsampled = (signal - trend.(t))[idx_downsample];
由于我们只对频谱的正向部分感兴趣,因此使用函数rfft
(我们也不必执行fftshift
):
# Найдем спектр сигнала
F1 = rfft(signal_downsampled)
# Рассчитаем вектор частот найденного спектра
freqs = rfftfreq(length(t_downsampled), sample_rate/downsample_factor)
# Выведем график
plot( freqs, abs.(F1) )
让我们找出频谱上的最大值,并确定该值对应的频率。
_,idx = findmax( abs.(F1) )
print( "Частота наиболее заметной компоненты: ", freqs[idx] )
找到频谱上的最大值后,我们就能确定最突出谐波成分的振幅频率和相位。现在,我们需要进行再组合--从描述其参数的复数中生成谐波信号。
# Находим параметры синусоиды
A1 = 2 * abs( F1[idx] ) ./ length(t_downsampled )
# Умножение на 2 происходит из-за того, что мощность сигнала в спектре
# делится между двумя пиками: положительным и отрицательным
f1 = abs( freqs[idx] ) # Частота синусоиды
phi1 = angle( F1[idx] ) + pi/2 # Фаза синусоиды
print("A1 = ", A1, "\nf1 = ", f1, "\nphi1 = ", phi1)
让我们将第一个正弦波定义为时间函数,并绘制出它的曲线:
# Функция, задающая синусоиду с найденными параметрами
sinusoid1(tt) = A1 * sin.( 2*pi*f1*tt .+ phi1 );
# Выводим графики
plot(
plot( t, [signal .- trend.(t), sinusoid1.(t)], label=["Данные без лин.тренда" "Первая синусоида"] ),
plot( t, signal .- trend.(t) .- sinusoid1(t), lc=:darkblue, label="Остаточный сигнал" ),
layout=(2,1)
)
好了,现在信号是正弦波和噪声的组合。
让我们识别第二个正弦波
重复寻找第一个正弦波的相同操作:
# Сигнал пониженной дискретизации (без тренда и первой синусоиды)
signal_downsampled2 = (signal .- trend.(t) .- sinusoid1(t))[idx_downsample]
# Находим спектр сигнала после вычета первых двух компонент
F2 = rfft(signal_downsampled2)
# Рассчитаем вектор частот найденного спектра
freqs = rfftfreq(length(t_downsampled), sample_rate/downsample_factor)
# Выведем график
plot( freqs, abs.(F2) )
让我们找出第二个正弦曲线的参数
# Находим максимум на спектре
_,idx2 = findmax( abs.(F2) )
# Определяем параметры синусоиды, соответствующие этому максимуму
A2 = 2 * abs( F2[idx2] ) ./ length( t_downsampled ) # Амплитуда
f2 = abs( freqs[idx2] ) # Частота
phi2 = angle( F2[idx2] ) + pi/2 # Фаза
print( "A2 = ", A2, "\nf2 = ", f2, "\nphi2 = ", phi2 )
让我们将第二个正弦曲线定义为时间函数,并绘制它的曲线图:
# Функция, задающая синусоиду с найденными параметрами
sinusoid2(tt) = A2 * sin.( 2*pi*f2*tt .+ phi2 );
# Выводим графики
plot(
plot(t, [signal.-trend.(t).-sinusoid1.(t) sinusoid2.(t)],
label = ["Сигнал минус две компоненты" "Вторая синусоида"]),
plot(t, signal.-trend.(t).-sinusoid1(t).-sinusoid2(t), lc=:darkblue,
label = "Остаточный сигнал", legend=:outertopright),
layout=(2,1)
)
我们怎样才能知道这个信号中是否还存在重要的系统成分呢?一种方法是将该信号想象成随机变量的实现,并测试其分布的正态性。
分析残差信号
让我们使用 Harker-Bera 检验 (JB-检验)来检验残差信号分量的正态分布假设:
h = JarqueBeraTest( signal .- trend.(t) .- sinusoid1(t) .- sinusoid2(t) )
请注意检验结果 (outcome
):
*fail to reject h_0
表示没有充分理由拒绝假设 :我们以 95%的置信度断言,观察到的信号是由正态分布的随机过程产生的。
*reject h_0
表示观察到的信号来源于正态分布的置信度不超过 5
试着从公式中排除某些成分--这样的信号很可能无法通过 Harkke-Bera 检验,你将得到reject h_0
的结果。
分析结果
获得的结果是完成寻找其他系统成分的基础。
让我们得出最终报告和参数估计误差。
df = DataFrame(
["T0" T0 T0m string(round(100*abs(T0 - T0m)/T0m, digits=2))*"%";
"T1" T1 T1m string(round(100*abs(T1 - T1m)/T1m, digits=2))*"%";
"A1" A1 A1m string(round(100*abs(A1 - A1m)/A1m, digits=2))*"%";
"f1" f1 f1m string(round(100*abs(f1 - f1m)/f1m, digits=2))*"%";
"phi1" phi1 phi1m string(round(abs(phi1 - phi1m),digits=2))*" рад";
"A2" A2 A2m string(round(100*abs(A2 - A2m)/A2m, digits=2))*"%";
"f2" f2 f2m string(round(100*abs(f2 - f2m)/f2m, digits=2))*"%";
"phi2" phi2 phi2m string(round(abs(phi2 - phi2m),digits=2))*" рад"
],
["Параметр", "Идентифицировано", "Модельное значение", "Погрешность"]
)
创建一个函数,用确定的参数计算模型数据:
function ModelBlock(t)
global T0, T1, A1, f1, phi1, A2, f2, phi2;
signal = T0 .+ T1 .* t .+
A1 .* sin.( 2*pi .* f1 .* t + phi1 ) .+
A2 .* sin.( 2*pi .* f2 .* t + phi2);
return signal
end;
让我们比较一下运行已确定模型的结果和运行ModelBlock
的结果:
plot( results["from_model"].time,
[results["from_model"].value, results["from_code"].value, ModelBlock.(t) ],
label=["Исходный сигнал" "Эталонная модель" "Идентифицированная модель"],
linecolor=[:lightblue :red :green])
在这个例子中,我们
- 实施了一个相当简单的参数识别程序、
- 确定所有参数的识别误差、
- 验证带识别参数的模型图与参考模型图非常相似。