A map is a lower dimensional discretised representation of some input space. The self organising map (SOM) is a kind of artificial neural network. It differs in the sense of using a neighbourhood function which preserves the topological properties of the input space. This makes the SOM applicable for visualisation of high-dimensional data.

The SOM consists of a grid of nodes. Each node holds a randomised weight vector of the same dimension as the training/input data. The nodes are connected with another through a neighbourhood function, e.g. the Gaussian neighbourhood function.

During the training of the SOM, input vectors are assigned to the closest node based on some kind of distance metric. The weight vector of all nodes is updated towards the assigned input vector by \[ w_i(t+1) = w_i(t) + \Theta(d(v_i,BMU),t)\gamma(t)(In(t)-w_i(t)) \] where the weight vector \( w_i \) is updated dependent on the neighbourhood scaling \(\Theta(v_i,t)\) which depends on the distance between the updating neuron \(v_i\) and the Best Matching Unit (\(BMU\)), a decreasing learning coefficient \(\gamma(t)\) and the difference between Input Vector (\(In(t\)) and the present weight vector \(w_i\). The training is done sequentially with a great number of input vectors. During each step all nodes are by some degree moved due to the neighbourhood function. This leads to similar features mapped close together and dissimilar features apart. This way the SOM forms a semantic map over the feature space of the input data with more nodes pointing to feature reach parts of the input space. This characteristic can be used for visualisation by calculating an U-matrix (unified distance matrix). The U-matrix consists of the euclidean distance between neighbouring weights which can be represented in a grey scale picture where spots of light colour show clusters of near weights and therefore of the higher dimensional input data. After the training phase, input data can be mapped.

A Python implementation of a Self Organising Map applied to images

The following Python code is a simple implementation of a SOM to map pictures to a reduced colour space. The neighbourhood function is a simple radius function with a constant weight within the radius. The training is done with sampled pixels from the input image.

The original input image

The trained SOM with 8x8 nodes<

The image after mapping it to the trained SOM with 8x8 nodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/python
# -*- coding: utf-8 -*-
# pictureSOM.py
# Version: 1
# Date: 25/09/2011
# Author: Jan Teichmann
"""
python pictureSOM.py IMAGE SOM_SIZE 
"""

import numpy
from PIL import Image
import sys

def draw(arr):
    size = (arr.shape[0], arr.shape[1])
    arr = arr.reshape(arr.shape[0]*arr.shape[1], arr.shape[2])
    arr = arr.astype(numpy.uint8)
    return Image.frombuffer("RGB", size, arr.tostring(), 'raw', mode, 0, 1)

def trainingsVec(data):
    V,H,c = data.shape
    y = int(H*numpy.random.rand())
    x = int(V*numpy.random.rand())
    return data[x,y]

def BMU(som, vec):
    dist = numpy.sqrt( numpy.square(som - vec).sum(2) )
    H = som.shape[1]
    v,h = divmod(dist.argmin(), H)
    return (v,h)

def SOM_update(som, vec, gamma, bmu):
    V, H, n = som.shape
    ind = numpy.indices((V,H))
    ind[0] -= bmu[0]
    ind[1] -= bmu[1]
    dist = numpy.sqrt(numpy.square(ind[0]) + numpy.square(ind[1]))
    mask = numpy.less(dist, gamma).astype(float)
    mask *= 0.01  # weight
    mask = numpy.multiply.outer(mask, numpy.ones(n,int))
    som += mask * (vec - som)
    return som

def iteration(data, som, gamma_init, rate):
    gamma = gamma_init
    i = 1
    while gamma &gt; 1:
        vec = trainingsVec(data)
        bmu = BMU(som, vec)
        som = SOM_update(som, vec, gamma, bmu)
        gamma *= rate
        i += 1
    return som

def readImage(f):
    img = Image.open(f)
    arr = numpy.array(img.getdata())
    arr = arr.reshape(img.size[0], img.size[1], 3)
    return arr

def mapping(data, som):
    V,H,n = data.shape
    arr = numpy.zeros((V,H,n), numpy.uint8)
    for i in range(V):
        for j in range(H):
            vec = data[i,j]
            bmu = BMU(som, vec)
            arr[i,j] = som[bmu[0], bmu[1]]
    img = draw(arr)
    return img


if __name__ == "__main__":
    data = readImage(sys.argv[1])
    origImg = draw(data)
    origImg.show()
    
    som = numpy.random.random_sample((int(sys.argv[2]),int(sys.argv[2]),3))
    som = iteration(data, som, 30, 0.999)
    somImg = draw(som)
    somImg.show()

    mapImg = mapping(data, som)
    mapImg.show()