Skip to content

Commit 94eeb79

Browse files
authored
Merge pull request #16 from JuliaDecisionFocusedLearning/argmax-ranking
Implement argmax and ranking benchmarks
2 parents de6213c + 16e34a0 commit 94eeb79

File tree

10 files changed

+272
-3
lines changed

10 files changed

+272
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/docs/src/index.md
33
data
44
scripts
5+
.DS_Store
56

67
# Files generated by invoking Julia with --code-coverage
78
*.jl.cov

docs/src/api/argmax.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Argmax
2+
3+
## Public
4+
5+
```@autodocs
6+
Modules = [DecisionFocusedLearningBenchmarks.Argmax]
7+
Private = false
8+
```
9+
10+
## Private
11+
12+
```@autodocs
13+
Modules = [DecisionFocusedLearningBenchmarks.Argmax]
14+
Public = false
15+
```

docs/src/api/ranking.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Ranking
2+
3+
## Public
4+
5+
```@autodocs
6+
Modules = [DecisionFocusedLearningBenchmarks.Ranking]
7+
Private = false
8+
```
9+
10+
## Private
11+
12+
```@autodocs
13+
Modules = [DecisionFocusedLearningBenchmarks.Ranking]
14+
Public = false
15+
```

docs/src/benchmarks/argmax.md

Whitespace-only changes.

docs/src/benchmarks/ranking.md

Whitespace-only changes.

src/Argmax/Argmax.jl

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
module Argmax
2+
3+
using ..Utils
4+
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES
5+
using Flux: Chain, Dense
6+
using Random
7+
8+
"""
9+
$TYPEDEF
10+
11+
Benchmark problem with an argmax as the CO algorithm.
12+
13+
# Fields
14+
$TYPEDFIELDS
15+
"""
16+
struct ArgmaxBenchmark <: AbstractBenchmark
17+
"iinstances dimension, total number of classes"
18+
instance_dim::Int
19+
"number of features"
20+
nb_features::Int
21+
end
22+
23+
function Base.show(io::IO, bench::ArgmaxBenchmark)
24+
(; instance_dim, nb_features) = bench
25+
return print(
26+
io, "ArgmaxBenchmark(instance_dim=$instance_dim, nb_features=$nb_features)"
27+
)
28+
end
29+
30+
function ArgmaxBenchmark(; instance_dim::Int=10, nb_features::Int=5)
31+
return ArgmaxBenchmark(instance_dim, nb_features)
32+
end
33+
34+
"""
35+
$TYPEDSIGNATURES
36+
37+
One-hot encoding of the argmax function.
38+
"""
39+
function one_hot_argmax(z::AbstractVector{R}; kwargs...) where {R<:Real}
40+
e = zeros(R, length(z))
41+
e[argmax(z)] = one(R)
42+
return e
43+
end
44+
45+
"""
46+
$TYPEDSIGNATURES
47+
48+
Return a top k maximizer.
49+
"""
50+
function Utils.generate_maximizer(bench::ArgmaxBenchmark)
51+
return one_hot_argmax
52+
end
53+
54+
"""
55+
$TYPEDSIGNATURES
56+
57+
Generate a dataset of labeled instances for the subset selection problem.
58+
The mapping between features and cost is identity.
59+
"""
60+
function Utils.generate_dataset(bench::ArgmaxBenchmark, dataset_size::Int=10; seed::Int=0)
61+
(; instance_dim, nb_features) = bench
62+
rng = MersenneTwister(seed)
63+
features = [randn(rng, Float32, nb_features, instance_dim) for _ in 1:dataset_size]
64+
mapping = Chain(Dense(nb_features => 1; bias=false), vec)
65+
costs = mapping.(features)
66+
solutions = one_hot_argmax.(costs)
67+
return [
68+
DataSample(; x, θ_true, y_true) for
69+
(x, θ_true, y_true) in zip(features, costs, solutions)
70+
]
71+
end
72+
73+
"""
74+
$TYPEDSIGNATURES
75+
76+
Initialize a linear model for `bench` using `Flux`.
77+
"""
78+
function Utils.generate_statistical_model(bench::ArgmaxBenchmark; seed=0)
79+
Random.seed!(seed)
80+
(; nb_features) = bench
81+
return Chain(Dense(nb_features => 1; bias=false), vec)
82+
end
83+
84+
export ArgmaxBenchmark
85+
86+
end

src/DecisionFocusedLearningBenchmarks.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ end
1919

2020
include("Utils/Utils.jl")
2121

22+
include("Argmax/Argmax.jl")
23+
include("Ranking/Ranking.jl")
24+
include("SubsetSelection/SubsetSelection.jl")
2225
include("Warcraft/Warcraft.jl")
2326
include("FixedSizeShortestPath/FixedSizeShortestPath.jl")
2427
include("PortfolioOptimization/PortfolioOptimization.jl")
25-
include("SubsetSelection/SubsetSelection.jl")
2628

2729
using .Utils
30+
using .Argmax
31+
using .Ranking
32+
using .SubsetSelection
2833
using .Warcraft
2934
using .FixedSizeShortestPath
3035
using .PortfolioOptimization
31-
using .SubsetSelection
3236

3337
# Interface
3438
export AbstractBenchmark, DataSample
@@ -39,9 +43,11 @@ export plot_data
3943
export compute_gap
4044

4145
# Export all benchmarks
46+
export ArgmaxBenchmark
47+
export RankingBenchmark
48+
export SubsetSelectionBenchmark
4249
export WarcraftBenchmark
4350
export FixedSizeShortestPathBenchmark
4451
export PortfolioOptimizationBenchmark
45-
export SubsetSelectionBenchmark
4652

4753
end # module DecisionFocusedLearningBenchmarks

src/Ranking/Ranking.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
module Ranking
2+
3+
using ..Utils
4+
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES
5+
using Flux: Chain, Dense
6+
using Random
7+
8+
"""
9+
$TYPEDEF
10+
11+
Benchmark problem with an argmax as the CO algorithm.
12+
13+
# Fields
14+
$TYPEDFIELDS
15+
"""
16+
struct RankingBenchmark <: AbstractBenchmark
17+
"iinstances dimension, total number of classes"
18+
instance_dim::Int
19+
"number of features"
20+
nb_features::Int
21+
end
22+
23+
function Base.show(io::IO, bench::RankingBenchmark)
24+
(; instance_dim, nb_features) = bench
25+
return print(
26+
io, "RankingBenchmark(instance_dim=$instance_dim, nb_features=$nb_features)"
27+
)
28+
end
29+
30+
function RankingBenchmark(; instance_dim::Int=10, nb_features::Int=5)
31+
return RankingBenchmark(instance_dim, nb_features)
32+
end
33+
34+
"""
35+
$TYPEDSIGNATURES
36+
37+
Compute the vector `r` such that `rᵢ` is the rank of `θᵢ` in `θ`.
38+
"""
39+
function ranking::AbstractVector; rev::Bool=false, kwargs...)
40+
return invperm(sortperm(θ; rev=rev))
41+
end
42+
43+
"""
44+
$TYPEDSIGNATURES
45+
46+
Return a top k maximizer.
47+
"""
48+
function Utils.generate_maximizer(bench::RankingBenchmark)
49+
return ranking
50+
end
51+
52+
"""
53+
$TYPEDSIGNATURES
54+
55+
Generate a dataset of labeled instances for the subset selection problem.
56+
The mapping between features and cost is identity.
57+
"""
58+
function Utils.generate_dataset(bench::RankingBenchmark, dataset_size::Int=10; seed::Int=0)
59+
(; instance_dim, nb_features) = bench
60+
rng = MersenneTwister(seed)
61+
features = [randn(rng, Float32, nb_features, instance_dim) for _ in 1:dataset_size]
62+
mapping = Chain(Dense(nb_features => 1; bias=false), vec)
63+
costs = mapping.(features)
64+
solutions = ranking.(costs)
65+
return [
66+
DataSample(; x, θ_true, y_true) for
67+
(x, θ_true, y_true) in zip(features, costs, solutions)
68+
]
69+
end
70+
71+
"""
72+
$TYPEDSIGNATURES
73+
74+
Initialize a linear model for `bench` using `Flux`.
75+
"""
76+
function Utils.generate_statistical_model(bench::RankingBenchmark; seed=0)
77+
Random.seed!(seed)
78+
(; nb_features) = bench
79+
return Chain(Dense(nb_features => 1; bias=false), vec)
80+
end
81+
82+
export RankingBenchmark
83+
84+
end

test/argmax.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@testitem "Argmax" begin
2+
using DecisionFocusedLearningBenchmarks
3+
4+
instance_dim = 10
5+
nb_features = 5
6+
7+
b = ArgmaxBenchmark(; instance_dim=instance_dim, nb_features=nb_features)
8+
9+
io = IOBuffer()
10+
show(io, b)
11+
@test String(take!(io)) == "ArgmaxBenchmark(instance_dim=10, nb_features=5)"
12+
13+
dataset = generate_dataset(b, 50)
14+
model = generate_statistical_model(b)
15+
maximizer = generate_maximizer(b)
16+
17+
for (i, sample) in enumerate(dataset)
18+
(; x, θ_true, y_true) = sample
19+
@test size(x) == (nb_features, instance_dim)
20+
@test length(θ_true) == instance_dim
21+
@test length(y_true) == instance_dim
22+
@test isnothing(sample.instance)
23+
@test all(y_true .== maximizer(θ_true))
24+
25+
θ = model(x)
26+
@test length(θ) == instance_dim
27+
28+
y = maximizer(θ)
29+
@test length(y) == instance_dim
30+
end
31+
end

test/ranking.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@testitem "Ranking" begin
2+
using DecisionFocusedLearningBenchmarks
3+
4+
instance_dim = 10
5+
nb_features = 5
6+
7+
b = RankingBenchmark(; instance_dim=instance_dim, nb_features=nb_features)
8+
9+
io = IOBuffer()
10+
show(io, b)
11+
@test String(take!(io)) == "RankingBenchmark(instance_dim=10, nb_features=5)"
12+
13+
dataset = generate_dataset(b, 50)
14+
model = generate_statistical_model(b)
15+
maximizer = generate_maximizer(b)
16+
17+
for (i, sample) in enumerate(dataset)
18+
(; x, θ_true, y_true) = sample
19+
@test size(x) == (nb_features, instance_dim)
20+
@test length(θ_true) == instance_dim
21+
@test length(y_true) == instance_dim
22+
@test isnothing(sample.instance)
23+
@test all(y_true .== maximizer(θ_true))
24+
25+
θ = model(x)
26+
@test length(θ) == instance_dim
27+
28+
y = maximizer(θ)
29+
@test length(y) == instance_dim
30+
end
31+
end

0 commit comments

Comments
 (0)