TensorFlow自定义神经网络参数估计误差过高问题排查
问题诱因
你的代码存在几个核心错误,直接导致训练效率低、参数估计不准,和优化器本身实现无关:
- 最严重的错误:优化器被反复重置初始化。你在
train方法的每轮训练循环内部都重新创建了Adam优化器实例,Adam依赖的历史梯度动量、一阶/二阶矩估计值每次迭代都会被清零,完全无法发挥自适应优化器的优势,退化成了单步梯度下降,收敛速度极慢、更新方向不稳定。 - 高阶自动微分计算逻辑错误。
net_f中计算PDE残差所需的二阶导u_xx和一阶导u_t的计算路径没有被梯度带完整追踪:你在with GradientTape上下文块内仅计算了u和u_x,u_t、u_xx的计算放在了上下文块外,且u_x作为梯度运算的输出,其对输入x的依赖没有被梯度带正确记录,算出来的高阶导数值错误,PDE残差损失完全失真,才会出现“损失值很低但参数误差极大”的现象——低损失是网络过拟合了错误的残差信号,没有学到正确的物理方程约束。 - 训练配置不合理:一是定义了学习率衰减策略但完全没有传入优化器使用;二是训练轮次仅1000轮,对于PINN类任务(同时拟合解、满足PDE约束、反演未知参数)远远不够;三是
lambda_1初始值设为0,初始状态下对流项直接失效,前期梯度更新方向偏差大。 - 冗余代码:
predict方法中存在重复赋值的无效写法,不影响运行但不规范。
正确实现方案
核心修改点
- 将优化器初始化移到训练循环外,不要每轮迭代新建实例;如果需要对网络权重、物理参数用不同学习率,保留两个优化器的写法即可,优化器的内部状态会在多轮迭代中持续保留。
- 修正高阶自动微分的计算逻辑,保证所有一阶、二阶导数的计算都被持久化梯度带正确追踪,避免计算图断开。
- 移除无效的学习率调度代码,或者将其正确传入优化器;训练轮次提升到10000轮以上,待Adam训练收敛后可以切换L-BFGS优化器做精调,进一步降低参数误差。
- 调整物理参数初始值,避免零初始化导致的梯度失效问题。
修正后的核心代码
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt import scipy.io from scipy.interpolate import griddata from pyDOE import lhs import math as ma class PhysicsInformedNN: def __init__(self,X_n,v,layers,lb,ub): self.lb = lb self.ub = ub self.layers = layers self.dx_n = tf.convert_to_tensor(X_n[:,0:1],dtype = 'float32') self.t_n = tf.convert_to_tensor(X_n[:,1:2],dtype = 'float32') self.v_r = tf.convert_to_tensor(v,dtype = 'float32') # 调整参数初始值,避免零初始化 self.lambda_1 = tf.Variable(0.5,dtype = 'float32') self.lambda_2 = tf.Variable(-6,dtype = 'float32') self.para =[self.lambda_1,self.lambda_2] self.weights, self.biases = self.initialize_NN(layers) # 优化器移到初始化阶段创建,保留训练状态 self.optimizer_net = tf.keras.optimizers.Adam(learning_rate=1e-4) self.optimizer_para = tf.keras.optimizers.Adam(learning_rate=1e-3) def initialize_NN(self,layers): weights = [] biases = [] num_layers = len(layers) for l in range(0,num_layers-1): W = self.xavier_init(size=[layers[l], layers[l+1]]) b = tf.Variable(tf.zeros([1,layers[l+1]], dtype='float32'), dtype='float32') weights.append(W) biases.append(b) return weights, biases def xavier_init(self, size): in_dim = size[0] out_dim = size[1] xavier_stddev = np.sqrt(2/(in_dim + out_dim)) return tf.Variable(tf.random.truncated_normal([in_dim, out_dim], stddev=xavier_stddev), dtype='float32') def neural_net(self, X, weights, biases): num_layers = len(weights) + 1 H = 2.0*(X - self.lb)/(self.ub - self.lb) - 1.0 for l in range(0,num_layers-2): W = weights[l] b = biases[l] H = tf.math.tanh(tf.math.add(tf.linalg.matmul(H, W), b)) W = weights[-1] b = biases[-1] Y = tf.math.add(tf.linalg.matmul(H, W), b) return Y def net_u(self, x, t): v = self.neural_net(tf.concat([x,t],1), self.weights, self.biases) return v def net_f(self, x, t): lambda_1 = self.para[0] lambda_2 = tf.exp(self.para[1]) with tf.GradientTape(persistent=True) as tape : tape.watch(t) tape.watch(x) u = self.net_u(x,t) # 一阶梯度全部在梯度带上下文内计算,保证计算图连通 u_x = tape.gradient(u,x) u_t = tape.gradient(u,t) # 持久化梯度带在上下文外仍可计算高阶导 u_xx = tape.gradient(u_x,x) del tape f = u_t + lambda_1*u*u_x - lambda_2*u_xx return f def callback(self, loss,n): if n % 100 == 0: # 每100轮打印一次,避免输出过多 print('Loss:', loss.numpy(), ' Epoch : ', n, 'lambda1:', self.lambda_1.numpy(), 'lambda2:', tf.exp(self.lambda_2).numpy()) def train(self,epoch): for i in range(epoch): with tf.GradientTape(persistent=True) as tape : # Variable默认自动watch,无需手动添加 f_pred = self.net_f(self.dx_n, self.t_n) v_pred = self.net_u(self.dx_n, self.t_n) loss = tf.reduce_mean(tf.square(self.v_r - v_pred)) + tf.reduce_mean(tf.square(f_pred)) dw = tape.gradient(loss,self.weights) db = tape.gradient(loss,self.biases) dp = tape.gradient(loss,self.para) # 直接调用预初始化的优化器更新参数 self.optimizer_net.apply_gradients(zip(dw, self.weights)) self.optimizer_net.apply_gradients(zip(db, self.biases)) self.optimizer_para.apply_gradients(zip(dp, self.para)) del tape self.callback(loss,i) def predict(self, X_star): v_star = self.net_u(X_star[:,0:1], X_star[:,1:2]) f_star = self.net_f(X_star[:,0:1], X_star[:,1:2]) para_last = self.para return v_star, f_star, para_last if __name__ == '__main__': np.random.seed(123) nu =0.01/np.pi layers = [2, 20, 20, 20, 20, 1] N_u = 2000 data = scipy.io.loadmat('burgers_shock.mat') t = data['t'].flatten()[:,None] x = data['x'].flatten()[:,None] Exact = np.real(data['usol']).T X, T = np.meshgrid(x,t) X_star = np.hstack((X.flatten()[:,None], T.flatten()[:,None])) u_star = Exact.flatten()[:,None] lb = X_star.min(0) ub = X_star.max(0) idx = np.random.choice(X_star.shape[0], N_u, replace=False) X_u_train = X_star[idx,:] u_train = u_star[idx,:] model = PhysicsInformedNN(X_u_train, u_train, layers, lb, ub) model.train(10000) # 训练轮次提升到10000轮以上 X_star = tf.convert_to_tensor(X_star,dtype='float32') u_pred, f_pred, param = model.predict(X_star) error_lambda_1 = np.abs(param[0].numpy() - 1.0)*100 error_lambda_2 = np.abs( np.exp(param[1].numpy())- nu)/nu * 100 print('lambda1误差(%):',error_lambda_1,'lambda2误差(%):',error_lambda_2)
调优建议
如果修改后参数误差仍未降到理想水平,可以尝试:
- 增加网络层数或神经元数量,比如换成
[2,30,30,30,30,30,1]的结构 - 调整两部分损失的权重,比如给PDE残差损失乘0.1~10的权重系数,平衡数据拟合和物理约束的贡献
- Adam训练收敛后,换用L-BFGS优化器做二阶精调,PINN反演问题用L-BFGS收尾通常能把参数误差降到1%以内
内容的提问来源于stack exchange,提问作者Eeshwari ash
相关产品推荐
相关产品推荐

