You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用神经网络同时预测期望值与方差?求损失函数及TensorFlow实现

异方差回归:双输出神经网络预测均值与输入依赖方差的完整指南

你描述的场景是典型的异方差回归问题——也就是噪声的方差随输入变化的回归任务,刚好我有过相关的实践经验,下面逐个解答你的疑问:


1. 如何用神经网络同时预测期望值与方差?

核心思路是让神经网络学习两个输入依赖的函数:

  • $\mu(x)$:对应你说的确定性函数部分(输出的均值)
  • $\sigma^2(x)$:对应依赖输入的噪声方差

网络结构上,推荐采用共享特征提取层+双输出头的设计:

  • 前几层隐藏层作为共享特征提取器,负责从输入$x$中提取通用的特征表示,所有输入样本共享这部分计算,提升效率;
  • 最后分支为两个独立的输出层:
    • 一个输出$\mu(x)$:无需额外约束,因为均值可以是任意实数;
    • 另一个输出$\log(\sigma2(x))$:用对数输出是为了确保后续转换后的方差非负(直接输出方差可能得到负数,不符合统计意义),之后通过指数运算$\sigma2(x) = \exp(\log_var)$得到合法的方差值。

2. 训练这类双输出网络的损失函数

必须使用**负对数似然(Negative Log-Likelihood, NLL)**作为损失函数,这是由你的数据生成逻辑推导出来的最优损失。

根据你的数据模型:
$$y = \mu(x) + \epsilon, \quad \epsilon \sim \mathcal{N}(0, \sigma^2(x))$$
样本$y$的似然函数是高斯分布:
$$p(y|x) = \frac{1}{\sqrt{2\pi\sigma^2(x)}} \exp\left(-\frac{(y - \mu(x))2}{2\sigma2(x)}\right)$$
取负对数后得到的损失公式为:
$$\mathcal{L} = \frac{1}{2} \log(\sigma^2(x)) + \frac{1}{2} \frac{(y - \mu(x))2}{\sigma2(x)}$$

这个损失的两个部分各有作用:

  • 第一项$\frac{1}{2} \log(\sigma^2(x))$:惩罚过大的方差,避免模型“偷懒”用大方差掩盖拟合误差;
  • 第二项$\frac{1}{2} \frac{(y - \mu(x))2}{\sigma2(x)}$:是被方差归一化后的均方误差,让模型更关注那些方差小(数据更可靠)的样本。

3. TensorFlow实现示例

下面是完整的可运行代码,包含模拟数据生成、模型构建、自定义损失、训练和结果可视化:

import tensorflow as tf
from tensorflow.keras import layers, Model
import numpy as np
import matplotlib.pyplot as plt

# ---------------------- 1. 生成符合要求的模拟数据 ----------------------
np.random.seed(42)
x = np.linspace(-3, 3, 1000).reshape(-1, 1)  # 输入特征
mu_true = np.sin(x)  # 真实的确定性函数(均值)
sigma_sq_true = 0.1 + 0.2 * (x + 2)**2  # 真实的输入依赖方差
epsilon = np.random.normal(0, np.sqrt(sigma_sq_true), size=x.shape)
y = mu_true + epsilon  # 最终观测值

# ---------------------- 2. 构建双输出神经网络 ----------------------
inputs = layers.Input(shape=(1,))
# 共享特征提取层
hidden = layers.Dense(64, activation='relu')(inputs)
hidden = layers.Dense(64, activation='relu')(hidden)
# 输出头1:预测均值mu
mu_output = layers.Dense(1, name='mu')(hidden)
# 输出头2:预测对数方差log_var(确保方差非负)
log_var_output = layers.Dense(1, name='log_var')(hidden)

# 定义模型
model = Model(inputs=inputs, outputs=[mu_output, log_var_output])

# ---------------------- 3. 自定义负对数似然损失函数 ----------------------
def negative_log_likelihood(y_true, y_pred):
    # 将双输出张量拆分为均值和对数方差
    mu_pred, log_var_pred = y_pred[:, 0:1], y_pred[:, 1:2]
    # 转换为方差(确保非负)
    sigma_sq_pred = tf.exp(log_var_pred)
    # 计算NLL损失
    loss = 0.5 * log_var_pred + 0.5 * tf.square(y_true - mu_pred) / sigma_sq_pred
    # 返回所有样本的平均损失
    return tf.reduce_mean(loss)

# 编译模型:将两个输出合并为一个张量,方便损失计算
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
              loss=negative_log_likelihood)

# ---------------------- 4. 训练模型 ----------------------
# 训练时将模型的两个输出合并为一个张量
combined_output = tf.concat([mu_output, log_var_output], axis=1)
history = model.fit(x, y, epochs=200, batch_size=32, verbose=1)

# ---------------------- 5. 预测与结果可视化 ----------------------
mu_pred, log_var_pred = model.predict(x)
sigma_sq_pred = np.exp(log_var_pred)
sigma_pred = np.sqrt(sigma_sq_pred)

plt.figure(figsize=(12, 6))

# 绘制均值拟合与置信区间
plt.subplot(1, 2, 1)
plt.scatter(x, y, alpha=0.3, label='观测样本')
plt.plot(x, mu_true, 'r--', linewidth=2, label='真实均值')
plt.plot(x, mu_pred, 'b-', linewidth=2, label='预测均值')
plt.fill_between(x.flatten(), 
                 mu_pred.flatten() - sigma_pred.flatten(),
                 mu_pred.flatten() + sigma_pred.flatten(),
                 color='blue', alpha=0.2, label='预测标准差区间')
plt.xlabel('输入x')
plt.ylabel('输出y')
plt.title('均值拟合结果')
plt.legend()

# 绘制方差拟合结果
plt.subplot(1, 2, 2)
plt.plot(x, sigma_sq_true, 'r--', linewidth=2, label='真实方差')
plt.plot(x, sigma_sq_pred, 'b-', linewidth=2, label='预测方差')
plt.xlabel('输入x')
plt.ylabel('方差')
plt.title('方差拟合结果')
plt.legend()

plt.tight_layout()
plt.show()

关于损失函数中处理单个输出的说明

在上面的代码中,我们通过切片操作y_pred[:, 0:1]和y_pred[:, 1:2]将模型的双输出张量拆分为均值和对数方差两个独立的张量,再分别代入损失公式计算。

如果你更倾向于用多输出的字典形式(比如模型输出是{'mu': mu_output, 'log_var': log_var_output}),也可以这样定义损失函数:

def nll_loss_dict(y_true, y_pred_dict):
    mu_pred = y_pred_dict['mu']
    log_var_pred = y_pred_dict['log_var']
    sigma_sq_pred = tf.exp(log_var_pred)
    loss = 0.5 * log_var_pred + 0.5 * tf.square(y_true - mu_pred) / sigma_sq_pred
    return tf.reduce_mean(loss)

编译时只需指定loss={'mu': nll_loss_dict, 'log_var': None}(不过实际中合并输出的方式更简洁)。


内容的提问来源于stack exchange,提问作者Dr. Hans-Peter Störrr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:46:36