返回文章列表

聲音辨識遷移學習與資料增強技術

本文探討如何利用遷移學習和資料增強技術提升聲音辨識模型的效能。首先,將音訊資料轉換為影像資料,並使用預訓練的 ResNet-50 模型進行遷移學習。接著,介紹了 SpecAugment 技術,包含頻率遮罩和時間遮罩,以及如何應用於 Mel

聲音辨識 深度學習

根據影像化的聲音辨識方法,本文將音訊資料轉換成影像後,利用預訓練的 ResNet-50 模型執行遷移學習,有效提升了模型在 ESC-50 資料集上的分類別準確度。過程中,凍結 ResNet-50 的大部分引數,僅調整輸出層以適應 50 個類別的分類別任務。此外,為了進一步提升模型的泛化能力和魯棒性,匯入了 SpecAugment 資料增強技術,對梅爾頻譜圖進行頻率和時間遮罩。透過調整遮罩的寬度和數量,可以模擬真實世界中常見的聲音失真和噪音幹擾,使模型在訓練過程中學習更具辨識力的特徵。

影像化聲音辨識:遷移學習的力量

在聲音辨識的領域中,將音訊資料轉換為影像資料並應用遷移學習技術,可以顯著提升模型的表現。本篇文章將探討如何利用預訓練的ResNet模型,對ESC-50資料集中的音訊資料進行分類別。

建立影像資料集

首先,我們需要將音訊資料轉換為影像資料,並建立一個新的資料集類別PrecomputedESC50。這個類別繼承自PyTorch的Dataset類別,並實作了__init____getitem____len__方法。

from PIL import Image
class PrecomputedESC50(Dataset):
    def __init__(self, path, dpi=50, transforms=None):
        files = Path(path).glob('{}*.wav.png'.format(dpi))
        self.items = [(f, int(f.name.split("-")[-1].replace(".wav.png", ""))) for f in files]
        self.length = len(self.items)
        if transforms is None:
            self.transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor()])
        else:
            self.transforms = transforms

    def __getitem__(self, index):
        filename, label = self.items[index]
        img = Image.open(filename)
        return (self.transforms(img), label)

    def __len__(self):
        return self.length

內容解密:

  1. __init__方法初始化資料集,讀取指定路徑下的影像檔案,並建立檔名與標籤的對映關係。
  2. __getitem__方法根據索引傳回對應的影像資料和標籤,並應用指定的轉換操作。
  3. __len__方法傳回資料集的大小。

應用遷移學習

接下來,我們將利用預訓練的ResNet-50模型,對ESC-50資料集進行分類別。首先,我們需要載入預訓練模型,並凍結其引數。

from torchvision import models
spec_resnet = models.resnet50(pretrained=True)
for param in spec_resnet.parameters():
    param.requires_grad = False
spec_resnet.fc = nn.Sequential(nn.Linear(spec_resnet.fc.in_features, 500),
                               nn.ReLU(),
                               nn.Dropout(), 
                               nn.Linear(500, 50))

內容解密:

  1. 載入預訓練的ResNet-50模型。
  2. 凍結模型的引數,以避免在訓練過程中更新。
  3. 更換模型的輸出層,以適應ESC-50資料集的分類別任務。

訓練模型

在訓練模型之前,我們需要建立資料載入器,並指定學習率。

esc50pre_train = PrecomputedESC50(PATH, transforms=torchvision.transforms.Compose([torchvision.transforms.ToTensor(),
                                                                                 torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                                                                                  std=[0.229, 0.224, 0.225])]))
esc50pre_valid = PrecomputedESC50(PATH, transforms=torchvision.transforms.Compose([torchvision.transforms.ToTensor(),
                                                                                 torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                                                                                  std=[0.229, 0.224, 0.225])]))
esc50_train_loader = DataLoader(esc50pre_train, batch_size=bs, shuffle=True)
esc50_valid_loader = DataLoader(esc50pre_valid, batch_size=bs, shuffle=False)

內容解密:

  1. 建立訓練和驗證資料集。
  2. 指定資料轉換操作,包括將影像資料轉換為張量,並進行標準化。

尋找最佳學習率

我們使用find_lr函式來尋找最佳的學習率。

loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(spec_resnet.parameters(), lr=1e-7)
logs, losses = find_lr(spec_resnet, loss_fn, optimizer)
plt.plot(logs, losses)

內容解密:

  1. 指定損失函式和最佳化器。
  2. 使用find_lr函式尋找最佳的學習率。
  3. 繪製學習率與損失的關係圖。

訓練結果

經過訓練,我們的模型達到了約80%的驗證準確率,明顯優於原始的AudioNet模型。

optimizer = optim.Adam(spec_resnet.parameters(), lr=[1e-2, 1e-4, 1e-8])
train(spec_resnet, optimizer, nn.CrossEntropyLoss(), esc50_train_loader, esc50_valid_loader, epochs=20, device="cuda")

內容解密:

  1. 指定最佳化器和學習率。
  2. 訓練模型,並評估其在驗證集上的表現。

音訊資料增強技術

在處理音訊資料時,資料增強(Data Augmentation)是提升模型準確度的重要手段。與影像處理類別似,音訊資料增強旨在透過對原始音訊資料進行變換,以增加模型的泛化能力。本章將探討在音訊領域中進行資料增強的方法。

音訊資料增強

在影像處理中,我們透過對圖片進行翻轉、裁剪等變換來提升模型的準確度。同樣地,在音訊處理中,我們也可以對原始音訊波形或其表示(如梅爾頻譜圖)進行變換,以達到資料增強的目的。

Torchaudio 變換

Torchaudio 提供了 transforms 模組,用於對輸入的音訊資料進行變換。然而,與 torchvision 相比,torchaudio 的變換功能相對較少。其中,torchaudio.transforms.PadTrim 是用於調整音訊張量的長度,使其符合所需的長度。若音訊張量過長,則進行修剪;若過短,則進行填充。

import torchaudio

# 載入音訊檔案
audio_tensor, rate = torchaudio.load("test.wav")
print(audio_tensor.shape)

# 修剪音訊張量至指定長度
trimmed_tensor = torchaudio.transforms.PadTrim(max_len=1000)(audio_tensor)

然而,若需要對音訊進行更複雜的變換,如增加回聲、雜訊或改變播放速度,則需要使用 SoX。

SoX效果鏈

SoX 是一個強大的音訊處理工具,而 torchaudio.sox_effects.SoxEffectsChain 允許我們建立一個 SoX 效果鏈,並將其應用於輸入的音訊檔案。

import torchaudio
from torch.utils.data import Dataset
from pathlib import Path

class ESC50WithPitchChange(Dataset):
    def __init__(self, path):
        # 取得路徑下的檔案清單
        files = Path(path).glob('*.wav')
        # 建立檔案名稱與標籤的列表
        self.items = [(f, f.name.split("-")[-1].replace(".wav", "")) for f in files]
        self.length = len(self.items)
        self.E = torchaudio.sox_effects.SoxEffectsChain()
        self.E.append_effect_to_chain("pitch", [0.5])  # 改變音調

    def __getitem__(self, index):
        filename, label = self.items[index]
        self.E.set_input_file(filename)
        audio_tensor, sample_rate = self.E.sox_build_flow_effects()
        return audio_tensor, label

    def __len__(self):
        return self.length

SpecAugment

除了對原始音訊進行變換外,我們還可以直接對梅爾頻譜圖進行變換。由於梅爾頻譜圖可以視為影像,因此可以使用影像變換技術。然而,需要謹慎選擇合適的變換方法,以避免破壞音訊的原始特徵。

# 對梅爾頻譜圖進行 SpecAugment
import numpy as np

def spec_augment(spec, num_mask=2, freq_masking_max_percentage=0.15, time_masking_max_percentage=0.3):
    spec = spec.copy()
    for _ in range(num_mask):
        freq_percentage = np.random.uniform(0.0, freq_masking_max_percentage)
        num_freqs_to_mask = int(freq_percentage * spec.shape[1])
        f0 = np.random.uniform(low=0.0, high=spec.shape[1] - num_freqs_to_mask)
        f0 = int(f0)
        spec[:, f0:f0 + num_freqs_to_mask] = 0

        time_percentage = np.random.uniform(0.0, time_masking_max_percentage)
        num_frames_to_mask = int(time_percentage * spec.shape[0])
        t0 = np.random.uniform(low=0.0, high=spec.shape[0] - num_frames_to_mask)
        t0 = int(t0)
        spec[t0:t0 + num_frames_to_mask, :] = 0

    return spec

內容解密:

  1. torchaudio.transforms.PadTrim:此函式用於調整音訊張量的長度,使其符合所需的長度。
  2. SoxEffectsChain:此類別允許建立一個 SoX 效果鏈,並將其應用於輸入的音訊檔案,以實作更複雜的音訊變換。
  3. SpecAugment:此技術直接對梅爾頻譜圖進行變換,以達到資料增強的目的。

音訊資料增強技術

在處理音訊資料時,資料增強是一種有效提升模型泛化能力的方法。透過對原始音訊資料進行各種變換,可以增加模型的訓練樣本數量,進而提高模型的準確性和穩定性。

SpecAugment 技術

2019 年,Google 發表了一篇論文《SpecAugment: A Simple Data Augmentation Method for Automatic Speech Recognition》,介紹了一種新的資料增強方法 SpecAugment。該方法透過對 Mel 頻譜圖進行三種不同的變換:時間扭曲(Time Warping)、頻率遮罩(Frequency Masking)和時間遮罩(Time Masking),取得了當時最先進的語音辨識結果。

頻率遮罩(Frequency Masking)

頻率遮罩是一種簡單而有效的資料增強技術。該方法隨機選擇一個或多個頻率範圍,並將其設定為零或平均值,以模擬現實世界中可能出現的頻率幹擾或損失。這種變換可以強制模型學習更 robust 的特徵,而不是簡單地記住原始輸入。

實作頻率遮罩

class FrequencyMask(object):
    def __init__(self, max_width, use_mean=True):
        self.max_width = max_width
        self.use_mean = use_mean

    def __call__(self, tensor):
        start = random.randrange(0, tensor.shape[2])
        end = start + random.randrange(1, self.max_width)
        if self.use_mean:
            tensor[:, start:end, :] = tensor.mean()
        else:
            tensor[:, start:end, :] = 0
        return tensor

內容解密:

  1. FrequencyMask 類別初始化時需要指定 max_width,代表最大遮罩寬度,以及 use_mean,代表是否使用平均值填充遮罩區域。
  2. __call__ 方法中,隨機選擇一個起始位置 start 和結束位置 end,並將該區域的數值設定為零或平均值。
  3. 使用 tensor.mean() 作為填充值,可以保持頻譜圖的整體數值範圍穩定。

時間遮罩(Time Masking)

時間遮罩與頻率遮罩類別似,但其作用於時間軸上。該方法隨機選擇一個或多個時間範圍,並將其設定為零或平均值,以模擬現實世界中可能出現的時間幹擾或損失。

實作時間遮罩

class TimeMask(object):
    def __init__(self, max_width, use_mean=True):
        self.max_width = max_width
        self.use_mean = use_mean

    def __call__(self, tensor):
        start = random.randrange(0, tensor.shape[1])
        end = start + random.randrange(0, self.max_width)
        if self.use_mean:
            tensor[:, :, start:end] = tensor.mean()
        else:
            tensor[:, :, start:end] = 0
        return tensor

內容解密:

  1. TimeMask 類別的實作與 FrequencyMask 類別似,但作用於時間軸上。
  2. __call__ 方法中,隨機選擇一個起始位置 start 和結束位置 end,並將該區域的數值設定為零或平均值。
  3. 使用 tensor.mean() 作為填充值,可以保持頻譜圖的整體數值範圍穩定。

結合頻率遮罩和時間遮罩

為了進一步增強模型的泛化能力,可以結合頻率遮罩和時間遮罩兩種技術。

實作 PrecomputedTransformESC50

class PrecomputedTransformESC50(Dataset):
    def __init__(self, path, dpi=50):
        files = Path(path).glob('{}*.wav.png'.format(dpi))
        self.items = [(f, f.name.split("-")[-1].replace(".wav.png", "")) for f in files]
        self.length = len(self.items)
        self.transforms = transforms.Compose([
            transforms.ToTensor(),
            RandomApply([FrequencyMask(self.max_freqmask_width)], p=0.5),
            RandomApply([TimeMask(self.max_timemask_width)], p=0.5)
        ])

    def __getitem__(self, index):
        filename, label = self.items[index]
        img = Image.open(filename)
        return (self.transforms(img), label)

內容解密:

  1. PrecomputedTransformESC50 類別繼承自 Dataset,用於載入和處理 ESC-50 資料集。
  2. __init__ 方法中,定義了資料增強的流程,包括將圖片轉換為 Tensor,並隨機應用頻率遮罩和時間遮罩。
  3. __getitem__ 方法中,載入圖片並應用資料增強流程,傳回處理後的圖片和對應的標籤。