I have been using Flux.jl and have been confused by the differences when running code within a let block and without. The following example runs without error:
using Flux
p = rand(2)
function f(x)
f, b = p
x*f + b
end
data = reduce(hcat, [[x, f(x)] for x in 0:0.1:1.0])
p = rand(2)
θ = params(p)
loss(y) = sum((y .- f.(data[1,:])).^2)
for n in 1:1000
grads = Flux.gradient(θ) do
loss(data[2,:])
end
Flux.Optimise.update!(ADAM(), θ, grads)
end
However, wrapping the same code in a let block does not work as I expect:
using Flux
let
...
end
and produces the stacktrace:
MethodError: objects of type Float64 are not callable
Maybe you forgot to use an operator such as [36m*, ^, %, / etc. [39m?
Stacktrace:
[1] macro expansion
@ ~/.julia/packages/Zygote/bJn8I/src/compiler/interface2.jl:0 [inlined]
[2] _pullback(ctx::Zygote.Context, f::Float64, args::Float64)
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface2.jl:9
[3] (::Zygote.var"#1100#1104"{Zygote.Context, Float64})(x::Float64)
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/lib/broadcast.jl:186
[4] _broadcast_getindex_evalf
@ ./broadcast.jl:670 [inlined]
[5] _broadcast_getindex
@ ./broadcast.jl:643 [inlined]
[6] getindex
@ ./broadcast.jl:597 [inlined]
[7] copy
@ ./broadcast.jl:899 [inlined]
[8] materialize
@ ./broadcast.jl:860 [inlined]
[9] _broadcast
@ ~/.julia/packages/Zygote/bJn8I/src/lib/broadcast.jl:163 [inlined]
[10] adjoint
@ ~/.julia/packages/Zygote/bJn8I/src/lib/broadcast.jl:186 [inlined]
[11] _pullback
@ ~/.julia/packages/ZygoteRules/AIbCs/src/adjoint.jl:65 [inlined]
[12] _apply
@ ./boot.jl:814 [inlined]
[13] adjoint
@ ~/.julia/packages/Zygote/bJn8I/src/lib/lib.jl:200 [inlined]
[14] _pullback
@ ~/.julia/packages/ZygoteRules/AIbCs/src/adjoint.jl:65 [inlined]
[15] _pullback
@ ./broadcast.jl:1297 [inlined]
[16] _pullback(::Zygote.Context, ::typeof(Base.Broadcast.broadcasted), ::Float64, ::Vector{Float64})
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface2.jl:0
[17] _pullback
@ ./In[198]:32 [inlined]
[18] _pullback(::Zygote.Context, ::var"#loss#155", ::Vector{Float64}, ::Vector{Float64})
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface2.jl:0
[19] _pullback
@ ./In[198]:37 [inlined]
[20] _pullback(::Zygote.Context, ::var"#152#156"{var"#loss#155", Matrix{Float64}})
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface2.jl:0
[21] pullback(f::Function, ps::Zygote.Params)
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface.jl:351
[22] gradient(f::Function, args::Zygote.Params)
@ Zygote ~/.julia/packages/Zygote/bJn8I/src/compiler/interface.jl:75
[23] top-level scope
@ In[198]:36
[24] eval
@ ./boot.jl:373 [inlined]
[25] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1196
Whereas I had expected them to both behave identically (at least in isolation). I cannot tell much myself from the stacktrace, since I have no experience with the implementation of Flux.jl or Zygote.jl. But the problem seems to be something to do with the definition of the function f, since changing the definition of f to:
function f(x)
a, b = p
x*a + b
end
Allows both the let and letless versions to work. Of course, I could fix it like this and call it a day. But I am curious if anyone knows why the two versions work differently?
Note
(@v1.7) pkg> status Flux
Status `~/.julia/environments/v1.7/Project.toml`
[587475ba] Flux v0.12.8