
简介这份PDF是一篇面向人脸识别与降维算法研究者的参考文献聚焦半监督优化局部保持投影SSOLPP及其在有限标注样本下的特征提取思路。它从局部保持投影LPP的基向量关联性和非正交性出发梳理正交局部保持投影、无关联局部保持投影与监督优化局部保持投影的改进脉络进而说明如何通过加权平衡参数融合主成分分析使投影数据同时保留全局散布结构与监督局部结构并在YaleB、AR人脸数据集上验证有效性。压缩包内仅含1个PDF文件约542KB适合作为算法推导、对比实验与论文写作的案头资料。已有72人学习读者可借此理解半监督降维的建模动机、目标函数设计、监督与非监督信息融合策略以及人脸识别预处理中降低冗余特征、缓解标注依赖的研究路径。1. 人脸识别门禁机里的半监督优化局部保持投影从 PCA 降维失效说起人脸识别门禁机在侧光、口罩、低分辨率抓拍下直接把 100×100 灰度图拉成 10000 维向量交给分类器距离度量会被光照和噪声主导。PCA 保留的是全局方差遇到“同一个人姿态变化大、不同人光照相近”时类间可分性并不好。半监督优化局部保持投影换了个思路用少量标注样本约束类内与类间关系把大量未标注抓拍用来估计人脸流形的局部邻居结构再找一个线性投影让近邻在低维空间仍然近邻。它适合标注成本高的人脸识别项目比如员工门禁、校园考勤、离线终端也适合已有 OpenCV 人脸检测和对齐流程、想替换识别后端降维模块的团队。下面从图权重、半监督约束、特征值求解一路落到可复现代码和门禁机部署参数。2. 局部保持投影与半监督图权重把人脸识别目标写成可解特征值问题2.1 从 PCA 的全局方差到 LPP 的局部邻域保持人脸识别算法在小样本条件下最容易踩的坑是把每个像素当作独立特征然后指望分类器自己学会光照不变性。PCA 做的是全局协方差分解它让投影后的数据方差最大但方差大不等于类间可分。两个人脸图像如果都受同一侧光影响PCA 可能把光照方向当成主成分把身份差异压在后面。局部保持投影 LPP 的目标不是全局方差而是局部邻域如果两张人脸在原始空间里是近邻投影后也应该近邻。LPP 的出发点是拉普拉斯特征映射的线性近似。给定样本矩阵 (X[x_1,\dots,x_n])先构造近邻图权重 (W_{ij})再求投影向量 (w)使目标函数[ \min_w \sum_{i,j}(w^T x_i - w^T x_j)^2 W_{ij} ]尽量小。这个式子展开后是 (w^T X L X^T w)其中 (LD-W)(D_{ii}\sum_j W_{ij})。为了避免所有 (w) 缩到零加一个约束 (w^T X D X^T w1)。最终变成广义特征值问题[ X L X^T w \lambda X D X^T w ]取最小几个特征值对应的特征向量就得到投影矩阵。它保留的是局部流形结构而不是全局方差。对人脸来说局部结构往往对应同一身份在不同姿态、表情、光照下的连续变化所以 LPP 比 PCA 更贴近“近邻仍近邻”的识别需求。2.2 半监督标签改写近邻图must-link 与 cannot-link标准 LPP 是无监督的近邻图只根据像素距离连边。问题在于两张不同人的脸可能因为光照相似而互为近邻图里就会出现错误连边同一个人的两张脸也可能因为姿态差异大而不相邻。半监督优化局部保持投影要做的第一件事就是用少量标注样本修正图权重。常见做法是对标注样本中的同类样本增加边权重形成 must-link对标注样本中的异类样本降低边权重甚至置零形成 cannot-link对未标注样本先用标签传播得到软标签再根据软标签相似度补充图权重。标签传播可以写成 (F(I-\alpha S)^{-1}Y)其中 (S) 是归一化相似度矩阵(Y) 是标注矩阵未标注行全零(\alpha\in(0,1)) 控制传播强度。得到软标签 (F) 后半监督相似度可以取[ W^{semi}_{ij}\exp\left(-\frac{|f_i-f_j|^2}{2\sigma^2}\right) ]再与原图权重融合[ W^{total}W\beta W^{semi} ](\beta) 是半监督强度。(\beta0) 时退化成普通 LPP(\beta) 太大时图会被软标签主导标注错误会被放大。实际项目里我一般从 0.1 到 0.5 之间搜标注比例低于 5% 时反而不能把 (\beta) 调太高。符号含义常用范围调大后的影响(k)近邻数515图更连通但可能混入异类(t) 或 (\sigma)热核尺度0.52.0权重更平滑局部性变弱(\alpha)标签传播系数0.60.95传播更远噪声也传播(\beta)半监督权重0.10.5监督约束更强可能过拟合标注2.3 优化目标推导到广义特征值把半监督图权重代入 LPP 目标得到[ \min_w w^T X L^{total} X^T w ]约束仍为 (w^T X D^{total} X^T w1)。其中 (L^{total}D^{total}-W^{total})。如果还想显式加入类间散度可以再加一项 (-\gamma w^T X S_b X^T w)但多数人脸识别项目只用图权重融合已经够用因为 LPP 本身已经保留了局部结构。求解时不必直接做广义特征值分解可以用 (B^{-1}A) 转成普通特征值问题import numpy as np # X: n x d已中心化的人脸特征 # W: n x n半监督融合后的近邻图权重 # r: 目标维度通常 80200 D np.diag(W.sum(axis1)) L D - W A X.T L X B X.T D X # B 可能奇异用 pinv 做伪逆再取实部最小特征值 M np.linalg.pinv(B) A eigvals, eigvecs np.linalg.eig(M) idx np.argsort(eigvals.real)[:r] W_proj eigvecs[:, idx].real # 投影后的人脸特征 Z X W_proj这段代码的逻辑是先由半监督图得到 (D) 和 (L)再构造广义特征值问题的左右矩阵最后取最小 (r) 个特征值对应的向量。r不能太大人脸识别里常见取 80200太小会丢身份信息太大则把噪声也带进来。np.linalg.pinv是为了处理 (X D X^T) 奇异的情况尤其在样本数少于原始维度时经常出现。eigvecs取实部是因为数值计算可能产生极小虚部不影响投影。2.4 为什么优化后的局部保持投影更适合小样本人脸识别把几种降维方式放在同一批人脸数据上对比差异会更清楚。下面这张表不是绝对排名而是帮助判断什么时候该选半监督优化局部保持投影。方法使用标签保留结构小样本表现主要风险PCA否全局方差一般光照主导主成分LDA是类间/类内散度依赖标注量小样本类内散度奇异LPP否局部邻域较好错误近邻连边半监督优化 LPP少量局部邻域标签约束标注少时更稳(\beta) 过大过拟合人脸识别门禁机的真实标注往往只有入职时几张注册照其余都是未标注抓拍。半监督优化局部保持投影的价值就在这里用少量注册照约束类内和类间用大量未标注抓拍估计流形再把线性投影固化下来。到了识别阶段只需做一次矩阵乘法计算量远小于深度模型适合 TX510 人脸识别模块这类离线终端或 Java 离线服务。3. 用 NumPy 和 OpenCV 跑通半监督优化局部保持投影人脸识别代码3.1 数据准备OpenCV 检测、对齐、灰度归一化OpenCV 人脸识别流程里检测和对齐只占前半段但会直接决定降维质量。常见做法是先用 Haar 或 DNN 检测人脸框再根据左右眼坐标做仿射对齐最后裁成 100×100 灰度图并做直方图均衡。对齐后的图像按列拉平得到 (x_i\in\mathbb{R}^{10000})。如果原始数据里有人脸检测失败不要用整图填充直接剔除否则图权重会被背景干扰。import cv2 import numpy as np def align_face(gray, left_eye, right_eye, size(100, 100)): # 根据双眼坐标做仿射对齐减少姿态和旋转差异 dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) eyes_center ((left_eye[0] right_eye[0]) / 2, (left_eye[1] right_eye[1]) / 2) M cv2.getRotationMatrix2D(eyes_center, angle, 1.0) aligned cv2.warpAffine(gray, M, (gray.shape[1], gray.shape[0]), flagscv2.INTER_LINEAR) x, y int(eyes_center[0] - size[0] / 2), int(eyes_center[1] - size[1] / 2) face aligned[max(0, y):y size[1], max(0, x):x size[0]] face cv2.resize(face, size) face cv2.equalizeHist(face) return face.astype(np.float32).flatten() / 255.0这里align_face的参数size常用 100×100 或 80×80equalizeHist缓解光照除以 255 是为了让热核尺度 (t) 的搜索范围稳定在 0.52.0。注意对齐失败样本宁可丢弃也不要让歪脸进入近邻图否则 LPP 会把错误姿态当成身份差异。3.2 划分标注、未标注、测试与构建 kNN 图人脸识别项目里半监督设定通常按人分层每人 13 张标注注册照其余未标注抓拍再留一部分做测试。假设有 50 个人每人 20 张标注 2 张、未标注 14 张、测试 4 张那么标注样本只有 100 张未标注 700 张测试 200 张。这个比例接近门禁机真实情况。子集每人类别总样本用途标注集2100构造 must-link / cannot-link未标注集14700估计局部流形、标签传播测试集4200验证 Top-1 与 FAR/FRR构建 kNN 图时先计算样本间欧氏距离再取每个样本的 (k) 个最近邻。为了避免有向图导致权重不对称通常做对称化 (W(WW^T)/2)。权重用热核[ W_{ij}\exp\left(-\frac{|x_i-x_j|^2}{t}\right) ]from sklearn.neighbors import NearestNeighbors def build_knn_graph(X, k7, t1.0): n X.shape[0] nbrs NearestNeighbors(n_neighborsk 1).fit(X) distances, indices nbrs.kneighbors(X) W np.zeros((n, n), dtypenp.float32) for i in range(n): for j in indices[i, 1:]: W[i, j] np.exp(-distances[i, list(indices[i]).index(j)] ** 2 / t) W np.maximum(W, W.T) return Wk7是常见起点t1.0适合归一化后的灰度特征。k越大图越连通但异类错误连边也会增加。构建完基础图后再用标注样本修正同类标注样本之间把权重乘 1.52.0异类标注样本之间置 0。3.3 半监督标签传播与权重融合有了基础图就可以做标签传播。先把 (W) 按行归一化成 (S)构造标注矩阵 (Y)然后迭代求解 (F(I-\alpha S)^{-1}Y)。如果矩阵较大直接求逆不快可以用迭代式 (F^{m1}\alpha S F^m(1-\alpha)Y)迭代 3050 次即可。def label_propagation(W, y, alpha0.8, n_iter50): n W.shape[0] S W / (W.sum(axis1, keepdimsTrue) 1e-12) num_classes len(np.unique(y[y 0])) Y np.zeros((n, num_classes), dtypenp.float32) for i, label in enumerate(y): if label 0: Y[i, label] 1.0 F Y.copy() for _ in range(n_iter): F alpha * (S F) (1 - alpha) * Y return F, S def fuse_semi_graph(W, F, y, beta0.3, sigma0.5): # 用软标签相似度补充图权重再融合基础图 diff F[:, None, :] - F[None, :, :] W_semi np.exp(-(diff ** 2).sum(axis2) / (2 * sigma ** 2)) W_total W beta * W_semi # 标注异类样本强制断边标注同类样本增强 for i in range(len(y)): if y[i] 0: continue for j in range(len(y)): if y[j] 0: continue if y[i] y[j]: W_total[i, j] * 1.8 else: W_total[i, j] 0.0 W_total np.maximum(W_total, W_total.T) return W_totalalpha0.8控制传播范围越大越依赖未标注结构beta0.3是半监督图权重sigma0.5控制软标签相似度衰减。异类断边这一步很关键它能阻止不同身份在低维空间被强行拉近。但标注错误时断边会误伤所以标注集必须清洗。3.4 求解投影矩阵并用 kNN 完成人脸识别验证把半监督融合后的 (W^{total}) 代入第 2 章的广义特征值求解得到W_proj。然后对所有样本做投影Z X W_proj。识别阶段只用标注样本的投影特征作为注册库测试样本投影后用余弦距离或欧氏距离找最近邻。注意投影矩阵必须用训练阶段全部样本标注未标注估计但分类器只用标注样本。def semi_lpp_project(X, W_total, r120): D np.diag(W_total.sum(axis1)) L D - W_total A X.T L X B X.T D X M np.linalg.pinv(B) A vals, vecs np.linalg.eig(M) idx np.argsort(vals.real)[:r] return vecs[:, idx].real # 假设 X_all 是标注未标注y_all 中 -1 表示未标注 W_base build_knn_graph(X_all, k7, t1.0) F, _ label_propagation(W_base, y_all, alpha0.8, n_iter50) W_total fuse_semi_graph(W_base, F, y_all, beta0.3, sigma0.5) W_proj semi_lpp_project(X_all, W_total, r120) Z_all X_all W_proj Z_labeled Z_all[y_all 0] y_labeled y_all[y_all 0] Z_test X_test W_proj # 最近邻分类 preds [] for z in Z_test: dists np.linalg.norm(Z_labeled - z, axis1) preds.append(y_labeled[np.argmin(dists)])r120是目标维度人脸识别里可在 80200 之间搜。距离度量用欧氏距离即可若光照差异大可以改用余弦距离。到这里OpenCV 人脸识别的前半段检测对齐和后半段 LPP 降维分类就连起来了整套代码可以在 PyCharm 或 Jupyter 里直接跑。4. 半监督优化局部保持投影参数怎么设k、t、beta 与排错清单4.1 近邻数 k 的网格搜索(k) 是最直接影响图结构的参数。(k) 太小图会碎成多个连通分量未标注样本无法通过标签传播获得有效软标签(k) 太大异类样本容易被连进来局部保持变成“局部混合”。我一般用 5、7、9、11、15 做网格搜索固定 (t1.0)、(\beta0.3)看测试 Top-1 和类间平均距离。def grid_search_k(X_all, y_all, X_test, y_test, k_list): best (0, None) for k in k_list: W build_knn_graph(X_all, kk, t1.0) F, _ label_propagation(W, y_all, alpha0.8, n_iter50) W_total fuse_semi_graph(W, F, y_all, beta0.3, sigma0.5) P semi_lpp_project(X_all, W_total, r120) Z_labeled (X_all P)[y_all 0] y_labeled y_all[y_all 0] Z_test X_test P correct 0 for z, label in zip(Z_test, y_test): d np.linalg.norm(Z_labeled - z, axis1) if y_labeled[np.argmin(d)] label: correct 1 acc correct / len(y_test) if acc best[0]: best (acc, k) return best这段代码的逻辑很直接每换一个 (k)重新构图、传播、融合、求投影、做最近邻分类。参数说明k_list建议从 5 到 15r120先固定避免维度变化干扰 (k) 的判断。如果 (k5) 和 (k15) 的准确率都低而 (k7) 或 9 明显高说明数据局部结构比较紧适合较小的邻域。4.2 热核尺度 t 与相似度阈值热核 (t) 控制权重随距离衰减的速度。(t) 太小时只有非常近的样本有非零权重图容易断裂(t) 太大时远近样本权重趋同局部保持退化。对齐后的人脸特征已经归一化到 [0,1](t) 常从 0.5、1.0、1.5、2.0 里选。可以配合相似度阈值把距离大于阈值的边直接删掉减少远端噪声。参数搜索范围太小的问题太大的问题(k)515图断裂软标签传不开异类混入类间塌缩(t)0.52.0权重过于尖锐噪声敏感邻域过大局部性丢失(\beta)0.10.5监督约束不足过拟合标注未标注被压制(r)80200身份信息不足噪声进入分类距离实际调参时先固定 (t1.0)、(\beta0.3)搜 (k)再用最优 (k) 搜 (t)最后搜 (\beta)。不要三组参数一起大网格组合爆炸且难以判断原因。4.3 半监督强度 beta 与标注比例(\beta) 是半监督优化局部保持投影的关键旋钮。标注比例高时比如每人有 5 张以上注册照(\beta) 可以到 0.5标注比例低于 5% 时(\beta) 建议不超过 0.2。原因在于软标签 (F) 的估计误差会通过 (W^{semi}) 进入图权重标注越少软标签越不可靠。def grid_search_beta(X_all, y_all, X_test, y_test, beta_list): W build_knn_graph(X_all, k7, t1.0) F, _ label_propagation(W, y_all, alpha0.8, n_iter50) results [] for beta in beta_list: W_total fuse_semi_graph(W, F, y_all, betabeta, sigma0.5) P semi_lpp_project(X_all, W_total, r120) Z_labeled (X_all P)[y_all 0] y_labeled y_all[y_all 0] Z_test X_test P pred [] for z in Z_test: d np.linalg.norm(Z_labeled - z, axis1) pred.append(y_labeled[np.argmin(d)]) acc np.mean(np.array(pred) y_test) results.append((beta, acc)) return resultsbeta_list可设[0.0, 0.1, 0.2, 0.3, 0.4, 0.5]。如果 (\beta0.0) 的准确率已经很高说明基础图质量好不必强加半监督如果 (\beta0.1) 到 0.3 有明显提升再往上掉说明监督信号有效但强度到顶了。4.4 矩阵奇异、类间塌缩、光照姿态偏移的排错矩阵奇异最常见于样本数少于原始维度。解决方式是先用 PCA 把 (X) 降到 200300 维再跑半监督 LPP。如果 (BX^T D X^T) 仍然接近奇异检查是否有样本权重全零或重复样本必要时加 (10^{-6}) 岭回归项。类间塌缩表现为不同人的投影特征几乎重合。优先检查异类标注断边是否生效再检查 (\beta) 是否太大导致软标签主导。可以把sigma从 0.5 调到 0.3让软标签相似度更尖锐。光照姿态偏移表现为同一人不同抓拍距离很远。除了对齐还可以在构建图之前做左右脸镜像增强或者用局部二值模式特征替代原始灰度。OpenCV 的 LBPH 人脸识别模块虽然自带识别接口但把它作为 LPP 前的局部纹理描述子往往比直接喂灰度图更稳。5. 半监督优化局部保持投影在离线人脸识别门禁机上的落地技巧5.1 投影矩阵固化为 float32 并放入 C/Java 推理训练完成后真正上线的不再是 Python 代码而是W_proj和注册库投影特征。W_proj是 (d\times r) 的矩阵人脸特征 (d10000)、(r120) 时约 4.8 MB用 float32 保存加载到内存后做一次矩阵乘法即可。若终端是 TX510 人脸识别模块这类离线门禁硬件通常只接受固定长度特征向量正好把 (r120) 的投影结果作为比对特征。# 导出到文件C/Java 侧按同样字节序读取 W_proj.astype(np.float32).tofile(semi_lpp_proj.bin) np.save(labeled_proj.npy, Z_labeled.astype(np.float32)) np.save(labeled_ids.npy, y_labeled.astype(np.int32))C 侧读取后用cv::Mat做face_vec * W_projJava 侧用float[][]或 ND4J 都行。注意训练时特征归一化方式必须和推理一致对齐、灰度、直方图均衡、除以 255一步都不能少。5.2 用 FAR/FRR 和 Top-1 验证优化是否有效门禁机只看 Top-1 不够还要看误接受率 FAR 和误拒绝率 FRR。半监督 LPP 优化后如果 Top-1 提升但 FAR 也升高说明类间距离被压近了这时应该提高异类断边强度或降低 (\beta)。验证集要按人分层不能随机打乱后把同一人的多张抓拍同时放进训练和测试否则指标会虚高。指标含义优化目标Top-1最近邻命中率越高越好FAR不同人被判为同一人门禁场景尽量低FRR同一人被判为不同人与用户体验权衡投影耗时单张矩阵乘法离线终端通常 5 ms5.3 新抓拍到达时只更新图权重的增量做法全量重训投影矩阵成本高。增量更新可以只把新未标注样本加入图更新 (W)、(D)、(L)当累计新样本超过阈值或识别率下降时再重算特征向量。下面的片段展示如何给新样本找 (k) 个旧邻居并扩展权重矩阵。def incremental_update(W_old, X_old, X_new, k7, t1.0): n_old, n_new len(X_old), len(X_new) W_new np.zeros((n_old n_new, n_old n_new), dtypenp.float32) W_new[:n_old, :n_old] W_old for i, x in enumerate(X_new): d np.linalg.norm(X_old - x, axis1) idx np.argsort(d)[:k] for j in idx: w np.exp(-d[j] ** 2 / t) W_new[n_old i, j] w W_new[j, n_old i] w return W_newk和t沿用训练时的值避免新旧图尺度不一致。新样本只更新图权重不立即改投影矩阵当新样本累计超过原训练集的 10% 或连续一周识别率下降再重新求解一次W_proj这样既保留离线终端的低开销又不会让图结构长期停滞。本文还有配套的精品资源点击获取