参考来源:
交互式维度缩减简介https://www.kaggle.com/code/arthurtok/interactive-intro-to-dimensionality-reduction
软件环境:Jupyter Notebook (anaconda3)
主要任务:
数据收集:收集与手写数字相关的数据。 数据预处理:对收集到的数据进行预处理和清洗,包括去除异常值、处理缺失数据、进行特征选择等。 特征提取与表示:根据研究的目标和问题,从原始数据中提取有意义的特征,并将其表示为适合进行模型融合和聚类的形式。 多模型融合:选择适合的多模型融合方法,将不同的模型进行融合,以综合考虑不同模型的优势和不足。 K 均值聚类:使用 K 均值聚类算法进行聚类,划分为不同的群组。 影响因素分析:对聚类结果进行分析,探索不同群组的特点和影响因素。 结果解释与验证:根据分析结果,解释不同因素的影响,并进行验证。
目录
3.2皮尔逊相关性图(Pearson Correlation Plot)
一、问题背景
数字识别是一种使用计算机视觉和机器学习技术来自动识别和分类手写或印刷数字的过程。这项技术在许多应用中都有重要作用,如光学字符识别(OCR)、自动化表单处理、车牌识别和智能手机上的手写输入。
MNIST数据集作为一个经典的手写数字图像数据集,被广泛用于数字识别算法的评估和比较。该数据集由大量的手写数字图像组成,每张图像都对应一个0到9之间的数字标签。这些图像数据的大小为28x28像素,每个像素的值表示该位置的像素强度。通过分析这些图像的像素值,建立一个准确可靠的数字识别模型。利用机器学习算法来训练模型,并通过对新的手写数字图像进行预测,将其正确分类为相应的数字标签。
数字识别器的目标是设计和实现一个能够高准确率地对手写数字进行分类的系统。该系统可以通过学习MNIST数据集中的样本图像,提取特征并训练模型,从而能够对新的手写数字图像进行自动识别。这样的数字识别器可以应用于各种场景,例如自动识别手写数字的应用程序、自动化数据输入和处理等。
二、流程分析
三、方案实现
3.1数据集介绍——MNIST数据集
MNIST集由42,000行和785列组成。有28 x 28像素的数字图像(贡献于784列)以及一个额外的标签列,基本上是一个类别标签,说明每个数字的行贡献是1或是9。MNIST数据集如下所示。
3.2皮尔逊相关性图(Pearson Correlation Plot)
由于数据集仍然包含相对较多的特征(列),通过保存标签特征从数据框中删除它来对训练数据进行一些清理。
# 保存标签特征
target = train['label']
# 从数据框中删除标签列
train = train.drop("label",axis=1)
通过这些步骤,数据就准备好进行降维操作了。例如使用PCA(主成分分析)或其他降维方法。
四、降维
4.1主成分分析(PCA)
主成分分析(Principal Component Analysis,简称 PCA)是一种常用的降维技术,主要用于数据预处理、数据压缩和特征提取。其核心思想是通过线性变换将高维数据投影到一个较低维的空间,同时尽可能保留数据的主要信息。
简而言之,PCA是一种线性变换算法,它试图将数据的原始特征投影到一个较小的特征集(或子空间)上,同时仍保留大部分信息。为了实现这一目标,该算法尝试找到最适合的方向/角度(即主成分),以最大化新子空间中的方差。
计算特征变量
观察MNIST数据集中的数字的方差,可能会有启发。因此,为了达到这个目的,计算协方差矩阵的特征向量和特征值,具体如下:
# 数据标准化
from sklearn.preprocessing import StandardScaler
X = train.values
X_std = StandardScaler().fit_transform(X)
# 计算协方差矩阵的特征向量和特征值
mean_vec = np.mean(X_std, axis=0)
cov_mat = np.cov(X_std.T)
eig_vals, eig_vecs = np.linalg.eig(cov_mat)
# Create a list of (eigenvalue, eigenvector) tuples
eig_pairs = [ (np.abs(eig_vals[i]),eig_vecs[:,i]) for i in range(len(eig_vals))]
# 将特征值和特征向量对从高到低排序
eig_pairs.sort(key = lambda x: x[0], reverse= True)
# 从特征值计算解释方差
tot = sum(eig_vals)
var_exp = [(i/tot)*100 for i in sorted(eig_vals, reverse=True)] # Individual explained variance
cum_var_exp = np.cumsum(var_exp) # Cumulative explained variance
在计算出单个解释方差和累积解释方差值后,用Plotly可视化软件包来制作一个互动图表来展示。
从图中可以看出,在784个特征或列中,大约90%的解释方差可以用200多个特征来描述。因此,如果我们想在这个数据集上实施PCA,提取前200个特征是一个非常合理的选择。下面将使用Sklearn工具包和它内置的PCA方法。
特征值可视化
PCA方法旨在获取捕捉到最大方差的最优方向(或特征向量)。因此,将这些方向及其相关特征值可视化可能会提供一些信息。使用PCA从数字数据集中仅提取前30个特征值(使用Sklearn的`.components_`方法),并将前5个特征值与其他较小的特征值进行比较,以查看是否能够获得一些洞见,如下所示:
# 调用 SKlearn 的 PCA 方法
n_components = 30
pca = PCA(n_components=n_components).fit(train.values)
eigenvalues = pca.components_.reshape(n_components, 28, 28)
# 提取 PCA 组件(特征值)
#eigenvalues = pca.components_.reshape(n_components, 28, 28)
eigenvalues = pca.components_
n_row = 4
n_col = 7
# 绘制前 8 个特征值
plt.figure(figsize=(13,12))
for i in list(range(n_row * n_col)):
offset =0
plt.subplot(n_row, n_col, i + 1)
plt.imshow(eigenvalues[i].reshape(28,28), cmap='jet')
title_text = '特征值 ' + str(i + 1)
plt.title(title_text, size=6.5)
plt.xticks(())
plt.yticks(())
plt.show()
从图中可以得出以下结论:上面的子图展示了PCA方法为数字数据集生成的前30个最优方向或主成分轴。值得关注的是,将第一个组件“特征值1”与第28个组件“特征值28”进行比较时,可以明显看出在寻求最大化新特征子空间方差的过程中,生成了更复杂的方向或组件。
为了观察MNIST数字集的本身,把实际的MNIST数字集绘制出来,看看底层数据集到底代表了什么,而不是只顾着看1和0。
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 12)) # 设置整个图形的大小为 14x12 英寸
# 遍历前 70 个样本(即前 70 张图片)
for digit_num in range(0, 70):
plt.subplot(7, 10, digit_num+1) # 创建一个 7x10 的子图网格,当前子图的索引为 digit_num+1
grid_data = train.iloc[digit_num].values.reshape(28, 28) # 将一维像素数组重新形状为二维数组
plt.imshow(grid_data, interpolation="none", cmap="afmhot") # 显示图像,使用 "afmhot" 颜色映射,禁用插值
plt.xticks([]) # 禁用 x 轴刻度
plt.yticks([]) # 禁用 y 轴刻度
plt.tight_layout() # 调整子图的布局,使其紧凑显示
plt.show() # 显示图形
Sklearn实现PCA
使用Sklearn工具箱,实现主成分分析算法如下:
# 删除之前创建的X对象
del X
# 仅使用前 N 行数据以加快处理速度
X = train[:6000].values
# 删除 train 数据框,释放内存
del train
# 标准化数据
X_std = StandardScaler().fit_transform(X)
# 调用 PCA 方法,选择5个主成分
pca = PCA(n_components=5)
pca.fit(X_std)
X_5d = pca.transform(X_std)
# 为 Plotly 图表中的聚类着色准备目标值(可能是标签)
Target = target[:6000]
上面的代码首先使用Sklearn的方便的StandardScaler调用来对数据进行归一化(实际上对于这个数据集没有必要,因为它们都是1和0)。接下来,用Sklearn的内置PCA函数,通过将n_components参数设置为数据投影到的组件/维度的数量。
可以通过观察捕获的方差比例与每个特征的特征值之间的关系来选择合适的组件,就像解释方差图中所示。实际上,有大量的文献研究了关于如何选择合适的组件的好指标。这里仅对5个组件进行PCA(而不是使用200个组件)。最后,调用fit和transform方法,它们分别对标准化的数字数据集进行PCA模型拟合,并通过对数据进行降维来进行转换。
PCA交互式可视化
对于这些降维方法,散点图是最常用的实现方式,因为可以方便地可视化聚类(如果存在)。接下来绘制前2个主成分的散点图,具体如下:
import plotly.graph_objs as go
import plotly.offline as py
trace0 = go.Scatter(
x = X_5d[:, 0], # 第一主成分
y = X_5d[:, 1], # 第二主成分
mode = 'markers', # 标记模式
text = Target, # 鼠标悬停显示的文本
showlegend = False, # 不显示图例
marker = dict(
size = 8, # 标记大小
color = Target, # 根据目标值设置颜色
colorscale = 'Jet', # 颜色映射表
showscale = False, # 不显示颜色条
line = dict(
width = 2, # 标记边界线宽度
color = 'rgb(255, 255, 255)' # 标记边界线颜色
),
opacity = 0.8 # 标记透明度
)
)
data = [trace0]
layout = go.Layout(
title = '主成分分析 (PCA)', # 图表标题
hovermode = 'closest', # 鼠标悬停模式
xaxis = dict(
title = '第一主成分', # X轴标题
ticklen = 5, # 刻度长度
zeroline = False, # 不显示零线
gridwidth = 2 # 网格线宽度
),
yaxis = dict(
title = '第二主成分', # Y轴标题
ticklen = 5, # 刻度长度
gridwidth = 2 # 网格线宽度
),
showlegend = True # 显示图例
)
fig = dict(data=data, layout=layout)
py.iplot(fig, filename='styled-scatter') # 绘制图表
下面代码使用 Plotly 绘制了一个三维散点图,以展示 PCA 分析的前三个主成分。每个点代表一个数据样本,颜色根据样本的目标值(标签)来设置。
import plotly.graph_objs as go
import plotly.offline as py
trace0 = go.Scatter3d(
x=X_5d[:, 0], # 第一主成分
y=X_5d[:, 1], # 第二主成分
z=X_5d[:, 2], # 第三主成分
mode='markers', # 标记模式
text=Target, # 鼠标悬停显示的文本
showlegend=False, # 不显示图例
marker=dict(
size=8, # 标记大小
color=Target, # 根据目标值设置颜色
colorscale='Jet', # 颜色映射表
showscale=False, # 不显示颜色条
line=dict(
width=2, # 标记边界线宽度
color='rgb(255, 255, 255)' # 标记边界线颜色
),
opacity=0.8 # 标记透明度
)
)
data = [trace0]
layout = go.Layout(
title='主成分分析 (PCA)', # 图表标题
hovermode='closest', # 鼠标悬停模式
scene=dict(
xaxis=dict(title='第一主成分'), # X轴标题
yaxis=dict(title='第二主成分'), # Y轴标题
zaxis=dict(title='第三主成分') # Z轴标题
),
showlegend=True # 显示图例
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='styled-scatter-3d') # 绘制图表
从散点图中可以观察到,可以勉强辨认出一些可辨别的聚类,这些聚类通过颜色的集合斑点表示。这些聚类代表着每个数据点应该对应的底层数字,因此可能会认为在这个部分实现和可视化PCA简单些。
K-Means 聚类识别类别
如何能够在新的特征空间中分离出数据点?可以在新的PCA投影数据上应用聚类算法,能得到不同的聚类。
首先,用Sklearn的KMeans调用设置一个KMeans聚类方法,并使用fit_predict方法来计算聚类中心,并预测第一和第二PCA投影的聚类指数(看看是否能观察到任何明显的聚类)。
from sklearn.cluster import KMeans # 导入 KMeans 聚类算法
import plotly.graph_objs as go
import plotly.offline as py
# 设置 KMeans 聚类,指定簇数为 9,并显式设置 n_init 参数
kmeans = KMeans(n_clusters=9, n_init=10, random_state=42)
# 计算簇中心并预测簇索引
X_clustered = kmeans.fit_predict(X_5d)
# 创建二维散点图对象
trace_Kmeans = go.Scatter(
x=X_5d[:, 0], # 第一主成分
y=X_5d[:, 1], # 第二主成分
mode="markers", # 标记模式
showlegend=False, # 不显示图例
marker=dict(
size=8, # 标记大小
color=X_clustered, # 根据聚类结果设置颜色
colorscale='Portland', # 颜色映射表
showscale=False, # 不显示颜色条
line=dict(
width=2, # 标记边界线宽度
color='rgb(255, 255, 255)' # 标记边界线颜色
)
)
)
# 设置图表布局
layout = go.Layout(
title='KMeans 聚类', # 图表标题
hovermode='closest', # 鼠标悬停模式
xaxis=dict(
title='第一主成分', # X轴标题
ticklen=5, # 刻度长度
zeroline=False, # 不显示零线
gridwidth=2 # 网格线宽度
),
yaxis=dict(
title='第二主成分', # Y轴标题
ticklen=5, # 刻度长度
gridwidth=2 # 网格线宽度
),
showlegend=True # 显示图例
)
# 创建图表对象
data = [trace_Kmeans]
fig1 = dict(data=data, layout=layout)
# 使用 Plotly 绘制图表
py.iplot(fig1, filename="kmeans")
使用 K-Means 聚类算法对 PCA 转换后的数据进行三维聚类,并使用 Plotly 可视化结果。具体来说,它将数据划分为 9 个簇,并在三维图表中绘制前三个主成分的散点图,每个点的颜色代表其所属的簇。
from sklearn.cluster import KMeans # 导入 KMeans 聚类算法
import plotly.graph_objs as go
import plotly.offline as py
# 设置 KMeans 聚类,指定簇数为 9
kmeans = KMeans(n_clusters=9, n_init=10, random_state=42)
# 计算簇中心并预测簇索引
X_clustered = kmeans.fit_predict(X_5d)
# 创建三维散点图对象
trace_Kmeans = go.Scatter3d(
x=X_5d[:, 0], # 第一主成分
y=X_5d[:, 1], # 第二主成分
z=X_5d[:, 2], # 第三主成分
mode="markers", # 标记模式
showlegend=False, # 不显示图例
marker=dict(
size=8, # 标记大小
color=X_clustered, # 根据聚类结果设置颜色
colorscale='Portland', # 颜色映射表
showscale=False, # 不显示颜色条
line=dict(
width=2, # 标记边界线宽度
color='rgb(255, 255, 255)' # 标记边界线颜色
)
)
)
# 设置图表布局
layout = go.Layout(
title='KMeans 聚类', # 图表标题
hovermode='closest', # 鼠标悬停模式
scene=dict(
xaxis=dict(title='第一主成分'), # X轴标题
yaxis=dict(title='第二主成分'), # Y轴标题
zaxis=dict(title='第三主成分') # Z轴标题
),
showlegend=True # 显示图例
)
# 创建图表对象
data = [trace_Kmeans]
fig1 = go.Figure(data=data, layout=layout)
# 使用 Plotly 绘制图表
py.iplot(fig1, filename="kmeans_3d")
由KMeans算法生成的聚类与PCA预测中加入类标签相比,似乎在聚类之间提供了更清晰的分界。因为PCA是一种无监督的方法,因此并不适合用于分离不同的类标签。
4.2线性判别分析(LDA)
线性判别分析 (LDA) 是一种用于分类和降维的技术。LDA 试图找到能够最好地区分各个类别的线性组合特征。与PCA类似,也是一种常用的线性变换方法,通常用于降维任务。然而,与PCA不同的是,LDA属于监督学习方法的范畴。因此,LDA的目标是通过计算能够实现不同类别之间最大分离的组件轴(线性判别子),来最大化不同类别之间的区分度。
LDA实现步骤
LDA的目标是在减少数据集的尺寸的同时保留类的分离信息。因此,从头开始实施该方法可以大致分为以下四个不同的阶段。
- 预测平均数
- 散点矩阵和解决方案
- 选择最佳的投影矩阵
- 将特征转换到新的子空间上
LDA通过Sklearn实现
在经历了理论上的LDA实现的细节之后,现在在实践中实现这个方法。Sklearn工具包也有自己内置的LDA功能,因此我们调用了一个LDA模型,如下所示:
lda = LDA(n_components=5)
# 作为第二个参数传入目标作为标签
X_LDA_2D = lda.fit_transform(X_std, Target.values )
LDA实现的语法与PCA非常相似,即调用拟合和转换方法,用数据拟合LDA模型,然后通过应用LDA降维来进行转换。
import plotly.graph_objs as go
import plotly.offline as py
# 使用 Plotly 绘制散点图
traceLDA = go.Scatter(
x=X_LDA_2D[:, 0],
y=X_LDA_2D[:, 1],
mode='markers',
text=Target,
showlegend=True,
marker=dict(
size=8,
color=Target,
colorscale='Jet',
showscale=False,
line=dict(
width=2,
color='rgb(255, 255, 255)'
),
opacity=0.8
)
)
data = [traceLDA]
layout = go.Layout(
title='线性判别分析 (LDA)',
hovermode='closest',
xaxis=dict(
title='第一线性判别',
ticklen=5,
zeroline=False,
gridwidth=2,
),
yaxis=dict(
title='第二线性判别',
ticklen=5,
gridwidth=2,
),
showlegend=False
)
fig = go.Figure(data=data, layout=layout)
# 使用 Plotly 在线绘制图表
py.iplot(fig, filename='styled-scatter')
使用 Plotly 绘制了 LDA 转换后的三维散点图。这里假设 X_LDA_2D
变量包含了经过 LDA 转换后的三个线性判别成分。
import plotly.graph_objs as go
import plotly.offline as py
# 假设 X_LDA_2D 包含了经过 LDA 转换后的三个线性判别成分
traceLDA = go.Scatter3d(
x=X_LDA_2D[:, 0],
y=X_LDA_2D[:, 1],
z=X_LDA_2D[:, 2],
mode='markers',
text=Target, # 假设 Target 是每个数据点的标签信息
showlegend=True,
marker=dict(
size=8,
color=Target, # 根据目标标签设置颜色
colorscale='Jet',
showscale=False,
line=dict(
width=2,
color='rgb(255, 255, 255)'
),
opacity=0.8
)
)
data = [traceLDA]
layout = go.Layout(
title='线性判别分析 (LDA)',
hovermode='closest',
scene=dict(
xaxis=dict(title='第一线性判别'),
yaxis=dict(title='第二线性判别'),
zaxis=dict(title='第三线性判别')
),
showlegend=False
)
fig = go.Figure(data=data, layout=layout)
# 使用 Plotly 绘制图表
py.iplot(fig, filename='styled-scatter-3d')
从上面的散点图中可以看到,与实施带有类标签的PCA相比,使用LDA时,数据点的聚类更加明显。这是拥有类标签来监督该方法的固有优势。
4.3T-SNE ( t-分布式随机邻接嵌入)
T-SNE是一种非线性降维技术,通常用于将高维数据转换为二维或三维空间,以便进行可视化或进一步分析。T-SNE 尝试保持高维空间中样本之间的局部结构关系,通过随机投影来减少维度,并优化低维空间中样本之间的距离。
# 调用 t-SNE 方法
tsne = TSNE(n_components=2)
tsne_results = tsne.fit_transform(X_std)
通过简单地调用TSNE()来调用t-SNE算法后,我们将数字数据拟合到模型中,并用fit_transform来减少其尺寸。最后,让我们把新特征空间中的前两个成分绘制成散点图。
import plotly.offline as py
import plotly.graph_objs as go
traceTSNE = go.Scatter(
x=tsne_results[:, 0], # t-SNE第一主成分
y=tsne_results[:, 1], # t-SNE第二主成分
name=str(Target), # 将Target转换为字符串,设置名称
hoveron='points', # 设置悬停提示类型为散点
mode='markers',
text=Target, # 指定悬停提示的文本内容为Target变量的值
showlegend=True,
marker=dict(
size=8,
color=Target, # 根据Target变量设置颜色
colorscale='Jet', # 设置颜色映射
showscale=False,
line=dict(
width=2,
color='rgb(255, 255, 255)'
),
opacity=0.8
)
)
data = [traceTSNE]
layout = dict(
title='TSNE (T-Distributed Stochastic Neighbour Embedding)', # 设置图表标题
hovermode='closest',
yaxis=dict(zeroline=False), # 隐藏Y轴的零线
xaxis=dict(zeroline=False), # 隐藏X轴的零线
showlegend=False # 不显示图例
)
fig = dict(data=data, layout=layout)
py.iplot(fig, filename='styled-scatter') # 使用Plotly绘制交互式图表
使用 Plotly 绘制了 t-SNE 转换后的二维散点图,并添加了悬停提示信息。
使用 Plotly 绘制了 t-SNE 转换后的三维散点图,并添加了悬停提示信息。
从t-SNE散点图中可以得出的结论是,首先是聚类(甚至是子聚类)非常清晰分离较好,形成了杰克逊-波洛克式的现代艺术视觉效果,比PCA和LDA方法更加明显。这种能够提供非常好的聚类可视化的能力可以归结为该算法的拓扑保持特性。
然而,t-SNE也有其局限性。由于算法正在识别聚类/子聚类,可能会出现多个局部最小值,这可以从散点图中看出,我们可以看到相同颜色的聚类存在于图中不同区域的2个子聚类中。
五、结论
本方案简要介绍了机器学习从业者常用的三种不同的降维方法——PCA、LDA和t-SNE。触及了寻找主成分和线性判别的概念,以及t-SNE的拓扑保存能力。讨论了使用监督和非监督方法的相对优势,以及在非监督情况下的KMeans聚类技术。
除了这三种常见的降维方法之外,还有一大批其他的降维方法没有在本方案中讨论。仅举几例,它们包括像Sammon's Mapping, Multi-dimensional Scaling或者甚至一些基于图形的可视化方法。