资讯动态

TVP-VAR模型MATLAB实现:状态空间设定、sa2参数与三维脉冲响应图详解

发布时间:2026/10/20 16:00:18 来源:尧图企业网站定制
简介这是一份基于Matlab的TVP-VAR时变参数向量自回归模型实现代码包面向经济学、金融学等领域需要开展时变参数结构分析的研究者与研究生。代码源自中岛上智教授的经典版本由企研数据在原基础上增加作图时间标签、三维脉冲响应图绘制功能并补充sa2参数的统计信息使不同时点的脉冲响应变化更直观、参数估计结果更完整。压缩包为zip格式整体大小约2MB文件围绕数据预处理、模型估计、后验结果输出与图形绘制等环节组织结构较为清晰适合已掌握基础VAR和贝叶斯推断方法的读者直接调用或二次修改。目前已有85人学习下载对于需要复现TVP-VAR分析、绘制三维脉冲响应图或扩展模型输出的论文写作者而言是一套实用且便于上手的参考工具可广泛应用于宏观经济政策传导、金融市场波动溢出等动态关系研究。三维脉冲响应图能从时间与冲击两个维度同时展示动态变化是论文中进行机制分析的重要可视化手段也有助于初学者结合代码理解时变参数模型的估计流程。1. TVP-VAR模型的MATLAB实现为什么你需要的不仅是系数估计做宏观经济或金融时间序列研究的人大概率都遇到过这样的困境拿一段长达十几年的月度数据跑VAR结果发现模型残差一直不稳定系数估计就像在溜冰场上走路怎么站都站不稳。这时候把数据切成两段分别估计又会发现两段结果差得离谱而你自己心里清楚这不是数据错了结构突变是真实存在的。TVP-VAR时变参数向量自回归模型解决的就是这个问题它允许截距、系数、误差协方差都随时间变化同时通过状态空间形式把参数的演化过程建模成一个随机游走或AR过程。市面上关于TVP-VAR的教程不少但大多数只给你最基本的系数估计和脉冲响应图真正拿到手你会发现少三样东西时间标签、三维脉冲响应图、以及一个被称为sa2的关键参数的解读。这篇笔记想做的是把一套能直接跑的TVP-VAR MATLAB代码讲透从数据结构怎么摆、先验怎么设到如何把时间标签落到图上、再做一张能旋转的三维脉冲响应图最后把那个让很多人一头雾水的sa2参数掰开揉碎。整个过程你可以照着敲也可以拿现有代码对照着改。如果你打算用TVP-VAR做论文实证、政策效应分析或者单纯想验证某一个时间点上的冲击响应是否发生结构性变化这篇内容值得你花二十分钟读到最后。先说结论代码本身不难难点全在设定和出图上而这两件事恰恰是这份代码帮你解决掉的核心问题。2. 理解TVP-VAR的状态空间结构先分清状态方程和量测方程2.1 TVP-VAR和标准VAR的本质差异参数本身变成了待估计的状态变量标准VAR模型把系数当成常数无论数据如何波动系数始终不变。这带来一个很强的前提假设样本区间内经济结构是稳定的。但现实往往不是这样。利率市场化改革前后、货币政策框架调整之后同一个变量的滞后期系数会明显变化。TVP-VAR把系数向量从常数改为随时间推移的状态变量量的测方程保持VAR形式状态方程描述系数如何演化。本质上TVP-VAR是一个线性高斯状态空间模型可以用卡尔曼滤波和模拟平滑simulation smoother来估计后验分布。在MATLAB实现中最直观的记忆方式是你不再求一个固定的系数矩阵而是求一条系数路径——每个时间点t都有一组对应的系数值。因此输出结果的核心对象不再是一个A矩阵而是一个三维数组尺寸通常是滞后阶数×变量数变量数时间点数。这套数组的含义必须在一开始搞清楚否则后面的所有画图都会变得无从下手。2.2 程序里状态变量怎么组织系数向量、协方差矩阵的元素、误差协方差的对数方差一个可运行的TVP-VAR代码状态变量通常分成三大类第一类是VAR系数向量记为β第二类是同期联立方程系数structural coefficients通常记为α第三类是误差项协方差矩阵的对数标准差记为h。这三类变量各自走各自的随机游走过程彼此之间后验独立。所以你在MATLAB代码里会看到三个主要的状态更新循环一个更新β一个更新α一个更新h。它们更新的方式都是基于卡尔曼滤波和平滑采样只是各自的观测方程和数据矩阵组织方式不同。对β的观测方程就是把滞后项作为回归矩阵数据矩阵通过“堆叠”方式排列每期一个行块对α的观测方程结构类似但需要先对残差做标准化的变换对h的观测方程则直接面对标准化残差的平方。这里最容易犯的错是把三个状态混在一个状态向量里更新。标准做法是分开处理每一步利用条件后验的分解结构依次采样β、α、h。在写代码时变量命名最好直接区分开beta_draw、alpha_draw、h_draw不要统一用theta否则后期调试会非常痛苦。2.3 sa2参数到底在哪个方程里先了解参数设定再谈输出很多人看到代码里的sa2就懵了因为它既不像β也不像h甚至在状态方程里都找不到它。实际上sa2是状态方程扰动项方差的先验缩放参数。TVP-VAR中每个状态变量都假设服从随机游走扰动项的方差非常关键太大参数路径会跳来跳去太小参数路径会过于平滑甚至趋近于常数VAR。sa2是控制状态变量扰动项方差初始值的一个缩放因子一般通过先验设定给出初始协方差矩阵乘以这个缩放参数来调节参数演化的平滑程度。如果查看原始代码中关于MCMC初始化的部分几乎都会出现这样的语句设置状态方程扰动协方差为单位矩阵乘以sa2然后把这个初始值代入卡尔曼滤波。因此sa2的大小直接决定采样初期的状态路径波动程度也影响收敛速度。实际应用中sa2的取值常常设为0.01、0.01、0.1一类的组合分别对应β、α、h的状态方差缩放。这是需要根据数据频率和变量量纲调整的地方也是后续参数讲解的重点。3. MATLAB代码的整体框架数据准备、先验设定与MCMC主循环3.1 输入数据怎么摆Y矩阵格式和滞后阶数的确定写代码的第一步是把数据整理成一个统一格式的矩阵。对于TVP-VAR需要的数据格式是T行K列T为观测期数K为变量个数。特别注意不需要像某些其他模型那样单独准备截距项代码内部会自动补充截距项列。数据频率可以任意但状态方程的方差设定和滞后阶数需要相应调整。滞后阶数的选择在入门阶段不需要过于纠结常见做法是直接借用一个标准VAR的滞后阶数准则或者干脆手动设定。月度数据一般设2阶季度数据设1到2阶年度数据设1阶。在代码中通常会有一个设定项比如lag 2修改这个值即可。确定滞后阶数后程序内部会自动生成滞后矩阵并按时间对齐丢弃前lag期的数据。3.2 先验参数表初始化方法、MCMC迭代次数、燃烧期设置TVP-VAR是贝叶斯模型必须先确定先验。常见做法是用一个子样本的OLS估计结果作为先验均值用OLS估计的协方差矩阵放大若干倍作为先验方差。这个子样本通常取总样本的前20%到30%。为什么不用完整样本因为完整样本估计结果本身就包含结构变化信息用来做先验会过度平滑导致参数路径变化幅度被压缩。MCMC迭代次数一般设10000次以上其中前1000到2000次作为燃烧期丢弃。计算资源够的话可以设到20000次燃烧5000次。迭代越多参数路径越稳定但运行时间会以近似线性增长。如果每次迭代耗时过长建议先从5000次迭代开始试跑确认结果稳定后再加到正式次数。参数项常见设定值作用与影响sa2[0.01, 0.01, 0.1]控制β、α、h状态的方差缩放影响平滑程度MCMC迭代次数10000~20000决定后验采样规模太小会导致结果不稳定燃烧期1000~5000丢弃早期未收敛样本影响参数后验均值准确性滞后阶数月度2阶季度1~2阶模型动态结构直接影响状态变量维度子样本比例0.2~0.3作为先验估计窗口影响先验均值和方差3.3 MCMC主循环的三段式更新采样β、采样α、采样h主循环的每一轮迭代分为三段分别更新β、α、h三个状态块。第一段以当前的α和h为条件利用卡尔曼滤波和模拟平滑采样β第二段以更新后的β和当前的h为条件对残差做同期变换采样α第三段再以更新后的β和α为条件计算标准化残差采样h。三者反复循环直到完成设定的迭代次数。每个采样过程的代码主体是相同的骨架先调用卡尔曼滤波计算均值和协方差再利用模拟平滑从后验分布中采样。不同之处在于传递给滤波函数的观测矩阵不同。在MATLAB里实现方式通常是把观测方程写成明确的赋值形式例如beta观测方程中的观测矩阵是滞后项矩阵Xalpha观测方程中的观测矩阵是x_i类似的结构h观测方程中的观测量则是标准化残差平方的对数形式。for iter 1:niter % 采样 beta条件是当前 alpha 和 h [beta_draw, beta_hist] simulate_beta(Y, X, beta_draw, alpha_draw, h_draw, prior_beta, sa2); % 采样 alpha条件是更新后的 beta 和当前 h [alpha_draw, alpha_hist] simulate_alpha(Y, X, beta_draw, alpha_draw, h_draw, prior_alpha, sa2); % 采样 h条件是更新后的 beta 和 alpha [h_draw, h_hist] simulate_h(Y, X, beta_draw, alpha_draw, h_draw, prior_h, sa2); % 记录本次迭代的样本 beta_record(:,:,iter) beta_draw; alpha_record(:,:,iter) alpha_draw; h_record(:,:,iter) h_draw; end这段代码的结构很典型。simulate_beta、simulate_alpha、simulate_h三个函数分别完成各自的卡尔曼滤波与平滑采样返回值是更新后的状态路径。每次迭代的记录数组保存的是后验样本后续所有统计量如均值、置信区间都来自这些记录。注意这里的h_draw在采样时就以对数形式存在不用额外处理。参数说明niter为总迭代次数prior_beta、prior_alpha、prior_h是各自先验的均值和协方差sa2以向量形式传入分别对应三个状态块。实际使用中如果三个函数的输出不稳定优先检查状态方程的初始协方差是否过大或者数据是否包含缺失值。缺失值会直接导致卡尔曼滤波崩溃这一点在数据准备阶段必须避免不能让矩阵中出现NaN。3.4 燃烧期输出如何判断MCMC是否已经收敛燃烧期的长度设置要结合图形诊断法画出β路径的后验均值随时间的变化图如果前几百次迭代存在明显漂移说明燃烧期不够长需要补足。另一个简单判断是看每轮迭代得到的对数后验密度它应该在一个水平线附近小幅波动出现持续上升或下降趋势说明尚未收敛。如果发现sa2取0.01时参数路径平滑得几乎没有波动可以适当调大sa2反之如果路径像锯齿一样高频率抖动则是sa2过大。这个调参过程不需要很精确关键是找到合理范围。另外MCMC的随机种子在代码中往往被固定方便结果复现如果你跑出来的结果和别人不同先检查随机种子是否一致这不算代码错误只是随机性差异。4. 增加时间标签把横轴从“第几期”变成真实日期4.1 为什么要加时间标签论文图表和结果解读都需要具体年份原始代码跑出来的脉冲响应图横轴通常显示的是t1, 2, 3这样的序号看起来既没有场景感也不能直接放进论文。更关键的是你在分析结构性变化时必须知道某个响应发生在哪一年才能和实际政策事件对应起来。比如在某一年某个货币政策工具出现调整对应时刻的脉冲响应明显变大这个发现的前提就是图上坐标轴标注的是年份。时间标签的添加并不改变模型本身它只是把绘图时的横轴数据从period序号替换成date序列。但有一点容易搞错如果数据有滞后阶数模型内部的样本观测数等于T-lag绘图时时间序列也要从第lag1个日期开始两者必须对齐。很多人在画图后发现曲线位置整体偏移了几期就是这个对齐没做对。4.2 MATLAB中时间标签的生成与对齐datetime与datenum的选择MATLAB里有两套时间处理方式一是datenum数值型日期二是datetime对象型日期。在绘图时我更推荐用datetime因为坐标轴自动展示年月信息不需要手动设置刻度标签。但注意如果后续需要做三维图需要先将datetime转换为datenum才能用于网格插值函数因为很多三维绘图函数不接受datetime作为网格坐标。生成时间序列的步骤是先根据数据的起始日期和频率生成完整日期向量然后截取从lag1开始到末尾的部分。如果数据是月度频率起始日期为1990年1月那么日期向量可以通过datetime(1990,1,1)加上calmonths(0:T-1)生成。季度和年度频率分别使用calquarters和calyears。% 假设数据起始日期为1990年1月月度频率 start_date datetime(1990, 1, 1); all_dates start_date calmonths(0:T-1); % 生成完整日期序列 use_dates all_dates(lag1:end); % 对齐模型观测区间 % 画二维脉冲响应时直接用 datetime 作为 X 轴 plot(use_dates, irf_series(:, j, k)); datetick(x, yyyy-mm, keepticks); % 如果坐标轴显示不友好可手动调整这段代码先构造完整日期向量再按滞后阶数对齐截取。关键点是lag1模型丢弃了前lag期作为滞后项所以绘图区间也必须是这个截断后的区间。datetick的yyyy-mm格式适合月度数据如果是季度数据可以用yyyy-qq。如果你用的是旧版MATLAB或者不习惯datetime可以用datenum完成同样的工作。绘图后通过set(gca, XTick, ...)和set(gca, XTickLabel, ...)手动指定刻度。两种方式结果一样只是工作量不同。时间标签的添加不会影响后验均值计算纯粹是出图层面的改进但它对论文可读性的提升非常直接。4.3 时间标签在三维脉冲响应图里的坐标映射三维图的时间轴和二维图略有不同。二维图通常固定冲击变量和响应变量只留一个时间轴三维图则要把时间放在一个维度上脉冲响应期数放在另一个维度响应值作为第三维。因此三维图的横轴有两个一个是响应期数0,1,2,...,H一个是时间具体年份或月份。对于三维绘图MATLAB的surf函数要求网格坐标必须是单调递增的数值矩阵不能直接用datetime。所以做法是先把use_dates转换为datenum数值构建和响应值矩阵相同尺寸的网格然后再调用surf。如果你希望坐标轴显示的是年份可以使用datetick(y,yyyy)把Y轴从datenum数值转回日期标签或者直接手动设置刻度标签。5. 三维脉冲响应图的绘制从surf到自定义视角与配色5.1 三维脉冲响应图能表达什么时间、响应期数和响应值的立体视角二维脉冲响应图只能显示一个固定时间点或固定响应期数的曲线要展示全样本时间段内同一冲击对某变量的完整响应路径需要同时画很多条二维曲线图面很快就变成一团乱麻。三维脉冲响应图把“时间”作为第二维度能很直观地展示响应强度在时间维度上的变化。比如货币政策冲击对通胀的响应在早期较弱、在中期增强、近期又减弱这种时序特征在三维图里表现为沿时间轴方向的一个“波峰带”。同一时期内的响应形态是标准的脉冲形状。正因为能同时展示两方面信息三维图是TVP-VAR结果展示的标配输出之一很多论文的在线附录都包含此类图。5.2 surf命令在TVP-VAR输出中的典型用法网格矩阵构造绘制前必须构造三个矩阵时间轴矩阵用datenum响应期数矩阵直接用0到H的整数响应值矩阵。其中前两个要用meshgrid生成尺寸必须与响应值矩阵一致。响应值矩阵取自后验均值结果中某一冲击变量对某一响应变量的全部时期和全部响应期。响应值矩阵的尺寸通常是(H1) ×样本期数注意这里的样本期数已经是从lag1到T。构造好三个矩阵后调用surf(X_time, Y_horizon, Z_response)即可。然后设置X轴的日期格式调整视角和配色得到可发布的图。% H 是最大响应期数nperiod 是样本期数 [h_grid, t_grid] meshgrid(0:H, 1:nperiod); % 响应期数和时间序号网格 dates_num datenum(use_dates); % 转换为数值日期 dates_grid repmat(dates_num, H1, 1); % 时间维度网格矩阵 % 注意矩阵尺寸dates_grid 应为 nperiod x (H1) % Z_response 为 nperiod x (H1)存储后验均值响应值 idx_irf squeeze(mean(irf_record(:, shock_idx, resp_idx, :), 4)); Z_response idx_irf; % 绘制三维图 figure; surf(dates_grid, h_grid, Z_response, EdgeColor, none); datetick(x, yyyy, keepticks); % X轴显示年份 xlabel(时间); ylabel(响应期数); zlabel(脉冲响应值); colormap(jet); colorbar; view(40, 30); % 自定义视角方位角40度仰角30度这段代码中meshgrid生成的h_grid和t_grid默认顺序要留意MATLAB的meshgrid和surf接受两个输入向量输出矩阵维度为length(第二个参数) × length(第一个参数)。为了避免维度错位上面代码里用repmat手动构造dates_grid这是一种稳妥的做法。Z_response的尺寸必须是nperiod × (H1)如果反了绘图结果会扭曲。参数说明shock_idx和resp_idx分别是要展示的冲击变量和响应变量的列序号。irf_record是保存所有MCMC样本脉冲响应的数组最后一维是迭代次数。squeeze和mean的配合用于计算后验均值也就是把迭代维度平均掉。如果你还想画置信区间可以从irf_record对应维度的5%和95%分位数再生成两个曲面叠加在半透明模式上。5.3 三维图的美化隐藏网格线、调整透明度、坐标轴刻度默认surf图带黑色网格线对论文输出来说太杂乱。通常设置EdgeColor为none配色选择jet或parula。对于需要打印黑白稿的场景建议改用冷色系渐变的colormap否则灰度转化后层次容易丢失。colorbar标签单独命名比如“脉冲响应值”。视角调整使用view函数方位角和仰角需要根据数据特征做一些微调。一般推荐view(40, 30)能同时看到时间轴和响应期数轴的变化如果波形被前面的曲面遮挡可以增大仰角到40度或45度。另外曲面透明度alpha设为0.9左右避免遮挡信息太多。时间轴的刻度密度需要根据样本期长度调整如果是20年的月度数据每年一个刻度比较合适如果样本很短则可以每季度或每半年一个刻度。三维图帮你发现的一个典型现象是某一段时间的脉冲响应形状和其余时间显著不同这种形状变化往往对应特定事件。如果三维图的峰值出现在某一时段你可以结合二维图再进一步确认甚至可以去做子样本分析。绘图不是终点而是帮你找出“什么时候变了”的手段。6. sa2参数详解状态方差缩放先验的调参逻辑与常见误区6.1 sa2在程序中的位置初始协方差矩阵的乘法因子只看代码调用过程sa2并不直接出现在状态方程中而是通过一行的乘法操作参与了初始协方差矩阵的构造。具体形式常见于这类代码% 状态方程先验扰动方差 sigma_beta eye(n_beta) * sa2(1); sigma_alpha eye(n_alpha) * sa2(2); sigma_h eye(n_h) * sa2(3);也就是说sa2的三个分量分别控制三个状态块的先验扰动方差大小。它定义的是“初始”方差在卡尔曼滤波的递推过程中协方差矩阵会随观测数据不断更新但初始值会影响早期迭代路径也会影响MCMC采样的收敛速度。由于状态方程是随机的sa2的选取可以理解为对状态变量波动程度的先验判断。如果数据本身波动较大而sa2设定很小状态变量会被“锁”在初始值附近导致估计结果近似常数VAR失去TVP的意义。反之如果sa2设定很大即使数据生成过程接近常数估计出的参数路径也会出现虚假的剧烈波动。所以sa2不是某个标准答案而是一个需要和数据特征匹配的量级选择。6.2 sa2取值空间与数据频率的关系月度、季度、年度各自怎么设不同频率下状态变量的自然波动幅度不一样。低频数据年度由于每期之间间隔较长真实参数变化可能在每一期中表现出更大的变化比例因此sa2通常可以设大一点月度数据每期间隔短参数变化相对平滑sa2就需要设小一些。常见的入门设定方案是月度数据sa2取0.01季度数据取0.05到0.1年度数据取0.1到0.5。实际运行中你很难一次性找到合适的sa2。常见经验做法是先以一个初始值跑一轮观察参数路径的波动幅度和后验分布再按比例调整。比如若参数路径太直尝试把sa2乘以10若路径毛刺明显乘以0.1。这种调试过程就像调整一个缩放旋钮快速、可预期。如果你的研究有先验文献参考可以直接用同类文献的设定然后再进行小范围调整。6.3 sa2和模型自由度为什么不能为了平滑而随意缩小sa2缩小会把参数路径逼向常数这会带来一个直观问题MCMC采样出的参数方差被低估。你会得到一张“看起来非常平滑”的图但平滑并不等于正确。当sa2过小时状态变量的后验分布实际上极其集中置信区间很窄但这是错误先验造成的假象模型不会主动去修正这种系统性偏差。所以在调sa2时不要只看参数路径是否平滑还要看后验置信区间的宽度是否合理。一个正常设定的TVP-VAR置信区间应当体现出不确定性早期样本少时区间宽后期样本多时区间窄。如果置信区间完全不变窄可能是因为状态方差被压得过小模型几乎没有利用后续数据更新状态。6.4 sa2的常见误区把sa2当成后验方差、套用默认值就跑常见误区有两个。第一个是把sa2当作后验状态方差直接用它来解释参数波动程度。这不对sa2是先验的缩放因子真正反映参数不确定性的应该是状态变量的后验标准差。第二个误区是对所有数据都用默认值不做调试。默认值只对某种特定频率和量纲的数据合适换了数据可能就完全失灵。出现这类问题时解决办法不是改sa2一个参数而是同时检查数据的标准化情况。数据量纲差异太大也会让sa2的适配变得困难比如一个变量是0到1的比例另一个变量是上万的总量sa2就很难同时适配。常见做法是先对数据做标准化或归一化处理再进行估计这能显著降低sa2调参的难度。7. 后验结果的可视化与解读从脉冲响应到置信区间7.1 后验均值图怎么读区分横轴是时间还是响应期数在TVP-VAR结果中你可能看到两种横轴完全不同的图。第一种图的横轴是时间纵轴是某时点的脉冲响应值这种图适合观察某一固定响应期数的响应随时间变化第二种图的横轴是响应期数纵轴是响应值通常画多条曲线对应不同时间点适合观察冲击传导路径的形状随时间如何变化。两种图各有用处但如果在解读时混淆结论会差之千里。在代码中输出通常以二维图为主。如果输出的是前者通常需要指定一个horizon参数如果是后者则要指定一个time参数比如第50期、第100期。三维图则同时展示两个维度解读难度稍高但信息量最大。建议先画三维图定位“何处发生变化”再用二维图做细节确认。7.2 置信区间的画法用MCMC样本的分位数构造上下边界代码里存储了每次迭代的脉冲响应结果所以置信区间可以从样本分位数直接提取。MATLAB中可以用quantile函数沿迭代维度计算5%和95%分位数。绘制二维图时可以用fill函数填充半透明带绘制三维图时可以生成两个额外的曲面并用透明度区分。% 提取某个冲击-响应组合的后验样本 irf_all squeeze(irf_record(:, shock_idx, resp_idx, :)); % (nperiod, niter) % 计算均值与5%、95%分位数 irf_mean mean(irf_all, 2); irf_lb quantile(irf_all, 0.05, 2); irf_ub quantile(irf_all, 0.95, 2); % 二维填充图示例 figure; plot(use_dates, irf_mean, LineWidth, 2); hold on; fill([use_dates; flipud(use_dates)], [irf_lb; flipud(irf_ub)], ... [0.8 0.8 0.8], EdgeColor, none, FaceAlpha, 0.5);这段代码中irf_all的维度是样本期数×迭代次数quantile的第三个参数2表示沿第二个维度计算。fill函数的用法是关键x轴统一为一个方向y轴先下界后上界翻转中间填充灰色带。FaceAlpha控制透明度0.5左右比较合适既能看清均值线又不遮挡。如果时间轴不是datetime而是数值fill同样适用。7.3 图形的保存与导出MATLAB绘图的矢量输出和格式选择论文插图建议使用矢量格式避免位图放大变模糊。MATLAB中可以直接用exportgraphics函数或print函数输出PDF、EPS。推荐优先使用exportgraphics因为它会自动裁剪白边生成的PDF图片清晰度和大小都合适。如果期刊要求位图再导出TIFF格式分辨率为300dpi或600dpi。exportgraphics(gca, irf_3d.pdf, ContentType, vector); print(gcf, irf_2d.tiff, -dtiff, -r300);这两行第一行输出矢量PDF第二行输出300dpi的TIFF。注意exportgraphics在较新的MATLAB版本中才可用如果是旧版本可以用saveas或print替代。无论哪种格式图形的字体大小、线宽、colormap都要在导出前确定否则后期修改需要重新绘图。8. TVP-VAR MATLAB代码的常见报错与避坑记录8.1 卡尔曼滤波出现矩阵奇异或NaN问题往往出在初始方差过大现象程序运行到某一步时出现矩阵奇异警告或者结果中出现大量NaN。原因状态方程的初始协方差矩阵设置过大导致卡尔曼滤波在早期迭代中协方差爆炸数值计算溢出另一种可能是数据存在缺失值或标准差为零的变量。解决把sa2缩小一个数量级再试如果数据中有恒定值变量删除该变量或先做差分检查数据矩阵中是否存在NaN若有可以用前值填充或直接剔除相应时段。8.2 三个状态采样顺序写反导致结果错乱β、α、h的依赖关系现象参数路径看起来平滑但脉冲响应值异常大或异常小图形形态完全不符合理论预期。原因采样顺序写错。如果先采样h再采样α会让h基于旧的α计算标准化残差状态之间的条件关系被破坏后验分布不是目标分布。解决严格按照β→α→h的顺序执行每次更新后立即用当前值参与下一状态的采样。代码结构上可以把三步写在一个循环体内部顺序固定不要拆成三个独立的循环。8.3 后验均值与先验几乎相同sa2缩得太小或燃烧期太长现象估计结果和初始值非常接近参数路径没有随时间变化脉冲响应图也几乎不随时间改变。原因sa2设置过小状态方程扰动方差几乎为零即使观测数据包含变化信息状态也不会更新。此外燃烧期过长也可能导致最终的样本全部来自极窄区间。解决先将sa2调到0.1级再观察路径波动如果燃烧期超过总迭代次数的一半考虑减少燃烧期或检查MCMC是否在初始值附近停滞。8.4 三维图坐标轴日期显示乱码datetime和数值混用现象三维图的日期轴显示为一串大数字datenum或者刻度标签重叠在一起。原因直接用datetime作为surf的坐标轴输入或者没有调用datetick也没有手动设置刻度。解决一律将日期转换为datenum后传给surf然后用datetick(x, yyyy)。如果坐标轴仍是数字可以手动设置XTick为特定年份的datenum值再设置XTickLabel为对应的年份字符串。这个操作看似简单但容易忽略也是三维图出问题的高频点。8.5 内存不足导致程序中断二维脉冲响应数组太庞大现象程序运行到保存后验脉冲响应时提示Out of Memory尤其是样本量较大或迭代次数较多时。原因irf_record数组维度为响应期数×变量数×变量数×迭代次数如果变量数为5响应期数为20迭代次数为20000单精度数组占用的内存依然可观若是double精度情况更严重。解决不要把每个迭代的脉冲响应都保存可以只保存每10次迭代的样本稀疏保存或者在后验均值计算中采用在线更新方式再单独保存分位数。另一种思路是先保存状态参数路径最后统一计算脉冲响应虽然耗时但内存占用小得多。如果代码支持并行分块保存到mat文件也是可行方案。9. 进阶技巧把TVP-VAR代码改造为可复用的函数包到这一步代码能跑、图能出、参数能解释剩下的事情是把代码变成可以换数据直接用的工具。常见做法是写一个主调用函数输入Y矩阵、日期序列、滞后阶数和先验设定几个关键参数输出后验估计、脉冲响应和三维图。这样既方便自己反复使用也方便分享给同行复现。函数设计的核心是把“数据处理”和“MCMC估计”分离。数据处理函数负责从原始Excel或CSV读取数据、生成日期向量、构造滞后矩阵、标准化数据MCMC估计函数只接收已经整理好的数值矩阵和结构参数不关心数据是从哪里来的。输出结果统一保存成一个结构体数组包含beta_mean、alpha_mean、h_mean、irf_mean、irf_lb、irf_ub等字段。function results tvpvar_estimate(Y, dates, lag, niter, burnin, sa2) % Y: T x K 数据矩阵 % dates: T x 1 日期向量可以是datetime或datenum % lag: 滞后阶数 % niter: MCMC总迭代次数 % burnin: 燃烧期 % sa2: 三个部分的状态方差缩放因子 % 数据预处理 [Ylag, T_eff] prepare_lag(Y, lag); use_dates dates(lag1:end); % MCMC主体 [beta_record, alpha_record, h_record] mcmc_tvpvar(... Y, Ylag, lag, niter, burnin, sa2); % 脉冲响应计算 irf_record compute_irf(beta_record, alpha_record, h_record, ...); % 汇总输出 results.irf_mean squeeze(mean(irf_record, 4)); results.irf_lb squeeze(quantile(irf_record, 0.05, 4)); results.irf_ub squeeze(quantile(irf_record, 0.95, 4)); results.use_dates use_dates; results.sa2 sa2; end上面这个函数骨架把整个流程收拢到一行调用输入原始数据、日期、滞后阶数、迭代次数和sa2输出一个包含估计结果的结构体。这个设计让代码的重用性大幅提升换数据时只需要改Y和dates两个变量不需要再动MCMC主体。参数说明prepare_lag函数生成滞后矩阵并返回有效样本期数注意它内部需要处理好截距项。mcmc_tvpvar函数是前面所有主循环的封装不接受日期变量只处理数值矩阵。compute_irf函数负责从状态路径中数值计算脉冲响应这一步是函数化的最大难点需要理解TVP-VAR脉冲响应的递推公式但没有必要自己从零推导参考公认的算法实现即可。我一般还会在函数开头加一个“参数检查”模块检查Y是否包含NaN、lag是否为正整数、dates长度是否与Y行数一致。这些检查初看起来多余但实际换数据时能省掉不少排查时间属于花费小收益大的习惯。代码写完后用一组模拟数据做全流程验证确保输出结果与直接脚本运行的结果一致然后再应用到真实数据上。最后想提一点TVP-VAR这套代码调通的关键真的不在于算法本身多复杂而在于参数设定和出图细节。sa2、时间对齐、三维图网格方向这三处是大多数人翻车的地方。我第一次跑通三维图时整整花了一个晚上发现日期轴的维度反了第二天把repmat顺序一改图立刻顺眼。希望你在这条路上走得比我顺利一些这套代码能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑