Classificação de áudio simples com tocha


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)

Classificação de áudio simples com tocha

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():

  1. começar com collate_fn <- operate(batch) browser().
  2. instanciar dataloader com o collate_fn()
  3. crie um ambiente chamando enumerate(dataloader) então você pode pedir para recuperar um lote do dataloader.
  4. correr surroundings((1))((1)). Agora você deve ser enviado para collate_fn() com acesso a batch objeto de entrada.
  5. 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 ondatensor com dimensão (32, 1, 16001)
  • lote ((2)): alvostensor com dimensão (32, 1)

Além disso, torchaudio vem com 3 carregadores, av_loader, tuner_loadere 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_subsetvamos 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
)

Desempenho do modelo: rótulos verdadeiros <--> rótulos previstos.” width=”336″/></p><p class=

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}
}

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *