关于音视频:AI-全栈-SOTA-综述-这些你都不知道怎么敢说会-AI语音识别原理-实战

28次阅读

共计 18118 个字符,预计需要花费 46 分钟才能阅读完成。

章目录

 前言
语音辨认原理
    信号处理,声学特征提取
    辨认字符,组成文本
    声学模型
    语言模型
    词汇模型
语音声学特征提取:MFCC 和 LogFBank 算法的原理
实战一 ASR 语音辨认模型
        零碎的流程
        基于 HTTP 协定的 API 接口
        客户端
        将来
实战二 调百度和科大讯飞 API
实战三 离线语音辨认 Vosk

前言

语音辨认原理

首先是语音工作,如语音辨认和语音唤醒。听到这些,你会想到科大讯飞、百度等中国的平台。因为这两家公司占据了中国 80% 的语音市场,所以他们做得十分好。然而因为高精度的技术,他们不能开源,其余公司不得不花很多钱购买他们的 API,然而语音辨认和其余利用很难学习(我培训了一个语音辨认我的项目,10 个图形卡须要运行 20 天),这导致了民间语音辨认的发展缓慢。陈军收集了大量 SOTA 在以后畛域的原理和实战局部。明天让咱们大饱眼福吧!

语音采样

在语音输入后的数字化过程中,首先要确定语音的起始和完结,而后进行降噪和滤波(除人声外还有许多噪声),以保障计算机可能辨认滤波后的语音信息。为了进一步解决,还须要对音频信号帧进行解决。同时,从宏观的角度来看,人们的语音信号个别在一段时间内是绝对稳固的,这就是所谓的短期平稳性,因而须要对语音信号进行帧间解决,以便于解决。

通常一帧须要 20~50ms,帧间存在重叠冗余,防止了帧两端信号的弱化,影响辨认精度。接下来是要害特征提取。因为对原始波形的辨认不能达到很好的辨认成果,须要通过频域变换提取特征参数。罕用的变换办法是提取 MFCC 特色,并依据人耳的生理个性将每帧波形变换为原始波形向量矩阵。

逐帧的向量不是很直观。您也能够应用下图中的频谱图来示意语音。每列从左到右是一个 25 毫秒的块。与原始声波相比,从这类数据中寻找法则要容易得多。

然而,频谱图次要用于语音钻研,语音辨认还须要逐帧应用特征向量。

辨认字符,组成文本

特征提取实现后,进行特色辨认和字符生成。这一部分的工作是从每一帧中找出以后的音位,而后从多个音位中构词,再从词中构词。当然,最艰难的是从每一帧中找出以后的音素,因为每一帧都少于一个音素,而且只有多个帧能力造成一个音素。如果一开始是错的,当前很难纠正。如何判断每一帧属于哪个音素?最简略的办法是概率,哪个音素的概率最高。如果每帧中多个音素的概率是雷同的呢?毕竟,这是可能的。每个人的口音、谈话速度和语调都不一样,人们很难了解你说的是你好还是霍尔。然而,语音辨认的文本后果只有一个,人们不可能参加到纠错的抉择中。此时,多个音素形成了单词的统计决策,单词形成了文本

这容许咱们失去三个可能的转录 -“你好”,“呼啦”和“奥洛”。最初,依据单词的概率,咱们会发现 hello 是最有可能的,所以咱们输入 hello 的文本。下面的例子分明地形容了概率如何决定从帧到音素,而后从音素到单词的所有。如何取得这些概率?咱们能数一数人类几千年来所说的所有音素、单词和句子,以便辨认一种语言,而后计算概率吗?这是不可能的。咱们该怎么办?那咱们须要模型:

声学模型

cv 君置信大家肯定晓得是么是声学模型~ 依据语音的根本状态和概率,尝试获取不同人群、年龄、性别、口音、谈话速度的语音语料,同时尝试采集各种宁静、嘈杂、边远的语音语料来生成声学模型。为了达到更好的成果,不同的语言和方言会采纳不同的声学模型来进步精度,缩小计算量。

语言模型

而后对根本的语言模型,单词和句子的概率,进行大量的文本训练。如果模型中只有“明天星期一”和“今天星期二”两句话,咱们只能辨认这两句话。如果咱们想辨认更多的句子,咱们只须要笼罩足够的语料库,然而模型会减少,计算量也会减少。所以咱们理论利用中的模型通常局限于应用领域,如智能家居、导航、智能音箱、集体助理、医疗等,能够缩小计算量,进步精度,

词汇模型

最初,它还是一个比拟罕用的词汇模型,是对语言模型的补充,是一个语言词典和不同发音的正文。例如,地名、人名、歌曲名、热门词汇、某些畛域的非凡词汇等都会定期更新。目前,已有许多简化但无效的计算方法,如 HMM 隐马尔可夫模型。隐马尔可夫模型次要基于两个假如:一是外部状态转移只与前一状态相干,二是输入值只与以后状态(或以后状态转移)相干。简化了问题,也就是说,一个句子中一个词序列的概率只与前一个词相干,因而计算量大大简化。

最初,将语音辨认为文本。语音声学特征提取:MFCC 和 logfbank 算法原理

简直所有的主动语音识别系统,第一步都是提取语音信号的特色。通过提取语音信号的相干特色,有助于辨认出相干的语音信息,并将背景噪声、情感等无关信息剔除。

1 MFCC

刚刚 cv 君说到了 MFCC,这个很经典哦~ MFCC 的全称是“梅尔频率倒谱系数”,这语音特征提取算法是这几十年来,罕用的算法之一。这算法通过在声音频率中,对非线性梅尔的对数能量频谱,线性变换失去的。

1.1 分帧

因为存储在计算机硬盘中的原始 wav 音频文件是可变长度的,咱们首先须要将其切割成几个固定长度的小块,即帧。依据语音信号变动快的特点,每帧的时长个别取 10-30ms,以保障一帧中有足够的周期,且变动不会太激烈。因而,这种傅里叶变换更适宜于安稳信号的剖析。因为数字音频的采样率不同,每个帧向量的维数也不同。

1.2 预减轻

因为人体声门收回的声音信号有 12dB/ 倍频程衰减,而嘴唇收回的声音信号有 6dB/ 倍频程衰减,因而通过疾速傅立叶变换后的高频信号中成分很少。因而,语音信号预减轻操作的次要目标是对每帧语音信号的高频局部进行加强,从而进步高频信号的分辨率。

1.3 加窗

在之前的成帧过程中,一个间断的语音信号被间接宰割成若干段,因为截断效应会导致频谱透露。开窗操作的目标是打消每帧两端边缘的短时信号不间断问题。在 MFCC 算法中,窗函数通常是 Hamming 窗、矩形窗和 Hanning 窗。须要留神的是,在开窗之前必须进行预强调。

1.4 疾速傅里叶变换

通过以上一系列的解决,咱们依然失去时域信号,而在时域中能够间接取得的语音信息量较少。在语音信号的进一步特征提取中,须要将每一帧的时域信号转换为其频域信号。对于存储在计算机中的语音信号,咱们须要应用离散傅立叶变换。因为一般离散傅里叶变换计算复杂度高,通常采纳疾速傅里叶变换来实现。因为 MFCC 算法是分帧的,每一帧都是一个短时域信号,所以这一步又称为短时疾速傅立叶变换。

1.5 计算幅度谱 (对复数取模)

实现疾速傅里叶变换后,语音特色是一个复矩阵,它是一个能谱。因为能谱中的相位谱蕴含的信息非常少,咱们个别抉择抛弃相位谱,保留幅度谱。

抛弃相位谱保留幅度谱个别有两种办法,别离是求每个复数的绝对值或平方值。

1.6 Mel 滤波

Mel 滤波的过程是 MFCC 的要害之一。Mel 滤波器是由 20 个三角形带通滤波器组成的,将线性频率转换为非线性散布的 Mel 频率。

2 logfBank

对数银行特征提取算法相似于 MFCC 算法,是基于对数银行的特征提取后果进行解决。然而 logfBank 和 MFCC 算法的次要区别在于是否进行离散余弦变换。

随着 DNN 和 CNN 的呈现,特地是深度学习的倒退,神经网络能够更好地利用 fBank 和 logfBank 特色之间的相关性来进步最终语音辨认的准确性,缩小 WER,因而能够省略离散余弦变换的步骤。

SOTA 原理 + 实战 1 深度全卷积神经网络 语音辨认

近年来,深度学习在人工智能畛域呈现,对语音辨认也产生了深远的影响。深度神经网络曾经逐步取代了最后的 HMM 隐马尔可夫模型。在人类的交换和常识流传中,大概 70% 的信息来自语音。在将来,语音辨认将不可避免地成为智能生存的重要组成部分,它能够为语音辅助和语音输入提供必要的根底,这将成为一种新的人机交互形式。因而,咱们须要让机器了解人的声音。

语音识别系统的声学模型采纳深度全卷积神经网络,间接以声谱图为输出。在模型的构造上,借鉴了图像识别中的最佳网络配置 VGG。这种网络模型具备很强的表达能力,能够看到很长的历史和将来的信息,比 RNN 更强壮。在输入端,模型能够通过 CTC 计划来实现

语音识别系统的声学模型采纳深度全卷积神经网络,间接以声谱图为输出。在模型的构造上,借鉴了图像识别中的最佳网络配置 VGG。这种网络模型具备很强的表达能力,能够看到很长的历史和将来的信息,比 RNN 更强壮。在输入端,该模型能够与 CTC 计划完满联合,实现整个模型的端到端训练,间接将声音波形信号转录成汉语普通话拼音序列。在语言模型上,通过最大熵隐马尔可夫模型将拼音序列转换成中文文本。并且,为了通过网络向所有用户提供服务。特征提取通过成帧和加窗操作将一般的 wav 语音信号转换成神经网络所需的二维频谱图像信号

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, concatenate
from 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='in
                       
  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 函数,启动

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)

原理 + 实战二 百度和科大讯飞语音辨认

端到端的深度单干学习钻研办法咱们能够用来进行辨认英语或汉语普通话,这是两种截然不同的语言。因为应用神经网络手工设计整个过程的每个组成部分,端到端的学习使咱们可能解决各种各样的声音,包含嘈杂的环境,压力和不同的语言。咱们的办法要害是进步咱们能够利用的 HPC 技术,以前倒退须要数周能力进行实现的试验,当初在几天内就能够通过实现。这使得学生咱们本人可能更快地进行迭代,以甄别出优越的架构和算法。最初,在数据信息中心进行应用称为 Batch Dispatch with GPU 的技术,咱们钻研表明,咱们的系统分析能够同时通过网络在线配置,以低成本部署,低提早地为大量用户治理提供一个服务。

端到端语音辨认是一个沉闷的钻研畛域,将其用于从新评估 DNN-HMM 的输入,获得了令人信服的后果。Rnn 编解码器应用编码器 rnn 将输出映射到固定长度矢量,而解码器网络将固定长度矢量映射到输入预测序列。有着本人注意力的 RNN 编码器 – 解码器在预测音素教学方面进行体现一个良好。联合 ctc 损失函数和 rnn 对工夫信息进行了模仿,并在字符输入的端到端语音辨认中获得了良好的成果。Ctc-rnn 模型也能很好地预测音素,只管在这种状况下依然须要一本词典。

数据技术也是端到端语音进行识别系统胜利的要害,Hannun 等人应用了中国超过 7000 小时的标记语言语音。数据加强对于进步计算机视觉和语音辨认等深度学习的性能十分无效。现有的语音零碎也能够用来疏导新的数据收集。咱们从以往的办法中失去启发,疏导更大的数据集和数据减少,以减少百度零碎中的无效标记数据量。

当初的演示是辨认音频文件的内容。token 获取见官网,这边调包没什么含金量 Python 技术篇 - 百度进行语音 API 鉴权认证信息获取 Access Token 注:上面的 token 是我本人能够申请的,:上面的 token 是我本人申请的,倡议依照我的文章本人来申请专属的。

import requests
import os
import base64
import json

apiUrl='http://vop.baidu.com/server_api'
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
    "dev_pid":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)

科大讯飞同样的形式,参见官网教程。

实战三 离线语音辨认 Vosk

cv 君明天因为篇幅问题,介绍了大量原理和 Sota 算法,所以当初再最初分享一个,须要理解更多,欢送继续关注本系列。

Vosk 反对 30 多种语言,并且当初做的不错,在离线语音外面不错了,https://github.com/alphacep/v…

带 Android python,c++ 的 pc 版本,等等 web 部署计划 Android 的话,就须要你装置 Android 包,而后还要下载编译工具,gradle,通过 Gradle 等形式编译。

即可编译,编译胜利后会生成 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;
}

这边实战的比较简单,后续我做了很多优化,反对 Android,python,c++,java 语言等部署,欢送征询 cv 君。

智能语音交互图

总结

明天说了很多,欢送各位看官赏脸观看,这篇文章较多地在介绍 Tricks,互动性和趣味性在前面实战局部~ 而且又是语音的算法,明天没法给大家演示很多乏味的。

后文,能够给大家由浅入深地进阶语音局部的其余畛域局部:

一:诸如 Siri,小爱同学这样的唤醒词算法和模型和 SOTA;

二:谈话人区别(甄别思维)的 SOTA

三:多语种思路 + 少语种 + 艰难语种思路和 SOTA

四:各个语音较量 SOTA 计划

正文完
 0