Skip to content
Merged
1 change: 0 additions & 1 deletion .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# See https://domluna.github.io/JuliaFormatter.jl/stable/ for a list of options
style = "blue"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
/Manifest*.toml
/docs/Manifest*.toml
/docs/build/
tensorboard_logs
.vscode
Manifest.toml
examples
scripts
34 changes: 24 additions & 10 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
name = "DecisionFocusedLearningAlgorithms"
uuid = "46d52364-bc3b-4fac-a992-eb1d3ef2de15"
version = "0.1.0"
authors = ["Members of JuliaDecisionFocusedLearning and contributors"]
version = "0.0.1"

[workspace]
projects = ["docs", "test"]

[deps]
DecisionFocusedLearningBenchmarks = "2fbe496a-299b-4c81-bab5-c44dfc55cf20"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
InferOpt = "4846b161-c94e-4150-8dac-c7ae193c601f"
MLUtils = "f1d291b0-491e-4a28-83b9-f70985020b54"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
ValueHistories = "98cad3c8-aec3-5f06-8e41-884608649ab7"

[compat]
DecisionFocusedLearningBenchmarks = "0.4"
DocStringExtensions = "0.9.5"
Flux = "0.16.5"
InferOpt = "0.7.1"
MLUtils = "0.4.8"
ProgressMeter = "1.11.0"
Random = "1.11.0"
Statistics = "1.11.1"
UnicodePlots = "3.8.1"
ValueHistories = "0.5.4"
julia = "1.11"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "JET", "JuliaFormatter", "Test"]
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,43 @@
[![Coverage](https://codecov.io/gh/JuliaDecisionFocusedLearning/DecisionFocusedLearningAlgorithms.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaDecisionFocusedLearning/DecisionFocusedLearningAlgorithms.jl)
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)
[![Aqua](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)

> [!WARNING]
> This package is currently under active development. The API may change in future releases.
> Please refer to the [documentation](https://JuliaDecisionFocusedLearning.github.io/DecisionFocusedLearningAlgorithms.jl/stable/) for the latest updates.

## Overview

This package provides a unified interface for training decision-focused learning algorithms that combine machine learning with combinatorial optimization. It implements several state-of-the-art algorithms for learning to predict parameters of optimization problems.

### Key Features

- **Unified Interface**: Consistent API across all algorithms via `train_policy!`
- **Policy-Centric Design**: `DFLPolicy` encapsulates statistical models and optimizers
- **Flexible Metrics**: Track custom metrics during training
- **Benchmark Integration**: Seamless integration with DecisionFocusedLearningBenchmarks.jl

### Quick Start

```julia
using DecisionFocusedLearningAlgorithms
using DecisionFocusedLearningBenchmarks

# Create a policy
benchmark = ArgmaxBenchmark()
model = generate_statistical_model(benchmark)
maximizer = generate_maximizer(benchmark)
policy = DFLPolicy(model, maximizer)

# Train with FYL algorithm
algorithm = PerturbedFenchelYoungLossImitation()
result = train_policy(algorithm, benchmark; epochs=50)
```

See the [documentation](https://JuliaDecisionFocusedLearning.github.io/DecisionFocusedLearningAlgorithms.jl/stable/) for more details.

## Available Algorithms

- **Perturbed Fenchel-Young Loss Imitation**: Differentiable imitation learning with perturbed optimization
- **AnticipativeImitation**: Imitation of anticipative solutions for dynamic problems
- **DAgger**: DAgger algorithm for dynamic problems
5 changes: 5 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[deps]
DecisionFocusedLearningAlgorithms = "46d52364-bc3b-4fac-a992-eb1d3ef2de15"
DecisionFocusedLearningBenchmarks = "2fbe496a-299b-4c81-bab5-c44dfc55cf20"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
MLUtils = "f1d291b0-491e-4a28-83b9-f70985020b54"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
38 changes: 26 additions & 12 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
using DecisionFocusedLearningAlgorithms
using Documenter
using Literate

DocMeta.setdocmeta!(
DecisionFocusedLearningAlgorithms,
:DocTestSetup,
:(using DecisionFocusedLearningAlgorithms);
recursive=true,
)
# Generate markdown files from tutorial scripts
tutorial_dir = joinpath(@__DIR__, "src", "tutorials")
tutorial_files = filter(f -> endswith(f, ".jl"), readdir(tutorial_dir))

# Convert .jl tutorial files to markdown
for file in tutorial_files
filepath = joinpath(tutorial_dir, file)
Literate.markdown(filepath, tutorial_dir; documenter=true, execute=false)
end

# Get list of generated markdown files for the docs
md_tutorial_files = [
joinpath("tutorials", replace(file, ".jl" => ".md")) for file in tutorial_files
]

makedocs(;
modules=[DecisionFocusedLearningAlgorithms],
authors="Members of JuliaDecisionFocusedLearning and contributors",
sitename="DecisionFocusedLearningAlgorithms.jl",
format=Documenter.HTML(;
canonical="https://JuliaDecisionFocusedLearning.github.io/DecisionFocusedLearningAlgorithms.jl",
edit_link="main",
assets=String[],
),
pages=["Home" => "index.md"],
format=Documenter.HTML(; size_threshold=typemax(Int)),
pages=[
"Home" => "index.md",
"Interface Guide" => "interface.md",
"Tutorials" => md_tutorial_files,
"API Reference" => "api.md",
],
)

deploydocs(;
repo="github.com/JuliaDecisionFocusedLearning/DecisionFocusedLearningAlgorithms.jl",
devbranch="main",
)

for file in md_tutorial_files
rm(joinpath(@__DIR__, "src", file))
end
6 changes: 6 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```@index
```

```@autodocs
Modules = [DecisionFocusedLearningAlgorithms]
```
41 changes: 33 additions & 8 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
```@meta
CurrentModule = DecisionFocusedLearningAlgorithms
```

# DecisionFocusedLearningAlgorithms

Documentation for [DecisionFocusedLearningAlgorithms](https://github.com/JuliaDecisionFocusedLearning/DecisionFocusedLearningAlgorithms.jl).

```@index
```
## Overview

This package provides a unified interface for training decision-focused learning algorithms that combine machine learning with combinatorial optimization. It implements several state-of-the-art algorithms for learning to predict parameters of optimization problems.

### Key Features

- **Unified Interface**: Consistent API across all algorithms via `train_policy!`
- **Policy-Centric Design**: `DFLPolicy` encapsulates statistical models and optimizers
- **Flexible Metrics**: Track custom metrics during training
- **Benchmark Integration**: Seamless integration with DecisionFocusedLearningBenchmarks.jl

```@autodocs
Modules = [DecisionFocusedLearningAlgorithms]
### Quick Start

```julia
using DecisionFocusedLearningAlgorithms
using DecisionFocusedLearningBenchmarks

# Create a policy
benchmark = ArgmaxBenchmark()
model = generate_statistical_model(benchmark)
maximizer = generate_maximizer(benchmark)
policy = DFLPolicy(model, maximizer)

# Train with FYL algorithm
algorithm = PerturbedFenchelYoungLossImitation()
result = train_policy(algorithm, benchmark; epochs=50)
```

See the [Interface Guide](interface.md) and [Tutorials](tutorials/tutorial.md) for more details.

## Available Algorithms

- **Perturbed Fenchel-Young Loss Imitation**: Differentiable imitation learning with perturbed optimization
- **AnticipativeImitation**: Imitation of anticipative solutions for dynamic problems
- **DAgger**: DAgger algorithm for dynamic problems
100 changes: 100 additions & 0 deletions docs/src/interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Algorithm Interface

This page describes the unified interface for Decision-Focused Learning algorithms provided by this package.

## Core Concepts

### DFLPolicy

The [`DFLPolicy`](@ref) is the central abstraction that encapsulates a decision-focused learning policy. It combines:
- A **statistical model** (typically a neural network) that predicts parameters from input features
- A **combinatorial optimizer** (maximizer) that solves optimization problems using the predicted parameters

```julia
policy = DFLPolicy(
Chain(Dense(input_dim => hidden_dim, relu), Dense(hidden_dim => output_dim)),
my_optimizer
)
```

### Training Interface

All algorithms in this package follow a unified training interface with two main functions:

#### Core Training Method

```julia
history = train_policy!(algorithm, policy, training_data; epochs=100, metrics=(), maximizer_kwargs=get_info)
```

**Arguments:**
- `algorithm`: An algorithm instance (e.g., `PerturbedFenchelYoungLossImitation`, `DAgger`, `AnticipativeImitation`)
- `policy::DFLPolicy`: The policy to train (contains the model and maximizer)
- `training_data`: Either a dataset of `DataSample` objects or `Environment` (depends on algorithm)
- `epochs::Int`: Number of training epochs (default: 100)
- `metrics::Tuple`: Metrics to evaluate during training (default: empty)
- `maximizer_kwargs::Function`: Function that extracts keyword arguments for the maximizer from data samples (default: `get_info`)

**Returns:**
- `history::MVHistory`: Training history containing loss values and metric evaluations

#### Benchmark Convenience Wrapper

```julia
result = train_policy(algorithm, benchmark; dataset_size=30, split_ratio=(0.3, 0.3), epochs=100, metrics=())
```

This high-level function handles all setup from a benchmark and returns a trained policy along with training history.

**Arguments:**
- `algorithm`: An algorithm instance
- `benchmark::AbstractBenchmark`: A benchmark from DecisionFocusedLearningBenchmarks.jl
- `dataset_size::Int`: Number of instances to generate
- `split_ratio::Tuple`: Train/validation/test split ratios
- `epochs::Int`: Number of training epochs
- `metrics::Tuple`: Metrics to track during training

**Returns:**
- `(; policy, history)`: Named tuple with trained policy and training history

## Metrics

Metrics allow you to track additional quantities during training.

### Built-in Metrics

#### FYLLossMetric

Evaluates Fenchel-Young loss on a validation dataset.

```julia
val_metric = FYLLossMetric(validation_data, :validation_loss)
```

#### FunctionMetric

Custom metric defined by a function.

```julia
# Simple metric (no stored data)
epoch_metric = FunctionMetric(ctx -> ctx.epoch, :epoch)

# Metric with stored data
gap_metric = FunctionMetric(:validation_gap, validation_data) do ctx, data
compute_gap(benchmark, data, ctx.policy.statistical_model, ctx.policy.maximizer)
end
```

### TrainingContext

Metrics receive a `TrainingContext` object containing:
- `policy::DFLPolicy`: The policy being trained
- `epoch::Int`: Current epoch number
- `maximizer_kwargs::Function`: Maximizer kwargs extractor
- `other_fields`: Algorithm-specific fields (e.g., `loss` for FYL)

Access policy components:
```julia
ctx.policy.statistical_model # Neural network
ctx.policy.maximizer # Combinatorial optimizer
```
67 changes: 67 additions & 0 deletions docs/src/tutorials/tutorial.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# # Basic Tutorial: Training with FYL on Argmax Benchmark
#
# This tutorial demonstrates the basic workflow for training a policy
# using the Perturbed Fenchel-Young Loss algorithm.

# ## Setup
using DecisionFocusedLearningAlgorithms
using DecisionFocusedLearningBenchmarks
using MLUtils: splitobs
using Plots

# ## Create Benchmark and Data
b = ArgmaxBenchmark()
dataset = generate_dataset(b, 100)
train_data, val_data, test_data = splitobs(dataset; at=(0.3, 0.3, 0.4))

# ## Create Policy
model = generate_statistical_model(b; seed=0)
maximizer = generate_maximizer(b)
policy = DFLPolicy(model, maximizer)

# ## Configure Algorithm
algorithm = PerturbedFenchelYoungLossImitation(;
nb_samples=10, ε=0.1, threaded=true, seed=0
)

# ## Define Metrics to track during training
validation_loss_metric = FYLLossMetric(val_data, :validation_loss)

val_gap_metric = FunctionMetric(:val_gap, val_data) do ctx, data
compute_gap(b, data, ctx.policy.statistical_model, ctx.policy.maximizer)
end

test_gap_metric = FunctionMetric(:test_gap, test_data) do ctx, data
compute_gap(b, data, ctx.policy.statistical_model, ctx.policy.maximizer)
end

metrics = (validation_loss_metric, val_gap_metric, test_gap_metric)

# ## Train the Policy
history = train_policy!(algorithm, policy, train_data; epochs=100, metrics=metrics)

# ## Plot Results
val_gap_epochs, val_gap_values = get(history, :val_gap)
test_gap_epochs, test_gap_values = get(history, :test_gap)

plot(
[val_gap_epochs, test_gap_epochs],
[val_gap_values, test_gap_values];
labels=["Val Gap" "Test Gap"],
xlabel="Epoch",
ylabel="Gap",
title="Gap Evolution During Training",
)

# Plot loss evolution
train_loss_epochs, train_loss_values = get(history, :training_loss)
val_loss_epochs, val_loss_values = get(history, :validation_loss)

plot(
[train_loss_epochs, val_loss_epochs],
[train_loss_values, val_loss_values];
labels=["Training Loss" "Validation Loss"],
xlabel="Epoch",
ylabel="Loss",
title="Loss Evolution During Training",
)
Loading
Loading