Skip to content

Commit e95461b

Browse files
authored
Merge pull request #70 from Arkoniak/broad_types
types broadening
2 parents 9e39b57 + c04d65b commit e95461b

File tree

10 files changed

+212
-94
lines changed

10 files changed

+212
-94
lines changed

src/elkan.jl

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@ struct Elkan <: AbstractKMeansAlg end
2121
function kmeans!(alg::Elkan, containers, X, k;
2222
n_threads = Threads.nthreads(),
2323
k_init = "k-means++", max_iters = 300,
24-
tol = 1e-6, verbose = false, init = nothing)
24+
tol = eltype(X)(1e-6), verbose = false, init = nothing)
2525
nrow, ncol = size(X)
2626
centroids = init == nothing ? smart_init(X, k, n_threads, init=k_init).centroids : deepcopy(init)
2727

2828
update_containers(alg, containers, centroids, n_threads)
2929
@parallelize n_threads ncol chunk_initialize(alg, containers, centroids, X)
3030

31+
T = eltype(X)
3132
converged = false
3233
niters = 0
33-
J_previous = 0.0
34+
J_previous = zero(T)
3435

3536
# Update centroids & labels with closest members until convergence
3637
while niters < max_iters
@@ -80,37 +81,38 @@ function kmeans!(alg::Elkan, containers, X, k;
8081
# TODO empty placeholder vectors should be calculated
8182
# TODO Float64 type definitions is too restrictive, should be relaxed
8283
# especially during GPU related development
83-
return KmeansResult(centroids, containers.labels, Float64[], Int[], Float64[], totalcost, niters, converged)
84+
return KmeansResult(centroids, containers.labels, T[], Int[], T[], totalcost, niters, converged)
8485
end
8586

86-
function create_containers(::Elkan, k, nrow, ncol, n_threads)
87+
function create_containers(alg::Elkan, X, k, nrow, ncol, n_threads)
88+
T = eltype(X)
8789
lng = n_threads + 1
88-
centroids_new = Vector{Array{Float64,2}}(undef, lng)
89-
centroids_cnt = Vector{Vector{Int}}(undef, lng)
90+
centroids_new = Vector{Matrix{T}}(undef, lng)
91+
centroids_cnt = Vector{Vector{T}}(undef, lng)
9092

9193
for i = 1:lng
92-
centroids_new[i] = zeros(nrow, k)
93-
centroids_cnt[i] = zeros(k)
94+
centroids_new[i] = zeros(T, nrow, k)
95+
centroids_cnt[i] = zeros(T, k)
9496
end
9597

96-
centroids_dist = Matrix{Float64}(undef, k, k)
98+
centroids_dist = Matrix{T}(undef, k, k)
9799

98100
# lower bounds
99-
lb = Matrix{Float64}(undef, k, ncol)
101+
lb = Matrix{T}(undef, k, ncol)
100102

101103
# upper bounds
102-
ub = Vector{Float64}(undef, ncol)
104+
ub = Vector{T}(undef, ncol)
103105

104106
# r(x) in original paper, shows whether point distance should be updated
105107
stale = ones(Bool, ncol)
106108

107109
# distance that centroid moved
108-
p = Vector{Float64}(undef, k)
110+
p = Vector{T}(undef, k)
109111

110112
labels = zeros(Int, ncol)
111113

112114
# total_sum_calculation
113-
sum_of_squares = Vector{Float64}(undef, n_threads)
115+
sum_of_squares = Vector{T}(undef, n_threads)
114116

115117
return (
116118
centroids_new = centroids_new,
@@ -132,6 +134,7 @@ function chunk_initialize(::Elkan, containers, centroids, X, r, idx)
132134
labels = containers.labels
133135
centroids_new = containers.centroids_new[idx]
134136
centroids_cnt = containers.centroids_cnt[idx]
137+
T = eltype(X)
135138

136139
@inbounds for i in r
137140
min_dist = distance(X, centroids, i, 1)
@@ -150,7 +153,7 @@ function chunk_initialize(::Elkan, containers, centroids, X, r, idx)
150153
end
151154
ub[i] = min_dist
152155
labels[i] = label
153-
centroids_cnt[label] += 1
156+
centroids_cnt[label] += one(T)
154157
for j in axes(X, 1)
155158
centroids_new[j, label] += X[j, i]
156159
end
@@ -160,10 +163,11 @@ end
160163
function update_containers(::Elkan, containers, centroids, n_threads)
161164
# unpack containers for easier manipulations
162165
centroids_dist = containers.centroids_dist
166+
T = eltype(centroids)
163167

164168
k = size(centroids_dist, 1) # number of clusters
165169
@inbounds for j in axes(centroids_dist, 2)
166-
min_dist = Inf
170+
min_dist = T(Inf)
167171
for i in j + 1:k
168172
d = distance(centroids, centroids, i, j)
169173
centroids_dist[i, j] = d
@@ -179,7 +183,7 @@ function update_containers(::Elkan, containers, centroids, n_threads)
179183
# TODO: oh, one should be careful here. inequality holds for eucledian metrics
180184
# not square eucledian. So, for Lp norm it should be something like
181185
# centroids_dist = 0.5^p. Should check one more time original paper
182-
centroids_dist .*= 0.25
186+
centroids_dist .*= T(0.25)
183187

184188
return centroids_dist
185189
end
@@ -193,6 +197,7 @@ function chunk_update_centroids(::Elkan, containers, centroids, X, r, idx)
193197
stale = containers.stale
194198
centroids_new = containers.centroids_new[idx]
195199
centroids_cnt = containers.centroids_cnt[idx]
200+
T = eltype(X)
196201

197202
@inbounds for i in r
198203
label_old = labels[i]
@@ -226,8 +231,8 @@ function chunk_update_centroids(::Elkan, containers, centroids, X, r, idx)
226231

227232
if label != label_old
228233
labels[i] = label
229-
centroids_cnt[label_old] -= 1
230-
centroids_cnt[label] += 1
234+
centroids_cnt[label_old] -= one(T)
235+
centroids_cnt[label] += one(T)
231236
for j in axes(X, 1)
232237
centroids_new[j, label_old] -= X[j, i]
233238
centroids_new[j, label] += X[j, i]
@@ -251,12 +256,13 @@ function chunk_update_bounds(alg, containers, centroids, r, idx)
251256
ub = containers.ub
252257
stale = containers.stale
253258
labels = containers.labels
259+
T = eltype(centroids)
254260

255261
@inbounds for i in r
256262
for j in axes(centroids, 2)
257-
lb[j, i] = lb[j, i] > p[j] ? lb[j, i] + p[j] - 2*sqrt(abs(lb[j, i]*p[j])) : 0.0
263+
lb[j, i] = lb[j, i] > p[j] ? lb[j, i] + p[j] - T(2)*sqrt(abs(lb[j, i]*p[j])) : zero(T)
258264
end
259265
stale[i] = true
260-
ub[i] += p[labels[i]] + 2*sqrt(abs(ub[i]*p[labels[i]]))
266+
ub[i] += p[labels[i]] + T(2)*sqrt(abs(ub[i]*p[labels[i]]))
261267
end
262268
end

src/hamerly.jl

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ struct Hamerly <: AbstractKMeansAlg end
2121
function kmeans!(alg::Hamerly, containers, X, k;
2222
n_threads = Threads.nthreads(),
2323
k_init = "k-means++", max_iters = 300,
24-
tol = 1e-6, verbose = false, init = nothing)
24+
tol = eltype(X)(1e-6), verbose = false, init = nothing)
2525
nrow, ncol = size(X)
2626
centroids = init == nothing ? smart_init(X, k, n_threads, init=k_init).centroids : deepcopy(init)
2727

2828
@parallelize n_threads ncol chunk_initialize(alg, containers, centroids, X)
2929

30+
T = eltype(X)
3031
converged = false
3132
niters = 0
32-
J_previous = 0.0
33+
J_previous = zero(T)
3334
p = containers.p
3435

3536
# Update centroids & labels with closest members until convergence
@@ -70,35 +71,36 @@ function kmeans!(alg::Hamerly, containers, X, k;
7071
# TODO empty placeholder vectors should be calculated
7172
# TODO Float64 type definitions is too restrictive, should be relaxed
7273
# especially during GPU related development
73-
return KmeansResult(centroids, containers.labels, Float64[], Int[], Float64[], totalcost, niters, converged)
74+
return KmeansResult(centroids, containers.labels, T[], Int[], T[], totalcost, niters, converged)
7475
end
7576

76-
function create_containers(alg::Hamerly, k, nrow, ncol, n_threads)
77+
function create_containers(alg::Hamerly, X, k, nrow, ncol, n_threads)
78+
T = eltype(X)
7779
lng = n_threads + 1
78-
centroids_new = Vector{Array{Float64,2}}(undef, lng)
79-
centroids_cnt = Vector{Vector{Int}}(undef, lng)
80+
centroids_new = Vector{Matrix{T}}(undef, lng)
81+
centroids_cnt = Vector{Vector{T}}(undef, lng)
8082

8183
for i = 1:lng
82-
centroids_new[i] = zeros(nrow, k)
83-
centroids_cnt[i] = zeros(k)
84+
centroids_new[i] = zeros(T, nrow, k)
85+
centroids_cnt[i] = zeros(T, k)
8486
end
8587

8688
# Upper bound to the closest center
87-
ub = Vector{Float64}(undef, ncol)
89+
ub = Vector{T}(undef, ncol)
8890

8991
# lower bound to the second closest center
90-
lb = Vector{Float64}(undef, ncol)
92+
lb = Vector{T}(undef, ncol)
9193

9294
labels = zeros(Int, ncol)
9395

9496
# distance that centroid has moved
95-
p = Vector{Float64}(undef, k)
97+
p = Vector{T}(undef, k)
9698

9799
# distance from the center to the closest other center
98-
s = Vector{Float64}(undef, k)
100+
s = Vector{T}(undef, k)
99101

100102
# total_sum_calculation
101-
sum_of_squares = Vector{Float64}(undef, n_threads)
103+
sum_of_squares = Vector{T}(undef, n_threads)
102104

103105
return (
104106
centroids_new = centroids_new,
@@ -118,12 +120,13 @@ end
118120
Initial calulation of all bounds and points labeling.
119121
"""
120122
function chunk_initialize(alg::Hamerly, containers, centroids, X, r, idx)
123+
T = eltype(X)
121124
centroids_cnt = containers.centroids_cnt[idx]
122125
centroids_new = containers.centroids_new[idx]
123126

124127
@inbounds for i in r
125128
label = point_all_centers!(containers, centroids, X, i)
126-
centroids_cnt[label] += 1
129+
centroids_cnt[label] += one(T)
127130
for j in axes(X, 1)
128131
centroids_new[j, label] += X[j, i]
129132
end
@@ -136,12 +139,13 @@ end
136139
Calculates minimum distances from centers to each other.
137140
"""
138141
function update_containers(::Hamerly, containers, centroids, n_threads)
142+
T = eltype(centroids)
139143
s = containers.s
140-
s .= Inf
144+
s .= T(Inf)
141145
@inbounds for i in axes(centroids, 2)
142146
for j in i+1:size(centroids, 2)
143147
d = distance(centroids, centroids, i, j)
144-
d = 0.25*d
148+
d = T(0.25)*d
145149
s[i] = s[i] > d ? d : s[i]
146150
s[j] = s[j] > d ? d : s[j]
147151
end
@@ -164,6 +168,7 @@ function chunk_update_centroids(alg::Hamerly, containers, centroids, X, r, idx)
164168
s = containers.s
165169
lb = containers.lb
166170
ub = containers.ub
171+
T = eltype(X)
167172

168173
@inbounds for i in r
169174
# m ← max(s(a(i))/2, l(i))
@@ -178,8 +183,8 @@ function chunk_update_centroids(alg::Hamerly, containers, centroids, X, r, idx)
178183
label_new = point_all_centers!(containers, centroids, X, i)
179184
if label != label_new
180185
labels[i] = label_new
181-
centroids_cnt[label_new] += 1
182-
centroids_cnt[label] -= 1
186+
centroids_cnt[label_new] += one(T)
187+
centroids_cnt[label] -= one(T)
183188
for j in axes(X, 1)
184189
centroids_new[j, label_new] += X[j, i]
185190
centroids_new[j, label] -= X[j, i]
@@ -199,9 +204,10 @@ function point_all_centers!(containers, centroids, X, i)
199204
ub = containers.ub
200205
lb = containers.lb
201206
labels = containers.labels
207+
T = eltype(X)
202208

203-
min_distance = Inf
204-
min_distance2 = Inf
209+
min_distance = T(Inf)
210+
min_distance2 = T(Inf)
205211
label = 1
206212
@inbounds for k in axes(centroids, 2)
207213
dist = distance(X, centroids, i, k)
@@ -230,9 +236,10 @@ in `centroids` and `p` respectively.
230236
function move_centers(::Hamerly, containers, centroids)
231237
centroids_new = containers.centroids_new[end]
232238
p = containers.p
239+
T = eltype(centroids)
233240

234241
@inbounds for i in axes(centroids, 2)
235-
d = 0.0
242+
d = zero(T)
236243
for j in axes(centroids, 1)
237244
d += (centroids[j, i] - centroids_new[j, i])^2
238245
centroids[j, i] = centroids_new[j, i]
@@ -251,6 +258,7 @@ function chunk_update_bounds(alg::Hamerly, containers, r1, r2, pr1, pr2, r, idx)
251258
ub = containers.ub
252259
lb = containers.lb
253260
labels = containers.labels
261+
T = eltype(containers.ub)
254262

255263
# Since bounds are squred distance, `sqrt` is used to make corresponding estimation, unlike
256264
# the original paper, where usual metric is used.
@@ -270,11 +278,11 @@ function chunk_update_bounds(alg::Hamerly, containers, r1, r2, pr1, pr2, r, idx)
270278
# The same applies to the lower bounds.
271279
@inbounds for i in r
272280
label = labels[i]
273-
ub[i] += 2*sqrt(abs(ub[i] * p[label])) + p[label]
281+
ub[i] += T(2)*sqrt(abs(ub[i] * p[label])) + p[label]
274282
if r1 == label
275-
lb[i] = lb[i] <= pr2 ? 0.0 : lb[i] + pr2 - 2*sqrt(abs(pr2*lb[i]))
283+
lb[i] = lb[i] <= pr2 ? zero(T) : lb[i] + pr2 - T(2)*sqrt(abs(pr2*lb[i]))
276284
else
277-
lb[i] = lb[i] <= pr1 ? 0.0 : lb[i] + pr1 - 2*sqrt(abs(pr1*lb[i]))
285+
lb[i] = lb[i] <= pr1 ? zero(T) : lb[i] + pr1 - T(2)*sqrt(abs(pr1*lb[i]))
278286
end
279287
end
280288
end
@@ -284,10 +292,10 @@ end
284292
285293
Finds maximum and next after maximum arguments.
286294
"""
287-
function double_argmax(p)
295+
function double_argmax(p::AbstractVector{T}) where T
288296
r1, r2 = 1, 1
289297
d1 = p[1]
290-
d2 = -1.0
298+
d2 = T(-Inf)
291299
for i in 2:length(p)
292300
if p[i] > d1
293301
r2 = r1

src/kmeans.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ end
9292
Allocationless calculation of square eucledean distance between vectors X1[:, i1] and X2[:, i2]
9393
"""
9494
function distance(X1, X2, i1, i2)
95-
d = 0.0
95+
d = zero(eltype(X1))
9696
# TODO: break of the loop if d is larger than threshold (known minimum disatnce)
9797
@inbounds @simd for i in axes(X1, 1)
9898
d += (X1[i, i1] - X2[i, i2])^2
@@ -110,7 +110,7 @@ design matrix(x), centroids (centre), and the number of desired groups (k).
110110
A Float type representing the computed metric is returned.
111111
"""
112112
function sum_of_squares(containers, x, labels, centre, r, idx)
113-
s = 0.0
113+
s = zero(eltype(x))
114114

115115
@inbounds for j in r
116116
for i in axes(x, 1)
@@ -151,9 +151,9 @@ A `KmeansResult` structure representing labels, centroids, and sum_squares is re
151151
function kmeans(alg, design_matrix, k;
152152
n_threads = Threads.nthreads(),
153153
k_init = "k-means++", max_iters = 300,
154-
tol = 1e-6, verbose = false, init = nothing)
154+
tol = eltype(design_matrix)(1e-6), verbose = false, init = nothing)
155155
nrow, ncol = size(design_matrix)
156-
containers = create_containers(alg, k, nrow, ncol, n_threads)
156+
containers = create_containers(alg, design_matrix, k, nrow, ncol, n_threads)
157157

158158
return kmeans!(alg, containers, design_matrix, k, n_threads = n_threads,
159159
k_init = k_init, max_iters = max_iters, tol = tol,

0 commit comments

Comments
 (0)