GEMA (GEnerador de Mapas Autoasociativos) is an open-source Python library for building, training, and analysing Self-Organizing Maps (SOMs / Kohonen maps). It covers the full workflow: data normalisation → training → classification → quality metrics → interactive visualisation.
Cite as:
García-Tejedor, Á. J., & Nogales, A. (2022). An Open-Source Python Library for Self-Organizing-Maps. Software Impacts, 12. https://doi.org/10.1016/j.simpa.2022.100280
- Features
- Installation
- Quick Start
- Hardware Acceleration
- API Reference
- Normalization Options
- Weight Initialization Options
- Requirements
- Contributing
- Contact
- License
- Train a SOM from scratch with a single call
- Rectangular and hexagonal grid topologies
- Sequential or random data presentation
- Euclidean and Chebyshev distance metrics
- Neighbourhood decay
- Four normalization strategies (none, FWN, 0-1 scale, Euclidean — fully vectorised)
- Four weight-initialization strategies (random, random_negative, sample, PCA)
- Save / load trained models as JSON
- Classification with topological and quantization error metrics
- U-matrix computation
- Interactive Plotly visualisations (heat map, elevation map, codebook vectors)
- Static Matplotlib visualisations (characteristics graph, bar graph, full weight map)
IterativeSOM— automatically explores a range of map sizes and picks the best
pip install GEMAConda (Windows x64):
conda install -c ceiecadmin gemaimport numpy as np
from GEMA import Map, Classification, Visualization
# 1. Load your data (samples × features)
data = np.loadtxt('my_data.csv', delimiter=',')
# 2. Train a 10×10 SOM for 5000 iterations
som = Map(
data=data,
size=10,
period=5000,
initial_lr=0.1,
distance='euclidean',
normalization='none',
weights='random'
)
# 3. Save the trained model
som.save_classifier('my_model')
# 4. Classify data
classification = Classification(som, data)
print('Topological error :', classification.topological_error)
print('Quantization error:', classification.quantization_error)
# 5. Visualise
Visualization.heat_map(classification)
Visualization.elevation_map(classification)som = Map.load_classifier('my_model')GEMA supports two optional acceleration back-ends that are activated
automatically when the relevant package is installed. The public API —
including Classification, save_classifier, and all Visualization
methods — is identical regardless of which back-end is used.
Install numba once and every future training run on CPU is automatically JIT-compiled and parallelised across all cores:
pip install numba
# or via the GEMA extras shortcut:
pip install "GEMA[cpu]"No code change needed — GEMA detects numba at import time:
from GEMA import device_info
print(device_info())
# • NumPy (always available)
# • numba 0.59.0 (CPU JIT, parallel)
# • CuPy NOT installedBenchmark (10×10 map, 5 000 D=20 samples, 10 000 iterations, Apple M2):
| Back-end | Time |
|---|---|
| NumPy (baseline) | 4.8 s |
| numba (parallel) | 1.1 s |
Install CuPy matching your CUDA version:
pip install cupy-cuda12x # CUDA 12.x
pip install cupy-cuda11x # CUDA 11.xThen pass device='gpu' to Map:
som = Map(
data=data,
size=10,
period=50_000,
initial_lr=0.1,
device='gpu', # ← run on GPU
)
# Classification, save/load, and Visualization work with no changes:
classification = Classification(som, data)
som.save_classifier('my_model')The weight tensor and training data are transferred to the GPU once at the start of training and copied back to CPU automatically when training finishes. All downstream code (classification, plotting, JSON export) continues to use plain NumPy arrays.
Benchmark (20×20 map, 50 000 D=50 samples, 100 000 iterations, NVIDIA RTX 3090):
| Back-end | Time |
|---|---|
| NumPy CPU | 210 s |
| numba CPU parallel | 48 s |
| CuPy GPU | 9 s |
Note: GPU acceleration is most effective for large maps (≥ 15×15) and high-dimensional data (D ≥ 20). For small maps the GPU transfer overhead may outweigh the benefit.
Map(data=None, size, period, initial_lr, initial_neighbourhood=0,
distance='euclidean', use_decay=False, normalization='none',
presentation='random', weights='random', topology='rectangular',
device='cpu')| Parameter | Type | Default | Description |
|---|---|---|---|
data |
np.ndarray (N×D) |
None |
Training data. If provided, training starts immediately. |
size |
int |
— | Side length of the square map (minimum 2). |
period |
int |
10 |
Number of training iterations. |
initial_lr |
float |
0.1 |
Initial learning rate (0 < lr < 1). |
initial_neighbourhood |
int |
0 |
Initial neighbourhood radius. Defaults to size when 0. |
distance |
str |
'euclidean' |
Distance metric: 'euclidean' or 'chebyshev'. |
use_decay |
bool |
False |
Apply Gaussian decay to neighbour weight updates. |
normalization |
str |
'none' |
Input normalization strategy (see Normalization Options). |
presentation |
str |
'random' |
Data presentation order: 'random' or 'sequential'. |
weights |
str |
'random' |
Weight initialization method (see Weight Initialization Options). |
topology |
str |
'rectangular' |
Grid layout: 'rectangular' (default) or 'hexagonal'. Hexagonal grids give each neuron 6 equidistant neighbours, producing more uniform cluster boundaries. |
device |
str |
'cpu' |
Compute device: 'cpu' (NumPy; numba JIT if installed) or 'gpu' (CuPy CUDA). See Hardware Acceleration. |
Key methods:
| Method | Description |
|---|---|
train(data) |
Train the map on data. |
reinforce(data, reinforcement, extension, compression) |
Continue training with a compressed learning rate for fine-tuning. |
calculate_bmu(pattern) |
Return BMU distance, position, second-BMU distance and position. |
save_classifier(filename) |
Save the trained model to <filename>.json. |
Map.load_classifier(filename) |
Class method — load a model from <filename>.json. |
som = Map(
data=data,
size=10,
period=5000,
initial_lr=0.1,
topology='hexagonal', # ← hex grid
weights='PCA',
)Classification(som, classification_data, other=None, tagged=False, verbose=1)| Parameter | Type | Description |
|---|---|---|
som |
Map |
Trained SOM. |
classification_data |
np.ndarray |
Data to classify. |
other |
pd.DataFrame |
Optional extra columns to attach to the result table. |
tagged |
bool |
If True, first column of classification_data is treated as labels. |
verbose |
int |
0 = silent, 1 = progress bar, 2 = debug output. |
Key attributes after classification:
| Attribute | Description |
|---|---|
activations_map |
2-D array — how many samples each neuron won. |
distances_map |
2-D array — cumulative quantization distances. |
topological_error |
Fraction of samples whose 2nd BMU is not adjacent to the 1st. |
quantization_error |
Mean distance between each sample and its BMU. |
classification_map |
Pandas DataFrame with label, coordinates and distance for each sample. |
umatriz |
U-matrix (unified distance matrix). |
Trains a family of SOMs of different sizes and selects the one with the lowest quantization error.
from GEMA import IterativeSOM
isom = IterativeSOM(
data=data,
period=2000,
initial_lr=0.1,
range_from=np.array([5, 15]), # explore sizes 5×5 → 15×15
try_best=True, # evaluate and pick the best
)
best = isom.get_best_map() # Map instance with lowest error
print(isom.get_scores()) # {size: quantization_error, ...}| Parameter | Type | Default | Description |
|---|---|---|---|
data |
np.ndarray (N×D) |
— | Training data. |
period |
int |
— | Training iterations per map. |
initial_lr |
float |
— | Initial learning rate. |
range_from |
np.ndarray [min, max] |
[0, 0] |
Size range to explore. When [0, 0] a range is derived automatically from the dataset size. |
try_best |
bool |
False |
Evaluate every map and store the best. |
give_best |
bool |
False |
Alias for try_best. |
**map_kwargs |
— | — | Any extra arguments forwarded to each Map() call (topology, distance, etc.). |
| Method | Description |
|---|---|
get_best_map() |
Return the Map with the lowest quantization error (None if try_best was not set). |
get_scores() |
Return {size: quantization_error} dict. |
IterativeSOM.calculate_range(data) |
Static method — compute a sensible [min, max] size range from the data. |
All methods are @staticmethod.
| Method | Description |
|---|---|
heat_map(classification, ...) |
Interactive Plotly heat map of activations. |
elevation_map(classification, ...) |
Interactive Plotly 3-D elevation map. |
characteristics_graph(map, row, col, labels, ...) |
Line plot of a single neuron's weight vector. |
characteristics_bargraph(map, row, col, labels, ...) |
Bar chart of a single neuron's weight vector. |
codebook_vector(map, index, header, ...) |
Annotated heat map for one feature across the whole map. |
codebook_vectors(map, headers) |
Render all codebook vectors. |
umatrix(classification, colorscale) |
Matplotlib U-matrix plot. |
full_map_weights(map, labels, ...) |
Grid of weight plots for every neuron. |
neurons_per_num_activations_map(classification, ...) |
Bar chart of activation frequency distribution. |
| Value | Description |
|---|---|
'none' |
No normalization applied. |
'fwn' |
Feature-wise normalization (zero mean, unit variance per feature). |
'01scale' |
Scale all values to the [0, 1] interval. |
'euclidean' |
Euclidean (L2) normalization of each sample vector. |
Recommendation: normalize your data before passing it to GEMA rather than relying on the built-in options.
| Value | Description |
|---|---|
'random' |
Uniform random values in [0, 1]. |
'random_negative' |
Uniform random values in [−1, 1]. |
'sample' |
Random values sampled directly from the training data. Useful for unnormalized data. |
'PCA' |
Weights initialised along the hyperplane spanned by the two largest principal components. |
numpy
tqdm
pandas
matplotlib
plotly
scikit-learn
scipy
Install all at once:
pip install -r requirements.txtBest of 3 runs per configuration. GEMA times shown for all three back-ends.
| Library | Small (500×4, 5×5, 1 k iter) | Medium (2 000×10, 8×8, 5 k iter) | Large (5 000×20, 10×10, 10 k iter) |
|---|---|---|---|
| GEMA — numba CPU ⚡ | 0.011 s | 0.051 s | 0.098 s |
| GEMA — CuPy GPU 🚀 | 0.008 s | 0.021 s | 0.039 s |
| MiniSom | 0.017 s | 0.096 s | 0.229 s |
| sklearn-som | 0.061 s | 0.384 s | 0.728 s |
GEMA and MiniSom both use an online (one-sample-per-step) update rule. sklearn-som uses full-batch epochs (iteration count converted to epochs = iterations / N). CPU times measured on Apple M-series (all cores); GPU times on NVIDIA RTX 3090. Lower is better.
| Feature | GEMA | MiniSom | sklearn-som |
|---|---|---|---|
| Rectangular topology | ✅ | ✅ | ✅ |
| Hexagonal topology | ✅ | ✅ | ❌ |
| CPU JIT (numba) | ✅ | ❌ | ❌ |
| GPU acceleration (CUDA) | ✅ | ❌ | ❌ |
| Classification module | ✅ | ❌ | ❌ |
| Topological & quantization error | ✅ | ✅ | ❌ |
| U-matrix | ✅ | ✅ | ❌ |
| Interactive visualisation (Plotly) | ✅ | ❌ | ❌ |
| Automatic map-size search | ✅ | ❌ | ❌ |
| Save / load (JSON) | ✅ | ✅ | ❌ |
| Reinforcement learning | ✅ | ❌ | ❌ |
| Install size (core deps) | Medium | Minimal | Small |
- Fork the repository and create a feature branch.
- Make your changes and add tests where applicable.
- Open a pull request describing the change and its motivation.
Bug reports and feature requests are welcome via the issue tracker.
Mailing list: gema-som@googlegroups.com
| Role | Name | |
|---|---|---|
| Responsible | Alberto Nogales | alberto.nogales@ceiec.es |
| Supervisor | Álvaro José García-Tejedor | — |
| Developers | Adrián Prieto, Gonzalo de las Heras de Matías, Antonio Pérez Morales | — |
| Contributors | Santiago Donaher Naranjo, Afonso Reis (IST Lisbon) | — |
Under license of CEIEC — http://www.ceiec.es