Updating registry at `~/.julia/registries/General.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
This use case of active inference comes from the retail industry. We use the published sales volume of a company over 250 time intervals. They state that this volume is composed of two components:
Product sales
Rental sales
For strategic reasons they only publish the combined volume. Their competitor came to us with a request to try and decouple the underlying signals. This will help the competitor to decide on a strategic repositioning in the marketplace.
To test our approach, we will use a simple random walk model to simulate both the product sales volume as well as the rental sales volume. We also add these two signals to simulate the published combined sales volume. Then we will use Bayesian inference inside the RxInfer Julia package to attempt to decouple the two sales volume signals - one for product volume and the other for rental volume. During inference we will not have access to the separate product and rental sales signals, but only to their combination. If it works well enough, we will take the real published data and attempt to decouple the two sales volume signals.
The Generative Process
Provision function (\(f_p\))
The provision function, provides another covariate vector. Because this is a sequential system, the provision function defines the transition between the previous state and the current state. This special case of the provision function is known as a transition function:
The tildes indicate that the parameters and variables are hidden and not observed.
## response function, provides the response to covariate vectorfunctionfᵣ(; f, s̃₁ₜ, s̃₂ₜ)returnf(s̃₁ₜ, s̃₂ₜ)end## fᵣ(; f=+, s̃₁ₜ=7.0, s̃₂ₜ=8.0)
fᵣ (generic function with 1 method)
Next, we create the sim_batch_data(...) function that accepts an argument \(f\) which is a function that captures how the two signals are coupled or combined. We will call the sim_batch_data() function with the \(+\) function because the product sales volume is simply added to the rental sales volume.
The function will return the real (but hidden) signals \(s̃₁\) and \(s̃₂\) (used for comparison later) and their coupled/combined version \(y\). We will use \(y\) (the published combined sales volume) as our observations during the inference. We also assume that \(y\) is corrupted with some observation noise.
## Data comes from either a simulation/lab (sim|lab) OR from the field (fld)## Data are handled either in batches (batch) OR online as individual points (point)## Batch data accumulates either## along the depth/examples dimension/axis (into the screen/page), OR## typical for supervised & unsupervised learning## along the time dimension/axis (down the screen page)## typical for sequential decision learning (reinforcement learning & active inference)# function sim_batch_data(f, T; seed=123, s̃₁₍ₜ₋₁₎, s̃₂₍ₜ₋₁₎, τ̃_s₁, τ̃_s₂, σ̃²ᵥ)functionsim_batch_data(f, T; seed, s̃₁₍ₜ₋₁₎, s̃₂₍ₜ₋₁₎, τ̃_s₁, τ̃_s₂, σ̃²ᵥ) rng =StableRNG(seed) p̃₁ =Vector{Float64}(undef, T) s̃₁ =Vector{Float64}(undef, T) p̃₂ =Vector{Float64}(undef, T) s̃₂ =Vector{Float64}(undef, T) r̃ =Vector{Float64}(undef, T) y =Vector{Float64}(undef, T)for t in1:T p̃₁[t] =fₚ(s̃ₜ₋₁=s̃₁₍ₜ₋₁₎) s̃₁[t] =rand(rng, Normal(p̃₁[t], sqrt(1.0/τ̃_s₁))) p̃₂[t] =fₚ(s̃ₜ₋₁=s̃₂₍ₜ₋₁₎) s̃₂[t] =rand(rng, Normal(p̃₂[t], sqrt(1.0/τ̃_s₂))) r̃[t] =fᵣ(f=f, s̃₁ₜ=s̃₁[t], s̃₂ₜ=s̃₂[t]) y[t] =rand(rng, Normal(r̃[t], sqrt(σ̃²ᵥ))) s̃₁₍ₜ₋₁₎ = s̃₁[t] s̃₂₍ₜ₋₁₎ = s̃₂[t]endreturn s̃₁, s̃₂, yend
RxInfer runs Bayesian inference as a variational optimisation procedure between the real solution and its variational proxy q. In our model specification we assumed noise components to be unknown so we need to enforce a structured mean-field assumption for the variational family of distributions q. This inevitably reduces the accuracy of the result, but makes the task easier and allows for fast and analytical message passing-based variational inference:
The inferred hidden signals track the their hidden counterparts initially, but then progressively deviates increasingly. We can conclude that the product volume declines consistently, but that the rental volume keeps on increasing, or at least does not show a declining trend. This kind of information could be valuable for the competitor that approached us. Based on the simulation results, they may just decide to position themselves to compete more aggresively in the product space (due to reduced competition), rather than the rental space.