基于pytorch实现的声音分类(代码片段)

夜雨飘零1 夜雨飘零1     2023-04-13     713

关键词:

前言

本章我们来介绍如何使用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):.2fs')
        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()

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

预测

在训练结束之后,我们得到了一个模型参数文件,我们使用这个模型预测音频,在执行预测之前,需要把音频转换为梅尔频谱数据,最后输出的结果即为预测概率最大的标签。

parser = argparse.ArgumentParser(description=__doc__)
add_arg = functools.partial(add_arguments, argparser=parser)
add_arg('audio_path',       str,    'dataset/UrbanSound8K/audio/fold5/156634-5-2-5.wav', '图片路径')
add_arg('num_classes',      int,    10,                        '分类的类别数量')
add_arg('label_list_path',  str,    'dataset/label_list.txt',  '标签列表路径')
add_arg('model_path',       str,    'models/model.pth',        '模型保存的路径')
args = parser.parse_args()


# 获取分类标签
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)
model.load_state_dict(torch.load(args.model_path))
model.eval()


def infer():
    data = load_audio(args.audio_path, mode='infer')
    data = data[np.newaxis, :]
    data = torch.tensor(data, dtype=torch.float32, device=device)
    # 执行预测
    output = model(data)
    result = torch.nn.functional.softmax(output, dim=-1)
    result = result.data.cpu().numpy()
    # 显示图片并输出结果最大的label
    lab = np.argsort(result)[0][-1]
    print(f'音频:args.audio_path 的预测结果标签为:class_labels[lab]')


if __name__ == '__main__':
    infer()

其他

为了方便读取录制数据和制作数据集,这里提供了两个程序,首先是 record_audio.py,这个用于录制音频,录制的音频帧率为44100,通道为1,16bit。

import pyaudio
import wave
import uuid
from tqdm import tqdm
import os

s = input('请输入你计划录音多少秒:')

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = int(s)
WAVE_OUTPUT_FILENAME = "save_audio/%s.wav" % str(uuid.uuid1()).replace('-', '')

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("开始录音, 请说话......")

frames = []

for i in tqdm(range(0, int(RATE / CHUNK * RECORD_SECONDS))):
    data = stream.read(CHUNK)
    frames.append(data)

print("录音已结束!")

stream.stop_stream()
stream.close()
p.terminate()

if not os.path.exists('save_audio'):
    os.makedirs('save_audio')

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

print('文件保存在:%s' % WAVE_OUTPUT_FILENAME)
os.system('pause')

创建 crop_audio.py,在训练是只是裁剪前面的3秒的音频,所以我们要把录制的硬盘安装每3秒裁剪一段,把裁剪后音频存放在音频名称命名的文件夹中。最后把这些文件按照训练数据的要求创建数据列表和训练数据。

import os
import uuid
import wave
from pydub import AudioSegment


# 按秒截取音频
def get_part_wav(sound, start_time, end_time查看详情  

基于torchtext的pytorch文本分类(代码片段)

...量的预处理和大量的计算资源。在这篇文章中,我们使用PyTorch来进行多类文本分类,因为它有如下优点:PyTorch提供了一种强大的方法来实现复杂的模型体系结构和算法,其预处理量相对较少,计算资源(包括执行时 查看详情

pytorch+huggingface实现基于bert模型的文本分类(附代码)(代码片段)

从RNN到BERT一年前的这个时候,我逃课了一个星期,从澳洲飞去上海观看电竞比赛,也顺便在上海的一个公司联系了面试。当时,面试官问我对RNN的了解程度,我回答“没有了解”。但我把这个问题带回了学校,从此接触了... 查看详情

基于pytorch框架实现手写图片的分类(代码片段)

...模型4,训练模型5,结果评价​编辑总结前言基于pytorch框架实现手写图片的分类🍨本文为🔗365天深度学习训练营中的学习记录博客🍦参考文章:Pytorch实战|第P4周:猴痘病识别🍖原作者:K同学... 查看详情

pytorch实现基于charrnn的文本分类与生成(代码片段)

Pytorch实现基于CharRNN的文本分类与生成标签:deep-learningpytorchnlp1简介本篇主要介绍使用pytorch实现基于CharRNN来进行文本分类与内容生成所需要的相关知识,并最终给出完整的实现代码。2相关API的说明pytorch框架中每种网络... 查看详情

基于梅尔频谱的音频信号分类识别(pytorch)(代码片段)

本项目将使用Pytorch,实现一个简单的的音频信号分类器,可应用于机械信号分类识别,鸟叫声信号识别等应用场景。项目使用librosa进行音频信号处理,backbone使用mobilenet_v2,在Urbansound8K数据上,最终收敛... 查看详情

基于梅尔频谱的音频信号分类识别(pytorch)(代码片段)

本项目将使用Pytorch,实现一个简单的的音频信号分类器,可应用于机械信号分类识别,鸟叫声信号识别等应用场景。 项目使用librosa进行音频信号处理,backbone使用mobilenet_v2,在Urbansound8K数据上,最终收敛... 查看详情

pt之transformer:基于pytorch框架利用transformer算法针对imdb数据集实现情感分类的应用案例代码解析(代码片段)

PT之Transformer:基于PyTorch框架利用Transformer算法针对IMDB数据集实现情感分类的应用案例代码解析目录基于PyTorch框架利用Transformer算法针对IMDB数据集实现情感分类的应用案例思路设计(1)、数据准备(2)、数据预处理(3)、模型构建(... 查看详情

图像分类基于pytorch搭建lstm实现mnist手写数字体识别(双向lstm,附完整代码和数据集)(代码片段)

...sdn.net/AugustMe/article/details/128969138文章中,我们使用了基于PyTorch搭建LSTM实现MNIST手写数字体识别,LSTM是单向的,现在我们使用双向LSTM试一试效果,和之前的单向LSTM模型稍微有差别,请注意查看代码的变化。1.导入依赖库这些依赖... 查看详情

基于梅尔频谱的音频信号分类识别(pytorch)(代码片段)

基于梅尔频谱的音频信号分类识别(Pytorch)目录基于梅尔频谱的音频信号分类识别(Pytorch)1.项目结构2.环境配置3.音频识别基础知识(1)STFT和声谱图(spectrogram)  (2) 梅尔频谱(3) 梅尔频率倒谱MFCC(4)MFCC特征的过程4.数据处理(1)... 查看详情

pytorch多分类的语义分割(代码片段)

基于 segmentation_models.pytorch 实现的多分类的语义分割。github中的示例没有包含多分类的内容,网上资料比较少,就动手调了一下。segmentation_models.pytorch\\examples\\carssegmentation(camvid).ipynb里写了怎么做单分类的调用,用... 查看详情

lenet模型对cifar-10数据集分类pytorch(代码片段)

LeNet模型对CIFAR-10数据集分类【pytorch】目录LeNet网络模型CIFAR-10数据集Pytorch实现代码目录本文为针对CIFAR-10数据集的基于简单神经网络LeNet分类实现(pytorch实现)LeNet网络模型Tip:(以上为原始LeNet)为了更好的... 查看详情

图像分类猫狗分类实战—基于pytorch框架的迁移学习(resnet50模型实现分类实战)(代码片段)

...讲解的特别详细,适合零基础入门。一、前置准备1、pytorch环境搭建pytorch环境安装:StartLocally|PyTorch复制这个代码到pytorch到pycharm环境进行安装:2、数据集介绍在当前目录创建一个dataset文件夹,用来存放数据集。... 查看详情

基于pytorch的mlp(以垃圾邮件分类为例)(代码片段)

本文是《Pytorch深度学习入门与实战》,中国水利水电出版社一书中的例子数据集:链接:https://pan.baidu.com/s/1rODLa65Js4K5rZ1iMDQ2DA提取码:d8fc代码全连接神经网络(Multi-LayerPerception,MLP)或称多层感知机。... 查看详情

tensorflow--基于卷积神经网络实现男女声音分类识别(代码片段)

现在网上基本都没有对应的基于神经网络声音分类识别的较简单的教程,所以我打算通过我自己的思路,编写出一个实现男女声音识别的一个深度学习的模型。因为没有验证过该想法是否正确,所以想通过该博客来记录实验的结... 查看详情

如何用pytorch实现一个分类器?(代码片段)

学习目标了解分类器的任务和数据样式掌握如何用Pytorch实现一个分类器分类器任务和数据介绍构造一个将不同图像进行分类的神经网络分类器,对输入的图片进行判别并完成分类.本案例采用CIFAR10数据集作为原始图片数据.CIFAR10数... 查看详情

pytorch实现简单的分类器(代码片段)

作为目前越来越受欢迎的深度学习框架,pytorch基本上成了新人进入深度学习领域最常用的框架。相比于TensorFlow,pytorch更易学,更快上手,也可以更容易的实现自己想要的demo。今天的文章就从pytorch的基础开始,帮助大家实现成... 查看详情

嘿~全流程带你基于pytorch手撸图片分类“框架“--huclassify(代码片段)

文章目录前言使用项目结构训练过程准备数据集与配置进入训练训练显示使用模型编码实现配置文件读取配置文件HU数据集解析器其他使用模型LeNet训练实现识别使用实现总结前言鸽了两天,从星期二晚上就开始说要发布这篇... 查看详情