55
66import numpy as _np
77import torch as _torch
8- import torch .distributed as _dist
9- from coremltools ._deps import _kmeans1d
10-
11- from coreai_opt ._utils .import_utils import lazy_import_module
128
139
1410class _EfficientKMeans :
@@ -19,21 +15,18 @@ class _EfficientKMeans:
1915 def __init__ (
2016 self ,
2117 n_clusters : int ,
22- init : str | _torch . Tensor ,
18+ init : str ,
2319 n_init : int = 0 ,
24- labels = None ,
2520 max_iter : int = 100 ,
2621 tol : float = 0.0001 ,
27- error_bnd : float = 0.0 ,
2822 ):
2923 self .n_clusters = n_clusters
3024 self .n_init = n_init
3125 self .max_iter = max_iter
3226 self .tol = tol
33- self .labels_ = labels
27+ self .labels_ = None
3428 self .inertia_ = None
3529 self .cluster_centers_ = init
36- self .error_bnd = error_bnd
3730
3831 assert self .max_iter > 0
3932 assert self .n_clusters > 0
@@ -69,21 +62,9 @@ def _get_cluster_avg(
6962
7063 return v_avg .to (vals .dtype )
7164
72- @staticmethod
73- def x_c_dist (params : _torch .Tensor , clusters : _torch .Tensor ) -> _torch .Tensor :
74- """
75- Calculate the distance between weights and clusters.
76- """
77- clusters = clusters .contiguous ()
78-
79- if _torch .finfo (params .dtype ).bits > _torch .finfo (clusters .dtype ).bits :
80- return _torch .cdist (params .to (clusters .dtype ), clusters ).square ()
81- else :
82- return _torch .cdist (params , clusters .to (params .dtype )).square ()
83-
8465 def _kmeans_pp (
8566 self , parameters : _torch .Tensor , sample_weight : _torch .Tensor | None = None
86- ) -> "_EfficientKMeans" :
67+ ) -> None :
8768 assert len (parameters ) >= self .n_clusters
8869
8970 num_update_list = []
@@ -166,8 +147,6 @@ def _kmeans_pp(
166147 if len (num_update_list ) >= INIT_EXIT and sum (num_update_list [- INIT_EXIT :]) == 0 :
167148 break
168149
169- return self
170-
171150 def fit (
172151 self , X : _torch .Tensor , sample_weight : _torch .Tensor | None = None
173152 ) -> "_EfficientKMeans" :
@@ -178,106 +157,13 @@ def fit(
178157
179158 assert N >= self .n_clusters , f"too many clusters { self .n_clusters } for { N } samples"
180159
181- if isinstance (self .cluster_centers_ , str ):
182- if "kmeans++" in self .cluster_centers_ :
183- if _dist .is_available () and _dist .is_initialized ():
184- rank = _dist .get_rank ()
185- else :
186- rank = 0
187-
188- if "cpu" in self .cluster_centers_ :
189-
190- def _import_sklearn ():
191- import sklearn as sk # noqa: PLC0415
192-
193- return sk
194-
195- sk = lazy_import_module (
196- _import_sklearn ,
197- "sklearn is required. Install it with: pip install scikit-learn" ,
198- )
199-
200- if "minibatch" in self .cluster_centers_ :
201- clustering_method = sk .cluster .MiniBatchKMeans
202- else :
203- clustering_method = sk .cluster .KMeans
204-
205- kmeans = clustering_method (
206- n_init = self .n_init ,
207- n_clusters = self .n_clusters ,
208- max_iter = self .max_iter ,
209- random_state = rank + 1 ,
210- tol = self .tol ,
211- ).fit (X .float ().cpu ().numpy (), sample_weight = sample_weight )
212- self .inertia_ = _torch .Tensor ([kmeans .inertia_ ]).to (X .device )
213- self .labels_ = _torch .from_numpy (kmeans .labels_ ).int ().to (X .device )
214- self .cluster_centers_ = None
215- else :
216- self ._kmeans_pp (X .float (), sample_weight = sample_weight )
217-
218- self .cluster_centers_ = _EfficientKMeans ._get_cluster_avg (
219- self .n_clusters , self .labels_ , X , sample_weight = sample_weight
220- )
221-
222- elif self .cluster_centers_ == "opt1d" :
223- self .labels_ , self .cluster_centers_ = _kmeans1d .cluster (
224- X , self .n_clusters , weights = sample_weight
225- )
226-
227- self .n_clusters = len (self .cluster_centers_ )
228- self .cluster_centers_ = (
229- _torch .Tensor (self .cluster_centers_ )
230- .to (device = X .device , dtype = X .dtype )
231- .view (- 1 , 1 )
232- )
233- self .labels_ = _torch .Tensor (self .labels_ ).int ().to (X .device )
234-
235- min_error , _ = _EfficientKMeans .x_c_dist (X , self .cluster_centers_ ).min (dim = - 1 )
236- self .inertia_ = min_error .sum ()
237- else :
238- self .inertia_ = None
239-
240- for _ in range (self .max_iter ):
241- self .cluster_centers_ = _EfficientKMeans ._get_cluster_avg (
242- self .n_clusters , self .labels_ , X , sample_weight = sample_weight
243- )
160+ if self .cluster_centers_ != "kmeans++" :
161+ raise ValueError (f"init must be 'kmeans++'; got { self .cluster_centers_ !r} " )
244162
245- # remove empty clusters perhaps due to pruning
246- nan_centers = self .cluster_centers_ .isnan ()
247- if nan_centers .any ():
248- self ._kmeans_pp (X , sample_weight = sample_weight )
249- continue
163+ self ._kmeans_pp (X .float (), sample_weight = sample_weight )
250164
251- x_c_dist = _EfficientKMeans .x_c_dist (X , self .cluster_centers_ )
252- min_error , self .labels_ = x_c_dist .min (dim = - 1 )
253- cur_inertia = min_error .sum ()
254-
255- if self .error_bnd and _torch .sqrt (cur_inertia / N ) < self .error_bnd :
256- unique , counts = _torch .unique (self .labels_ , return_counts = True )
257- idx = unique [counts .argmin ()]
258-
259- reduce_cluster_centers_ = self .cluster_centers_ .clone ()
260- reduce_cluster_centers_ [idx ] = _np .nan
261-
262- reduce_cluster_centers_ = reduce_cluster_centers_ [
263- ~ _torch .isnan (reduce_cluster_centers_ )
264- ].view (- 1 , 1 )
265- reduce_min_error , reduce_labels_ = _EfficientKMeans .x_c_dist (
266- X , reduce_cluster_centers_
267- ).min (dim = - 1 )
268- reduce_inertia = reduce_min_error .sum ()
269- rmse_error = _torch .sqrt (reduce_inertia / N )
270-
271- if rmse_error < self .error_bnd :
272- self .cluster_centers_ = reduce_cluster_centers_
273- self .labels_ = reduce_labels_
274- self .n_clusters = len (self .cluster_centers_ )
275- continue
276-
277- if self .inertia_ is None or abs (self .inertia_ - cur_inertia ) > self .tol :
278- self .inertia_ = cur_inertia
279- else :
280- self .inertia_ = cur_inertia
281- break
165+ self .cluster_centers_ = _EfficientKMeans ._get_cluster_avg (
166+ self .n_clusters , self .labels_ , X , sample_weight = sample_weight
167+ )
282168
283169 return self
0 commit comments