flowchart TD
A[固定 h w 与目标 y] --> B[枚举八个等概率掩码]
B --> C[分别计算线性预测 z]
C --> D[平均预测 等于负三]
C --> E[每次先算平方损失]
E --> F[再取平均 得到十三]
A --> G[关闭 Dropout 得到负三]
G --> H[平方损失 四点五]
F --> I[差额来自预测方差]
H --> I
为什么非线性后面不能直接交换期望
再看一个更小的反例:h=[1,−1],w=[1,1],p=0.5。四种掩码给出的 z 为 0、−2、2、0,平均为 0;通过 ReLU 后,四个值为 0、0、2、0,平均为 0.5,而 ReLU(E[z])=0。
import itertools import json import math import random from dataclasses import dataclass
defvector(values, name): ifnotisinstance(values, (list, tuple)) ornot values: raise ValueError(name + " must be a nonempty list or tuple") out = [] for value in values: ifisinstance(value, bool) ornotisinstance(value, (int, float)): raise ValueError(name + " requires real numbers") try: item = float(value) except OverflowError as error: raise ValueError(name + " is too large") from error ifnot math.isfinite(item): raise ValueError(name + " must be finite") out.append(item) return out
defprobability(p): value = vector([p], "p")[0] ifnot0 <= value < 1: raise ValueError("require 0 <= p < 1") return value
@dataclass(frozen=True) classCache: scale: tuple
defdropout(h, p, *, training=True, mask=None, rng=None): h, p = vector(h, "h"), probability(p) iftype(training) isnotbool: raise ValueError("training must be bool") ifnot training: if mask isnotNone: raise ValueError("evaluation does not accept a mask") return h[:], Cache(tuple(1.0for _ in h)) if mask isNone: if rng isNone: rng = random.Random() ifnotisinstance(rng, random.Random): raise ValueError("rng must be random.Random") mask = [int(rng.random() >= p) for _ in h] if p else [1]*len(h) if (notisinstance(mask, (list, tuple)) orlen(mask) != len(h) orany(type(bit) isnotintor bit notin (0, 1) for bit in mask)): raise ValueError("mask must contain one integer bit per input") if p == 0andany(bit == 0for bit in mask): raise ValueError("p=0 cannot produce a dropped entry") scale = tuple(bit/(1-p) for bit in mask) out = vector([v*s for v, s inzip(h, scale)], "output") return out, Cache(scale)
defbackward(g, cache): g = vector(g, "g") ifnotisinstance(cache, Cache): raise ValueError("cache must come from dropout") scale = vector(cache.scale, "scale") iflen(g) != len(scale) orany(s < 0for s in scale): raise ValueError("invalid gradient/cache shape or scale") return vector([v*s for v, s inzip(g, scale)], "gradient")
defenumerate_head(h, w, target, p): h, w = vector(h, "h"), vector(w, "w") target, p = vector([target], "target")[0], probability(p) iflen(h) != len(w) orlen(h) > 12: raise ValueError("matching vectors with at most 12 entries required") q = 1-p mean = second = loss = 0.0 grad = [0.0]*len(h) for mask in itertools.product((0, 1), repeat=len(h)): mass = q**sum(mask) * p**(len(h)-sum(mask)) if mass == 0: continue dropped, _ = dropout(h, p, mask=mask) z = math.fsum(a*b for a, b inzip(w, dropped)) error = z-target mean += mass*z second += mass*z*z loss += mass*0.5*error*error for i, value inenumerate(dropped): grad[i] += mass*error*value deterministic = math.fsum(a*b for a, b inzip(w, h)) evaluation_loss = 0.5*(deterministic-target)*(deterministic-target) values = [mean, second, loss, deterministic, evaluation_loss, *grad] vector(values, "enumerated moments") return {"mean_prediction": mean, "variance_prediction": second-mean*mean, "mean_loss": loss, "mean_weight_gradient": grad, "evaluation_loss": evaluation_loss}