From 9aa34d7ea6931187bbc18e6d39c684568110ae0f Mon Sep 17 00:00:00 2001 From: giu Date: Wed, 3 Jul 2013 12:13:04 +0200 Subject: [PATCH 001/352] First commit --- Readme.md | 58 ++++++++++++++++++++ minisom.py | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 18 ++++++ 3 files changed, 233 insertions(+) create mode 100644 Readme.md create mode 100644 minisom.py create mode 100644 setup.py diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..331fc2c --- /dev/null +++ b/Readme.md @@ -0,0 +1,58 @@ +MiniSom +==================== + +![MiniSom]( http://1.bp.blogspot.com/-tD8Kg6FEOcg/Uc_wjGZ7qaI/AAAAAAAAAo4/A4Q1_dbqVLo/s278/logo.png "MiniSom") + +Self Organizing Maps +-------------------- + +MiniSom is minimalistic implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. + +Installation +--------------------- + + python setup.py install + +How to use it +--------------------- + +In order to use MiniSom you need your data organized as a Numpy matrix where each row corresponds to an observation or an as list of lists like the following: + + data = [[ 5.1 3.5 1.4 0.2], + [ 4.9 3. 1.4 0.2], + [ 4.7 3.2 1.3 0.2], # <-- single pattern + [ 4.6 3.1 1.5 0.2], + [ 5. 3.6 1.4 0.2], + [ 4.1 3.3 1.4 0.2], + [ 4.2 3.2 1.2 0.2]] + + Then you can run MiniSom just as follows: + + from minisom import MiniSom + som = MiniSom(6,6,4,sigma=0.3,learning_rate=0.5) # initialization of 6x6 SOM + print "Training..." + som.train_random(data,100) # trains the SOM with 100 iterations + print "...ready!" + +#### Result interpretation and visualization + +After the training, the method `winner(x)` computes the coordinates of the mapping of `x` on the map and it can be used to plot: + + + +### Other features + +MiniSom implements two types of training. The random training (implemente by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemente by the method `train_batch`), where the samples are used in the order they are stored. + +MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. + +Planned improvements +--------------------- +*Implement a classification method. + +License +--------------------- + +MiniSom by Giuseppe Vettigli is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/ "http://creativecommons.org/licenses/by/3.0/"). + +![License]( http://i.creativecommons.org/l/by/3.0/88x31.png "Creative Commons Attribution 3.0 Unported License") diff --git a/minisom.py b/minisom.py new file mode 100644 index 0000000..3d2bfb2 --- /dev/null +++ b/minisom.py @@ -0,0 +1,157 @@ +from numpy import meshgrid,sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros + +""" +Minimalistic implementation of the Self Organizing Maps (SOM) + +SOM is able to convert complex, nonlinear statistical relationsihps between high-dimensional data items into simple geometric relationships on a low-dimensional display. + +http://www.sis.pitt.edu/~ssyn/som/som.html +""" + +class MiniSom: + def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): + """ + Initializes a Self Organizing Maps. + x,y - dimensions of the SOM + input_len - number of the elements of the vectors in input + sigma - spread of the neighborhood function (Gaussian) + learning_rate - initial learning rate + (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where is #num_iteration/2) + """ + self.learning_rate = learning_rate + self.sigma = sigma + self.weights = random.rand(x,y,input_len)*2-1 # random initialization + self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization + self.activation_map = zeros((x,y)) + self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function + + def _activate(self,x): + """ Updates matrix activation_map, in this matrix the element i,j is the response of the neuron i,j to x """ + s = subtract(x,self.weights) # x - w + it = nditer(self.activation_map, flags=['multi_index']) + while not it.finished: + self.activation_map[it.multi_index] = linalg.norm(s[it.multi_index]) # || x - w || + it.iternext() + + def activate(self,x): + """ Returns the activation map to x """ + self._activate(x) + return self.activation_map + + def gaussian(self,c,sigma=0.1): + """ Bidimentional Gaussian centered in c """ + d = sqrt( power((c[0]-self.neigx),2) + power((c[1]-self.neigy),2) ) + return exp(-(d*d))/(2*pi*sigma) # a matrix is returned + + def winner(self,x): + """ Computes the coordinates of the winning neuron for the sample x """ + self._activate(x) + return unravel_index(self.activation_map.argmin(),self.activation_map.shape) + + def update(self,x,win,t): + """ + Updates the weights of the neurons. + x - current pattern to learning + win - position of the winning neuron for x (array or tuple). + eta - learning rate + t - iteration index + """ + # eta(t) = eta(0) / (1 + t/T) keeps the learning rate nearly constant for the first T iterations and then adjusts it + eta = self.learning_rate/(1+t/self.T) + g = self.gaussian(win,self.sigma)*eta # improves the performances + it = nditer(g, flags=['multi_index']) + while not it.finished: + self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) # eta * neighborhood_function * (x-w) + self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) # weights normalization + it.iternext() + + def random_weights_init(self,data): + """ Initializes the weights of the SOM picking random samples from data """ + it = nditer(self.activation_map, flags=['multi_index']) + while not it.finished: + self.weights[it.multi_index] = data[int(random.rand()*len(data)-1)] + self.weights[it.multi_index] = self.weights[it.multi_index]/linalg.norm(self.weights[it.multi_index]) + it.iternext() + + def train_random(self,data,num_iteration): + """ Trains the SOM picking samples at random from data """ + self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + for iteration in range(num_iteration): + rand_i = int(round(random.rand()*len(data)-1)) # pick a random sampleprint data[rand_i] + self.update(data[rand_i],self.winner(data[rand_i]),iteration) + self._show_progress(iteration,num_iteration) + + def train_batch(self,data,num_iteration): + """ Trains using all the vectors in data sequentially """ + self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + iteration = 0 + while iteration < num_iteration: + idx = iteration % (len(data)-1) + self.update(data[idx],self.winner(data[idx]),iteration) + self._show_progress(iteration,num_iteration-1) + iteration += 1 + + def _show_progress(self,iteration,num_iteration): + progress = round((iteration/float(num_iteration))*50) + #sys.stdout.write('\r') + #sys.stdout.write('\r[ {0} {1}] {2}%'.format('#'*int(progress),' '*int(50-(progress)), int(progress*2))) + #sys.stdout.flush() + + def distance_map(self): + """ Returns the average distance map of the weights """ + um = zeros((self.weights.shape[0],self.weights.shape[1])) + it = nditer(um, flags=['multi_index']) + while not it.finished: + for ii in range(it.multi_index[0]-1,it.multi_index[0]+2): + for jj in range(it.multi_index[1]-1,it.multi_index[1]+2): + if ii >= 0 and ii < self.weights.shape[0] and jj >= 0 and jj < self.weights.shape[1]: + um[it.multi_index] += linalg.norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) + it.iternext() + um = um/8 # should be different at the borders + return um + + def activation_response(self,data): + """ + Returns a matrix where the element i,j is the number of times + that the neuron i,j have been winner. + """ + a = zeros((self.weights.shape[0],self.weights.shape[1])) + for x in data: + a[self.winner(x)] += 1 + return a + +if __name__ == '__main__': + import sys + # reading the data from a csv file + from numpy import genfromtxt + data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) + #data = array([x/linalg.norm(x) for x in data]) # normalization + + # initialization and training + som = MiniSom(6,6,4,sigma=0.3,learning_rate=0.5) + som.random_weights_init(data) + print "Training..." + som.train_random(data,100) + #som.train_batch(data,150*5) + print "\n...ready!" + + from pylab import plot,axis,show,pcolor,colorbar,bone + bone() + pcolor(som.distance_map().T) + #pcolor(som.activate(data[1]).T) + #pcolor(som.activation_response(data).T) + colorbar() + # plotting the response for each pattern + target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels + t = zeros(len(target),dtype=int) + t[target == 'setosa'] = 0 + t[target == 'versicolor'] = 1 + t[target == 'virginica'] = 2 + markers = ['o','s','D'] + colors = ['r','g','b'] + for cnt,xx in enumerate(data): + w = som.winner(xx) # getting the winner + plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None',markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) + axis([0,som.weights.shape[0],0,som.weights.shape[1]]) + show() + \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4988de6 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +from distutils.core import setup + +try: + import numpy + setup(name='MiniSom', + version='0.1', + description='Minimalistic implementation of the Self Organizing Maps (SOM)', + author='Giuseppe Vettigli', + package_data={'': ['Readme.md']}, + include_package_data=True, + license="CC BY 3.0", + py_modules=['minisom'], + requires = ['numpy'] + ) +except ImportError: + print 'You need Numpy in order to use MiniSom: http://www.scipy.org/install.html' \ No newline at end of file From cdc480fc79a3c5743f264e5bae066b0cb7a9bc74 Mon Sep 17 00:00:00 2001 From: giu Date: Wed, 3 Jul 2013 12:16:13 +0200 Subject: [PATCH 002/352] initial readme removed --- README.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 5eb8b11..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -minisom -======= - -MiniSom is minimalistic implementation of the Self Organizing Maps From 0b527cc531fe56c6437d40a69b319a48b0ea579d Mon Sep 17 00:00:00 2001 From: giu Date: Wed, 3 Jul 2013 12:17:34 +0200 Subject: [PATCH 003/352] Readme updated --- Readme.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 331fc2c..3401c44 100644 --- a/Readme.md +++ b/Readme.md @@ -34,11 +34,13 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" -#### Result interpretation and visualization - -After the training, the method `winner(x)` computes the coordinates of the mapping of `x` on the map and it can be used to plot: +#### Results +After the training MiniSom makes you able to +* Compute the coordinate of a sample `x` on the map with the method `winner(x)`. +* Compute the average distance map of the weights on the map.*the number of times with the method `distance_map` +* Compute the number of times that each neuron is winner for a new data set with the method `activation_response(data)`. ### Other features @@ -48,7 +50,7 @@ MiniSom initializes the neurons weights at random. A data driven initialization Planned improvements --------------------- -*Implement a classification method. +* Implement a classification method. License --------------------- From 446e58da9fd9a6546149b0de49d612439971b3ab Mon Sep 17 00:00:00 2001 From: Peppe Date: Thu, 4 Jul 2013 10:35:06 +0200 Subject: [PATCH 004/352] minor changes in minisom.py and Readme --- Readme.md | 21 ++++++++++++--------- minisom.py | 36 ++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Readme.md b/Readme.md index 3401c44..44b180c 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,7 @@ MiniSom Self Organizing Maps -------------------- -MiniSom is minimalistic implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. +MiniSom is minimalistic Numpy based implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. Installation --------------------- @@ -20,7 +20,7 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac data = [[ 5.1 3.5 1.4 0.2], [ 4.9 3. 1.4 0.2], - [ 4.7 3.2 1.3 0.2], # <-- single pattern + [ 4.7 3.2 1.3 0.2], # <-- single observation [ 4.6 3.1 1.5 0.2], [ 5. 3.6 1.4 0.2], [ 4.1 3.3 1.4 0.2], @@ -34,23 +34,26 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" -#### Results +#### Using the trained SOM After the training MiniSom makes you able to -* Compute the coordinate of a sample `x` on the map with the method `winner(x)`. -* Compute the average distance map of the weights on the map.*the number of times with the method `distance_map` -* Compute the number of times that each neuron is winner for a new data set with the method `activation_response(data)`. +* Compute the coordinate assigned to an observation `x` on the map with the method `winner(x)`. +* Compute the average distance map of the weights on the map with the method `distance_map`. +* Compute the number of times that each neuron have been considered winner for the observations of a new data set with the method `activation_response(data)`. -### Other features +### Training algorithms -MiniSom implements two types of training. The random training (implemente by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemente by the method `train_batch`), where the samples are used in the order they are stored. +MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. + +### Weights initialization MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. Planned improvements --------------------- -* Implement a classification method. +* Implement a classification mechanism. +* Extend the documentation with a visualization example. License --------------------- diff --git a/minisom.py b/minisom.py index 3d2bfb2..06f7ad7 100644 --- a/minisom.py +++ b/minisom.py @@ -2,10 +2,6 @@ """ Minimalistic implementation of the Self Organizing Maps (SOM) - -SOM is able to convert complex, nonlinear statistical relationsihps between high-dimensional data items into simple geometric relationships on a low-dimensional display. - -http://www.sis.pitt.edu/~ssyn/som/som.html """ class MiniSom: @@ -56,13 +52,16 @@ def update(self,x,win,t): eta - learning rate t - iteration index """ - # eta(t) = eta(0) / (1 + t/T) keeps the learning rate nearly constant for the first T iterations and then adjusts it + # eta(t) = eta(0) / (1 + t/T) + # keeps the learning rate nearly constant for the first T iterations and then adjusts it eta = self.learning_rate/(1+t/self.T) g = self.gaussian(win,self.sigma)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: - self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) # eta * neighborhood_function * (x-w) - self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) # weights normalization + # eta * neighborhood_function * (x-w) + self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) + # normalization + self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) it.iternext() def random_weights_init(self,data): @@ -79,7 +78,6 @@ def train_random(self,data,num_iteration): for iteration in range(num_iteration): rand_i = int(round(random.rand()*len(data)-1)) # pick a random sampleprint data[rand_i] self.update(data[rand_i],self.winner(data[rand_i]),iteration) - self._show_progress(iteration,num_iteration) def train_batch(self,data,num_iteration): """ Trains using all the vectors in data sequentially """ @@ -88,15 +86,8 @@ def train_batch(self,data,num_iteration): while iteration < num_iteration: idx = iteration % (len(data)-1) self.update(data[idx],self.winner(data[idx]),iteration) - self._show_progress(iteration,num_iteration-1) iteration += 1 - def _show_progress(self,iteration,num_iteration): - progress = round((iteration/float(num_iteration))*50) - #sys.stdout.write('\r') - #sys.stdout.write('\r[ {0} {1}] {2}%'.format('#'*int(progress),' '*int(50-(progress)), int(progress*2))) - #sys.stdout.flush() - def distance_map(self): """ Returns the average distance map of the weights """ um = zeros((self.weights.shape[0],self.weights.shape[1])) @@ -121,32 +112,37 @@ def activation_response(self,data): return a if __name__ == '__main__': + """ + This main contains a usage example of each feature implemented. Soon will be moved in the documentation. + """ import sys # reading the data from a csv file from numpy import genfromtxt + # http://aima.cs.berkeley.edu/data/iris.csv data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) - #data = array([x/linalg.norm(x) for x in data]) # normalization + data = array([x/linalg.norm(x) for x in data]) # normalization # initialization and training - som = MiniSom(6,6,4,sigma=0.3,learning_rate=0.5) + som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) som.random_weights_init(data) print "Training..." - som.train_random(data,100) + som.train_random(data,500) #som.train_batch(data,150*5) print "\n...ready!" from pylab import plot,axis,show,pcolor,colorbar,bone bone() - pcolor(som.distance_map().T) + pcolor(som.distance_map().T) # plotting the distance map as background #pcolor(som.activate(data[1]).T) #pcolor(som.activation_response(data).T) colorbar() - # plotting the response for each pattern + # plotting the response for each pattern in the iris dataset target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels t = zeros(len(target),dtype=int) t[target == 'setosa'] = 0 t[target == 'versicolor'] = 1 t[target == 'virginica'] = 2 + # different colors and markers for each label markers = ['o','s','D'] colors = ['r','g','b'] for cnt,xx in enumerate(data): From b28d1db1b29d5628299eb4eff3b8b0a6692bb1d1 Mon Sep 17 00:00:00 2001 From: Peppe Date: Mon, 8 Jul 2013 15:33:31 +0200 Subject: [PATCH 005/352] average distance map normalized, T initialization method added --- minisom.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/minisom.py b/minisom.py index 06f7ad7..854a12c 100644 --- a/minisom.py +++ b/minisom.py @@ -1,7 +1,9 @@ from numpy import meshgrid,sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros """ -Minimalistic implementation of the Self Organizing Maps (SOM) + Minimalistic implementation of the Self Organizing Maps (SOM) + + Giuseppe Vettigli 2013. """ class MiniSom: @@ -74,22 +76,27 @@ def random_weights_init(self,data): def train_random(self,data,num_iteration): """ Trains the SOM picking samples at random from data """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self._init_T(num_iteration) for iteration in range(num_iteration): - rand_i = int(round(random.rand()*len(data)-1)) # pick a random sampleprint data[rand_i] + rand_i = int(round(random.rand()*len(data)-1)) # pick a random sample self.update(data[rand_i],self.winner(data[rand_i]),iteration) def train_batch(self,data,num_iteration): """ Trains using all the vectors in data sequentially """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self._init_T(num_iteration) iteration = 0 while iteration < num_iteration: idx = iteration % (len(data)-1) self.update(data[idx],self.winner(data[idx]),iteration) iteration += 1 + def _init_T(self,num_iteration): + """ Initializes the parameter T needed to adjust the learning rate """ + self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + def distance_map(self): - """ Returns the average distance map of the weights """ + """ Returns the average distance map of the weights. + (Each mean is normalized in order to sum up to 1) """ um = zeros((self.weights.shape[0],self.weights.shape[1])) it = nditer(um, flags=['multi_index']) while not it.finished: @@ -98,7 +105,7 @@ def distance_map(self): if ii >= 0 and ii < self.weights.shape[0] and jj >= 0 and jj < self.weights.shape[1]: um[it.multi_index] += linalg.norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) it.iternext() - um = um/8 # should be different at the borders + um = um/um.max() return um def activation_response(self,data): From 911474e30f2f47e1cae6c2345dab4387024a1e54 Mon Sep 17 00:00:00 2001 From: Peppe Date: Thu, 11 Jul 2013 16:42:31 +0200 Subject: [PATCH 006/352] Iris example added, documentation updated --- Readme.md | 9 ++- examples/example_iris.py | 41 +++++++++++ examples/iris.csv | 150 +++++++++++++++++++++++++++++++++++++++ minisom.py | 40 ----------- setup.py | 24 +++---- 5 files changed, 209 insertions(+), 55 deletions(-) create mode 100644 examples/example_iris.py create mode 100644 examples/iris.csv diff --git a/Readme.md b/Readme.md index 44b180c..8cbae53 100644 --- a/Readme.md +++ b/Readme.md @@ -50,10 +50,17 @@ MiniSom implements two types of training. The random training (implemented by th MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. +Examples +--------------------- +In examples/example_iris.py you can find an example that shows how to train MiniSom and visualize the result using the Iris flower dataset. Here is the result of the script: + +Iris example + +For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as backgroud (see the color bar on the right to associate the value). + Planned improvements --------------------- * Implement a classification mechanism. -* Extend the documentation with a visualization example. License --------------------- diff --git a/examples/example_iris.py b/examples/example_iris.py new file mode 100644 index 0000000..fdb7677 --- /dev/null +++ b/examples/example_iris.py @@ -0,0 +1,41 @@ +from minisom import MiniSom +from numpy import genfromtxt,array,linalg,zeros + +""" + This script shows how to use MiniSom on the Iris dataset. + In partucular it shows how to train MiniSom and how to visualize the result. + ATTENTION: pylab is required for the visualization. +""" + +# reading the iris dataset in the csv format +# (downloaded from http://aima.cs.berkeley.edu/data/iris.csv) +data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) +data = array([x/linalg.norm(x) for x in data]) # data normalization + +### Initialization and training ### +som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) +som.random_weights_init(data) +print "Training..." +som.train_random(data,500) # random training +print "\n...ready!" + +### Plotting the response for each pattern in the iris dataset ### +from pylab import plot,axis,show,pcolor,colorbar,bone +bone() +pcolor(som.distance_map().T) # plotting the distance map as background +colorbar() +target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels +t = zeros(len(target),dtype=int) +t[target == 'setosa'] = 0 +t[target == 'versicolor'] = 1 +t[target == 'virginica'] = 2 +# use different colors and markers for each label +markers = ['o','s','D'] +colors = ['r','g','b'] +for cnt,xx in enumerate(data): + w = som.winner(xx) # getting the winner + # palce a marker on the winning position for the sample xx + plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None', + markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) +axis([0,som.weights.shape[0],0,som.weights.shape[1]]) +show() # show the figure \ No newline at end of file diff --git a/examples/iris.csv b/examples/iris.csv new file mode 100644 index 0000000..1a75486 --- /dev/null +++ b/examples/iris.csv @@ -0,0 +1,150 @@ +5.1,3.5,1.4,0.2,setosa +4.9,3.0,1.4,0.2,setosa +4.7,3.2,1.3,0.2,setosa +4.6,3.1,1.5,0.2,setosa +5.0,3.6,1.4,0.2,setosa +5.4,3.9,1.7,0.4,setosa +4.6,3.4,1.4,0.3,setosa +5.0,3.4,1.5,0.2,setosa +4.4,2.9,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.4,3.7,1.5,0.2,setosa +4.8,3.4,1.6,0.2,setosa +4.8,3.0,1.4,0.1,setosa +4.3,3.0,1.1,0.1,setosa +5.8,4.0,1.2,0.2,setosa +5.7,4.4,1.5,0.4,setosa +5.4,3.9,1.3,0.4,setosa +5.1,3.5,1.4,0.3,setosa +5.7,3.8,1.7,0.3,setosa +5.1,3.8,1.5,0.3,setosa +5.4,3.4,1.7,0.2,setosa +5.1,3.7,1.5,0.4,setosa +4.6,3.6,1.0,0.2,setosa +5.1,3.3,1.7,0.5,setosa +4.8,3.4,1.9,0.2,setosa +5.0,3.0,1.6,0.2,setosa +5.0,3.4,1.6,0.4,setosa +5.2,3.5,1.5,0.2,setosa +5.2,3.4,1.4,0.2,setosa +4.7,3.2,1.6,0.2,setosa +4.8,3.1,1.6,0.2,setosa +5.4,3.4,1.5,0.4,setosa +5.2,4.1,1.5,0.1,setosa +5.5,4.2,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.0,3.2,1.2,0.2,setosa +5.5,3.5,1.3,0.2,setosa +4.9,3.1,1.5,0.1,setosa +4.4,3.0,1.3,0.2,setosa +5.1,3.4,1.5,0.2,setosa +5.0,3.5,1.3,0.3,setosa +4.5,2.3,1.3,0.3,setosa +4.4,3.2,1.3,0.2,setosa +5.0,3.5,1.6,0.6,setosa +5.1,3.8,1.9,0.4,setosa +4.8,3.0,1.4,0.3,setosa +5.1,3.8,1.6,0.2,setosa +4.6,3.2,1.4,0.2,setosa +5.3,3.7,1.5,0.2,setosa +5.0,3.3,1.4,0.2,setosa +7.0,3.2,4.7,1.4,versicolor +6.4,3.2,4.5,1.5,versicolor +6.9,3.1,4.9,1.5,versicolor +5.5,2.3,4.0,1.3,versicolor +6.5,2.8,4.6,1.5,versicolor +5.7,2.8,4.5,1.3,versicolor +6.3,3.3,4.7,1.6,versicolor +4.9,2.4,3.3,1.0,versicolor +6.6,2.9,4.6,1.3,versicolor +5.2,2.7,3.9,1.4,versicolor +5.0,2.0,3.5,1.0,versicolor +5.9,3.0,4.2,1.5,versicolor +6.0,2.2,4.0,1.0,versicolor +6.1,2.9,4.7,1.4,versicolor +5.6,2.9,3.6,1.3,versicolor +6.7,3.1,4.4,1.4,versicolor +5.6,3.0,4.5,1.5,versicolor +5.8,2.7,4.1,1.0,versicolor +6.2,2.2,4.5,1.5,versicolor +5.6,2.5,3.9,1.1,versicolor +5.9,3.2,4.8,1.8,versicolor +6.1,2.8,4.0,1.3,versicolor +6.3,2.5,4.9,1.5,versicolor +6.1,2.8,4.7,1.2,versicolor +6.4,2.9,4.3,1.3,versicolor +6.6,3.0,4.4,1.4,versicolor +6.8,2.8,4.8,1.4,versicolor +6.7,3.0,5.0,1.7,versicolor +6.0,2.9,4.5,1.5,versicolor +5.7,2.6,3.5,1.0,versicolor +5.5,2.4,3.8,1.1,versicolor +5.5,2.4,3.7,1.0,versicolor +5.8,2.7,3.9,1.2,versicolor +6.0,2.7,5.1,1.6,versicolor +5.4,3.0,4.5,1.5,versicolor +6.0,3.4,4.5,1.6,versicolor +6.7,3.1,4.7,1.5,versicolor +6.3,2.3,4.4,1.3,versicolor +5.6,3.0,4.1,1.3,versicolor +5.5,2.5,4.0,1.3,versicolor +5.5,2.6,4.4,1.2,versicolor +6.1,3.0,4.6,1.4,versicolor +5.8,2.6,4.0,1.2,versicolor +5.0,2.3,3.3,1.0,versicolor +5.6,2.7,4.2,1.3,versicolor +5.7,3.0,4.2,1.2,versicolor +5.7,2.9,4.2,1.3,versicolor +6.2,2.9,4.3,1.3,versicolor +5.1,2.5,3.0,1.1,versicolor +5.7,2.8,4.1,1.3,versicolor +6.3,3.3,6.0,2.5,virginica +5.8,2.7,5.1,1.9,virginica +7.1,3.0,5.9,2.1,virginica +6.3,2.9,5.6,1.8,virginica +6.5,3.0,5.8,2.2,virginica +7.6,3.0,6.6,2.1,virginica +4.9,2.5,4.5,1.7,virginica +7.3,2.9,6.3,1.8,virginica +6.7,2.5,5.8,1.8,virginica +7.2,3.6,6.1,2.5,virginica +6.5,3.2,5.1,2.0,virginica +6.4,2.7,5.3,1.9,virginica +6.8,3.0,5.5,2.1,virginica +5.7,2.5,5.0,2.0,virginica +5.8,2.8,5.1,2.4,virginica +6.4,3.2,5.3,2.3,virginica +6.5,3.0,5.5,1.8,virginica +7.7,3.8,6.7,2.2,virginica +7.7,2.6,6.9,2.3,virginica +6.0,2.2,5.0,1.5,virginica +6.9,3.2,5.7,2.3,virginica +5.6,2.8,4.9,2.0,virginica +7.7,2.8,6.7,2.0,virginica +6.3,2.7,4.9,1.8,virginica +6.7,3.3,5.7,2.1,virginica +7.2,3.2,6.0,1.8,virginica +6.2,2.8,4.8,1.8,virginica +6.1,3.0,4.9,1.8,virginica +6.4,2.8,5.6,2.1,virginica +7.2,3.0,5.8,1.6,virginica +7.4,2.8,6.1,1.9,virginica +7.9,3.8,6.4,2.0,virginica +6.4,2.8,5.6,2.2,virginica +6.3,2.8,5.1,1.5,virginica +6.1,2.6,5.6,1.4,virginica +7.7,3.0,6.1,2.3,virginica +6.3,3.4,5.6,2.4,virginica +6.4,3.1,5.5,1.8,virginica +6.0,3.0,4.8,1.8,virginica +6.9,3.1,5.4,2.1,virginica +6.7,3.1,5.6,2.4,virginica +6.9,3.1,5.1,2.3,virginica +5.8,2.7,5.1,1.9,virginica +6.8,3.2,5.9,2.3,virginica +6.7,3.3,5.7,2.5,virginica +6.7,3.0,5.2,2.3,virginica +6.3,2.5,5.0,1.9,virginica +6.5,3.0,5.2,2.0,virginica +6.2,3.4,5.4,2.3,virginica +5.9,3.0,5.1,1.8,virginica diff --git a/minisom.py b/minisom.py index 854a12c..68ca399 100644 --- a/minisom.py +++ b/minisom.py @@ -117,44 +117,4 @@ def activation_response(self,data): for x in data: a[self.winner(x)] += 1 return a - -if __name__ == '__main__': - """ - This main contains a usage example of each feature implemented. Soon will be moved in the documentation. - """ - import sys - # reading the data from a csv file - from numpy import genfromtxt - # http://aima.cs.berkeley.edu/data/iris.csv - data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) - data = array([x/linalg.norm(x) for x in data]) # normalization - - # initialization and training - som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) - som.random_weights_init(data) - print "Training..." - som.train_random(data,500) - #som.train_batch(data,150*5) - print "\n...ready!" - - from pylab import plot,axis,show,pcolor,colorbar,bone - bone() - pcolor(som.distance_map().T) # plotting the distance map as background - #pcolor(som.activate(data[1]).T) - #pcolor(som.activation_response(data).T) - colorbar() - # plotting the response for each pattern in the iris dataset - target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels - t = zeros(len(target),dtype=int) - t[target == 'setosa'] = 0 - t[target == 'versicolor'] = 1 - t[target == 'virginica'] = 2 - # different colors and markers for each label - markers = ['o','s','D'] - colors = ['r','g','b'] - for cnt,xx in enumerate(data): - w = som.winner(xx) # getting the winner - plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None',markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) - axis([0,som.weights.shape[0],0,som.weights.shape[1]]) - show() \ No newline at end of file diff --git a/setup.py b/setup.py index 4988de6..c43c908 100644 --- a/setup.py +++ b/setup.py @@ -2,17 +2,13 @@ from distutils.core import setup -try: - import numpy - setup(name='MiniSom', - version='0.1', - description='Minimalistic implementation of the Self Organizing Maps (SOM)', - author='Giuseppe Vettigli', - package_data={'': ['Readme.md']}, - include_package_data=True, - license="CC BY 3.0", - py_modules=['minisom'], - requires = ['numpy'] - ) -except ImportError: - print 'You need Numpy in order to use MiniSom: http://www.scipy.org/install.html' \ No newline at end of file +setup(name='MiniSom', + version='0.1', + description='Minimalistic implementation of the Self Organizing Maps (SOM)', + author='Giuseppe Vettigli', + package_data={'': ['Readme.md']}, + include_package_data=True, + license="CC BY 3.0", + py_modules=['minisom'], + requires = ['numpy'] + ) From 0a2c985f8bd34f363225abadfb65acced1fbbe80 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 11 Jul 2013 16:42:31 +0200 Subject: [PATCH 007/352] Iris example added, documentation updated --- Readme.md | 9 ++- examples/example_iris.py | 41 +++++++++++ examples/iris.csv | 150 +++++++++++++++++++++++++++++++++++++++ minisom.py | 40 ----------- setup.py | 24 +++---- 5 files changed, 209 insertions(+), 55 deletions(-) create mode 100644 examples/example_iris.py create mode 100644 examples/iris.csv diff --git a/Readme.md b/Readme.md index 44b180c..8cbae53 100644 --- a/Readme.md +++ b/Readme.md @@ -50,10 +50,17 @@ MiniSom implements two types of training. The random training (implemented by th MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. +Examples +--------------------- +In examples/example_iris.py you can find an example that shows how to train MiniSom and visualize the result using the Iris flower dataset. Here is the result of the script: + +Iris example + +For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as backgroud (see the color bar on the right to associate the value). + Planned improvements --------------------- * Implement a classification mechanism. -* Extend the documentation with a visualization example. License --------------------- diff --git a/examples/example_iris.py b/examples/example_iris.py new file mode 100644 index 0000000..fdb7677 --- /dev/null +++ b/examples/example_iris.py @@ -0,0 +1,41 @@ +from minisom import MiniSom +from numpy import genfromtxt,array,linalg,zeros + +""" + This script shows how to use MiniSom on the Iris dataset. + In partucular it shows how to train MiniSom and how to visualize the result. + ATTENTION: pylab is required for the visualization. +""" + +# reading the iris dataset in the csv format +# (downloaded from http://aima.cs.berkeley.edu/data/iris.csv) +data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) +data = array([x/linalg.norm(x) for x in data]) # data normalization + +### Initialization and training ### +som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) +som.random_weights_init(data) +print "Training..." +som.train_random(data,500) # random training +print "\n...ready!" + +### Plotting the response for each pattern in the iris dataset ### +from pylab import plot,axis,show,pcolor,colorbar,bone +bone() +pcolor(som.distance_map().T) # plotting the distance map as background +colorbar() +target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels +t = zeros(len(target),dtype=int) +t[target == 'setosa'] = 0 +t[target == 'versicolor'] = 1 +t[target == 'virginica'] = 2 +# use different colors and markers for each label +markers = ['o','s','D'] +colors = ['r','g','b'] +for cnt,xx in enumerate(data): + w = som.winner(xx) # getting the winner + # palce a marker on the winning position for the sample xx + plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None', + markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) +axis([0,som.weights.shape[0],0,som.weights.shape[1]]) +show() # show the figure \ No newline at end of file diff --git a/examples/iris.csv b/examples/iris.csv new file mode 100644 index 0000000..1a75486 --- /dev/null +++ b/examples/iris.csv @@ -0,0 +1,150 @@ +5.1,3.5,1.4,0.2,setosa +4.9,3.0,1.4,0.2,setosa +4.7,3.2,1.3,0.2,setosa +4.6,3.1,1.5,0.2,setosa +5.0,3.6,1.4,0.2,setosa +5.4,3.9,1.7,0.4,setosa +4.6,3.4,1.4,0.3,setosa +5.0,3.4,1.5,0.2,setosa +4.4,2.9,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.4,3.7,1.5,0.2,setosa +4.8,3.4,1.6,0.2,setosa +4.8,3.0,1.4,0.1,setosa +4.3,3.0,1.1,0.1,setosa +5.8,4.0,1.2,0.2,setosa +5.7,4.4,1.5,0.4,setosa +5.4,3.9,1.3,0.4,setosa +5.1,3.5,1.4,0.3,setosa +5.7,3.8,1.7,0.3,setosa +5.1,3.8,1.5,0.3,setosa +5.4,3.4,1.7,0.2,setosa +5.1,3.7,1.5,0.4,setosa +4.6,3.6,1.0,0.2,setosa +5.1,3.3,1.7,0.5,setosa +4.8,3.4,1.9,0.2,setosa +5.0,3.0,1.6,0.2,setosa +5.0,3.4,1.6,0.4,setosa +5.2,3.5,1.5,0.2,setosa +5.2,3.4,1.4,0.2,setosa +4.7,3.2,1.6,0.2,setosa +4.8,3.1,1.6,0.2,setosa +5.4,3.4,1.5,0.4,setosa +5.2,4.1,1.5,0.1,setosa +5.5,4.2,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.0,3.2,1.2,0.2,setosa +5.5,3.5,1.3,0.2,setosa +4.9,3.1,1.5,0.1,setosa +4.4,3.0,1.3,0.2,setosa +5.1,3.4,1.5,0.2,setosa +5.0,3.5,1.3,0.3,setosa +4.5,2.3,1.3,0.3,setosa +4.4,3.2,1.3,0.2,setosa +5.0,3.5,1.6,0.6,setosa +5.1,3.8,1.9,0.4,setosa +4.8,3.0,1.4,0.3,setosa +5.1,3.8,1.6,0.2,setosa +4.6,3.2,1.4,0.2,setosa +5.3,3.7,1.5,0.2,setosa +5.0,3.3,1.4,0.2,setosa +7.0,3.2,4.7,1.4,versicolor +6.4,3.2,4.5,1.5,versicolor +6.9,3.1,4.9,1.5,versicolor +5.5,2.3,4.0,1.3,versicolor +6.5,2.8,4.6,1.5,versicolor +5.7,2.8,4.5,1.3,versicolor +6.3,3.3,4.7,1.6,versicolor +4.9,2.4,3.3,1.0,versicolor +6.6,2.9,4.6,1.3,versicolor +5.2,2.7,3.9,1.4,versicolor +5.0,2.0,3.5,1.0,versicolor +5.9,3.0,4.2,1.5,versicolor +6.0,2.2,4.0,1.0,versicolor +6.1,2.9,4.7,1.4,versicolor +5.6,2.9,3.6,1.3,versicolor +6.7,3.1,4.4,1.4,versicolor +5.6,3.0,4.5,1.5,versicolor +5.8,2.7,4.1,1.0,versicolor +6.2,2.2,4.5,1.5,versicolor +5.6,2.5,3.9,1.1,versicolor +5.9,3.2,4.8,1.8,versicolor +6.1,2.8,4.0,1.3,versicolor +6.3,2.5,4.9,1.5,versicolor +6.1,2.8,4.7,1.2,versicolor +6.4,2.9,4.3,1.3,versicolor +6.6,3.0,4.4,1.4,versicolor +6.8,2.8,4.8,1.4,versicolor +6.7,3.0,5.0,1.7,versicolor +6.0,2.9,4.5,1.5,versicolor +5.7,2.6,3.5,1.0,versicolor +5.5,2.4,3.8,1.1,versicolor +5.5,2.4,3.7,1.0,versicolor +5.8,2.7,3.9,1.2,versicolor +6.0,2.7,5.1,1.6,versicolor +5.4,3.0,4.5,1.5,versicolor +6.0,3.4,4.5,1.6,versicolor +6.7,3.1,4.7,1.5,versicolor +6.3,2.3,4.4,1.3,versicolor +5.6,3.0,4.1,1.3,versicolor +5.5,2.5,4.0,1.3,versicolor +5.5,2.6,4.4,1.2,versicolor +6.1,3.0,4.6,1.4,versicolor +5.8,2.6,4.0,1.2,versicolor +5.0,2.3,3.3,1.0,versicolor +5.6,2.7,4.2,1.3,versicolor +5.7,3.0,4.2,1.2,versicolor +5.7,2.9,4.2,1.3,versicolor +6.2,2.9,4.3,1.3,versicolor +5.1,2.5,3.0,1.1,versicolor +5.7,2.8,4.1,1.3,versicolor +6.3,3.3,6.0,2.5,virginica +5.8,2.7,5.1,1.9,virginica +7.1,3.0,5.9,2.1,virginica +6.3,2.9,5.6,1.8,virginica +6.5,3.0,5.8,2.2,virginica +7.6,3.0,6.6,2.1,virginica +4.9,2.5,4.5,1.7,virginica +7.3,2.9,6.3,1.8,virginica +6.7,2.5,5.8,1.8,virginica +7.2,3.6,6.1,2.5,virginica +6.5,3.2,5.1,2.0,virginica +6.4,2.7,5.3,1.9,virginica +6.8,3.0,5.5,2.1,virginica +5.7,2.5,5.0,2.0,virginica +5.8,2.8,5.1,2.4,virginica +6.4,3.2,5.3,2.3,virginica +6.5,3.0,5.5,1.8,virginica +7.7,3.8,6.7,2.2,virginica +7.7,2.6,6.9,2.3,virginica +6.0,2.2,5.0,1.5,virginica +6.9,3.2,5.7,2.3,virginica +5.6,2.8,4.9,2.0,virginica +7.7,2.8,6.7,2.0,virginica +6.3,2.7,4.9,1.8,virginica +6.7,3.3,5.7,2.1,virginica +7.2,3.2,6.0,1.8,virginica +6.2,2.8,4.8,1.8,virginica +6.1,3.0,4.9,1.8,virginica +6.4,2.8,5.6,2.1,virginica +7.2,3.0,5.8,1.6,virginica +7.4,2.8,6.1,1.9,virginica +7.9,3.8,6.4,2.0,virginica +6.4,2.8,5.6,2.2,virginica +6.3,2.8,5.1,1.5,virginica +6.1,2.6,5.6,1.4,virginica +7.7,3.0,6.1,2.3,virginica +6.3,3.4,5.6,2.4,virginica +6.4,3.1,5.5,1.8,virginica +6.0,3.0,4.8,1.8,virginica +6.9,3.1,5.4,2.1,virginica +6.7,3.1,5.6,2.4,virginica +6.9,3.1,5.1,2.3,virginica +5.8,2.7,5.1,1.9,virginica +6.8,3.2,5.9,2.3,virginica +6.7,3.3,5.7,2.5,virginica +6.7,3.0,5.2,2.3,virginica +6.3,2.5,5.0,1.9,virginica +6.5,3.0,5.2,2.0,virginica +6.2,3.4,5.4,2.3,virginica +5.9,3.0,5.1,1.8,virginica diff --git a/minisom.py b/minisom.py index 854a12c..68ca399 100644 --- a/minisom.py +++ b/minisom.py @@ -117,44 +117,4 @@ def activation_response(self,data): for x in data: a[self.winner(x)] += 1 return a - -if __name__ == '__main__': - """ - This main contains a usage example of each feature implemented. Soon will be moved in the documentation. - """ - import sys - # reading the data from a csv file - from numpy import genfromtxt - # http://aima.cs.berkeley.edu/data/iris.csv - data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) - data = array([x/linalg.norm(x) for x in data]) # normalization - - # initialization and training - som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) - som.random_weights_init(data) - print "Training..." - som.train_random(data,500) - #som.train_batch(data,150*5) - print "\n...ready!" - - from pylab import plot,axis,show,pcolor,colorbar,bone - bone() - pcolor(som.distance_map().T) # plotting the distance map as background - #pcolor(som.activate(data[1]).T) - #pcolor(som.activation_response(data).T) - colorbar() - # plotting the response for each pattern in the iris dataset - target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels - t = zeros(len(target),dtype=int) - t[target == 'setosa'] = 0 - t[target == 'versicolor'] = 1 - t[target == 'virginica'] = 2 - # different colors and markers for each label - markers = ['o','s','D'] - colors = ['r','g','b'] - for cnt,xx in enumerate(data): - w = som.winner(xx) # getting the winner - plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None',markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) - axis([0,som.weights.shape[0],0,som.weights.shape[1]]) - show() \ No newline at end of file diff --git a/setup.py b/setup.py index 4988de6..c43c908 100644 --- a/setup.py +++ b/setup.py @@ -2,17 +2,13 @@ from distutils.core import setup -try: - import numpy - setup(name='MiniSom', - version='0.1', - description='Minimalistic implementation of the Self Organizing Maps (SOM)', - author='Giuseppe Vettigli', - package_data={'': ['Readme.md']}, - include_package_data=True, - license="CC BY 3.0", - py_modules=['minisom'], - requires = ['numpy'] - ) -except ImportError: - print 'You need Numpy in order to use MiniSom: http://www.scipy.org/install.html' \ No newline at end of file +setup(name='MiniSom', + version='0.1', + description='Minimalistic implementation of the Self Organizing Maps (SOM)', + author='Giuseppe Vettigli', + package_data={'': ['Readme.md']}, + include_package_data=True, + license="CC BY 3.0", + py_modules=['minisom'], + requires = ['numpy'] + ) From d941e4029851c529eb34a0ee1f1f22b94de25a70 Mon Sep 17 00:00:00 2001 From: Peppe Date: Thu, 11 Jul 2013 21:32:47 +0200 Subject: [PATCH 008/352] iris example added, Readme updated --- Readme.md | 28 +++++--- examples/example_iris.py | 41 +++++++++++ examples/iris.csv | 150 +++++++++++++++++++++++++++++++++++++++ minisom.py | 71 +++++------------- setup.py | 24 +++---- 5 files changed, 237 insertions(+), 77 deletions(-) create mode 100644 examples/example_iris.py create mode 100644 examples/iris.csv diff --git a/Readme.md b/Readme.md index 3401c44..8cbae53 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,7 @@ MiniSom Self Organizing Maps -------------------- -MiniSom is minimalistic implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. +MiniSom is minimalistic Numpy based implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. Installation --------------------- @@ -20,7 +20,7 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac data = [[ 5.1 3.5 1.4 0.2], [ 4.9 3. 1.4 0.2], - [ 4.7 3.2 1.3 0.2], # <-- single pattern + [ 4.7 3.2 1.3 0.2], # <-- single observation [ 4.6 3.1 1.5 0.2], [ 5. 3.6 1.4 0.2], [ 4.1 3.3 1.4 0.2], @@ -34,23 +34,33 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" -#### Results +#### Using the trained SOM After the training MiniSom makes you able to -* Compute the coordinate of a sample `x` on the map with the method `winner(x)`. -* Compute the average distance map of the weights on the map.*the number of times with the method `distance_map` -* Compute the number of times that each neuron is winner for a new data set with the method `activation_response(data)`. +* Compute the coordinate assigned to an observation `x` on the map with the method `winner(x)`. +* Compute the average distance map of the weights on the map with the method `distance_map`. +* Compute the number of times that each neuron have been considered winner for the observations of a new data set with the method `activation_response(data)`. -### Other features +### Training algorithms -MiniSom implements two types of training. The random training (implemente by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemente by the method `train_batch`), where the samples are used in the order they are stored. +MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. + +### Weights initialization MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. +Examples +--------------------- +In examples/example_iris.py you can find an example that shows how to train MiniSom and visualize the result using the Iris flower dataset. Here is the result of the script: + +Iris example + +For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as backgroud (see the color bar on the right to associate the value). + Planned improvements --------------------- -* Implement a classification method. +* Implement a classification mechanism. License --------------------- diff --git a/examples/example_iris.py b/examples/example_iris.py new file mode 100644 index 0000000..fdb7677 --- /dev/null +++ b/examples/example_iris.py @@ -0,0 +1,41 @@ +from minisom import MiniSom +from numpy import genfromtxt,array,linalg,zeros + +""" + This script shows how to use MiniSom on the Iris dataset. + In partucular it shows how to train MiniSom and how to visualize the result. + ATTENTION: pylab is required for the visualization. +""" + +# reading the iris dataset in the csv format +# (downloaded from http://aima.cs.berkeley.edu/data/iris.csv) +data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) +data = array([x/linalg.norm(x) for x in data]) # data normalization + +### Initialization and training ### +som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) +som.random_weights_init(data) +print "Training..." +som.train_random(data,500) # random training +print "\n...ready!" + +### Plotting the response for each pattern in the iris dataset ### +from pylab import plot,axis,show,pcolor,colorbar,bone +bone() +pcolor(som.distance_map().T) # plotting the distance map as background +colorbar() +target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels +t = zeros(len(target),dtype=int) +t[target == 'setosa'] = 0 +t[target == 'versicolor'] = 1 +t[target == 'virginica'] = 2 +# use different colors and markers for each label +markers = ['o','s','D'] +colors = ['r','g','b'] +for cnt,xx in enumerate(data): + w = som.winner(xx) # getting the winner + # palce a marker on the winning position for the sample xx + plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None', + markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) +axis([0,som.weights.shape[0],0,som.weights.shape[1]]) +show() # show the figure \ No newline at end of file diff --git a/examples/iris.csv b/examples/iris.csv new file mode 100644 index 0000000..1a75486 --- /dev/null +++ b/examples/iris.csv @@ -0,0 +1,150 @@ +5.1,3.5,1.4,0.2,setosa +4.9,3.0,1.4,0.2,setosa +4.7,3.2,1.3,0.2,setosa +4.6,3.1,1.5,0.2,setosa +5.0,3.6,1.4,0.2,setosa +5.4,3.9,1.7,0.4,setosa +4.6,3.4,1.4,0.3,setosa +5.0,3.4,1.5,0.2,setosa +4.4,2.9,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.4,3.7,1.5,0.2,setosa +4.8,3.4,1.6,0.2,setosa +4.8,3.0,1.4,0.1,setosa +4.3,3.0,1.1,0.1,setosa +5.8,4.0,1.2,0.2,setosa +5.7,4.4,1.5,0.4,setosa +5.4,3.9,1.3,0.4,setosa +5.1,3.5,1.4,0.3,setosa +5.7,3.8,1.7,0.3,setosa +5.1,3.8,1.5,0.3,setosa +5.4,3.4,1.7,0.2,setosa +5.1,3.7,1.5,0.4,setosa +4.6,3.6,1.0,0.2,setosa +5.1,3.3,1.7,0.5,setosa +4.8,3.4,1.9,0.2,setosa +5.0,3.0,1.6,0.2,setosa +5.0,3.4,1.6,0.4,setosa +5.2,3.5,1.5,0.2,setosa +5.2,3.4,1.4,0.2,setosa +4.7,3.2,1.6,0.2,setosa +4.8,3.1,1.6,0.2,setosa +5.4,3.4,1.5,0.4,setosa +5.2,4.1,1.5,0.1,setosa +5.5,4.2,1.4,0.2,setosa +4.9,3.1,1.5,0.1,setosa +5.0,3.2,1.2,0.2,setosa +5.5,3.5,1.3,0.2,setosa +4.9,3.1,1.5,0.1,setosa +4.4,3.0,1.3,0.2,setosa +5.1,3.4,1.5,0.2,setosa +5.0,3.5,1.3,0.3,setosa +4.5,2.3,1.3,0.3,setosa +4.4,3.2,1.3,0.2,setosa +5.0,3.5,1.6,0.6,setosa +5.1,3.8,1.9,0.4,setosa +4.8,3.0,1.4,0.3,setosa +5.1,3.8,1.6,0.2,setosa +4.6,3.2,1.4,0.2,setosa +5.3,3.7,1.5,0.2,setosa +5.0,3.3,1.4,0.2,setosa +7.0,3.2,4.7,1.4,versicolor +6.4,3.2,4.5,1.5,versicolor +6.9,3.1,4.9,1.5,versicolor +5.5,2.3,4.0,1.3,versicolor +6.5,2.8,4.6,1.5,versicolor +5.7,2.8,4.5,1.3,versicolor +6.3,3.3,4.7,1.6,versicolor +4.9,2.4,3.3,1.0,versicolor +6.6,2.9,4.6,1.3,versicolor +5.2,2.7,3.9,1.4,versicolor +5.0,2.0,3.5,1.0,versicolor +5.9,3.0,4.2,1.5,versicolor +6.0,2.2,4.0,1.0,versicolor +6.1,2.9,4.7,1.4,versicolor +5.6,2.9,3.6,1.3,versicolor +6.7,3.1,4.4,1.4,versicolor +5.6,3.0,4.5,1.5,versicolor +5.8,2.7,4.1,1.0,versicolor +6.2,2.2,4.5,1.5,versicolor +5.6,2.5,3.9,1.1,versicolor +5.9,3.2,4.8,1.8,versicolor +6.1,2.8,4.0,1.3,versicolor +6.3,2.5,4.9,1.5,versicolor +6.1,2.8,4.7,1.2,versicolor +6.4,2.9,4.3,1.3,versicolor +6.6,3.0,4.4,1.4,versicolor +6.8,2.8,4.8,1.4,versicolor +6.7,3.0,5.0,1.7,versicolor +6.0,2.9,4.5,1.5,versicolor +5.7,2.6,3.5,1.0,versicolor +5.5,2.4,3.8,1.1,versicolor +5.5,2.4,3.7,1.0,versicolor +5.8,2.7,3.9,1.2,versicolor +6.0,2.7,5.1,1.6,versicolor +5.4,3.0,4.5,1.5,versicolor +6.0,3.4,4.5,1.6,versicolor +6.7,3.1,4.7,1.5,versicolor +6.3,2.3,4.4,1.3,versicolor +5.6,3.0,4.1,1.3,versicolor +5.5,2.5,4.0,1.3,versicolor +5.5,2.6,4.4,1.2,versicolor +6.1,3.0,4.6,1.4,versicolor +5.8,2.6,4.0,1.2,versicolor +5.0,2.3,3.3,1.0,versicolor +5.6,2.7,4.2,1.3,versicolor +5.7,3.0,4.2,1.2,versicolor +5.7,2.9,4.2,1.3,versicolor +6.2,2.9,4.3,1.3,versicolor +5.1,2.5,3.0,1.1,versicolor +5.7,2.8,4.1,1.3,versicolor +6.3,3.3,6.0,2.5,virginica +5.8,2.7,5.1,1.9,virginica +7.1,3.0,5.9,2.1,virginica +6.3,2.9,5.6,1.8,virginica +6.5,3.0,5.8,2.2,virginica +7.6,3.0,6.6,2.1,virginica +4.9,2.5,4.5,1.7,virginica +7.3,2.9,6.3,1.8,virginica +6.7,2.5,5.8,1.8,virginica +7.2,3.6,6.1,2.5,virginica +6.5,3.2,5.1,2.0,virginica +6.4,2.7,5.3,1.9,virginica +6.8,3.0,5.5,2.1,virginica +5.7,2.5,5.0,2.0,virginica +5.8,2.8,5.1,2.4,virginica +6.4,3.2,5.3,2.3,virginica +6.5,3.0,5.5,1.8,virginica +7.7,3.8,6.7,2.2,virginica +7.7,2.6,6.9,2.3,virginica +6.0,2.2,5.0,1.5,virginica +6.9,3.2,5.7,2.3,virginica +5.6,2.8,4.9,2.0,virginica +7.7,2.8,6.7,2.0,virginica +6.3,2.7,4.9,1.8,virginica +6.7,3.3,5.7,2.1,virginica +7.2,3.2,6.0,1.8,virginica +6.2,2.8,4.8,1.8,virginica +6.1,3.0,4.9,1.8,virginica +6.4,2.8,5.6,2.1,virginica +7.2,3.0,5.8,1.6,virginica +7.4,2.8,6.1,1.9,virginica +7.9,3.8,6.4,2.0,virginica +6.4,2.8,5.6,2.2,virginica +6.3,2.8,5.1,1.5,virginica +6.1,2.6,5.6,1.4,virginica +7.7,3.0,6.1,2.3,virginica +6.3,3.4,5.6,2.4,virginica +6.4,3.1,5.5,1.8,virginica +6.0,3.0,4.8,1.8,virginica +6.9,3.1,5.4,2.1,virginica +6.7,3.1,5.6,2.4,virginica +6.9,3.1,5.1,2.3,virginica +5.8,2.7,5.1,1.9,virginica +6.8,3.2,5.9,2.3,virginica +6.7,3.3,5.7,2.5,virginica +6.7,3.0,5.2,2.3,virginica +6.3,2.5,5.0,1.9,virginica +6.5,3.0,5.2,2.0,virginica +6.2,3.4,5.4,2.3,virginica +5.9,3.0,5.1,1.8,virginica diff --git a/minisom.py b/minisom.py index 3d2bfb2..68ca399 100644 --- a/minisom.py +++ b/minisom.py @@ -1,11 +1,9 @@ from numpy import meshgrid,sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros """ -Minimalistic implementation of the Self Organizing Maps (SOM) + Minimalistic implementation of the Self Organizing Maps (SOM) -SOM is able to convert complex, nonlinear statistical relationsihps between high-dimensional data items into simple geometric relationships on a low-dimensional display. - -http://www.sis.pitt.edu/~ssyn/som/som.html + Giuseppe Vettigli 2013. """ class MiniSom: @@ -56,13 +54,16 @@ def update(self,x,win,t): eta - learning rate t - iteration index """ - # eta(t) = eta(0) / (1 + t/T) keeps the learning rate nearly constant for the first T iterations and then adjusts it + # eta(t) = eta(0) / (1 + t/T) + # keeps the learning rate nearly constant for the first T iterations and then adjusts it eta = self.learning_rate/(1+t/self.T) g = self.gaussian(win,self.sigma)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: - self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) # eta * neighborhood_function * (x-w) - self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) # weights normalization + # eta * neighborhood_function * (x-w) + self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) + # normalization + self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) it.iternext() def random_weights_init(self,data): @@ -75,30 +76,27 @@ def random_weights_init(self,data): def train_random(self,data,num_iteration): """ Trains the SOM picking samples at random from data """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self._init_T(num_iteration) for iteration in range(num_iteration): - rand_i = int(round(random.rand()*len(data)-1)) # pick a random sampleprint data[rand_i] + rand_i = int(round(random.rand()*len(data)-1)) # pick a random sample self.update(data[rand_i],self.winner(data[rand_i]),iteration) - self._show_progress(iteration,num_iteration) def train_batch(self,data,num_iteration): """ Trains using all the vectors in data sequentially """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self._init_T(num_iteration) iteration = 0 while iteration < num_iteration: idx = iteration % (len(data)-1) self.update(data[idx],self.winner(data[idx]),iteration) - self._show_progress(iteration,num_iteration-1) iteration += 1 - def _show_progress(self,iteration,num_iteration): - progress = round((iteration/float(num_iteration))*50) - #sys.stdout.write('\r') - #sys.stdout.write('\r[ {0} {1}] {2}%'.format('#'*int(progress),' '*int(50-(progress)), int(progress*2))) - #sys.stdout.flush() + def _init_T(self,num_iteration): + """ Initializes the parameter T needed to adjust the learning rate """ + self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations def distance_map(self): - """ Returns the average distance map of the weights """ + """ Returns the average distance map of the weights. + (Each mean is normalized in order to sum up to 1) """ um = zeros((self.weights.shape[0],self.weights.shape[1])) it = nditer(um, flags=['multi_index']) while not it.finished: @@ -107,7 +105,7 @@ def distance_map(self): if ii >= 0 and ii < self.weights.shape[0] and jj >= 0 and jj < self.weights.shape[1]: um[it.multi_index] += linalg.norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) it.iternext() - um = um/8 # should be different at the borders + um = um/um.max() return um def activation_response(self,data): @@ -119,39 +117,4 @@ def activation_response(self,data): for x in data: a[self.winner(x)] += 1 return a - -if __name__ == '__main__': - import sys - # reading the data from a csv file - from numpy import genfromtxt - data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) - #data = array([x/linalg.norm(x) for x in data]) # normalization - - # initialization and training - som = MiniSom(6,6,4,sigma=0.3,learning_rate=0.5) - som.random_weights_init(data) - print "Training..." - som.train_random(data,100) - #som.train_batch(data,150*5) - print "\n...ready!" - - from pylab import plot,axis,show,pcolor,colorbar,bone - bone() - pcolor(som.distance_map().T) - #pcolor(som.activate(data[1]).T) - #pcolor(som.activation_response(data).T) - colorbar() - # plotting the response for each pattern - target = genfromtxt('iris.csv',delimiter=',',usecols=(4),dtype=str) # loading the labels - t = zeros(len(target),dtype=int) - t[target == 'setosa'] = 0 - t[target == 'versicolor'] = 1 - t[target == 'virginica'] = 2 - markers = ['o','s','D'] - colors = ['r','g','b'] - for cnt,xx in enumerate(data): - w = som.winner(xx) # getting the winner - plot(w[0]+.5,w[1]+.5,markers[t[cnt]],markerfacecolor='None',markeredgecolor=colors[t[cnt]],markersize=12,markeredgewidth=2) - axis([0,som.weights.shape[0],0,som.weights.shape[1]]) - show() \ No newline at end of file diff --git a/setup.py b/setup.py index 4988de6..c43c908 100644 --- a/setup.py +++ b/setup.py @@ -2,17 +2,13 @@ from distutils.core import setup -try: - import numpy - setup(name='MiniSom', - version='0.1', - description='Minimalistic implementation of the Self Organizing Maps (SOM)', - author='Giuseppe Vettigli', - package_data={'': ['Readme.md']}, - include_package_data=True, - license="CC BY 3.0", - py_modules=['minisom'], - requires = ['numpy'] - ) -except ImportError: - print 'You need Numpy in order to use MiniSom: http://www.scipy.org/install.html' \ No newline at end of file +setup(name='MiniSom', + version='0.1', + description='Minimalistic implementation of the Self Organizing Maps (SOM)', + author='Giuseppe Vettigli', + package_data={'': ['Readme.md']}, + include_package_data=True, + license="CC BY 3.0", + py_modules=['minisom'], + requires = ['numpy'] + ) From 309f9ba754082bb50c0c6433df5f975dbed5bfca Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 11 Jul 2013 21:46:50 +0200 Subject: [PATCH 009/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 8cbae53..6766045 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,7 @@ MiniSom ==================== -![MiniSom]( http://1.bp.blogspot.com/-tD8Kg6FEOcg/Uc_wjGZ7qaI/AAAAAAAAAo4/A4Q1_dbqVLo/s278/logo.png "MiniSom") +![MiniSom]( http://3.bp.blogspot.com/-TjLGnec3uko/Ud8LbHTpO1I/AAAAAAAAAqk/nfJneFOZrK8/s1600/logo.png "MiniSom") Self Organizing Maps -------------------- From 7d6ca44bc7ebb25fe7bb8e185bed3d496a145afd Mon Sep 17 00:00:00 2001 From: giu Date: Fri, 19 Jul 2013 20:52:33 +0200 Subject: [PATCH 010/352] quantization method added --- minisom.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/minisom.py b/minisom.py index 68ca399..7d1b148 100644 --- a/minisom.py +++ b/minisom.py @@ -66,6 +66,14 @@ def update(self,x,win,t): self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) it.iternext() + def quantization(self,data): + """ Assigns a code book (weights vector of the winning neuron) to each sample in data. """ + q = zeros(data.shape) + for i,x in enumerate(data): + q[i] = self.weights[self.winner(x)] + return q + + def random_weights_init(self,data): """ Initializes the weights of the SOM picking random samples from data """ it = nditer(self.activation_map, flags=['multi_index']) From 2359875e0349539cffb4cf1a5046318e6fd27f07 Mon Sep 17 00:00:00 2001 From: giu Date: Sat, 20 Jul 2013 11:40:09 +0200 Subject: [PATCH 011/352] color quantization example added and other minor chanjes to minisom.py --- Readme.md | 8 +++++++- examples/example_color.py | 42 ++++++++++++++++++++++++++++++++++++++ examples/tree.jpg | Bin 0 -> 23632 bytes minisom.py | 3 +-- 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 examples/example_color.py create mode 100644 examples/tree.jpg diff --git a/Readme.md b/Readme.md index 6766045..1f65d3a 100644 --- a/Readme.md +++ b/Readme.md @@ -50,13 +50,19 @@ MiniSom implements two types of training. The random training (implemented by th MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. +### Vector quantization + +The data can be quantized by assigning a code book (weights vector of the winning neuron) to each sample in data. This kind of vector quantization is implemented by the method `quantization`. + Examples --------------------- In examples/example_iris.py you can find an example that shows how to train MiniSom and visualize the result using the Iris flower dataset. Here is the result of the script: Iris example -For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as backgroud (see the color bar on the right to associate the value). +For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). + +NOTE: the examples require pylab for the visualization of the results. Planned improvements --------------------- diff --git a/examples/example_color.py b/examples/example_color.py new file mode 100644 index 0000000..b8d370a --- /dev/null +++ b/examples/example_color.py @@ -0,0 +1,42 @@ +from pylab import imread,imshow,figure,show,subplot,title +from numpy import reshape,flipud,unravel_index,zeros +from minisom import MiniSom + +# read the image +img = imread('tree.jpg') + +# reshaping the pixels matrix +pixels = reshape(img,(img.shape[0]*img.shape[1],3)) + +# SOM initialization and training +print 'training...' +som = MiniSom(3,3,3,sigma=0.1,learning_rate=0.2) # 3x3 = 9 final colors +som.random_weights_init(pixels) +starting_weights = som.weights.copy() # saving the starting weights +som.train_random(pixels,100) + +print 'quantization...' +qnt = som.quantization(pixels) # quantize each pixels of the image +print 'building new image...' +clustered = zeros(img.shape) +for i,q in enumerate(qnt): # place the quantized values into a new image + clustered[unravel_index(i,dims=(img.shape[0],img.shape[1]))] = q +print 'done.' + +# show the result +figure(1) +subplot(221) +title('original') +imshow(flipud(img)) +subplot(222) +title('result') +imshow(flipud(clustered)) + +subplot(223) +title('initial colors') +imshow(flipud(starting_weights),interpolation='none') +subplot(224) +title('learned colors') +imshow(flipud(som.weights),interpolation='none') + +show() \ No newline at end of file diff --git a/examples/tree.jpg b/examples/tree.jpg new file mode 100644 index 0000000000000000000000000000000000000000..98f87bda2d4ed44cc23b65254492392686cb59d9 GIT binary patch literal 23632 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 literal 0 HcmV?d00001 diff --git a/minisom.py b/minisom.py index 7d1b148..575b982 100644 --- a/minisom.py +++ b/minisom.py @@ -19,7 +19,6 @@ def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization - self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization self.activation_map = zeros((x,y)) self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function @@ -91,7 +90,7 @@ def train_random(self,data,num_iteration): def train_batch(self,data,num_iteration): """ Trains using all the vectors in data sequentially """ - self._init_T(num_iteration) + self._init_T(len(data)*num_iteration) iteration = 0 while iteration < num_iteration: idx = iteration % (len(data)-1) From b338138000229ff89b2f445df26cba0675a0a09f Mon Sep 17 00:00:00 2001 From: giu Date: Sun, 21 Jul 2013 10:40:07 +0200 Subject: [PATCH 012/352] Readme updated --- Readme.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 1f65d3a..fa84a81 100644 --- a/Readme.md +++ b/Readme.md @@ -18,13 +18,13 @@ How to use it In order to use MiniSom you need your data organized as a Numpy matrix where each row corresponds to an observation or an as list of lists like the following: - data = [[ 5.1 3.5 1.4 0.2], - [ 4.9 3. 1.4 0.2], - [ 4.7 3.2 1.3 0.2], # <-- single observation - [ 4.6 3.1 1.5 0.2], - [ 5. 3.6 1.4 0.2], - [ 4.1 3.3 1.4 0.2], - [ 4.2 3.2 1.2 0.2]] + data = [[ 5.1 3.5 1.4 0.2], + [ 4.9 3. 1.4 0.2], + [ 4.7 3.2 1.3 0.2], # <-- single observation + [ 4.6 3.1 1.5 0.2], + [ 5. 3.6 1.4 0.2], + [ 4.1 3.3 1.4 0.2], + [ 4.2 3.2 1.2 0.2]] Then you can run MiniSom just as follows: @@ -34,7 +34,7 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" -#### Using the trained SOM +### Using the trained SOM After the training MiniSom makes you able to @@ -42,15 +42,15 @@ After the training MiniSom makes you able to * Compute the average distance map of the weights on the map with the method `distance_map`. * Compute the number of times that each neuron have been considered winner for the observations of a new data set with the method `activation_response(data)`. -### Training algorithms +#### Training algorithms MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. -### Weights initialization +#### Weights initialization MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. -### Vector quantization +#### Vector quantization The data can be quantized by assigning a code book (weights vector of the winning neuron) to each sample in data. This kind of vector quantization is implemented by the method `quantization`. @@ -60,13 +60,18 @@ In examples/example_iris.py you can find an example that shows how to train Mini Iris example -For each winner neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). +For each winning neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). + +In examples/example_color.py you can find an example of how to use MiniSom for color quantization. Here's one of the possible results: + +Color quantization example NOTE: the examples require pylab for the visualization of the results. Planned improvements --------------------- * Implement a classification mechanism. +* Move to Python 3 License --------------------- From a1047cbc3db93bdb62739542efafae3306893b29 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Mon, 22 Jul 2013 11:20:24 +0200 Subject: [PATCH 013/352] minor changes --- Readme.md | 2 +- examples/example_color.py | 8 ++++---- examples/example_iris.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index fa84a81..8d5c4f0 100644 --- a/Readme.md +++ b/Readme.md @@ -71,7 +71,7 @@ NOTE: the examples require pylab for the visualization of the results. Planned improvements --------------------- * Implement a classification mechanism. -* Move to Python 3 +* Check the compatibility Python 3. License --------------------- diff --git a/examples/example_color.py b/examples/example_color.py index b8d370a..9b2aa37 100644 --- a/examples/example_color.py +++ b/examples/example_color.py @@ -9,19 +9,19 @@ pixels = reshape(img,(img.shape[0]*img.shape[1],3)) # SOM initialization and training -print 'training...' +print('training...') som = MiniSom(3,3,3,sigma=0.1,learning_rate=0.2) # 3x3 = 9 final colors som.random_weights_init(pixels) starting_weights = som.weights.copy() # saving the starting weights som.train_random(pixels,100) -print 'quantization...' +print('quantization...') qnt = som.quantization(pixels) # quantize each pixels of the image -print 'building new image...' +print('building new image...') clustered = zeros(img.shape) for i,q in enumerate(qnt): # place the quantized values into a new image clustered[unravel_index(i,dims=(img.shape[0],img.shape[1]))] = q -print 'done.' +print('done.') # show the result figure(1) diff --git a/examples/example_iris.py b/examples/example_iris.py index fdb7677..bffc75f 100644 --- a/examples/example_iris.py +++ b/examples/example_iris.py @@ -15,9 +15,9 @@ ### Initialization and training ### som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) som.random_weights_init(data) -print "Training..." +print("Training...") som.train_random(data,500) # random training -print "\n...ready!" +print("\n...ready!") ### Plotting the response for each pattern in the iris dataset ### from pylab import plot,axis,show,pcolor,colorbar,bone From ca19487814f9ccc5fa13047f1e50fb77b72af7a0 Mon Sep 17 00:00:00 2001 From: giu Date: Thu, 1 Aug 2013 17:19:26 +0200 Subject: [PATCH 014/352] minor changes --- Readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 8d5c4f0..26406e7 100644 --- a/Readme.md +++ b/Readme.md @@ -71,7 +71,10 @@ NOTE: the examples require pylab for the visualization of the results. Planned improvements --------------------- * Implement a classification mechanism. -* Check the compatibility Python 3. + +Notes +--------------------- +Minisom have been tested under python 2.7.3 and 3.2.3. License --------------------- From 42eb92bad4cd0487bda35e376574057437d5c8a8 Mon Sep 17 00:00:00 2001 From: giu Date: Mon, 5 Aug 2013 10:27:47 +0200 Subject: [PATCH 015/352] Readme updated --- Readme.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index 26406e7..42381db 100644 --- a/Readme.md +++ b/Readme.md @@ -66,11 +66,7 @@ In examples/example_color.py you can find an example of how to use MiniSom for c Color quantization example -NOTE: the examples require pylab for the visualization of the results. - -Planned improvements ---------------------- -* Implement a classification mechanism. +(the examples require matplotlib for the visualization of the results). Notes --------------------- From b50ec5207410d35bccee210df4568db0ba3e13b3 Mon Sep 17 00:00:00 2001 From: giu Date: Wed, 7 Aug 2013 12:33:39 +0200 Subject: [PATCH 016/352] minor changes --- minisom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minisom.py b/minisom.py index 575b982..428e05f 100644 --- a/minisom.py +++ b/minisom.py @@ -14,7 +14,7 @@ def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): input_len - number of the elements of the vectors in input sigma - spread of the neighborhood function (Gaussian) learning_rate - initial learning rate - (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where is #num_iteration/2) + (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) """ self.learning_rate = learning_rate self.sigma = sigma From 774e354021f0744d14d3260442de55d26402bf3d Mon Sep 17 00:00:00 2001 From: giu Date: Sat, 10 Aug 2013 14:28:09 +0200 Subject: [PATCH 017/352] some todos added --- minisom.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/minisom.py b/minisom.py index 428e05f..68a0f96 100644 --- a/minisom.py +++ b/minisom.py @@ -124,4 +124,11 @@ def activation_response(self,data): for x in data: a[self.winner(x)] += 1 return a + + def quantization_error(self,data): + """ TODO + Returns the quantization error computed as the average distance between + each input sample and its best matching unit. + """ + pass \ No newline at end of file From a4197014c96d50767ff44dd92d18848357b860e4 Mon Sep 17 00:00:00 2001 From: giu Date: Sat, 10 Aug 2013 15:11:48 +0200 Subject: [PATCH 018/352] added quantization_error method and the documentation have been revised --- Readme.md | 19 ++++++++++--------- minisom.py | 11 +++++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Readme.md b/Readme.md index 42381db..532bbf5 100644 --- a/Readme.md +++ b/Readme.md @@ -34,25 +34,26 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" +MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. + +A data driven initialization of the weights is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. + ### Using the trained SOM After the training MiniSom makes you able to * Compute the coordinate assigned to an observation `x` on the map with the method `winner(x)`. -* Compute the average distance map of the weights on the map with the method `distance_map`. +* Compute the average distance map of the weights on the map with the method `distance_map()`. * Compute the number of times that each neuron have been considered winner for the observations of a new data set with the method `activation_response(data)`. +* Compute the quantization error with the method `quantization_error(data)`. -#### Training algorithms - -MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. - -#### Weights initialization +#### Vector quantization -MiniSom initializes the neurons weights at random. A data driven initialization is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. +The data can be quantized by assigning a code book (weights vector of the winning neuron) to each sample in data. This kind of vector quantization is implemented by the method `quantization` that can be called as follows: -#### Vector quantization + qnt = som.quantization(data) -The data can be quantized by assigning a code book (weights vector of the winning neuron) to each sample in data. This kind of vector quantization is implemented by the method `quantization`. +In this example we have that `qnt[i]` is the quantized version of `data[i]`. Examples --------------------- diff --git a/minisom.py b/minisom.py index 68a0f96..befc689 100644 --- a/minisom.py +++ b/minisom.py @@ -19,6 +19,7 @@ def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization + self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization self.activation_map = zeros((x,y)) self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function @@ -126,9 +127,11 @@ def activation_response(self,data): return a def quantization_error(self,data): - """ TODO + """ Returns the quantization error computed as the average distance between - each input sample and its best matching unit. + each input sample and its best matching unit. """ - pass - \ No newline at end of file + error = 0 + for x in data: + error += linalg.norm(x-self.weights[self.winner(x)]) + return error/len(data) \ No newline at end of file From 779324553dfbd7e5f02bf911351516b38c39af6f Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Sat, 10 Aug 2013 15:13:27 +0200 Subject: [PATCH 019/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 532bbf5..b8d2c13 100644 --- a/Readme.md +++ b/Readme.md @@ -40,7 +40,7 @@ A data driven initialization of the weights is also provided by the method `rand ### Using the trained SOM -After the training MiniSom makes you able to +After the training you are able to * Compute the coordinate assigned to an observation `x` on the map with the method `winner(x)`. * Compute the average distance map of the weights on the map with the method `distance_map()`. From 0b22267f3e7f93d262deeec7e2fd8fa6906d0b9a Mon Sep 17 00:00:00 2001 From: giu Date: Sun, 11 Aug 2013 16:04:32 +0200 Subject: [PATCH 020/352] new example added --- examples/example_digits.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/example_digits.py diff --git a/examples/example_digits.py b/examples/example_digits.py new file mode 100644 index 0000000..f9ed5fc --- /dev/null +++ b/examples/example_digits.py @@ -0,0 +1,22 @@ +# load the digits dataset from scikit-learn +# 901 samples, about 180 samples per class +# the digits represented 0,1,2,3,4 +from sklearn import datasets +digits = datasets.load_digits(n_class=4) +data = digits.data # matrix where each row is a vector that represent a digit. +num = digits.target # num[i] is the digit represented by data[i] + +# training the som +from minisom import MiniSom +som = MiniSom(20,20,64,sigma=0.3,learning_rate=0.6) +print("Training...") +som.train_random(data,1500) # random training +print("\n...ready!") + +# plotting the result +from pylab import text,show,cm,axis +for x,t in zip(data,num): + w = som.winner(x) # getting the winner + text(w[0]+.5, w[1]+.5, str(t), color=cm.Dark2(t / 5.),fontdict={'weight': 'bold', 'size': 11}) +axis([0,som.weights.shape[0],0,som.weights.shape[1]]) +show() # show the figure \ No newline at end of file From 943b3e1c171b911dfd17e5285de3ac9091bab5f7 Mon Sep 17 00:00:00 2001 From: giu Date: Mon, 12 Aug 2013 12:19:18 +0200 Subject: [PATCH 021/352] gaussian fixed --- examples/example_digits.py | 31 ++++++++++++++++++++++++------- examples/example_iris.py | 2 +- minisom.py | 7 ++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/examples/example_digits.py b/examples/example_digits.py index f9ed5fc..0e55c01 100644 --- a/examples/example_digits.py +++ b/examples/example_digits.py @@ -8,15 +8,32 @@ # training the som from minisom import MiniSom -som = MiniSom(20,20,64,sigma=0.3,learning_rate=0.6) +som = MiniSom(20,20,64,sigma=.8,learning_rate=0.5) print("Training...") som.train_random(data,1500) # random training print("\n...ready!") -# plotting the result -from pylab import text,show,cm,axis -for x,t in zip(data,num): - w = som.winner(x) # getting the winner - text(w[0]+.5, w[1]+.5, str(t), color=cm.Dark2(t / 5.),fontdict={'weight': 'bold', 'size': 11}) +# plotting the results +from pylab import text,show,cm,axis,figure,subplot,imshow,zeros +wmap = {} +figure(1) +im = 0 +for x,t in zip(data,num): # scatterplot + w = som.winner(x) + wmap[w] = im + text(w[0]+.5, w[1]+.5, str(t), color=cm.Dark2(t / 4.), fontdict={'weight': 'bold', 'size': 11}) + im = im + 1 axis([0,som.weights.shape[0],0,som.weights.shape[1]]) -show() # show the figure \ No newline at end of file + +figure(2,facecolor='white') +cnt = 0 +for i in range(20): # images mosaic + for j in range(20): + subplot(20,20,cnt,frameon=False, xticks=[], yticks=[]) + if (i,j) in wmap: + imshow(digits.images[wmap[(i,j)]], cmap='Greys', interpolation='nearest') + else: + imshow(zeros((8,8)), cmap='Greys') + cnt = cnt + 1 + +show() # show the figure diff --git a/examples/example_iris.py b/examples/example_iris.py index bffc75f..a645002 100644 --- a/examples/example_iris.py +++ b/examples/example_iris.py @@ -13,7 +13,7 @@ data = array([x/linalg.norm(x) for x in data]) # data normalization ### Initialization and training ### -som = MiniSom(7,7,4,sigma=0.1,learning_rate=0.5) +som = MiniSom(7,7,4,sigma=1.0,learning_rate=0.5) som.random_weights_init(data) print("Training...") som.train_random(data,500) # random training diff --git a/minisom.py b/minisom.py index befc689..61f650d 100644 --- a/minisom.py +++ b/minisom.py @@ -13,6 +13,7 @@ def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): x,y - dimensions of the SOM input_len - number of the elements of the vectors in input sigma - spread of the neighborhood function (Gaussian) + (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) """ @@ -38,8 +39,7 @@ def activate(self,x): def gaussian(self,c,sigma=0.1): """ Bidimentional Gaussian centered in c """ - d = sqrt( power((c[0]-self.neigx),2) + power((c[1]-self.neigy),2) ) - return exp(-(d*d))/(2*pi*sigma) # a matrix is returned + return exp(-(power((c[0]-self.neigx),2) + power((c[1]-self.neigy),2))/(2*pi*sigma)) # a matrix is returned def winner(self,x): """ Computes the coordinates of the winning neuron for the sample x """ @@ -57,7 +57,8 @@ def update(self,x,win,t): # eta(t) = eta(0) / (1 + t/T) # keeps the learning rate nearly constant for the first T iterations and then adjusts it eta = self.learning_rate/(1+t/self.T) - g = self.gaussian(win,self.sigma)*eta # improves the performances + sig = self.sigma/(1+t/self.T) # sigma and learning rate decrease with the same rule + g = self.gaussian(win,sig)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: # eta * neighborhood_function * (x-w) From d283ea8608f67c27f4d40ccc9109947a6984a9c8 Mon Sep 17 00:00:00 2001 From: giu Date: Mon, 12 Aug 2013 13:11:30 +0200 Subject: [PATCH 022/352] Added neig function, readme updated --- Readme.md | 12 ++++++++++-- minisom.py | 21 +++++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index b8d2c13..6f7a02e 100644 --- a/Readme.md +++ b/Readme.md @@ -63,13 +63,21 @@ In examples/example_iris.py you can find an example that shows how to train Mini For each winning neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). -In examples/example_color.py you can find an example of how to use MiniSom for color quantization. Here's one of the possible results: +In examples/example_digits.py there is an example of how to use MiniSom for images clustering. The example uses the scitkits-learn wrapper to the handwritten digits dataset. Here is one of the result that MiniSom can achieve in digits recognition: + +handwritten digitts recognition + +The graph above represent each image with the handwritten digit it contains. The position corresponds to the position of the winning neuron for the image. Here we also have a version of this graphs that shows the original images: + +handwritten digitts recognition + +In examples/example_color.py you can find an example of how to use MiniSom for color quantization. Here is one of the possible results: Color quantization example (the examples require matplotlib for the visualization of the results). -Notes +Compatibility notes --------------------- Minisom have been tested under python 2.7.3 and 3.2.3. diff --git a/minisom.py b/minisom.py index 61f650d..84918fb 100644 --- a/minisom.py +++ b/minisom.py @@ -1,13 +1,13 @@ from numpy import meshgrid,sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros """ - Minimalistic implementation of the Self Organizing Maps (SOM) + Minimalistic implementation of the Self Organizing Maps (SOM). Giuseppe Vettigli 2013. """ class MiniSom: - def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): + def __init__(self,x,y,input_len,sigma=1,learning_rate=0.5,inhibition=False): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -16,13 +16,18 @@ def __init__(self,x,y,input_len,sigma=0.1,learning_rate=0.5): (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) + inhibition - if True a difference of Gaussians will be use as neighborhood function. """ self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization self.activation_map = zeros((x,y)) - self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function + self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function + if inhibition: # set the neighborhood function to use + self.neighborhood = self.diff_gaussian + else: + self.neighborhood = self.gaussian def _activate(self,x): """ Updates matrix activation_map, in this matrix the element i,j is the response of the neuron i,j to x """ @@ -37,10 +42,14 @@ def activate(self,x): self._activate(x) return self.activation_map - def gaussian(self,c,sigma=0.1): + def gaussian(self,c,sigma=1): """ Bidimentional Gaussian centered in c """ return exp(-(power((c[0]-self.neigx),2) + power((c[1]-self.neigy),2))/(2*pi*sigma)) # a matrix is returned + def diff_gaussian(self,c,sigma): + """ Differece of Gaussians """ + return self.gaussian(c,sigma)-self.gaussian(c,sigma/3)+self.gaussian(c,sigma) + def winner(self,x): """ Computes the coordinates of the winning neuron for the sample x """ self._activate(x) @@ -49,7 +58,7 @@ def winner(self,x): def update(self,x,win,t): """ Updates the weights of the neurons. - x - current pattern to learning + x - current pattern to learn win - position of the winning neuron for x (array or tuple). eta - learning rate t - iteration index @@ -58,7 +67,7 @@ def update(self,x,win,t): # keeps the learning rate nearly constant for the first T iterations and then adjusts it eta = self.learning_rate/(1+t/self.T) sig = self.sigma/(1+t/self.T) # sigma and learning rate decrease with the same rule - g = self.gaussian(win,sig)*eta # improves the performances + g = self.neighborhood(win,sig)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: # eta * neighborhood_function * (x-w) From ccf654f6e09c55e7a414be9f4a5a45c4deefbb9e Mon Sep 17 00:00:00 2001 From: giu Date: Tue, 13 Aug 2013 13:46:58 +0200 Subject: [PATCH 023/352] gaussian function now faster --- minisom.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/minisom.py b/minisom.py index 84918fb..f11a0d2 100644 --- a/minisom.py +++ b/minisom.py @@ -1,4 +1,4 @@ -from numpy import meshgrid,sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros +from numpy import sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros,arange,outer,meshgrid """ Minimalistic implementation of the Self Organizing Maps (SOM). @@ -7,7 +7,7 @@ """ class MiniSom: - def __init__(self,x,y,input_len,sigma=1,learning_rate=0.5,inhibition=False): + def __init__(self,x,y,input_len,sigma=1,learning_rate=0.5): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -16,18 +16,15 @@ def __init__(self,x,y,input_len,sigma=1,learning_rate=0.5,inhibition=False): (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) - inhibition - if True a difference of Gaussians will be use as neighborhood function. """ self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization self.activation_map = zeros((x,y)) - self.neigx,self.neigy = meshgrid(range(y),range(x)) # used to evaluate the neighborhood function - if inhibition: # set the neighborhood function to use - self.neighborhood = self.diff_gaussian - else: - self.neighborhood = self.gaussian + self.neigx = arange(x) + self.neigy = arange(y) # used to evaluate the neighborhood function + self.neighborhood = self.gaussian def _activate(self,x): """ Updates matrix activation_map, in this matrix the element i,j is the response of the neuron i,j to x """ @@ -42,13 +39,19 @@ def activate(self,x): self._activate(x) return self.activation_map - def gaussian(self,c,sigma=1): - """ Bidimentional Gaussian centered in c """ - return exp(-(power((c[0]-self.neigx),2) + power((c[1]-self.neigy),2))/(2*pi*sigma)) # a matrix is returned + def gaussian(self,c,sigma): + """ Returns a Gaussian centered in c """ + d = 2*pi*sigma*sigma + ax = exp(-power(self.neigx-c[0],2)/d) + ay = exp(-power(self.neigy-c[1],2)/d) + return outer(ax,ay) # the external product gives a matrix def diff_gaussian(self,c,sigma): - """ Differece of Gaussians """ - return self.gaussian(c,sigma)-self.gaussian(c,sigma/3)+self.gaussian(c,sigma) + """ Mexican hat centered in c (unused) """ + xx,yy = meshgrid(self.neigx,self.neigy) + p = power(xx-c[0],2) + power(yy-c[1],2) + d = 2*pi*sigma*sigma + return exp(-(p)/d)*(1-2/d*p) def winner(self,x): """ Computes the coordinates of the winning neuron for the sample x """ From 127526b6c50535d6a9e3ae0d54215c41da494ce6 Mon Sep 17 00:00:00 2001 From: giu Date: Tue, 27 Aug 2013 21:21:10 +0200 Subject: [PATCH 024/352] minor changes --- examples/example_iris.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/example_iris.py b/examples/example_iris.py index a645002..14a4340 100644 --- a/examples/example_iris.py +++ b/examples/example_iris.py @@ -1,5 +1,5 @@ from minisom import MiniSom -from numpy import genfromtxt,array,linalg,zeros +from numpy import genfromtxt,array,linalg,zeros,mean,std,apply_along_axis """ This script shows how to use MiniSom on the Iris dataset. @@ -9,14 +9,14 @@ # reading the iris dataset in the csv format # (downloaded from http://aima.cs.berkeley.edu/data/iris.csv) -data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) -data = array([x/linalg.norm(x) for x in data]) # data normalization - +data = genfromtxt('iris.csv', delimiter=',',usecols=(0,1,2,3)) +data = apply_along_axis(lambda x: x/linalg.norm(x),1,data) # data normalization + ### Initialization and training ### som = MiniSom(7,7,4,sigma=1.0,learning_rate=0.5) -som.random_weights_init(data) +#som.random_weights_init(data) print("Training...") -som.train_random(data,500) # random training +som.train_random(data,100) # random training print("\n...ready!") ### Plotting the response for each pattern in the iris dataset ### From 5ace778df055f12df58e0aeb65d1e1a0e7d62f12 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 12 Sep 2013 17:42:16 +0200 Subject: [PATCH 025/352] Update Readme.md --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 6f7a02e..0b83991 100644 --- a/Readme.md +++ b/Readme.md @@ -65,11 +65,11 @@ For each winning neuron we have a marker. Each type of marker represents a class In examples/example_digits.py there is an example of how to use MiniSom for images clustering. The example uses the scitkits-learn wrapper to the handwritten digits dataset. Here is one of the result that MiniSom can achieve in digits recognition: -handwritten digitts recognition +handwritten digitts recognition The graph above represent each image with the handwritten digit it contains. The position corresponds to the position of the winning neuron for the image. Here we also have a version of this graphs that shows the original images: -handwritten digitts recognition +handwritten digitts recognition In examples/example_color.py you can find an example of how to use MiniSom for color quantization. Here is one of the possible results: From fb08e753c7db742300b15b740426465206a93c37 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Sun, 15 Sep 2013 09:39:55 +0200 Subject: [PATCH 026/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 0b83991..dd89e04 100644 --- a/Readme.md +++ b/Readme.md @@ -61,7 +61,7 @@ In examples/example_iris.py you can find an example that shows how to train Mini Iris example -For each winning neuron we have a marker. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). +For each observation we have a marker placed on the position of the winning neuron on the map. Each type of marker represents a class of the iris data. The average distance map of the weights is used as background (see the color bar on the right to associate the value). In examples/example_digits.py there is an example of how to use MiniSom for images clustering. The example uses the scitkits-learn wrapper to the handwritten digits dataset. Here is one of the result that MiniSom can achieve in digits recognition: From f54d6a071afee6ab91c774f38788f2224ec9f6fd Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Sun, 15 Sep 2013 09:51:23 +0200 Subject: [PATCH 027/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index dd89e04..13d6a84 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,7 @@ MiniSom Self Organizing Maps -------------------- -MiniSom is minimalistic Numpy based implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. +MiniSom is a minimalistic and Numpy based implementation of the Self Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able to convert complex, nonlinear statistical relationships between high-dimensional data items into simple geometric relationships on a low-dimensional display. Installation --------------------- From 4a2d8804dcfbc03ebc335462f3ab8fe328b10574 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Tue, 17 Sep 2013 14:36:59 +0200 Subject: [PATCH 028/352] added map_win method --- minisom.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index f11a0d2..db167d2 100644 --- a/minisom.py +++ b/minisom.py @@ -1,4 +1,5 @@ from numpy import sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros,arange,outer,meshgrid +from collections import defaultdict """ Minimalistic implementation of the Self Organizing Maps (SOM). @@ -7,7 +8,7 @@ """ class MiniSom: - def __init__(self,x,y,input_len,sigma=1,learning_rate=0.5): + def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -147,4 +148,20 @@ def quantization_error(self,data): error = 0 for x in data: error += linalg.norm(x-self.weights[self.winner(x)]) - return error/len(data) \ No newline at end of file + return error/len(data) + + def win_map(self,data): + """ + Returns a dictionary wm where wm[(i,j)] is a list with all the patterns + that have been mapped in the position i,j. + """ + winmap = defaultdict(list) + for x in data: + winmap[self.winner(x)].append(x) + return winmap + + +if __name__ == '__main__': + data = random.rand(100,3) + som = MiniSom(5,5,3) + som.train_random(data,50) From 64e345bc0c53e266af80b8c3248984ebf31ed8e0 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 9 Jan 2014 22:21:16 +0100 Subject: [PATCH 029/352] Update Readme.md --- Readme.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Readme.md b/Readme.md index 13d6a84..5e95292 100644 --- a/Readme.md +++ b/Readme.md @@ -77,6 +77,13 @@ In examples/example_color.py you can find an example of how to use MiniSom for c (the examples require matplotlib for the visualization of the results). +Dependencies +------------ + +MiniSom has the following dependencies: + +* Numpy + Compatibility notes --------------------- Minisom have been tested under python 2.7.3 and 3.2.3. From 4b89a3f59dcd590ac6e810bac5c8e75c8363a2ad Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Sat, 5 Apr 2014 17:18:03 +0100 Subject: [PATCH 030/352] added link to paper --- Readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Readme.md b/Readme.md index 5e95292..3706928 100644 --- a/Readme.md +++ b/Readme.md @@ -77,6 +77,14 @@ In examples/example_color.py you can find an example of how to use MiniSom for c (the examples require matplotlib for the visualization of the results). +Who uses Minisom? +------------ + + + Dependencies ------------ From 0d991ade81e883e552578397ed067fe5feeaf6d9 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 10 Jun 2014 14:46:09 +0100 Subject: [PATCH 031/352] unit tests added --- minisom.py | 60 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/minisom.py b/minisom.py index db167d2..463a6b2 100644 --- a/minisom.py +++ b/minisom.py @@ -64,7 +64,6 @@ def update(self,x,win,t): Updates the weights of the neurons. x - current pattern to learn win - position of the winning neuron for x (array or tuple). - eta - learning rate t - iteration index """ # eta(t) = eta(0) / (1 + t/T) @@ -151,17 +150,48 @@ def quantization_error(self,data): return error/len(data) def win_map(self,data): - """ - Returns a dictionary wm where wm[(i,j)] is a list with all the patterns - that have been mapped in the position i,j. - """ - winmap = defaultdict(list) - for x in data: - winmap[self.winner(x)].append(x) - return winmap - - -if __name__ == '__main__': - data = random.rand(100,3) - som = MiniSom(5,5,3) - som.train_random(data,50) + """ + Returns a dictionary wm where wm[(i,j)] is a list with all the patterns + that have been mapped in the position i,j. + """ + winmap = defaultdict(list) + for x in data: + winmap[self.winner(x)].append(x) + return winmap + +### unit tests +from numpy.testing import assert_almost_equal + +class TestMinisom: + def setup_method(self, method): + self.som = MiniSom(5,5,1) + for w in self.som.weights: # checking weights normalization + assert_almost_equal(1.0,linalg.norm(w)) + self.som.weights = zeros((5,5)) # fake weights + self.som.weights[2,3] = 5.0 + self.som.weights[1,1] = 2.0 + + def test_gaussian(self): + bell = self.som.gaussian((2,2),1) + assert bell.max() == 1.0 + assert bell.argmax() == 12 + + def test_win_map(self): + winners = self.som.win_map([5.0,2.0]) + assert winners[(2,3)][0] == 5.0 + assert winners[(1,1)][0] == 2.0 + + def test_activation_reponse(self): + response = self.som.activation_response([5.0,2.0]) + assert response[2,3] == 1 + assert response[1,1] == 1 + + def test_quantization_error(self): + self.som.quantization_error([5,2]) == 0.0 + self.som.quantization_error([4,1]) == 0.5 + + def test_quantization(self): + q = self.som.quantization(array([4,2])) + assert q[0] == 5.0 + assert q[1] == 2.0 + From bc8eab0c26154086a16f83cb053a5b9de84eb536 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 10 Jun 2014 15:07:59 +0100 Subject: [PATCH 032/352] added random seed option --- minisom.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index 463a6b2..0985c20 100644 --- a/minisom.py +++ b/minisom.py @@ -8,7 +8,7 @@ """ class MiniSom: - def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5): + def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -17,7 +17,10 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5): (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) + random_seed, random seed to use. """ + if random_seed: + random.seed(random_seed) self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization @@ -160,7 +163,7 @@ def win_map(self,data): return winmap ### unit tests -from numpy.testing import assert_almost_equal +from numpy.testing import assert_almost_equal, assert_array_almost_equal class TestMinisom: def setup_method(self, method): @@ -195,3 +198,20 @@ def test_quantization(self): assert q[0] == 5.0 assert q[1] == 2.0 + def test_random_seed(self): + som1 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + assert_array_almost_equal(som1.weights,som2.weights) # same initialization + data = random.rand(100,2) + som1 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + som1.train_random(data,10) + som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + som2.train_random(data,10) + assert_array_almost_equal(som1.weights,som2.weights) # same state after training + +if __name__ == '__main__': + som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=None) + data = random.rand(100,2) + som.train_batch(data,10) + print som.quantization_error([.5]) + From 5d4b71103209c640023ac77ead4fcecf56c7ccb9 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 10 Jun 2014 16:32:30 +0100 Subject: [PATCH 033/352] tests improved --- minisom.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/minisom.py b/minisom.py index 0985c20..df2c9da 100644 --- a/minisom.py +++ b/minisom.py @@ -13,7 +13,7 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): Initializes a Self Organizing Maps. x,y - dimensions of the SOM input_len - number of the elements of the vectors in input - sigma - spread of the neighborhood function (Gaussian) + sigma - spread of the neighborhood function (Gaussian), needs to be adequate to the dimensions of the map. (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) @@ -177,7 +177,7 @@ def setup_method(self, method): def test_gaussian(self): bell = self.som.gaussian((2,2),1) assert bell.max() == 1.0 - assert bell.argmax() == 12 + assert bell.argmax() == 12 # unravel(12) = (2,2) def test_win_map(self): winners = self.som.win_map([5.0,2.0]) @@ -188,6 +188,9 @@ def test_activation_reponse(self): response = self.som.activation_response([5.0,2.0]) assert response[2,3] == 1 assert response[1,1] == 1 + + def test_activate(self): + assert self.som.activate(5.0).argmin() == 13 # unravel(13) = (2,3) def test_quantization_error(self): self.som.quantization_error([5,2]) == 0.0 @@ -209,9 +212,3 @@ def test_random_seed(self): som2.train_random(data,10) assert_array_almost_equal(som1.weights,som2.weights) # same state after training -if __name__ == '__main__': - som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=None) - data = random.rand(100,2) - som.train_batch(data,10) - print som.quantization_error([.5]) - From fee9c7c42a3bbc4de5b7c88d886caa0f458f7dd2 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 19 Jun 2014 12:58:04 +0100 Subject: [PATCH 034/352] Documentation updated --- Readme.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index 3706928..889770f 100644 --- a/Readme.md +++ b/Readme.md @@ -11,6 +11,8 @@ MiniSom is a minimalistic and Numpy based implementation of the Self Organizing Installation --------------------- +Download MiniSom to the directory of your choice, enter it and run (may require root privileges): + python setup.py install How to use it @@ -34,13 +36,13 @@ In order to use MiniSom you need your data organized as a Numpy matrix where eac som.train_random(data,100) # trains the SOM with 100 iterations print "...ready!" -MiniSom implements two types of training. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. +MiniSom implements two types of trainings. The random training (implemented by the method `train_random`), where the model is trained picking random samples from your data, and the batch training (implemented by the method `train_batch`), where the samples are used in the order they are stored. A data driven initialization of the weights is also provided by the method `random_weights_init` which initializes the weights picking random samples from the data. ### Using the trained SOM -After the training you are able to +After the training you will be able to * Compute the coordinate assigned to an observation `x` on the map with the method `winner(x)`. * Compute the average distance map of the weights on the map with the method `distance_map()`. @@ -85,13 +87,6 @@ Who uses Minisom? Pages 192-193. -Dependencies ------------- - -MiniSom has the following dependencies: - -* Numpy - Compatibility notes --------------------- Minisom have been tested under python 2.7.3 and 3.2.3. From 59cd9232dcfd550462fee6094ed4f1c8341661f2 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 24 Jun 2014 10:29:14 +0100 Subject: [PATCH 035/352] added warning for incorrect sigma values --- minisom.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index df2c9da..44e1575 100644 --- a/minisom.py +++ b/minisom.py @@ -1,5 +1,6 @@ from numpy import sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros,arange,outer,meshgrid from collections import defaultdict +from warnings import warn """ Minimalistic implementation of the Self Organizing Maps (SOM). @@ -19,8 +20,10 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) random_seed, random seed to use. """ + if sigma >= x/2.0 or sigma >= y/2.0: + warn('Warning: sigma is too high for the dimension of the map.') if random_seed: - random.seed(random_seed) + random.seed(random_seed) self.learning_rate = learning_rate self.sigma = sigma self.weights = random.rand(x,y,input_len)*2-1 # random initialization @@ -211,4 +214,3 @@ def test_random_seed(self): som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) som2.train_random(data,10) assert_array_almost_equal(som1.weights,som2.weights) # same state after training - From 78fab5e74a07e1987410468548443284685c1a92 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Tue, 1 Jul 2014 13:34:02 +0100 Subject: [PATCH 036/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 889770f..3fab559 100644 --- a/Readme.md +++ b/Readme.md @@ -83,7 +83,7 @@ Who uses Minisom? ------------ From ea92e5e084d88b678feedc9f3ac612433661896c Mon Sep 17 00:00:00 2001 From: justglowing Date: Sat, 6 Sep 2014 10:49:01 +0100 Subject: [PATCH 037/352] RandomState object now used --- minisom.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/minisom.py b/minisom.py index 44e1575..8540d75 100644 --- a/minisom.py +++ b/minisom.py @@ -23,10 +23,12 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): if sigma >= x/2.0 or sigma >= y/2.0: warn('Warning: sigma is too high for the dimension of the map.') if random_seed: - random.seed(random_seed) + self.random_generator = random.RandomState(random_seed) + else: + self.random_generator = random.RandomState(random_seed) self.learning_rate = learning_rate self.sigma = sigma - self.weights = random.rand(x,y,input_len)*2-1 # random initialization + self.weights = self.random_generator.rand(x,y,input_len)*2-1 # random initialization self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization self.activation_map = zeros((x,y)) self.neigx = arange(x) @@ -97,7 +99,7 @@ def random_weights_init(self,data): """ Initializes the weights of the SOM picking random samples from data """ it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: - self.weights[it.multi_index] = data[int(random.rand()*len(data)-1)] + self.weights[it.multi_index] = data[int(self.random_generator.rand()*len(data)-1)] self.weights[it.multi_index] = self.weights[it.multi_index]/linalg.norm(self.weights[it.multi_index]) it.iternext() @@ -105,7 +107,7 @@ def train_random(self,data,num_iteration): """ Trains the SOM picking samples at random from data """ self._init_T(num_iteration) for iteration in range(num_iteration): - rand_i = int(round(random.rand()*len(data)-1)) # pick a random sample + rand_i = int(round(self.random_generator.rand()*len(data)-1)) # pick a random sample self.update(data[rand_i],self.winner(data[rand_i]),iteration) def train_batch(self,data,num_iteration): From db240b1dd20bde8aa021ce1bb586cd6e4c1084cf Mon Sep 17 00:00:00 2001 From: justglowing Date: Wed, 10 Sep 2014 09:26:50 +0100 Subject: [PATCH 038/352] new tests added --- minisom.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/minisom.py b/minisom.py index 8540d75..744260c 100644 --- a/minisom.py +++ b/minisom.py @@ -168,7 +168,7 @@ def win_map(self,data): return winmap ### unit tests -from numpy.testing import assert_almost_equal, assert_array_almost_equal +from numpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal class TestMinisom: def setup_method(self, method): @@ -182,7 +182,7 @@ def setup_method(self, method): def test_gaussian(self): bell = self.som.gaussian((2,2),1) assert bell.max() == 1.0 - assert bell.argmax() == 12 # unravel(12) = (2,2) + assert bell.argmax() == 12 # unravel(12) = (2,2) def test_win_map(self): winners = self.som.win_map([5.0,2.0]) @@ -195,7 +195,7 @@ def test_activation_reponse(self): assert response[1,1] == 1 def test_activate(self): - assert self.som.activate(5.0).argmin() == 13 # unravel(13) = (2,3) + assert self.som.activate(5.0).argmin() == 13.0 # unravel(13) = (2,3) def test_quantization_error(self): self.som.quantization_error([5,2]) == 0.0 @@ -216,3 +216,26 @@ def test_random_seed(self): som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) som2.train_random(data,10) assert_array_almost_equal(som1.weights,som2.weights) # same state after training + + def test_train_batch(self): + som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + data = array([[4,2],[3,1]]) + q1 = som.quantization_error(data) + som.train_batch(data,10) + assert q1 > som.quantization_error(data) + + def test_train_random(self): + som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + data = array([[4,2],[3,1]]) + q1 = som.quantization_error(data) + som.train_random(data,10) + assert q1 > som.quantization_error(data) + + def test_random_weights_init(self): + som = MiniSom(2,2,2,random_seed=1) + som.random_weights_init(array([[1.0,.0]])) + for w in som.weights: + assert_array_equal(w[0],array([1.0,.0])) + + + From c1d043531fdcf4c6d9026337296580f097b13718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B3mas=20=C3=81rni=20J=C3=B3nasson?= Date: Wed, 11 Feb 2015 14:51:00 -0800 Subject: [PATCH 039/352] Replace linalg.norm with faster method I've obtained up to 40% speedup on simple tests. --- minisom.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/minisom.py b/minisom.py index 744260c..c406429 100644 --- a/minisom.py +++ b/minisom.py @@ -1,13 +1,27 @@ -from numpy import sqrt,sqrt,array,unravel_index,nditer,linalg,random,subtract,power,exp,pi,zeros,arange,outer,meshgrid +from math import sqrt + +from numpy import (array, unravel_index, nditer, linalg, random, subtract, + power, exp, pi, zeros, arange, outer, meshgrid, dot) from collections import defaultdict from warnings import warn + """ Minimalistic implementation of the Self Organizing Maps (SOM). Giuseppe Vettigli 2013. """ + +def fast_norm(x): + """Returns a norm of a 1-D array/vector `x`. + + Turns out it's much faster than linalg.norm in case of 1-D arrays. + Measured with up to ~80% increase in speed. + """ + return sqrt(dot(x, x.conj())) + + class MiniSom: def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): """ @@ -40,7 +54,7 @@ def _activate(self,x): s = subtract(x,self.weights) # x - w it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: - self.activation_map[it.multi_index] = linalg.norm(s[it.multi_index]) # || x - w || + self.activation_map[it.multi_index] = fast_norm(s[it.multi_index]) # || x - w || it.iternext() def activate(self,x): @@ -84,7 +98,7 @@ def update(self,x,win,t): # eta * neighborhood_function * (x-w) self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) # normalization - self.weights[it.multi_index] = self.weights[it.multi_index] / linalg.norm(self.weights[it.multi_index]) + self.weights[it.multi_index] = self.weights[it.multi_index] / fast_norm(self.weights[it.multi_index]) it.iternext() def quantization(self,data): @@ -100,7 +114,7 @@ def random_weights_init(self,data): it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: self.weights[it.multi_index] = data[int(self.random_generator.rand()*len(data)-1)] - self.weights[it.multi_index] = self.weights[it.multi_index]/linalg.norm(self.weights[it.multi_index]) + self.weights[it.multi_index] = self.weights[it.multi_index]/fast_norm(self.weights[it.multi_index]) it.iternext() def train_random(self,data,num_iteration): @@ -132,7 +146,7 @@ def distance_map(self): for ii in range(it.multi_index[0]-1,it.multi_index[0]+2): for jj in range(it.multi_index[1]-1,it.multi_index[1]+2): if ii >= 0 and ii < self.weights.shape[0] and jj >= 0 and jj < self.weights.shape[1]: - um[it.multi_index] += linalg.norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) + um[it.multi_index] += fast_norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) it.iternext() um = um/um.max() return um @@ -154,7 +168,7 @@ def quantization_error(self,data): """ error = 0 for x in data: - error += linalg.norm(x-self.weights[self.winner(x)]) + error += fast_norm(x-self.weights[self.winner(x)]) return error/len(data) def win_map(self,data): From 82d18e02677a5b08d4ffe4342cdb4ddab594cfb0 Mon Sep 17 00:00:00 2001 From: justglowing Date: Fri, 13 Feb 2015 09:20:28 +0000 Subject: [PATCH 040/352] fast_norm test added --- minisom.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/minisom.py b/minisom.py index c406429..f222039 100644 --- a/minisom.py +++ b/minisom.py @@ -14,12 +14,11 @@ def fast_norm(x): - """Returns a norm of a 1-D array/vector `x`. + """Returns norm-2 of a 1-D numpy array. - Turns out it's much faster than linalg.norm in case of 1-D arrays. - Measured with up to ~80% increase in speed. + * faster than linalg.norm in case of 1-D arrays (numpy 1.9.2rc1). """ - return sqrt(dot(x, x.conj())) + return sqrt(dot(x, x.T)) class MiniSom: @@ -193,6 +192,9 @@ def setup_method(self, method): self.som.weights[2,3] = 5.0 self.som.weights[1,1] = 2.0 + def test_fast_norm(self): + assert fast_norm(array([1,3])) == sqrt(1+9) + def test_gaussian(self): bell = self.som.gaussian((2,2),1) assert bell.max() == 1.0 From c1e734222a05c8301bfac935469d9e22e3358adc Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Tue, 24 Feb 2015 08:50:29 +0000 Subject: [PATCH 041/352] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 3fab559..bd52b32 100644 --- a/Readme.md +++ b/Readme.md @@ -89,7 +89,7 @@ Pages 192-193. Compatibility notes --------------------- -Minisom have been tested under python 2.7.3 and 3.2.3. +Minisom has been tested under python 2.7.3 and 3.2.3. License --------------------- From 50d23cbb82f9618218f23492319ea8e0a3001099 Mon Sep 17 00:00:00 2001 From: justglowing Date: Mon, 15 Jun 2015 21:03:02 +0100 Subject: [PATCH 042/352] mpl warning fixed --- examples/example_digits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_digits.py b/examples/example_digits.py index 0e55c01..0f40131 100644 --- a/examples/example_digits.py +++ b/examples/example_digits.py @@ -1,5 +1,5 @@ # load the digits dataset from scikit-learn -# 901 samples, about 180 samples per class +# 901 samples, about 180 samples per class # the digits represented 0,1,2,3,4 from sklearn import datasets digits = datasets.load_digits(n_class=4) @@ -29,7 +29,7 @@ cnt = 0 for i in range(20): # images mosaic for j in range(20): - subplot(20,20,cnt,frameon=False, xticks=[], yticks=[]) + subplot(20,20,cnt+1,frameon=False, xticks=[], yticks=[]) if (i,j) in wmap: imshow(digits.images[wmap[(i,j)]], cmap='Greys', interpolation='nearest') else: From dda2f84f58ce5c1a14ba1a249a5c3eb75f65f78d Mon Sep 17 00:00:00 2001 From: justglowing Date: Thu, 16 Jul 2015 14:05:17 +0100 Subject: [PATCH 043/352] decay function param added --- minisom.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/minisom.py b/minisom.py index f222039..1526ed2 100644 --- a/minisom.py +++ b/minisom.py @@ -22,7 +22,7 @@ def fast_norm(x): class MiniSom: - def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): + def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,decay_function=None,random_seed=None): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -31,6 +31,8 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): (at the iteration t we have sigma(t) = sigma / (1 + t/T) where T is #num_iteration/2) learning_rate - initial learning rate (at the iteration t we have learning_rate(t) = learning_rate / (1 + t/T) where T is #num_iteration/2) + decay_function, function that reduces learning_rate and sigma at each iteration + default function: lambda x,current_iteration,max_iter: x/(1+current_iteration/max_iter) random_seed, random seed to use. """ if sigma >= x/2.0 or sigma >= y/2.0: @@ -39,6 +41,10 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,random_seed=None): self.random_generator = random.RandomState(random_seed) else: self.random_generator = random.RandomState(random_seed) + if decay_function: + self._decay_function = decay_function + else: + self._decay_function = lambda x, t, max_iter: x/(1+t/max_iter) self.learning_rate = learning_rate self.sigma = sigma self.weights = self.random_generator.rand(x,y,input_len)*2-1 # random initialization @@ -87,10 +93,8 @@ def update(self,x,win,t): win - position of the winning neuron for x (array or tuple). t - iteration index """ - # eta(t) = eta(0) / (1 + t/T) - # keeps the learning rate nearly constant for the first T iterations and then adjusts it - eta = self.learning_rate/(1+t/self.T) - sig = self.sigma/(1+t/self.T) # sigma and learning rate decrease with the same rule + eta = self._decay_function(self.learning_rate, t, self.T) + sig = self._decay_function(self.sigma, t, self.T) # sigma and learning rate decrease with the same rule g = self.neighborhood(win,sig)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: @@ -192,6 +196,9 @@ def setup_method(self, method): self.som.weights[2,3] = 5.0 self.som.weights[1,1] = 2.0 + def test_decay_function(self): + assert self.som._decay_function(1.,2.,3.) == 1./(1.+2./3.) + def test_fast_norm(self): assert fast_norm(array([1,3])) == sqrt(1+9) From 9fc1ff238a8851b8dd72f7b53b9c37acbac97ba1 Mon Sep 17 00:00:00 2001 From: justglowing Date: Thu, 16 Jul 2015 14:38:19 +0100 Subject: [PATCH 044/352] some restyle --- minisom.py | 136 ++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/minisom.py b/minisom.py index 1526ed2..2d8063e 100644 --- a/minisom.py +++ b/minisom.py @@ -22,7 +22,7 @@ def fast_norm(x): class MiniSom: - def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,decay_function=None,random_seed=None): + def __init__(self, x, y, input_len, sigma=1.0, learning_rate=0.5, decay_function=None, random_seed=None): """ Initializes a Self Organizing Maps. x,y - dimensions of the SOM @@ -54,39 +54,39 @@ def __init__(self,x,y,input_len,sigma=1.0,learning_rate=0.5,decay_function=None, self.neigy = arange(y) # used to evaluate the neighborhood function self.neighborhood = self.gaussian - def _activate(self,x): + def _activate(self, x): """ Updates matrix activation_map, in this matrix the element i,j is the response of the neuron i,j to x """ - s = subtract(x,self.weights) # x - w + s = subtract(x, self.weights) # x - w it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: - self.activation_map[it.multi_index] = fast_norm(s[it.multi_index]) # || x - w || + self.activation_map[it.multi_index] = fast_norm(s[it.multi_index]) # || x - w || it.iternext() - def activate(self,x): + def activate(self, x): """ Returns the activation map to x """ self._activate(x) return self.activation_map - def gaussian(self,c,sigma): + def gaussian(self, c, sigma): """ Returns a Gaussian centered in c """ d = 2*pi*sigma*sigma - ax = exp(-power(self.neigx-c[0],2)/d) - ay = exp(-power(self.neigy-c[1],2)/d) - return outer(ax,ay) # the external product gives a matrix + ax = exp(-power(self.neigx-c[0], 2)/d) + ay = exp(-power(self.neigy-c[1], 2)/d) + return outer(ax, ay) # the external product gives a matrix - def diff_gaussian(self,c,sigma): + def diff_gaussian(self, c, sigma): """ Mexican hat centered in c (unused) """ - xx,yy = meshgrid(self.neigx,self.neigy) - p = power(xx-c[0],2) + power(yy-c[1],2) + xx, yy = meshgrid(self.neigx, self.neigy) + p = power(xx-c[0], 2) + power(yy-c[1], 2) d = 2*pi*sigma*sigma - return exp(-(p)/d)*(1-2/d*p) + return exp(-p/d)*(1-2/d*p) - def winner(self,x): + def winner(self, x): """ Computes the coordinates of the winning neuron for the sample x """ self._activate(x) - return unravel_index(self.activation_map.argmin(),self.activation_map.shape) + return unravel_index(self.activation_map.argmin(), self.activation_map.shape) - def update(self,x,win,t): + def update(self, x, win, t): """ Updates the weights of the neurons. x - current pattern to learn @@ -95,7 +95,7 @@ def update(self,x,win,t): """ eta = self._decay_function(self.learning_rate, t, self.T) sig = self._decay_function(self.sigma, t, self.T) # sigma and learning rate decrease with the same rule - g = self.neighborhood(win,sig)*eta # improves the performances + g = self.neighborhood(win, sig)*eta # improves the performances it = nditer(g, flags=['multi_index']) while not it.finished: # eta * neighborhood_function * (x-w) @@ -104,15 +104,14 @@ def update(self,x,win,t): self.weights[it.multi_index] = self.weights[it.multi_index] / fast_norm(self.weights[it.multi_index]) it.iternext() - def quantization(self,data): + def quantization(self, data): """ Assigns a code book (weights vector of the winning neuron) to each sample in data. """ q = zeros(data.shape) - for i,x in enumerate(data): + for i, x in enumerate(data): q[i] = self.weights[self.winner(x)] return q - - def random_weights_init(self,data): + def random_weights_init(self, data): """ Initializes the weights of the SOM picking random samples from data """ it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: @@ -120,51 +119,51 @@ def random_weights_init(self,data): self.weights[it.multi_index] = self.weights[it.multi_index]/fast_norm(self.weights[it.multi_index]) it.iternext() - def train_random(self,data,num_iteration): + def train_random(self, data, num_iteration): """ Trains the SOM picking samples at random from data """ self._init_T(num_iteration) for iteration in range(num_iteration): rand_i = int(round(self.random_generator.rand()*len(data)-1)) # pick a random sample - self.update(data[rand_i],self.winner(data[rand_i]),iteration) + self.update(data[rand_i], self.winner(data[rand_i]), iteration) - def train_batch(self,data,num_iteration): + def train_batch(self, data, num_iteration): """ Trains using all the vectors in data sequentially """ self._init_T(len(data)*num_iteration) iteration = 0 while iteration < num_iteration: idx = iteration % (len(data)-1) - self.update(data[idx],self.winner(data[idx]),iteration) + self.update(data[idx], self.winner(data[idx]), iteration) iteration += 1 - def _init_T(self,num_iteration): + def _init_T(self, num_iteration): """ Initializes the parameter T needed to adjust the learning rate """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations def distance_map(self): """ Returns the average distance map of the weights. (Each mean is normalized in order to sum up to 1) """ - um = zeros((self.weights.shape[0],self.weights.shape[1])) + um = zeros((self.weights.shape[0], self.weights.shape[1])) it = nditer(um, flags=['multi_index']) while not it.finished: - for ii in range(it.multi_index[0]-1,it.multi_index[0]+2): - for jj in range(it.multi_index[1]-1,it.multi_index[1]+2): + for ii in range(it.multi_index[0]-1, it.multi_index[0]+2): + for jj in range(it.multi_index[1]-1, it.multi_index[1]+2): if ii >= 0 and ii < self.weights.shape[0] and jj >= 0 and jj < self.weights.shape[1]: - um[it.multi_index] += fast_norm(self.weights[ii,jj,:]-self.weights[it.multi_index]) + um[it.multi_index] += fast_norm(self.weights[ii, jj, :]-self.weights[it.multi_index]) it.iternext() um = um/um.max() return um - def activation_response(self,data): + def activation_response(self, data): """ Returns a matrix where the element i,j is the number of times that the neuron i,j have been winner. """ - a = zeros((self.weights.shape[0],self.weights.shape[1])) + a = zeros((self.weights.shape[0], self.weights.shape[1])) for x in data: a[self.winner(x)] += 1 return a - def quantization_error(self,data): + def quantization_error(self, data): """ Returns the quantization error computed as the average distance between each input sample and its best matching unit. @@ -174,7 +173,7 @@ def quantization_error(self,data): error += fast_norm(x-self.weights[self.winner(x)]) return error/len(data) - def win_map(self,data): + def win_map(self, data): """ Returns a dictionary wm where wm[(i,j)] is a list with all the patterns that have been mapped in the position i,j. @@ -187,78 +186,79 @@ def win_map(self,data): ### unit tests from numpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal + class TestMinisom: def setup_method(self, method): - self.som = MiniSom(5,5,1) - for w in self.som.weights: # checking weights normalization - assert_almost_equal(1.0,linalg.norm(w)) - self.som.weights = zeros((5,5)) # fake weights - self.som.weights[2,3] = 5.0 - self.som.weights[1,1] = 2.0 + self.som = MiniSom(5, 5, 1) + for w in self.som.weights: # checking weights normalization + assert_almost_equal(1.0, linalg.norm(w)) + self.som.weights = zeros((5, 5)) # fake weights + self.som.weights[2, 3] = 5.0 + self.som.weights[1, 1] = 2.0 def test_decay_function(self): - assert self.som._decay_function(1.,2.,3.) == 1./(1.+2./3.) + assert self.som._decay_function(1., 2., 3.) == 1./(1.+2./3.) def test_fast_norm(self): - assert fast_norm(array([1,3])) == sqrt(1+9) + assert fast_norm(array([1, 3])) == sqrt(1+9) def test_gaussian(self): - bell = self.som.gaussian((2,2),1) + bell = self.som.gaussian((2, 2), 1) assert bell.max() == 1.0 assert bell.argmax() == 12 # unravel(12) = (2,2) def test_win_map(self): - winners = self.som.win_map([5.0,2.0]) - assert winners[(2,3)][0] == 5.0 - assert winners[(1,1)][0] == 2.0 + winners = self.som.win_map([5.0, 2.0]) + assert winners[(2, 3)][0] == 5.0 + assert winners[(1, 1)][0] == 2.0 def test_activation_reponse(self): - response = self.som.activation_response([5.0,2.0]) - assert response[2,3] == 1 - assert response[1,1] == 1 + response = self.som.activation_response([5.0, 2.0]) + assert response[2, 3] == 1 + assert response[1, 1] == 1 def test_activate(self): assert self.som.activate(5.0).argmin() == 13.0 # unravel(13) = (2,3) def test_quantization_error(self): - self.som.quantization_error([5,2]) == 0.0 - self.som.quantization_error([4,1]) == 0.5 + self.som.quantization_error([5, 2]) == 0.0 + self.som.quantization_error([4, 1]) == 0.5 def test_quantization(self): - q = self.som.quantization(array([4,2])) + q = self.som.quantization(array([4, 2])) assert q[0] == 5.0 assert q[1] == 2.0 def test_random_seed(self): - som1 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) - som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) - assert_array_almost_equal(som1.weights,som2.weights) # same initialization + som1 = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) + som2 = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) + assert_array_almost_equal(som1.weights, som2.weights) # same initialization data = random.rand(100,2) - som1 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + som1 = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) som1.train_random(data,10) - som2 = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) + som2 = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) som2.train_random(data,10) - assert_array_almost_equal(som1.weights,som2.weights) # same state after training + assert_array_almost_equal(som1.weights,som2.weights) # same state after training def test_train_batch(self): - som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) - data = array([[4,2],[3,1]]) + som = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) + data = array([[4, 2], [3, 1]]) q1 = som.quantization_error(data) - som.train_batch(data,10) + som.train_batch(data, 10) assert q1 > som.quantization_error(data) def test_train_random(self): - som = MiniSom(5,5,2,sigma=1.0,learning_rate=0.5,random_seed=1) - data = array([[4,2],[3,1]]) + som = MiniSom(5, 5, 2, sigma=1.0, learning_rate=0.5, random_seed=1) + data = array([[4, 2], [3, 1]]) q1 = som.quantization_error(data) - som.train_random(data,10) + som.train_random(data, 10) assert q1 > som.quantization_error(data) def test_random_weights_init(self): - som = MiniSom(2,2,2,random_seed=1) - som.random_weights_init(array([[1.0,.0]])) + som = MiniSom(2, 2, 2, random_seed=1) + som.random_weights_init(array([[1.0, .0]])) for w in som.weights: - assert_array_equal(w[0],array([1.0,.0])) + assert_array_equal(w[0], array([1.0, .0])) From 95686bae4e0e17e929ee123cf0a2410bdfecfdc5 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Mon, 16 Nov 2015 10:54:58 +0000 Subject: [PATCH 045/352] Update minisom.py --- minisom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index 2d8063e..e6309b3 100644 --- a/minisom.py +++ b/minisom.py @@ -140,8 +140,9 @@ def _init_T(self, num_iteration): self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations def distance_map(self): - """ Returns the average distance map of the weights. - (Each mean is normalized in order to sum up to 1) """ + """ Returns the distance map of the weights. + Each cell is the normalised sum of the distances between a neuron and its neighbours. + """ um = zeros((self.weights.shape[0], self.weights.shape[1])) it = nditer(um, flags=['multi_index']) while not it.finished: From 7dc768c91804e941d03950c000ee4b8fff539547 Mon Sep 17 00:00:00 2001 From: Phan Hai Phong Date: Thu, 21 Jan 2016 18:07:16 +0700 Subject: [PATCH 046/352] Use randint() instead of rand(); remove whitespaces --- minisom.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/minisom.py b/minisom.py index e6309b3..63c14da 100644 --- a/minisom.py +++ b/minisom.py @@ -99,7 +99,7 @@ def update(self, x, win, t): it = nditer(g, flags=['multi_index']) while not it.finished: # eta * neighborhood_function * (x-w) - self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) + self.weights[it.multi_index] += g[it.multi_index]*(x-self.weights[it.multi_index]) # normalization self.weights[it.multi_index] = self.weights[it.multi_index] / fast_norm(self.weights[it.multi_index]) it.iternext() @@ -115,15 +115,15 @@ def random_weights_init(self, data): """ Initializes the weights of the SOM picking random samples from data """ it = nditer(self.activation_map, flags=['multi_index']) while not it.finished: - self.weights[it.multi_index] = data[int(self.random_generator.rand()*len(data)-1)] + self.weights[it.multi_index] = data[self.random_generator.randint(len(data))] self.weights[it.multi_index] = self.weights[it.multi_index]/fast_norm(self.weights[it.multi_index]) it.iternext() def train_random(self, data, num_iteration): """ Trains the SOM picking samples at random from data """ - self._init_T(num_iteration) + self._init_T(num_iteration) for iteration in range(num_iteration): - rand_i = int(round(self.random_generator.rand()*len(data)-1)) # pick a random sample + rand_i = self.random_generator.randint(len(data)) # pick a random sample self.update(data[rand_i], self.winner(data[rand_i]), iteration) def train_batch(self, data, num_iteration): @@ -155,7 +155,7 @@ def distance_map(self): return um def activation_response(self, data): - """ + """ Returns a matrix where the element i,j is the number of times that the neuron i,j have been winner. """ @@ -165,9 +165,9 @@ def activation_response(self, data): return a def quantization_error(self, data): - """ + """ Returns the quantization error computed as the average distance between - each input sample and its best matching unit. + each input sample and its best matching unit. """ error = 0 for x in data: @@ -220,7 +220,7 @@ def test_activation_reponse(self): def test_activate(self): assert self.som.activate(5.0).argmin() == 13.0 # unravel(13) = (2,3) - + def test_quantization_error(self): self.som.quantization_error([5, 2]) == 0.0 self.som.quantization_error([4, 1]) == 0.5 From be15c96529af56d43dbb4d9ec29a216d66229baf Mon Sep 17 00:00:00 2001 From: Phan Hai Phong Date: Wed, 27 Jan 2016 18:24:50 +0700 Subject: [PATCH 047/352] Fix the drawing order in the digits example image --- examples/example_digits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_digits.py b/examples/example_digits.py index 0f40131..19f5a6b 100644 --- a/examples/example_digits.py +++ b/examples/example_digits.py @@ -27,8 +27,8 @@ figure(2,facecolor='white') cnt = 0 -for i in range(20): # images mosaic - for j in range(20): +for j in reversed(range(20)): # images mosaic + for i in range(20): subplot(20,20,cnt+1,frameon=False, xticks=[], yticks=[]) if (i,j) in wmap: imshow(digits.images[wmap[(i,j)]], cmap='Greys', interpolation='nearest') From e113e0778098a534b91ae4ab45930c9cb14a45f5 Mon Sep 17 00:00:00 2001 From: Phan Hai Phong Date: Wed, 27 Jan 2016 19:09:00 +0700 Subject: [PATCH 048/352] Fix comment about learning rate --- minisom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minisom.py b/minisom.py index 63c14da..1d24cd6 100644 --- a/minisom.py +++ b/minisom.py @@ -137,7 +137,7 @@ def train_batch(self, data, num_iteration): def _init_T(self, num_iteration): """ Initializes the parameter T needed to adjust the learning rate """ - self.T = num_iteration/2 # keeps the learning rate nearly constant for the first half of the iterations + self.T = num_iteration/2 # keeps the learning rate nearly constant for the last half of the iterations def distance_map(self): """ Returns the distance map of the weights. From 2198e9406cd9bb8a60223a892cc3d29e96a9d464 Mon Sep 17 00:00:00 2001 From: justglowing Date: Wed, 27 Jan 2016 13:39:52 +0000 Subject: [PATCH 049/352] initial normalization changed --- minisom.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/minisom.py b/minisom.py index 1d24cd6..837f49a 100644 --- a/minisom.py +++ b/minisom.py @@ -8,8 +8,6 @@ """ Minimalistic implementation of the Self Organizing Maps (SOM). - - Giuseppe Vettigli 2013. """ @@ -48,7 +46,9 @@ def __init__(self, x, y, input_len, sigma=1.0, learning_rate=0.5, decay_function self.learning_rate = learning_rate self.sigma = sigma self.weights = self.random_generator.rand(x,y,input_len)*2-1 # random initialization - self.weights = array([v/linalg.norm(v) for v in self.weights]) # normalization + for i in range(x): + for j in range(y): + self.weights[i,j] = self.weights[i,j] / fast_norm(self.weights[i,j]) # normalization self.activation_map = zeros((x,y)) self.neigx = arange(x) self.neigy = arange(y) # used to evaluate the neighborhood function From 78db3494e21d1b335df2e1b1f5141366ee84fc97 Mon Sep 17 00:00:00 2001 From: justglowing Date: Wed, 27 Jan 2016 13:47:13 +0000 Subject: [PATCH 050/352] test about normalization passes --- minisom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index 837f49a..ca7bdc3 100644 --- a/minisom.py +++ b/minisom.py @@ -191,8 +191,8 @@ def win_map(self, data): class TestMinisom: def setup_method(self, method): self.som = MiniSom(5, 5, 1) - for w in self.som.weights: # checking weights normalization - assert_almost_equal(1.0, linalg.norm(w)) + #for w in self.som.weights: # checking weights normalization + # assert_almost_equal(1.0, linalg.norm(w)) self.som.weights = zeros((5, 5)) # fake weights self.som.weights[2, 3] = 5.0 self.som.weights[1, 1] = 2.0 From bd391d40f4d3991bd8f41e546869d74057cc0127 Mon Sep 17 00:00:00 2001 From: justglowing Date: Wed, 27 Jan 2016 13:48:41 +0000 Subject: [PATCH 051/352] amend amend amend... --- minisom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/minisom.py b/minisom.py index ca7bdc3..2c5741b 100644 --- a/minisom.py +++ b/minisom.py @@ -191,8 +191,9 @@ def win_map(self, data): class TestMinisom: def setup_method(self, method): self.som = MiniSom(5, 5, 1) - #for w in self.som.weights: # checking weights normalization - # assert_almost_equal(1.0, linalg.norm(w)) + for i in range(5): + for j in range(5): + assert_almost_equal(1.0, linalg.norm(self.som.weights[i,j])) # checking weights normalization self.som.weights = zeros((5, 5)) # fake weights self.som.weights[2, 3] = 5.0 self.som.weights[1, 1] = 2.0 From 68dd20415f285cbc10448803cbca3fa43349a908 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Fri, 18 Mar 2016 20:54:45 +0000 Subject: [PATCH 052/352] new user citation --- Readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.md b/Readme.md index bd52b32..4bedf25 100644 --- a/Readme.md +++ b/Readme.md @@ -83,6 +83,9 @@ Who uses Minisom? ------------ From 62444aae2c5c3236f80e64c293d92fcc56add414 Mon Sep 17 00:00:00 2001 From: Matt Poegel Date: Mon, 13 Jun 2016 08:37:25 -0700 Subject: [PATCH 053/352] made MiniSom inherit from object for python 2 --- minisom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minisom.py b/minisom.py index 2c5741b..8b37329 100644 --- a/minisom.py +++ b/minisom.py @@ -19,7 +19,7 @@ def fast_norm(x): return sqrt(dot(x, x.T)) -class MiniSom: +class MiniSom(object): def __init__(self, x, y, input_len, sigma=1.0, learning_rate=0.5, decay_function=None, random_seed=None): """ Initializes a Self Organizing Maps. From 73494250429b48eeee45b38b1a3e0ba2ff013506 Mon Sep 17 00:00:00 2001 From: justglowing Date: Wed, 5 Oct 2016 09:03:34 +0100 Subject: [PATCH 054/352] setup material --- README.rst | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 2 + 2 files changed, 176 insertions(+) create mode 100644 README.rst create mode 100644 setup.cfg diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..63c2967 --- /dev/null +++ b/README.rst @@ -0,0 +1,174 @@ +MiniSom +======= + +.. figure:: http://3.bp.blogspot.com/-TjLGnec3uko/Ud8LbHTpO1I/AAAAAAAAAqk/nfJneFOZrK8/s1600/logo.png + :alt: MiniSom + + MiniSom + +Self Organizing Maps +-------------------- + +MiniSom is a minimalistic and Numpy based implementation of the Self +Organizing Maps (SOM). SOM is a type of Artificial Neural Networks able +to convert complex, nonlinear statistical relationships between +high-dimensional data items into simple geometric relationships on a +low-dimensional display. + +Installation +------------ + +Download MiniSom to the directory of your choice, enter it and run (may +require root privileges): + +:: + + python setup.py install + +How to use it +------------- + +In order to use MiniSom you need your data organized as a Numpy matrix +where each row corresponds to an observation or an as list of lists like +the following: + +:: + + data = [[ 5.1 3.5 1.4 0.2], + [ 4.9 3. 1.4 0.2], + [ 4.7 3.2 1.3 0.2], # <-- single observation + [ 4.6 3.1 1.5 0.2], + [ 5. 3.6 1.4 0.2], + [ 4.1 3.3 1.4 0.2], + [ 4.2 3.2 1.2 0.2]] + +Then you can run MiniSom just as follows: + +:: + + from minisom import MiniSom + som = MiniSom(6,6,4,sigma=0.3,learning_rate=0.5) # initialization of 6x6 SOM + print "Training..." + som.train_random(data,100) # trains the SOM with 100 iterations + print "...ready!" + +MiniSom implements two types of trainings. The random training +(implemented by the method ``train_random``), where the model is trained +picking random samples from your data, and the batch training +(implemented by the method ``train_batch``), where the samples are used +in the order they are stored. + +A data driven initialization of the weights is also provided by the +method ``random_weights_init`` which initializes the weights picking +random samples from the data. + +Using the trained SOM +~~~~~~~~~~~~~~~~~~~~~ + +After the training you will be able to + +- Compute the coordinate assigned to an observation ``x`` on the map + with the method ``winner(x)``. +- Compute the average distance map of the weights on the map with the + method ``distance_map()``. +- Compute the number of times that each neuron have been considered + winner for the observations of a new data set with the method + ``activation_response(data)``. +- Compute the quantization error with the method + ``quantization_error(data)``. + +Vector quantization +^^^^^^^^^^^^^^^^^^^ + +The data can be quantized by assigning a code book (weights vector of +the winning neuron) to each sample in data. This kind of vector +quantization is implemented by the method ``quantization`` that can be +called as follows: + +:: + + qnt = som.quantization(data) + +In this example we have that ``qnt[i]`` is the quantized version of +``data[i]``. + +Examples +-------- + +In examples/example\_iris.py you can find an example that shows how to +train MiniSom and visualize the result using the Iris flower dataset. +Here is the result of the script: + +For each observation we have a marker placed on the position of the +winning neuron on the map. Each type of marker represents a class of the +iris data. The average distance map of the weights is used as background +(see the color bar on the right to associate the value). + +In examples/example\_digits.py there is an example of how to use MiniSom +for images clustering. The example uses the scitkits-learn wrapper to +the handwritten digits dataset. Here is one of the result that MiniSom +can achieve in digits recognition: + +The graph above represent each image with the handwritten digit it +contains. The position corresponds to the position of the winning neuron +for the image. Here we also have a version of this graphs that shows the +original images: + +In examples/example\_color.py you can find an example of how to use +MiniSom for color quantization. Here is one of the possible results: + +(the examples require matplotlib for the visualization of the results). + +Who uses Minisom? +----------------- + +.. raw:: html + +
    + +.. raw:: html + +
  • + +Makiyama, Vitor Hirota, M. Jordan Raddick, and Rafael DC Santos. Text +Mining Applied to SQL Queries: A Case Study for the SDSS SkyServer. 2nd +Annual International Symposium on Information Management and Big Data. +2015. + +.. raw:: html + +
  • + +.. raw:: html + +
  • + +Ivana Kajić, Guido Schillaci, Saša Bodiroža, Verena V. Hafner, Learning +hand-eye coordination for a humanoid robot using SOMs. Proceedings of +the 2014 ACM/IEEE international conference on Human-robot interaction +Pages 192-193. + +.. raw:: html + +
  • + +.. raw:: html + +
+ +Compatibility notes +------------------- + +Minisom has been tested under python 2.7.3 and 3.2.3. + +License +------- + +MiniSom by Giuseppe Vettigli is licensed under the Creative Commons +Attribution 3.0 Unported License. To view a copy of this license, visit +http://creativecommons.org/licenses/by/3.0/. + +.. figure:: http://i.creativecommons.org/l/by/3.0/88x31.png + :alt: Creative Commons Attribution 3.0 Unported License + + License diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5aef279 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.rst From 9ff4abbbd99a64cef402d1adc0ca8a6224ea7e3f Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Thu, 13 Oct 2016 13:10:20 +0100 Subject: [PATCH 055/352] new citation --- Readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.md b/Readme.md index 4bedf25..3d4fd46 100644 --- a/Readme.md +++ b/Readme.md @@ -84,6 +84,9 @@ Who uses Minisom? From 8d575643530331f31224c46d88860d7d14fd3aa7 Mon Sep 17 00:00:00 2001 From: JustGlowing Date: Sat, 5 Nov 2016 11:05:45 +0000 Subject: [PATCH 057/352] Update Readme.md --- Readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.md b/Readme.md index beade6e..913cf36 100644 --- a/Readme.md +++ b/Readme.md @@ -84,6 +84,9 @@ Who uses Minisom?