在机器学习领域,降维技术是探索高维数据集结构的重要工具。本文将展示如何在手写数字数据集上应用多种流形学习技术,并通过可视化手段比较它们的性能和特点。手写数字数据集是一个经典的数据集,包含了1797个8x8像素的手写数字图像,每个图像可以看作是64维的特征向量。
首先,从手写数字数据集中加载数据,并只使用前六个类别的数据。然后,使用不同的流形学习技术对数据进行降维,并将原始数据投影到降维后的空间中。通过比较不同技术生成的投影,可以观察到手写数字在降维空间中的分布情况,以及它们是否能够保持原有的类别结构。
from sklearn.datasets import load_digits
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import offsetbox
# 加载手写数字数据集
digits = load_digits(n_class=6)
X, y = digits.data, digits.target
n_samples, n_features = X.shape
n_neighbors = 30
# 绘制前100个手写数字
fig, axs = plt.subplots(nrows=10, ncols=10, figsize=(6, 6))
for idx, ax in enumerate(axs.ravel()):
ax.imshow(X[idx].reshape((8, 8)), cmap=plt.cm.binary)
ax.axis('off')
_ = fig.suptitle("手写数字数据集的部分样本", fontsize=16)
接下来,定义一个辅助函数来绘制数据的嵌入投影。这个函数将使用不同的流形学习技术对数据进行降维,并在二维空间中绘制出每个数字的投影点。通过这种方式,可以直观地比较不同技术的效果,例如它们是否能够将不同类别的数字聚集在一起,或者在降维空间中是否能够保持数字的形状特征。
def plot_embedding(X, title):
fig, ax = plt.subplots()
X = MinMaxScaler().fit_transform(X)
for digit in digits.target_names:
ax.scatter(*X[y == digit].T, marker=f"${digit}$", s=60,
color=plt.cm.Dark2(digit), alpha=0.425, zorder=2)
shown_images = np.array([[1.0, 1.0]])
for i in range(X.shape[0]):
dist = np.sum((X[i] - shown_images) ** 2, 1)
if np.min(dist) < 4e-3:
continue
shown_images = np.concatenate([shown_images, [X[i]]], axis=0)
imagebox = offsetbox.AnnotationBbox(offsetbox.OffsetImage(digits.images[i], cmap=plt.cm.gray_r), X[i])
imagebox.set(zorder=1)
ax.add_artist(imagebox)
ax.set_title(title)
ax.axis('off')
在比较不同的流形学习技术时,需要注意以下几点:首先,随机森林嵌入(RandomTreesEmbedding)并不是一种典型的流形学习方法,因为它首先学习一个高维表示,然后应用降维方法。然而,它在将数据集转换为类别线性可分的表示中非常有用。其次,线性判别分析(LinearDiscriminantAnalysis)和邻域成分分析(NeighborhoodComponentsAnalysis)是监督降维方法,它们利用了提供的数据标签,与其他方法不同。此外,t-SNE在这个例子中使用PCA生成的嵌入进行初始化,以确保嵌入的全局稳定性,即嵌入结果不依赖于随机初始化。
from sklearn.decomposition import TruncatedSVD
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.ensemble import RandomTreesEmbedding
from sklearn.manifold import MDS, TSNE, Isomap, LocallyLinearEmbedding, SpectralEmbedding
from sklearn.neighbors import NeighborhoodComponentsAnalysis
from sklearn.pipeline import make_pipeline
from sklearn.random_projection import SparseRandomProjection
embeddings = {
"随机投影嵌入": SparseRandomProjection(n_components=2, random_state=42),
"截断SVD嵌入": TruncatedSVD(n_components=2),
"线性判别分析嵌入": LinearDiscriminantAnalysis(n_components=2),
"Isomap嵌入": Isomap(n_neighbors=n_neighbors, n_components=2),
"标准LLE嵌入": LocallyLinearEmbedding(n_neighbors=n_neighbors, n_components=2, method="standard"),
"修改LLE嵌入": LocallyLinearEmbedding(n_neighbors=n_neighbors, n_components=2, method="modified"),
"Hessian LLE嵌入": LocallyLinearEmbedding(n_neighbors=n_neighbors, n_components=2, method="hessian"),
"LTSA LLE嵌入": LocallyLinearEmbedding(n_neighbors=n_neighbors, n_components=2, method="ltsa"),
"MDS嵌入": MDS(n_components=2, n_init=1, max_iter=120, n_jobs=2),
"随机森林嵌入": make_pipeline(RandomTreesEmbedding(n_estimators=200, max_depth=5, random_state=0),
TruncatedSVD(n_components=2)),
"谱嵌入": SpectralEmbedding(n_components=2, random_state=0, eigen_solver="arpack"),
"t-SNE嵌入": TSNE(n_components=2, max_iter=500, n_iter_without_progress=150, n_jobs=2, random_state=0),
"NCA嵌入": NeighborhoodComponentsAnalysis(n_components=2, init="pca", random_state=0),
}
projections, timing = {}, {}
for name, transformer in embeddings.items():
if name.startswith("线性判别分析"):
data = X.copy()
data.flat[::X.shape[1] + 1] += 0.01 # 使X可逆
else:
data = X
print(f"计算{name}...")
start_time = time()
projections[name] = transformer.fit_transform(data, y)
timing[name] = time() - start_time
# 绘制每种方法的结果投影
for name in timing:
title = f"{name} (时间 {timing[name]:.3f}s)"
plot_embedding(projections[name], title)
plt.show()