Introdução
Neste tutorial, criaremos um modelo de aprendizado profundo para classificar palavras. Vamos usar tfdatasets para lidar com dados IO e pré-processamento, e Keras Para construir e treinar o modelo.
Vamos usar o DataSet comandos de fala que consiste em 65.000 arquivos de áudio de um segundo de pessoas dizendo 30 palavras diferentes. Cada arquivo contém uma única palavra em inglês falado. O conjunto de dados foi lançado pelo Google sob licença CC.
Nosso modelo é uma porta de Keras do Tutorial do TensorFlow Reconhecimento de áudio simples que por sua vez foi inspirado por Redes neurais convolucionais para manchas de palavras-chave de fita pequena. Existem outras abordagens para a tarefa de reconhecimento de fala, como Redes neurais recorrentesAssim, Convóluis dilatados (atross) ou Aprendendo com exemplos entre lessons para reconhecimento de som profundo.
O modelo que implementaremos aqui não é o estado da arte para sistemas de reconhecimento de áudio, que são muito mais complexos, mas são relativamente simples e rápidos de treinar. Além disso, mostramos como usar com eficiência tfdatasets para pré -processar e servir dados.
Representação de áudio
Muitos modelos de aprendizado profundo são de ponta a ponta, ou seja, deixamos o modelo aprender representações úteis diretamente dos dados brutos. No entanto, os dados de áudio crescem muito rápido – 16.000 amostras por segundo, com uma estrutura muito rica em muitas escalas de tempo. Para evitar ter que lidar com dados de som de ondas cruas, os pesquisadores geralmente usam algum tipo de engenharia de recursos.
Cada onda sonora pode ser representada por seu espectro e digitalmente pode ser calculada usando o Transformação rápida de Fourier (FFT).

Uma maneira comum de representar dados de áudio é dividi -los em pequenos pedaços, que geralmente se sobrepõem. Para cada pedaço, usamos a FFT para calcular a magnitude do espectro de frequência. Os espectros são então combinados, lado a lado, para formar o que chamamos de espectrograma.
Também é comum os sistemas de reconhecimento de fala transformarem ainda mais o espectro e calcular o Coeficientes cepstrais de frequência de Mel. Essa transformação leva em consideração que o ouvido humano não pode discernir a diferença entre duas frequências espaçadas e cria compartimentos inteligentes no eixo de frequência. Um ótimo tutorial sobre MFCCs pode ser encontrado aqui.

Após esse procedimento, temos uma imagem para cada amostra de áudio e podemos usar redes neurais convolucionais, o tipo de arquitetura padrão nos modelos de reconhecimento de imagem.
Obtain
Primeiro, vamos baixar dados para um diretório em nosso projeto. Você pode baixar de este hyperlink (~ 1 GB) ou de R com:
dir.create("knowledge")
obtain.file(
url = "http://obtain.tensorflow.org/knowledge/speech_commands_v0.01.tar.gz",
destfile = "knowledge/speech_commands_v0.01.tar.gz"
)
untar("knowledge/speech_commands_v0.01.tar.gz", exdir = "knowledge/speech_commands_v0.01")
Dentro do knowledge
diretório teremos uma pasta chamada speech_commands_v0.01
. Os arquivos de áudio WAV dentro deste diretório estão organizados em sub-dobras com os nomes dos rótulos. Por exemplo, todos os arquivos de áudio de um segundo de pessoas que falam a palavra “cama” estão dentro do mattress
diretório. Existem 30 deles e um especial chamado _background_noise_
que contém vários padrões que podem ser misturados para simular o ruído de fundo.
Importação
Nesta etapa, listaremos todos os arquivos de áudio .wav em um tibble
com 3 colunas:
fname
: o nome do arquivo;class
: o rótulo para cada arquivo de áudio;class_id
: Um número inteiro exclusivo a partir de zero para cada classe – usado para codificar as lessons.
Isso será útil para a próxima etapa, quando criaremos um gerador usando o tfdatasets
pacote.
Gerador
Agora vamos criar nosso Dataset
que no contexto de tfdatasets
Adiciona operações ao gráfico do TensorFlow para ler e pré-processo. Como são operações de tensorflow, elas são executadas em C ++ e em paralelo com o treinamento do modelo.
O gerador que criaremos será responsável por ler os arquivos de áudio do disco, criando o espectrograma para cada um e em lote as saídas.
Vamos começar criando o conjunto de dados a partir de fatias do knowledge.body
com nomes de arquivos de áudio e lessons que acabamos de criar.
Agora, vamos definir os parâmetros para a criação do Spectrogram. Precisamos definir window_size_ms
qual é o tamanho em milissegundos de cada pedaço em que vamos dividir a onda de áudio e window_stride_ms
a distância entre os centros de pedaços adjacentes:
window_size_ms <- 30
window_stride_ms <- 10
Agora, converteremos o tamanho da janela e o passo de milissegundos em amostras. Estamos considerando que nossos arquivos de áudio têm 16.000 amostras por segundo (1000 ms).
window_size <- as.integer(16000*window_size_ms/1000)
stride <- as.integer(16000*window_stride_ms/1000)
Obteremos outras quantidades que serão úteis para a criação do espectrograma, como o número de pedaços e o tamanho da FFT, ou seja, o número de caixas no eixo de frequência. A função que vamos usar para calcular o espectrograma não nos permite alterar o tamanho da FFT e, por padrão, usa a primeira potência de 2 maior que o tamanho da janela.
Nós agora usaremos dataset_map
que nos permite especificar uma função de pré-processamento para cada observação (linha) do nosso conjunto de dados. É nesta etapa que lemos o arquivo de áudio bruto do disco e criamos seu espectrograma e o vetor de resposta codificado em um scorching.
# shortcuts to used TensorFlow modules.
audio_ops <- tf$contrib$framework$python$ops$audio_ops
ds <- ds %>%
dataset_map(perform(obs) {
# a great way to debug when constructing tfdatsets pipelines is to make use of a print
# assertion like this:
# print(str(obs))
# decoding wav recordsdata
audio_binary <- tf$read_file(tf$reshape(obs$fname, form = checklist()))
wav <- audio_ops$decode_wav(audio_binary, desired_channels = 1)
# create the spectrogram
spectrogram <- audio_ops$audio_spectrogram(
wav$audio,
window_size = window_size,
stride = stride,
magnitude_squared = TRUE
)
# normalization
spectrogram <- tf$log(tf$abs(spectrogram) + 0.01)
# transferring channels to final dim
spectrogram <- tf$transpose(spectrogram, perm = c(1L, 2L, 0L))
# remodel the class_id right into a one-hot encoded vector
response <- tf$one_hot(obs$class_id, 30L)
checklist(spectrogram, response)
})
Agora, especificaremos como queremos observações em lote do conjunto de dados. Estamos usando dataset_shuffle
Como queremos embaralhar as observações do conjunto de dados, caso contrário, ele seguiria a ordem do df
objeto. Então nós usamos dataset_repeat
Para dizer ao TensorFlow que queremos continuar fazendo observações do conjunto de dados, mesmo que todas as observações já tenham sido usadas. E o mais importante aqui, nós usamos dataset_padded_batch
Para especificar que queremos lotes do tamanho 32, mas eles devem ser acolchoados, ou seja. Se alguma observação tiver um tamanho diferente, o encaixamos com zeros. A forma acolchoada é passada para dataset_padded_batch
através do padded_shapes
argumento e nós usamos NULL
Para afirmar que essa dimensão não precisa ser acolchoada.
Esta é a nossa especificação do conjunto de dados, mas precisaríamos reescrever todo o código para os dados de validação, por isso é uma boa prática envolver isso em uma função dos dados e outros parâmetros importantes como window_size_ms
e window_stride_ms
. Abaixo, definiremos uma função chamada data_generator
Isso criará o gerador, dependendo dessas entradas.
data_generator <- perform(df, batch_size, shuffle = TRUE,
window_size_ms = 30, window_stride_ms = 10) {
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(window_size/2, 16000 - window_size/2, stride))
ds <- tensor_slices_dataset(df)
if (shuffle)
ds <- ds %>% dataset_shuffle(buffer_size = 100)
ds <- ds %>%
dataset_map(perform(obs) {
# decoding wav recordsdata
audio_binary <- tf$read_file(tf$reshape(obs$fname, form = checklist()))
wav <- audio_ops$decode_wav(audio_binary, desired_channels = 1)
# create the spectrogram
spectrogram <- audio_ops$audio_spectrogram(
wav$audio,
window_size = window_size,
stride = stride,
magnitude_squared = TRUE
)
spectrogram <- tf$log(tf$abs(spectrogram) + 0.01)
spectrogram <- tf$transpose(spectrogram, perm = c(1L, 2L, 0L))
# remodel the class_id right into a one-hot encoded vector
response <- tf$one_hot(obs$class_id, 30L)
checklist(spectrogram, response)
}) %>%
dataset_repeat()
ds <- ds %>%
dataset_padded_batch(batch_size, checklist(form(n_chunks, fft_size, NULL), form(NULL)))
ds
}
Agora, podemos definir geradores de dados de treinamento e validação. Vale a pena notar que executar isso não calculará nenhum espectrograma ou leia qualquer arquivo. Ele definirá apenas no gráfico do tensorflow como ele deve ler e pré-processo.
set.seed(6)
id_train <- pattern(nrow(df), dimension = 0.7*nrow(df))
ds_train <- data_generator(
df(id_train,),
batch_size = 32,
window_size_ms = 30,
window_stride_ms = 10
)
ds_validation <- data_generator(
df(-id_train,),
batch_size = 32,
shuffle = FALSE,
window_size_ms = 30,
window_stride_ms = 10
)
Para obter um lote do gerador, poderíamos criar uma sessão de tensorflow e pedir para executar o gerador. Por exemplo:
sess <- tf$Session()
batch <- next_batch(ds_train)
str(sess$run(batch))
Checklist of two
$ : num (1:32, 1:98, 1:257, 1) -4.6 -4.6 -4.61 -4.6 -4.6 ...
$ : num (1:32, 1:30) 0 0 0 0 0 0 0 0 0 0 ...
Cada vez que você corre sess$run(batch)
Você deve ver um lote diferente de observações.
Definição do modelo
Agora que sabemos como alimentaremos nossos dados, podemos nos concentrar na definição do modelo. O espectrograma pode ser tratado como uma imagem; portanto, as arquiteturas comumente usadas nas tarefas de reconhecimento de imagem também devem funcionar bem com os espectrogramas.
Vamos construir uma rede neural convolucional semelhante ao que construímos aqui Para o conjunto de dados MNIST.
O tamanho da entrada é definido pelo número de pedaços e pelo tamanho da FFT. Como explicamos anteriormente, eles podem ser obtidos do window_size_ms
e window_stride_ms
usado para gerar o espectrograma.
Agora vamos definir nosso modelo usando a API sequencial de Keras:
mannequin <- keras_model_sequential()
mannequin %>%
layer_conv_2d(input_shape = c(n_chunks, fft_size, 1),
filters = 32, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_conv_2d(filters = 64, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_conv_2d(filters = 128, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_conv_2d(filters = 256, kernel_size = c(3,3), activation = 'relu') %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_dropout(charge = 0.25) %>%
layer_flatten() %>%
layer_dense(items = 128, activation = 'relu') %>%
layer_dropout(charge = 0.5) %>%
layer_dense(items = 30, activation = 'softmax')
Utilizamos 4 camadas de convoluções combinadas com camadas máximas de pool para extrair recursos das imagens do espectrograma e 2 camadas densas na parte superior. Nossa rede é comparativamente simples quando comparada a arquiteturas mais avançadas como Resnet ou Densenet, que têm um desempenho muito bom nas tarefas de reconhecimento de imagens.
Agora vamos compilar nosso modelo. Usaremos a entropia cruzada categórica como a função de perda e usaremos o otimizador do Adadelta. Também é aqui que definimos que examinaremos a métrica de precisão durante o treinamento.
Ajuste do modelo
Agora, vamos nos encaixar no nosso modelo. Em Keras, podemos usar conjuntos de dados de tensorflow como entradas para o fit_generator
função e nós faremos isso aqui.
Epoch 1/10
1415/1415 (==============================) - 87s 62ms/step - loss: 2.0225 - acc: 0.4184 - val_loss: 0.7855 - val_acc: 0.7907
Epoch 2/10
1415/1415 (==============================) - 75s 53ms/step - loss: 0.8781 - acc: 0.7432 - val_loss: 0.4522 - val_acc: 0.8704
Epoch 3/10
1415/1415 (==============================) - 75s 53ms/step - loss: 0.6196 - acc: 0.8190 - val_loss: 0.3513 - val_acc: 0.9006
Epoch 4/10
1415/1415 (==============================) - 75s 53ms/step - loss: 0.4958 - acc: 0.8543 - val_loss: 0.3130 - val_acc: 0.9117
Epoch 5/10
1415/1415 (==============================) - 75s 53ms/step - loss: 0.4282 - acc: 0.8754 - val_loss: 0.2866 - val_acc: 0.9213
Epoch 6/10
1415/1415 (==============================) - 76s 53ms/step - loss: 0.3852 - acc: 0.8885 - val_loss: 0.2732 - val_acc: 0.9252
Epoch 7/10
1415/1415 (==============================) - 75s 53ms/step - loss: 0.3566 - acc: 0.8991 - val_loss: 0.2700 - val_acc: 0.9269
Epoch 8/10
1415/1415 (==============================) - 76s 54ms/step - loss: 0.3364 - acc: 0.9045 - val_loss: 0.2573 - val_acc: 0.9284
Epoch 9/10
1415/1415 (==============================) - 76s 53ms/step - loss: 0.3220 - acc: 0.9087 - val_loss: 0.2537 - val_acc: 0.9323
Epoch 10/10
1415/1415 (==============================) - 76s 54ms/step - loss: 0.2997 - acc: 0.9150 - val_loss: 0.2582 - val_acc: 0.9323
A precisão do modelo é de 93,23%. Vamos aprender a fazer previsões e dar uma olhada na matriz de confusão.
Fazendo previsões
Podemos usar opredict_generator
função para fazer previsões em um novo conjunto de dados. Vamos fazer previsões para o nosso conjunto de dados de validação. O predict_generator
A função precisa de um argumento de etapa, que é o número de vezes que o gerador será chamado.
Podemos calcular o número de etapas conhecendo o tamanho do lote e o tamanho do conjunto de dados de validação.
df_validation <- df(-id_train,)
n_steps <- nrow(df_validation)/32 + 1
Podemos então usar o predict_generator
função:
predictions <- predict_generator(
mannequin,
ds_validation,
steps = n_steps
)
str(predictions)
num (1:19424, 1:30) 1.22e-13 7.30e-19 5.29e-10 6.66e-22 1.12e-17 ...
Isso produzirá uma matriz com 30 colunas – uma para cada palavra e n_steps*batch_size número de linhas. Observe que ele começa a repetir o conjunto de dados no ultimate para criar um lote completo.
Podemos calcular a classe prevista, tomando a coluna com a maior probabilidade, por exemplo.
lessons <- apply(predictions, 1, which.max) - 1
Uma boa visualização da matriz de confusão é criar um diagrama aluvial:
library(dplyr)
library(alluvial)
x <- df_validation %>%
mutate(pred_class_id = head(lessons, nrow(df_validation))) %>%
left_join(
df_validation %>% distinct(class_id, class) %>% rename(pred_class = class),
by = c("pred_class_id" = "class_id")
) %>%
mutate(right = pred_class == class) %>%
rely(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
)

Podemos ver no diagrama que o erro mais relevante que nosso modelo comete é classificar a “árvore” como “três”. Existem outros erros comuns, como classificar “Go” como “não”, “Up” como “Off”. Com precisão de 93% para 30 lessons e considerando os erros, podemos dizer que esse modelo é bastante razoável.
O modelo salvo ocupa 25 MB de espaço em disco, o que é razoável para uma área de trabalho, mas pode não estar em dispositivos pequenos. Poderíamos treinar um modelo menor, com menos camadas, e ver quanto o desempenho diminui.
Nas tarefas de reconhecimento de fala, também é comum fazer algum tipo de aumento de dados, misturando um ruído de fundo no áudio falado, tornando -o mais útil para aplicativos reais, onde é comum ter outros sons irrelevantes acontecendo no ambiente.
O código completo para reproduzir este tutorial está disponível aqui.