开始前,会这些就够了
- 知道均值、标准差和方差的关系
- 会读自然对数 ln
- 能区分连续数值与离散类别
- 需要补基础时:先补变量、函数与单位
- 需要补基础时:再补期望与方差
遇到不熟悉的词,先看这 2 个解释
打开完整术语小抄 ↗一个回归模型预测误差约为 0.1 米,标准差为 0.2 米。换成厘米后,误差变成 10,标准差变成 20。实际预测完全相同,但完整高斯负对数似然从 −0.565499 变为 4.039671。
这不是模型突然变差,也不必是程序出错。连续变量的密度数值依赖所用坐标单位;我们先算明白密度怎样变化,再用一个区间的概率确认,描述的随机事件并没有改变。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- gaussian_nll_units.py:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B gaussian_nll_units.py解压到独立练习文件夹,在含 gaussian_nll_units.py 的目录打开终端运行。固定一维高斯密度与单位变换算术;未训练回归器、未运行 PyTorch、未验证真实数据分布或校准。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
负对数密度可以为负
对单个连续目标 y,模型给出均值 μ 和方差 v=σ²。完整高斯 NLL 为 0.5×[ln(2π)+ln(v)+(y−μ)²/v]。这里取的是观测位置的概率密度的负对数,不是“精确等于这个数”的概率的负对数。
当 μ=0、y=0.1、σ=0.2,方差为 0.04,密度约为 1.760327 每米。密度高于 1 没有违反概率上限;概率要对一个区间累积。对应 NLL 为 −0.565499,负数本身不是错误信号。
对于连续分布,单个精确数值的概率为零;密度描述单位长度附近积累概率的速率。离散类别的概率与连续数值的密度都能进入似然,但它们的数值边界与单位含义不同。
单位变换会改变密度的数值
设新数值 y′=a×y,a>0。为了让同一段物理区间的概率不变,新密度必须满足 p′(y′)=p(y)/a。因此 NLL′=NLL+ln(a)。这个除以 a 的因子来自坐标伸缩的 Jacobian,也就是变换如何拉长单位区间。
米变厘米时 a=100,所以单个一维观测的完整 NLL 增加 ln(100)=4.605170。均值与标准差都乘 100,方差必须乘 10000;只把方差乘 100 会把分布真的改错。
程序同时算区间 [−0.1,0.1] 米与 [−10,10] 厘米的概率,两边都为 0.382925。密度数值变小、NLL 数值变大,与区间概率保持不变完全相容。
同一数据、同一维度并统一单位后,所有模型加上相同常数,所以排序不会仅因这种单位变换改变。不同单位、维度、求和/平均方式或常数项混在一起时,不能直接横向比较一个裸露的 loss 数字。
把密度、区间宽度和损失放在同一张账里
仍在 y=0.1 米附近看一小段 [0.09,0.11] 米,它和 [9,11] 厘米是同一物理范围。区间宽度从 0.02 米变成 2 厘米,数字放大了 100 倍;相应的密度从约 1.760327 每米变成 0.017603 每厘米,数字缩小为百分之一。宽度和密度一增一减,概率才保持一致。
“中点密度×区间宽度”在有限区间里只是近似,因为区间内密度还会变化。本例近似值为 0.035206533,用高斯累积概率作差得到的区间概率为 0.035195533。把单位换成厘米后,累积概率差仍为 0.035195533。所以密度大于 1,不是在说这段范围的概率超过 100%。
再把 NLL 公式拆开看:米单位下,残差平方除以方差为 0.1²/0.04=0.25;厘米单位下为 10²/400=0.25,这一项没有变。常数 ln(2π) 也没有变。变化只来自 0.5×ln(v):它增加 0.5×ln(400/0.04)=ln(100)。这就是同一维度、同一物理分布只改单位时 NLL 增加 4.605170 的来源。
这里的 Jacobian 因子可以先读成“单位刻度被拉长多少倍,就把每单位的密度除以多少倍”。它是在保持区间概率,不是模型突然更不确定。这个一维正比例例子用一个倍数就能说明;涉及多维或非线性变换时,需要重新写出相应变换规则。
接口写 var,就传方差
PyTorch GaussianNLLLoss 的 var 表示正方差。本文 σ=0.2,因此正确输入为 0.04。误把标准差 0.2 塞进方差位置,相当于把分布改宽,完整 NLL 会变成 0.139220。它仍是个正常数字,形状检查通常发现不了这类语义错误。
该接口默认 full=False,会省略 0.5×ln(2π) 常数项。本例省略后是 −1.484438,比完整值少 0.918939。固定同一任务时这不改变参数梯度,但会影响你拿数字去和其他实现对照。本文只核对文档,未调用 PyTorch。
接口还可能用 eps 对方差做数值下限处理。我们的小程序选择直接拒绝非正方差,以便暴露输入错误;这不是完整模拟框架的稳定化或自动求导行为。对齐实现时,应连同 full、reduction、eps 和输入量纲一起核对。
残差越小,并不代表方差可以随意缩到零
只看一个非零残差 r,完整 NLL 作为方差的函数在 v=r² 处有极小值。脚本对本例 r=0.1,比较 v=0.01 与其一半、两倍,核对这个局部数学关系。它不是用一条样本可靠估计真实不确定性的统计方法。
如果预测恰好命中 y=μ,残差项为零,σ 从 1 缩到 0.1、0.01 时,NLL 分别为 0.918939、−1.383647、−3.686232,继续缩小还会继续下降。一个过度灵活、逐样本控制方差的模型可能因此产生不合理的极小方差。
真实训练需要结合参数化、方差约束、独立数据和校准检查判断,而不是把训练 NLL 越负越好当成充分结论。本例没有训练神经网络,也没有比较具体的正则化方法。
把量纲与区间都打印出来
保存为 gaussian_nll_units.py,运行 python -B gaussian_nll_units.py。程序打印米和厘米两套数值,核对 NLL 差、密度伸缩、区间概率、常数项以及非正方差边界,并写入 report.json。
区间概率使用 Python 标准库 math.erf 计算高斯累积概率的差,没有依赖统计库。输出明确写出密度是每米还是每厘米,避免看见 1.760327 就把它读成 176% 概率。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Gaussian density and NLL under a declared change of measurement units."""
import json
import math
from pathlib import Path
def nll(y, mean, variance, full=True):
if variance <= 0:
raise ValueError('variance must be positive')
value = 0.5 * (math.log(variance) + (y - mean) ** 2 / variance)
return value + (0.5 * math.log(2 * math.pi) if full else 0.0)
def interval_probability(low, high, mean, sigma):
if sigma <= 0 or high < low:
raise ValueError('invalid interval or sigma')
return 0.5 * (math.erf((high-mean)/(sigma*math.sqrt(2))) - math.erf((low-mean)/(sigma*math.sqrt(2))))
def main():
y, mean, sigma = 0.1, 0.0, 0.2
factor = 100.0
meters = nll(y, mean, sigma**2)
centimeters = nll(y*factor, mean*factor, (sigma*factor)**2)
delta = centimeters - meters
assert math.isclose(delta, math.log(factor), abs_tol=1e-12)
density_m, density_cm = math.exp(-meters), math.exp(-centimeters)
assert density_m > 1
assert math.isclose(density_m/factor, density_cm, abs_tol=1e-12)
prob_m = interval_probability(-0.1,0.1,mean,sigma)
prob_cm = interval_probability(-10,10,mean*factor,sigma*factor)
assert math.isclose(prob_m, prob_cm, abs_tol=1e-12)
omitted = nll(y, mean, sigma**2, full=False)
wrong_variance = nll(y, mean, sigma)
print(f'meters: variance={sigma**2:.6f}, full NLL={meters:.6f}, density={density_m:.6f} per meter')
print(f'centimeters: variance={(sigma*factor)**2:.6f}, full NLL={centimeters:.6f}, density={density_cm:.6f} per centimeter')
print(f'NLL difference={delta:.6f}; log(100)={math.log(factor):.6f}')
print(f'interval probability: [-0.1,0.1] m={prob_m:.6f}; [-10,10] cm={prob_cm:.6f}')
print(f'constant omitted NLL={omitted:.6f}; difference={meters-omitted:.6f}')
print(f'incorrectly passing sigma as variance: NLL={wrong_variance:.6f}')
exact_fit = [{'sigma':s, 'nll':nll(0,0,s*s)} for s in [1.0,0.1,0.01]]
print('exact-fit NLL as sigma shrinks: '+', '.join(f"{r['nll']:.6f}" for r in exact_fit))
assert all(a['nll'] > b['nll'] for a,b in zip(exact_fit,exact_fit[1:]))
residual = y - mean
optimum = residual**2
assert nll(y,mean,optimum) < nll(y,mean,optimum/2)
assert nll(y,mean,optimum) < nll(y,mean,optimum*2)
try:
nll(y,mean,0)
except ValueError:
pass
else:
raise AssertionError('zero variance must fail')
report = {'meters':{'nll':meters,'density':density_m},'centimeters':{'nll':centimeters,'density':density_cm},
'nll_delta':delta,'interval_probability':prob_m,'constant_omitted':omitted,
'sigma_as_variance':wrong_variance,'exact_fit':exact_fit,
'scope':'Fixed univariate Gaussian arithmetic; no network fitting, PyTorch execution or empirical calibration.'}
Path('report.json').write_text(json.dumps(report, indent=2, allow_nan=False)+'\n', encoding='utf-8')
print('checks passed: Jacobian scaling, interval invariance, constant, variance boundary')
if __name__ == '__main__':
main()
怎样运行:在独立练习文件夹中执行 python -B gaussian_nll_units.py。2026-10-17 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。固定一维高斯密度与单位变换算术;未训练回归器、未运行 PyTorch、未验证真实数据分布或校准。
运行后对照结果
meters: variance=0.040000, full NLL=-0.565499, density=1.760327 per meter
centimeters: variance=400.000000, full NLL=4.039671, density=0.017603 per centimeter
NLL difference=4.605170; log(100)=4.605170
interval probability: [-0.1,0.1] m=0.382925; [-10,10] cm=0.382925
constant omitted NLL=-1.484438; difference=0.918939
incorrectly passing sigma as variance: NLL=0.139220
exact-fit NLL as sigma shrinks: 0.918939, -1.383647, -3.686232
checks passed: Jacobian scaling, interval invariance, constant, variance boundary比较回归概率时,保留完整约定
一个可比较的记录至少包含目标单位、输出维度、标准差还是方差、是否保留常数、每样本还是每元素归约,以及测试数据。只看损失正负,很难区分模型变化、单位变化与实现约定变化。
本次验证限于一维高斯公式与固定数字,没有学习回归器、检查真实数据是否近似高斯,也没有完成经验覆盖率或校准评估。它能解释数字为何变化,不能证明高斯就是某项任务的合适分布。
记住这些要点
- 连续密度可以高于 1,负对数密度可以小于 0。
- 米改厘米使一维完整 NLL 增加 ln100,但同一物理区间的概率不变。
- 标准差乘 a,方差必须乘 a²。
- 比较 NLL 必须对齐单位、常数项、维度与归约方式。
别把例子用过头
- 把密度值当成一个事件的概率。
- 将 sigma 直接传入名为 var 的参数。
- 不同实现一个保留常数、一个省略,却解释为模型质量差异。
- 把一个样本的最优方差当作可靠的不确定性估计。
自己试一小步
若两个目标分别使用米和秒,整体 NLL 在更换其中一个单位时该怎样变化?
- 先假设两个目标独立,逐维计算完整 NLL 再求和。
- 只把长度换成厘米,时间单位保持秒。
- 分别打印逐维项、总和与按维平均值。
- 验证同一矩形区域的联合概率保持不变。
做到这些,就算完成
- 明确独立假设,不把公式当成一般相关多元高斯。
- 分别推导并核对总和与按两维平均的变化,说明两种归约为何不同。
- 写明每个方差的单位。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- PyTorch 2.14:GaussianNLLLoss (新标签页)↗
高斯 NLL、var 输入、full 默认值与 eps 行为。
- SciPy:scipy.stats.norm (新标签页)↗
位置/尺度变换后的密度要除以 scale。
- NIST Engineering Statistics Handbook:Normal Distribution (新标签页)↗
高斯密度、累积概率与 sigma 的尺度含义。