万字长文讲解机器学习领域中11种降维技术

文摘   2024-08-05 06:39   上海  
AI算法之道报道

编辑:AIWay 1024


在统计学和机器学习中,数据集的属性、特征或输入变量的数量被称为其维度。例如,我们假设一个非常简单的数据集,其中包含名为 "身高 "和 "体重 "的两个属性。这是一个二维数据集,该数据集的任何观测值都可以绘制成二维图。

如果我们在上述同一个数据集上再添加一个名为 "年龄 "的维度,它就变成了一个三维数据集,任何观测值都位于三维空间中。

同样,现实世界的数据集也有很多属性。这些数据集的观测值位于难以想象的高维空间中。以下是数据科学家、统计学家和机器学习工程师考虑的与维度有关的数据集的一般几何解释。

在包含行和列的表格数据集中,列代表 n 维特征空间的维数,行则是该空间中的数据点。

降维简单地说,就是在尽可能保留原始数据集变化的前提下,减少数据集中属性的数量。这是一个数据预处理步骤,意味着我们要在训练模型之前进行降维。在本文中,我们将讨论 11 种降维技术,并使用 Python 和 Scikit-learn 库在现实世界的数据集上实现这些技术。




降维的重要性
当我们降低数据集的维度时,我们会损失原始数据中一定比例(通常为 1%-15%,取决于我们保留的成分或特征的数量)的可变性。不过,不用担心会丢失原始数据中那么大比例的可变性,因为降维会带来以下优势。
  • 数据维数越少,意味着训练时间和计算资源越少,机器学习算法的整体性能也就越高--涉及许多特征的机器学习问题会使训练速度极慢。高维空间中的大多数数据点都非常接近该空间的边界。这是因为高维空间的空间很大。在高维数据集中,大多数数据点之间可能相距甚远。因此,算法无法有效、高效地对高维数据进行训练。在机器学习中,这种问题被称为 "维度诅咒"(curse of dimensionality)--这只是一个技术术语,你不必担心!
  • 降维可避免过拟合问题--当数据中存在许多特征时,模型就会变得更加复杂,并倾向于对训练数据进行过拟合。
  • 降维对于数据可视化非常有用--当我们把高维数据的维数降为两到三个分量时,数据就可以很容易地绘制成二维或三维图。
  • 降维处理多重共线性--在回归中,当一个自变量与一个或多个其他自变量高度相关时,就会出现多重共线性。降维可以利用这一点,将这些高度相关的变量合并成一组不相关的变量。这将解决多重共线性问题。

  • 降维对于因子分析非常有用--这是一种有用的方法,可以找到潜在的变量,这些变量不是直接用单一变量来测量的,而是从数据集中的其他变量推断出来的。这些潜变量被称为因子。

  • 降维消除数据中的噪音--通过只保留最重要的特征并去除冗余特征,降维消除了数据中的噪音。这将提高模型的准确性。

  • 降维可用于图像压缩--图像压缩是一种在尽可能保持图像质量的前提下,将图像的字节大小减至最小的技术。构成图像的像素可视为图像数据的维数(列/变量)。我们进行 PCA 处理,以获得最佳的分量数,从而在图像数据的可解释性和图像质量之间取得平衡。





降维的方法
有几种降维方法可用于满足不同要求的不同类型数据。下图总结了这些降维方法。


图 1:不同的降维方法总结
降维方法主要有两种。这两种方法都能减少维数,但方法不同。区分这两种方法非常重要。一种方法只保留数据集中最重要的特征,去除多余的特征。这种方法不对特征集进行转换。后向消除、前向选择和随机森林就是这种方法的例子。另一种方法是找到新特征的组合。对特征集进行适当的转换。新的特征集包含不同的值,而不是原始值。这种方法又可分为线性方法和非线性方法。非线性方法就是众所周知的 Manifold 学习。主成分分析法(PCA)、因子分析法(FA)、线性判别分析法(LDA)和截断奇异值分解法(SVD)都是线性降维方法的例子。核 PCA、t 分布随机邻域嵌入(t-SNE)、多维缩放(MDS)和等距映射(Isomap)是非线性降维方法的例子。


线性方法
线性方法是将原始数据线性投影到低维空间。我们将讨论线性方法中的 PCA、FA、LDA 和 Truncated SVD。这些方法可用于线性数据,但在非线性数据上表现不佳。
1. 主成分分析- PCA
PCA是我最喜欢的机器学习算法之一。PCA是一种线性降维技术(算法),它将一组相关变量(p)转换为较小的k(k<p)个不相关变量,称为主成分,同时尽可能多地保留原始数据集中的变化。在机器学习(ML)的背景下,PCA是一种用于降维的无监督机器学习算法。
2. 因子分析- FA
因子分析(FA)和主成分分析(PCA)都是降维技术。因子分析的主要目的不仅仅是降低数据的维度。因子分析是一种有用的方法,可以找到潜在变量,这些变量不是直接用单一变量测量的,而是从数据集中的其他变量推断出来的。这些潜变量被称为因子。
3. 线性判别分析- LDA
LDA 通常用于多类别分类。它也可用作降维技术。LDA 可以根据训练实例的类别对其进行最佳分离或判别(因此被称为 LDA)。LDA 与 PCA 的主要区别在于,LDA 可以找到输入特征的线性组合,从而优化类的可分离性,而 PCA 则试图在数据集中找到一组方差最大的不相关成分。两者的另一个主要区别是,PCA 是一种无监督算法,而 LDA 是一种有监督算法,它将类标签考虑在内。
LDA 有一些局限性。要应用 LDA,数据应呈正态分布。数据集还应包含已知的类标签。LDA 能找到的最大成分数是类别的数量减去 1。如果数据集中只有 3 个类别标签,那么 LDA 在降维时只能找到 2 个(3-1)成分。应用 LDA 时不需要进行特征缩放。另一方面,PCA 需要对数据进行缩放。不过,PCA 不需要类标签。PCA 能找到的最大分量数是原始数据集中输入特征的数量。
下面的 Python 代码描述了 LDA 和 PCA 技术在虹膜数据集上的应用,并展示了两者之间的区别。原始虹膜数据集有四个特征。LDA 和 PCA 将特征数量减少为两个,并实现了二维可视化。
from sklearn.datasets import load_irisfrom sklearn.decomposition import PCAimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysis
iris = load_iris()X = iris.datay = iris.target
from sklearn.preprocessing import StandardScalersc = StandardScaler()X_scaled = sc.fit_transform(X)
pca = PCA(n_components=2)X_pca = pca.fit_transform(X_scaled)
lda = LinearDiscriminantAnalysis(n_components=2, solver='svd')X_lda = lda.fit_transform(X, y)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(13.5 ,4))sns.scatterplot(X_pca[:,0], X_pca[:,1], hue=y, palette='Set1', ax=ax[0])sns.scatterplot(X_lda[:,0], X_lda[:,1], hue=y, palette='Set1', ax=ax[1])ax[0].set_title("PCA of IRIS dataset", fontsize=15, pad=15)ax[1].set_title("LDA of IRIS dataset", fontsize=15, pad=15)ax[0].set_xlabel("PC1", fontsize=12)ax[0].set_ylabel("PC2", fontsize=12)ax[1].set_xlabel("LD1", fontsize=12)ax[1].set_ylabel("LD2", fontsize=12)plt.savefig('PCA vs LDA.png', dpi=80)
对比结果如下:

4. 截断奇异值分解- SVD
这种方法通过截断奇异值分解(SVD)进行线性降维。它能很好地处理稀疏数据(其中许多行值为零)。相比之下,PCA 对稠密数据效果更佳。截断 SVD 也可用于密集数据。截断 SVD 和 PCA 的另一个主要区别是,SVD 的因式分解是在数据矩阵上进行的,而 PCA 的因式分解是在协方差矩阵上进行的。
Scikit-learn库中的截断 SVD 实现非常简单。可以使用 TruncatedSVD() 函数来实现。下面的 Python 代码描述了截断 SVD 和 PCA 技术在虹膜数据集上的实现。
from sklearn.datasets import load_irisfrom sklearn.decomposition import PCAimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.preprocessing import StandardScalerfrom sklearn.decomposition import TruncatedSVD
iris = load_iris()X = iris.datay = iris.targetsc = StandardScaler()X_scaled = sc.fit_transform(X)
pca = PCA(n_components=2)X_pca = pca.fit_transform(X_scaled)
svd = TruncatedSVD(n_components=2, algorithm='randomized', random_state=0)X_svd = svd.fit_transform(X_scaled)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(13.5 ,4))sns.scatterplot(X_pca[:,0], X_pca[:,1], hue=y, palette='Set1', ax=ax[0])sns.scatterplot(X_svd[:,0], X_svd[:,1], hue=y, palette='Set1', ax=ax[1])ax[0].set_title("PCA of IRIS dataset", fontsize=15, pad=15)ax[1].set_title("Truncated SVD of IRIS dataset", fontsize=15, pad=15)ax[0].set_xlabel("PC1", fontsize=12)ax[0].set_ylabel("PC2", fontsize=12)ax[1].set_xlabel("SVD1", fontsize=12)ax[1].set_ylabel("SVD2", fontsize=12)plt.savefig('PCA vs SVD.png', dpi=100)
对比结果如下:




非线性方法
如果我们处理的是实际应用中经常使用的非线性数据,那么迄今为止讨论过的线性降维方法就不能很好地发挥作用。在本节中,我们将讨论四种可用于非线性数据的非线性降维方法。
1. 内核主成分分析- Kernel PCA
Kernel PCA 是一种使用核的非线性降维技术。它也可以被视为普通 PCA 的非线性形式。在普通 PCA 无法有效使用的非线性数据集上,内核 PCA 可以很好地发挥作用。
内核 PCA 背后的直觉非常有趣。数据首先通过一个核函数运行,并暂时投射到一个新的高维特征空间中,在这个空间中,类别变得线性可分(画一条直线即可划分类别)。然后,算法使用普通 PCA 将数据投射回低维空间。这样,核 PCA 就能将非线性数据转换为可与线性分类器一起使用的低维空间数据。
在内核 PCA 中,我们需要指定 3 个重要的超参数--希望保留的成分数、核类型和核系数(也称为伽马值)。关于核类型,我们可以使用 "linear"、"poly"、"rbf "、"sigmoid "或者 "cosine"。rbf 内核被称为径向基函数内核,是最常用的非线性内核。
现在,我们要对非线性数据实施基于RBF内核的PCA,这些数据可以通过 Scikit-learn 中的make_moons() 函数生成。
import seaborn as snsimport matplotlib.pyplot as pltfrom sklearn.datasets import make_moonssns.set_style('darkgrid')
X, y = make_moons(n_samples = 500, random_state=42)sns.scatterplot(X[:, 0], X[:, 1], hue=y, palette='Set1')plt.savefig('Non-linear data.png')
运行结果如下:

非线性数据示例
我们可以清楚地看到,上述非线性数据中的两类数据无法通过简单画一条直线来区分。让我们对上述数据同时执行 PCA 和 Kernel PCA,看看会发生什么!
from sklearn.decomposition import PCAfrom sklearn.decomposition import KernelPCA
pca = PCA(n_components=2)X_pca = pca.fit_transform(X)
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15, random_state=42)X_kpca = kpca.fit_transform(X)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(13.5 ,4))sns.scatterplot(X_pca[:, 0], X_pca[:, 1], hue=y, palette='Set1', ax=ax[0])sns.scatterplot(X_kpca[:, 0], X_kpca[:, 1], hue=y, palette='Set1', ax=ax[1])ax[0].set_title("PCA", fontsize=15, pad=15)ax[1].set_title("RBF Kernel PCA", fontsize=15, pad=15)ax[0].set_xlabel("Component 1", fontsize=12)ax[0].set_ylabel("Component 2", fontsize=12)ax[1].set_xlabel("Component 1", fontsize=12)ax[1].set_ylabel("Component 2", fontsize=12)plt.savefig('PCA vs Kernel PCA.png', dpi=100)

对比结果如下:

从上图可以看出,普通 PCA 无法将非线性数据转化为线性数据。在对相同数据应用核 PCA 后,两类数据在线性上得到了很好的分离(现在,可以通过画一条垂直直线来划分类别)。

那么,内核 PCA 是否真的降低了数据的维度呢?答案是 "是的",因为 RBF 核函数暂时将二维数据投影到一个新的高维特征空间,在这个空间中,类别变得线性可分,然后算法将高维数据投影回二维数据,并绘制成二维图。在类别可线性分离的同时,降维过程在幕后进行。

使用核 PCA 进行降维处理的一个限制是,我们必须在运行算法之前指定伽马超参数值。这就需要采用网格搜索等超参数调整技术来找到伽玛的最佳值。这超出了本文的讨论范围。
2. t分布随机领域嵌入 -  t-SNE
这也是一种非线性降维方法,主要用于数据可视化。除此之外,它还广泛应用于图像处理和 NLP。Scikit-learn 文档建议,如果数据集中的特征数量超过 50 个,则在使用 t-SNE 之前先使用 PCA 或 Truncated SVD。以下是在 PCA 之后执行 t-SNE 的一般语法。此外,请注意在 PCA 之前需要对特征进行缩放。
from sklearn.decomposition import PCAfrom sklearn.manifold import TSNEfrom sklearn.preprocessing import StandardScalersc = StandardScaler()X_scaled = sc.fit_transform(X)pca = PCA()X_pca = pca.fit_transform(X_scaled)tsne = TSNE()X_tsne = tsne.fit_transform(X_pca)

现在,我们将 t-SNE 应用于虹膜数据集。它只有 4 个特征。因此,我们不需要在 t-SNE 之前运行 PCA。


from sklearn.datasets import load_irisimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.manifold import TSNEfrom sklearn.preprocessing import StandardScalersns.set_style('darkgrid')
iris = load_iris()X = iris.datay = iris.target
sc = StandardScaler()X_scaled = sc.fit_transform(X)
tsne = TSNE(n_components=2, random_state=1)X_tsne = tsne.fit_transform(X_scaled)
sns.scatterplot(X_tsne[:,0], X_tsne[:,1], hue=y, palette='Set1')plt.title("t-SNE of IRIS dataset", fontsize=15, pad=15)plt.savefig('t-SNE.png')

运行结果如下:

3. Multidimensional Scaling -  MDS
MDS 是另一种非线性降维技术,它试图在降低非线性数据维度的同时保留实例之间的距离。MDS 算法有两种类型:度量算法和非度量算法。Scikit-learn 中的 MDS() 类通过将度量超参数设置为 True(度量类型)或 False(非度量类型)来实现这两种算法。
下面的代码实现了对虹膜数据集的 MDS。
from sklearn.datasets import load_irisimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.manifold import MDSsns.set_style('darkgrid')
iris = load_iris()X = iris.datay = iris.target
mds = MDS(n_components=2, metric=True, random_state=2)X_mds = mds.fit_transform(X)
sns.scatterplot(X_mds[:,0], X_mds[:,1], hue=y, palette='Set1')plt.title("MDS of IRIS dataset", fontsize=15, pad=15)plt.savefig('MDS.png')
运行结果如下:

4. 等轴映射图 -  Isomap
该方法通过等距映射进行非线性降维。它是 MDS 或 Kernel PCA 的扩展。它通过计算每个实例与其最近邻居的曲线距离或大地距离将其连接起来,从而降低维度。在 Scikit-learn 中实现等距映射算法的 Isomap() 类中,可以通过 n_neighbors 超参数指定每个点要考虑的邻居数量。
下面的代码实现了 Iris 数据集的 Isomap。
from sklearn.datasets import load_irisimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.manifold import Isomapsns.set_style('darkgrid')
iris = load_iris()X = iris.datay = iris.target
isomap = Isomap(n_neighbors=5, n_components=2, eigen_solver='auto')X_isomap = isomap.fit_transform(X)
sns.scatterplot(X_isomap[:,0], X_isomap[:,1], hue=y, palette='Set1')plt.title("Isomap of IRIS dataset", fontsize=15, pad=15)plt.savefig('Isomap.png')view raw
运行结果如下:




其他方法
在这个类别下,我们将讨论 3 种方法。这些方法只保留数据集中最重要的特征,去除多余的特征。因此,它们主要用于特征选择。但是,在选择最佳特征的同时,降维也会自动发生!因此,它们也可以被视为降维方法。这些方法可以提高模型的准确度分数,或提升高维数据集的性能。
1. 逆向消除
这种方法通过递归特征消除(RFE)过程从数据集中消除(移除)特征。算法首先尝试在数据集中的初始特征集上训练模型,并计算模型的性能(通常是分类模型的准确率得分和回归模型的 RMSE)。然后,算法每次删除一个特征(变量),在剩余特征上训练模型并计算性能得分。算法会重复删除特征,直到检测到模型的性能得分变化很小(或没有变化)为止!
现在,我们在虹膜数据上训练逻辑回归模型,并通过反向特征消除找出最重要的特征。
import pandas as pdfrom sklearn.datasets import load_irisfrom sklearn.feature_selection import RFEfrom sklearn.linear_model import LogisticRegressionfrom yellowbrick.model_selection import feature_importances
iris = load_iris()X = iris.datay = iris.target
estimator = LogisticRegression(max_iter=150)selector = RFE(estimator, n_features_to_select=3, step=1)selector.fit(X, y)X_selected = selector.transform(X)
print('Data with initial features')print(pd.DataFrame(X, columns=iris.feature_names).head())print()print('Data with selected features')print(pd.DataFrame(X_selected).head())print()print(feature_importances(estimator, X, y, stack=True, labels=iris.feature_names, relative=False))
运行结果如下:

从输出结果可以看出,递归特征消除(RFE)算法已将sepal length(厘米)从逻辑回归模型中剔除。其余特征包含初始数据集中的原始值。如图所示,最好在模型中保留其他 3 个特征。
2. 前向选择
这种方法可以看作是与后向消除相反的过程。该算法不是递归地消除特征,而是尝试在数据集中的单个特征上训练模型,并计算模型的性能(通常是分类模型的准确率得分和回归模型的 RMSE)。然后,算法每次添加(选择)一个特征(变量),在这些特征上训练模型并计算性能得分。该算法会重复添加特征,直到检测到模型的性能得分变化很小(或没有变化)为止!
让我们对虹膜数据进行前向特征选择,找出最重要的特征。
import pandas as pdfrom sklearn.datasets import load_irisfrom sklearn.feature_selection import f_classiffrom sklearn.feature_selection import SelectKBest
iris = load_iris()X = iris.datay = iris.target
X_selected = SelectKBest(f_classif, k=3).fit_transform(X, y)
# Let's see F-vlues for each featureprint('F-values: ', f_classif(X,y)[0])print()print('Data with initial features')print(pd.DataFrame(X, columns=iris.feature_names).head())print()print('Data with selected features')print(pd.DataFrame(X_selected).head())
运行结果如下:

从输出结果可以看出,前向特征选择过程选择了 F 值较高的sepal length(厘米)、petal length(厘米)和petal width(厘米)。
3. 随机森林
随机森林是一种基于树的模型,广泛用于非线性数据的回归和分类任务。它还可用于特征选择,其内置的 feature_importances_ 属性可在训练模型时根据 "gini"标准(衡量内部节点分割质量的标准)为每个特征计算特征重要性分数。
下面的 Python 代码针对虹膜数据实现了随机森林分类器,并计算和可视化了特征导入值。
import pandas as pdimport seaborn as snsimport matplotlib.pyplot as pltfrom sklearn.datasets import load_irisfrom sklearn.ensemble import RandomForestClassifier
iris = load_iris()X = iris.datay = iris.target
rf = RandomForestClassifier(n_estimators=100, max_depth=3, bootstrap=True, n_jobs=-1, random_state=0)rf.fit(X, y)
feature_imp = pd.Series(rf.feature_importances_, index=iris.feature_names).sort_values(ascending=False)
print('Feature importances: ', rf.feature_importances_)print(sns.barplot(x=feature_imp, y=feature_imp.index))plt.xlabel('Feature Importance Score', fontsize=12)plt.ylabel('Features', fontsize=12)plt.title("Visualizing Important Features", fontsize=15, pad=15)
运行结果如下:

通过观察特征的重要性,我们可以决定放弃萼片宽度sepal width(厘米)特征,因为它对模型的制作没有足够的贡献。让我们来看看是怎么做的!
from sklearn.feature_selection import SelectFromModel
selector = SelectFromModel(rf, threshold=0.05)features_important = selector.fit_transform(X, y)
print('Data with initial features')print(pd.DataFrame(X, columns=iris.feature_names).head())print()print('Data with selected features')print(pd.DataFrame(features_important).head())
运行结果如下:

Scikit-learn 中的 SelectFromModel 只选择重要性大于或等于指定阈值的特征。SelectFromModel 返回的值可用作随机森林分类器的新输入 X,该分类器现在只对选定的特征进行训练!
rf = RandomForestClassifier(n_estimators=100, max_depth=3,                            bootstrap=True, n_jobs=-1,                            random_state=0)  rf.fit(features_important, y)

© THE END 

转载请联系本公众号获得授权


AI算法之道
一个专注于深度学习、计算机视觉和自动驾驶感知算法的公众号,涵盖视觉CV、神经网络、模式识别等方面,包括相应的硬件和软件配置,以及开源项目等。
 最新文章