-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add 7 workflow examples for MLJFlux
- Loading branch information
1 parent
01ad08e
commit d5fe2c7
Showing
35 changed files
with
15,048 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ | |
.DS_Store | ||
sandbox/ | ||
docs/build | ||
/examples/mnist/mnist_machine* | ||
/examples/mnist/mnist_machine* | ||
Manifest.toml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
docs/src/workflow examples/Basic Neural Architecture Search/Project.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[deps] | ||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" | ||
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" | ||
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" | ||
MLJ = "add582a8-e3ab-11e8-2d5e-e98b27df1bc7" | ||
MLJFlux = "094fc8d1-fd35-5302-93ea-dabda2abf845" | ||
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b" |
343 changes: 343 additions & 0 deletions
343
docs/src/workflow examples/Basic Neural Architecture Search/tuning.ipynb
Large diffs are not rendered by default.
Oops, something went wrong.
126 changes: 126 additions & 0 deletions
126
docs/src/workflow examples/Basic Neural Architecture Search/tuning.jl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# # Neural Architecture Search with MLJFlux | ||
|
||
# Neural Architecture Search is (NAS) is an instance of hyperparameter tuning concerned with tuning model hyperparameters | ||
# defining the architecture itself. Although it's typically performed with sophisticated search algorithms for efficiency, | ||
# in this example we will be using a simple random search. | ||
|
||
using Pkg #src | ||
Pkg.activate(@__DIR__); #src | ||
Pkg.instantiate(); #src | ||
|
||
# **Julia version** is assumed to be 1.10.* | ||
|
||
# ### Basic Imports | ||
|
||
using MLJ # Has MLJFlux models | ||
using Flux # For more flexibility | ||
using RDatasets: RDatasets # Dataset source | ||
using DataFrames # To view tuning results in a table | ||
|
||
# ### Loading and Splitting the Data | ||
|
||
iris = RDatasets.dataset("datasets", "iris"); | ||
y, X = unpack(iris, ==(:Species), colname -> true, rng = 123); | ||
X = Float32.(X); # To be compatible with type of network network parameters | ||
first(X, 5) | ||
|
||
|
||
|
||
# ### Instantiating the model | ||
|
||
# Now let's construct our model. This follows a similar setup the one followed in the [Quick Start](../../index.md). | ||
NeuralNetworkClassifier = @load NeuralNetworkClassifier pkg = "MLJFlux" | ||
clf = NeuralNetworkClassifier( | ||
builder = MLJFlux.MLP(; hidden = (1, 1, 1), σ = Flux.relu), | ||
optimiser = Flux.ADAM(0.01), | ||
batch_size = 8, | ||
epochs = 10, | ||
rng = 42, | ||
) | ||
|
||
|
||
# ### Generating Network Architectures | ||
# We know that the MLP builder takes a tuple of the form $(z_1, z_2, ..., z_k)$ to define a network with $k$ hidden layers and | ||
# where the ith layer has $z_i$ neurons. We will proceed by defining a function that can generate all possible networks with a | ||
# specific number of hidden layers, a minimum and maximum number of neurons per layer and increments to consider for the number of neurons. | ||
|
||
function generate_networks(; | ||
min_neurons::Int, | ||
max_neurons::Int, | ||
neuron_step::Int, | ||
num_layers::Int, | ||
) | ||
## Define the range of neurons | ||
neuron_range = min_neurons:neuron_step:max_neurons | ||
|
||
## Empty list to store the network configurations | ||
networks = Vector{Tuple{Vararg{Int, num_layers}}}() | ||
|
||
## Recursive helper function to generate all combinations of tuples | ||
function generate_tuple(current_layers, remaining_layers) | ||
if remaining_layers > 0 | ||
for n in neuron_range | ||
## current_layers =[] then current_layers=[(min_neurons)], | ||
## [(min_neurons+neuron_step)], [(min_neurons+2*neuron_step)],... | ||
## for each of these we call generate_layers again which appends | ||
## the n combinations for each one of them | ||
generate_tuple(vcat(current_layers, [n]), remaining_layers - 1) | ||
end | ||
else | ||
## in the base case, no more layers to "recurse on" | ||
## and we just append the current_layers as a tuple | ||
push!(networks, tuple(current_layers...)) | ||
end | ||
end | ||
|
||
## Generate networks for the given number of layers | ||
generate_tuple([], num_layers) | ||
|
||
return networks | ||
end | ||
|
||
|
||
# Now let's generate an array of all possible neural networks with three hidden layers and number of neurons per layer ∈ [1,64] with a step of 4 | ||
networks_space = | ||
generate_networks(min_neurons = 1, max_neurons = 64, neuron_step = 4, num_layers = 3) | ||
|
||
networks_space[1:5] | ||
|
||
# ### Wrapping the Model for Tuning | ||
|
||
|
||
# Let's use this array to define the range of hyperparameters and pass it along with the model to the `TunedModel` constructor. | ||
r1 = range(clf, :(builder.hidden), values = networks_space) | ||
|
||
tuned_clf = TunedModel( | ||
model = clf, | ||
tuning = RandomSearch(), | ||
resampling = CV(nfolds = 4, rng = 42), | ||
range = [r1], | ||
measure = cross_entropy, | ||
n = 100, # searching over 100 random samples are enough | ||
); | ||
|
||
# ### Performing the Search | ||
|
||
# Similar to the last workflow example, all we need now is to fit our model and the search will take place automatically: | ||
mach = machine(tuned_clf, X, y); | ||
fit!(mach, verbosity = 0); | ||
fitted_params(mach).best_model | ||
|
||
# ### Analyzing the Search Results | ||
|
||
# Let's analyze the search results by converting the history array to a dataframe and viewing it: | ||
history = report(mach).history | ||
history_df = DataFrame( | ||
mlp = [x[:model].builder for x in history], | ||
measurement = [x[:measurement][1] for x in history], | ||
) | ||
first(sort!(history_df, [order(:measurement)]), 10) | ||
|
||
|
||
|
||
|
||
using Literate #src | ||
Literate.markdown(@__FILE__, @__DIR__, execute = false) #src | ||
Literate.notebook(@__FILE__, @__DIR__, execute = true) #src |
141 changes: 141 additions & 0 deletions
141
docs/src/workflow examples/Basic Neural Architecture Search/tuning.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
```@meta | ||
EditURL = "tuning.jl" | ||
``` | ||
|
||
# Neural Architecture Search with MLJFlux | ||
|
||
Neural Architecture Search is (NAS) is an instance of hyperparameter tuning concerned with tuning model hyperparameters | ||
defining the architecture itself. Although it's typically performed with sophisticated search algorithms for efficiency, | ||
in this example we will be using a simple random search. | ||
|
||
**Julia version** is assumed to be 1.10.* | ||
|
||
### Basic Imports | ||
|
||
````@example tuning | ||
using MLJ # Has MLJFlux models | ||
using Flux # For more flexibility | ||
using RDatasets: RDatasets # Dataset source | ||
using DataFrames # To view tuning results in a table | ||
```` | ||
|
||
### Loading and Splitting the Data | ||
|
||
````@example tuning | ||
iris = RDatasets.dataset("datasets", "iris"); | ||
y, X = unpack(iris, ==(:Species), colname -> true, rng = 123); | ||
X = Float32.(X); # To be compatible with type of network network parameters | ||
first(X, 5) | ||
```` | ||
|
||
### Instantiating the model | ||
|
||
Now let's construct our model. This follows a similar setup the one followed in the [Quick Start](../../index.md). | ||
|
||
````@example tuning | ||
NeuralNetworkClassifier = @load NeuralNetworkClassifier pkg = "MLJFlux" | ||
clf = NeuralNetworkClassifier( | ||
builder = MLJFlux.MLP(; hidden = (1, 1, 1), σ = Flux.relu), | ||
optimiser = Flux.ADAM(0.01), | ||
batch_size = 8, | ||
epochs = 10, | ||
rng = 42, | ||
) | ||
```` | ||
|
||
### Generating Network Architectures | ||
We know that the MLP builder takes a tuple of the form $(z_1, z_2, ..., z_k)$ to define a network with $k$ hidden layers and | ||
where the ith layer has $z_i$ neurons. We will proceed by defining a function that can generate all possible networks with a | ||
specific number of hidden layers, a minimum and maximum number of neurons per layer and increments to consider for the number of neurons. | ||
|
||
````@example tuning | ||
function generate_networks(; | ||
min_neurons::Int, | ||
max_neurons::Int, | ||
neuron_step::Int, | ||
num_layers::Int, | ||
) | ||
# Define the range of neurons | ||
neuron_range = min_neurons:neuron_step:max_neurons | ||
# Empty list to store the network configurations | ||
networks = Vector{Tuple{Vararg{Int, num_layers}}}() | ||
# Recursive helper function to generate all combinations of tuples | ||
function generate_tuple(current_layers, remaining_layers) | ||
if remaining_layers > 0 | ||
for n in neuron_range | ||
# current_layers =[] then current_layers=[(min_neurons)], | ||
# [(min_neurons+neuron_step)], [(min_neurons+2*neuron_step)],... | ||
# for each of these we call generate_layers again which appends | ||
# the n combinations for each one of them | ||
generate_tuple(vcat(current_layers, [n]), remaining_layers - 1) | ||
end | ||
else | ||
# in the base case, no more layers to "recurse on" | ||
# and we just append the current_layers as a tuple | ||
push!(networks, tuple(current_layers...)) | ||
end | ||
end | ||
# Generate networks for the given number of layers | ||
generate_tuple([], num_layers) | ||
return networks | ||
end | ||
```` | ||
|
||
Now let's generate an array of all possible neural networks with three hidden layers and number of neurons per layer ∈ [1,64] with a step of 4 | ||
|
||
````@example tuning | ||
networks_space = | ||
generate_networks(min_neurons = 1, max_neurons = 64, neuron_step = 4, num_layers = 3) | ||
networks_space[1:5] | ||
```` | ||
|
||
### Wrapping the Model for Tuning | ||
|
||
Let's use this array to define the range of hyperparameters and pass it along with the model to the `TunedModel` constructor. | ||
|
||
````@example tuning | ||
r1 = range(clf, :(builder.hidden), values = networks_space) | ||
tuned_clf = TunedModel( | ||
model = clf, | ||
tuning = RandomSearch(), | ||
resampling = CV(nfolds = 4, rng = 42), | ||
range = [r1], | ||
measure = cross_entropy, | ||
n = 100, # searching over 100 random samples are enough | ||
); | ||
nothing #hide | ||
```` | ||
|
||
### Performing the Search | ||
|
||
Similar to the last workflow example, all we need now is to fit our model and the search will take place automatically: | ||
|
||
````@example tuning | ||
mach = machine(tuned_clf, X, y); | ||
fit!(mach, verbosity = 0); | ||
fitted_params(mach).best_model | ||
```` | ||
|
||
### Analyzing the Search Results | ||
|
||
Let's analyze the search results by converting the history array to a dataframe and viewing it: | ||
|
||
````@example tuning | ||
history = report(mach).history | ||
history_df = DataFrame( | ||
mlp = [x[:model].builder for x in history], | ||
measurement = [x[:measurement][1] for x in history], | ||
) | ||
first(sort!(history_df, [order(:measurement)]), 10) | ||
```` | ||
|
||
--- | ||
|
||
*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[deps] | ||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" | ||
DecisionTree = "7806a523-6efd-50cb-b5f6-3fa6f1930dbb" | ||
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" | ||
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" | ||
MLJ = "add582a8-e3ab-11e8-2d5e-e98b27df1bc7" | ||
MLJDecisionTreeInterface = "c6f25543-311c-4c74-83dc-3ea6d1015661" | ||
MLJFlux = "094fc8d1-fd35-5302-93ea-dabda2abf845" | ||
MLJMultivariateStatsInterface = "1b6a4a23-ba22-4f51-9698-8599985d3728" | ||
MLJXGBoostInterface = "54119dfa-1dab-4055-a167-80440f4f7a91" | ||
MultivariateStats = "6f286f6a-111f-5878-ab1e-185364afe411" | ||
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" | ||
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b" |
Oops, something went wrong.