Nonlinear Component Analysis as a Kernel Eigenvalue Problem

Scholkopf B, Smola A J, Muller K, et al. Nonlinear component analysis as a kernel eigenvalue problem[J]. Neural Computation, 1998, 10(5): 1299-1319.

普通的PCA将下式进行特征分解(用论文的话讲就是对角化):

C=1Mj=1MxjxjT

其中xjRN,j=1,,M,且j=1Mxj=0(中心化)。

而kernel PCA试图通过一个非线性函数:

Φ:RNF,xX

其中F是一个高维空间(甚至是无限维)。
所以我们要解决这么一个问题:

C¯=1Mj=1MΦ(xj)Φ(xj)T

其实我们面对的第一个问题不是维度的问题而是Φ的选择或者说构造。我们为什么要把数据映射到高维的空间?因为当前数据的结构(或者说分布)并不理想。

比如满足(x1)2+(y1)2=4的点,我们可以扩充到高维空间(x2,x,y,y2),在高维空间是线性的(虽然这个例子用在kernel SVM 比较好)。

因为Φ()的构造蛮麻烦的,即便有一些先验知识。我们来看一种比较简单的泛用的映射:

(x1,x2,x3)(x13,x23,x33,x12x2,x12x3,x1x22,x1x32,x22x3,x2x32,x1x2x3)

这种样子的映射,很容易把维度扩充到很大很大,这个时候求解特征问题会变得很麻烦。

kernel PCA

假设i=1MΦ(xi)=0(如何保证这个性质的成立在最后讲,注意即便i=1Mxi=0i=1MΦ(xi)=0也不一定成立)。

假设我们找到了C¯的特征向量V0:

C¯V=λV

因为VΦ(xi),i=1,,M的线性组合(这个容易证明),所以,V可以由下式表示:

V=i=1MαiΦ(xi)

所以:

λVTΦ(xj)=VTC¯Φ(xj),forallj=1,,M

等价于(记Φ=[Φ(x1),,Φ(xM)]):

λi=1Mαi(ΦT(xi)Φ(xj))=λ{ΦTΦ(xj)}Tα=1Mi=1MαiΦT(xi)ΦΦTΦ(xj)=1M{ΦTΦΦTΦ(xj)}Tα

对于j=1,,M均成立,其中α=[α1,,αM]T

等价于:

MλΦTΦα=ΦTΦΦTΦα

K=ΦTΦ,那么可写作:

MλKα=K2α

其中Kij=ΦT(xi)Φ(xj)

所以,我们可以通过下式来求解α:

Mλα=Kα

αK的特征向量(注意,当α为特征向量的时候是一定符合MλKα=K2α的,反之也是的,即二者是等价的)。

假设λ1λ2λM对应α1,,αM,那么相应的V也算是求出来了。

需要注意的是,α往往不为1,因为我们希望V=1,所以:

VTV=αTKα=λα2=1

所以α=1λ

PCA当然需要求主成分,假设有一个新的样本x,我们需要求:

Φ(x)TV=ΦT(x)Φα=i=1MαiΦT(xi)Φ(x)

注意,我们只需要计算ΦT(xi)Φ(x)

现在回到kernel PCA 上的关键kernel上。注意到,无论是K,还是最后计算主成分,我们都只需要计算ΦT(x)Φ(y)就可以了,所以如果我们能够找到一个函数k(x,y)来替代就不必显示将x映射到Φ(x)了,这就能够避免了Φ()的选择问题和计算问题。

kernel 的选择

显然,PCA的λ0,所以我们也必须保证K为半正定矩阵,相应的核函数k称为正定核,Mercer定理有相应的构建。

也有现成的正定核:

多项式核

k(x,y)=(xTy+1)d

论文中是(xTy)d

高斯核函数

k(x,y)=exp{ xy22σ2}

性质

在这里插入图片描述
论文用上面的一个例子来说明,kernel PCA可能更准确地抓住数据的结构。

kernel PCA具有普通PCA的性质,良好的逼近(从方差角度),以及拥有最多的互信息等等。并且,如果k(x,y)=k(xHy),那么kernel PCA还具有酉不变性。

因为普通的PCA处理的是一个N×N的协方差矩阵,所以,至多获得N个载荷向量,而kernel PCA至多获得M个载荷向量(特征值非零)。所以,kernel PCA有望比普通PCA更加精准。

一些问题

中心化

PCA处理的是协方差矩阵,正如我们最开始所假设的,i=1MΦ(xi)=0,即中心化。因为Φ()并不是线性函数,所以,即便i=1Mxi=0也不能保证i=1MΦ(xi)=0,不过有别的方法处理。

Φ~(xi)=Φ(xi)1Mk=1MΦ(xk)K~ij=Φ~T(xi)Φ(xj)1M={1}ijM×M

可以得到:

K~ij=Φ~T(xi)Φ(xj)=(Φ(xi)1Mk=1MΦ(xk))T(Φ(xj)1Mk=1MΦ(xk))=Kij1Mk=1MKkj1Mk=1MKik+1M2m,n=1MKmn=(K1MKK1M+1MK1M)ij

于是,我们通过K可以构造出K~。只需再求解K~α~=Mλα~即可。

恢复

我们知道,根据PCA选出的载荷向量以及主成分,我们能够恢复出原数据(或者近似,如果我们只选取了部分载荷向量)。对于kernel PCA,比较困难,因为我们并没有显式构造Φ(),也就没法显式找到V,更何况,有时候我们高维空间找到V在原空间中并不存在原像。
或许, 我们可以通过:

minxΦ(x)Φ(x^)2

来求解,注意到,上式也只和核函数k(x,y)有关。

代码


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])

"""

posted @   馒头and花卷  阅读(733)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· 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
点击右上角即可分享
微信分享提示