转换成立体声
一些声音文件是单声道(即1个音频通道),而大多数则是立体声(即2个音频通道)。由于我们的模型期望所有项目都具有相同的尺寸,因此我们将第一个通道复制到第二个通道,从而将单声道文件转换为立体声。
#----------------------------#Convertthegivenaudiotothedesirednumberofchannels#----------------------------defrechannel(aud, new_channel): sig, sr=audif (sig.shape[0] ==new_channel): #Nothingtodoreturnaudif (new_channel==1): #Convertfromstereotomonobyselectingonlythefirstchannelresig=sig[:1, :] else: #Convertfrommonotostereobyduplicatingthefirstchannelresig=torch.cat([sig, sig]) return ((resig, sr))
标准化采样率
一些声音文件以48000Hz的采样率采样,而大多数声音文件以44100Hz的采样率采样。这意味着对于某些声音文件,1秒音频的数组大小为48000,而对于其他声音文件,其数组大小为44100。,我们必须将所有音频标准化并将其转换为相同的采样率,以使所有阵列具有相同的尺寸。
#----------------------------#SinceResampleappliestoasinglechannel, weresampleonechannelatatime#----------------------------defresample(aud, newsr): sig, sr=audif (sr==newsr): #Nothingtodoreturnaudnum_channels=sig.shape[0] #Resamplefirstchannelresig=torchaudio.transforms.Resample(sr, newsr)(sig[:1,:]) if (num_channels>1): #Resamplethesecondchannelandmergebothchannelsretwo=torchaudio.transforms.Resample(sr, newsr)(sig[1:,:]) resig=torch.cat([resig, retwo]) return ((resig, newsr))
调整为相同长度
然后,我们将所有音频样本的大小调整为具有相同的长度,方法是通过使用静默填充或通过截断其长度来延长其持续时间。我们将该方法添加到AudioUtil类中。
#----------------------------#Pad (ortruncate) thesignaltoafixedlength'max_ms'inmilliseconds#----------------------------defpad_trunc(aud, max_ms): sig, sr=audnum_rows, sig_len=sig.shapemax_len=sr//1000 * max_msif (sig_len>max_len): #Truncatethesignaltothegivenlengthsig=sig[:,:max_len] elif (sig_len<max_len): #Lengthofpaddingtoaddatthebeginningandendofthesignalpad_begin_len=random.randint(0, max_len-sig_len) pad_end_len=max_len-sig_len-pad_begin_len#Padwith0spad_begin=torch.zeros((num_rows, pad_begin_len)) pad_end=torch.zeros((num_rows, pad_end_len)) sig=torch.cat((pad_begin, sig, pad_end), 1) return (sig, sr)
数据扩充增广:时移
接下来,我们可以通过应用时间偏移将音频向左或向右移动随机量来对原始音频信号进行数据增广。在本文中,我将详细介绍此技术和其他数据增广技术。
#----------------------------#Shiftsthesignaltotheleftorrightbysomepercent. Valuesattheend#are'wrapped around'tothestartofthetransformedsignal. #----------------------------deftime_shift(aud, shift_limit): sig,sr=aud_, sig_len=sig.shapeshift_amt=int(random.random() *shift_limit*sig_len) return (sig.roll(shift_amt), sr)
梅尔频谱图
我们将增广后的音频转换为梅尔频谱图。它们捕获了音频的基本特征,并且通常是将音频数据输入到深度学习模型中的最合适方法。
#----------------------------#GenerateaSpectrogram#----------------------------defspectro_gram(aud, n_mels=64, n_fft=1024, hop_len=None): sig,sr=audtop_db=80#spechasshape [channel, n_mels, time], wherechannelismono, stereoetcspec=transforms.MelSpectrogram(sr, n_fft=n_fft, hop_length=hop_len, n_mels=n_mels)(sig) #Converttodecibelsspec=transforms.AmplitudeToDB(top_db=top_db)(spec) return (spec)
数据扩充:时间和频率屏蔽
现在我们可以进行另一轮扩充,这次是在Mel频谱图上,而不是在原始音频上。我们将使用一种称为SpecAugment的技术,该技术使用以下两种方法:
频率屏蔽-通过在频谱图上添加水平条来随机屏蔽一系列连续频率。
时间掩码-与频率掩码类似,不同之处在于,我们使用竖线从频谱图中随机地遮挡了时间范围。
#----------------------------#AugmenttheSpectrogrambymaskingoutsomesectionsofitinboththefrequency#dimension (ie. horizontalbars) andthetimedimension (verticalbars) toprevent#overfittingandtohelpthemodelgeneralisebetter. Themaskedsectionsare#replacedwiththemeanvalue. #----------------------------defspectro_augment(spec, max_mask_pct=0.1, n_freq_masks=1, n_time_masks=1): _, n_mels, n_steps=spec.shapemask_value=spec.mean() aug_spec=specfreq_mask_param=max_mask_pct*n_melsfor_inrange(n_freq_masks): aug_spec=transforms.FrequencyMasking(freq_mask_param)(aug_spec, mask_value) time_mask_param=max_mask_pct*n_stepsfor_inrange(n_time_masks): aug_spec=transforms.TimeMasking(time_mask_param)(aug_spec, mask_value) returnaug_spec
自定义数据加载器
现在,我们已经定义了所有预处理转换函数,我们将定义一个自定义的Pytorch Dataset对象。
要将数据提供给使用Pytorch的模型,我们需要两个对象:
一个自定义Dataset对象,该对象使用所有音频转换来预处理音频文件并一次准备一个数据项。
内置的DataLoader对象,该对象使用Dataset对象来获取单个数据项并将其打包为一批数据。
fromtorch.utils.dataimportDataLoader, Dataset, random_splitimporttorchaudio#----------------------------#SoundDataset#----------------------------classSoundDS(Dataset): def__init__(self, df, data_path): self.df=dfself.data_path=str(data_path) self.duration=4000self.sr=44100self.channel=2self.shift_pct=0.4#----------------------------#Numberofitemsindataset#----------------------------def__len__(self): returnlen(self.df) #----------------------------#Geti'th item in dataset# ----------------------------def __getitem__(self, idx):# Absolute file path of the audio file - concatenate the audio directory with# the relative pathaudio_file = self.data_path + self.df.loc[idx, 'relative_path']# Get the Class IDclass_id = self.df.loc[idx, 'classID']aud = AudioUtil.open(audio_file)# Some sounds have a higher sample rate, or fewer channels compared to the# majority. So make all sounds have the same number of channels and same# sample rate. Unless the sample rate is the same, the pad_trunc will still# result in arrays of different lengths, even though the sound duration is# the same.reaud = AudioUtil.resample(aud, self.sr)rechan = AudioUtil.rechannel(reaud, self.channel)dur_aud = AudioUtil.pad_trunc(rechan, self.duration)shift_aud = AudioUtil.time_shift(dur_aud, self.shift_pct)sgram = AudioUtil.spectro_gram(shift_aud, n_mels=64, n_fft=1024, hop_len=None)aug_sgram = AudioUtil.spectro_augment(sgram, max_mask_pct=0.1, n_freq_masks=2, n_time_masks=2)return aug_sgram, class_id