Skip to content

Optimize Fuzzy C Means - #432

Open
andrewdalpino wants to merge 1 commit into
masterfrom
optimize-fuzzy-c-means
Open

Optimize Fuzzy C Means#432
andrewdalpino wants to merge 1 commit into
masterfrom
optimize-fuzzy-c-means

Conversation

@andrewdalpino

Copy link
Copy Markdown
Member

FuzzyCMeans computes every sample↔centroid distance twice per epoch — inertia() at src/Clusterers/FuzzyCMeans.php:436 recomputes what probaSample already did, and the membership-exponent + total sum is computed a redundant factor-of-f (times feature count) in the centroid update. Compute the n×c distance matrix + weighted matrix once per epoch. ~2× epoch cost.

Shape (n × d × c) Before After Speedup Removed work dominates when…
9999 × 4 × 3 (official bench) 20.6 ms/epoch 14.6 ms/epoch 1.41× small d, c → fixed overheads dominate
5000 × 16 × 5 51.2 ms/epoch 28.1 ms/epoch 1.82×  
5000 × 32 × 8 157.4 ms/epoch 83.8 ms/epoch 1.88× large d, c → ≈2× as predicted

@andrewdalpino
andrewdalpino requested review from a team and a lite review from Copilot August 22, 2026 18:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes Fuzzy C-Means training by caching distances and streamlining centroid updates.

Changes:

  • Reuses per-epoch distance and membership matrices.
  • Aggregates weighted centroid sums in one pass.
  • Extracts shared membership calculation logic.

A moderate issue remains: preserve raw distances for inertia and apply EPSILON only when deriving memberships to retain exact-match loss semantics.

Suppressed comments (4)

src/Clusterers/FuzzyCMeans.php:293

  • This path no longer calls probaSample(), although that protected method is the hook used by predictSample() and is not marked @internal. A subclass overriding it to customize memberships will therefore train with the base implementation but predict with the override, making the estimator's training and prediction behavior inconsistent. Preserve the existing hook in the cached-distance design or explicitly make this a documented breaking change.
                $memberships[] = $this->membershipsFromDistances($row);

src/Clusterers/FuzzyCMeans.php:277

  • numFeatures() reads $samples[0], but Unlabeled::quick()/the verify=false constructor preserves arbitrary row keys. A dataset such as [10 => [1.0], 11 => [2.0]] therefore reports zero features here, leaving every $sums[$cluster] empty and preventing centroid updates. Normalize the samples once (and use that normalized array throughout the epoch) or derive the dimensionality from the first iterated sample.
        $numFeatures = $dataset->numFeatures();

src/Clusterers/FuzzyCMeans.php:322

  • The first pass appends memberships with [], creating a packed list, while this pass indexes it with the dataset's original row key. Unlabeled::quick() can preserve non-sequential keys, so [0 => [...], 2 => [...]] reaches an undefined $memberships[2] offset (and can associate rows incorrectly). Iterate over normalized samples or preserve the original keys when building $memberships.
            foreach ($dataset->samples() as $i => $sample) {
                foreach ($memberships[$i] as $cluster => $membership) {

src/Clusterers/FuzzyCMeans.php:328

  • This update also assumes feature vectors have zero-based contiguous keys: $sums is initialized with keys 0..$numFeatures-1, but $j comes directly from each sample. Since Unlabeled::quick() can preserve feature keys, a sample like [10 => 1.0, 20 => 2.0] now indexes nonexistent sum entries and fails to update the centroid; the previous transpose path reindexed columns. Iterate over array_values($sample) (or normalize the dataset) before accumulating.
                    foreach ($sample as $j => $value) {
                        $sums[$cluster][$j] += $weight * $value;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

$row = [];

foreach ($this->centroids as $centroid) {
$row[] = $this->kernel->compute($sample, $centroid) ?: EPSILON;
@andrewdalpino andrewdalpino added the optimization Make something perform faster label Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimization Make something perform faster

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants