AI 的瞎咧咧:0️⃣ 问题抽象(数学视角)#
你原始数据是一个样本集合:
[
X = {x_1, x_2, \dots, x_n}
]
你只能存一小部分信息
目标不是“精确复原 (X)”,而是构造一个新样本集合:
[
\tilde{X} = {\tilde{x}_1, \dots, \tilde{x}_m}
]
使得:
- 分布形态尽量接近
- 一阶 / 二阶统计量接近
- 尾部和边界不炸
1️⃣ 平均值 & 标准差(一阶 / 二阶矩)#
定义#
[
\mu = \frac{1}{n}\sum x_i
]
[
\sigma = \sqrt{\frac{1}{n}\sum (x_i - \mu)^2}
]
数学意义#
- (\mu):位置(location)
- (\sigma):尺度(scale)
但它们只刻画“中心 + 离散程度”
完全不描述分布形状
👉 所以我们没有用“正态假设直接生成”,而是只把它们当 约束条件 / 噪声尺度。
2️⃣ 分位数(Quantiles)——形状的骨架#
定义#
p 分位数 (Q(p)) 满足:
[
P(X \le Q(p)) = p
]
你存的是:
[
Q(0), Q(0.05), Q(0.25), Q(0.5), Q(0.75), Q(0.95), Q(1)
]
数学本质#
- 这是在 CDF(累积分布函数) 上打“锚点”
- 它们是 分布形状的骨架 相比矩:
| 方法 | 是否稳健 |
|---|---|
| 高阶矩 | ❌ 对离群点极敏感 |
| 分位数 | ✅ 稳定、鲁棒 |
3️⃣ 逆变换采样(Inverse Transform Sampling)#
这是整个还原过程的核心数学原理。
理论基础#
设:
- (U \sim \text{Uniform}(0,1))
- (F(x)) 是原分布的 CDF
则:
[
X = F^{-1}(U)
] 服从分布 (F)。
在你这个问题里的变形#
你不知道完整的 (F),
但你知道 若干个点上的 (F^{-1}(p)):
p : 0.0 0.05 0.25 0.50 0.75 0.95 1.0
x : min p05 p25 p50 p75 p95 max于是你构造一个 分段线性的 (\tilde{F}^{-1}):
[
\tilde{F}^{-1}(p) =
\text{linear interpolation between known quantiles}
]
这就是:
np.interp(u, probs, quantiles)数学意义#
- 你在 概率空间均匀采样
- 再映射回 数值空间
- 保证每个分位区间占对的比例
👉 分布形状被保留下来
4️⃣ 线性插值(Piecewise Linear Approximation)#
数学形式#
对于 (p \in [p_i, p_{i+1}]):
[#
\tilde{F}^{-1}(p)
x_i + (x_{i+1}-x_i)\frac{p - p_i}{p_{i+1}-p_i}
]
这是对真实逆 CDF 的一阶近似。
为什么不用更高阶插值?#
| 方法 | 问题 |
|---|---|
| 样条 | 会过冲(overshoot) |
| 多项式 | 不单调 |
| 线性 | 单调、稳定、可解释 |
👉 工程里:宁可钝一点,也不炸
5️⃣ 加噪声(正则化思想)#
插值本身的问题:
会产生“硬折线分布”,点都落在几条线上 于是我们加一个 小扰动项: [
\tilde{x}’ = \tilde{x} + \varepsilon
] [
\varepsilon \sim \mathcal{N}(0, \alpha\sigma)
] 其中: [
\alpha \ll 1 \quad (\text{如 } 0.02)
]
数学解释#
- 相当于对估计分布做 核平滑
- 类似 KDE 中的 bandwidth 但我们:
- 不需要存 kernel
- 不需要存全样本
6️⃣ 边界裁剪(Support Constraint)#
你存了:
[
x_{\min}, x_{\max}
]
于是强制:
[
\tilde{x} \in [x_{\min}, x_{\max}]
]
数学意义#
- 限制分布的 support
- 防止噪声破坏物理 / 业务边界
7️⃣ 整体等价于什么(高层视角)#
你做的事情 ≈
用有限分位点构造经验分布函数(EDF)的近似 再从这个近似 EDF 中采样。 这在统计学里是完全正统的做法。
8️⃣ 为什么它是“接近最优”的#
| 条件 | 满足 |
|---|---|
| 存储极小 | ✅ |
| 不依赖分布假设 | ✅ |
| 鲁棒 | ✅ |
| 可复现 | ✅ |
| 不产生非法值 | ✅ |
Show the Code#
def estimate_precision(arr, eps=1e-12):
"""
估计数据精度(最小步长)
"""
arr = np.asarray(arr, dtype=float)
uniq = np.unique(arr)
if uniq.size < 2:
return 0.0 # 常数数组
diffs = np.diff(np.sort(uniq))
diffs = diffs[diffs > eps]
if diffs.size == 0:
return 0.0
# 用中位数,避免异常
return float(np.median(diffs))
def summarize_array(arr):
arr = np.asarray(arr, dtype=float)
if arr.size == 0:
raise ValueError("array is empty")
precision = estimate_precision(arr)
summary = {
"n": int(arr.size),
"mean": float(arr.mean()),
"std": float(arr.std(ddof=0)),
"min": float(arr.min()),
"max": float(arr.max()),
"p05": float(np.percentile(arr, 5)),
"p25": float(np.percentile(arr, 25)),
"p50": float(np.percentile(arr, 50)),
"p75": float(np.percentile(arr, 75)),
"p95": float(np.percentile(arr, 95)),
"precision": precision,
}
return summary
def restore_array(summary, size=None, random_state=None):
rng = np.random.default_rng(random_state)
if size is None:
size = summary["n"]
quantiles = np.array([
summary["min"],
summary["p05"],
summary["p25"],
summary["p50"],
summary["p75"],
summary["p95"],
summary["max"],
])
probs = np.array([0.0, 0.05, 0.25, 0.5, 0.75, 0.95, 1.0])
u = rng.uniform(0.0, 1.0, size)
restored = np.interp(u, probs, quantiles)
# 加轻噪声
noise_scale = summary["std"] * 0.02
restored += rng.normal(0, noise_scale, size)
# 裁剪边界
restored = np.clip(restored, summary["min"], summary["max"])
# ★ 精度量化(关键)
precision = summary.get("precision", 0.0)
if precision > 0:
restored = np.round(restored / precision) * precision
return restored