From e6a3ad520235231a503c915c796db274622d6770 Mon Sep 17 00:00:00 2001 From: tonaerospace Date: Fri, 19 May 2023 14:54:12 +0700 Subject: [PATCH] add neuroplasticity --- src/Ironpen.jl | 3 +- src/learn.jl | 13 +----- src/snn_utils.jl | 104 +++++++++++++++++++++++++++++++++++------------ src/types.jl | 49 +++++++++++++--------- 4 files changed, 110 insertions(+), 59 deletions(-) diff --git a/src/Ironpen.jl b/src/Ironpen.jl index 84acd60..2b15b95 100644 --- a/src/Ironpen.jl +++ b/src/Ironpen.jl @@ -34,8 +34,6 @@ using .interface """ Todo: - [3] verify that model can complete learning cycle with no error - [*2] neuroplasticity() i.e. change connection [] using RL to control learning signal [] consider using Dates.now() instead of timestamp because time_stamp may overflow [] training should include adjusting α, neuron membrane potential decay factor @@ -58,6 +56,7 @@ using .interface [DONE] reset_epsilonRec after ΔwRecChange is calculated [DONE] synaptic connection strength concept. use sigmoid, turn connection offline [DONE] wRec should not normalized whole. it should be local 5 conn normalized. + [DONE] neuroplasticity() i.e. change connection Change from version: v06_36a - diff --git a/src/learn.jl b/src/learn.jl index 0f48875..ff1a9f5 100644 --- a/src/learn.jl +++ b/src/learn.jl @@ -54,8 +54,7 @@ function learn!(kfn::kfn_1, correctAnswer::AbstractVector) n.wRec .*= nonFlipedSign synapticConnStrength!(n) - #TODO neuroplasticity - println("") + neuroplasticity!(n, kfn.firedNeurons, kfn.nExInType) end end @@ -68,17 +67,9 @@ function learn!(kfn::kfn_1, correctAnswer::AbstractVector) n.wRec .*= nonFlipedSign # set weight that fliped sign to 0 for random new connection synapticConnStrength!(n) - #TODO neuroplasticity + neuroplasticity!(n, kfn.firedNeurons, kfn.nExInType) end - - resetLearningParams!(n) - - # clear variables - kfn.firedNeurons = Vector{Int64}() - kfn.firedNeurons_t0 = Vector{Bool}() - kfn.firedNeurons_t1 = Vector{Bool}() - kfn.learningStage = "inference" end end diff --git a/src/snn_utils.jl b/src/snn_utils.jl index 7c855a8..6ba5cb6 100644 --- a/src/snn_utils.jl +++ b/src/snn_utils.jl @@ -36,7 +36,7 @@ reset_epsilonRec!(n::computeNeuron) = n.epsilonRec = n.epsilonRec * 0.0 reset_epsilonRec!(n::outputNeuron) = n.epsilonRec = n.epsilonRec * 0.0 reset_epsilonRecA!(n::alifNeuron) = n.epsilonRecA = n.epsilonRecA * 0.0 reset_epsilon_in!(n::computeNeuron) = n.epsilon_in = isnothing(n.epsilon_in) ? nothing : n.epsilon_in * 0.0 -reset_error!(n::Union{computeNeuron, linearNeuron}) = n.error = nothing +reset_error!(n::Union{computeNeuron, outputNeuron}) = n.error = nothing reset_w_in_change!(n::computeNeuron) = n.w_in_change = isnothing(n.w_in_change) ? nothing : n.w_in_change * 0.0 reset_wRecChange!(n::Union{computeNeuron, outputNeuron}) = n.wRecChange = n.wRecChange * 0.0 reset_a!(n::alifNeuron) = n.a = n.a * 0.0 @@ -44,7 +44,7 @@ reset_reg_voltage_a!(n::computeNeuron) = n.reg_voltage_a = n.reg_voltage_a * 0.0 reset_reg_voltage_b!(n::computeNeuron) = n.reg_voltage_b = n.reg_voltage_b * 0.0 reset_reg_voltage_error!(n::computeNeuron) = n.reg_voltage_error = n.reg_voltage_error * 0.0 reset_firing_counter!(n::Union{computeNeuron, outputNeuron}) = n.firingCounter = n.firingCounter * 0.0 -reset_firing_diff!(n::Union{computeNeuron, linearNeuron}) = n.firingDiff = n.firingDiff * 0.0 +reset_firing_diff!(n::Union{computeNeuron, outputNeuron}) = n.firingDiff = n.firingDiff * 0.0 reset_refractoryCounter!(n::Union{computeNeuron, outputNeuron}) = n.refractoryCounter = n.refractoryCounter * 0.0 reset_z_i_t_commulative!(n::Union{computeNeuron, outputNeuron}) = n.z_i_t_commulative = n.z_i_t_commulative * 0.0 @@ -304,31 +304,6 @@ end function synapticConnStrength!(n::inputNeuron) end -function neuroplasticity!(n::computeNeuron, firedNeurons::Vector) - # if there is 0-weight then replace it with new connection - zero_weight_index = findall(iszero.(n.wRec)) - if length(zero_weight_index) != 0 - """ sampling new connection from list of neurons that fires instead of ramdom choose from - all compute neuron because there is no point to connect to neuron that not fires i.e. - not fire = no information - """ - - subscribe_options = filter(x -> x ∉ [n.id], firedNeurons) # exclude this neuron id from the list - filter!(x -> x ∉ n.subscriptionList, subscribe_options) # exclude this neuron's subscriptionList from the list - shuffle!(subscribe_options) - end - - new_connection_percent = 10 - ((n.optimiser.eta / 0.0001) / 10) # percent is in range 0.1 to 10 - percentage = [new_connection_percent, 100.0 - new_connection_percent] / 100.0 - for i in zero_weight_index - if Utils.random_choices([true, false], percentage) - n.subscriptionList[i] = pop!(subscribe_options) - n.wRec[i] = 0.01 # new connection should not send large signal otherwise it would throw - # RSNN off path. Let weight grow by an optimiser - end - end -end - """ normalize a part of a vector centering at a vector's maximum value along with nearby value within its radius. radius must be odd number. v1 will be normalized based on v2's peak @@ -343,10 +318,85 @@ function normalizePeak!(v1::Vector, v2::Vector, radius::Integer=2) normalize!(subvector, 1) end +""" rewire of neuron synaptic connection that has 0 weight. With connection's excitatory and + inhabitory ratio constraint. +""" +# function neuroplasticity!(n::Union{computeNeuron, outputNeuron}, firedNeurons::Vector, +# nExcitatory::Vector, nInhabitory::Vector, excitatoryPercent::Integer) +# # if there is 0-weight then replace it with new connection +# zeroWeightConnIndex = findall(iszero.(n.wRec)) # connection that has 0 weight +# desiredEx = Int(floor((excitatoryPercent / 100) * length(n.subscriptionList))) +# desiredIn = length(n.subscriptionList) - desiredEx +# wRecSign = sign.(n.wRec) +# inConn = sum(isequal.(wRecSign, -1)) + +# # random new synaptic connection +# inConnToAdd = desiredIn - inConn +# if inConnToAdd <= 0 +# # skip all new Conn will be excitatory type +# else +# newConnVecSign = ones(length(zeroWeightConnIndex)) +# newConnVecSign = view(newConnVecSign, 1:inConnToAdd) * -1 +# end + +# # new synaptic connection must sample fron neuron that fires +# inPool = nInhabitory ∩ firedNeurons +# filter!(x -> x ∉ [n.id], inPool) # exclude this neuron id from the list +# filter!(x -> x ∉ n.subscriptionList, inPool) # exclude this neuron's subscriptionList from the list +# exPool = nExcitatory ∩ firedNeurons +# filter!(x -> x ∉ [n.id], exPool) # exclude this neuron id from the list +# filter!(x -> x ∉ n.subscriptionList, exPool) # exclude this neuron's subscriptionList from the list + +# w = [rand(0.01:0.01:0.2, length(zeroWeightConnIndex))] .* newConnVecSign +# synapticStrength = [rand(-5:0.01:-4, length(zeroWeightConnIndex))] + +# # add new synaptic connection to neuron +# for (i, connIndex) in enumerate(zeroWeightConnIndex) +# n.subscriptionList[connIndex] = newConnVecSign[i] < 0 ? pop!(inPool) : pop!(exPool) +# n.wRec[connIndex] = w[i] +# n.synapticStrength[connIndex] = synapticStrength[i] +# end +# end +""" rewire of neuron synaptic connection that has 0 weight. Without connection's excitatory and + inhabitory ratio constraint. +""" +function neuroplasticity!(n::Union{computeNeuron, outputNeuron}, firedNeurons::Vector, + nExInTypeList::Vector) + # if there is 0-weight then replace it with new connection + zeroWeightConnIndex = findall(iszero.(n.wRec)) # connection that has 0 weight + # new synaptic connection must sample fron neuron that fires + nFiredPool = filter(x -> x ∉ [n.id], firedNeurons) # exclude this neuron id from the id list + filter!(x -> x ∉ n.subscriptionList, nFiredPool) # exclude this neuron's subscriptionList from the list + nNonFiredPool = setdiff!([1:length(nExInTypeList)...], nFiredPool) + filter!(x -> x ∉ [n.id], nNonFiredPool) # exclude this neuron id from the id list + filter!(x -> x ∉ n.subscriptionList, nNonFiredPool) # exclude this neuron's subscriptionList from the list + + w = rand(0.01:0.01:0.2, length(zeroWeightConnIndex)) + synapticStrength = rand(-5:0.01:-4, length(zeroWeightConnIndex)) + + shuffle!(nFiredPool) + shuffle!(nNonFiredPool) + + # add new synaptic connection to neuron + for (i, connIndex) in enumerate(zeroWeightConnIndex) + if length(nFiredPool) != 0 + newConn = popfirst!(nFiredPool) + else + newConn = popfirst!(nNonFiredPool) + end + + """ conn that is being replaced has to go into nNonFiredPool so nNonFiredPool isn't empty + """ + push!(nNonFiredPool, n.subscriptionList[connIndex]) + n.subscriptionList[connIndex] = newConn + n.wRec[connIndex] = w[i] * nExInTypeList[newConn] + n.synapticStrength[connIndex] = synapticStrength[i] + end +end diff --git a/src/types.jl b/src/types.jl index fa056c8..e741ab2 100644 --- a/src/types.jl +++ b/src/types.jl @@ -111,6 +111,10 @@ Base.@kwdef mutable struct kfn_1 <: knowledgeFn avgNeuronsFiringRate::Union{Float64,Nothing} = 0.0 # for displaying average firing rate over all neurons avgNeurons_v_t1::Union{Float64,Nothing} = 0.0 # for displaying average v_t1 over all neurons + nExcitatory::Union{Array,Nothing} = Vector{Integer}() # list of excitatory neuron id + nInhabitory::Union{Array,Nothing} = Vector{Integer}() # list of inhabitory neuron id + nExInType::Union{Array,Nothing} = Vector{Integer}() # list all neuron EX or IN + excitatoryPercent::Integer = 60 # percentage of excitatory neuron, inhabitory percent will be 100-ExcitatoryPercent end #------------------------------------------------------------------------------------------------100 @@ -229,7 +233,7 @@ function kfn_1(kfnParams::Dict) end # excitatory neuron to inhabitory neuron = 60:40 % of computeNeuron - ex_number = Int(floor(0.6 * kfn.kfnParams[:computeNeuronNumber])) + ex_number = Int(floor((kfn.excitatoryPercent/100.0) * kfn.kfnParams[:computeNeuronNumber])) ex_n = [1 for i in 1:ex_number] in_number = kfn.kfnParams[:computeNeuronNumber] - ex_number in_n = [-1 for i in 1:in_number] @@ -237,17 +241,22 @@ function kfn_1(kfnParams::Dict) # input neurons are always excitatory, compute_neurons are random between excitatory # and inhabitory - for n in reverse(kfn.neuronsArray) + for n in kfn.neuronsArray try n.ExInType = pop!(ex_in) catch end end # add ExInType into each computeNeuron subExInType - for n in reverse(kfn.neuronsArray) + for n in kfn.neuronsArray try # input neuron doest have n.subscriptionList for (i, sub_id) in enumerate(n.subscriptionList) n_ExInType = kfn.neuronsArray[sub_id].ExInType - # push!(n.subExInType, n_ExInType) n.wRec[i] *= n_ExInType + # add id exin type to kfn + if n_ExInType < 0 + push!(kfn.nInhabitory, sub_id) + else + push!(kfn.nExcitatory, sub_id) + end end catch end @@ -258,13 +267,16 @@ function kfn_1(kfnParams::Dict) try # input neuron doest have n.subscriptionList for (i, sub_id) in enumerate(n.subscriptionList) n_ExInType = kfn.neuronsArray[sub_id].ExInType - # push!(n.subExInType, n_ExInType) n.wRec[i] *= n_ExInType end catch end end + for n in kfn.neuronsArray + push!(kfn.nExInType, n.ExInType) + end + return kfn end @@ -457,7 +469,6 @@ Base.@kwdef mutable struct alifNeuron <: computeNeuron reset epsilon_j. "reflect" = neuron will merge wRecChange into wRec then reset wRecChange. """ learningStage::String = "inference" - end """ alif neuron outer constructor @@ -604,10 +615,10 @@ end # n.id = id # n.knowledgeFnName = kfnParams[:knowledgeFnName] # subscription_options = shuffle!([1:(kfnParams[:input_neuron_number]+kfnParams[:computeNeuronNumber])...]) -# if typeof(kfnParams[:synaptic_connection_number]) == String -# percent = parse(Int, kfnParams[:synaptic_connection_number][1:end-1]) / 100 -# synaptic_connection_number = floor(length(subscription_options) * percent) -# n.subscriptionList = [pop!(subscription_options) for i = 1:synaptic_connection_number] +# if typeof(kfnParams[:synapticConnectionPercent]) == String +# percent = parse(Int, kfnParams[:synapticConnectionPercent][1:end-1]) / 100 +# synapticConnectionPercent = floor(length(subscription_options) * percent) +# n.subscriptionList = [pop!(subscription_options) for i = 1:synapticConnectionPercent] # end # filter!(x -> x != n.id, n.subscriptionList) # n.epsilonRec = zeros(length(n.subscriptionList)) @@ -621,13 +632,13 @@ function init_neuron!(id::Int64, n::lifNeuron, n_params::Dict, kfnParams::Dict) n.id = id n.knowledgeFnName = kfnParams[:knowledgeFnName] subscription_options = shuffle!([1:kfnParams[:totalNeurons]...]) - subscription_numbers = Int(floor(n_params[:synaptic_connection_number] * - kfnParams[:totalNeurons] / 100.0)) + subscription_numbers = Int(floor((n_params[:synapticConnectionPercent] / 100.0) * + kfnParams[:totalNeurons])) n.subscriptionList = [pop!(subscription_options) for i = 1:subscription_numbers] # prevent subscription to itself by removing this neuron id filter!(x -> x != n.id, n.subscriptionList) - n.synapticStrength = rand(-5:0.1:-3, length(n.subscriptionList)) + n.synapticStrength = rand(-5:0.01:-4, length(n.subscriptionList)) n.epsilonRec = zeros(length(n.subscriptionList)) n.wRec = rand(-0.2:0.01:0.2, length(n.subscriptionList)) @@ -641,13 +652,13 @@ function init_neuron!(id::Int64, n::alifNeuron, n_params::Dict, n.id = id n.knowledgeFnName = kfnParams[:knowledgeFnName] subscription_options = shuffle!([1:kfnParams[:totalNeurons]...]) - subscription_numbers = Int(floor(n_params[:synaptic_connection_number] * - kfnParams[:totalNeurons] / 100.0)) + subscription_numbers = Int(floor((n_params[:synapticConnectionPercent] / 100.0) * + kfnParams[:totalNeurons])) n.subscriptionList = [pop!(subscription_options) for i = 1:subscription_numbers] # prevent subscription to itself by removing this neuron id filter!(x -> x != n.id, n.subscriptionList) - n.synapticStrength = rand(-5:0.1:-3, length(n.subscriptionList)) + n.synapticStrength = rand(-5:0.01:-4, length(n.subscriptionList)) n.epsilonRec = zeros(length(n.subscriptionList)) n.wRec = rand(-0.2:0.01:0.2, length(n.subscriptionList)) @@ -667,10 +678,10 @@ function init_neuron!(id::Int64, n::linearNeuron, n_params::Dict, kfnParams::Dic n.knowledgeFnName = kfnParams[:knowledgeFnName] subscription_options = shuffle!([kfnParams[:totalInputPort]+1 : kfnParams[:totalNeurons]...]) - subscription_numbers = Int(floor(n_params[:synaptic_connection_number] * - kfnParams[:totalComputeNeuron] / 100.0)) + subscription_numbers = Int(floor((n_params[:synapticConnectionPercent] / 100.0) * + kfnParams[:totalNeurons])) n.subscriptionList = [pop!(subscription_options) for i = 1:subscription_numbers] - n.synapticStrength = rand(-5:0.1:-3, length(n.subscriptionList)) + n.synapticStrength = rand(-5:0.01:-4, length(n.subscriptionList)) n.epsilonRec = zeros(length(n.subscriptionList)) n.wRec = rand(-0.2:0.01:0.2, length(n.subscriptionList))