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.).

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

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

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 à :
- 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
- 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
- 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 torchaudio
bibliothèque ou de la librosa
bibliothè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)

La librosa
bibliothèque fournit également diverses méthodes pour afficher rapidement les données audio à des fins d’exploration. Si vous aviez l’habitude torchaudio
de 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]](https://miro.medium.com/v2/resize:fit:700/0*8_PUF3tkRnIP3hKg.png)
É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.

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). Celahop_length
aura 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)

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

Ci-dessous, vous pouvez voir un exemple de PyTorch Dataset
qui 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 :

É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 utiliserTimeStretch
dans le domaine fréquentiel) - Modification de la hauteur (
PitchShift
)

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]](https://miro.medium.com/v2/resize:fit:700/0*yijgOMqL4JhKSXCc.png)
- SpecAugment [7] (
FrequencyMasking
etTimeMasking
)
![Augmentation des données pour le spectrogramme : SpecAugment [2]](https://miro.medium.com/v2/resize:fit:700/0*zCvyef9lsPte41Qw.png)
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 Dataset
de 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 = 1
car 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.

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 :
- 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
- 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
- Affiner un modèle de classification d’images pré-entraîné pour lutter contre le déséquilibre des classes