Repository: scikit-learn/scikit-learn
File: sklearn/cluster/_kmeans.py (Python)

Code before:
                n_threads=self._n_threads,
            )

            if self.verbose:
                print(f"Inertia for init {init_idx + 1}/{self._n_init}: {inertia}")
            if best_inertia is None or inertia < best_inertia:
                init_centers = cluster_centers
                best_inertia = inertia

        centers = init_centers
        centers_new = np.empty_like(centers)

        # Initialize counts
        self._counts = np.zeros(self.n_clusters, dtype=X.dtype)

        # Attributes to monitor the convergence
        self._ewa_inertia = None
        self._ewa_inertia_min = None
        self._no_improvement = 0

        # Initialize number of samples seen since last reassignment
        self._n_since_last_reassign = 0

        n_effective_samples = np.sum(sample_weight)
        # Rescaling step for sample weights otherwise doesn not pass test_scaled_weights
        n_steps = int((self.max_iter * n_effective_samples)) // (self._batch_size)
        normalized_sample_weight = sample_weight / np.sum(sample_weight)
        unit_sample_weight = np.ones_like(sample_weight, shape=(self._batch_size,))
        with _get_threadpool_controller().limit(limits=1, user_api="blas"):
            # Perform the iterative optimization until convergence
            for i in range(n_steps):
                # Sample a minibatch from the full dataset
                minibatch_indices = random_state.choice(
                    n_samples,
                    self._batch_size,
                    p=normalized_sample_weight,
                    replace=True,
                )
                # Perform the actual update step on the minibatch data
                # Note: since the sampling of the minibatch is sample_weight aware,
                # we pass fixed unit weights to the `_mini_batch_step` call to avoid
                # accounting for the weights twice. Also note that `_mini_batch_step`
                # can be called with non-unit weights when the caller constructs
                # the batches explicitly by calling the public `partial_fit` method
                # instead.
                batch_inertia = _mini_batch_step(
                    X=X[minibatch_indices],
                    sample_weight=unit_sample_weight,
                    centers=centers,
                    centers_new=centers_new,
                    weight_sums=self._counts,

Review comment (performance):
There's actually an easy way to make MinibatchKMeans pass the scaled weights test: make n_steps independent of the weights as it was before (since max_iter doesn't take weights into account).

That is
```
n_steps = (self.max_iter * n_samples) // self._batch_size
```
note that this is the same as
```
max_iter * sum(sample_weigths) / (batch_size * mean(sample_weights))
```
which is a reasonable expectation.

That way, the name number of batches are processed no matter the scaling of sample weights.
The counterpart is that the total weight seen during fit is scaled by the sample weight scaling but I don't think that it's an issue.


Code after:
                n_threads=self._n_threads,
            )

            if self.verbose:
                print(f"Inertia for init {init_idx + 1}/{self._n_init}: {inertia}")
            if best_inertia is None or inertia < best_inertia:
                init_centers = cluster_centers
                best_inertia = inertia

        centers = init_centers
        centers_new = np.empty_like(centers)

        # Initialize counts
        self._counts = np.zeros(self.n_clusters, dtype=X.dtype)

        # Attributes to monitor the convergence
        self._ewa_inertia = None
        self._ewa_inertia_min = None
        self._no_improvement = 0

        # Initialize number of samples seen since last reassignment
        self._n_since_last_reassign = 0

        sum_of_weights = np.sum(sample_weight)

        n_steps = (self.max_iter * n_samples) // self._batch_size
        normalized_sample_weight = sample_weight / sum_of_weights
        unit_sample_weight = np.ones_like(sample_weight, shape=(self._batch_size,))

        with _get_threadpool_controller().limit(limits=1, user_api="blas"):
            # Perform the iterative optimization until convergence
            for i in range(n_steps):
                # Sample a minibatch from the full dataset
                minibatch_indices = random_state.choice(
                    n_samples,
                    self._batch_size,
                    p=normalized_sample_weight,
                    replace=True,
                )
                # Perform the actual update step on the minibatch data
                # Note: since the sampling of the minibatch is sample_weight aware,
                # we pass fixed unit weights to the `_mini_batch_step` call to avoid
                # accounting for the weights twice. Also note that `_mini_batch_step`
                # can be called with non-unit weights when the caller constructs
                # the batches explicitly by calling the public `partial_fit` method
                # instead.
                batch_inertia = _mini_batch_step(
                    X=X[minibatch_indices],
                    sample_weight=unit_sample_weight,
                    centers=centers,
                    centers_new=centers_new,