EDIT: contrary to the title, I have come to the conclusion that the problem lies elsewhere (see post below). Unless this contradicts etiquette, I will not modify the original title, nor the original question below.
NEW EDIT: The bottom line is that the issue presented here was due to the inherent instability of iterating over a heterogeneous Tuple.
I have coded my own neural network that I have been using for some time. My neural network is an array of layers. I have defined a struct
for a layer. The code works well, but contains a type instability that I only recently detected via @code_warntype
. Though probably obvious to the experienced eye, I am puzzled as to why these type instabilities arise. Below, I paste my code (sorry for the idiosyncratic formatting) and the output of @code_warntype
.
using LinearAlgebra, Random
#########################################################
# Constructor
#########################################################
struct layer{F}
Din::Int64 # number of inputs to layer
Dout::Int64 # number of outputs of layer
f::F
end
function makelayer(;out=nou::Int64, in=nin::Int64, f = tanh)
layer(in, out, f)
end
#########################################################
# Evaluation
#########################################################
#----------------------------------------------------
function (a::layer)(weights, x)
#----------------------------------------------------
MARK = 0
aux1 = @view weights[MARK+1:MARK + a.Dout * a.Din]
w = reshape(aux1, a.Dout, a.Din)
MARK += a.Dout * a.Din
aux2 = @view weights[MARK+1:MARK + a.Dout]
b = reshape(aux2, a.Dout)
MARK += a.Dout
@assert(MARK == length(weights)) # make sure all weights have been used up
a.f.(w * x .+ b) # evaluation of layer
end
#----------------------------------------------------
function (a::Array{layer,1})(weights, x)
#----------------------------------------------------
MARK = 0
out = x
for l in a
# the output of each layer is the input of the next layer
# the input to the first layer is x
out = @views l(weights[MARK+1:MARK+numweights(l)], out)
MARK += numweights(l)
end
@assert(MARK == numweights(a)) # make sure all weights have been used up
return out
end
#########################################################
# Number of parameters in network
#########################################################
numweights(a::layer) = a.Din * a.Dout + a.Dout
numweights(a::Array{layer,1}) = mapreduce(numweights, +, a)
numlayers(a::Array{layer,1}) = length(a)
I have verified that function function (a::layer)(weights, x)
is type stable.
As an example of creating a network and evaluating it for a set of weights and an input, we can use the following:
net = [makelayer(out=50, in=1, f=tanh); makelayer(in=50,out=50,f=tanh); makelayer(in = 50, out=1, f=identity)]; # create a 3-layer network
w = randn(numweights(net)); # instantiate random weights
x = randn(1,1000); # instantiate 1000 random inputs
net(w,x) # evaluate the network
The output of @code_warntype net(w,x)
follows below:
MethodInstance for (::Vector{ForwardNeuralNetworks.layer})(::Vector{Float64}, ::Matrix{Float64})
from (a::Vector{ForwardNeuralNetworks.layer})(weights, x) @ ForwardNeuralNetworks ~/.julia/dev/ForwardNeuralNetworks/src/neuralnetwork.jl:49
Arguments
a::Vector{ForwardNeuralNetworks.layer}
weights::Vector{Float64}
x::Matrix{Float64}
Locals
@_4::Union{Nothing, Tuple{ForwardNeuralNetworks.layer, Int64}}
out::Any
MARK::Int64
l::ForwardNeuralNetworks.layer
Body::Any
1 ─ (MARK = 0)
│ (out = x)
│ %3 = a::Vector{ForwardNeuralNetworks.layer}
│ (@_4 = Base.iterate(%3))
│ %5 = (@_4 === nothing)::Bool
│ %6 = Base.not_int(%5)::Bool
└── goto #4 if not %6
2 ┄ %8 = @_4::Tuple{ForwardNeuralNetworks.layer, Int64}
│ (l = Core.getfield(%8, 1))
│ %10 = Core.getfield(%8, 2)::Int64
│ %11 = (MARK + 1)::Int64
│ %12 = MARK::Int64
│ %13 = ForwardNeuralNetworks.numweights(l)::Int64
│ %14 = (%12 + %13)::Int64
│ %15 = (%11:%14)::UnitRange{Int64}
│ %16 = (Base.maybeview)(weights, %15)::Core.PartialStruct(SubArray{Float64, 1, Vector{Float64}, Tuple{UnitRange{Int64}}, true}, Any[Vector{Float64}, Tuple{UnitRange{Int64}}, Int64, Core.Const(1)])
│ (out = (l)(%16, out))
│ %18 = MARK::Int64
│ %19 = ForwardNeuralNetworks.numweights(l)::Int64
│ (MARK = %18 + %19)
│ (@_4 = Base.iterate(%3, %10))
│ %22 = (@_4 === nothing)::Bool
│ %23 = Base.not_int(%22)::Bool
└── goto #4 if not %23
3 ─ goto #2
4 ┄ %26 = MARK::Int64
│ %27 = ForwardNeuralNetworks.numweights(a)::Int64
│ %28 = (%26 == %27)::Bool
└── goto #6 if not %28
5 ─ goto #7
6 ─ %31 = Base.AssertionError("MARK == numweights(a)")::Any
└── Base.throw(%31)
7 ┄ return out
I don’t undestand why out
is type instable. out
is bound to x
whose type we know. Also function function (a::layer)(weights, x)
is type stable.
I apologise if the formatting above is not helpful.