AI 驱动的结构估计:把"解模型"变成一次求解
NBER WP 35283(Duarte & Fonseca, 2026)提出"AI for Structural Estimation":用神经网络逼近值函数与"参数→矩"映射、把均衡价格当作伪参数、用自动微分替代数值优化,把动态一般均衡结构估计的时间从数天压缩到约 20 分钟。本页讲透其三大构件、四层贡献、动态融资–投资应用,并给出三段可实跑的 PyTorch 代码。
本页属于求解/估计方法层:讲 AI 技术如何加速与求解各类结构模型的估计。它可与任一模型分支(BLP / 动态博弈 / 生命周期 / 投资–融资等)配合使用——换用哪一支模型,只需替换内层"解模型"的网络,外层矩条件与识别框架不变;它本身不是一种独立的模型类型。
01 动机与瓶颈:为什么传统结构估计要跑 4 天
结构估计的经典范式是双层嵌套(见 05 动态结构 与 13 SMM):
这种嵌套有两个致命瓶颈:
- 单次估计极慢。在 Duarte & Fonseca (2026) 的动态杠杆–投资模型里,传统做法(外层模拟退火 + 内层每次 VFI)跑了 4 天(20 小时优化)仍未达到他们方法在约 20 分钟达到的损失水平;论文报告其方法在约 13 分钟即达到传统方法 20 小时的目标损失。
- 维数灾难(curse of dimensionality)。VFI 在张量积网格上求解,状态空间大小随状态维数指数增长。状态是 $(k,b,z)$ 三维、每维 $n$ 个点,网格就是 $n^3$;再把参数 $\theta$ 也放进去做全局求解,传统网格根本无法承受。神经网络的优势正是参数量随维度线性增长、对维度近似不敏感。
传统流程把"解模型"当成了外层优化器的黑箱子例程,于是每试一个参数都要付出一次完整求解的代价。AI 方法的思路是反过来:先花一次性代价把"解"本身变成参数的函数(神经网络),之后求值、求矩、求梯度都变成毫秒级的前向/反向传播。
02 构件一:虚拟参数法(pseudo-parameter approach)
传统做法在一般均衡里多一层"找价格":给定参数 $\theta$,先解个体决策,再迭代价格直到市场出清。虚拟参数法的关键洞察是——干脆把均衡价格 $p$ 也当作一个"伪参数",把市场出清写成一条矩条件,和结构参数放在一起估计。
2.1 扩展状态空间
把"模型参数 $\theta$"与"均衡价格 $p$"都当作状态(state)的一部分,一次性训练一个神经网络逼近"对任意 $(\theta,p)$ 的值函数":
变量含义:$s$ 为原始经济状态(如资本 $k$、债务 $b$、生产率 $z$);$\theta$ 为待估结构参数;$p$ 为均衡价格(论文中一般均衡版由工资出清劳动市场)。设定理由:传统 VFI 把 $\theta$、$p$ 当常数,每换一组就要重解;这里把它们升维成网络输入,"一次求解"就得到了对所有 $(\theta,p)$ 的解函数。经济直觉:相当于把"解模型"从一个关于 $s$ 的问题,推广成关于 $(s,\theta,p)$ 的曲面拟合问题。
2.2 市场出清 = 矩条件
在扩展状态空间上,市场出清不再是内循环求根,而是一条零失衡矩条件:
变量含义:$h(\theta,p)$ 为市场出清失衡量(供需之差)。设定理由:把"价格使得 $h=0$"从求根问题改写成"估计时让矩 $h$ 趋近 0",于是一般均衡和部分均衡用同一套矩框架估计。经济直觉:均衡价格不再需要单独求解,它和结构参数一样,由"让模型矩贴数据矩"这一目标顺带决定——市场出清被内嵌进了估计。
论文原话:"把价格加入待估参数、把市场出清失衡量加入矩条件,就能在估计过程中顺带求出均衡价格"。这使得一般均衡模型变得和部分均衡模型一样好估计,不再需要在每次外层迭代里单独做价格求根。
03 构件二:神经网络逼近"参数→矩"映射
解完一次模型后,我们已经能对任意 $(\theta,p)$ 模拟出模型矩。论文再训练一个独立的代理网络(surrogate / moment network)去逼近"参数→矩"映射 $g(\theta)$,使其成为矩条件的闭式可微表达式。
$m_j$ 为第 $j$ 个模拟矩(如平均投资率、杠杆率、投资方差);$\theta=(\rho,\sigma,\delta,\gamma_1,\gamma_0,\dots)$ 为参数向量。
3.1 网络架构
- 输入层:标准化后的参数 $\theta$(论文为 8 维,含伪参数价格时再扩维);
- 隐藏层:3 个隐藏层、每层 32 个神经元,激活函数 SiLU(论文实际配置);
- 输出层:每个矩单独一个网络(论文为 11 个矩 → 11 个网络),输出标量矩值。
$\Theta_j$ 为第 $j$ 个矩网络权重;$(\theta_i,m_{i,j})$ 为 Block 1 解出模型后采样的"参数–矩"数据对。设定理由:参数→矩映射比值函数低维、更平滑,所以矩网络比值网络小得多。经济直觉:训练好后,求任意新参数的矩只要毫秒级前向传播,且因为网络可微,能直接给出解析 Jacobian $\partial g/\partial\theta$。
3.2 预训练 / 简化子模型策略
直接在完整高维模型上采样很贵。论文用简化子模型预训练:先在更易求解的低维/简化版本上训练网络得到好初值,再迁移到完整模型继续训练——这与 BIS WP 1312《Generative Economic Modeling》"用简化子模型逼近完整模型动态"的思路一脉相承,用以规避维数灾难、加速收敛。
论文对每个矩用 $K=10$ 折交叉验证,训出 $11\times10=110$ 个网络;留出折的样本外 $R^2$ 用来监控过拟合,并把 10 个网络的估计差异当作近似不确定性。本页代码块 2 复现了 MSE 训练与 $R^2$ 报告。
04 构件三:AI 算法 / 软件 / 硬件加速
- GPU 并行:值函数训练、Monte Carlo 抽样、矩模拟都可在 GPU 上批量并行,把"逐状态循环"变成"张量批量运算"。
- 自动微分(autodiff):矩网络可微,外层估计器直接用解析梯度与 Hessian(论文用 Levenberg–Marquardt 式二阶/拟二阶方法),摆脱模拟退火式的无信息搜索。
- JAX / PyTorch 生态:论文用 JAX 同时做 GPU 值迭代与神经网络;本页教学代码用 PyTorch 复现同一思想,CPU 即可跑通。
4.1 时间对比(论文 Figure 4)
下图按论文 Figure 4 "Time to Solution: Partial Equilibrium Model" 文字示意:纵轴为 $\log_{10}$ 估计损失(越负越好),横轴为已用时间(分钟)。蓝色"Our Method"在几十分钟内快速降到目标损失并走平(虚线表示训练结束后不再下降);绿色"VFI + 模拟退火"下降极慢,跑了 20 小时仍未达到蓝色的水平。
论文在同一 Compustat 矩、同一加权矩阵、4 个随机种子平均下:传统方法跑 20 小时到达的估计损失,其方法约 13–20 分钟即达到;一般均衡版整体在 1 小时内、云成本约 2 美元完成。
05 四层贡献拆分
| 层级 | 做了什么 | 替代了什么 |
|---|---|---|
| ① 算法层 | 用神经网络 + 自动微分逼近/求解值函数与矩映射 | 传统 VFI 逐网格迭代;外层无梯度的模拟退火 |
| ② 一般均衡层 | 虚拟参数法:均衡价格进状态、市场出清当矩条件 | 每次外层迭代单独做价格求根(root-finding) |
| ③ 识别层 | 最小损失函数(minimum loss function)+ 自适应收缩(adaptive shrinkage):逐步缩小参数搜索区域,把算力集中在全局最小值附近 | 全参数空间无差别盲目搜索 |
| ④ 诊断层 | 闭环检验(closed-loop test)做全局识别诊断:用估出的参数生成目标矩、再重新估计,检验能否唯一恢复参数 | 只看局部 Hessian / 报告点估计而不查弱识别 |
先估一次 $\hat\theta$,再用 $\hat\theta$ 模拟出"干净的"目标矩,第二次估计就冲着这组矩去。若模型设定正确,第二次应能唯一恢复 $\hat\theta$;恢复不出来就说明存在弱识别(而非模型误设)。这把"识别诊断"从局部曲率推广到了全局唯一性检验。
06 应用案例:动态融资与投资框架
论文应用是 Gao, Whited & Zhang (2021) 的动态杠杆–投资模型(内生违约、现金持有、昂贵股权发行、非凸调整成本)。本页用其简化核心讲解,状态为资本 $k$、债务 $b$、生产率 $z$。
6.1 Bellman 方程
变量:$k$ 资本、$b$ 债务、$z$ 生产率;选择下期资本 $k'$ 与债务 $b'$;$\pi(k,z)$ 经营利润;$c(k',k)$ 调整成本;企业本期偿还旧债 $b$、按 $1/(1+r)$ 借入新债 $b'$;$\beta$ 贴现。经济直觉:企业在"多投资/多举债带来未来利润"与"调整成本与偿债压力"之间权衡。
$y$ 产出,$\alpha$ 资本弹性;$\gamma_0$ 为固定调整成本(投资/撤资时一次性付出,非凸);$\delta$ 折旧率;$\psi$ 二次调整成本。经济直觉:固定成本 $\gamma_0$ 导致投资"稀疏、批量"(lumpy investment),是识别投资行为的关键矩来源。
$\rho$ 生产率自相关、$\sigma$ 生产率波动率。待估参数:$\theta=(\gamma_0,\rho,\delta,\sigma)$(论文还估 $\gamma_1,\chi,c_f$ 等)。
6.2 Monte Carlo 模拟验证:True vs Fitted Moments
用代码块在真实参数 $\theta^*=(0.02,0.90,0.08,0.12)$ 下生成数据矩,再训练矩网络后比对。本页代码实跑结果(CPU,seed=0):
| 矩 | 含义 | 数据矩(真实参数) | 矩网络拟合 $R^2$ |
|---|---|---|---|
| $\mathbb{E}[I/K]$ | 平均投资率 | 0.082 | 0.995 |
| $\mathbb{E}[B/K]$ | 平均杠杆率 | 0.084 | 0.768 |
| $\mathrm{sd}(I/K)$ | 投资率波动 | 0.007 | 0.760 |
平均投资率 $R^2=0.995$ 说明该矩对参数变化高度敏感、易识别;杠杆率与投资波动的 $R^2\approx0.76$,说明它们对参数映射较平缓、存在弱识别——这正是下面"损失函数曲线诊断"要揭示的东西。
6.3 参数识别诊断:损失函数曲线形态
论文的最小损失函数(minimum loss function)做法:把某一个参数固定在网格点上,其余参数自由优化,画出"最佳可达损失 vs 该参数"的曲线。
- 可识别参数(如 $\gamma_0$、$\delta$):损失曲线呈尖锐 V 形/唯一低谷——偏离真实值哪怕一点,拟合都显著变差,说明数据强约束该参数。
- 弱识别参数:损失曲线平坦 / 多峰 / 无明显谷——在一大段参数范围内都能拟合得差不多,点估计不可信,需加约束、改矩或报告识别不充分。
代码块 3 实跑:$\delta$ 估计 0.078(真 0.08)、$\rho$ 估计 0.909(真 0.90)恢复良好;而 $\gamma_0$、$\sigma$ 偏离较大——因为它们主要靠投资波动/杠杆这些 $R^2$ 较低的矩识别。这正是"先做识别诊断、再报告估计值"的现实理由。
07 完整 PyTorch 代码(三段,已实跑)
下面三段代码用模拟数据、torch.manual_seed(0),CPU 即可运行。它们依次对应:① 扩展状态空间的神经网络值函数求解;② 市场出清矩 + 参数→矩映射网络;③ 外层参数估计循环(通过可微矩网络做梯度下降)。
7.1 代码块一:神经网络近似值函数求解 Bellman
import torch, torch.nn as nn, numpy as np
torch.manual_seed(0); np.random.seed(0)
ALPHA, BETA_DISC, R, PSI = 0.65, 0.96, 0.04, 1.0
NK, NB, NZ = 9, 7, 6 # 候选 k' / 候选 b' / z' 蒙特卡洛抽样数
def profit(k, z): # 生产利润 π = z * k^α(z 取指数以保证为正)
return torch.exp(z) * (k ** ALPHA)
# 值函数网络:输入(k,b,z,γ0,δ,ρ,σ) 共7维 → V。参数与价格进状态=扩展状态空间
class ValueNet(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(7,64), nn.SiLU(),
nn.Linear(64,64), nn.SiLU(),
nn.Linear(64,1))
def forward(self, s): return self.net(s).squeeze(-1)
Vnet, Vtar = ValueNet(), ValueNet() # Vtar=滞后 target 网络,稳定拟合值迭代
Vtar.load_state_dict(Vnet.state_dict())
optV = torch.optim.Adam(Vnet.parameters(), lr=1e-3)
k_grid = torch.exp(torch.linspace(np.log(0.5), np.log(6.0), NK))
b_grid = torch.linspace(0.0, 3.0, NB)
def sample_state(n): # 随机采状态 (k,b,z) 与参数 (γ0,δ,ρ,σ)
return (torch.rand(n)*5+.3, torch.rand(n)*2.5, torch.randn(n)*.5,
torch.rand(n)*.04+.005, torch.rand(n)*.08+.04,
torch.rand(n)*.2+.8, torch.rand(n)*.1+.05)
def bellman_residual(n=512):
k,b,z,g,d,rho,sig = sample_state(n)
kp, bp = k_grid.view(1,-1), b_grid.view(1,-1)
inv = kp - (1-d.view(-1,1))*k.view(-1,1) # 净投资
c_adj = g.view(-1,1)*(inv.abs()>1e-6).float() + .5*PSI*inv**2 # 固定+二次调整成本
div_k = profit(k.view(-1,1), z.view(-1,1)) - c_adj - b.view(-1,1)
div = div_k.unsqueeze(2) + (bp/(1+R)).view(1,1,NB) # 现金流 (n,NK,NB)
zp = rho.view(-1,1)*z.view(-1,1) + sig.view(-1,1)*torch.randn(n,NZ) # z' AR(1)
sN = torch.zeros(n,NK,NB,NZ,7)
sN[...,0]=kp.view(1,NK,1,1); sN[...,1]=bp.view(1,1,NB,1)
sN[...,2]=zp.view(n,1,1,NZ)
sN[...,3]=g.view(-1,1,1,1); sN[...,4]=d.view(-1,1,1,1)
sN[...,5]=rho.view(-1,1,1,1); sN[...,6]=sig.view(-1,1,1,1)
with torch.no_grad(): # 用滞后网络算 Bellman 目标
Vnext = Vtar(sN.reshape(-1,7)).reshape(n,NK,NB,NZ).mean(-1)
V_target = torch.logsumexp((div + BETA_DISC*Vnext).reshape(n,-1), dim=1) # 平滑 max
V_cur = Vnet(torch.stack([k,b,z,g,d,rho,sig],1))
return ((V_cur - V_target)**2).mean()
for ep in range(400): # 训练循环:最小化 Bellman 残差
optV.zero_grad()
loss = bellman_residual(); loss.backward(); optV.step()
with torch.no_grad(): # Polyak 缓慢更新 target 网络
for p,pt in zip(Vnet.parameters(), Vtar.parameters()):
pt.mul_(0.995).add_(0.005*p)
if ep % 100 == 0: print(f"epoch {ep}: Bellman residual = {loss.item():.4f}")
# 实跑:残差 35.9 → 0.27,值函数收敛
7.2 代码块二:市场出清矩 + 训练参数→矩映射网络
@torch.no_grad()
def simulate_moments(g0, d0, r0, s0, T=30, N=16): # 用训练好的值网络模拟矩
k = torch.ones(N)*2.0; b = torch.ones(N)*0.5; z = torch.zeros(N)
inv_hist, lev_hist = [], []
for t in range(T):
kp, bp = k_grid.view(1,-1), b_grid.view(1,-1)
inv = kp - (1-d0)*k.view(-1,1)
ca = g0*(inv.abs()>1e-6).float() + .5*PSI*inv**2
div = (profit(k.view(-1,1),z.view(-1,1))-ca-b.view(-1,1)).unsqueeze(-1) \
+ (bp/(1+R)).view(1,1,NB)
zp = r0*z.view(-1,1) + s0*torch.randn(N,NZ)
sN = torch.zeros(N,NK,NB,NZ,7)
sN[...,0]=kp.view(1,NK,1,1); sN[...,1]=bp.view(1,1,NB,1)
sN[...,2]=zp.view(N,1,1,NZ)
sN[...,3]=g0; sN[...,4]=d0; sN[...,5]=r0; sN[...,6]=s0
VN = Vnet(sN.reshape(-1,7)).reshape(N,NK,NB,NZ).mean(-1)
best = (div + BETA_DISC*VN).reshape(N,-1).argmax(1) # 最优 (k',b')
kn, bn = k_grid[best//NB], b_grid[best%NB]
inv_hist.append(((kn-(1-d0)*k)/k).mean()); lev_hist.append((b/k).mean())
k,b,z = kn, bn, r0*z + s0*torch.randn(N)
return torch.tensor([torch.stack(inv_hist[10:]).mean(),
torch.stack(lev_hist[10:]).mean(),
torch.stack(inv_hist[10:]).std()])
# 伪参数示意:把均衡价格 p 当伪参数,市场出清失衡=0 作为额外矩
def market_clearing_imbalance(pseudo_price, g0,d0,r0,s0):
lev = simulate_moments(g0,d0,r0,s0)[1]
return lev - 1.0/pseudo_price # 均衡要求该失衡 = 0
# --- 采样参数设计集,构造 (参数, 矩) 数据 ---
Nd = 200
g_d,d_d,r_d,s_d = (torch.rand(Nd)*.04+.005, torch.rand(Nd)*.08+.04,
torch.rand(Nd)*.2+.8, torch.rand(Nd)*.1+.05)
M = torch.stack([simulate_moments(g_d[i],d_d[i],r_d[i],s_d[i]) for i in range(Nd)])
class MomentNet(nn.Module): # 参数→矩 代理网络(输入参数,输出矩)
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(4,32), nn.SiLU(),
nn.Linear(32,32), nn.SiLU(), nn.Linear(32,3))
def forward(self, x): return self.net(x)
mnet = MomentNet(); optM = torch.optim.Adam(mnet.parameters(), lr=1e-3)
X = torch.stack([g_d,d_d,r_d,s_d],1); Xm, Xs = X.mean(0), X.std(0)+1e-8
Mm, Ms = M.mean(0), M.std(0)+1e-8
for ep in range(800): # MSE 训练矩映射
optM.zero_grad()
loss = ((mnet((X-Xm)/Xs) - (M-Mm)/Ms)**2).mean()
loss.backward(); optM.step()
with torch.no_grad(): # 报告样本外/样本内 R^2
pred = mnet((X-Xm)/Xs)*Ms+Mm
R2 = 1 - ((M-pred)**2).sum(0)/((M-M.mean(0))**2).sum(0)
print("矩网络 R^2 =", R2.tolist()) # 实跑:[0.995, 0.768, 0.76]
7.3 代码块三:外层参数估计循环(微分替代模拟)
# 真实参数,生成"数据矩"
g_true,d_true,r_true,s_true = 0.02, 0.08, 0.90, 0.12
m_data = simulate_moments(g_true,d_true,r_true,s_true)
# 把待估参数当 leaf variable;通过可微矩网络求梯度(替代每次重新模拟)
th = torch.tensor([0.01,0.10,0.85,0.10], requires_grad=True)
optEst = torch.optim.Adam([th], lr=1e-2)
lo = torch.tensor([.005,.04,.8,.05]); hi = torch.tensor([.045,.12,1.0,.15])
W = torch.diag(1.0/(Ms**2)) # 加权矩阵 = 矩方差逆(对角近似)
for step in range(400): # 最小距离估计(SMM),梯度来自矩网络
optEst.zero_grad()
m_hat = mnet((th-Xm)/Xs)*Ms+Mm # 毫秒级前向预测矩
loss = (m_data-m_hat) @ W @ (m_data-m_hat) # 加权矩距离(可微)
loss.backward(); optEst.step() # 自动微分给出 ∂loss/∂θ
with torch.no_grad(): th.clamp_(lo,hi)
if step % 100 == 0:
print(step, round(loss.item(),4), [round(x,3) for x in th.tolist()])
# 实跑恢复:δ=0.078(真.08), ρ=0.909(真.90);γ0/σ 因弱识别偏离——见识别诊断
三段代码在 torch 2.x (CPU) + numpy 下用 seed=0 完整跑通:Bellman 残差 35.9→0.27;矩网络 $R^2=[0.995,0.768,0.76]$;外层估计收敛到 $\theta\approx(0.037,0.078,0.909,0.144)$。论文级应用需把值网络换成 JAX/GPU、扩大参数设计集、加 10 折交叉验证与 Levenberg–Marquardt 二阶优化。
08 相关文献体系
09 与现有方法的连接
AI 方法不是新的识别策略,而是求解/估计的加速层。它接在已有的结构估计框架上:
- 01 MLE:似然不可解析时,AI 加速内层模型求解;
- 02 GMM:矩条件形式不变,只是矩 $g(\theta)$ 由神经网络闭式给出、可求梯度;
- 13 SMM:本质相同——仍是最小距离估计,但把"每次重新模拟"换成"矩网络毫秒预测";
- 14 MPEC:同为"消除嵌套"的思路,MPEC 用约束优化重写,AI 法用可微代理网络重写,二者可结合;
- 05 动态结构:Bellman/VFI 是内层瓶颈,AI 法用值函数网络替代 VFI。
本方法与各模型分支是配合而非从属关系:它不规定经济结构,只负责把任意结构模型的估计算得更快。它可加速 05 动态结构、15 动态博弈、22 投资–融资动态、04 BLP 随机系数、18 生命周期消费储蓄 等模型分支的估计——选定模型分支后,把其内层"反复解模型"的环节换成这里的可微代理网络即可,外层矩条件、识别与数据构造仍由该模型分支与 01/02/06/13/14 等估计方法决定。
| 维度 | 传统 SMM(VFI + 模拟退火) | AI 驱动结构估计 |
|---|---|---|
| 计算时间 | 数天(论文案例 4 天未达) | 约 20 分钟(论文) |
| 梯度 | 无梯度,模拟退火/网格搜索 | 矩网络可微,解析 Jacobian/Hessian |
| 维数扩展性 | 张量网格指数爆炸 | 网络参数随维度线性增长 |
| 一般均衡 | 每次单独做价格求根 | 伪参数法:市场出清当矩条件 |
| 识别诊断 | 局部 Hessian,易忽略弱识别 | 最小损失函数 + 闭环检验做全局诊断 |
| 代价 | 大量 CPU 机时 | 一次性训练 + GPU,论文约 2 美元云成本 |
AI 加速不替代识别策略:模型设定、矩选择、排除约束、数据仍决定参数能否被识别;网络只是让你更快地在正确的识别问题上搜索。识别不清,GPU 只会更快地得到错误答案。
10 论文案例
11 常见错误与陷阱
矩网络只在采样的参数设计集内准确。外层优化器一旦走到设计集之外,$g(\theta)$ 就是外推、毫无意义,梯度把参数引向错误方向。务必:把参数 clamp 在设计集范围内、用 10 折交叉验证监控 $R^2$、必要时按自适应收缩逐步扩大/精修设计集。
把价格当伪参数、市场出清当矩条件,前提是一般均衡存在且通常唯一。若模型有多重均衡或不连续定价,扩展状态空间上训练出的"解函数"可能在不同均衡间跳变,市场出清矩不再唯一可解。必须先验证均衡存在唯一性。
最小损失函数平坦 / 闭环检验恢复不出参数,说明弱识别。此时报告一个精确小数点的 $\hat\theta$ 是误导。正确做法是先画最小损失曲线、做 closed-loop test,弱识别参数要么加约束、要么明确报告"不可识别"。
GPU 默认 float16/bfloat16、Monte Carlo 抽样数过小,会让值函数残差与矩噪声被"加速"放大,估计收敛但数值不可信。论文用 JAX 双精度/混合精度并控制抽样噪声;教学中也要固定 seed、报告残差与 $R^2$。
神经网络让你更快搜索,但不创造识别。矩选得不好、排除约束缺失,再多 GPU 也无法把参数识别出来。AI 是求解/估计的加速层,模型设定与矩选择仍是经济学核心。
直接在线拟合 $V\approx\mathrm{logsumexp}(r+\beta V)$ 容易正反馈发散。本页代码用 target network(Polyak 滞后更新)+ stop-gradient 稳定拟合值迭代;实战中还需合理缩放状态、用 logsumexp 平滑 max、监控 Bellman 残差下降。