Сообщество Engee

Снижение размерности

Автор
avatar-artpgchartpgch
Notebook

Снижение размерности в машинном обучении

Введение

Снижение размерности представляет собой совокупность методов, направленных на преобразование многомерного пространства признаков в пространство существенно меньшей размерности при сохранении значимой структуры исходных данных. Такой подход находит широкое применение в задачах визуализации многомерных массивов информации, позволяя идентифицировать группы схожих объектов и выявлять скрытые закономерности.

Актуальность методов снижения размерности обусловлена несколькими факторами. Во-первых, они обеспечивают возможность графического представления многомерных данных в двух- или трёхмерном пространстве, что существенно облегчает интерпретацию результатов анализа. Во-вторых, данные методы способствуют обнаружению кластерной структуры совокупности наблюдений, которая может оставаться незаметной при рассмотрении исходных признаков по отдельности.

В данном примере мы рассмотрим три различных алгоритма:

  • PCA (Principal Component Analysis) — метод главных компонент для линейного проецирования данных;

  • 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)   
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)
Computing t-SNE   4%|█▋                                  |  ETA: 0:00:04
   KL_divergence: 2.7028 (warmup)Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  23%|████████▎                           |  ETA: 0:00:05
   KL_divergence: 2.2825 (warmup)Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  34%|████████████                        |  ETA: 0:00:04
   KL_divergence: 1.5760Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  40%|██████████████▎                     |  ETA: 0:00:04
   KL_divergence: 0.5194Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  41%|██████████████▉                     |  ETA: 0:00:04
   KL_divergence: 0.4084Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  44%|███████████████▋                    |  ETA: 0:00:04
   KL_divergence: 0.4084Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  46%|████████████████▌                   |  ETA: 0:00:03
   KL_divergence: 0.3932Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  48%|█████████████████▍                  |  ETA: 0:00:03
   KL_divergence: 0.3932Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  59%|█████████████████████▎              |  ETA: 0:00:02
   KL_divergence: 0.3852Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  61%|██████████████████████▏             |  ETA: 0:00:02
   KL_divergence: 0.3837Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  62%|██████████████████████▌             |  ETA: 0:00:02
   KL_divergence: 0.3837Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  64%|███████████████████████▏            |  ETA: 0:00:02
   KL_divergence: 0.3837Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  66%|███████████████████████▋            |  ETA: 0:00:02
   KL_divergence: 0.3827Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  67%|████████████████████████▎           |  ETA: 0:00:02
   KL_divergence: 0.3827Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  68%|████████████████████████▌           |  ETA: 0:00:02
   KL_divergence: 0.3827Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  77%|███████████████████████████▊        |  ETA: 0:00:01
   KL_divergence: 0.3816Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  79%|████████████████████████████▍       |  ETA: 0:00:01
   KL_divergence: 0.3816Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  88%|███████████████████████████████▋    |  ETA: 0:00:01
   KL_divergence: 0.3808Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  90%|████████████████████████████████▎   |  ETA: 0:00:01
   KL_divergence: 0.3808Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  91%|████████████████████████████████▉   |  ETA: 0:00:01
   KL_divergence: 0.3806Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE  93%|█████████████████████████████████▍  |  ETA: 0:00:00
   KL_divergence: 0.3806Warning: ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. 
 - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. 
 - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`.
@ ProgressMeter /usr/local/ijulia-core/packages/ProgressMeter/N660J/src/ProgressMeter.jl:607

Computing t-SNE 100%|████████████████████████████████████| Time: 0:00:06
   KL_divergence: 0.3801

Здесь наблюдается та же структура данных, хотя сам график выглядит иначе из-за особенностей алгоритма t-SNE.

Метод нелинейной проекции, основанный на теории многообразий (UMAP)

Вычислим корреляционную матрицу признаков и применим UMAP для снижения размерности до двух компонент.

In [ ]:
L = cor(data,data,dims=2)
emb = umap(L, 2)
Out[0]:
2×392 Matrix{Float64}:
 8.90004  9.31387  9.12432  8.99644  8.98337  …  -3.69147  -7.46719  -7.7113
 1.0783   1.0703   1.67862  1.1965   1.61616      3.29017  -2.80254  -2.62627

Визуализируем полученные 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}:
 -5.52181  -7.93004  -5.81131  -5.84666  …   2.74843   3.89177   3.82577
  4.20106   2.37663   3.63995   3.76002     -2.97344  -5.61324  -5.86275

Визуализируем UMAP-проекции на точечном графике.

In [ ]:
p5 = Plots.scatter(emb[1,:],emb[2,:],color=uniqueids, legend=false)
Plots.xlabel!(p5,"Первый признак")
Plots.ylabel!(p5,"Второй признак")
display(p5)

Заключение

В данном примере были рассмотрены три метода снижения размерности в применении к набору данных о технических характеристиках автомобилей. Каждый из алгоритмов позволил спроецировать исходное многомерное пространство признаков на плоскость для визуального анализа структуры данных.
Полученные результаты демонстрируют высокую степень согласованности между различными подходами. На всех визуализациях отчётливо наблюдаются три кластера, причём автомобили американского производства образуют два крупных скопления, в то время как японские и европейские модели формируют отдельные смешанные группы.

Данная закономерность свидетельствует о том, что технические характеристики американских автомобилей обладают большей вариативностью и распадаются на несколько различных типов, тогда как японские и европейские модели рассматриваемого периода характеризуются большей однородностью параметров.
Таким образом, снижение размерности выступает эффективным инструментом анализа данных, позволяя выявлять скрытые структуры и взаимосвязи между объектами, недоступные при непосредственном рассмотрении исходных признаков. Согласованность результатов, полученных различными методами, подтверждает устойчивость выявленных паттернов и достоверность сделанных выводов.