【代码阅读记录】Spiking-Neural-Network---Genre-Recognizer(脉冲神经网络的风格识别器)(1)

前言:

{

    之前从工作中了解到了脉冲神经网络(Spiking Neural Network,SNN)。SNN在1952年被首次提出,被誉为第三代神经网络[1]。不过这次不打算再读论文了,我想直接找段代码[2]读读看。

    此篇记录中的代码和图全都来自[2]。

}

正文:

{

    代码比较少,一共不到700行,只分成了两个.py文件:FinalProject.py和FinalProjectNeuron.py。FinalProject.py应该是人口文件。

    GenreClassifier是分类器的类。根据代码1,此分类器的网络结构很简单,有1个输入层,3个隐含层和1个输出层,具体是138-10-20-10-1。可以看到输出层的权值被单独地初始化了。

#代码1
def __init__(self):
	self.timeStep = 2
	self.learningRate = 0.001
	self.spikingThreshold = 0.8			#Found through testing my specific neurons
	self.inputLayerSize = 138
	self.numSeconds = 2005						#Number of input neurons/pixels
	self.timeThreshold = 10  				#Time to simulate neuron for

	self.classifications = 2
	self.hiddenLayerNum = 3
	self.neuronPerLayer = [10, 20, 10]

	self.dataList = []
	self.isFirst = 0

	self.inputLayer = []
	for i in range(self.inputLayerSize):
		self.inputLayer.append(Neuron(self.timeThreshold,0))

	self.middleLayer = []
	currNumInputs = 138
	for i in range(self.hiddenLayerNum):
		currLayer = []
		for j in range(self.neuronPerLayer[i]):
			currLayer.append(Neuron(self.timeThreshold, currNumInputs))
		self.middleLayer.append(currLayer)
		currNumInputs = self.neuronPerLayer[i]

	self.outputLayer = Neuron(self.timeThreshold, 0)	

	weights = []
	for i in range(math.floor(currNumInputs/2)):
		self.outputLayer.weights.append(math.ceil(uniform(0,1000))/1000)
	for i in range(math.floor(currNumInputs/2), currNumInputs):
                self.outputLayer.weights.append(math.ceil(uniform(0,1000))/1000)

    作者还给出了结构图,见图1。

图1(这个水印怎么去掉?)

    Neuron的构造函数(C++中叫构造函数,一时间忘了这个在Python里叫什么)如代码2。

#代码2
def __init__(self, durationForSimulation, numInputs):
	self.T = durationForSimulation #Time to simulate, in ms
	self.dT = 0.1 #the dT time step in dV/dT
	self.VArray = zeros(int((self.T/self.dT) + 1)) #Array of membrane potentials, for plotting later
	self.Vt = 1 #Threshold, in V
	self.Vr = 0 #Reset potential, in mV
	self.initialV = 0 #Initial Membrane Potential = Formula is change in mV
	self.R = 1 #Membrane resistance, in kOhms
	self.C = 10 #Capacitance in uF
	self.tauM = self.R*self.C #Membrane time constant, in miliseconds
	self.firingRate = 10

	self.currentChangeInPotential = float(10.0) #Change in current membrane potential - Used in array

	self.numFired = 0
	self.notFired = 0
	self.fired = 0
	self.spikeRateForData = []
	self.totalSpikingRate = 0

	self.classificationRate = 0
	self.classificationActivity = 0

	self.weights = [];
	for i in range(numInputs):
            randomWeight = math.ceil(uniform(0,2000)-1000)/1000
            self.weights.append(randomWeight)

    可以看出,此分类器是全连接结构。但是作者对最后一层与前一层之间的权值单独进行了定义,之后应该会涉及到原因。另外还定义了电阻和电容等参数,比之前的神经网络要真实。

    Neuron的反馈通过代码3得出。

#代码3
def runNeuron(self, inputCurrent):
	self.counter = 0.0
	I = inputCurrent #input current, in Amps - Given by parameter, plus minimum threshold to actually fire

	spikeSum = 0.
	self.VArray = zeros(int((self.T/self.dT) + 1)) #Array of membrane potentials, for plotting later
	self.currentChangeInPotential = 0

	for i in range(1, len(self.VArray)) :
		self.currentChangeInPotential = (-1*self.VArray[i-1] + self.R*I)
		self.VArray[i] = self.VArray[i-1] + self.currentChangeInPotential/self.tauM*self.dT
		if(self.VArray[i] >= self.Vt):
			self.VArray[i] = self.Vr
			spikeSum += 1

return (math.ceil((spikeSum/self.T)*1000))/1000

    可以看到,每个Neuron都有一个设定长度的VArray,其模拟一段时间(durationForSimulation)内此Neuron的状态。当计算Neuron的输出时,遍历VArray,根据VArray中的上一个元素(电压)、当前输入电流和Neuron的电阻更新VArray中当前的元素,其中增加的电压和上一个电压成反比,和输入电流成正比;并且在遍历中如果增加后的电压高于一个阈值,则产生一个脉冲(spike);最后输出一个和脉冲数成正比的值。

    我的理解是,每更新一次相当于过滤durationForSimulation,Neuron的外环境对应地更新一次,但内环境的初始状态会重置,而且之后会更新很多次。

    接下来是主要内容:训练。训练函数太大,比较复杂,而且不止一个,我就不在这放出来了。train函数负责更新隐含层的权值,其步骤大致如下:

  1. 陆续输入输入列表中的初始输入数据到输入层;
  2. 计算输入层每个Neuron每次输出有没有超过一个阈值spikingThreshold,并且如果超过spikingThreshold的情况过半,则设其fired变量为1(当然也记录了每次的输出);
  3. 计算随后的隐含层的每个Neuron(输入数据变成了输入层的输出乘以权值)的输出currentSpikeRate ,其中当初始输入数据的第139维数据为0时(网络输入的大小为138,第139维是多出来的一个维度),本次的输入会*0.7;
  4. 如果currentSpikeRate 输入落在了[spikingThreshold-0.1, spikingThreshold),或者某权值对应的输入层Neuron的fired不为1,则跳过此权值的更新,否则计算此权值的更新量deltaW = (学习率*(1-此权值))/时间步,其中时间步是上述代码1中的self.timeStep;
  5. 如果currentSpikeRate 大于等于spikingThreshold且此权值和deltaW的和落在(-1, 1],则增加deltaW到此权值,但是如果currentSpikeRate小于等于spikingThreshold-0.1且此权值和deltaW的和大于等于-1,则从此权值减去deltaW,并且对于剩下的情况,同样跳过此权值的更新;
  6. 对所有隐含层的其他权值也进行上述操作。

    看这段代码的时候我发现了一个问题,代码4中的两个elif永远执行不到,可能这两个elif原本是无条件的。

#代码4
if(currWeight+deltaW <= 1 and currWeight+deltaW>-1):
    neuron.weights[j] += deltaW
    # neuron.weights[j] = round(neuron.weights[j])
elif(currWeight+deltaW == 1):
    neuron.weights[j] = 1.000
#--------------------------------------------------------------------
if(currWeight+deltaW >= -1):
    neuron.weights[j] -= deltaW
    # neuron.weights[j] = round(neuron.weights[j])
elif(currWeight+deltaW == -1):
    neuron.weights[j] = -1.000

}

结语:

{

    本次就先更新这么多,我先消化一下,剩下的下次再更。

    参考资料:

    {

        [1]https://baike.baidu.com/item/%E8%84%89%E5%86%B2%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/4452657?fr=aladdin

        [2]https://github.com/shashank135sharma/Spiking-Neural-Network---Genre-Recognizer

    }

}

猜你喜欢

转载自blog.csdn.net/fish_like_apple/article/details/83112217