Skip to content

Commit fc86c97

Browse files
authored
Merge pull request #52 from LAMPSPUC/dev
Feature/improve in out map - version 0.1.6
2 parents 0e6db8a + a324bd7 commit fc86c97

16 files changed

+592
-144
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
name = "ApplicationDrivenLearning"
22
uuid = "0856f1c8-ef17-4e14-9230-2773e47a789e"
33
authors = ["Giovanni Amorim", "Joaquim Garcia"]
4-
version = "0.1.5"
4+
version = "0.1.6"
55

66
[deps]
77
BilevelJuMP = "485130c0-026e-11ea-0f1a-6992cd14145c"
88
DiffOpt = "930fe3bc-9c6b-11ea-2d94-6184641e85e7"
99
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
10+
Functors = "d9f16b24-f501-4c13-a1f2-28368ffc5196"
1011
JobQueueMPI = "32d208e1-246e-420c-b6ff-18b71b410923"
1112
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
1213
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
@@ -20,6 +21,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
2021
BilevelJuMP = "0.6.2"
2122
DiffOpt = "0.5.0"
2223
Flux = "0.16.3"
24+
Functors = "0.5.2"
2325
JobQueueMPI = "0.1.1"
2426
JuMP = "1.24"
2527
MPI = "0.20.22"

docs/make.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ Documenter.makedocs(;
88
authors = "Giovanni Amorim, Joaquim Garcia",
99
pages = [
1010
"Home" => "index.md",
11-
"Tutorials" => joinpath.(
12-
"tutorials",
13-
["getting_started.md", "modes.md", "custom_forecast.md"],
14-
),
11+
"Tutorials" =>
12+
joinpath.(
13+
"tutorials",
14+
["getting_started.md", "modes.md", "custom_forecast.md"],
15+
),
1516
"Examples" => joinpath.("examples", ["scheduling.md", "newsvendor.md"]),
1617
"API Reference" => "reference.md",
1718
],

docs/src/examples/newsvendor.md

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ i=2 \longrightarrow c=10; \quad q = 11;\quad r = 1
3131

3232
The demand series for both items will be generated using a discrete uniform distribution.
3333

34-
Let's start by loading the necessary packages and defining the data.
34+
Let's start by loading the necessary packages and defining the problem parameters.
3535

3636
```julia
3737
using Flux
@@ -47,8 +47,6 @@ Random.seed!(123)
4747
c = [10, 10]
4848
q = [19, 11]
4949
r = [9, 1]
50-
y_d = rand(10:100, (100, 2)) .|> Float32
51-
x_d = ones(100, 1) .|> Float32
5250
```
5351

5452
Now, we can initialize the application driven learning model, build the plan and assess models and set the forecast model.
@@ -91,11 +89,21 @@ pred = Flux.Dense(1 => 2, exp)
9189
ADL.set_forecast_model(model, pred)
9290
```
9391

92+
Then, we can initialize the data, referencing forecast variables.
93+
94+
```julia
95+
x_d = ones(100, 1) .|> Float32
96+
y_d = Dict(
97+
d[1] => rand(10:100, 100) .|> Float32,
98+
d[2] => rand(10:100, 100) .|> Float32
99+
)
100+
```
101+
94102
We can check how the model performs by computing the assess cost with the initial (random) forecast model.
95103

96104
```julia
97105
julia> ADL.compute_cost(model, x_d, y_d)
98-
-5.118482679128647
106+
-3.571615f0
99107
```
100108

101109
Now let's train the model using the GradientMode.
@@ -109,49 +117,49 @@ julia> gd_sol = ApplicationDrivenLearning.train!(
109117
epochs=30
110118
)
111119
)
112-
Epoch 1 | Time = 0.5s | Cost = -5.12
113-
Epoch 2 | Time = 1.0s | Cost = -6.25
114-
Epoch 3 | Time = 1.5s | Cost = -7.64
115-
Epoch 4 | Time = 2.1s | Cost = -9.33
116-
Epoch 5 | Time = 2.6s | Cost = -11.4
117-
Epoch 6 | Time = 3.1s | Cost = -13.93
118-
Epoch 7 | Time = 3.6s | Cost = -17.02
119-
Epoch 8 | Time = 4.1s | Cost = -20.82
120-
Epoch 9 | Time = 4.7s | Cost = -25.17
121-
Epoch 10 | Time = 5.2s | Cost = -29.51
122-
Epoch 11 | Time = 5.7s | Cost = -33.42
123-
Epoch 12 | Time = 6.3s | Cost = -37.56
124-
Epoch 13 | Time = 6.8s | Cost = -42.92
125-
Epoch 14 | Time = 7.5s | Cost = -50.45
126-
Epoch 15 | Time = 8.2s | Cost = -60.3
127-
Epoch 16 | Time = 8.9s | Cost = -72.4
128-
Epoch 17 | Time = 9.5s | Cost = -87.18
129-
Epoch 18 | Time = 10.2s | Cost = -105.31
130-
Epoch 19 | Time = 10.8s | Cost = -127.53
131-
Epoch 20 | Time = 11.4s | Cost = -154.36
132-
Epoch 21 | Time = 12.1s | Cost = -185.68
133-
Epoch 22 | Time = 12.8s | Cost = -222.14
134-
Epoch 23 | Time = 13.4s | Cost = -265.19
135-
Epoch 24 | Time = 14.0s | Cost = -315.45
136-
Epoch 25 | Time = 14.5s | Cost = -370.01
137-
Epoch 26 | Time = 15.1s | Cost = -425.62
138-
Epoch 27 | Time = 15.7s | Cost = -464.52
139-
Epoch 28 | Time = 16.3s | Cost = -461.25
140-
Epoch 29 | Time = 16.9s | Cost = -439.36
141-
Epoch 30 | Time = 17.5s | Cost = -419.52
142-
ApplicationDrivenLearning.Solution(-464.5160680770874, Real[1.6317965f0, 1.7067692f0, 2.7623773f0, 0.9124785f0])
120+
Epoch 1 | Time = 0.4s | Cost = -3.57
121+
Epoch 2 | Time = 0.8s | Cost = -4.36
122+
Epoch 3 | Time = 1.2s | Cost = -5.33
123+
Epoch 4 | Time = 1.6s | Cost = -6.51
124+
Epoch 5 | Time = 2.0s | Cost = -7.95
125+
Epoch 6 | Time = 2.4s | Cost = -9.72
126+
Epoch 7 | Time = 2.8s | Cost = -11.88
127+
Epoch 8 | Time = 3.2s | Cost = -14.53
128+
Epoch 9 | Time = 3.6s | Cost = -17.78
129+
Epoch 10 | Time = 4.0s | Cost = -21.77
130+
Epoch 11 | Time = 4.4s | Cost = -26.68
131+
Epoch 12 | Time = 4.8s | Cost = -32.73
132+
Epoch 13 | Time = 5.2s | Cost = -39.52
133+
Epoch 14 | Time = 5.6s | Cost = -46.64
134+
Epoch 15 | Time = 6.0s | Cost = -54.15
135+
Epoch 16 | Time = 6.4s | Cost = -62.95
136+
Epoch 17 | Time = 6.8s | Cost = -74.74
137+
Epoch 18 | Time = 7.2s | Cost = -90.36
138+
Epoch 19 | Time = 7.6s | Cost = -110.35
139+
Epoch 20 | Time = 8.0s | Cost = -135.12
140+
Epoch 21 | Time = 8.4s | Cost = -164.34
141+
Epoch 22 | Time = 8.8s | Cost = -197.82
142+
Epoch 23 | Time = 9.2s | Cost = -237.1
143+
Epoch 24 | Time = 9.6s | Cost = -282.57
144+
Epoch 25 | Time = 10.0s | Cost = -334.87
145+
Epoch 26 | Time = 10.4s | Cost = -389.66
146+
Epoch 27 | Time = 10.8s | Cost = -442.7
147+
Epoch 28 | Time = 11.2s | Cost = -469.92
148+
Epoch 29 | Time = 11.6s | Cost = -452.58
149+
Epoch 30 | Time = 12.0s | Cost = -430.85
150+
ApplicationDrivenLearning.Solution(-469.91516f0, Real[1.6040976f0, 1.2566354f0, 2.8811285f0, 1.1966338f0])
143151

144152
julia> ADL.compute_cost(model, x_d, y_d)
145-
-464.5160680770874
153+
-469.91516f0
146154
```
147155

148156
After training, we can check the cost of the solution found by the gradient mode and even analyze the predictions from it.
149157

150158
```julia
151159
julia> model.forecast(x_d[1,:])
152-
2-element Vector{Float32}:
153-
80.977684
154-
13.725394
160+
2-element ApplicationDrivenLearning.VariableIndexedVector{Float32}:
161+
88.69701
162+
11.626293
155163
```
156164

157165
As we can see, the forecast model overestimates the demand for the first item and underestimates the demand for the second item (both items average demand is 55), following the incentives from the model structure.

docs/src/examples/scheduling.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ We can check how the model performs by computing the assess cost with the initia
5353

5454
```julia
5555
X = ones(2, 1) .|> Float32
56-
Y = zeros(2, 1) .|> Float32
57-
Y[2, 1] = 2.0
56+
Y = Dict(y => [0.0, 2.0] .|> Float32)
5857
set_optimizer(model, Gurobi.Optimizer)
5958
set_silent(model)
6059
```
@@ -121,10 +120,10 @@ Iter Function value √(Σ(yᵢ-ȳ)²)/n
121120
* time: 0.1640000343322754
122121
ApplicationDrivenLearning.Solution(29.99998f0, Real[0.6931467f0])
123122

124-
julia> model.forecast(X[1,:]) # previsão final
123+
julia> model.forecast(X[1,:]) # final forecast
125124
1-element Vector{Float32}:
126125
1.999999
127126

128-
julia> ADL.compute_cost(model, X, Y) # custo final
127+
julia> ADL.compute_cost(model, X, Y) # final cost
129128
29.99998
130129
```

docs/src/reference.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ ApplicationDrivenLearning.apply_gradient!
6060
## Other functions
6161

6262
```@docs
63-
forecast
6463
compute_cost
6564
train!
6665
ApplicationDrivenLearning.build_plan_model_forecast_params

docs/src/tutorials/custom_forecast.md

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,74 @@ The basic approach to define a forecast model is to use a `Chain` from the `Flux
44

55
## Input-Output mapping
66

7-
The connection between predictive model outputs and plan model inputs is not always a straightforward one. Because of this, the `set_forecast_model` function, used to define the predictive model in the ApplicationDrivenLearning.jl package, includes the `input_output_map` parameter.
8-
This parameter allows users to declare an explicit mapping between the outputs produced by Flux models and the forecast variables used in the planning model. This is useful in contexts where the same prediction logic can be applied across several entities (such as production units or geographical locations), promoting model reuse and computational efficiency.
7+
The connection between predictive model outputs and plan model inputs is not always a straightforward one. Because of this, the definition of a `PredictiveModel` structure, used to define the predictive model in the ApplicationDrivenLearning.jl package, includes the `input_output_map` parameter.
8+
This parameter allows users to declare an explicit mapping between the outputs produced by Flux models and the forecast variables used in the planning model. This is useful in contexts where the same prediction logic can be applied across several entities (such as production units or geographical locations), promoting model reuse and computational and parameter efficiency.
99

10-
Consider a scenario where the input dataset contains 3 predictive variables (for example expected temperature on location 1, expected temperature on location 2 and weekday), there are 2 forecast variables (energy demand on the two locations of interest) and the forecast model should use only the expected temperature of a location to predict it’s demand. That means we would make two predictions using the same model and concatenate those values. This can be easily achieved with a dictionary mapping the data input and forecast variable indexes.
10+
Consider a scenario where the input dataset contains 3 variables (for example expected temperature on location 1, expected temperature on location 2 and weekday), there are 2 forecast variables (energy demand on the two locations of interest) and the forecast model should use only the expected temperature of a location to predict it’s demand. That means we would make two predictions using the same model and concatenate those values. This can be easily achieved with a dictionary mapping the data input and forecast variable indexes.
1111

1212
```julia
13+
model = ApplicationDrivenLearning.Model()
14+
@variable(model, demand[1:2], ApplicationDrivenLearning.Forecast)
15+
1316
X = [
1417
76 89 2;
1518
72 85 3
16-
] # input dataset of size 2 by 3
19+
] .|> Float32 # input dataset of size 2 by 3
20+
Y = Dict(
21+
demand[1] => [101, 89] .|> Float32,
22+
demand[2] => [68, 49] .|> Float32
23+
)
1724
dem_forecast = Dense(
1825
2 => 1
1926
) # forecast model takes 2 inputs and outputs single value
2027

2128
input_output_map = Dict(
22-
[1, 3] => [1], # input indexes 1 and 3 map to 1st forecast variable
23-
[2, 3] => [2] # input indexes 2 and 3 map to 2nd forecast variable
29+
[1, 3] => [demand[1]], # input indexes 1 and 3 map to 1st forecast variable
30+
[2, 3] => [demand[2]] # input indexes 2 and 3 map to 2nd forecast variable
2431
)
25-
ApplicationDrivenLearning.set_forecast_model(model, dem_forecast, input_output_map)
32+
predictive = PredictiveModel(dem_forecast, input_output_map)
33+
ApplicationDrivenLearning.set_forecast_model(model, predictive)
2634
```
2735

2836
## Multiple Flux models
2937

3038
The definition of the predictive model can also be done using multiple Flux models. This supports the modular construction of predictive architectures, where specialized components are trained to forecast different aspects of the problem, without the difficulty of defining custom architectures.
3139

32-
This can be achieved providing an array of model objects and an array of dictionaries as input-output mapping to the `set_forecast_model` function. Using the context from previous example, let’s assume there is an additional variable that has to be predicted to each location but not variable on time (that is, on dataset samples). This can be achieved defining an additional model that maps a constant input value to the correct output indexes.
40+
This can be achieved providing an array of model objects and an array of dictionaries as input-output mapping to the `PredictiveModel` construction. Using the context from previous example, let’s assume we also want to predict price for each location using a single model that receives average lagged price. This can be achieved defining an additional model and mapping.
3341

3442
```julia
43+
model = ApplicationDrivenLearning.Model()
44+
@variables(model, begin
45+
demand[1:2], ApplicationDrivenLearning.Forecast
46+
price[1:2], ApplicationDrivenLearning.Forecast
47+
end)
48+
3549
X = [
36-
76 89 2 1;
37-
72 85 3 1
38-
] # input dataset of size 2 by 4
50+
76 89 2 103;
51+
72 85 3 89
52+
] .|> Float32 # input dataset of size 2 by 4
53+
Y = Dict(
54+
demand[1] => [101, 89] .|> Float32,
55+
demand[2] => [68, 49] .|> Float32,
56+
price[1] => [101, 89] .|> Float32,
57+
price[2] => [68, 49] .|> Float32,
58+
)
3959
dem_forecast = Dense(
4060
2 => 1
4161
) # demand forecast model takes 2 inputs and outputs single value
42-
aux_forecast = Dense(
62+
prc_forecast = Dense(
4363
1 => 2
44-
) # auxiliar forecast model takes 1 input and outputs 2 values
45-
forecast_objs = [dem_forecast, aux_forecast]
64+
) # price forecast model takes 1 input and outputs 2 values
65+
forecast_objs = [dem_forecast, prc_forecast]
4666
input_output_map = [
4767
Dict(
48-
[1, 3] => [1],
49-
[2, 3] => [2]
50-
), # input indexes 1,2,3 are used to compute forecast vars 1,2 with 1st Flux.Dense object
68+
[1, 3] => [demand[1]],
69+
[2, 3] => [demand[2]]
70+
), # input indexes 1,2,3 are used to compute demand forecast vars separately with 1st Flux.Dense object
5171
Dict(
52-
[4] => [3, 4]
53-
), # input index 4 is used to compute forecast vars 3,4 with 2nd Flux.Dense object
72+
[4] => price
73+
), # input index 4 is used to compute both price forecast vars with 2nd Flux.Dense object
5474
]
55-
ApplicationDrivenLearning.set_forecast_model(model, forecast_objs, input_output_map)
75+
predictive = PredictiveModel(forecast_objs, input_output_map)
76+
ApplicationDrivenLearning.set_forecast_model(model, predictive)
5677
```

docs/src/tutorials/getting_started.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ using Flux
1616
import HiGHS
1717
using ApplicationDrivenLearning
1818

19-
# data
20-
X = reshape([1 1], (2, 1)) .|> Float32
21-
Y = reshape([10 20], (2, 1)) .|> Float32
22-
2319
# main model and policy / forecast variables
2420
model = ApplicationDrivenLearning.Model()
2521
@variables(model, begin
@@ -53,6 +49,10 @@ end)
5349
set_optimizer(model, HiGHS.Optimizer)
5450
set_silent(model)
5551

52+
# data
53+
X = reshape([1 1], (2, 1)) .|> Float32
54+
Y = Dict=> [10, 20] .|> Float32)
55+
5656
# forecast model
5757
nn = Chain(Dense(1 => 1; bias=false))
5858
ApplicationDrivenLearning.set_forecast_model(model, nn)
@@ -89,13 +89,6 @@ We have to include a solver for solving the optimization models. In this case, w
8989
using HiGHS
9090
```
9191

92-
As explained, the data used to train the model is very limited, composed of only two samples of energy demand. Values of one are used as input data, without adding any real additional information to the model. Both `X` and `Y` values are transformed to `Float32` type to match Flux parameters.
93-
94-
```julia
95-
X = reshape([1 1], (2, 1)) .|> Float32
96-
Y = reshape([10 20], (2, 1)) .|> Float32
97-
```
98-
9992
Just like regular JuMP, ApplicationDrivenLearning has a `Model` function to initialize an empty model. After initializing, we can declare the policy and forecast variables.
10093

10194
- Policy variables represent decision variables that should be maintained from the `Plan` to the `Assess` model.
@@ -148,6 +141,14 @@ set_optimizer(model, HiGHS.Optimizer)
148141
set_silent(model)
149142
```
150143

144+
As explained, the data used to train the model is very limited, composed of only two samples of energy demand. Values of one are used as input data, without adding any real additional information to the model. `X` is a matrix representing input values and the dictionary `Y` maps the forecast variable `θ` to numerical values to be used. Both `X` and `Y` values are transformed to `Float32` type to match Flux parameters.
145+
146+
```julia
147+
X = reshape([1 1], (2, 1)) .|> Float32
148+
Y = Dict=> [10, 20] .|> Float32)
149+
```
150+
151+
151152
A simple forecast model with only one parameter can be defined as a `Flux.Dense` layer with just 1 weight and no bias. We can associate the predictive model with our ApplicationDrivenLearning model only if its output size matches the number of declared forecast variables.
152153

153154
```julia

0 commit comments

Comments
 (0)