文章目录
前言
语音识别原理
信号处理,声学特征提取
识别字符,组成文本
声学模型
语言模型
词汇模型
语音声学特征提取:MFCC和LogFBank算法的原理
实战一:ASR语音识别模型系统的流程基于HTTP协议的API接口客户端未来
实战二:调百度和科大讯飞API
实战三:离线语音识别Vosk
前言
首先,大家好,我是cv君 ,AI算法工程师,多个顶级AI 竞赛Top,世界,国家奖多个。今天我下血本费时整理了AI在音视频领域的大量的方向,形成本文综述,从原理到底层算法,到上层应用,统统透析~本系列由于综述文章过长的原因,所以分开写了。文章附带大量的算法原理+代码实现教学,欢迎关注,一起AI。
语音识别原理
首先,是语音识别和语音唤醒等语音类任务。一听到你就会想起中国科大讯飞,百度等平台,由于这两家企业在中国的语音领域占80+市场,所以他们做得很优秀,不过由于高精技术无法开源,其他企业只得花费大量的金钱去购买其API,而无法研究语音识别等应用,导致民间语音识别发展较慢,cv君收集了现在领域SOTA,和大量原理,实战部分,今天我们来一饱眼福吧!
信号处理,声学特征提取
我们都知道声音信号是连续的模拟信号,要让计算机处理首先要转换成离散的数字信号,进行采样处理。正常人听觉的频率范围大约在20Hz~20KHz之间,为了保证音频不失真影响识别,同时数据又不会太大,通常的采样率为16KHz。
语音采样
在数字化的过程中,我们首先要确定语音的开始和结束,然后进行降噪和过滤处理(除了人声之外,存在很多的噪音),保证让计算机识别的是过滤的语音信息。为了进一步的处理我们还需要对音频信号分帧。同时从微观上来看一段时间内人的语音信号一般是比较平稳的,称为短时平稳性,所以会需要将语音信号分帧,便于处理。
通常一帧为20~50毫秒,帧与帧之间有交叠冗余,避免一帧的信号在两个端头被削弱了影响识别精度。
随后进行的就是极为关键的特征提取。将原始波形进行识别并不能取得好的识别效果,需要进行频域变换提取的特征参数用于识别。常见的变换方法是提取MFCC特征,根据人耳的生理特性,把每帧波形变成一个多维向量,可以理解为这个向量包含了这帧语音的内容信息。
比如下图示例中,每一帧f1,f2,f3…转换为了14维的特征向量,然后整个语音转换为了14*N(N为帧数)的向量矩阵。

一帧一帧的向量如果不太直观,还可以用下图的频谱图表示语音,每一列从左到右都是一个25毫秒的块,相比于原始声波,从这种数据中寻找规律要容易得多。

不过频谱图主要用作语音研究,语音识别还是需要用一帧一帧的特征向量。
识别字符,组成文本
特征提取完成后,就进入了特征识别,字符生成环节。这部分的工作就是从每一帧当中找出当前说的音素,再由多个音素组成单词,再由单词组成文本句子。其中最难的当然是从每一帧中找出当前说的音素,因为我们每帧是小于一个音素的,多个帧才能构成一个音素,如果开始就错了则后续很难纠正。
怎么判断每一个帧属于哪个音素?最容易的办法就是概率,看哪个音素的概率最大。那如果每一帧有多个音素的概率相同怎么办,毕竟这是可能的,每个人口音、语速、语气都不同,人也很难听清楚你说的到底是Hello还是Hallo。而我们语音识别的文本结果只有一个,不可能还让人参与选择进行纠正 。
这时候多个音素组成单词的统计决策,单词组成文本的统计决策就发挥了作用,它们也是同样的基于概率:音素概率相同的情况下,再比较组成单词的概率,单词组成之后再比较句子的概率。

这让我们得到三种可能的转写——「Hello」、「Hullo」和「Aullo」,最终根据单词概率我们会发现Hello是最可能的,所以输出Hello的文本。上面的例子很明确的描述怎么从帧到音素,再从音素到单词,概率决定一切,那这些概率是怎么获得的了?难道为了识别一种语言我们把人类几千上百年说过的所有音素,单词,句子都统计出来,然后再计算概率?这是不可能的,那怎么办,这时我们就需要模型:
声学模型
cv君相信大家一定知道是么是声学模型~ 发声的基本状态和概率,尽量获得不同人、年纪、性别、口音、语速的发声语料,同时尽量采集多种场景安静的,嘈杂的,远距离的发声语料生成声学模型。为了达到更好的效果,针对不同的语言,不同的方言会用不同的声学模型,在提高精度的同时降低计算量。
语言模型
再到必不可少的——语言模型,单词和语句的概率,使用大量的文本训练出来。如果模型中只有两句话“今天星期一”和“明天星期二”,那我们就只能识别出这两句,而我们想要识别更多,只需要涵盖足够的语料就行,不过随之而来的就是模型增大,计算量增大。所以我们实际应用中的模型通常是限定应用域的,同比如智能家居的,导航的,智能音箱的,个人助理的,医疗的等等,降低计算量的同时还能提高精度。
词汇模型
最后也是较常用的词汇模型~ 针对语言模型的补充,语言词典和不同的发音标注。比如定期更新的地名,人名,歌曲名称,热词,某些领域的特殊词汇等等。
这时我们有很多简化但是有效的方法进行计算,比如说HMM隐马尔科夫模型Hidden Markov Model。
隐马尔科夫模型基于了两个最大的假设:一是内部状态的转移只与上一状态有关,另一是输出值只与当前状态(或当前的状态转移)有关。就把问题简化了,
也就是说一个句子中某个单词序列出现的概率只和前面的一个单词有关,这样计算量就被大大简化了。
P(S) = P(W1)·P(W2|W1)·P(W3|W2)···P(Wn|Wn-1)
如上示例,基于隐马尔科夫算法生成语言模型。只要按照实际要求构造出对应的模型,模型中涵盖足够的语料,就能解决各种语音识别问题。
语音识别过程其实就是在模型的状态网络中搜索一条最佳路径,语音对应这条路径的概率最大,这称之为“解码”。路径搜索的算法是一种动态规划剪枝的算法,称之为Viterbi算法,用于寻找全局最优路径。
如此一来整个语音识别的流程就很清晰了,再来回顾以下整个步骤:
信号处理:模数转换,识别端头,降噪等等。信号表征:信号分帧,特征提取,向量化等等。模式识别:寻找最优概率路径,声学模型识别音素,词汇模型和语言模型识别单词和句子。

最后将语音识别成文本。语音声学特征提取:MFCC和LogFBank算法的原理
几乎任何做自动语音识别的系统,第一步就是对语音信号,进行特征的提取。通过提取语音信号的相关特征,有利于识别相关的语音信息,并丢弃携带的其他不相关的所有信息,如背景噪声、情绪等。

1 MFCC
刚刚cv君说到了MFCC, 这个很经典哦~ MFCC的全称是“梅尔频率倒谱系数”,这种语音特征提取算法是这几十年来,最常用的算法之一。这种算法通过在声音频率中,对非线性梅尔刻度的对数能量频谱,线性变换得到的。
1.1分帧
由于存储在计算机硬盘中的原始wav音频文件是不定长的,我们首先需要将其按一定方法切分为固定长度的多个小片段,也就是分帧。根据语音信号变化迅速的特性,每一帧的时间长度一般取10-30毫秒,以保证一帧内有足够多的周期,且变化不会过于剧烈,因此,更适合这种适用于分析平稳信号的傅里叶变换。由于数字音频的采样率不同,分帧所得的每一帧向量的维度也不同。
1.2预加重
由于声音信号从人的声门发出后,存在12dB/倍频程的衰减,在通过口唇辐射后,还存在6dB/倍频程的衰减,因而在进行快速傅里叶变换之后,高频信号部分中的成分较少。所以,对语音信号进行预加重操作,其主要目的是加强语音信号的每一帧中,那些高频部分的信号,以提高其高频信号的分辨率。
1.3加窗
在之前的分帧过程中,直接将一个连续的语音信号切分为若干个片段,会造成截断效应产生的频谱泄漏,加窗操作的目的是消除每个帧的短时信号在其两端边缘处出现的信号不连续性问题。MFCC算法中,选取的窗函数通常是汉明窗,也可以使用矩形窗和汉宁窗。需要注意的是,预加重必须在加窗之前进行。
汉明窗的窗函数为:W(n)=0.53836−0.46164×cos(2πnN−1)(0≤n≤N,n=0,1,2,…,N)(3)加窗过程为:S′(n)=W(n)×S(n)(4)
1.4快速傅里叶变换
在经过上述的一系列的处理过程之后,我们得到的仍然是时域的信号,而时域中可直接获取的语音信息量较少。在进行进一步的语音信号特征提取时,还需要将每一帧的时域信号对应转换到其频域信号。对于存储在计算机上的语音信号,我们需要使用离散的傅里叶变换,由于普通的离散傅里叶变换的计算复杂度较高,通常使用快速傅里叶变换来实现。由于MFCC算法经过分帧之后,每一帧都是短时间内的时域信号,所以这一步也成为短时快速傅里叶变换。P(n)=∑N−1k=0S(n)×e−j⋅2πknN(0
根据奈奎斯特定理,如果要再次从离散的数字信号无损地转换到模拟信号上,在对模拟信号进行采样时,我们需要采用模拟信号最高频率值的2倍以上的采样率,对模拟信号进行模数转换的采样。对于语音识别常用的16kHz采样率音频,傅里叶变换之后的频率范围为0到8kHz之间。
1.5计算幅度谱(对复数取模)
在完成了快速傅里叶变换之后,得到的语音特征是一个复数矩阵,它是一个能量谱,由于能量谱中的相位谱包含的信息量极少,所以我们一般选择丢弃相位谱,而保留幅度谱。
丢弃相位谱保留幅度谱的方法一般是两种,对每一个复数求绝对值或者求平方值。
1.6 Mel滤波

Mel滤波的过程是MFCC和fBank特征的关键之一。Mel滤波器是由20个三角形带通滤波器组成的,将线性频率转换为非线性分布的Mel频率。
2 logfBank
logfBank特征提取算法类似于MFCC算法,都是基于fBank特征提取结果的基础上,再进行一些处理的。不过logfBank跟MFCC算法的主要区别在于,是否再进行离散余弦变换。
随着DNN和CNN的出现,尤其是深度学习的发展,由于fBank以及logfBank特征之间的相关性可以更好地被神经网络利用,以提高最终语音识别的准确率,降低WER,因此,可以省略掉离散余弦变换这一步骤。
SOTA实例1深度全卷积神经网络 语音识别
近年来,深度学习在人工智能领域兴起,其对语音识别也产生了深远影响,深层的神经网络逐步替代了原来的GMM-HMM模型。在人类的交流和知识传播中,大约 70% 的信息是来自于语音。未来,语音识别将必然成为智能生活里重要的一部分,它可以为语音助手、语音输入等提供必不可少的基础,这将会成为一种新的人机交互方式。因此,我们需要让机器听懂人的声音。
该语音识别系统的声学模型采用了深度全卷积神经网络,直接将语谱图作为输入。模型结构上,借鉴了图像识别中效果最好的网络配置VGG,这种网络模型有着很强的表达能力,可以看到非常长的历史和未来信息,相比RNN在鲁棒性上更出色。在输出端,这种模型可以和CTC方案可以完美结合,以实现整个模型的端到端训练,将声音波形信号直接转录为中文普通话拼音序列。在语言模型上,通过最大熵隐含马尔可夫模型,将拼音序列转换为中文文本。并且,为了通过网络提供服务给所有的用户。
特征提取将普通的wav语音信号通过分帧加窗等操作转换为神经网络需要的二维频谱图像信号,即语谱图。
声学模型基于Keras和TensorFlow框架,使用这种参考了VGG的深层的卷积神经网络作为网络模型,并训练。
CTC解码 在语音识别系统的声学模型的输出中,往往包含了大量连续重复的符号,因此,我们需要将连续相同的符合合并为同一个符号,然后再去除静音分隔标记符,得到最终实际的语音拼音符号序列。
语言模型使用统计语言模型,将拼音转换为最终的识别文本并输出。拼音转文本的本质被建模为一条隐含马尔可夫链,这种模型有着很高的准确率。下面深度解析代码,包会系列~
导入Keras系列。
import platform as plat
import os
import time
from general_function.file_wav import *
from general_function.file_dict import *
from general_function.gen_func import *
from general_function.muti_gpu import *
import keras as kr
import numpy as np
import random
from keras.models import Sequential, Model
from keras.layers import Dense, Dropout, Input, Reshape, BatchNormalization # , Flatten
from keras.layers import Lambda, TimeDistributed, Activation,Conv2D, MaxPooling2D,GRU #, Merge
from keras.layers.merge import add, concatenatefrom keras import backend as K
from keras.optimizers import SGD, Adadelta, Adam
from readdata24 import DataSpeech
导入声学模型默认输出的拼音的表示大小是1428,即1427个拼音+1个空白块
abspath = ''
ModelName='261'
NUM_GPU = 2
class ModelSpeech(): # 语音模型类
def __init__(self, datapath):
'''
初始化
默认输出的拼音的表示大小是1428,即1427个拼音+1个空白块
'''
MS_OUTPUT_SIZE = 1428
self.MS_OUTPUT_SIZE = MS_OUTPUT_SIZE # 神经网络最终输出的每一个字符向量维度的大小
#self.BATCH_SIZE = BATCH_SIZE # 一次训练的batch
self.label_max_string_length = 64
self.AUDIO_LENGTH = 1600
self.AUDIO_FEATURE_LENGTH = 200
self._model, self.base_model = self.CreateModel() 转换路径
self.datapath = datapath
self.slash = ''
system_type = plat.system() # 由于不同的系统的文件路径表示不一样,需要进行判断
if(system_type == 'Windows'):
self.slash='\\' # 反斜杠
elif(system_type == 'Linux'):
self.slash='/' # 正斜杠
else:
print('*[Message] Unknown System\n')
self.slash='/' # 正斜杠
if(self.slash != self.datapath[-1]): # 在目录路径末尾增加斜杠
self.datapath = self.datapath + self.slash 定义CNN/LSTM/CTC模型,使用函数式模型,设计输入层,隐藏层和输出层。
def CreateModel(self):
'''
定义CNN/LSTM/CTC模型,使用函数式模型
输入层:200维的特征值序列,一条语音数据的最大长度设为1600(大约16s)
隐藏层:卷积池化层,卷积核大小为3x3,池化窗口大小为2
隐藏层:全连接层
输出层:全连接层,神经元数量为self.MS_OUTPUT_SIZE,使用softmax作为激活函数,
CTC层:使用CTC的loss作为损失函数,实现连接性时序多输出
''' input_data = Input(name='the_input', shape=(self.AUDIO_LENGTH, self.AUDIO_FEATURE_LENGTH, 1))
layer_h1 = Conv2D(32, (3,3), use_bias=False, activation='relu', padding='same', kernel_initializer='he_normal')(input_data) # 卷积层
#layer_h1 = Dropout(0.05)(layer_h1)
layer_h2 = Conv2D(32, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h1) # 卷积层
layer_h3 = MaxPooling2D(pool_size=2, strides=None, padding="valid")(layer_h2) # 池化层
#layer_h3 = Dropout(0.05)(layer_h3) # 随机中断部分神经网络连接,防止过拟合
layer_h4 = Conv2D(64, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h3) # 卷积层
#layer_h4 = Dropout(0.1)(layer_h4)
layer_h5 = Conv2D(64, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h4) # 卷积层
layer_h6 = MaxPooling2D(pool_size=2, strides=None, padding="valid")(layer_h5) # 池化层
#layer_h6 = Dropout(0.1)(layer_h6)
layer_h7 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h6) # 卷积层
#layer_h7 = Dropout(0.15)(layer_h7)
layer_h8 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h7) # 卷积层
layer_h9 = MaxPooling2D(pool_size=2, strides=None, padding="valid")(layer_h8) # 池化层
#layer_h9 = Dropout(0.15)(layer_h9)
layer_h10 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h9) # 卷积层
#layer_h10 = Dropout(0.2)(layer_h10)
layer_h11 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h10) # 卷积层
layer_h12 = MaxPooling2D(pool_size=1, strides=None, padding="valid")(layer_h11) # 池化层
#layer_h12 = Dropout(0.2)(layer_h12)
layer_h13 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h12) # 卷积层
#layer_h13 = Dropout(0.3)(layer_h13)
layer_h14 = Conv2D(128, (3,3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal')(layer_h13) # 卷积层
layer_h15 = MaxPooling2D(pool_size=1, strides=None, padding="valid")(layer_h14) # 池化层
#test=Model(inputs = input_data, outputs = layer_h12)
#test.summary()
layer_h16 = Reshape((200, 3200))(layer_h15) #Reshape层
#layer_h16 = Dropout(0.3)(layer_h16) # 随机中断部分神经网络连接,防止过拟合
layer_h17 = Dense(128, activation="relu", use_bias=True, kernel_initializer='he_normal')(layer_h16) # 全连接层
inner = layer_h17
#layer_h5 = LSTM(256, activation='relu', use_bias=True, return_sequences=True)(layer_h4) # LSTM层
rnn_size=128
gru_1 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru1')(inner)
gru_1b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', name='gru1_b')(inner)
gru1_merged = add([gru_1, gru_1b])
gru_2 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru2')(gru1_merged)
gru_2b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', name='gru2_b')(gru1_merged)
gru2 = concatenate([gru_2, gru_2b])
layer_h20 = gru2
#layer_h20 = Dropout(0.4)(gru2)
layer_h21 = Dense(128, activation="relu", use_bias=True, kernel_initializer='he_normal')(layer_h20) # 全连接层
#layer_h17 = Dropout(0.3)(layer_h17)
layer_h22 = Dense(self.MS_OUTPUT_SIZE, use_bias=True, kernel_initializer='he_normal')(layer_h21) # 全连接层
y_pred = Activation('softmax', name='Activation0')(layer_h22)
model_data = Model(inputs = input_data, outputs = y_pred)
#model_data.summary()
labels = Input(name='the_labels', shape=[self.label_max_string_length], dtype='float32')
input_length = Input(name='input_length', shape=[1], dtype='int64')
label_length = Input(name='label_length', shape=[1], dtype='int64')
# Keras doesn't currently support loss funcs with extra parameters
# so CTC loss is implemented in a lambda layer
#layer_out = Lambda(ctc_lambda_func,output_shape=(self.MS_OUTPUT_SIZE, ), name='ctc')([y_pred, labels, input_length, label_length])#(layer_h6) # CTC
loss_out = Lambda(self.ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length]) 模型加载方式
model = Model(inputs=[input_data, labels, input_length, label_length], outputs=loss_out)
model.summary()
# clipnorm seems to speeds up convergence
#sgd = SGD(lr=0.0001, decay=1e-6, momentum=0.9, nesterov=True, clipnorm=5)
#ada_d = Adadelta(lr = 0.01, rho = 0.95, epsilon = 1e-06)
opt = Adam(lr = 0.001, beta_1 = 0.9, beta_2 = 0.999, decay = 0.0, epsilon = 10e-8)
#model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=sgd)
model.build((self.AUDIO_LENGTH, self.AUDIO_FEATURE_LENGTH, 1))
model = ParallelModel(model, NUM_GPU)
model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer = opt) 定义ctc解码
# captures output of softmax so we can decode the output during visualization
test_func = K.function([input_data], [y_pred])
#print('[*提示] 创建模型成功,模型编译成功')
print('[*Info] Create Model Successful, Compiles Model Successful. ')
return model, model_data
def ctc_lambda_func(self, args):
y_pred, labels, input_length, label_length = args
y_pred = y_pred[:, :, :]
#y_pred = y_pred[:, 2:, :]
return K.ctc_batch_cost(labels, y_pred, input_length, label_length) 定义训练模型和训练参数
def TrainModel(self, datapath, epoch = 2, save_step = 1000, batch_size = 32, filename = abspath + 'model_speech/m' + ModelName + '/speech_model'+ModelName):
'''
训练模型
参数:
datapath: 数据保存的路径
epoch: 迭代轮数
save_step: 每多少步保存一次模型
filename: 默认保存文件名,不含文件后缀名
'''
data=DataSpeech(datapath, 'train')
num_data = data.GetDataNum() # 获取数据的数量
yielddatas = data.data_genetator(batch_size, self.AUDIO_LENGTH)
for epoch in range(epoch): # 迭代轮数
print('[running] train epoch %d .' % epoch)
n_step = 0 # 迭代数据数
while True:
try:
print('[message] epoch %d . Have train datas %d+'%(epoch, n_step*save_step))
# data_genetator是一个生成器函数
#self._model.fit_generator(yielddatas, save_step, nb_worker=2)
self._model.fit_generator(yielddatas, save_step)
n_step += 1
except StopIteration:
print('[error] generator error. please check data format.')
break
self.SaveModel(comment='_e_'+str(epoch)+'_step_'+str(n_step * save_step))
self.TestModel(self.datapath, str_dataset='train', data_count = 4)
self.TestModel(self.datapath, str_dataset='dev', data_count = 4)
def LoadModel(self,filename = abspath + 'model_speech/m'+ModelName+'/speech_model'+ModelName+'.model'):
'''
加载模型参数
'''
self._model.load_weights(filename)
self.base_model.load_weights(filename + '.base')
def SaveModel(self,filename = abspath + 'model_speech/m'+ModelName+'/speech_model'+ModelName,comment=''):
'''
保存模型参数
'''
self._model.save_weights(filename+comment+'.model')
self.base_model.save_weights(filename + comment + '.model.base')
f = open('step'+ModelName+'.txt','w')
f.write(filename+comment)
f.close()
def TestModel(self, datapath='', str_dataset='dev', data_count = 32, out_report = False, show_ratio = True):
'''
测试检验模型效果
'''
data=DataSpeech(self.datapath, str_dataset)
#data.LoadDataList(str_dataset)
num_data = data.GetDataNum() # 获取数据的数量
if(data_count <= 0 or data_count > num_data): # 当data_count为小于等于0或者大于测试数据量的值时,则使用全部数据来测试
data_count = num_data
try:
ran_num = random.randint(0,num_data - 1) # 获取一个随机数
words_num = 0
word_error_num = 0
nowtime = time.strftime('%Y%m%d_%H%M%S',time.localtime(time.time()))
if(out_report == True):
txt_obj = open('Test_Report_' + str_dataset + '_' + nowtime + '.txt', 'w', encoding='UTF-8') # 打开文件并读入
txt = ''
for i in range(data_count):
data_input, data_labels = data.GetData((ran_num + i) % num_data) # 从随机数开始连续向后取一定数量数据
# 数据格式出错处理 开始
# 当输入的wav文件长度过长时自动跳过该文件,转而使用下一个wav文件来运行
num_bias = 0
while(data_input.shape[0] > self.AUDIO_LENGTH):
print('*[Error]','wave data lenghth of num',(ran_num + i) % num_data, 'is too long.','\n A Exception raise when test Speech Model.')
num_bias += 1
data_input, data_labels = data.GetData((ran_num + i + num_bias) % num_data) # 从随机数开始连续向后取一定数量数据
# 数据格式出错处理 结束
pre = self.Predict(data_input, data_input.shape[0] // 8)
words_n = data_labels.shape[0] # 获取每个句子的字数
words_num += words_n # 把句子的总字数加上
edit_distance = GetEditDistance(data_labels, pre) # 获取编辑距离
if(edit_distance <= words_n): # 当编辑距离小于等于句子字数时
word_error_num += edit_distance # 使用编辑距离作为错误字数
else: # 否则肯定是增加了一堆乱七八糟的奇奇怪怪的字
word_error_num += words_n # 就直接加句子本来的总字数就好了
if(i % 10 == 0 and show_ratio == True):
print('Test Count: ',i,'/',data_count)
txt = ''
if(out_report == True):
txt += str(i) + '\n'
txt += 'True:\t' + str(data_labels) + '\n'
txt += 'Pred:\t' + str(pre) + '\n'
txt += '\n'
txt_obj.write(txt) 定义预测函数和返回预测结果。
#print('*[测试结果] 语音识别 ' + str_dataset + ' 集语音单字错误率:', word_error_num / words_num * 100, '%')
print('*[Test Result] Speech Recognition ' + str_dataset + ' set word error ratio: ', word_error_num / words_num * 100, '%')
if(out_report == True):
txt = '*[测试结果] 语音识别 ' + str_dataset + ' 集语音单字错误率: ' + str(word_error_num / words_num * 100) + ' %'
txt_obj.write(txt)
txt_obj.close()
except StopIteration:
print('[Error] Model Test Error. please check data format.')
def Predict(self, data_input, input_len):
'''
预测结果
返回语音识别后的拼音符号列表
'''
batch_size = 1
in_len = np.zeros((batch_size),dtype = np.int32)
in_len[0] = input_len
x_in = np.zeros((batch_size, 1600, self.AUDIO_FEATURE_LENGTH, 1), dtype=np.float)
for i in range(batch_size):
x_in[i,0:len(data_input)] = data_input
base_pred = self.base_model.predict(x = x_in)
#print('base_pred:\n', base_pred)
#y_p = base_pred
#for j in range(200):
# mean = np.sum(y_p[0][j]) / y_p[0][j].shape[0]
# print('max y_p:',np.max(y_p[0][j]),'min y_p:',np.min(y_p[0][j]),'mean y_p:',mean,'mid y_p:',y_p[0][j][100])
# print('argmin:',np.argmin(y_p[0][j]),'argmax:',np.argmax(y_p[0][j]))
# count=0
# for i in range(y_p[0][j].shape[0]):
# if(y_p[0][j][i] < mean):
# count += 1
# print('count:',count)
base_pred =base_pred[:, :, :]
#base_pred =base_pred[:, 2:, :]
r = K.ctc_decode(base_pred, in_len, greedy = True, beam_width=100, top_paths=1)
#print('r', r)
r1 = K.get_value(r[0][0])
#print('r1', r1)
#r2 = K.get_value(r[1])
#print(r2)
r1=r1[0]
return r1
pass
def RecognizeSpeech(self, wavsignal, fs):
'''
最终做语音识别用的函数,识别一个wav序列的语音
'''
#data = self.data
#data = DataSpeech('E:\\语音数据集')
#data.LoadDataList('dev')
# 获取输入特征
#data_input = GetMfccFeature(wavsignal, fs)
#t0=time.time()
data_input = GetFrequencyFeature3(wavsignal, fs)
#t1=time.time()
#print('time cost:',t1-t0)
input_length = len(data_input)
input_length = input_length // 8
data_input = np.array(data_input, dtype = np.float)
#print(data_input,data_input.shape)
data_input = data_input.reshape(data_input.shape[0],data_input.shape[1],1)
#t2=time.time()
r1 = self.Predict(data_input, input_length)
#t3=time.time()
#print('time cost:',t3-t2)
list_symbol_dic = GetSymbolList(self.datapath) # 获取拼音列表 最终做语音识别用的函数,识别一个wav序列的语音
r_str=[]
for i in r1:
r_str.append(list_symbol_dic[i])
return r_str
pass
def RecognizeSpeech_FromFile(self, filename):
'''
最终做语音识别用的函数,识别指定文件名的语音
'''
wavsignal,fs = read_wav_data(filename)
r = self.RecognizeSpeech(wavsignal, fs)
return r
pass
@property
def model(self):
'''
返回keras model
'''
return self._model
if(__name__=='__main__'): main函数,启动
#import tensorflow as tf
#from keras.backend.tensorflow_backend import set_session
#os.environ["CUDA_VISIBLE_DEVICES"] = "1"
#进行配置,使用70%的GPU
#config = tf.ConfigProto()
#config.gpu_options.per_process_gpu_memory_fraction = 0.95
#config.gpu_options.allow_growth=True #不全部占满显存, 按需分配
#set_session(tf.Session(config=config))
datapath = abspath + ''
modelpath = abspath + 'model_speech'
if(not os.path.exists(modelpath)): # 判断保存模型的目录是否存在
os.makedirs(modelpath) # 如果不存在,就新建一个,避免之后保存模型的时候炸掉
system_type = plat.system() # 由于不同的系统的文件路径表示不一样,需要进行判断
if(system_type == 'Windows'):
datapath = 'E:\\语音数据集'
modelpath = modelpath + '\\'
elif(system_type == 'Linux'):
datapath = abspath + 'dataset'
modelpath = modelpath + '/'
else:
print('*[Message] Unknown System\n')
datapath = 'dataset'
modelpath = modelpath + '/'
ms = ModelSpeech(datapath)
#ms.LoadModel(modelpath + 'm261/speechmodel261e0step98000.model')
ms.TrainModel(datapath, epoch = 50, batchsize = 16, savestep = 500)
#ms.TestModel(datapath, strdataset='test', datacount = 128, outreport = True)
#r = ms.RecognizeSpeechFromFile('E:\语音数据集\ST-CMDS-201700011-OS\20170001P00241I0053.wav')
#r = ms.RecognizeSpeechFromFile('E:\语音数据集\ST-CMDS-201700011-OS\20170001P00020I0087.wav')
#r = ms.RecognizeSpeechFromFile('E:\语音数据集\wav\train\A11\A11167.WAV')
#r = ms.RecognizeSpeechFromFile('E:\语音数据集\wav\test\D4\D4750.wav')
#print('*[提示] 语音识别结果:\n',r) 实战二调百度和科大讯飞API

现在演示的是识别音频文件的内容。token获取见官网,这边调包没什么含金量。Python 技术篇-百度语音API鉴权认证获取Access Token注:下面的token 是我自己申请的,建议按照我的文章自己来申请专属的。
import requests
import os
import base64
import json
apiUrl='http://vop.baidu.com/serverapi'
filename = "16k.pcm" # 这是我下载到本地的音频样例文件名
size = os.path.getsize(filename) # 获取本地语音文件尺寸
file1 = open(filename, "rb").read() # 读取本地语音文件
text = base64.b64encode(file1).decode("utf-8") # 对读取的文件进行base64编码
data = {
"format":"pcm", # 音频格式
"rate":16000, # 采样率,固定值16000
"devpid":1536, # 普通话
"channel":1, # 频道,固定值1
"token":"24.0c828682d414bf79b08f89c4c7dcd83a.2592000.1562739150.282335-16470175", # 重要,鉴权认证Access Token,需要自己来申请
"cuid":"DC-85-DE-F9-08-59", # 随便一个值就好了,官网推荐是个人电脑的MAC地址
"len":size, # 语音文件的尺寸
"speech":text, # base64编码的语音文件
}
try:
r = requests.post(apiUrl, data = json.dumps(data)).json()
print(r)
print(r.get("result")[0])
except Exception as e:
print(e)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 科大讯飞同样的方式,参见官网教程。
实战三离线语音识别Vosk
Vosk 支持30多种语言,并且现在做的不错,在离线语音里面不错了,https://github.com/alphacep/vosk-api
带Android python,c++ 的pc版本,等等web部署方案Android 的话,就需要你安装Android 包,然后还要下载编译工具,gradlecd androidgradle build即可编译,编译成功后会生成apk安装包,手机就能安装,离线使用了。部分代码:
/**
* Adds listener.
*/
public void addListener(RecognitionListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Removes listener.
*/
public void removeListener(RecognitionListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
/**
* Starts recognition. Does nothing if recognition is active.
*
* @return true if recognition was actually started
*/
public boolean startListening() {
if (null != recognizerThread)
return false;
recognizerThread = new RecognizerThread();
recognizerThread.start();
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 这边实战的比较简单,后续我做了很多优化,支持Android,python ,c++,java语言等部署,欢迎咨询我。
