KNN 插补
一、直观理解:向“最像你的几个人”借数值
KNN 插补(K-Nearest Neighbors Imputation)的假设很生活化:若两个人在已观测特征上很相似,那么他们在缺失特征上的取值也往往接近。于是,当样本 的第 个变量缺失时,先在特征空间里找到与 最靠近的 个“邻居”,再用邻居们在变量 上的取值(平均、加权平均或众数)来填空。
好比问卷里有人没填“每周运动时长”,但他的年龄、BMI、作息与另外五个人很像,而这五个人都填了运动时长——与其填全体均值,不如借这五个人的水平来估。
与均值插补相比,KNN 插补:
- 利用了多变量局部结构,同列也可以填出不同的值;
- 更能保持一定的局部变异与条件关系;
- 但依赖距离定义,受量纲、无关变量、高维稀疏和 选择影响很大。
它仍然是一种单次确定性(或加权)填补(除非你再加随机性),一般不会像多重插补那样正确传播“估计缺失值”的不确定性到最终标准误。
二、适用场景与优缺点
| 类型 | 说明 |
|---|---|
| 适用场景 | 数值型特征为主,变量之间存在可借力的相关 / 冗余;缺失不是极端高比例;样本量和维数适中,距离还有意义;需要比全局均值更“个性化”的基线,但又不想上完整贝叶斯 / 链式方程。 |
| 核心优点 | 能使用其他变量信息;实现思路清晰;对局部非线性关系有一定适应性;连续与(经适当处理的)类别特征都可扩展。 |
| 局限与缺点 | 距离被量纲支配,必须先缩放;高维下“最近邻不近”;计算量随 增大;邻居若也大量缺失会失效; 与距离度量敏感;默认实现常难严格处理训练 / 测试隔离;对 MNAR 无特效。 |
使用 KNN 插补前通常需要完成以下检查:
- 先处理明显的错误值与常量列,避免污染距离;
- 对参与距离的变量做标准化或稳健缩放;
- 决策:标签 / 响应变量是否允许进入距离(预测任务中通常禁止,防泄漏);
- 缺失模式是否导致某些样本几乎没有可用坐标;
- 用“人为遮盖已知值再还原”的方式选择 与权重。
三、核心原理:距离、邻居与聚合
1. 在观测坐标上定义距离
设样本 在同时非缺失的变量集合 上可比较。常用平方欧氏距离的平均形式(减少“重叠维度多少不同”的不公):
其中 是缩放后的特征。若 太小,这对邻居不可靠,应设最小重叠阈值。
2. 寻找 近邻
对目标样本 ,在候选集(通常是训练集中该特征 已观测的样本)里取 最小的 个,记为 。
3. 聚合邻居的取值
连续变量常用距离倒数加权:
也可用简单算术平均()。类别变量则对邻居做多数表决,或按权重求和再取最大类。
4. 与均值插补的关系
当 且权重均匀、又不用其他条件信息时,KNN 插补退化为接近全局均值的行为。 越小越局部,也越容易过拟合噪声邻居。
5. 算法流程(训练期)
- 选定用于距离的特征集并缩放;
- 对每个缺失位置 ,在“ 已观测”的样本中找邻居;
- 聚合得到 ;
- 可选:多次迭代(用已填补值更新距离)直到稳定——迭代能用上更多信息,也可能传播早期错误。
四、关键决策 / 如何选参数
1. 怎么选
| 特点 | |
|---|---|
| 1 | 方差大,照搬单个邻居,不稳定 |
| 3–10 | 常用搜索区间 |
| 过大 | 趋向全局平均,失去 KNN 意义 |
选择方法:在训练集上随机遮盖一部分原本已知的条目,用候选 插补后算 MAE / RMSE(连续)或准确率(类别),取验证误差最小者。可与交叉验证结合。
2. 距离度量
- 全连续:标准化后的欧氏 / 曼哈顿;
- 混合类型:Gower 距离更合适,但实现更重;
- 高维:可先 PCA 到中等维再找邻居,或特征筛选后 KNN。
3. 权重
- 均匀权重:简单;
- 距离倒数 / 高斯核:更强调近邻,通常略好。
4. 缩放
必须在训练折上估均值方差(或分位数)再变换。用原始量纲时,“收入”会压倒“年龄”。
5. 训练 / 测试隔离
正确做法:邻居只从训练集里找;测试样本的缺失用训练样本作为邻居来填。若把测试点两两互当邻居,或先全表插补再划分,都会泄漏。
6. 计算预算
朴素法对每个缺失样本扫全表, 量级。 很大时需子采样邻居库、KD 树(低维)或近似最近邻。
五、MATLAB 示例
下面给出不依赖工具箱的清晰实现,便于竞赛复现;若有 Bioinformatics Toolbox,可用 knnimpute,但注意其默认面向基因矩阵(常为基因×样本)且要自行处理好转置与缩放。
rng(5);
% 四列相关特征;人为制造缺失
N = 40;
z = randn(N, 1);
Xtrue = [z, 0.8*z + 0.2*randn(N,1), -0.5*z + 0.3*randn(N,1), randn(N,1)];
X = Xtrue;
% 随机遮盖约 15% 条目
mask = rand(size(X)) < 0.15;
X(mask) = NaN;
% 训练 / 测试划分
tr = 1:30;
te = 31:40;
Xtr = X(tr, :);
Xte = X(te, :);
% ----- 仅用训练集做缩放 -----
mu = mean(Xtr, 1, "omitnan");
sg = std(Xtr, 0, 1, "omitnan");
sg(sg == 0 | isnan(sg)) = 1;
Ztr = (Xtr - mu) ./ sg;
Zte = (Xte - mu) ./ sg;
K = 5;
epsilon = 1e-8;
% 用训练集插补训练集(邻居来自训练)
Ztr_imp = knnImputeWithRef(Ztr, Ztr, K, epsilon);
% 用训练集插补测试集
Zte_imp = knnImputeWithRef(Zte, Ztr, K, epsilon);
% 还原量纲
Xtr_imp = Ztr_imp .* sg + mu;
Xte_imp = Zte_imp .* sg + mu;
disp("测试集插补前:");
disp(Xte);
disp("测试集插补后:");
disp(Xte_imp);
% ----- 遮盖实验选 K -----
Ks = [1, 3, 5, 7, 10];
mae = zeros(size(Ks));
known = ~isnan(Xtr);
for t = 1:numel(Ks)
Xtmp = Xtr;
% 在已知位置再遮盖一部分做验证
cand = find(known);
nHide = floor(0.1 * numel(cand));
hide = cand(randperm(numel(cand), nHide));
Xtmp(hide) = NaN;
Ztmp = (Xtmp - mu) ./ sg;
Zhat = knnImputeWithRef(Ztmp, Ztmp, Ks(t), epsilon);
Xhat = Zhat .* sg + mu;
mae(t) = mean(abs(Xhat(hide) - Xtr(hide)));
end
disp(table(Ks', mae', 'VariableNames', {'K', 'MAE_on_masked'}));
% ===== 本地函数:用参考集 ref 为 query 插补 =====
function Qimp = knnImputeWithRef(Q, ref, K, epsilon)
Qimp = Q;
[nQ, p] = size(Q);
nR = size(ref, 1);
for i = 1:nQ
missCols = find(isnan(Q(i, :)));
if isempty(missCols)
continue;
end
for jj = 1:numel(missCols)
j = missCols(jj);
% 候选邻居:参考集中 j 列已知
cand = find(~isnan(ref(:, j)));
if isempty(cand)
% 回退:该列参考均值
Qimp(i, j) = mean(ref(:, j), "omitnan");
continue;
end
% 计算距离(仅在双方均已知的特征上)
d = inf(numel(cand), 1);
for t = 1:numel(cand)
r = cand(t);
both = ~isnan(Q(i, :)) & ~isnan(ref(r, :));
if nnz(both) == 0
continue;
end
diff = Q(i, both) - ref(r, both);
d(t) = sqrt(mean(diff.^2));
end
% 取 K 个最近且距离有限
valid = isfinite(d);
cand = cand(valid);
d = d(valid);
if isempty(cand)
Qimp(i, j) = mean(ref(:, j), "omitnan");
continue;
end
Kuse = min(K, numel(cand));
[ds, ord] = sort(d, "ascend");
nb = cand(ord(1:Kuse));
ds = ds(1:Kuse);
w = 1 ./ (ds + epsilon);
vals = ref(nb, j);
Qimp(i, j) = sum(w .* vals) / sum(w);
end
end
end
六、如何解释结果
KNN 插补给出的 应解释为:
在已选特征度量下,与样本 最相似且该变量可用的 个训练样本的局部聚合值。
报告建议写明:
- 参与距离的变量、是否含标签;
- 缩放方法与参数来源(训练折);
- 距离定义、最小重叠维度、权重形式;
- 的选择依据(遮盖实验 / CV 曲线);
- 插补前后分布、相关结构的变化;
- 对测试集邻居库的界定;
- 下游模型指标相对均值插补、直接删除的提升;
- 失败回退策略(无邻居时用列均值等)。
不要把插补值写成“真实测量被恢复”,尤其在邻居相似度不高时,它只是条件期望的粗糙近似。
七、常见误区
1. 不标准化就算欧氏距离
收入、年龄、评分混在一起时,邻居关系基本被大尺度变量绑架。
2. 把预测目标纳入距离特征
分类 / 回归竞赛中这会造成严重泄漏,验证分数虚高。
3. 且不做稳定性检查
单个邻居若异常,缺失会被准确复制成异常。
4. 全表插补完成后再切分训练测试
测试信息通过邻居关系渗入训练填补,评估偏乐观。
5. 高维稀疏仍强行 KNN
“最近邻”可能并不相似。应先降维 / 选特征,或换模型插补。
八、小结
KNN 插补借助“相似样本取值相近”的假设,用 个近邻的聚合值填补缺失,是介于全局均值与重型模型插补之间的实用方法。
它的核心思想可以概括为:
空格不必填成全班平均数;先找到几个最像的人,再向他们借答案。
用好它的关键是:合理缩放、隔离训练邻居库、认真选 ,并在高维与高缺失时保持警惕。它能改善局部结构的利用,但依旧是单次填补,不能替代对缺失机制的讨论,也不能自动给出正确的不确定度。