【AI】PaddlePaddle实现自动语音识别

news2025/1/13 14:02:35

文章目录

  • 文档背景
  • 安装环境
    • Python版本
    • pip环境
    • 安装模型需要的环境
  • 项目目录结构
  • 数据准备
    • 生成数据字典
    • 数据预处理
  • 训练模型
    • 创建模型
      • 构建模型的目的
      • 模型黑盒在模型中充当什么角色
    • 解码方法
  • 总结

文档背景

学习AI的过程中,难免会出现各种各样的问题。比如,什么样的模型需要什么样的环境。依赖与Python版本不兼容时怎么办。数据集如何自定义。构建模型的目的是什么。原本模型黑盒是如何训练并得以优化的,等等等等。基于这些问题,利用入门级PP ASR简单的语音模型,做一个详细的记录。

安装环境

Python版本

在这里插入图片描述

pip环境

pip install --upgrade pip

安装模型需要的环境

!pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ numpy scipy tqdm pytest-runner librosa  python-Levenshtein==0.12.2 visualdl --upgrade --user
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ SoundFile --upgrade --user
# 执行create_mainfest.py依赖下载
!pip uninstall -y numba
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ --upgrade numba
!pip uninstall -y resampy
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ --upgrade resampy
!pip uninstall -y librosa
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ --upgrade  librosa

项目目录结构

在这里插入图片描述

数据准备

  1. 可以直接使用官方提供的训练数据集。
    在这里插入图片描述
  2. 也可以直接使用自定义数据集
    在这里插入图片描述

生成数据字典

当数据集构建完成后。将所有数据进行分词。因为模型不是以音频区分的。而是将音频处理成序列的形式进行排列,最后输出时,根据生成的字典进行查找,来返回最终的结果。所以生成数据字典的目的,既是固定构建音频生成序列的规则,也是语音识别的依赖。

数据预处理

音频数据预处理通常包括以下步骤:

  1. 读入音频文件:使用库如 librosa、pydub 等加载音频文件,得到音频数据和采样率。

  2. 数据截取:按照需要的长度截取音频数据。例如,将音频数据切割成固定长度的语音片段。

  3. 数据增强:数据增强指对原始数据进行变换、扩充、合成等处理,以提高模型的鲁棒性和泛化能力。例如,随机速度变化、加入噪音、改变音高等方式对数据进行增强。

  4. 特征提取:通过对音频数据进行短时傅里叶变换(STFT)、梅尔频率倒谱系数(MFCC)等方式提取音频特征。特征提取是将高维的音频数据转换为低维度的特征表示。

  5. 数据标准化:标准化是为了使不同样本的特征具有可比性,减少由于特征维度大小造成的模型不稳定或低效问题。例如,将特征值归一化到 [0,1] 范围内、均值为 0,标准差为 1 等方式进行标准化。

  6. 数据划分:将数据划分为训练集、验证集和测试集,可使用库如 sklearn 等将样本划分为不同集合。

  7. 数据存储:将处理好的数据按照一定格式保存,以便在模型训练和测试时使用。

下面是数据预处理工具类 data.py
注意! 需要前面的环境才可以使用

import json
import wave

import librosa
import numpy as np
import soundfile
from paddle.io import Dataset


# 加载二进制音频文件,转成短时傅里叶变换
def load_audio_stft(wav_path, mean=None, std=None):
    with wave.open(wav_path) as wav:
        wav = np.frombuffer(wav.readframes(wav.getnframes()), dtype="int16").astype("float32")
    stft = librosa.stft(wav, n_fft=255, hop_length=160, win_length=200, window="hamming")
    spec, phase = librosa.magphase(stft)
    spec = np.log1p(spec)
    if mean is not None and std is not None:
        spec = (spec - mean) / std
    return spec


# 读取音频文件转成梅尔频率倒谱系数(MFCCs)
def load_audio_mfcc(wav_path, mean=None, std=None):
    wav, sr = librosa.load(wav_path, sr=16000)
    mfccs = librosa.feature.mfcc(y=wav, sr=sr, n_mfcc=128, n_fft=512, hop_length=128).astype("float32")
    if mean is not None and std is not None:
        mfccs = (mfccs - mean) / std
    return mfccs


# 改变音频采样率为16000Hz
def change_rate(audio_path):
    data, sr = soundfile.read(audio_path)
    if sr != 16000:
        # librosa最新版本在调用参数时必须明确指定参数名,避免出现参数传递错误
        data = librosa.resample(y=data, orig_sr=sr, target_sr=16000)
        soundfile.write(audio_path, data, samplerate=16000)


# 音频数据加载器
class PPASRDataset(Dataset):
    def __init__(self, data_list, dict_path, mean=None, std=None, min_duration=0, max_duration=-1):
        super(PPASRDataset, self).__init__()
        self.mean = mean
        self.std = std
        # 获取数据列表
        with open(data_list, 'r', encoding='utf-8') as f:
            lines = f.readlines()
        self.data_list = []
        for line in lines:
            line = json.loads(line)
            # 跳过超出长度限制的音频
            if line["duration"] < min_duration:
                continue
            if max_duration != -1 and line["duration"] > max_duration:
                continue
            self.data_list.append([line["audio_path"], line["text"]])
        # 加载数据字典
        with open(dict_path, 'r', encoding='utf-8') as f:
            labels = eval(f.read())
        self.vocabulary = dict([(labels[i], i) for i in range(len(labels))])

    def __getitem__(self, idx):
        # 分割音频路径和标签
        wav_path, transcript = self.data_list[idx]
        # 读取音频并转换为梅尔频率倒谱系数(MFCCs)
        mfccs = load_audio_mfcc(wav_path, self.mean, self.std)
        # 将字符标签转换为int数据
        transcript = list(filter(None, [self.vocabulary.get(x) for x in transcript]))
        transcript = np.array(transcript, dtype='int32')
        return mfccs, transcript

    def __len__(self):
        return len(self.data_list)


# 对一个batch的数据处理
def collate_fn(batch):
    # 找出音频长度最长的
    batch = sorted(batch, key=lambda sample: sample[0].shape[1], reverse=True)
    freq_size = batch[0][0].shape[0]
    max_audio_length = batch[0][0].shape[1]
    batch_size = len(batch)
    # 找出标签最长的
    batch_temp = sorted(batch, key=lambda sample: len(sample[1]), reverse=True)
    max_label_length = len(batch_temp[0][1])
    # 以最大的长度创建0张量
    inputs = np.zeros((batch_size, freq_size, max_audio_length), dtype='float32')
    labels = np.zeros((batch_size, max_label_length), dtype='int32')
    input_lens = []
    label_lens = []
    for x in range(batch_size):
        sample = batch[x]
        tensor = sample[0]
        target = sample[1]
        seq_length = tensor.shape[1]
        label_length = target.shape[0]
        # 将数据插入都0张量中,实现了padding
        inputs[x, :, :seq_length] = tensor[:, :]
        labels[x, :label_length] = target[:]
        input_lens.append(seq_length)
        label_lens.append(len(target))
    input_lens = np.array(input_lens, dtype='int64')
    label_lens = np.array(label_lens, dtype='int64')
    return inputs, labels, input_lens, label_lens

训练模型

创建模型

构建模型的目的

PPASR模型是一个只使用卷积层的模型,并没有使用更加复杂的RNN模型,以下就是使用PaddlePaddle实现的一个语音识别模型。使用动态图自定义网络模型非常简单。

在语音识别的过程中,构建模型的目的是为了将语音信号转化为文字(文本)信息。这个过程可以分为两个主要步骤:语音特征提取和声学模型训练。

在语音特征提取过程中,将来自音频数据的语音信号转化为机器学习算法能够处理的形式,例如用梅尔频率倒谱系数(Mel Frequency Cepstral Coefficients,MFCCs)等特征来描述语音信号。

在声学模型训练过程中,使用大量的带有标注的语音数据来训练语音识别模型。常用的模型包括隐马尔可夫模型(Hidden Markov Model,HMM)、深度学习模型等。机器学习算法通过学习这些带有标注的语音样本的特征,来建立模型,以将音频信号转化为文字(文本)信息。

因此,构建模型的目的是为了使机器能够对人类语音进行自动识别,实现语音识别应用,例如语音助手、语音输入等。

模型黑盒在模型中充当什么角色

原本的黑盒指的是模型的内部结构和工作流程难以被理解和解释的情况。在语音识别的过程中,原本的黑盒主要存在于模型内部,由于模型参数众多且相互作用复杂,难以解释和理解模型内部的具体工作流程。

在构建的模型中,原本的黑盒逐渐被打破,因为随着机器学习技术的发展,一些新的方法和技术被引入来提高模型的解释性和可理解性。例如,利用深度神经网络模型中的可视化方法来可视化模型的神经网络结构,可以更好地理解模型的工作原理。同时,通过对模型进行逐层解析,可以逐步了解模型对不同特征以及语音信号的处理方式。

总之,在构建的模型中,原本的黑盒逐渐被打破,通过不断的优化和改进,模型的解释性和可理解性得到了提高,使得我们更加深入地理解语音识别的工作原理。

所以,原本的模型黑盒,我们就可以将它看做成一个已经存在的智能AI对象。

import paddle
import paddle.nn as nn
from paddle.nn.initializer import KaimingNormal


# 门控线性单元 Gated Linear Units (GLU)
class GLU(nn.Layer):
    def __init__(self, axis):
        super(GLU, self).__init__()
        self.sigmoid = nn.Sigmoid()
        self.axis = axis

    def forward(self, x):
        a, b = paddle.split(x, num_or_sections=2, axis=self.axis)
        act_b = self.sigmoid(b)
        out = paddle.multiply(x=a, y=act_b)
        return out


# 基本卷积块
class ConvBlock(nn.Layer):
    def __init__(self, in_channels, out_channels, kernel_size, stride, padding=0, p=0.5):
        super(ConvBlock, self).__init__()
        self.conv = nn.Conv1D(in_channels, out_channels, kernel_size, stride, padding, weight_attr=KaimingNormal())
        self.conv = nn.utils.weight_norm(self.conv)
        self.act = GLU(axis=1)
        self.dropout = nn.Dropout(p)

    def forward(self, x):
        x = self.conv(x)
        x = self.act(x)
        x = self.dropout(x)
        return x


# PPASR模型
class PPASR(nn.Layer):
    def __init__(self, vocabulary, data_mean=None, data_std=None, name="PPASR"):
        super(PPASR, self).__init__(name_scope=name)
        # 数据均值和标准值到模型中,方便以后推理使用
        if data_mean is None:
            data_mean = paddle.to_tensor(1.0)
        if data_std is None:
            data_std = paddle.to_tensor(1.0)
        self.register_buffer("data_mean", data_mean, persistable=True)
        self.register_buffer("data_std", data_std, persistable=True)
        # 模型的输出大小,字典大小+1
        self.output_units = len(vocabulary) + 1
        self.conv1 = ConvBlock(128, 500, 48, 2, padding=97, p=0.2)
        self.conv2 = ConvBlock(250, 500, 7, 1, p=0.3)
        self.conv3 = ConvBlock(250, 2000, 32, 1, p=0.3)
        self.conv4 = ConvBlock(1000, 2000, 1, 1, p=0.3)
        self.out = nn.utils.weight_norm(nn.Conv1D(1000, self.output_units, 1, 1))

    def forward(self, x, input_lens=None):
        x = self.conv1(x)
        for i in range(7):
            x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.out(x)
        if input_lens is not None:
            return x, paddle.to_tensor(input_lens / 2 + 1, dtype='int64')
        return x

解码方法

import Levenshtein as Lev
import paddle


class GreedyDecoder(object):
    def __init__(self, vocabulary, blank_index=0):
        self.int_to_char = dict([(i, c) for (i, c) in enumerate(vocabulary)])
        self.blank_index = blank_index

    # 给定一个数字序列列表,返回相应的字符串
    def convert_to_strings(self, sequences, sizes=None, remove_repetitions=False, return_offsets=False):
        strings = []
        offsets = [] if return_offsets else None
        for x in range(len(sequences)):
            seq_len = sizes[x] if sizes is not None else len(sequences[x])
            string, string_offsets = self.process_string(sequences[x], seq_len, remove_repetitions)
            strings.append([string])
            if return_offsets:
                offsets.append([string_offsets])
        if return_offsets:
            return strings, offsets
        else:
            return strings

    # 获取字符,并删除重复的字符
    def process_string(self, sequence, size, remove_repetitions=False):
        string = ""
        offsets = []
        sequence = sequence.numpy()
        for i in range(size):
            char = self.int_to_char[sequence[i].item()]
            if char != self.int_to_char[self.blank_index]:
                # 是否删除重复的字符
                if remove_repetitions and i != 0 and char == self.int_to_char[sequence[i - 1].item()]:
                    pass
                else:
                    string = string + char
                    offsets.append(i)
        return string, paddle.to_tensor(offsets, dtype='int64')

    def cer(self, s1, s2):
        """
       通过计算两个字符串的距离,得出字错率
        """
        s1, s2, = s1.replace(" ", ""), s2.replace(" ", "")
        return Lev.distance(s1, s2)

    def decode(self, probs, sizes=None):
        """
        解码,传入结果的概率解码得到字符串,删除序列中的重复元素和空格。
        """
        max_probs = paddle.argmax(probs, 2)
        strings, offsets = self.convert_to_strings(
            max_probs,
            sizes,
            remove_repetitions=True,
            return_offsets=True)
        return strings, offsets

总结

  1. 训练的过程和评估详情可以查看此链接: https://aistudio.baidu.com/aistudio/projectdetail/5902290?forkThirdPart=1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/419634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

制造业生产管理系统(500强制造企业数字化实践)

前言 制造业是国民经济的支柱产业之一&#xff0c;随着科技和数字化的发展&#xff0c;制造业正在经历着一场新的变革。传统的制造模式已经无法满足市场的快速变化和客户的多样化需求&#xff0c;制造企业急需通过数字化和智能化转型升级&#xff0c;提高生产效率和质量水平&a…

第十四届蓝桥杯嵌入式详解

目录 第一部分 客观试题&#xff08;15 分&#xff09; 不定项选择&#xff08;1.5 分/题&#xff09; 第二部分 程序设计试题&#xff08;85 分&#xff09; 2.1 STM32CubeMX初始化配置 2.1.1 配置GPIO 2.1.2 配置ADC 2.1.3 配置RCC 2.1.4 配置定时器TIM 2.1.5 配置ADC1、AD…

【从零开始学Skynet】基础篇(二):了解Skynet

1、节点和服务 在下图所示的服务端系统中&#xff0c;每个Skynet进程&#xff08;操作系统进程&#xff09;都称为一个节点&#xff0c;每个节点都可以开启数千个Lua服务&#xff0c;每个服务都是一个Actor。不同节点可以部署在不同的物理机上&#xff0c;提供分布式集群的能力…

Velocity入门到精通(上篇)

最近自己所做的项目使用到这个Velocity模板引擎&#xff0c;分享一下在互联网找的学习资料。 目录 一. velocity简介 1. velocity简介 2. 应用场景 3. velocity 组成结构 二. 快速入门 1. 需求分析 2. 步骤分析 3. 代码实现 3.1 创建maven工程 3.2 引入坐标 3.3 编…

Redis锁的租约问题

目录Redis的租约问题Redis租约问题的想法Redis租约问题的解决方案Redis的租约问题 首先我们先来说一说什么是Redis的租约问题。   在我们实现Redis分布式锁的时候&#xff0c;我们会出现Redis锁的时间<业务执行执行时间&#xff0c;这其实就是一个典型的租约问题&#xf…

【C++】你了解命名空间吗?

C语言之父&#xff1a;Bjarne Stroustrup博士(本贾尼) 当我们在编写代码的时候&#xff0c;可能会产生一些命名冲突&#xff0c;为了解决这一冲突我们引出命名空间的概念 (ps:命名冲突的产生主要包括两个方面原因&#xff1a;1、与库函数名冲突&#xff1b;2、相互之间的冲突&…

【LeetCode】剑指 Offer 51. 数组中的逆序对 p249 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 1. 题目介绍&#xff08;51. 数组中的逆序对&#xff09; 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xf…

python3 DataFrame一些好玩且高效的操作

pandas在处理Excel/DBs中读取出来&#xff0c;处理为DataFrame格式的数据时&#xff0c;处理方式和性能上有很大差异&#xff0c;下面是一些高效&#xff0c;方便处理数据的方法。 map/apply/applymaptransformagg遍历求和/求平均shift/diff透视表切片&#xff0c;索引&#x…

VS Code 将推出更多 AI 功能给 Java 开发者

大家好&#xff0c;欢迎来到我们的二月更新&#xff01;我们将为您带来与 JUnit 5 并行测试相关的新功能以及用于 Spring Boot Dashboard 的过滤功能。另外&#xff0c;OpenAI 和 ChatGPT 是最近的热点&#xff0c;所以在 GitHub Copilot 方面也有一些令人激动的消息&#xff0…

【郭东白架构课 模块二:创造价值】19|节点二:架构活动的目标为什么常常被忽略?

你好&#xff0c;我是郭东白。从这节课开始&#xff0c;我们就进入到架构活动第二个环节的学习&#xff0c;那就是目标确认。 为架构活动确认一个正确目标&#xff0c;是架构师能为架构活动做出最大贡献的环节。从我的个人经验来看&#xff0c;一大半架构活动的目标都不具备正…

类文件具有错误的版本 55.0, 应为 52.0

最近在编译时报如下错误 java: 无法访问com.xx错误的类文件: /xxx.jar!/aa.class类文件具有错误的版本 55.0, 应为 52.0请删除该文件或确保该文件位于正确的类路径子目录中。 原来我依赖的jar包的编译版本是jdk11,而我本地代码编译的版本的jdk1.8,两个版本不一致&#xff0c;所…

C++类和对象终章——友元函数 | 友元类 | 内部类 | 匿名对象 | 关于拷贝对象时一些编译器优化

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;友元&#x1f33a;概念&#x1f33a;友元函数&#x1f341;友元函数的重要性质&#x1f33a;友元类&#x1f341;友元类的重要性质&#x1f337;内部类&#xff08;不常用&#xff09;&#x1f33a;内部类的性…

Ubuntu 下载并切换Python默认版本(无痛顺畅版)

Ubuntu 下载并切换Python默认版本的方法 文章目录Ubuntu 下载并切换Python默认版本的方法一&#xff0c;前言二&#xff0c;在ubantu中下载指定python版本1&#xff0c;更新apt版本为最新2&#xff0c;安装software-properties-common3&#xff0c;将 deadsnakes PPA 添加到你的…

并发 并行 进程 线程

并发 并行 进程 线程 进程和线程介绍 程序、进程和线程的关系示意图 并发和并行 1)多线程程序在单核上运行&#xff0c;就是并发 2)多线程程序在多核上运行&#xff0c;就是并行 示意图: 小结

大模型时代的“Linux”生态,开启人工智能新十年

演讲 | 林咏华 智源人工智能研究院副院长 整理 | 何苗出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;2018 年以来&#xff0c;超大规模预训练模型的出现推动了 AI 科研范式从面向特定应用场景、训练专有模型&#xff0c;转变为大模型微调模型服务的AI工业化开…

016 - 如何写一个 C++ 类

到目前为止&#xff0c;我们学了类 class&#xff0c;本期我们要尝试着从头开始写一个类。 本期不会讲的太深。我们不会写非常复杂的类&#xff0c;我们要会完成一个基本的 log 类&#xff0c;来演示一下我们已经学过的相关知识。 接下来的几期&#xff0c;我们会继续学习类。…

银行数字化转型导师坚鹏:《银行业金融机构数据治理指引》

《银行业金融机构数据治理指引》 ——“监”听则明 护航银行高质量发展课程背景&#xff1a; 很多金融机构存在以下问题&#xff1a; 不清楚《银行业金融机构数据治理指引》出台背景&#xff1f; 不知道如何理解《银行业金融机构数据治理指引》相关规定&#xff1f; 不清楚…

重生之我是孔乙己——查找数组缺失元素的几种方法

&#x1f48c; 博客内容&#xff1a;查找缺失元素 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是…

【MySQL | 基础篇】03、MySQL 约束

目录 一、概述 二、约束演示 三、外键约束 3.1 介绍 3.2 语法 3.3 删除/更新行为 一、概述 概念: 约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确、有效性和完整性。 分类&#xff1a; 注意&#xff1a…

千耘农机导航的“星地一体”能力究竟是什么?

伴随农业机械化和智能化的发展&#xff0c;越来越多的人开始使用农机自动驾驶系统助力耕作&#xff0c;千耘农机导航的“星地一体”能力可有效解决信号受限的问题&#xff0c;实现作业提效。究竟什么是“星地一体”&#xff0c;又是如何解决智能化农机作业的痛点的&#xff1f;…