RBFKernelPCA: RBF 核主成分分析
实现RBF核主成分分析用于非线性降维
> from mlxtend.feature_extraction import RBFKernelPCA
概述
大多数机器学习算法都是为线性可分数据开发的并经过统计验证的。流行的例子包括线性分类器,如支持向量机(SVM)或用于降维的(标准)主成分分析(PCA)。然而,大多数现实世界数据需要非线性方法,以成功执行涉及模式分析和发现的任务。
本概述的重点是简要介绍核方法的概念,并实现一种高斯径向基函数(RBF)核,该核用于通过BF核主成分分析(kPCA)进行非线性降维。
主成分分析
主成分分析(PCA)的主要目的是对数据进行分析,以识别能够“很好”表示数据的模式。主成分可以理解为数据集的新轴,这些轴沿着它们最大化了方差(协方差矩阵的特征向量)。换句话说,PCA旨在找到最大方差的轴,沿着这些轴数据分布最广。
欲了解更多详细信息,请参见关于 mlxtend.feature_extraction.PrincipalComponentAnalysis
的相关文章。
非线性降维
上述描述的“经典”PCA方法是一种线性投影技术,对于线性可分的数据效果良好。然而,对于线性不可分的数据,如果任务是降低数据集的维度,则需要一种非线性技术。
核函数和核技巧
处理线性不可分数据的基本思路是将其投影到更高维的空间中,使其变得线性可分。我们将这种非线性映射函数称为 $\phi$,这样一个样本 $\mathbf{x}$ 的映射可以写成 $\mathbf{x} \rightarrow \phi (\mathbf{x})$,这就是所谓的“核函数”。
现在,“核”这个词描述了一个计算样本在 $\phi$ 下映射的图像的点积的函数。
$$\kappa(\mathbf{x_i, x_j}) = \phi (\mathbf{x_i}) \phi (\mathbf{x_j})^T$$
关于这个方程推导的更多细节可以参考Quan Wang的这篇优秀综述文章:核主成分分析及其在面部识别和主动形状模型中的应用。[1]
换句话说,函数 $\phi$ 通过创建原始特征的非线性组合,将原始的 d 维特征映射到一个更大、k 维的特征空间中。例如,如果 $\mathbf{x}$ 由 2 个特征组成:
$$ \mathbf{x} = \big[x_1 \quad x_2\big]^T \quad \quad \mathbf{x} \in I!R^d $$
$$ \Downarrow \phi $$
$$ \mathbf{x}' = \big[x_1 \quad x_2 \quad x_1 x_2 \quad x_{1}^2 \quad x_1 x_{2}^3 \quad \dots \big]^T \quad \quad \mathbf{x} \in I!R^k (k >> d) $$
通常,RBF 核的数学定义可以写成并实现为
$$ \kappa(\mathbf{x_i, x_j}) = exp\bigg(- \gamma \; \lVert\mathbf{x_i - x_j }\rVert^{2}_{2} \bigg) $$
其中 $\textstyle\gamma = \tfrac{1}{2\sigma^2}$ 是一个需要优化的自由参数。
高斯径向基函数 (RBF) 核主成分分析
在线性主成分分析 (PCA) 方法中,我们关注的是最大化数据集中方差的主成分。这是通过提取对应于协方差矩阵中最大特征值的特征向量(主成分)来实现的:
$$\text{Cov} = \frac{1}{N} \sum_{i=1}^{N} \mathbf{x_i} \mathbf{x_i}^T$$
Bernhard Scholkopf (核主成分分析 [2]) 将这种方法推广到通过核函数映射到更高维空间的数据:
$$\text{Cov} = \frac{1}{N} \sum_{i=1}^{N} \phi(\mathbf{x_i}) \phi(\mathbf{x_i})^T$$
然而,实际上在更高维空间中的协方差矩阵并没有被明确计算(核技巧)。因此,RBF 核主成分分析的实现并不会产生主成分轴(与标准 PCA 相对),但获得的特征向量可以理解为数据在主成分上的投影。
RBF核PCA逐步解析
1. 计算核(相似性)矩阵。
在第一步中,我们需要计算
$$\kappa(\mathbf{x_i, x_j}) = exp\bigg(- \gamma \; \lVert\mathbf{x_i - x_j }\rVert^{2}_{2} \bigg)$$
对于每一对点。例如,如果我们有一个包含100个样本的数据集,这一步将产生一个对称的100x100核矩阵。
2. 核矩阵的特征分解。
由于核矩阵不一定是中心化的,我们可以应用以下公式来进行中心化:
$$K' = K - \mathbf{1_N} K - K \mathbf{1_N} + \mathbf{1_N} K \mathbf{1_N}$$
其中 $\mathbf{1_N}$ 是一个 $N\times N$ 的矩阵,所有值都等于 $\frac{1}{N}$。[3]
现在,我们需要获得中心化核矩阵的特征向量,目标是得到最大特征值对应的特征向量。这些特征向量是已经投影到各自主成分上的数据点。
投影新数据
到目前为止,我们已经在上面的部分将一个数据集投影到一个新的特征子空间。然而,在实际应用中,我们通常感兴趣的是将新数据点映射到相同的新特征子空间(例如,如果我们在模式分类任务中处理训练集和测试集)。
请记住,当我们计算中心化核矩阵的特征向量 $\mathbf{\alpha}$ 时,这些值实际上已经是投影到主成分轴 $\mathbf{g}$ 的数据点。
如果我们想要将一个新的数据点 $\mathbf{x}$ 投影到这个主成分轴上,我们需要计算 $\phi(\mathbf{x})^T \mathbf{g}$。
幸运的是,在这里我们也不需要显式地计算 $\phi(\mathbf{x})^T \mathbf{g}$,而是使用核技巧来计算新数据点与训练数据集中每个数据点 $j$ 之间的 RBF 核:
$$\phi(\mathbf{x})^T \mathbf{g} = \sum_j \alpha_{i} \; \phi(\mathbf{x}) \; \phi(\mathbf{x_j})^T$$
$$= \sum_j \alpha_{i} \; \kappa(\mathbf{x}, \mathbf{x_j})$$
而且核矩阵 $\mathbf{K}$ 的特征向量 $\alpha$ 和特征值 $\lambda$ 满足方程 $\mathbf{K} \alpha = \lambda \alpha$,我们只需通过相应的特征值对特征向量进行归一化。
参考文献
[1] Q. Wang. 核主成分分析及其在面部识别和主动形状模型中的应用. CoRR, abs/1207.3538, 2012.
[2] B. Scholkopf, A. Smola, 和 K.-R. Muller. 核主成分分析. 第583–588页, 1997.
[3] B. Scholkopf, A. Smola, 和 K.-R. Muller. 作为核特征值问题的非线性成分分析. 神经计算, 10(5):1299–1319, 1998.
示例 1 - 半月形状
我们将从一个简单的例子开始,该例子由scikit-learn中的make_moons
函数生成的两个半月形状。
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=50, random_state=1)
plt.scatter(X[y==0, 0], X[y==0, 1],
color='red', marker='o', alpha=0.5)
plt.scatter(X[y==1, 0], X[y==1, 1],
color='blue', marker='^', alpha=0.5)
plt.ylabel('y coordinate')
plt.xlabel('x coordinate')
plt.show()
由于这两个半月形状是线性不可分的,我们预计“经典”的主成分分析(PCA)将无法为我们提供在一维空间中“良好”的数据表示。让我们使用 PCA
类来执行降维操作。
from mlxtend.feature_extraction import PrincipalComponentAnalysis as PCA
pca = PCA(n_components=2)
X_pca = pca.fit(X).transform(X)
plt.scatter(X_pca[y==0, 0], X_pca[y==0, 1],
color='red', marker='o', alpha=0.5)
plt.scatter(X_pca[y==1, 0], X_pca[y==1, 1],
color='blue', marker='^', alpha=0.5)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.show()
正如我们所见,得到的主成分并未形成一个数据线性分隔良好的子空间。请注意,PCA是一种无监督的方法,并不“考虑”类别标签,以便最大化方差,这与线性判别分析相反。在这里,蓝色和红色只是为了可视化目的而添加,用于指示分隔的程度。
接下来,我们将通过RBF核PCA对我们的半月形数据进行降维。$\gamma$的选择取决于数据集,可以通过超参数调优技术如网格搜索来获得。超参数调优本身是一个广泛的话题,这里我只会使用一个我发现能够产生“好”结果的$\gamma$值。
from mlxtend.data import iris_data
from mlxtend.preprocessing import standardize
from mlxtend.feature_extraction import RBFKernelPCA as KPCA
kpca = KPCA(gamma=15.0, n_components=2)
kpca.fit(X)
X_kpca = kpca.X_projected_
请注意,核方法的组件,例如RBF核PCA,已经表示了投影的数据点(与PCA不同,在PCA中,组件轴是用于构建投影矩阵的“前k个”特征向量,然后使用该矩阵转换训练样本)。因此,拟合后可以通过.X_projected_
属性获得投影训练集。
plt.scatter(X_kpca[y==0, 0], X_kpca[y==0, 1],
color='red', marker='o', alpha=0.5)
plt.scatter(X_kpca[y==1, 0], X_kpca[y==1, 1],
color='blue', marker='^', alpha=0.5)
plt.title('First 2 principal components after RBF Kernel PCA')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.show()
新的特征空间现在是线性可分的。由于我们通常对降维感兴趣,让我们仅关注第一个分量。
import numpy as np
plt.scatter(X_kpca[y==0, 0], np.zeros((25, 1)),
color='red', marker='o', alpha=0.5)
plt.scatter(X_kpca[y==1, 0], np.zeros((25, 1)),
color='blue', marker='^', alpha=0.5)
plt.title('First principal component after RBF Kernel PCA')
plt.xlabel('PC1')
plt.yticks([])
plt.show()
我们可以清楚地看到,通过径向基函数核主成分分析(RBF Kernel PCA)得到的投影产生了一个类别分隔良好的子空间。这样的子空间可以用作广义线性分类模型的输入,例如逻辑回归。
投影新数据
最后,借助transform方法,我们可以将新数据投影到新的组件轴上。
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
X2, y2 = make_moons(n_samples=200, random_state=5)
X2_kpca = kpca.transform(X2)
plt.scatter(X_kpca[y==0, 0], X_kpca[y==0, 1],
color='red', marker='o', alpha=0.5, label='fit data')
plt.scatter(X_kpca[y==1, 0], X_kpca[y==1, 1],
color='blue', marker='^', alpha=0.5, label='fit data')
plt.scatter(X2_kpca[y2==0, 0], X2_kpca[y2==0, 1],
color='orange', marker='v',
alpha=0.2, label='new data')
plt.scatter(X2_kpca[y2==1, 0], X2_kpca[y2==1, 1],
color='cyan', marker='s',
alpha=0.2, label='new data')
plt.legend()
plt.show()
示例 2 - 同心圆
根据示例1中解释的概念,让我们来看另一个经典案例:2个同心圆,外加由scikit-learn的make_circles
生成的随机噪声。
from sklearn.datasets import make_circles
X, y = make_circles(n_samples=1000, random_state=123,
noise=0.1, factor=0.2)
plt.figure(figsize=(8,6))
plt.scatter(X[y==0, 0], X[y==0, 1], color='red', alpha=0.5)
plt.scatter(X[y==1, 0], X[y==1, 1], color='blue', alpha=0.5)
plt.title('Concentric circles')
plt.ylabel('y coordinate')
plt.xlabel('x coordinate')
plt.show()
from mlxtend.data import iris_data
from mlxtend.preprocessing import standardize
from mlxtend.feature_extraction import RBFKernelPCA as KPCA
kpca = KPCA(gamma=15.0, n_components=2)
kpca.fit(X)
X_kpca = kpca.X_projected_
plt.scatter(X_kpca[y==0, 0], X_kpca[y==0, 1],
color='red', marker='o', alpha=0.5)
plt.scatter(X_kpca[y==1, 0], X_kpca[y==1, 1],
color='blue', marker='^', alpha=0.5)
plt.title('First 2 principal components after RBF Kernel PCA')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.show()
plt.scatter(X_kpca[y==0, 0], np.zeros((500, 1)),
color='red', marker='o', alpha=0.5)
plt.scatter(X_kpca[y==1, 0], np.zeros((500, 1)),
color='blue', marker='^', alpha=0.5)
plt.title('First principal component after RBF Kernel PCA')
plt.xlabel('PC1')
plt.yticks([])
plt.show()
API
RBFKernelPCA(gamma=15.0, n_components=None, copy_X=True)
RBF Kernel Principal Component Analysis for dimensionality reduction.
Parameters
-
gamma
: float (default: 15.0)Free parameter (coefficient) of the RBF kernel.
-
n_components
: int (default: None)The number of principal components for transformation. Keeps the original dimensions of the dataset if
None
. -
copy_X
: bool (default: True)Copies training data, which is required to compute the projection of new data via the transform method. Uses a reference to X if False.
Attributes
-
e_vals_
: array-like, shape=[n_features]Eigenvalues in sorted order.
-
e_vecs_
: array-like, shape=[n_features]Eigenvectors in sorted order.
-
X_projected_
: array-like, shape=[n_samples, n_components]Training samples projected along the component axes.
Examples
For usage examples, please see https://rasbt.github.io/mlxtend/user_guide/feature_extraction/RBFKernelPCA/
Methods
fit(X)
Learn model from training data.
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
Returns
self
: object
transform(X)
Apply the non-linear transformation on X.
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
Returns
-
X_projected
: np.ndarray, shape = [n_samples, n_components]Projected training vectors.