Я использую K-medoids, чтобы сократить свои данные (потенциальные будущие сценарии цены акций) с 400 сценариев до n = 25. Я вычислил амтрикс расстояния на основе евклидова расстояния:
C = pairwise(Euclidean(), data', data')
где data
мои 400 сценариев - файл данных можно найти по ссылке . Затем я запускаю К-медоиды:
kmedoids(C, n, init = :kmpp)
Я получаю ошибку:
ERROR: AssertionError: !(isempty(grp))
Кто-нибудь из вас знает, что может быть не так? Кажется, на Python он работает нормально (из sklearn_extra.cluster import KMedoids), но я думаю, что две функции K-medoids разные. Мне не удалось найти документацию о том, почему это не удается.
(В общем, предоставьте минимальный полный работоспособный пример кода, демонстрирующего проблему, чтобы читатели могли быстро воспроизвести ее, импортировав те же пакеты и прочитав в тот же объект данных. Я предположил следующее.)
import Clustering.kmedoids
import Distances.Euclidean
import Distances.pairwise
import DelimitedFiles.readdlm
data = readdlm("data.csv")
(Теперь ваш код воспроизводит ошибку.)
n = 25
C = pairwise(Euclidean(), data', data')
kmedoids(C, n, init = :kmpp)
ERROR: AssertionError: !(isempty(grp))
Stacktrace:
[1] _find_medoid(dist::Matrix{Float64}, grp::Vector{Int64})
@ Clustering C:\Users\...\.julia\packages\Clustering\JwhfU\src\kmedoids.jl:229
[2] _kmedoids!(medoids::Vector{Int64}, dist::Matrix{Float64}, maxiter::Int64, tol::Float64, displevel::Int64)
@ Clustering C:\Users\...\.julia\packages\Clustering\JwhfU\src\kmedoids.jl:148
[3] kmedoids(dist::Matrix{Float64}, k::Int64; init::Symbol, maxiter::Int64, tol::Float64, display::Symbol)
@ Clustering C:\Users\...\.julia\packages\Clustering\JwhfU\src\kmedoids.jl:77
[4] top-level scope
@ REPL[13]:1
Возможно, в данных недостаточно различных значений для создания 25 или более различных непустых кластеров.
Set(data)
производит
Set{Float64} with 24 elements:
-26.917
12.619999999999997
12.811
12.79
-4.722000000000001
-27.619000000000003
12.771
11.009999999999998
12.800999999999995
-27.649
9.980999999999995
12.780999999999999
10.780000000000001
12.820999999999998
12.790999999999997
-27.001
-27.586000000000002
10.210999999999999
7.9809999999999945
11.989999999999995
-4.579000000000001
7.9709999999999965
9.190999999999995
-11.868000000000002
Справочная документация ?kmediods
REPL заканчивается примечанием об алгоритме.
Функция реализует алгоритм стиля K-средних вместо PAM (разделение вокруг медоидов). K-означает стиль Алгоритм сходится за меньшее количество итераций, но было показано, что он дает худшие результаты (общие затраты выше на 10-20%) (см. например Шуберт и Руссеу (2019)).
Clustering.kmedoids
документация ссылки
Тейтц, М.Б. и Барт П. (1968). Эвристические методы оценки обобщенной вершинной медианы взвешенного графа. Исследование операций, 16(5), 955–961. doi:10.1287/opre.16.5.955
Шуберт Э. и Русси П.Дж. (2019). Ускоренная кластеризация k-медоидов: улучшение алгоритмов PAM, CLARA и CLARANS. СИСАП, 171–187. doi:10.1007/978-3-030-32047-8_16
Пожалуйста!
Основная причина находится в ответе @user9712582.
В определенных ситуациях вы предпочитаете иметь пустые классы, а не ошибку. В таких случаях вы можете использовать BetaML.KMedoidsClusterer:
julia> using HTTP, DelimitedFiles, Pipe, BetaML, Statistics
julia> url = "https://github.com/user-attachments/files/16040190/data.csv";
julia> data = @pipe HTTP.get(url).body |> readdlm(_);
julia> unique(data)
24-element Vector{Float64}:
12.79
12.790999999999997
12.619999999999997
12.820999999999998
-11.868000000000002
12.771
12.800999999999995
-27.619000000000003
9.190999999999995
9.980999999999995
-27.649
⋮
-26.917
12.811
-4.579000000000001
10.780000000000001
10.210999999999999
-27.001
11.989999999999995
7.9809999999999945
-4.722000000000001
-27.586000000000002
julia> m = KMedoidsClusterer(n_classes=25, initialisation_strategy = "grid")
KMedoidsClusterer - A K-Medoids Model (unfitted)
julia> classes = fit!(m,data)
500-element Vector{Int64}:
25
25
25
25
25
25
10
25
10
10
25
⋮
10
10
10
25
25
25
1
10
10
10
julia> unique(classes)
6-element Vector{Int64}:
25
10
1
23
24
15
Тем не менее, для ваших данных достаточно 4 классов:
pd = pairwise(data)
for i in 1:25
m = KMedoidsClusterer(n_classes=i,initialisation_strategy = "grid")
classes = fit!(m,data)
s = mean(silhouette(pd,classes))
n_unique = length(unique(classes))
println("Avg siluette for $i classes ($n_unique unique): $s")
end
Avg siluette for 1 classes (1 unique): 1.0
Avg siluette for 2 classes (2 unique): 0.9767906918585568
Avg siluette for 3 classes (3 unique): 0.99164186448072
Avg siluette for 4 classes (4 unique): 0.9929543302371584
Avg siluette for 5 classes (4 unique): 0.9929543302371584
Avg siluette for 6 classes (4 unique): 0.9929543302371584
Avg siluette for 7 classes (5 unique): 0.9858369866514152
Avg siluette for 8 classes (5 unique): 0.9858369866514152
Avg siluette for 9 classes (5 unique): 0.9858369866514152
Avg siluette for 10 classes (5 unique): 0.9858369866514152
Avg siluette for 11 classes (5 unique): 0.9858369866514152
Avg siluette for 12 classes (5 unique): 0.9858369866514152
Avg siluette for 13 classes (5 unique): 0.9858369866514152
Avg siluette for 14 classes (5 unique): 0.9858369866514152
Avg siluette for 15 classes (5 unique): 0.9858369866514152
Avg siluette for 16 classes (5 unique): 0.9858369866514152
Avg siluette for 17 classes (6 unique): 0.9872595910397074
Avg siluette for 18 classes (6 unique): 0.9872595910397074
Avg siluette for 19 classes (6 unique): 0.9872595910397074
Avg siluette for 20 classes (6 unique): 0.9872595910397074
Avg siluette for 21 classes (6 unique): 0.9872595910397074
Avg siluette for 22 classes (6 unique): 0.9872595910397074
Avg siluette for 23 classes (6 unique): 0.9861063345422215
Avg siluette for 24 classes (6 unique): 0.9861063345422215
Avg siluette for 25 classes (6 unique): 0.9861063345422215
Спасибо большое, это именно то, что я хотел :)
Думаю, вы абсолютно правы — большое спасибо :) И спасибо за отзыв по вопросу в целом :)