【程式碼閱讀記錄】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。
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函式負責更新隱含層的權值,其步驟大致如下:
- 陸續輸入輸入列表中的初始輸入資料到輸入層;
- 計算輸入層每個Neuron每次輸出有沒有超過一個閾值spikingThreshold,並且如果超過spikingThreshold的情況過半,則設其fired變數為1(當然也記錄了每次的輸出);
- 計算隨後的隱含層的每個Neuron(輸入資料變成了輸入層的輸出乘以權值)的輸出currentSpikeRate ,其中當初始輸入資料的第139維資料為0時(網路輸入的大小為138,第139維是多出來的一個維度),本次的輸入會*0.7;
- 如果currentSpikeRate 輸入落在了[spikingThreshold-0.1, spikingThreshold),或者某權值對應的輸入層Neuron的fired不為1,則跳過此權值的更新,否則計算此權值的更新量deltaW = (學習率*(1-此權值))/時間步,其中時間步是上述程式碼1中的self.timeStep;
- 如果currentSpikeRate 大於等於spikingThreshold且此權值和deltaW的和落在(-1, 1],則增加deltaW到此權值,但是如果currentSpikeRate小於等於spikingThreshold-0.1且此權值和deltaW的和大於等於-1,則從此權值減去deltaW,並且對於剩下的情況,同樣跳過此權值的更新;
- 對所有隱含層的其他權值也進行上述操作。
看這段程式碼的時候我發現了一個問題,程式碼4中的兩個elif永遠執行不到,可能這兩個elif原本是無條件的else。
#程式碼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
}
結語:
{
本次就先更新這麼多,我先消化一下,剩下的下次再更。
參考資料:
{
}
}