# Code from Chapter 14 of Machine Learning: An Algorithmic Perspective (2nd Edition) # by Stephen Marsland (http://stephenmonika.net) # You are free to use, change, or redistribute the code in any way you wish for # non-commercial purposes, but please maintain the name of the original author. # This code comes with no warranty of any kind. # Stephen Marsland, 2008, 2014 import numpy as np import pca class som: """A Basic 2D Self-Organising Map The map connections can be initialised randomly or with PCA""" def __init__(self,x,y,inputs,eta_b=0.3,eta_n=0.1,nSize=0.5,alpha=1,usePCA=1,useBCs=0,eta_bfinal=0.03,eta_nfinal=0.01,nSizefinal=0.05): self.nData = np.shape(inputs)[0] self.nDim = np.shape(inputs)[1] self.mapDim = 2 self.x = x self.y = y self.eta_b = eta_b self.eta_bfinal = eta_bfinal self.eta_n = eta_n self.eta_nfinal = eta_nfinal self.nSize = nSize self.nSizefinal = nSizefinal self.alpha = alpha self.map = np.mgrid[0:1:np.complex(0,x),0:1:np.complex(0,y)] self.map = np.reshape(self.map,(2,x*y)) if usePCA: dummy1,dummy2,evals,evecs = pca.pca(inputs,2) self.weights = np.zeros((self.nDim,x*y)) for i in range(x*y): for j in range(self.mapDim): self.weights[:,i] += (self.map[j,i]-0.5)*2*evecs[:,j] else: self.weights = (np.random.rand(self.nDim,x*y)-0.5)*2 self.mapDist = np.zeros((self.x*self.y,self.x*self.y)) if useBCs: for i in range(self.x*self.y): for j in range(i+1,self.x*self.y): xdist = np.min((self.map[0,i]-self.map[0,j])**2,(self.map[0,i]+1+1./self.x-self.map[0,j])**2,(self.map[0,i]-1-1./self.x-self.map[0,j])**2,(self.map[0,i]-self.map[0,j]+1+1./self.x)**2,(self.map[0,i]-self.map[0,j]-1-1./self.x)**2) ydist = np.min((self.map[1,i]-self.map[1,j])**2,(self.map[1,i]+1+1./self.y-self.map[1,j])**2,(self.map[1,i]-1-1./self.y-self.map[1,j])**2,(self.map[1,i]-self.map[1,j]+1+1./self.y)**2,(self.map[1,i]-self.map[1,j]-1-1./self.y)**2) self.mapDist[i,j] = np.sqrt(xdist+ydist) self.mapDist[j,i] = self.mapDist[i,j] else: for i in range(self.x*self.y): for j in range(i+1,self.x*self.y): self.mapDist[i,j] = np.sqrt((self.map[0,i] - self.map[0,j])**2 + (self.map[1,i] - self.map[1,j])**2) self.mapDist[j,i] = self.mapDist[i,j] def somtrain(self,inputs,nIterations): self.eta_binit = self.eta_b self.eta_ninit = self.eta_n self.nSizeinit = self.nSize for iterations in range(nIterations): for i in range(self.nData): #print inputs[i,:] best,activation = self.somfwd(inputs[i,:]) # Update the weights of the best match self.weights[:,best] += self.eta_b * (inputs[i,:] - self.weights[:,best]) #print self.weights # Find the neighbours and update their weights neighbours = np.where(self.mapDist[best,:]<=self.nSize,1,0) neighbours[best] = 0 #print neighbours self.weights += self.eta_n * neighbours*np.transpose((inputs[i,:] - np.transpose(self.weights))) #print self.weights # Modify learning rates self.eta_b = self.eta_binit*np.power(self.eta_bfinal/self.eta_binit,float(iterations)/nIterations) self.eta_n = self.eta_ninit*np.power(self.eta_nfinal/self.eta_ninit,float(iterations)/nIterations) # Modify neighbourhood size self.nSize = self.nSizeinit*np.power(self.nSizefinal/self.nSizeinit,float(iterations)/nIterations) def somfwd(self,inputs): activations = np.sum((np.transpose(np.tile(inputs,(self.x*self.y,1)))-self.weights)**2,axis=0) best = np.argmin(activations) return best,activations