Nonlinear Component Analysis as a Kernel Eigenvalue Problem
引
普通的PCA将下式进行特征分解(用论文的话讲就是对角化):
其中,且(中心化)。
而kernel PCA试图通过一个非线性函数:
其中是一个高维空间(甚至是无限维)。
所以我们要解决这么一个问题:
其实我们面对的第一个问题不是维度的问题而是的选择或者说构造。我们为什么要把数据映射到高维的空间?因为当前数据的结构(或者说分布)并不理想。
比如满足的点,我们可以扩充到高维空间,在高维空间是线性的(虽然这个例子用在kernel SVM 比较好)。
因为的构造蛮麻烦的,即便有一些先验知识。我们来看一种比较简单的泛用的映射:
这种样子的映射,很容易把维度扩充到很大很大,这个时候求解特征问题会变得很麻烦。
kernel PCA
假设(如何保证这个性质的成立在最后讲,注意即便,也不一定成立)。
假设我们找到了的特征向量:
因为是的线性组合(这个容易证明),所以,可以由下式表示:
所以:
等价于(记):
对于均成立,其中。
等价于:
令,那么可写作:
其中
所以,我们可以通过下式来求解:
即是的特征向量(注意,当为特征向量的时候是一定符合的,反之也是的,即二者是等价的)。
假设对应,那么相应的也算是求出来了。
需要注意的是,往往不为1,因为我们希望,所以:
所以
PCA当然需要求主成分,假设有一个新的样本,我们需要求:
注意,我们只需要计算。
现在回到kernel PCA 上的关键kernel上。注意到,无论是K,还是最后计算主成分,我们都只需要计算就可以了,所以如果我们能够找到一个函数来替代就不必显示将映射到了,这就能够避免了的选择问题和计算问题。
kernel 的选择
显然,PCA的,所以我们也必须保证为半正定矩阵,相应的核函数称为正定核,Mercer定理有相应的构建。
也有现成的正定核:
多项式核
论文中是
高斯核函数
性质
论文用上面的一个例子来说明,kernel PCA可能更准确地抓住数据的结构。
kernel PCA具有普通PCA的性质,良好的逼近(从方差角度),以及拥有最多的互信息等等。并且,如果,那么kernel PCA还具有酉不变性。
因为普通的PCA处理的是一个的协方差矩阵,所以,至多获得个载荷向量,而kernel PCA至多获得个载荷向量(特征值非零)。所以,kernel PCA有望比普通PCA更加精准。
一些问题
中心化
PCA处理的是协方差矩阵,正如我们最开始所假设的,,即中心化。因为并不是线性函数,所以,即便也不能保证,不过有别的方法处理。
令
可以得到:
于是,我们通过可以构造出。只需再求解即可。
恢复
我们知道,根据PCA选出的载荷向量以及主成分,我们能够恢复出原数据(或者近似,如果我们只选取了部分载荷向量)。对于kernel PCA,比较困难,因为我们并没有显式构造,也就没法显式找到,更何况,有时候我们高维空间找到在原空间中并不存在原像。
或许, 我们可以通过:
来求解,注意到,上式也只和核函数有关。
代码
import numpy as np
class KernelPCA:
def __init__(self, data, kernel=1, pra=3):
self.__n, self.__d = data.shape
self.__data = np.array(data, dtype=float)
self.kernel = self.kernels(kernel, pra)
self.__K = self.center()
@property
def shape(self):
return self.__n, self.__d
@property
def data(self):
return self.data
@property
def K(self):
return self.__K
@property
def alpha(self):
return self.__alpha
@property
def eigenvalue(self):
return self.__value
def kernels(self, label, pra):
"""
数据是一维的时候可能有Bug
:param label: 1:多项式;2:exp
:param pra:1: d; 2: sigma
:return: 函数 或报错
"""
if label is 1:
return lambda x, y: (x @ y) ** pra
elif label is 2:
return lambda x, y: \
np.exp(-(x-y) @ (x-y) / (2 * pra ** 2))
else:
raise TypeError("No such kernel...")
def center(self):
"""中心化"""
oldK = np.zeros((self.__n, self.__n), dtype=float)
one_n = np.ones((self.__n, self.__n), dtype=float)
for i in range(self.__n):
for j in range(i, self.__n):
x = self.__data[i]
y = self.__data[j]
oldK[i, j] = oldK[j, i] = self.kernel(x, y)
return oldK - 2 * one_n @ oldK + one_n @ oldK @ one_n
def processing(self):
"""实际上就是K的特征分解,再对alpha的大小进行一下调整"""
value, alpha = np.linalg.eig(self.__K)
index = value > 0
value = value[index]
alpha = alpha[:, index] * (1 / np.sqrt(value))
self.__alpha = alpha
self.__value = value / self.__n
def score(self, x):
"""来了一个新的样本,我们进行得分"""
k = np.zeros(self.__n)
for i in range(self.__n):
y = self.__data[i]
k[i] = self.kernel(x, y)
return k @ self.__alpha
"""
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x ** 2 + [np.random.randn() * 0.1 for i in range(100)]
data = np.array([x, y]).T
test = KernelPCA(data, pra=1)
test.processing()
print(test.alpha.shape)
print(test.alpha[:, 0])
"""
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix