Skip to content

Commit 311b7a1

Browse files
CopilotJustGlowing
andcommitted
Add optional Numba JIT acceleration to train_batch_offline with NumPy fallback
Co-authored-by: JustGlowing <4929177+JustGlowing@users.noreply.github.com>
1 parent b31c74d commit 311b7a1

1 file changed

Lines changed: 81 additions & 13 deletions

File tree

minisom.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212
from datetime import timedelta
1313
import pickle
1414
import os
15+
import numpy as np
16+
17+
try:
18+
from numba import njit
19+
NUMBA_AVAILABLE = True
20+
except ImportError:
21+
NUMBA_AVAILABLE = False
22+
23+
def njit(*args, **kwargs):
24+
"""Identity decorator fallback when Numba is not installed."""
25+
def decorator(func):
26+
return func
27+
if len(args) == 1 and callable(args[0]):
28+
return args[0]
29+
return decorator
1530

1631
# for unit tests
1732
from numpy.testing import assert_almost_equal, assert_array_almost_equal
@@ -72,6 +87,48 @@ def fast_norm(x):
7287
return sqrt(dot(x, x.T))
7388

7489

90+
@njit(fastmath=True, cache=True)
91+
def _accumulate_batch(data, weights, bmu_indices, xx, yy, sigma):
92+
"""JIT-compiled accumulation of numerator/denominator
93+
for the batch SOM update (Gaussian neighborhood only).
94+
95+
Parameters
96+
----------
97+
data : 2D float64 array (n_samples, input_len)
98+
weights : 3D float64 array (map_x, map_y, input_len)
99+
bmu_indices : 1D int64 array (n_samples,) — flat BMU index per sample
100+
xx, yy : 2D float64 arrays — meshgrid coordinates
101+
sigma : float64
102+
103+
Returns
104+
-------
105+
numerator : 3D array (map_x, map_y, input_len)
106+
denominator : 2D array (map_x, map_y)
107+
"""
108+
map_x, map_y, input_len = weights.shape
109+
numerator = np.zeros((map_x, map_y, input_len))
110+
denominator = np.zeros((map_x, map_y))
111+
d = 2.0 * sigma * sigma
112+
113+
for s in range(data.shape[0]):
114+
bi = bmu_indices[s] // map_y
115+
bj = bmu_indices[s] % map_y
116+
117+
cx = xx[bj, bi]
118+
cy = yy[bj, bi]
119+
120+
for i in range(map_x):
121+
for j in range(map_y):
122+
dx = xx[j, i] - cx
123+
dy = yy[j, i] - cy
124+
g = np.exp(-(dx * dx + dy * dy) / d)
125+
denominator[i, j] += g
126+
for k in range(input_len):
127+
numerator[i, j, k] += g * data[s, k]
128+
129+
return numerator, denominator
130+
131+
75132
class MiniSom(object):
76133
Y_HEX_CONV_FACTOR = (3.0 / 2.0) / sqrt(3)
77134

@@ -595,6 +652,7 @@ def train_batch_offline(self, data, num_iteration, verbose=False):
595652
"""
596653
self._check_iteration_number(num_iteration)
597654
self._check_input_len(data)
655+
data = array(data, dtype=float)
598656
iterations = range(num_iteration)
599657
if verbose:
600658
iterations = _wrap_index__in_verbose(iterations)
@@ -608,19 +666,29 @@ def train_batch_offline(self, data, num_iteration, verbose=False):
608666
iteration,
609667
num_iteration)
610668

611-
# Initialize accumulators
612-
numerator = zeros_like(self._weights)
613-
denominator = zeros((self._weights.shape[0],
614-
self._weights.shape[1]))
615-
616-
# Process all samples
617-
for sample in data:
618-
bmu = self.winner(sample)
619-
g = self.neighborhood(bmu, sigma)
620-
# Vectorized accumulation
621-
g_expanded = g[:, :, newaxis]
622-
numerator += g_expanded * sample
623-
denominator += g
669+
if NUMBA_AVAILABLE:
670+
# Fast path: vectorized BMU search + Numba JIT accumulation
671+
dists = self._distance_from_weights(data)
672+
bmu_indices = argmin(dists, axis=1)
673+
674+
numerator, denominator = _accumulate_batch(
675+
data, self._weights,
676+
bmu_indices.astype(np.int64),
677+
self._xx.astype(np.float64),
678+
self._yy.astype(np.float64),
679+
float(sigma))
680+
else:
681+
# Fallback: original pure-NumPy implementation
682+
numerator = zeros_like(self._weights)
683+
denominator = zeros((self._weights.shape[0],
684+
self._weights.shape[1]))
685+
686+
for sample in data:
687+
bmu = self.winner(sample)
688+
g = self.neighborhood(bmu, sigma)
689+
g_expanded = g[:, :, newaxis]
690+
numerator += g_expanded * sample
691+
denominator += g
624692

625693
# Batch update with safety check
626694
denominator_safe = where(denominator[:, :, newaxis] > 0,

0 commit comments

Comments
 (0)