根據影像化的聲音辨識方法,本文將音訊資料轉換成影像後,利用預訓練的 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
內容解密:
__init__方法初始化資料集,讀取指定路徑下的影像檔案,並建立檔名與標籤的對映關係。__getitem__方法根據索引傳回對應的影像資料和標籤,並應用指定的轉換操作。__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))
內容解密:
- 載入預訓練的ResNet-50模型。
- 凍結模型的引數,以避免在訓練過程中更新。
- 更換模型的輸出層,以適應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)
內容解密:
- 建立訓練和驗證資料集。
- 指定資料轉換操作,包括將影像資料轉換為張量,並進行標準化。
尋找最佳學習率
我們使用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)
內容解密:
- 指定損失函式和最佳化器。
- 使用
find_lr函式尋找最佳的學習率。 - 繪製學習率與損失的關係圖。
訓練結果
經過訓練,我們的模型達到了約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")
內容解密:
- 指定最佳化器和學習率。
- 訓練模型,並評估其在驗證集上的表現。
音訊資料增強技術
在處理音訊資料時,資料增強(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
內容解密:
- torchaudio.transforms.PadTrim:此函式用於調整音訊張量的長度,使其符合所需的長度。
- SoxEffectsChain:此類別允許建立一個 SoX 效果鏈,並將其應用於輸入的音訊檔案,以實作更複雜的音訊變換。
- 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
內容解密:
FrequencyMask類別初始化時需要指定max_width,代表最大遮罩寬度,以及use_mean,代表是否使用平均值填充遮罩區域。- 在
__call__方法中,隨機選擇一個起始位置start和結束位置end,並將該區域的數值設定為零或平均值。 - 使用
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
內容解密:
TimeMask類別的實作與FrequencyMask類別似,但作用於時間軸上。- 在
__call__方法中,隨機選擇一個起始位置start和結束位置end,並將該區域的數值設定為零或平均值。 - 使用
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)
內容解密:
PrecomputedTransformESC50類別繼承自Dataset,用於載入和處理 ESC-50 資料集。- 在
__init__方法中,定義了資料增強的流程,包括將圖片轉換為 Tensor,並隨機應用頻率遮罩和時間遮罩。 - 在
__getitem__方法中,載入圖片並應用資料增強流程,傳回處理後的圖片和對應的標籤。