| name | r-deeplearning |
| description | Deep learning in R using torch and keras3 for neural networks across domains. Use when mentions "deep learning em R", "deep learning in R", "torch", "torch em R", "torch for R", "keras3", "keras3 em R", "neural network", "rede neural", "redes neurais", "CNN", "convolutional neural network", "rede convolucional", "RNN", "LSTM", "GRU", "recurrent neural network", "transformer", "GPU em R", "GPU in R", "train neural network", "treinar rede neural", "image classification", "classificação de imagens", "text classification", "classificação de texto", "sound classification", "audio classification", "classificação de sons", "audio deep learning", "deep learning com áudio", "spectrogram CNN", "CRNN", "mel-spectrogram", "torchaudio", "luz package", "build neural network", "construir rede neural", "transfer learning", "fine-tuning", "pretrained model", "modelo pré-treinado", or building/training neural network models in R for vision, NLP, audio, time series, or tabular data. |
| version | 1.0.0 |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
Deep Learning in R - torch & keras3
Expert guidance for deep learning in R using torch and keras3 frameworks across multiple domains: computer vision, NLP, audio processing, time series, and tabular data.
Overview
This skill provides comprehensive knowledge for building, training, and deploying neural networks in R using:
- torch: Low-level, flexible deep learning framework (PyTorch port to R)
- keras3: High-level API with multiple backends (TensorFlow, JAX, torch)
- torchaudio: Audio-specific transformations and datasets
- luz: High-level training interface for torch
Covers complete workflows from data preparation through training, evaluation, and deployment with special emphasis on audio deep learning for bioacoustics.
Core Concepts
torch vs keras3
| Aspect | torch | keras3 |
|---|
| Level | Low-level, explicit control | High-level, concise API |
| Flexibility | Maximum (custom everything) | Moderate (configurable) |
| Learning curve | Steeper | Gentler |
| Training | Manual loops or luz | Built-in fit() |
| Backend | Pure torch | TensorFlow/JAX/torch |
| Best for | Research, custom architectures | Production, standard models |
When to use torch:
- Custom loss functions or training procedures
- Research and experimentation
- Need full control over training loop
- Implementing novel architectures
When to use keras3:
- Standard architectures (ResNet, LSTM, etc.)
- Rapid prototyping
- Prefer high-level API
- Deploying to TensorFlow Serving
Framework Fundamentals
torch Basics
library(torch)
x <- torch_tensor(c(1, 2, 3, 4, 5, 6))
x <- x$view(c(2, 3))
device <- if (cuda_is_available()) "cuda" else "cpu"
x <- x$to(device = device)
x <- torch_tensor(c(1.0, 2.0, 3.0), requires_grad = TRUE)
y <- x$pow(2)$sum()
y$backward()
print(x$grad)
model <- nn_module(
"SimpleNet",
initialize = function(input_dim, hidden_dim, output_dim) {
self$fc1 <- nn_linear(input_dim, hidden_dim)
self$fc2 <- nn_linear(hidden_dim, output_dim)
self$relu <- nn_relu()
},
forward = function(x) {
x |>
self$fc1() |>
self$relu() |>
self$fc2()
}
)
net <- model(input_dim = 784, hidden_dim = 128, output_dim = 10)
net <- net$to(device = device)
optimizer <- optim_adam(net$parameters, lr = 0.001)
criterion <- nn_cross_entropy_loss()
net$train()
optimizer$zero_grad()
output <- net(input)
loss <- criterion(output, target)
loss$backward()
optimizer$step()
net$eval()
with_no_grad({
predictions <- net(test_input)
})
keras3 Basics
library(keras3)
model <- keras_model_sequential(input_shape = c(784)) |>
layer_dense(units = 128, activation = "relu") |>
layer_dropout(rate = 0.2) |>
layer_dense(units = 10, activation = "softmax")
inputs <- layer_input(shape = c(784))
outputs <- inputs |>
layer_dense(units = 128, activation = "relu") |>
layer_dropout(rate = 0.2) |>
layer_dense(units = 10, activation = "softmax")
model <- keras_model(inputs, outputs)
model |> compile(
optimizer = optimizer_adam(learning_rate = 0.001),
loss = loss_categorical_crossentropy(),
metrics = list(metric_categorical_accuracy())
)
history <- model |> fit(
x_train, y_train,
epochs = 20,
batch_size = 32,
validation_split = 0.2,
callbacks = list(
callback_early_stopping(patience = 3, restore_best_weights = TRUE),
callback_reduce_lr_on_plateau(factor = 0.5, patience = 2)
)
)
model |> evaluate(x_test, y_test)
predictions <- model |> predict(x_new)
luz (torch High-Level Training)
library(luz)
fitted <- net |>
setup(
loss = nn_cross_entropy_loss(),
optimizer = optim_adam,
metrics = list(
luz_metric_accuracy()
)
) |>
set_hparams(input_dim = 784, hidden_dim = 128, output_dim = 10) |>
set_opt_hparams(lr = 0.001) |>
fit(
train_dataloader,
epochs = 20,
valid_data = valid_dataloader,
callbacks = list(
luz_callback_early_stopping(patience = 3),
luz_callback_lr_scheduler(torch::lr_step, step_size = 5, gamma = 0.5)
)
)
Domain-Specific Patterns
Computer Vision (CNNs)
library(torch)
cnn_model <- nn_module(
"CNN",
initialize = function(num_classes = 10) {
self$conv1 <- nn_conv2d(in_channels = 3, out_channels = 32, kernel_size = 3, padding = 1)
self$conv2 <- nn_conv2d(32, 64, 3, padding = 1)
self$conv3 <- nn_conv2d(64, 128, 3, padding = 1)
self$pool <- nn_max_pool2d(kernel_size = 2, stride = 2)
self$dropout <- nn_dropout(0.3)
self$fc1 <- nn_linear(128 * 4 * 4, 512)
self$fc2 <- nn_linear(512, num_classes)
self$relu <- nn_relu()
},
forward = function(x) {
x <- self$conv1(x) |> self$relu() |> self$pool()
x <- self$conv2(x) |> self$relu() |> self$pool()
x <- self$conv3(x) |> self$relu() |> self$pool()
x <- x$view(c(x$size(1), -1))
x <- self$fc1(x) |> self$relu() |> self$dropout()
x <- self$fc2(x)
return(x)
}
)
augmentation_transform <- function(x) {
x |>
transform_random_horizontal_flip(p = 0.5) |>
transform_random_rotation(degrees = 15) |>
transform_color_jitter(brightness = 0.2, contrast = 0.2)
}
library(keras3)
base_model <- application_resnet50(
weights = "imagenet",
include_top = FALSE,
input_shape = c(224, 224, 3)
)
base_model$trainable <- FALSE
inputs <- layer_input(shape = c(224, 224, 3))
outputs <- inputs |>
base_model() |>
layer_global_average_pooling_2d() |>
layer_dense(256, activation = "relu") |>
layer_dropout(0.3) |>
layer_dense(num_classes, activation = "softmax")
model <- keras_model(inputs, outputs)
model |> compile(
optimizer = optimizer_adam(1e-3),
loss = loss_categorical_crossentropy(),
metrics = "accuracy"
)
model |> fit(train_data, epochs = 10)
base_model$trainable <- TRUE
model |> compile(
optimizer = optimizer_adam(1e-5),
loss = loss_categorical_crossentropy(),
metrics = "accuracy"
)
model |> fit(train_data, epochs = 10)
Natural Language Processing (RNNs/LSTMs)
library(torch)
lstm_model <- nn_module(
"TextLSTM",
initialize = function(vocab_size, embedding_dim = 128, hidden_dim = 256, num_classes = 2) {
self$embedding <- nn_embedding(vocab_size, embedding_dim)
self$lstm <- nn_lstm(embedding_dim, hidden_dim, num_layers = 2,
dropout = 0.3, batch_first = TRUE)
self$fc <- nn_linear(hidden_dim, num_classes)
self$dropout <- nn_dropout(0.3)
},
forward = function(x) {
embedded <- self$embedding(x)
lstm_out <- self$lstm(embedded)
last_hidden <- lstm_out[[2]][[1]][2, , ]
out <- last_hidden |> self$dropout() |> self$fc()
return(out)
}
)
library(keras3)
model <- keras_model_sequential() |>
layer_embedding(input_dim = vocab_size, output_dim = 128, input_length = max_len) |>
layer_lstm(units = 64, return_sequences = TRUE) |>
bidirectional(layer_lstm(units = 64)) |>
layer_dense(64, activation = "relu") |>
layer_dropout(0.3) |>
layer_dense(num_classes, activation = "softmax")
tokenizer <- text_tokenizer(num_words = 10000)
tokenizer |> fit_text_tokenizer(texts)
sequences <- texts_to_sequences(tokenizer, texts)
x_train <- pad_sequences(sequences, maxlen = max_len)
Time Series (RNNs/ConvLSTM)
library(torch)
gru_model <- nn_module(
"TimeSeriesGRU",
initialize = function(input_dim, hidden_dim = 64, num_layers = 2, output_steps = 1) {
self$gru <- nn_gru(input_dim, hidden_dim, num_layers = num_layers,
dropout = 0.2, batch_first = TRUE)
self$fc <- nn_linear(hidden_dim, output_steps)
},
forward = function(x) {
gru_out <- self$gru(x)
last_output <- gru_out[[1]][, -1, ]
predictions <- self$fc(last_output)
return(predictions)
}
)
cnn_ts_model <- nn_module(
"TimeSeriesCNN",
initialize = function(input_dim, num_filters = c(32, 64, 128), output_steps = 1) {
self$conv1 <- nn_conv1d(input_dim, num_filters[1], kernel_size = 3, padding = 1)
self$conv2 <- nn_conv1d(num_filters[1], num_filters[2], 3, padding = 1)
self$conv3 <- nn_conv1d(num_filters[2], num_filters[3], 3, padding = 1)
self$pool <- nn_adaptive_avg_pool1d(1)
self$fc <- nn_linear(num_filters[3], output_steps)
self$relu <- nn_relu()
},
forward = function(x) {
x <- self$conv1(x) |> self$relu()
x <- self$conv2(x) |> self$relu()
x <- self$conv3(x) |> self$relu()
x <- self$pool(x)$squeeze(-1)
x <- self$fc(x)
return(x)
}
)
Tabular Data (Dense Networks)
library(torch)
tabular_model <- nn_module(
"TabularNN",
initialize = function(num_numeric, cat_dims, embedding_dims, hidden_dims = c(256, 128, 64)) {
self$embeddings <- nn_module_list(lapply(1:length(cat_dims), function(i) {
nn_embedding(cat_dims[i], embedding_dims[i])
}))
total_dim <- num_numeric + sum(embedding_dims)
self$fc1 <- nn_linear(total_dim, hidden_dims[1])
self$fc2 <- nn_linear(hidden_dims[1], hidden_dims[2])
self$fc3 <- nn_linear(hidden_dims[2], hidden_dims[3])
self$output <- nn_linear(hidden_dims[3], 1)
self$batch_norm1 <- nn_batch_norm1d(hidden_dims[1])
self$batch_norm2 <- nn_batch_norm1d(hidden_dims[2])
self$dropout <- nn_dropout(0.3)
self$relu <- nn_relu()
},
forward = function(x_numeric, x_categorical) {
embedded <- lapply(1:length(x_categorical), function(i) {
self$embeddings[[i]](x_categorical[[i]])
})
embedded_cat <- torch_cat(embedded, dim = 2)
x <- torch_cat(list(x_numeric, embedded_cat), dim = 2)
x <- self$fc1(x) |> self$batch_norm1() |> self$relu() |> self$dropout()
x <- self$fc2(x) |> self$batch_norm2() |> self$relu() |> self$dropout()
x <- self$fc3(x) |> self$relu()
x <- self$output(x)
return(x)
}
)
Audio Deep Learning (Detailed)
Audio Preprocessing Pipeline
library(torch)
library(torchaudio)
library(tuneR)
preprocess_audio <- function(audio_path, target_sr = 22050, duration = 5.0) {
audio <- readWave(audio_path)
if (audio@stereo) {
audio <- mono(audio, which = "both")
}
if (audio@samp.rate != target_sr) {
audio <- downsample(audio, samp.rate = target_sr)
}
audio <- normalize(audio, unit = "16")
target_length <- as.integer(target_sr * duration)
current_length <- length(audio@left)
if (current_length < target_length) {
padding <- rep(0, target_length - current_length)
waveform <- c(audio@left, padding)
} else {
waveform <- audio@left[1:target_length]
}
waveform_tensor <- torch_tensor(waveform)$unsqueeze(1)
return(waveform_tensor)
}
mel_spectrogram_transform <- function(sample_rate = 22050,
n_fft = 2048,
hop_length = 512,
n_mels = 128) {
transform_mel_spectrogram(
sample_rate = sample_rate,
n_fft = n_fft,
hop_length = hop_length,
n_mels = n_mels,
normalized = TRUE
)
}
audio_to_log_mel <- function(waveform, mel_transform) {
mel_spec <- mel_transform(waveform)
log_mel_spec <- torch_log(mel_spec + 1e-9)
return(log_mel_spec)
}
mfcc_transform <- function(sample_rate = 22050, n_mfcc = 13) {
transform_mfcc(
sample_rate = sample_rate,
n_mfcc = n_mfcc,
melkwargs = list(
n_fft = 2048,
hop_length = 512,
n_mels = 128
)
)
}
CNN for Audio Classification
audio_cnn <- nn_module(
"AudioCNN",
initialize = function(num_classes, n_mels = 128) {
self$conv1 <- nn_conv2d(1, 32, kernel_size = c(3, 3), padding = c(1, 1))
self$conv2 <- nn_conv2d(32, 64, c(3, 3), padding = c(1, 1))
self$conv3 <- nn_conv2d(64, 128, c(3, 3), padding = c(1, 1))
self$conv4 <- nn_conv2d(128, 256, c(3, 3), padding = c(1, 1))
self$pool <- nn_max_pool2d(kernel_size = c(2, 2))
self$dropout <- nn_dropout(0.3)
self$batch_norm1 <- nn_batch_norm2d(32)
self$batch_norm2 <- nn_batch_norm2d(64)
self$batch_norm3 <- nn_batch_norm2d(128)
self$batch_norm4 <- nn_batch_norm2d(256)
self$global_pool <- nn_adaptive_avg_pool2d(c(1, 1))
self$fc1 <- nn_linear(256, 256)
self$fc2 <- nn_linear(256, num_classes)
self$relu <- nn_relu()
},
forward = function(x) {
x <- self$conv1(x) |> self$batch_norm1() |> self$relu() |> self$pool()
x <- self$conv2(x) |> self$batch_norm2() |> self$relu() |> self$pool()
x <- self$conv3(x) |> self$batch_norm3() |> self$relu() |> self$pool()
x <- self$conv4(x) |> self$batch_norm4() |> self$relu()
x <- self$global_pool(x)$squeeze(c(3, 4))
x <- self$fc1(x) |> self$relu() |> self$dropout()
x <- self$fc2(x)
return(x)
}
)
CRNN for Audio (CNN + RNN)
audio_crnn <- nn_module(
"AudioCRNN",
initialize = function(num_classes, n_mels = 128, rnn_hidden = 128) {
self$conv1 <- nn_conv2d(1, 64, kernel_size = c(3, 3), padding = c(1, 1))
self$conv2 <- nn_conv2d(64, 128, c(3, 3), padding = c(1, 1))
self$conv3 <- nn_conv2d(128, 256, c(3, 3), padding = c(1, 1))
self$pool <- nn_max_pool2d(c(2, 2))
self$batch_norm1 <- nn_batch_norm2d(64)
self$batch_norm2 <- nn_batch_norm2d(128)
self$batch_norm3 <- nn_batch_norm2d(256)
self$gru <- nn_gru(
input_size = 256 * (n_mels %/% 8),
hidden_size = rnn_hidden,
num_layers = 2,
dropout = 0.3,
batch_first = TRUE,
bidirectional = TRUE
)
self$attention <- nn_linear(rnn_hidden * 2, 1)
self$fc <- nn_linear(rnn_hidden * 2, num_classes)
self$dropout <- nn_dropout(0.3)
self$relu <- nn_relu()
},
forward = function(x) {
batch_size <- x$size(1)
x <- self$conv1(x) |> self$batch_norm1() |> self$relu() |> self$pool()
x <- self$conv2(x) |> self$batch_norm2() |> self$relu() |> self$pool()
x <- self$conv3(x) |> self$batch_norm3() |> self$relu() |> self$pool()
freq_dim <- x$size(3)
time_dim <- x$size(4)
x <- x$permute(c(1, 4, 2, 3))
x <- x$reshape(c(batch_size, time_dim, -1))
rnn_out <- self$gru(x)[[1]]
attention_weights <- self$attention(rnn_out)
attention_weights <- nnf_softmax(attention_weights, dim = 2)
weighted <- (rnn_out * attention_weights)$sum(dim = 2)
out <- weighted |> self$dropout() |> self$fc()
return(out)
}
)
Audio Data Augmentation
time_augmentation <- function(waveform, sample_rate) {
shift <- sample(-sample_rate:sample_rate, 1)
if (shift > 0) {
waveform <- torch_cat(list(torch_zeros(shift), waveform[1:(length(waveform) - shift)]))
} else if (shift < 0) {
shift <- abs(shift)
waveform <- torch_cat(list(waveform[(shift + 1):length(waveform)], torch_zeros(shift)))
}
if (runif(1) > 0.5) {
noise_level <- runif(1, 0.001, 0.01)
noise <- torch_randn_like(waveform) * noise_level
waveform <- waveform + noise
}
return(waveform)
}
spec_augment <- function(spec, freq_mask_param = 15, time_mask_param = 35, n_freq_masks = 2, n_time_masks = 2) {
for (i in 1:n_freq_masks) {
freq_start <- sample(1:(spec$size(2) - freq_mask_param), 1)
spec[, freq_start:(freq_start + freq_mask_param - 1), ] <- 0
}
for (i in 1:n_time_masks) {
time_start <- sample(1:(spec$size(3) - time_mask_param), 1)
spec[, , time_start:(time_start + time_mask_param - 1)] <- 0
}
return(spec)
}
mixup_batch <- function(x, y, alpha = 0.2) {
batch_size <- x$size(1)
lambda <- torch_tensor(rbeta(batch_size, alpha, alpha))$to(device = x$device)
indices <- torch_randperm(batch_size)
x_mixed <- lambda$view(c(-1, 1, 1, 1)) * x + (1 - lambda$view(c(-1, 1, 1, 1))) * x[indices]
y_mixed <- lambda * y + (1 - lambda) * y[indices]
list(x = x_mixed, y = y_mixed)
}
Audio Dataset and DataLoader
library(torch)
audio_dataset <- dataset(
name = "audio_dataset",
initialize = function(file_paths, labels, transform = NULL, augment = FALSE) {
self$file_paths <- file_paths
self$labels <- labels
self$transform <- transform
self$augment <- augment
},
.getitem = function(index) {
waveform <- preprocess_audio(self$file_paths[index])
if (self$augment) {
waveform <- time_augmentation(waveform, sample_rate = 22050)
}
if (!is.null(self$transform)) {
spectrogram <- self$transform(waveform)
if (self$augment) {
spectrogram <- spec_augment(spectrogram)
}
} else {
spectrogram <- waveform
}
label <- torch_tensor(self$labels[index], dtype = torch_long())
list(x = spectrogram, y = label)
},
.length = function() {
length(self$file_paths)
}
)
mel_transform <- mel_spectrogram_transform()
train_ds <- audio_dataset(
train_files, train_labels,
transform = mel_transform,
augment = TRUE
)
valid_ds <- audio_dataset(
valid_files, valid_labels,
transform = mel_transform,
augment = FALSE
)
train_dl <- dataloader(train_ds, batch_size = 32, shuffle = TRUE, num_workers = 4)
valid_dl <- dataloader(valid_ds, batch_size = 32, shuffle = FALSE, num_workers = 4)
Training Audio Models with luz
library(luz)
fitted <- audio_cnn |>
setup(
loss = nn_cross_entropy_loss(),
optimizer = optim_adam,
metrics = list(
luz_metric_accuracy(),
luz_metric_binary_auroc()
)
) |>
set_hparams(num_classes = length(unique(train_labels)), n_mels = 128) |>
set_opt_hparams(lr = 0.001, weight_decay = 1e-4) |>
fit(
train_dl,
epochs = 50,
valid_data = valid_dl,
callbacks = list(
luz_callback_early_stopping(patience = 10, monitor = "valid_loss"),
luz_callback_lr_scheduler(
torch::lr_reduce_on_plateau,
mode = "min",
factor = 0.5,
patience = 5
),
luz_callback_model_checkpoint(path = "models/", monitor = "valid_loss"),
luz_callback_csv_logger("training_log.csv")
),
verbose = TRUE
)
evaluate(fitted, valid_dl)
predictions <- predict(fitted, test_dl)
Handling Class Imbalance in Audio
class_counts <- table(train_labels)
class_weights <- 1.0 / as.numeric(class_counts)
class_weights <- class_weights / sum(class_weights) * length(class_weights)
class_weights_tensor <- torch_tensor(class_weights)
criterion <- nn_cross_entropy_loss(weight = class_weights_tensor$to(device = device))
focal_loss <- nn_module(
"FocalLoss",
initialize = function(alpha = 1, gamma = 2) {
self$alpha <- alpha
self$gamma <- gamma
self$ce_loss <- nn_cross_entropy_loss(reduction = "none")
},
forward = function(inputs, targets) {
ce_loss <- self$ce_loss(inputs, targets)
pt <- torch_exp(-ce_loss)
focal_loss <- self$alpha * (1 - pt)^self$gamma * ce_loss
return(focal_loss$mean())
}
)
optimize_thresholds <- function(probabilities, true_labels, metric = "f1") {
num_classes <- ncol(probabilities)
optimal_thresholds <- numeric(num_classes)
for (i in 1:num_classes) {
thresholds <- seq(0.1, 0.9, by = 0.05)
scores <- sapply(thresholds, function(t) {
preds <- probabilities[, i] > t
if (metric == "f1") {
tp <- sum(preds & true_labels[, i])
fp <- sum(preds & !true_labels[, i])
fn <- sum(!preds & true_labels[, i])
precision <- tp / (tp + fp + 1e-9)
recall <- tp / (tp + fn + 1e-9)
2 * (precision * recall) / (precision + recall + 1e-9)
}
})
optimal_thresholds[i] <- thresholds[which.max(scores)]
}
return(optimal_thresholds)
}
Inference on Continuous Audio
infer_continuous_audio <- function(model, audio_path, window_sec = 5, overlap = 0.5,
mel_transform, device = "cpu") {
audio <- readWave(audio_path)
sr <- audio@samp.rate
duration_sec <- length(audio@left) / sr
window_samples <- as.integer(window_sec * sr)
hop_samples <- as.integer(window_samples * (1 - overlap))
timestamps <- c()
predictions <- list()
model$eval()
with_no_grad({
start_sample <- 1
while (start_sample + window_samples <= length(audio@left)) {
window_audio <- audio@left[start_sample:(start_sample + window_samples - 1)]
waveform <- torch_tensor(window_audio)$unsqueeze(1)$to(device = device)
spectrogram <- mel_transform(waveform)$unsqueeze(1)
logits <- model(spectrogram)
probs <- nnf_softmax(logits, dim = 2)
timestamps <- c(timestamps, start_sample / sr)
predictions[[length(predictions) + 1]] <- as.array(probs$cpu())
start_sample <- start_sample + hop_samples
}
})
results <- tibble(
start_time = timestamps,
end_time = timestamps + window_sec,
predictions = predictions
)
return(results)
}
smooth_predictions <- function(predictions, window_size = 5) {
smoothed <- apply(predictions, 2, function(x) {
zoo::rollmean(x, k = window_size, fill = "extend")
})
return(smoothed)
}
aggregate_windows <- function(inference_results, method = "mean") {
if (method == "mean") {
aggregated <- inference_results |>
group_by(start_time = floor(start_time)) |>
summarize(
predictions = list(Reduce(`+`, predictions) / length(predictions))
)
} else if (method == "max") {
aggregated <- inference_results |>
group_by(start_time = floor(start_time)) |>
summarize(
predictions = list(do.call(pmax, predictions))
)
}
return(aggregated)
}
Training Best Practices
Data Preparation
-
Standardization:
- Fixed sample rate across all audio
- Consistent duration (pad/crop)
- Normalization to [-1, 1] or [0, 1]
-
Train/validation/test splits:
- For audio: group by
recording_id or site_id (prevent leakage)
- Use
group_vfold_cv() from tidymodels or grouped resampling
- Never split randomly for time series or spatial data
-
Data augmentation:
- Time-domain: time shift, noise addition, speed/pitch changes
- Spectrogram: SpecAugment (frequency/time masking)
- Batch-level: mixup, cutmix
Model Training
-
Learning rate scheduling:
- Start with 1e-3 for Adam
- Use
lr_reduce_on_plateau (factor=0.5, patience=3-5)
- Or
lr_one_cycle for faster convergence
-
Regularization:
- Dropout: 0.2-0.5 after dense layers
- Batch normalization after conv layers
- Weight decay: 1e-4 to 1e-5
- Early stopping: patience=5-10 epochs
-
Gradient management:
- Gradient clipping if loss spikes (max_norm=1.0)
- Mixed precision training for GPU memory (torch_cuda_amp)
-
Checkpointing:
- Save best model based on validation metric
- Save optimizer state for resuming training
- Track hyperparameters with each checkpoint
Evaluation
-
Metrics:
- Classification: accuracy, macro/micro F1, per-class precision/recall
- Imbalanced: weighted F1, PR-AUC (not ROC-AUC)
- Multi-label: Hamming loss, subset accuracy
-
Validation strategy:
- Cross-validation with grouped folds
- Hold-out test set (never touched during development)
- Monitor training/validation curves for overfitting
-
Error analysis:
- Confusion matrix for classification
- Per-class performance breakdown
- Visualize misclassified examples
Integration with R Ecosystem
With tidyverse
library(tidyverse)
audio_df <- tibble(
file_path = list.files("audio", pattern = "\\.wav$", full.names = TRUE),
file_name = basename(file_path)
) |>
mutate(
species = str_extract(file_name, "^[A-Za-z]+"),
label = as.integer(factor(species)) - 1
)
train_test_split <- audio_df |>
mutate(recording_id = str_extract(file_name, "rec[0-9]+")) |>
group_by(recording_id) |>
slice_sample(n = 1) |>
ungroup() |>
mutate(split = sample(c("train", "test"), n(), replace = TRUE, prob = c(0.8, 0.2))) |>
select(recording_id, split)
audio_df <- audio_df |>
mutate(recording_id = str_extract(file_name, "rec[0-9]+")) |>
left_join(train_test_split, by = "recording_id")
train_df <- audio_df |> filter(split == "train")
test_df <- audio_df |> filter(split == "test")
With tidymodels
library(tidymodels)
extract_embeddings <- function(model, dataloader, device = "cpu") {
model$eval()
embeddings <- list()
with_no_grad({
coro::loop(for (batch in dataloader) {
x <- batch$x$to(device = device)
features <- model$conv1(x) |>
model$conv2() |>
model$conv3() |>
model$global_pool()
embeddings[[length(embeddings) + 1]] <- as.array(features$cpu())
})
})
do.call(rbind, embeddings)
}
recipe_with_embeddings <- recipe(species ~ ., data = feature_df) |>
step_normalize(all_numeric_predictors()) |>
step_pca(starts_with("embedding_"), num_comp = 50)
rf_spec <- rand_forest(trees = 500) |>
set_engine("ranger") |>
set_mode("classification")
wf <- workflow() |>
add_recipe(recipe_with_embeddings) |>
add_model(rf_spec)
fit_resamples(wf, resamples = vfold_cv(feature_df, v = 5))
Common Patterns
Pattern: Save and Load Models
torch_save(model, "model.pt")
torch_save(optimizer, "optimizer.pt")
model <- torch_load("model.pt")
optimizer <- torch_load("optimizer.pt")
torch_save(model$state_dict(), "model_state.pt")
model$load_state_dict(torch_load("model_state.pt"))
model |> save_model("model.keras")
model <- load_model("model.keras")
luz_callback_model_checkpoint(path = "models/", monitor = "valid_loss")
model <- luz_load("models/best_model.pt")
Pattern: Reproducibility
set.seed(42)
torch_manual_seed(42)
if (cuda_is_available()) {
cuda_manual_seed(42)
cuda_manual_seed_all(42)
}
torch_backends_cudnn_deterministic(TRUE)
torch_backends_cudnn_benchmark(FALSE)
library(config)
config <- list(
model = "AudioCNN",
n_mels = 128,
batch_size = 32,
learning_rate = 0.001,
epochs = 50,
seed = 42
)
yaml::write_yaml(config, "config.yaml")
Pattern: Multi-GPU Training
if (cuda_device_count() > 1) {
model <- nn_data_parallel(model)
}
model <- model$to(device = "cuda")
Troubleshooting
Issue: CUDA out of memory
- Reduce batch size
- Use gradient accumulation (effective batch size)
- Enable mixed precision training
- Use smaller model or spectrograms
- Clear cache with
cuda_empty_cache()
Issue: Model not learning (loss not decreasing)
- Check learning rate (try 1e-4, 1e-3, 1e-2)
- Verify data preprocessing (normalization, labels)
- Check for NaN/Inf in gradients (
torch_any(torch_isnan(model$fc1$weight$grad)))
- Simplify model architecture first
- Visualize input data (ensure spectrograms look correct)
Issue: Overfitting (validation loss increasing)
- Add more dropout
- Increase weight decay
- Use data augmentation
- Reduce model capacity
- Early stopping with patience
Issue: Spectrograms look wrong
- Check sample rate matches expectation
- Verify mono conversion
- Check n_fft, hop_length, n_mels parameters
- Apply log scaling to mel-spectrogram
- Visualize before training
Issue: Slow training
- Use GPU (check
cuda_is_available())
- Increase batch size
- Use more num_workers in dataloader
- Profile with
profvis to find bottlenecks
- Consider mixed precision training
Additional Resources
Integration with Other Skills
- r-bioacoustics: For audio preprocessing and feature extraction before DL
- r-tidymodels: For integrating DL embeddings as features in ML pipelines
- learning-paradigms: For weak supervision, few-shot, and self-supervised learning
- r-performance: For profiling and optimizing training pipelines
- ggplot2: For visualizing training curves and spectrograms
This skill provides comprehensive deep learning guidance across all domains with special emphasis on audio for bioacoustic applications. For audio-specific workflows, consider integrating with the r-bioacoustics skill for preprocessing and feature engineering.