张海腾:语音识别实践教程

2021-06-01 16:22:43 浏览数 (1)

作者:张海腾,标贝科技,Datawhale优秀学习者

作为智能语音交互相关的从业者,今天以天池学习赛:《零基础入门语音识别:食物声音识别》为例,带大家梳理一些自动语音识别技术(ASR)关的知识,同时给出线上可运行的完整代码实践,供大家练习。

实践背景

赛题名称:零基础入门语音识别-食物声音识别

语音相关知识点梳理

一些在我司常听到的关键词

语音不像文本,可以看得见,仅有对应的音频,需要对语音有一个“可以看见”的过程,于是有了下列的几种音频文件的表示方法:

1)波形图

语音的保存形式可用波形图展现,可以看作是上下摆动的数字序列,每一秒的音频用16000个电压数值表示,采样率即为16kHz。

2)采样点

采样点是对波形图的放大,可以看到的更细的单位

3)频谱图

可以变为频谱图,颜色代表频带能量大小,语音的傅立叶变换是按帧进行,短的窗口有着高时域和低频域,长时窗口有低时域和高频域。

4)基本单位

对于语音而言,基本单位是帧(对应文本的token),一帧即是一个向量,整条语音可以表示为以帧为单位的向量组。帧是由ASR的前端声学特征提取模块产生,提取的技术设计“离散傅立叶变换”和”梅尔滤波器组“

整体解决思路

在我的理解认知中,对于ASR的解决方法可以分为两种,一种是声学模型加语言模型的组合,另外一种是端到端的解决方式。

第一种方式: 路线的个人理解大约是,有一个音频,先有声学模型,将对应的音频信号处理为对应的声学特征,再有语言模型,将声学特征的结果得到概率最大的输出字符串。 在上图中,

X

代表的是声学特征向量,

W

代表输出的文本序列,在(2.1)中,

P(X|W)

代表的是声学模型,

P(W)

代表的是语言模型

第二种方式: 端到端的解决手段,个人印象中在吴恩达的课程里提到,ASR在CTC提出后有一个较大的提升。个人理解是在CTC之前,seq2seq的建模方式比较难处理输出序列远短于输入序列的情况,以及在不同帧出现的相同音素的输出

其他术语

声学模型:常用的话,包括了HMM,GMM,DNN-HM的声学模型。

语言模型:常用的语言模型这里包括了n-gram语言模型以及RNN的语言模型。

解码器:最终目的是取得最大概率的字符输出,解码本质上是一个搜索问题,并可借助加权有限状态转换器(Weighted Finite State Transducer,WFST) 统一进行最优路径搜索。

端到端的方法:seq2seq CTC 损失函数, RNN Transducer, Transforme,这里需要补充的话 应该要去看李宏毅2020年的人类语言处理的课程。

完整实践代码

本代码已经部署到天池DSW实验平台上,可直接免配置环境运行,对于DSW不熟悉的学习者可参考:小白如何用免费GPU跑天池算法大赛!

赛题介绍: 有20种不同食物的咀嚼声音,给出对应的音频,对声音的数据进行建模,判断是哪种食物的咀嚼声音

Baseline思路:将对应的音频文件,使用librosa转化为梅尔谱作为输入的特征,用CNN对梅尔谱的特征进行建模分类预测。

Baseline地址:https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.3.78ac14e9eP5wk4&postId=198902

数据获取

代码语言:javascript复制
# 下载数据集
!wget [http://tianchi-competition.oss-cn-hangzhou.aliyuncs.com/531887/train_sample.zip](http://tianchi-competition.oss-cn-hangzhou.aliyuncs.com/531887/train_sample.zip)
!wget [http://tianchi-competition.oss-cn-hangzhou.aliyuncs.com/531887/test_a.zip](http://tianchi-competition.oss-cn-hangzhou.aliyuncs.com/531887/test_a.zip)
# 解压并删除压缩包
!unzip -qq train_sample.zip
!rm train_sample.zip
!unzip -qq test_a.zip
!rm test_a.zip

基本库及模型框架导入

环境要求:

  • TensorFlow的版本:2.0
  • keras
  • sklearn
  • librosa
代码语言:javascript复制
#基本库
import pandas as pd 
import numpy as np

from sklearn.model_selection import train_test_split #数据分割
from sklearn.metrics import classification_report #评价
from sklearn.model_selection import GridSearchCV #网格搜索,最优模型选择

from sklearn.preprocessing import MinMaxScaler #特征标准化
代码语言:javascript复制
# 加载深度学习框架
# 搭建分类模型所需要的库
from tensorflow.keras.models import Sequential 
from tensorflow.keras.layers import Conv2D, Flatten, Dense, MaxPool2D, Dropout
from tensorflow.keras.utils import to_categorical 

from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
代码语言:javascript复制
# 安装librosa Librosa是一个用于音频、音乐分析、处理的python工具包。
# 一些常见的时频处理、特征提取、绘制声音图形等功能应有尽有,功能十分强大
!pip install librosa --user 
代码语言:javascript复制
# 其他库
#音频处理
import librosa
import librosa.display
#文件处理
import glob
import os

特征提取以及数据集的建立

代码语言:javascript复制
feature = []
label = []
# 建立类别标签,不同类别对应不同的数字。
label_dict = {'aloe': 0, 'burger': 1, 'cabbage': 2,'candied_fruits':3, 'carrots': 4, 'chips':5,
                  'chocolate': 6, 'drinks': 7, 'fries': 8, 'grapes': 9, 'gummies': 10, 'ice-cream':11,
                  'jelly': 12, 'noodles': 13, 'pickles': 14, 'pizza': 15, 'ribs': 16, 'salmon':17,
                  'soup': 18, 'wings': 19}
# 不同数字对应不同的类别。
label_dict_inv = {v:k for k,v in label_dict.items()}
代码语言:javascript复制
#特征提取
from tqdm import tqdm #提示进度条
def extract_features(parent_dir, sub_dirs, max_file=10, file_ext="*.wav"):#提取特征以及标签
    c = 0
    label, feature = [], []
    for sub_dir in sub_dirs:
        for fn in tqdm(glob.glob(os.path.join(parent_dir, sub_dir, file_ext))[:max_file]): # 遍历每个数据集的所有文件
            
           # segment_log_specgrams, segment_labels = [], []
            #sound_clip,sr = librosa.load(fn)
            #print(fn)
            label_name = fn.split('/')[-2]
            label.extend([label_dict[label_name]])
            X, sample_rate = librosa.load(fn,res_type='kaiser_fast')
            mels = np.mean(librosa.feature.melspectrogram(y=X,sr=sample_rate).T,axis=0) # 计算梅尔频谱(mel spectrogram),并把它作为特征
            feature.extend([mels])
            
    return [feature, label]
代码语言:javascript复制
parent_dir = './train_sample/'
save_dir = "./"
folds = sub_dirs = np.array(['aloe','burger','cabbage','candied_fruits',
                             'carrots','chips','chocolate','drinks','fries',
                            'grapes','gummies','ice-cream','jelly','noodles','pickles',
                            'pizza','ribs','salmon','soup','wings'])

# 获取特征feature以及类别的label
temp = extract_features(parent_dir,sub_dirs,max_file=100)

代码语言:javascript复制
temp = np.array(temp)#列表转换成矩阵
data = temp.transpose()#矩阵转置
代码语言:javascript复制
# 获取特征
X = np.vstack(data[:, 0])
#X的特征尺寸是: (1000, 128)

# 获取标签
Y = np.array(data[:, 1])
#Y的特征尺寸是: (1000,)
代码语言:javascript复制
#数据集划分
#训练集的大小 750
#测试集的大小 250
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state = 1, stratify=Y)
#random_state 是随机数的种子。

建立模型

1、搭建CNN网络

代码语言:javascript复制
model = Sequential()#多个网络层的线性堆叠
# 输入的大小
input_dim = (16, 8, 1)
model.add(Conv2D(64, (3, 3), padding = "same", activation = "tanh", input_shape = input_dim))# 卷积层
# 输入shape
model.add(MaxPool2D(pool_size=(2, 2)))# 最大池化

model.add(Conv2D(128, (3, 3), padding = "same", activation = "tanh")) #卷积层
model.add(MaxPool2D(pool_size=(2, 2))) # 最大池化层
model.add(Dropout(0.1))
#为输入数据施加Dropout。Dropout将在训练过程中每次更新参数时按一定概率(rate)随机断开输入神经元,Dropout层用于防止过拟合。
model.add(Flatten()) # 展开
model.add(Dense(1024, activation = "tanh"))
model.add(Dense(20, activation = "softmax")) # 输出层:20个units输出20个类的概率
# 编译模型,设置损失函数,优化方法以及评价标准
model.compile(optimizer = 'adam', loss = 'categorical_crossentropy', metrics = ['accuracy'])
代码语言:javascript复制
model.summary()
代码语言:javascript复制
# 训练模型
model.fit(X_train, Y_train, epochs = 20, batch_size = 15, validation_data = (X_test, Y_test))

2、预测测试集

代码语言:javascript复制

#提取测试集数据特征
def extract_features(test_dir, file_ext="*.wav"):
    feature = []
    for fn in tqdm(glob.glob(os.path.join(test_dir, file_ext))[:]): # 遍历数据集的所有文件
        X, sample_rate = librosa.load(fn,res_type='kaiser_fast')
        mels = np.mean(librosa.feature.melspectrogram(y=X,sr=sample_rate).T,axis=0) # 计算梅尔频谱(mel spectrogram),并把它作为特征
        feature.extend([mels])
    return feature

X_test = extract_features('./test_a/')
代码语言:javascript复制
#输出测试结果
preds = np.argmax(predictions, axis = 1)
preds = [label_dict_inv[x] for x in preds]

path = glob.glob('./test_a/*.wav')
result = pd.DataFrame({'name':path, 'label': preds})

result['name'] = result['name'].apply(lambda x: x.split('/')[-1])
result.to_csv('submit.csv',index=None)
代码语言:javascript复制
!ls ./test_a/*.wav | wc -l
!wc -l submit.csv
#利用wc指令我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。

0 人点赞