Este artigo traduz Daniel Falbelde ‘Classificação de áudio simples’ artigo de tensorflow/keras
para torch/torchaudio
. O objetivo principal é apresentar o torchaudio e ilustrar suas contribuições para o torch
ecossistema. Aqui, nos concentramos em um conjunto de dados in style, o carregador de áudio e o transformador de espectrograma. Um produto secundário interessante é o paralelo entre tocha e tensorflow, mostrando ora as diferenças, ora as semelhanças entre eles.
Baixando e importando
torchaudio tem o speechcommand_dataset
integrado. Ele filtra background_noise por padrão e nos permite escolher entre as versões v0.01
e v0.02
.
# set an current folder right here to cache the dataset
DATASETS_PATH <- "~/datasets/"
# 1.4GB obtain
df <- speechcommand_dataset(
root = DATASETS_PATH,
url = "speech_commands_v0.01",
obtain = TRUE
)
# count on folder: _background_noise_
df$EXCEPT_FOLDER
# (1) "_background_noise_"
# variety of audio information
size(df)
# (1) 64721
# a pattern
pattern <- df(1)
pattern$waveform(, 1:10)
torch_tensor
0.0001 *
0.9155 0.3052 1.8311 1.8311 -0.3052 0.3052 2.4414 0.9155 -0.9155 -0.6104
( CPUFloatType{1,10} )
pattern$sample_rate
# 16000
pattern$label
# mattress
plot(pattern$waveform(1), sort = "l", col = "royalblue", predominant = pattern$label)

Figura 1: Um exemplo de forma de onda para uma ‘cama’.
Aulas
(1) "mattress" "chicken" "cat" "canine" "down" "eight" "5"
(8) "4" "go" "glad" "home" "left" "marvin" "9"
(15) "no" "off" "on" "one" "proper" "seven" "sheila"
(22) "six" "cease" "three" "tree" "two" "up" "wow"
(29) "sure" "zero"
Gerador Carregador de dados
torch::dataloader
tem a mesma tarefa que data_generator
definido no artigo unique. Ele é responsável por preparar lotes – incluindo embaralhamento, preenchimento, codificação one-hot, and so forth. – e por cuidar do paralelismo/orquestração de E/S do dispositivo.
Na tocha, fazemos isso passando o subconjunto trem/teste para torch::dataloader
e encapsulando toda a lógica de configuração em lote dentro de um collate_fn()
função.
Neste ponto, dataloader(train_subset)
não funcionaria porque as amostras não são preenchidas. Portanto, precisamos construir o nosso próprio collate_fn()
com a estratégia de preenchimento.
Sugiro usar a seguinte abordagem ao implementar o collate_fn()
:
- começar com
collate_fn <- operate(batch) browser()
. - instanciar
dataloader
com ocollate_fn()
- crie um ambiente chamando
enumerate(dataloader)
então você pode pedir para recuperar um lote do dataloader. - correr
surroundings((1))((1))
. Agora você deve ser enviado para collate_fn() com acesso abatch
objeto de entrada. - construir a lógica.
collate_fn <- operate(batch) {
browser()
}
ds_train <- dataloader(
train_subset,
batch_size = 32,
shuffle = TRUE,
collate_fn = collate_fn
)
ds_train_env <- enumerate(ds_train)
ds_train_env((1))((1))
A closing collate_fn()
preenche a forma de onda até o comprimento 16001 e depois empilha tudo junto. Neste ponto ainda não há espectrogramas. Faremos a transformação do espectrograma como parte da arquitetura do modelo.
pad_sequence <- operate(batch) {
# Make all tensors in a batch the identical size by padding with zeros
batch <- sapply(batch, operate(x) (x$t()))
batch <- torch::nn_utils_rnn_pad_sequence(batch, batch_first = TRUE, padding_value = 0.)
return(batch$permute(c(1, 3, 2)))
}
# Ultimate collate_fn
collate_fn <- operate(batch) {
# Enter construction:
# listing of 32 lists: listing(waveform, sample_rate, label, speaker_id, utterance_number)
# Transpose it
batch <- purrr::transpose(batch)
tensors <- batch$waveform
targets <- batch$label_index
# Group the listing of tensors right into a batched tensor
tensors <- pad_sequence(tensors)
# goal encoding
targets <- torch::torch_stack(targets)
listing(tensors = tensors, targets = targets) # (64, 1, 16001)
}
A estrutura do lote é:
- lote ((1)): formas de onda –
tensor
com dimensão (32, 1, 16001) - lote ((2)): alvos –
tensor
com dimensão (32, 1)
Além disso, torchaudio vem com 3 carregadores, av_loader
, tuner_loader
e audiofile_loader
– mais por vir. set_audio_backend()
é usado para definir um deles como carregador de áudio. Suas performances diferem com base no formato de áudio (mp3 ou wav). Ainda não existe um mundo perfeito: tuner_loader
é melhor para mp3, audiofile_loader
é melhor para wav, mas nenhum deles tem a opção de carregar parcialmente uma amostra de um arquivo de áudio sem primeiro trazer todos os dados para a memória.
Para um determinado back-end de áudio, precisamos passá-lo para cada trabalhador por meio worker_init_fn()
argumento.
ds_train <- dataloader(
train_subset,
batch_size = 128,
shuffle = TRUE,
collate_fn = collate_fn,
num_workers = 16,
worker_init_fn = operate(.) {torchaudio::set_audio_backend("audiofile_loader")},
worker_globals = c("pad_sequence") # pad_sequence is required for collect_fn
)
ds_test <- dataloader(
test_subset,
batch_size = 64,
shuffle = FALSE,
collate_fn = collate_fn,
num_workers = 8,
worker_globals = c("pad_sequence") # pad_sequence is required for collect_fn
)
Definição de modelo
Em vez de keras::keras_model_sequential()
vamos definir um torch::nn_module()
. Conforme referenciado no artigo unique, o modelo é baseado em esta arquitetura para MNIST deste tutoriale vou chamá-lo de ‘DanielNN’.
dan_nn <- torch::nn_module(
"DanielNN",
initialize = operate(
window_size_ms = 30,
window_stride_ms = 10
) {
# spectrogram spec
window_size <- as.integer(16000*window_size_ms/1000)
stride <- as.integer(16000*window_stride_ms/1000)
fft_size <- as.integer(2^trunc(log(window_size, 2) + 1))
n_chunks <- size(seq(0, 16000, stride))
self$spectrogram <- torchaudio::transform_spectrogram(
n_fft = fft_size,
win_length = window_size,
hop_length = stride,
normalized = TRUE,
energy = 2
)
# convs 2D
self$conv1 <- torch::nn_conv2d(in_channels = 1, out_channels = 32, kernel_size = c(3,3))
self$conv2 <- torch::nn_conv2d(in_channels = 32, out_channels = 64, kernel_size = c(3,3))
self$conv3 <- torch::nn_conv2d(in_channels = 64, out_channels = 128, kernel_size = c(3,3))
self$conv4 <- torch::nn_conv2d(in_channels = 128, out_channels = 256, kernel_size = c(3,3))
# denses
self$dense1 <- torch::nn_linear(in_features = 14336, out_features = 128)
self$dense2 <- torch::nn_linear(in_features = 128, out_features = 30)
},
ahead = operate(x) {
x %>% # (64, 1, 16001)
self$spectrogram() %>% # (64, 1, 257, 101)
torch::torch_add(0.01) %>%
torch::torch_log() %>%
self$conv1() %>%
torch::nnf_relu() %>%
torch::nnf_max_pool2d(kernel_size = c(2,2)) %>%
self$conv2() %>%
torch::nnf_relu() %>%
torch::nnf_max_pool2d(kernel_size = c(2,2)) %>%
self$conv3() %>%
torch::nnf_relu() %>%
torch::nnf_max_pool2d(kernel_size = c(2,2)) %>%
self$conv4() %>%
torch::nnf_relu() %>%
torch::nnf_max_pool2d(kernel_size = c(2,2)) %>%
torch::nnf_dropout(p = 0.25) %>%
torch::torch_flatten(start_dim = 2) %>%
self$dense1() %>%
torch::nnf_relu() %>%
torch::nnf_dropout(p = 0.5) %>%
self$dense2()
}
)
mannequin <- dan_nn()
system <- torch::torch_device(if(torch::cuda_is_available()) "cuda" else "cpu")
mannequin$to(system = system)
print(mannequin)
An `nn_module` containing 2,226,846 parameters.
── Modules ──────────────────────────────────────────────────────
● spectrogram: #0 parameters
● conv1: #320 parameters
● conv2: #18,496 parameters
● conv3: #73,856 parameters
● conv4: #295,168 parameters
● dense1: #1,835,136 parameters
● dense2: #3,870 parameters
Encaixe do modelo
Ao contrário do tensorflow, não há mannequin %>% compile(...)
pise na tocha, então vamos definir loss criterion
, optimizer technique
e analysis metrics
explicitamente no ciclo de treinamento.
loss_criterion <- torch::nn_cross_entropy_loss()
optimizer <- torch::optim_adadelta(mannequin$parameters, rho = 0.95, eps = 1e-7)
metrics <- listing(acc = yardstick::accuracy_vec)
Ciclo de treinamento
library(glue)
library(progress)
pred_to_r <- operate(x) {
lessons <- issue(df$lessons)
lessons(as.numeric(x$to(system = "cpu")))
}
set_progress_bar <- operate(whole) {
progress_bar$new(
whole = whole, clear = FALSE, width = 70,
format = ":present/:whole (:bar) - :elapsed - loss: :loss - acc: :acc"
)
}
epochs <- 20
losses <- c()
accs <- c()
for(epoch in seq_len(epochs)) {
pb <- set_progress_bar(size(ds_train))
pb$message(glue("Epoch {epoch}/{epochs}"))
coro::loop(for(batch in ds_train) {
optimizer$zero_grad()
predictions <- mannequin(batch((1))$to(system = system))
targets <- batch((2))$to(system = system)
loss <- loss_criterion(predictions, targets)
loss$backward()
optimizer$step()
# eval reviews
prediction_r <- pred_to_r(predictions$argmax(dim = 2))
targets_r <- pred_to_r(targets)
acc <- metrics$acc(targets_r, prediction_r)
accs <- c(accs, acc)
loss_r <- as.numeric(loss$merchandise())
losses <- c(losses, loss_r)
pb$tick(tokens = listing(loss = spherical(imply(losses), 4), acc = spherical(imply(accs), 4)))
})
}
# check
predictions_r <- c()
targets_r <- c()
coro::loop(for(batch_test in ds_test) {
predictions <- mannequin(batch_test((1))$to(system = system))
targets <- batch_test((2))$to(system = system)
predictions_r <- c(predictions_r, pred_to_r(predictions$argmax(dim = 2)))
targets_r <- c(targets_r, pred_to_r(targets))
})
val_acc <- metrics$acc(issue(targets_r, ranges = 1:30), issue(predictions_r, ranges = 1:30))
cat(glue("val_acc: {val_acc}nn"))
Epoch 1/20
(W SpectralOps.cpp:590) Warning: The operate torch.rfft is deprecated and might be eliminated in a future PyTorch launch. Use the brand new torch.fft module capabilities, as an alternative, by importing torch.fft and calling torch.fft.fft or torch.fft.rfft. (operate operator())
354/354 (=========================) - 1m - loss: 2.6102 - acc: 0.2333
Epoch 2/20
354/354 (=========================) - 1m - loss: 1.9779 - acc: 0.4138
Epoch 3/20
354/354 (============================) - 1m - loss: 1.62 - acc: 0.519
Epoch 4/20
354/354 (=========================) - 1m - loss: 1.3926 - acc: 0.5859
Epoch 5/20
354/354 (==========================) - 1m - loss: 1.2334 - acc: 0.633
Epoch 6/20
354/354 (=========================) - 1m - loss: 1.1135 - acc: 0.6685
Epoch 7/20
354/354 (=========================) - 1m - loss: 1.0199 - acc: 0.6961
Epoch 8/20
354/354 (=========================) - 1m - loss: 0.9444 - acc: 0.7181
Epoch 9/20
354/354 (=========================) - 1m - loss: 0.8816 - acc: 0.7365
Epoch 10/20
354/354 (=========================) - 1m - loss: 0.8278 - acc: 0.7524
Epoch 11/20
354/354 (=========================) - 1m - loss: 0.7818 - acc: 0.7659
Epoch 12/20
354/354 (=========================) - 1m - loss: 0.7413 - acc: 0.7778
Epoch 13/20
354/354 (=========================) - 1m - loss: 0.7064 - acc: 0.7881
Epoch 14/20
354/354 (=========================) - 1m - loss: 0.6751 - acc: 0.7974
Epoch 15/20
354/354 (=========================) - 1m - loss: 0.6469 - acc: 0.8058
Epoch 16/20
354/354 (=========================) - 1m - loss: 0.6216 - acc: 0.8133
Epoch 17/20
354/354 (=========================) - 1m - loss: 0.5985 - acc: 0.8202
Epoch 18/20
354/354 (=========================) - 1m - loss: 0.5774 - acc: 0.8263
Epoch 19/20
354/354 (==========================) - 1m - loss: 0.5582 - acc: 0.832
Epoch 20/20
354/354 (=========================) - 1m - loss: 0.5403 - acc: 0.8374
val_acc: 0.876705979296493
Fazendo previsões
Já temos todas as previsões calculadas para test_subset
vamos recriar a parcela aluvial do artigo unique.
library(dplyr)
library(alluvial)
df_validation <- information.body(
pred_class = df$lessons(predictions_r),
class = df$lessons(targets_r)
)
x <- df_validation %>%
mutate(right = pred_class == class) %>%
depend(pred_class, class, right)
alluvial(
x %>% choose(class, pred_class),
freq = x$n,
col = ifelse(x$right, "lightblue", "pink"),
border = ifelse(x$right, "lightblue", "pink"),
alpha = 0.6,
conceal = x$n < 20
)

Figura 2: Desempenho do modelo: rótulos verdadeiros <–> rótulos previstos.
A precisão do modelo é de 87,7%, um pouco pior do que a versão tensorflow da postagem unique. No entanto, todas as conclusões da postagem unique ainda são válidas.
Reutilizar
Texto e figuras estão licenciados sob Artistic Commons Attribution CC POR 4,0. As figuras que foram reutilizadas de outras fontes não se enquadram nesta licença e podem ser reconhecidas por uma nota na sua legenda: “Figura de …”.
Citação
Para atribuição, cite este trabalho como
Damiani (2021, Feb. 4). Posit AI Weblog: Easy audio classification with torch. Retrieved from https://blogs.rstudio.com/tensorflow/posts/2021-02-04-simple-audio-classification-with-torch/
Citação BibTeX
@misc{athossimpleaudioclassification, creator = {Damiani, Athos}, title = {Posit AI Weblog: Easy audio classification with torch}, url = {https://blogs.rstudio.com/tensorflow/posts/2021-02-04-simple-audio-classification-with-torch/}, yr = {2021} }