Flux.jl

repository·master·Indexed 26 days ago

https://github.com/fluxml/flux.jl

A 100% pure-Julia machine learning stack providing lightweight abstractions for GPU and Automatic Differentiation (AD) support. Flux allows model definition using standard Julia closures or built-in layers via `Chain`, including `Dense`, `Conv`, and `ConvTranspose`. It features tools for training loops via `Flux.train!` and `Flux.withgradient`, optimizer state initialization with `Flux.setup`, and utilities for data handling like `Flux.DataLoader` and `onehotbatch`.

Tokens
28K
Snippets
84
Records
171
Agent score
89%

What's inside Flux.jl

  1. Quickstart: Train a Neural Network in Flux

    master

    This guide demonstrates how to build, train, and evaluate a multi-layer perceptron using Flux. It covers data generation, model definition with Chain, GPU acceleration, data loading with DataLoader, and the training loop using withgradient and update!.

    # Install everything, including CUDA, and load packages:
    using Pkg; Pkg.add(["Flux", "CUDA", "cuDNN", "ProgressMeter"])
    using Flux, Statistics, ProgressMeter
    using CUDA  # optional
    device = gpu_device()  # function to move data and model to the GPU
    
    # Generate some data for the XOR problem: vectors of length 2, as columns of a matrix:
    noisy = rand(Float32, 2, 1000)                                    # 2×1000 Matrix{Float32}
    truth = [xor(col[1]>0.5, col[2]>0.5) for col in eachcol(noisy)]   # 1000-element Vector{Bool}
    
    # Define our model, a multi-layer perceptron with one hidden layer of size 3:
    model = Chain(
        Dense(2 => 3, tanh),      # activation function inside layer
        BatchNorm(3),
        Dense(3 => 2)) |> device  # move model to GPU, if one is available
    
    # The model encapsulates parameters, randomly initialised. Its initial output is:
    out1 = model(noisy |> device)    # 2×1000 Matrix{Float32}, or CuArray{Float32}
    probs1 = softmax(out1) |> cpu    # normalise to get probabilities (and move off GPU)
    
    # To train the model, we use batches of 64 samples, and one-hot encoding:
    target = Flux.onehotbatch(truth, [true, false])                   # 2×1000 OneHotMatrix
    loader = Flux.DataLoader((noisy, target), batchsize=64, shuffle=true);
    
    opt_state = Flux.setup(Flux.Adam(0.01), model)  # will store optimiser momentum, etc.
    
    # Training loop, using the whole data set 1000 times:
    losses = []
    @showprogress for epoch in 1:1_000
        for xy_cpu in loader
            # Unpack batch of data, and move to GPU:
            x, y = xy_cpu |> device
            loss, grads = Flux.withgradient(model) do m
                # Evaluate model and loss inside gradient context:
                y_hat = m(x)
                Flux.logitcrossentropy(y_hat, y)
            end
            Flux.update!(opt_state, model, grads[1])
            push!(losses, loss)  # logging, outside gradient context
        end
    end
    
    opt_state # parameters, momenta and output have all changed
    
    out2 = model(noisy |> device)         # first row is prob. of true, second row p(false)
    probs2 = softmax(out2) |> cpu         # normalise to get probabilities
    mean((probs2[1,:] .> 0.5) .== truth)  # accuracy 94% so far!
  2. Quickstart with Flux.jl

    master

    Flux is a pure-Julia machine learning library that provides lightweight abstractions for GPU and Automatic Differentiation (AD) support. You can define models using standard Julia closures (parameterized functions) or by composing built-in layers using Chain.

    To train a model, use Flux.setup to initialize the optimizer state and Flux.train! to execute the training loop.

    using Flux
    data = [(x, 2x-x^3) for x in -2:0.1f0:2]
    
    model = let
      w, b, v = (randn(Float32, 23) for _ in 1:3)  # parameters
      x -> sum(v .* tanh.(w*x .+ b))               # callable
    end
    # model = Chain(vcat, Dense(1 => 23, tanh), Dense(23 => 1, bias=false), only)
    
    opt_state = Flux.setup(Adam(), model)
    for epoch in 1:100
      Flux.train!((m,x,y) -> (m(x) - y)^2, model, data, opt_state)
    end
    
    using Plots
    plot(x -> 2x-x^3, -2, 2, label="truth")
    scatter!(model, -2:0.1f0:2, label="learned")
  3. Prepare mini-batches for training

    master

    When training, images and labels should be grouped into mini-batches. For image data, this typically involves creating a 4D array of shape (height, width, channels, batch_size). Labels should be converted to a one-hot encoded format using Flux.onehotbatch.

    function make_minibatch(X, Y, idxs)
       X_batch = Array{Float32}(undef, size(X)[1:end-1]..., 1, length(idxs))
       for i in 1:length(idxs)
           X_batch[:, :, :, i] = Float32.(X[:,:,idxs[i]])
       end
       Y_batch = onehotbatch(Y[idxs], 0:9)
       return (X_batch, Y_batch)
    end
  4. Initialize and use Optimisation Rules

    master

    Optimisation rules (like Descent, Momentum, or Adam) define how parameters are adjusted.

    1. Setup: Always call Flux.setup(rule, model) before training. This creates the necessary state (e.g., momentum buffers) for the specific model architecture.
    2. Update: Use Flux.update!(opt_state, model, grads[1]) to apply the rule to the model parameters using the computed gradients.
    # Initialise momentum
    opt_state = Flux.setup(Momentum(0.01, 0.9), model)
    
    for data in train_set
      grads = Flux.gradient(model) do m
          loss(m(data[1]), data[2])
      end
    
      # Update both model parameters and optimiser state
      Flux.update!(opt_state, model, grads[1])
    end
  5. Select and move models to specific GPU devices

    master

    You can manage specific GPU devices using gpu_device(id). Note that while CUDA.devices() uses 0-based indexing, gpu_device expects 1-based indexing. Once a device handle is obtained, you can move models or arrays to that device using the |> operator.

    Note: Data movement across different devices is currently only supported for CUDA and AMDGPU backends; it is not supported for Metal.jl.

    using Flux, CUDA;
    
    # List devices (0-indexed)
    CUDA.devices()
    
    # Select device 0 using 1-based indexing
    device0 = gpu_device(1)
    
    # Move a model to the device
    dense_model = Dense(2 => 3)
    dense_model = dense_model |> device0
    
    # Verify the device
    CUDA.device(dense_model.weight)
  6. Load and save model parameters

    master

    You can persist and restore model weights using Flux.params in conjunction with the BSON package.

    To save: Use BSON.@save to store the parameters extracted via params(model). It is recommended to move parameters to the CPU using cpu.(params(model)) before saving if you are training on a GPU.

    To load:

    1. Reconstruct the model architecture with the same dimensions used during training.
    2. Load the saved parameters using BSON.@load.
    3. Apply the loaded parameters to the model instance using Flux.loadparams!(model, loaded_params).
  7. Encapsulate parameters in Flux models

    master

    Flux models are parameterized functions. While parameters can be global variables or explicit arguments, Flux's preferred pattern is to encapsulate parameters within the function itself using closures or struct instances. This encapsulation allows for easy function composition using the Julia composition operator .

    Any callable struct that stores parameters can act as a valid Flux model.

    struct Poly3{T}
        θ3::T
    end
    (p::Poly3)(x::Real) = evalpoly(x, p.θ3)
    
    poly3s = Poly3([10, 1, 0.1])
    # poly3s is now a valid Flux model
  8. Checkpoint training progress with model and optimizer state

    master

    To resume training after an interruption, you should save both the model state and the optimizer state. This allows you to restart training exactly where you left off.

    Use Flux.state(model) for the model parameters and the state object returned by Flux.setup for the optimizer.

    using Flux
    using JLD2
    
    model = Chain(Dense(10 => 5, relu), Dense(5 => 2))
    opt_state = Flux.setup(AdamW(), model)
    
    # ... during training loop ...
    
    # Save both to a checkpoint file
    model_state = Flux.state(model)
    jldsave("checkpoint_epoch=42.jld2"; model_state, opt_state)
  9. Compose models with Flux.Chain

    master

    While you can compose functions using the Julia composition operator , Flux provides Flux.Chain for a more structured approach.

    Key differences between Chain and standard function composition:

    • Chain works left-to-right (the reverse of ).
    • Layers are stored in a tuple, allowing access via model.layers[i].weight.
    • Built-in layers like Dense use glorot_uniform initialization by default and include performance optimizations for batch processing and memory reuse.
  10. Manage training and inference modes for Normalisation layers

    master

    Many normalisation layers (like BatchNorm) behave differently during training versus inference (testing).

    Flux attempts to automatically detect the mode, which works best with Zygote. If you are using other automatic differentiation packages (like Tracker, Yota, or ForwardDiff), automatic detection may fail.

    You can manually switch the mode for an entire model using Flux.trainmode! and Flux.testmode!.

  11. Load and prepare datasets with DataLoader

    master

    To train neural networks, you typically use DataLoader to handle minibatch learning. This involves splitting your data into training and validation sets and wrapping them in a DataLoader with a specified batchsize and shuffle preference. For classification tasks, use onehotbatch to convert labels into a format suitable for loss functions like logitcrossentropy.

    using Flux
    using MLUtils: splitobs, numobs
    using MLDatasets: CIFAR10
    
    # Load data
    train_x, train_y = CIFAR10(:train)[:]
    labels = onehotbatch(train_y, 0:9)
    
    # Split into training and validation sets
    trainset, valset = splitobs((train_x, labels), at = 45000)
    
    # Create DataLoaders
    trainloader = DataLoader(trainset, batchsize = 1000, shuffle = true)
    valloader = DataLoader(trainset, batchsize = 1000)