I stumbled upon this
very nice example
from
@SebastianCallh
in which Neural ODE are used to model and forecast the weather in the city of Delhi. I have a keen interest in learning how to apply differential equations using Julia, so I thought I could give it a try and adapt this code to work on my data.
Took some time to understand the basics of Julia (first timer coming from Python!), but in the end I managed to use
@SebastianCallh
example to put everything together as follows:
using Dates
using Plots
using Plotly
using DiffEqFlux
using Statistics
using DataFrames, CSV
using OrdinaryDiffEq, Flux, Random
function neural_ode(t, data_dim; saveat = t)
f = FastChain(FastDense(data_dim, 64, swish),
FastDense(64, 32, swish),
FastDense(32, data_dim))
node = NeuralODE(f, (minimum(t), maximum(t)), Tsit5(),
saveat = saveat, abstol = 1e-9,
reltol = 1e-9)
function train(node, θ, y, opt, maxiters, y0 = y[:, 1]; kwargs...)
predict(θ) = Array(node(y0, θ))
loss(θ) = begin
ŷ = predict(θ)
Flux.mse(ŷ, y)
θ = θ == nothing ? node.p : θ
res = DiffEqFlux.sciml_train(
loss, θ, opt,
maxiters = maxiters;
kwargs...
return res.minimizer
log_results(θs, losses) =
(θ, loss) -> begin
push!(θs, copy(θ))
push!(losses, loss)
false
################
# Main program #
################
# Loading data and setting up metadata
meta_cols = ["rowid", "SiteId", "datetime", "longitude", "latitude"]
data_cols = ["datetime", "DryBulbTemperature_Celsius"]
# Loading 168 records of hourly temperature for a given week in July 2018
# Note: Contains 'missing' values that are casted to NaN and then ignored with filter(!isnan, )
path_in = "PATH/week_of_hourly_temperature_records.csv"
df = DataFrame!(CSV.read(path_in))[data_cols]
df["DryBulbTemperature_Celsius"] = replace(df["DryBulbTemperature_Celsius"], missing=>NaN)
# Normalizing and splitting on train/test
T = 72 # first 3 days for training, remaining 4 days for testing
mean_temp = mean(filter(!isnan, df["DryBulbTemperature_Celsius"])) # taking care of NaNs
stdev_temp = std(filter(!isnan, df["DryBulbTemperature_Celsius"]))
y = Matrix(df[:, ["DryBulbTemperature_Celsius"]] |>
y -> Matrix(y)' |>
y -> (y .- mean_temp) ./ stdev_temp)
train_dates, test_dates = df["datetime"][1:T], df["datetime"][T:end]
vals_train_norm, vals_test_norm = y[1:T], y[T:end]
# Starting with the analysis
Random.seed!(1);
maxiters = 150
θs, losses = [], []
θ = nothing
num_obs = 4:4:length(vals_train_norm)
for k in num_obs
chunk = filter(!isnan, vals_train_norm[1:k])
node = neural_ode(chunk, size(y, 1))
θ = train(
node, θ, chunk,
ADAMW(8e-3), maxiters;
cb = log_results(θs, losses)
However, I found this persistent error and I don’t know how to fix it:
ERROR: LoadError: **UndefVarError: θ not defined**
Stacktrace:
[1] top-level scope at /PATH/JuliaProjects/basics_of/attempt.jl:87
in expression starting at /PATH/JuliaProjects/basics_of/attempt.jl:78
Because this θ is first set to “nothing” and it seems that will become iteratively calculated/updated. So it seems it makes sense to set up the variable with and empty value and let it change. I printed “node” and “f” and they contain values, so something starts happening, but then it crashes. I even changed the Greek symbol to “theta” just in case there was some problem there , but nothing.
Please, could you let me know what is missing in this code? How can I “define” theta properly? Perhaps an extra library import is required? Maybe the author can share the version creating these nice plots as well?
Thanks for your help!
for k in num_obs
chunk = filter(!isnan, vals_train_norm[1:k])
node = neural_ode(chunk, size(y, 1))
θ = train(
node, θ, chunk,
ADAMW(8e-3), maxiters;
cb = log_results(θs, losses)
You need a global θ
declaration in this loop, since the loop is in global scope. (It wouldn’t show up if you were writing your loop in a function.)
See Scope of Variables · The Julia Language
This is the infamous global-scope rule (Global variable scope rules lead to unintuitive behavior at the REPL/notebook · Issue #28789 · JuliaLang/julia · GitHub); the error message should be clearer in Julia 1.5.
This is due to Julia’s scoping rules, which regularly trip up beginners. Variables defined in global scope (in this case, θ
) aren’t visible within for loops unless they’re explicitly annotated as global variables. See here for further discussion.
A quick example:
julia> θ = 0
julia> for i = 1:10
θ += 1
ERROR: UndefVarError: θ not defined
Stacktrace:
[1] top-level scope at .\REPL[8]:2
julia> for i = 1:10
global θ += 1
julia> θ
@Irene I’m glad you found it useful, and welcome to Julia! @stevengj and @stillyslalom are absolutely right. The code you see in my blog post is inside a function which has different scoping rules. So it is totally fine to initialize a variable to nothing
, but in this case the variable itself is not “seen” from inside the loop.
That change is a good suggestion, but this has already been changed more significantly in the upcoming 1.5 release. This works heuristically in the REPL and errors more clearly in a file. In the REPL:
julia> θ = 0
julia> for i = 1:10
θ += 1
julia> θ
In a file:
┌ Warning: Assignment to `θ` in soft scope is ambiguous because
| a global variable by the same name exists: `θ` will be treated
| as a new local. Disambiguate by using `local θ` to suppress this
| warning or `global θ` to assign to the existing global variable.
└ @ string:5
ERROR: LoadError: UndefVarError: θ not defined
Hopefully that’s clear enough that it would have helped you figure it out.