Utilisation du Deep Learning en Python pour la classification des données audio

Bienvenue dans une autre édition de « The Kaggle Blueprints », où nous analyserons les solutions gagnantes des concours Kaggle pour tirer des leçons que nous pouvons appliquer à nos propres projets de science des données.

Cette édition passera en revue les techniques et approches issues du concours « BirdCLEF 2022 » , qui s’est achevé en mai 2022.

Énoncé du problème : classification audio avec changement de domaine

L’objectif du concours “BirdCLEF 2022” était d’identifier les espèces d’oiseaux hawaïens par le son. Les concurrents ont reçu de courts fichiers audio d’appels d’oiseaux uniques et ont été invités à prédire si un oiseau spécifique était présent dans un enregistrement plus long.

Contrairement à un problème de classification audio vanille, cette compétition a ajouté de la saveur avec les défis suivants :

  • Changement de domaine – Les données d’entraînement consistaient en des enregistrements audio propres d’un seul appel d’oiseau séparés de tout son supplémentaire (quelques secondes, différentes longueurs). Cependant, les données de test consistaient en des enregistrements « impurs » plus longs (1 minute) pris « dans la nature » et contenaient différents sons autres que les cris d’oiseaux (par exemple, vent, pluie, autres animaux, etc.).
Changement de domaine dans les données audio
Changement de domaine dans les données audio
  • Déséquilibre de classe/Apprentissage à quelques coups — Comme certains oiseaux sont moins communs que d’autres, nous avons affaire à une distribution de classe à longue queue où certains oiseaux n’ont qu’un seul échantillon.
Distribution de classe à longue queue
Distribution de classe à longue queue

Insérez vos données ici! — Pour suivre cet article, votre ensemble de données devrait ressembler à ceci :

Insérez vos données ici : Comment formater la trame de données de votre ensemble de données audio
Insérez vos données ici : Comment formater la trame de données de votre ensemble de données audio

Aborder la classification audio comme un problème de classification d’images avec Deep Learning

Une approche populaire parmi les concurrents à ce problème de classification audio consistait à :

  1. Conversion du problème de classification audio en un problème de classification d’image en convertissant l’audio de la forme d’onde en un spectrogramme Mel et en appliquant un modèle d’apprentissage en profondeur
  2. Application d’augmentations de données aux données audio sous forme d’onde et dans les spectrogrammes pour lutter contre le décalage de domaine et le déséquilibre de classe
  3. Affiner un modèle de classification d’images pré-entraîné pour lutter contre le déséquilibre des classes

Cet article utilisera PyTorch (version 1.13.0) pour le framework Deep Learning et torchaudio(version 0.13.0) et librosa(version 0.10.0) pour le traitement audio. De plus, nous utiliserons timm(version 0.6.12) pour affiner les modèles d’image pré-formés.

# Framework Deep Learning 
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.optim import lr_scheduler
from torch.utils.data import Dataset, DataLoader

# Traitement audio
import torchaudio
import torchaudio. se transforme en T
import librosa

# Modèles d'image pré-formés
import timm

Préparatifs : Se familiariser avec les données audio

Avant de commencer à résoudre un problème de classification audio, familiarisons-nous d’abord avec l’utilisation des données audio. Vous pouvez charger l’audio et son taux d’échantillonnage à partir de différents formats de fichiers (par exemple, .wav, .ogg, etc.) avec la .load()méthode de la torchaudiobibliothèque ou de la librosabibliothèque.

PATH = "audio_example.wav" 

# Charger un exemple de fichier audio avec torchaudio
original_audio, sample_rate = torchaudio.load(PATH)

# Charger un exemple de fichier audio avec librosa
original_audio, sample_rate = librosa.load(PATH,
sr = None) # Gotcha : Réglez sr sur Aucun pour obtenir le taux d'échantillonnage d'origine. Sinon, la valeur par défaut est 22050

Si vous souhaitez écouter l’audio chargé directement dans un cahier Jupyter pour des explorations, le code suivant vous fournira un lecteur audio.

# Jouez l'audio dans le bloc-notes Jupyter 
à partir d' IPython.display import Audio

Audio(data = original_audio, rate = sample_rate)
Affichage du lecteur audio pour les données chargées dans le bloc-notes Jupyter
Affichage du lecteur audio pour les données chargées dans le bloc-notes Jupyter

La librosabibliothèque fournit également diverses méthodes pour afficher rapidement les données audio à des fins d’exploration. Si vous aviez l’habitude torchaudiode charger le fichier audio, assurez-vous de convertir les tenseurs en tableaux NumPy.

importer librosa. afficher  comme dsp 
dsp. waveshow (original_audio, sr = sample_rate);
Données audio originales du mot "stop" sous forme d'onde de l'ensemble de données "Speech Commands" [1]
Données audio originales du mot “stop” sous forme d’onde de l’ensemble de données “Speech Commands” [0]

Étape 1 : Convertir le problème de classification audio en problème de classification d’image

Une méthode populaire pour modéliser les données audio avec un modèle d’apprentissage en profondeur consiste à convertir le problème de “l’audition par ordinateur” en un problème de vision par ordinateur [2]. Plus précisément, la forme d’onde audio est convertie en un spectrogramme Mel (qui est un type d’image) comme indiqué ci-dessous.

Conversion d'un fichier audio de forme d'onde (domaine temporel) en spectrogramme Mel (domaine fréquentiel)
Conversion d’un fichier audio de forme d’onde (domaine temporel) en spectrogramme Mel (domaine fréquentiel)

Habituellement, vous utiliseriez une transformée de Fourier rapide (FFT) pour convertir par calcul un signal audio du domaine temporel (forme d’onde) au domaine fréquentiel (spectrogramme).

Cependant, la FFT vous donnera les composantes de fréquence globales pour toute la série temporelle du signal audio dans son ensemble. Ainsi, vous perdez les informations temporelles lors de la conversion des données audio du domaine temporel au domaine fréquentiel.

Au lieu de la FFT, vous pouvez utiliser la transformée de Fourier à court terme (STFT) pour conserver les informations temporelles. La STFT est une variante de la FFT qui décompose le signal audio en sections plus petites en utilisant une fenêtre temporelle glissante. Il prend la FFT sur chaque section, puis les combine.

  • n_fft—longueur de la fenêtre glissante (par défaut : 2048)
  • hop_length— nombre d’échantillons par lesquels faire glisser la fenêtre (par défaut : 512). Cela hop_lengthaura un impact direct sur la taille de l’image résultante. Si vos données audio ont une longueur fixe et que vous souhaitez convertir la forme d’onde en une taille d’image fixe, vous pouvez définirhop_length = audio_length // (image_size[1] — 1)
Transformée de Fourier à court terme (STFT)
Transformée de Fourier à court terme (STFT)

Ensuite, vous convertirez l’amplitude en décibels et classerez les fréquences selon l’échelle Mel. A cet effet, n_melsest le nombre de bandes de fréquence (Mel bins). Ce sera la hauteur du spectrogramme résultant.

Convertissez l'amplitude en décibel et appliquez Mel binning au spectre
Convertissez l’amplitude en décibels et appliquez Mel binning au spectre

Ci-dessous, vous pouvez voir un exemple de PyTorch Datasetqui charge un fichier audio et convertit la forme d’onde en un spectrogramme Mel après quelques étapes de prétraitement.

class  AudioDataset ( Dataset ): 
def __init__ ( self,
df,
target_sample_rate= 32000 ,
audio_length
wave_transforms= None ,
spec_transforms= None ):
self.df = df
self.file_paths = df[ 'file_path' ].values
​​self.labels = df[ [ 'class_0' , ..., 'class_N' ]].values
​​self.target_sample_rate = target_sample_rate
self.num_samples = target_sample_rate * audio_length
self.wave_transforms = wave_transforms
self.spec_transforms = spec_transforms

def __len__ ( self ):
return len (self.df)

def __getitem__ ( self, index ):

# Charger l'audio du fichier à la forme d'onde
audio, sample_rate = torchaudio.load(self.file_paths[ index])

# Convertir en mono
audio = torch.mean(audio, axis= 0 )

# Rééchantillonner
si sample_rate != self.target_sample_rate:
resample = T.Resample(sample_rate, self.target_sample_rate)
audio = resample(audio)

# Ajuster le nombre d'échantillons
if audio.shape[ 0 ] > self.num_samples :
# Crop
audio = audio[:self.num_samples]
elif audio.shape[ 0 ] < self.num_samples :
# Pad
audio = F.pad(audio, ( 0 , self. num_samples - audio.shape[ 0 ]))


# Ajoutez tout prétraitement que vous aimez ici
# (par exemple, suppression du bruit, etc.)
...

# Ajoutez toutes les augmentations de données pour la forme d'onde que vous aimez ici
# (par exemple, injection de bruit, temps de décalage, changer la vitesse et la hauteur)
...

# Convertir en spectrogramme Mel
melspectrogram = T.MelSpectrogram(sample_rate = self.target_sample_rate,
n_mels = 128 ,
n_fft = 2048 ,
hop_length = 512 )
melspec = melspectrogram(audio)

# Ajoutez toutes les augmentations de données pour le spectrogramme que vous aimez ici
# (par exemple, Mixup, cutmix, masquage temporel, masquage fréquentiel)
...

return { "image" : torch.stack([melspec]),
"label" : torch.tensor(self.labels[index]). flottant ()}

Votre ensemble de données résultant devrait produire des échantillons qui ressemblent à ceci avant que nous le transmettions au réseau de neurones :

Exemple de structure de l’ensemble de données audio

Étape 2 : appliquer des augmentations aux données audio

Une technique pour relever les défis de ce concours de changement de domaine et de déséquilibre de classe consistait à appliquer des augmentations de données aux données d’entraînement [5, 8, 10, 11]. Vous pouvez appliquer des augmentations de données pour les données audio dans la forme d’onde et le spectrogramme. La torchaudio bibliothèque fournit déjà de nombreuses augmentations de données différentes pour les données audio.

Les techniques d’augmentation de données populaires pour les données audio sous forme d’onde (domaine temporel) sont :

  • L’injection de bruit comme le bruit blanc, le bruit coloré ou le bruit de fond ( AddNoise)
  • Temps de changement
  • Modification de la vitesse ( Speed; alternativement utiliser TimeStretchdans le domaine fréquentiel)
  • Modification de la hauteur ( PitchShift)
Vue d'ensemble des différentes techniques d'augmentation de données pour l'audio en forme d'onde : injection de bruit (bruit blanc, bruit coloré, bruit de fond), temps de décalage, changement de vitesse et de hauteur
Vue d’ensemble des différentes techniques d’augmentation de données pour l’audio en forme d’onde : injection de bruit (bruit blanc, bruit coloré, bruit de fond), temps de décalage, changement de vitesse et de hauteur

Les techniques d’augmentation de données populaires pour les données audio dans le spectrogramme (domaine fréquentiel) sont :

  • Techniques d’augmentation d’image populaires comme Mixup [13] ou Cutmix [3]
Augmentation des données pour le spectrogramme : confusion [4]
Augmentation des données pour le spectrogramme : Mixup [13]
  • SpecAugment [7] ( FrequencyMaskinget TimeMasking)
Augmentation des données pour le spectrogramme : SpecAugment [2]
Augmentation des données pour le spectrogramme : SpecAugment [7]

Comme vous pouvez le constater, tout en fournissant de nombreuses augmentations audio, torchaudio ne fournit pas toutes les augmentations de données proposées.

Dans l’exemple Datasetde classe PyTorch précédent, vous pouvez appliquer les augmentations de données comme suit :

class  AudioDataset ( Dataset ): 
def __init__ ( self,
df,
target_sample_rate= 32000 ,
audio_length ):
self.df = df
self.file_paths = df[ 'file_path' ].values
​​self.labels = df[[ 'class_0' , .. ., 'class_N' ]].values
​​self.target_sample_rate = target_sample_rate
self.num_samples = target_sample_rate * audio_length

def __len__ ( self ):
return len (self.df)

def __getitem__ ( self, index ):

# Charger l'audio du fichier vers
l'audio de forme d'onde, sample_rate = torchaudio.load(self.file_paths[index])

# Ajouter tout prétraitement que vous aimez ici
# (par exemple, conversion en mono, rééchantillonnage, ajustement de la taille, suppression du bruit, etc.)
...

# Ajoutez toutes les augmentations de données pour la forme d'onde que vous aimez ici
# (par exemple, injection de bruit, temps de décalage, changement de vitesse et de hauteur)
wave_transforms = T.PitchShift(sample_rate, 4 )
audio = wave_transforms(audio)

# Convertir en spectrogramme Mel
melspec = ...

# Ajouter toutes les augmentations de données pour le spectrogramme que vous aimez ici
# (par exemple, Mixup, cutmix, masquage temporel, masquage fréquentiel)
spec_transforms = T.FrequencyMasking(freq_mask_param= 80 )
melspec = spec_transforms(melspec)

return { "image" : torch.stack([melspec]),
"label" : torch .tenseur(self.labels[index]). flottant ()}

Étape 3 : affiner un modèle de classification d’images pré-entraîné pour un apprentissage en quelques prises de vue

Dans cette compétition, on a affaire à un déséquilibre de classe. Comme certaines classes n’ont qu’un seul échantillon, nous sommes confrontés à un problème d’apprentissage en quelques coups. Nakamura et Harada [6] ont montré en 2019 que le réglage fin pouvait être une approche efficace de l’apprentissage en quelques coups.

De nombreux concurrents [2, 5, 8, 10, 11] ont affiné des modèles de classification d’images pré-entraînés tels que

  • EfficientNet (par exemple, tf_efficientnet_b3_ns) [9],
  • SE-ResNext (par exemple, se_resnext50_32x4d) [3],
  • NFNet (par exemple, eca_nfnet_l0) [1]

Vous pouvez charger n’importe quel modèle de classification d’images pré-formé avec la timm bibliothèque pour un réglage fin. Assurez-vous de régler in_chans = 1car nous ne travaillons pas avec des images à 3 canaux mais des spectrogrammes Mel à 1 canal.

class  AudioModel (nn.Module): 
def __init__ ( self,
model_name = 'tf_efficientnet_b3_ns' ,
pretrained = True ,
num_classes ):
super (AudioModel, self).__init__()

self.model = timm.create_model(model_name,
pretrained = pretrained,
in_chans = 1 )
self.in_features = self.model.classifier.in_features
self.model.classifier = nn.Sequential(
nn.Linear(self.in_features, num_classes)
)

def forward ( self, images ):
logits = self.model(images)
return logits

D’autres concurrents ont signalé des succès grâce à des modèles de réglage fin pré-formés sur des problèmes de classification audio similaires [4, 10].

Le réglage fin est effectué avec un ordonnanceur de taux d’apprentissage de recuit cosinus ( CosineAnnealingLR) pour quelques époques [2, 8].

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, 
T_max = ..., # Nombre maximal d'itérations.
eta_min = ...) # Taux d'apprentissage minimal.
PyTorch Cosine Annealing / Decay Learning Rate Scheduler (Image de l'auteur, initialement publiée dans "A Visual Guide to Learning Rate Schedulers in PyTorch")
PyTorch Cosine Annealing / Decay Learning Rate Scheduler (Image de l’auteur, initialement publiée dans “A Visual Guide to Learning Rate Schedulers in PyTorch” )

Résumé

Il y a beaucoup plus de leçons à tirer de l’examen des ressources d’apprentissage que les Kagglers ont créées au cours du concours “BirdCLEF 2022” . Il existe également de nombreuses solutions différentes pour ce type d’énoncé de problème.

Dans cet article, nous nous sommes concentrés sur l’approche générale qui était populaire parmi de nombreux concurrents :

  1. Conversion du problème de classification audio en un problème de classification d’image en convertissant l’audio de la forme d’onde en un spectrogramme Mel et en appliquant un modèle d’apprentissage en profondeur
  2. Application d’augmentations de données aux données audio sous forme d’onde et dans les spectrogrammes pour lutter contre le décalage de domaine et le déséquilibre de classe
  3. Affiner un modèle de classification d’images pré-entraîné pour lutter contre le déséquilibre des classes

Leave a Reply

Your email address will not be published. Required fields are marked *