AnyMath 文档
Notebook

机器学习中的降维

导言

降维是一组方法,旨在将多维特征空间转换为显着更小的维度空间,同时保持源数据的有意义结构。 这种方法广泛应用于多维信息数组的可视化任务,使我们能够识别相似对象的组并识别隐藏的模式。

降维方法的相关性归因于几个因素。 首先,它们提供了在二维或三维空间中以图形方式表示多维数据的能力,这极大地促进了分析结果的解释。 其次,这些方法有助于检测一组观测值的聚类结构,这些观测值在单独考虑初始特征时可能保持不可察觉。

在这个例子中,我们将看三种不同的算法。:

*PCA(主成分分析)是一种用于线性数据投影的主成分方法;

*t-SNE(t-distributed Stochastic Neighbor Embedding)是一种在保留局部关系的同时进行非线性降维的算法;

*UMAP(Uniform Manifold Approximation and Projection)是一种基于流形理论的非线性投影方法。

初始数据

我们将附加必要的库。

In [ ]:
# EngeePkg.purge()
import Pkg
Pkg.add(["UMAP", "Makie", "XLSX", "VegaDatasets", "DataFrames", "MultivariateStats", "RDatasets", "StatsBase", "Statistics", "LinearAlgebra", "ScikitLearn", "MLBase", "Distances", "TSne", "PyCall"])
using UMAP, Makie, XLSX, VegaDatasets, DataFrames, MultivariateStats, RDatasets, StatsBase, Statistics, LinearAlgebra, ScikitLearn, MLBase, Distances, TSne, PyCall

对于分析,我们将使用来自VegaDatasets包的数据集,其中包括406种不同车型的参数。

In [ ]:
C = DataFrame(VegaDatasets.dataset("cars"))
Out[0]:
406×9 DataFrame
381 rows omitted
RowNameMiles_per_GallonCylindersDisplacementHorsepowerWeight_in_lbsAccelerationYearOrigin
StringFloat64?Int64Float64Int64?Int64Float64StringString
1chevrolet chevelle malibu18.08307.0130350412.01970-01-01USA
2buick skylark 32015.08350.0165369311.51970-01-01USA
3plymouth satellite18.08318.0150343611.01970-01-01USA
4amc rebel sst16.08304.0150343312.01970-01-01USA
5ford torino17.08302.0140344910.51970-01-01USA
6ford galaxie 50015.08429.0198434110.01970-01-01USA
7chevrolet impala14.08454.022043549.01970-01-01USA
8plymouth fury iii14.08440.021543128.51970-01-01USA
9pontiac catalina14.08455.0225442510.01970-01-01USA
10amc ambassador dpl15.08390.019038508.51970-01-01USA
11citroen ds-21 pallasmissing4133.0115309017.51970-01-01Europe
12chevrolet chevelle concours (sw)missing8350.0165414211.51970-01-01USA
13ford torino (sw)missing8351.0153403411.01970-01-01USA
395buick century limited25.06181.0110294516.41982-01-01USA
396oldsmobile cutlass ciera (diesel)38.06262.085301517.01982-01-01USA
397chrysler lebaron medallion26.04156.092258514.51982-01-01USA
398ford granada l22.06232.0112283514.71982-01-01USA
399toyota celica gt32.04144.096266513.91982-01-01Japan
400dodge charger 2.236.04135.084237013.01982-01-01USA
401chevrolet camaro27.04151.090295017.31982-01-01USA
402ford mustang gl27.04140.086279015.61982-01-01USA
403vw pickup44.0497.052213024.61982-01-01Europe
404dodge rampage32.04135.084229511.61982-01-01USA
405ford ranger28.04120.079262518.61982-01-01USA
406chevy s-1031.04119.082272019.41982-01-01USA

我们将删除缺少数据的行,并显示数据集的列名。

In [ ]:
dropmissing!(C)
M = Matrix(C[:,2:7])
names(C)
Out[0]:
9-element Vector{String}:
 "Name"
 "Miles_per_Gallon"
 "Cylinders"
 "Displacement"
 "Horsepower"
 "Weight_in_lbs"
 "Acceleration"
 "Year"
 "Origin"

主成分分析(PCA)

在第一阶段,我们将执行数据居中。

In [ ]:
car_origin = C[:,:Origin]
carmap = labelmap(car_origin)
uniqueids = labelencode(carmap,car_origin)
data = M
data = (data .- mean(data,dims = 1))./ std(data,dims=1)
Out[0]:
392×6 Matrix{Float64}:
 -0.697747   1.48205    1.07591    0.663285   0.619748   -1.28362
 -1.08212    1.48205    1.48683    1.57258    0.842258   -1.46485
 -0.697747   1.48205    1.18103    1.18288    0.539692   -1.64609
 -0.953992   1.48205    1.04725    1.18288    0.53616    -1.28362
 -0.82587    1.48205    1.02813    0.923085   0.554997   -1.82732
 -1.08212    1.48205    2.24177    2.42992    1.60515    -2.00855
 -1.21024    1.48205    2.48068    3.00148    1.62045    -2.37102
 -1.21024    1.48205    2.34689    2.87158    1.57101    -2.55226
 -1.21024    1.48205    2.49023    3.13138    1.70404    -2.00855
 -1.08212    1.48205    1.86908    2.22208    1.02709    -2.55226
 -1.08212    1.48205    1.80219    1.70248    0.689209   -2.00855
 -1.21024    1.48205    1.39127    1.44268    0.743365   -2.73349
 -1.08212    1.48205    1.96464    1.18288    0.922314   -2.18979
  ⋮                                                       ⋮
  0.199113   0.309571  -0.128168   0.143685  -0.0383613   0.311242
  1.86471    0.309571   0.645885  -0.505815   0.0440496   0.528722
  0.327236  -0.862911  -0.367073  -0.323955  -0.462189   -0.377448
 -0.185255   0.309571   0.359199   0.195645  -0.167864   -0.304954
  1.09597   -0.862911  -0.481748  -0.220035  -0.368005   -0.594928
  1.60847   -0.862911  -0.567753  -0.531795  -0.715308   -0.92115
  0.455359  -0.862911  -0.414854  -0.375915  -0.0324748   0.637463
  0.455359  -0.862911  -0.519972  -0.479835  -0.220842    0.0212673
  2.63345   -0.862911  -0.930889  -1.36315   -0.997859    3.28348
  1.09597   -0.862911  -0.567753  -0.531795  -0.803605   -1.4286
  0.583482  -0.862911  -0.711097  -0.661694  -0.415097    1.10867
  0.967851  -0.862911  -0.720653  -0.583754  -0.303253    1.39865

PCA算法将每列解释为单独的观测值,因此我们需要转置原始矩阵。

In [ ]:
data'
Out[0]:
6×392 adjoint(::Matrix{Float64}) with eltype Float64:
 -0.697747  -1.08212   -0.697747  …   1.09597    0.583482   0.967851
  1.48205    1.48205    1.48205      -0.862911  -0.862911  -0.862911
  1.07591    1.48683    1.18103      -0.567753  -0.711097  -0.720653
  0.663285   1.57258    1.18288      -0.531795  -0.661694  -0.583754
  0.619748   0.842258   0.539692     -0.803605  -0.415097  -0.303253
 -1.28362   -1.46485   -1.64609   …  -1.4286     1.10867    1.39865

让我们使用主成分方法构建一个模型。 参数 maxoutdim 设置数据的最终维度。 对于二维可视化,我们将其值设置为2。

In [ ]:
p = fit(PCA, data', maxoutdim=2)
Out[0]:
PCA(indim = 6, outdim = 2, principalratio = 0.9194828785333574)

Pattern matrix (unstandardized loadings):
───────────────────────
         PC1        PC2
───────────────────────
1  -0.873037  -0.20899
2   0.942277   0.126601
3   0.97054    0.092613
4   0.94995   -0.141833
5   0.941156   0.244211
6  -0.638795   0.761967
───────────────────────

Importance of components:
─────────────────────────────────────────────
                                PC1       PC2
─────────────────────────────────────────────
SS Loadings (Eigenvalues)  4.78827   0.728631
Variance explained         0.798044  0.121439
Cumulative variance        0.798044  0.919483
Proportion explained       0.867927  0.132073
Cumulative proportion      0.867927  1.0
─────────────────────────────────────────────

投影矩阵可以使用函数获得 projection.

In [ ]:
P = projection(p)
Out[0]:
6×2 Matrix{Float64}:
  0.398973  -0.244835
 -0.430615   0.148314
 -0.443531   0.108497
 -0.434122  -0.166158
 -0.430103   0.286095
  0.291926   0.892652

具有投影矩阵 我们可以将其应用于单个汽车,如下所示:

In [ ]:
P'*(data[1,:]-mean(p))
Out[0]:
2-element Vector{Float64}:
 -2.323001696522692
 -0.571351964264469

您还可以使用函数转换整个数据 transform.

In [ ]:
Yte = MultivariateStats.transform(p, data')
Out[0]:
2×392 Matrix{Float64}:
 -2.323     -3.20196  -2.66658   -2.60214   …   1.22011  1.70921   1.86951
 -0.571352  -0.68187  -0.992744  -0.621975     -1.87471  0.632857  0.815607

我们还可以使用函数执行从二维空间到原始六维空间的反向转换 reconstruct. 然而,这一次的复苏将接近。

In [ ]:
Xr = reconstruct(p, Yte)
Out[0]:
6×392 Matrix{Float64}:
 -0.786928  -1.11055  -0.820834  …   0.945785   0.526984   0.546196
  0.91558    1.27768   1.00103      -0.803445  -0.64215   -0.684075
  0.968334   1.34619   1.075        -0.744559  -0.689425  -0.740696
  1.1034     1.50334   1.32257      -0.218179  -0.847159  -0.947116
  0.835669   1.18209   0.862883     -1.06112   -0.554079  -0.570742
 -1.18816   -1.54341  -1.66462   …  -1.31728    1.06388    1.27381

我们来估计一下反向转换后数据恢复的误差。

In [ ]:
norm(Xr-data')
Out[0]:
13.743841055569009

我们使用点图可视化结果。:

In [ ]:
p1 = Plots.scatter(Yte[1,car_origin.=="USA"],Yte[2,car_origin.=="USA"],color=1,label="美国")
Plots.xlabel!(p1,"第一个标志")
Plots.ylabel!(p1,"第二个标志")
Plots.scatter!(p1,Yte[1,car_origin.=="Japan"],Yte[2,car_origin.=="Japan"],color=2,label="日本")
Plots.scatter!(p1,Yte[1,car_origin.=="Europe"],Yte[2,car_origin.=="Europe"],color=3,label="欧洲")
display(p1)

可视化显示了三个不同的集群。

让我们训练PCA模型,将数据投影到三维空间,执行转换,并在三维图上可视化结果。

In [ ]:
p = fit(PCA,data',maxoutdim=3)
Yte = MultivariateStats.transform(p, data')
p2 = scatter3d(Yte[1,:],Yte[2,:],Yte[3,:],color=uniqueids,legend=false)
display(p2)

非线性降维同时保持局部关系(t-SNE)

让我们应用t-SNE算法将数据维度减少到两个分量,并在散点图上可视化结果。

In [ ]:
Y2 = tsne(data, 2, 30, 1000, verbose=false, eta=200.0) 
In [ ]:
p3 = Plots.scatter(Y2[:,1], Y2[:,2], 
color=uniqueids, legend=false, size=(400, 300), 
markersize=3, title="t-SNE可视化")
Plots.xlabel!(p3,"第一个标志")
Plots.ylabel!(p3,"第二个标志")
display(p3)

这里观察到相同的数据结构,尽管由于t-SNE算法的特殊性,图本身看起来不同。

基于流形理论的非线性投影方法(UMAP)

让我们计算特征的相关矩阵,并应用UMAP将维度降低到两个分量。

In [ ]:
L = cor(data,data,dims=2)
emb = umap(L, 2)
Out[0]:
2×392 Matrix{Float64}:
 9.05422  8.63462  8.63881  9.05669  8.83635  …  -3.6627   -6.24698  -6.4616
 2.78768  2.53555  3.36606  2.57112  3.25251      5.45083  -1.99136  -1.71899

我们在点图上可视化获得的UMAP投影。

In [ ]:
p4 = Plots.scatter(emb[1,:],emb[2,:],color=uniqueids,legend=false)
Plots.xlabel!(p4,"第一个标志")
Plots.ylabel!(p4,"第二个标志")
display(p4)

UMAP算法允许使用替代方法来计算对象之间的两两距离。 让我们计算所有观测值对之间的欧几里德距离,并将UMAP应用于相似性矩阵。

In [ ]:
L = pairwise(Euclidean(), data, data,dims=1) 
emb = umap(-L, 2)
Out[0]:
2×392 Matrix{Float64}:
 -6.60819  -8.88751  -7.2234   -7.1497   …   5.48309   2.84922   2.73662
  5.93806   3.48894   5.82943   5.80849     -4.54102  -3.01769  -3.13033

我们在点图上可视化UMAP投影。

In [ ]:
p5 = Plots.scatter(emb[1,:],emb[2,:],color=uniqueids, legend=false)
Plots.xlabel!(p5,"第一个标志")
Plots.ylabel!(p5,"第二个标志")
display(p5)

结论

在这个例子中,当应用于一组关于车辆规格的数据时,考虑了三种降维方法。 每种算法都可以将原始多维特征空间投影到平面上,以便对数据结构进行可视化分析。
所获得的结果表明不同方法之间的高度一致性。 三个集群在所有可视化中都清晰可见,美国制造的汽车形成了两个大集群,而日本和欧洲的车型形成了单独的混合组。

这种模式表明,美国汽车的技术特征更加多变,分为几种不同的类型,而本报告所述期间的日本和欧洲车型的特征在于参数的更大均匀性。
因此,降维是一种有效的数据分析工具,允许您识别在直接检查初始要素时无法访问的对象之间的隐藏结构和关系。 通过各种方法获得的结果的一致性证实了所识别的模式的稳定性和得出的结论的可靠性。