您现在的位置是:网站首页> 编程资料编程资料

基于Pytorch实现的声音分类实例代码_python_

2023-05-26 355人已围观

简介 基于Pytorch实现的声音分类实例代码_python_

前言

本章我们来介绍如何使用Pytorch训练一个区分不同音频的分类模型,例如你有这样一个需求,需要根据不同的鸟叫声识别是什么种类的鸟,这时你就可以使用这个方法来实现你的需求了。

源码地址:https://github.com/yeyupiaoling/AudioClassification-Pytorch

环境准备

主要介绍libsora,PyAudio,pydub的安装,其他的依赖包根据需要自行安装。

  • Python 3.7
  • Pytorch 1.10.0

安装libsora

最简单的方式就是使用pip命令安装,如下:

pip install pytest-runner pip install librosa==0.9.1 

注意: 如果pip命令安装不成功,那就使用源码安装,下载源码:https://github.com/librosa/librosa/releases/, windows的可以下载zip压缩包,方便解压。

pip install pytest-runner tar xzf librosa-<版本号>.tar.gz 或者 unzip librosa-<版本号>.tar.gz cd librosa-<版本号>/ python setup.py install 

如果出现 libsndfile64bit.dll': error 0x7e错误,请指定安装版本0.6.3,如 pip install librosa==0.6.3

安装ffmpeg, 下载地址:http://blog.gregzaal.com/how-to-install-ffmpeg-on-windows/,笔者下载的是64位,static版。
然后到C盘,笔者解压,修改文件名为 ffmpeg,存放在 C:\Program Files\目录下,并添加环境变量 C:\Program Files\ffmpeg\bin

最后修改源码,路径为 C:\Python3.7\Lib\site-packages\audioread\ffdec.py,修改32行代码,如下:

COMMANDS = ('C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe', 'avconv') 

安装PyAudio

使用pip安装命令,如下:

pip install pyaudio 

在安装的时候需要使用到C++库进行编译,如果读者的系统是windows,Python是3.7,可以在这里下载whl安装包,下载地址:https://github.com/intxcc/pyaudio_portaudio/releases

安装pydub

使用pip命令安装,如下:

pip install pydub 

训练分类模型

把音频转换成训练数据最重要的是使用了librosa,使用librosa可以很方便得到音频的梅尔频谱(Mel Spectrogram),使用的API为 librosa.feature.melspectrogram(),输出的是numpy值。关于梅尔频谱具体信息读者可以自行了解,跟梅尔频谱同样很重要的梅尔倒谱(MFCCs)更多用于语音识别中,对应的API为 librosa.feature.mfcc()。同样以下的代码,就可以获取到音频的梅尔频谱。

wav, sr = librosa.load(data_path, sr=16000) features = librosa.feature.melspectrogram(y=wav, sr=sr, n_fft=400, n_mels=80, hop_length=160, win_length=400) features = librosa.power_to_db(features, ref=1.0, amin=1e-10, top_db=None) 

生成数据列表

生成数据列表,用于下一步的读取需要,audio_path为音频文件路径,用户需要提前把音频数据集存放在dataset/audio目录下,每个文件夹存放一个类别的音频数据,每条音频数据长度在3秒以上,如 dataset/audio/鸟叫声/······audio是数据列表存放的位置,生成的数据类别的格式为 音频路径\t音频对应的类别标签,音频路径和标签用制表符 \t分开。读者也可以根据自己存放数据的方式修改以下函数。

Urbansound8K 是目前应用较为广泛的用于自动城市环境声分类研究的公共数据集,包含10个分类:空调声、汽车鸣笛声、儿童玩耍声、狗叫声、钻孔声、引擎空转声、枪声、手提钻、警笛声和街道音乐声。数据集下载地址:https://zenodo.org/record/1203745/files/UrbanSound8K.tar.gz。以下是针对Urbansound8K生成数据列表的函数。如果读者想使用该数据集,请下载并解压到 dataset目录下,把生成数据列表代码改为以下代码。

# 生成数据列表 def get_data_list(audio_path, list_path): sound_sum = 0 audios = os.listdir(audio_path) f_train = open(os.path.join(list_path, 'train_list.txt'), 'w') f_test = open(os.path.join(list_path, 'test_list.txt'), 'w') for i in range(len(audios)): sounds = os.listdir(os.path.join(audio_path, audios[i])) for sound in sounds: if '.wav' not in sound:continue sound_path = os.path.join(audio_path, audios[i], sound) t = librosa.get_duration(filename=sound_path) # 过滤小于2.1秒的音频 if t >= 2.1: if sound_sum % 100 == 0: f_test.write('%s\t%d\n' % (sound_path, i)) else: f_train.write('%s\t%d\n' % (sound_path, i)) sound_sum += 1 print("Audio:%d/%d" % (i + 1, len(audios))) f_test.close() f_train.close() if __name__ == '__main__': get_data_list('dataset/UrbanSound8K/audio', 'dataset') 

创建 reader.py用于在训练时读取数据。编写一个 CustomDataset类,用读取上一步生成的数据列表。

class CustomDataset(Dataset): def __init__(self, data_list_path, model='train', sr=16000, chunk_duration=3): super(CustomDataset, self).__init__() with open(data_list_path, 'r') as f: self.lines = f.readlines() self.model = model self.sr = sr self.chunk_duration = chunk_duration def __getitem__(self, idx): try: audio_path, label = self.lines[idx].replace('\n', '').split('\t') spec_mag = load_audio(audio_path, mode=self.model, sr=self.sr, chunk_duration=self.chunk_duration) return spec_mag, np.array(int(label), dtype=np.int64) except Exception as ex: print(f"[{datetime.now()}] 数据: {self.lines[idx]} 出错,错误信息: {ex}", file=sys.stderr) rnd_idx = np.random.randint(self.__len__()) return self.__getitem__(rnd_idx) def __len__(self): return len(self.lines) 

下面是在训练时或者测试时读取音频数据,训练时对转换的梅尔频谱数据随机裁剪,如果是测试,就取前面的,最好要执行归一化。

def load_audio(audio_path, mode='train', sr=16000, chunk_duration=3): # 读取音频数据 wav, sr_ret = librosa.load(audio_path, sr=sr) if mode == 'train': # 随机裁剪 num_wav_samples = wav.shape[0] # 数据太短不利于训练 if num_wav_samples < sr: raise Exception(f'音频长度不能小于1s,实际长度为:{(num_wav_samples / sr):.2f}s') num_chunk_samples = int(chunk_duration * sr) if num_wav_samples > num_chunk_samples + 1: start = random.randint(0, num_wav_samples - num_chunk_samples - 1) stop = start + num_chunk_samples wav = wav[start:stop] # 对每次都满长度的再次裁剪 if random.random() > 0.5: wav[:random.randint(1, sr // 2)] = 0 wav = wav[:-random.randint(1, sr // 2)] elif mode == 'eval': # 为避免显存溢出,只裁剪指定长度 num_wav_samples = wav.shape[0] num_chunk_samples = int(chunk_duration * sr) if num_wav_samples > num_chunk_samples + 1: wav = wav[:num_chunk_samples] features = librosa.feature.melspectrogram(y=wav, sr=sr, n_fft=400, n_mels=80, hop_length=160, win_length=400) features = librosa.power_to_db(features, ref=1.0, amin=1e-10, top_db=None) # 归一化 mean = np.mean(features, 0, keepdims=True) std = np.std(features, 0, keepdims=True) features = (features - mean) / (std + 1e-5) features = features.astype('float32') return features 

训练

接着就可以开始训练模型了,创建 train.py。我们搭建简单的卷积神经网络,如果音频种类非常多,可以适当使用更大的卷积神经网络模型。通过把音频数据转换成梅尔频谱。然后定义优化方法和获取训练和测试数据。要注意 args.num_classes参数的值,这个是类别的数量,要根据你数据集中的分类数量来修改。

def train(args): # 获取数据 train_dataset = CustomDataset(args.train_list_path, model='train') train_loader = DataLoader(dataset=train_dataset, batch_size=args.batch_size, shuffle=True, collate_fn=collate_fn, num_workers=args.num_workers) test_dataset = CustomDataset(args.test_list_path, model='eval') test_loader = DataLoader(dataset=test_dataset, batch_size=args.batch_size, collate_fn=collate_fn, num_workers=args.num_workers) # 获取分类标签 with open(args.label_list_path, 'r', encoding='utf-8') as f: lines = f.readlines() class_labels = [l.replace('\n', '') for l in lines] # 获取模型 device = torch.device("cuda") model = EcapaTdnn(num_classes=args.num_classes) model.to(device) # 获取优化方法 optimizer = torch.optim.Adam(params=model.parameters(), lr=args.learning_rate, weight_decay=5e-4) # 获取学习率衰减函数 scheduler = CosineAnnealingLR(optimizer, T_max=args.num_epoch) # 恢复训练 if args.resume is not None: model.load_state_dict(torch.load(os.path.join(args.resume, 'model.pth'))) state = torch.load(os.path.join(args.resume, 'model.state')) last_epoch = state['last_epoch'] optimizer_state = torch.load(os.path.join(args.resume, 'optimizer.pth')) optimizer.load_state_dict(optimizer_state) print(f'成功加载第 {last_epoch} 轮的模型参数和优化方法参数') # 获取损失函数 loss = torch.nn.CrossEntropyLoss() 

最后执行训练,每100个batch打印一次训练日志,训练一轮之后执行测试和保存模型,在测试时,把每个batch的输出都统计,最后求平均值。

 for epoch in range(args.num_epoch): loss_sum = [] accuracies = [] for batch_id, (spec_mag, label) in enumerate(train_loader): spec_mag = spec_mag.to(device) label = label.to(device).long() output = model(spec_mag) # 计算损失值 los = loss(output, label) optimizer.zero_grad() los.backward() optimizer.step() # 计算准确率 output = torch.nn.functional.softmax(output, dim=-1) output = output.data.cpu().numpy() output = np.argmax(output, axis=1) label = label.data.cpu().numpy() acc = np.mean((output == label).astype(int)) accuracies.append(acc) loss_sum.append(los) if batch_id % 100 == 0: print(f'[{datetime.now()}] Train epoch [{epoch}/{args.num_epoch}], batch: {batch_id}/{len(train_loader)}, ' f'lr: {scheduler.get_last_lr()[0]:.8f}, loss: {sum(loss_sum) / len(loss_sum):.8f}, ' f'accuracy: {sum(accuracies) / len(accuracies):.8f}') scheduler.step() 

每轮训练结束之后都会执行一次评估,和保存模型。评估会出来输出准确率,还保存了混合矩阵图片,如下。

提示: 本文由整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!

-六神源码网