当前位置: 首页 > 科技观察

用Python训练自己的语音识别系统,这波操作稳了

时间:2023-03-18 23:17:01 科技观察

近年来,语音识别技术发展迅猛,从手机中的Siri语音智能助手,到微软的Cortana和各平台的智能音箱等,各种语音识别项目得到广泛应用。语音识别属于感知智能,让机器从简单的识别语音到理解语音已经上升到认知智能的层面。机器的自然语言理解能力也成为了它是否智能的标志,而自然语言理解正是目前的难点。同时,考虑到目前的语音识别平台大多依赖于智能云,语音识别的训练对大多数人来说还是比较神秘的,所以今天我们就用python来搭建自己的语音识别系统。最终模型的识别效果如下:实验前的准备首先,我们使用的python版本是3.6.5。使用的库是图像处理的cv2库;Numpy库用于矩阵运算;Keras框架用于训练和加载模型。Librosa和python_speech_features库用于提取音频特征。Glob和pickle库用于读取本地数据集。数据集准备首先,数据集使用清华大学thchs30中文数据。这些录音根据其文本内容分为四个部分,A(句子ID为1~250)、B(句子ID为251~500)、C(501~750)、D(751~1000)。ABC组包括30个人的10893个发音用于训练,D组包括10个人的2496个发音用于测试。data文件夹包含(.wav文件和.trn文件;.wav文件的文字说明存放在trn文件中:第一行是word,第二行是拼音,第三行是音素);数据集如下:模型训练1、提取语音数据集的MFCC特征:首先,人的声音是通过声道产生的,声道的形状决定了它发出什么样的声音。如果我们能准确地知道这个形状,那么我们就可以准确描述产生的音素。声道的形状显示在语音的短期功率谱的包络中。MFCC是准确描述此包络的功能。提取的MFCC特征如下图所示。因此,我们需要在读取数据集的基础上,提取并存储其语音特征,以方便加载到神经网络中进行训练。对应代码如下:#读取数据集文件text_paths=glob.glob('data/*.trn')total=len(text_paths)print(total)withopen(text_paths[0],'r',encoding='utf8')asfr:lines=fr.readlinesprint(lines)#dataset文件trn内容被读取并保存到数组texts=paths=forpathintext_paths:withopen(path,'r',encoding='utf8')asfr:lines=fr.readlinesline=lines[0].strip('\n').replace('','')texts.append(line)paths.append(path.rstrip('.trn'))print(paths[0],texts[0])#Definemfccnumbermfcc_dim=13#读入根据数据集校准的音素defload_and_trim(path):audio,sr=librosa.load(path)energy=librosa.feature.rmse(audio)frames=np.nonzero(能量>=np.max(能量)/5)indices=librosa.core.frames_to_samples(frames)[1]audio=audio[indices[0]:indices[-1]]ifindices.sizeelsaudio[0:0]returnaudio,sr#提取音频特征并存储特征=foriintqdm(range(total)):path=paths[i]audio,sr=load_and_trim(path)features.append(mfcc(audio,sr,numcep=mfcc_dim,nfft=551))print(len(特征),features[0].shape)2.神经网络预处理:在神经网络加载和训练之前,我们需要对读取到的MFCC特征进行归一化处理,主要目的是加快收敛,增强效果,减少干扰。然后处理数据集和标签来定义输入和输出。对应代码如下:#随机选取100个数据集samples=random.sample(features,100)samples=np.vstack(samples)#平均MFCC值用于归一化处理mfcc_mean=np.mean(samples,axis=0)#计算归一化的标准差mfcc_std=np.std(samples,axis=0)print(mfcc_mean)print(mfcc_std)#归一化特征features=[(feature-mfcc_mean)/(mfcc_std+1e-14)forfeatureinfeatures]#将数据集读入label和对应的id存储列表chars={}fortextintexts:forcintext:chars[c]=chars.get(c,0)+1chars=sorted(chars.items,key=lambdax:x[1],reverse=True)chars=[char[0]forcharinchars]print(len(chars),chars[:100])char2id={c:ifori,cinenumerate(chars)}id2char={i:cfori,cinenumerate(chars)}data_index=np.arange(total)np.random.shuffle(data_index)train_size=int(0.9*total)test_size=total-train_sizetrain_index=data_index[:train_size]test_index=data_index[train_size:]#neuralnetworkinput和输出X,Y的读入数据集特征X_train=[features[i]foriintrain_index]Y_train=[texts[i]foriintrain_index]X_test=[features[i]foriintest_index]Y_test=[texts[i]foriintest_index]3.神经网络函数定义:包括训练批次、卷积层函数、归一化函数、激活层函数等第一维是小段的个数,原始语音越长,第一维越大,第二维是MFCC特征的维数。一旦获得了原始语音的数值表示,就可以使用WaveNet来实现。由于MFCC特征是一维序列,所以使用Conv1D进行卷积。因果关系是指卷积的输出只与当前位置之前的输入相关,即不使用未来的特征,可以理解为将卷积的位置前移。WaveNet模型结构如下?:具体如下可见:batch_size=16#定义训练批次的生成,一次训练16个defbatch_generator(x,y,batch_size=batch_size):offset=0whileTrue:offset+=batch_sizeifoffset==batch_lensize(x):data_index=np.arange(len(x))np.random.shuffle(data_index)x=[x[i]foriindata_index]y=[y[i]foriindata_index]offset=batch_sizeX_data=x[offset-batch_size:offset]Y_data=y[offset-batch_size:offset]X_maxlen=max([X_data[i].shape[0]foriinrange(batch_size)])Y_maxlen=max([len(Y_data[i])foriinrange(batch_size)])X_batch=np.zeros([batch_size,X_maxlen,mfcc_dim])Y_batch=np.ones([batch_size,Y_maxlen])*len(char2id)X_length=np.zeros([batch_size,1],dtype='int32')Y_length=np.zeros([batch_size,1],dtype='int32')foriinrange(batch_size):X_length[i,0]=X_data[i].shape[0]X_batch[i,:X_length[i,0],:]=X_data[i]Y_length[i,0]=len(Y_data[i])Y_batch[i,:Y_length[i,0]]=[char2id[c]forcinY_data[i]]inputs={'X':X_batch,'Y':Y_batch,'X_length':X_length,'Y_length':Y_length}outputs={'ctc':np.zeros([batch_size])}epochs=50num_blocks=3filters=128X=Input(shape=(None,mfcc_dim,),dtype='float32',name='X')Y=Input(shape=(None,),dtype='float32',name='Y')X_length=Input(shape=(1,),dtype='int32',name='X_length')Y_length=Input(shape=(1,),dtype='int32',name='Y_length')#卷积1层defconv1d(inputs,filters,kernel_size,dilation_rate):returnConv1D(filters=filters,kernel_size=kernel_size,strides=1,padding='causal',activation=None,dilation_rate=dilation_rate)(inputs)#标准化函数defbatchnorm(inputs):returnBatchNormalization(inputs)#激活层函数defactivation(inputs,activation):returnActivation(activation)(inputs)#全连接层函数defres_block(inputs,filters,kernel_size,dilation_rate):hf=activation(batchnorm(conv1d(输入,过滤器,kernel_size,dilation_rate),'tanh')hg=激活(batchnorm(conv1d(输入,过滤器,kernel_size,dilation_rate)),'sigmoid')h0=乘法([hf,hg])ha=激活(batchnorm(conv1d(h0,过滤器,1,1)),'tanh')hs=激活(batchnorm(conv1d(h0,过滤器,1,1)),'tanh')returnAdd([ha,输入]),hsh0=活动vation(batchnorm(conv1d(X,filters,1,1)),'tanh')shortcut=foriinrange(num_blocks):forrin[1,2,4,8,16]:h0,s=res_block(h0,filters,7,r)shortcut.append(s)h1=activation(Add(shortcut),'relu')h1=activation(batchnorm(conv1d(h1,filters,1,1)),'relu')#softmax损失函数输出结果Y_pred=activation(batchnorm(conv1d(h1,len(char2id)+1,1,1)),'softmax')sub_model=Model(inputs=X,outputs=Y_pred)#计算损失函数defcalc_ctc_loss(args):y,yp,ypl,yl=argsreturnK.ctc_batch_cost(y,yp,ypl,yl)4.模型训练:训练过程可以看如下:ctc_loss=Lambda(calc_ctc_loss,output_shape=(1,),name='ctc')([Y,Y_pred,X_length,Y_length])#加载模型训练模型=模型(inputs=[X,Y,X_length,Y_length],outputs=ctc_loss)#buildoptimizeroptimizer=SGD(lr=0.02,momentum=0.9,nesterov=True,clipnorm=5)#激活模型开始计算model.compile(loss={'ctc':lambdactc_true,ctc_pred:ctc_pred},op??timizer=optimizer)checkpointer=ModelCheckpoint(filepath='asr.h5',详细=0)lr_decay=ReduceLROnPlateau(monitor='loss',factor=0.2,patience=1,min_lr=0.000)#开始训练history=model.fit_generator(generator=batch_generator(X_train,Y_train),steps_per_epoch=len(X_train)//batch_size,epochs=epochs,validation_data=batch_generator(X_test,Y_test),validation_steps=len(X_test)//batch_size,callbacks=[checkpointer,lr_decay])#savemodelsub_model.save('asr.h5')#保存pl=pkl中的单词withopen('dictionary.pkl','wb')asfw:pickle.dump([char2id,id2char,mfcc_mean,mfcc_std],fw)train_loss=history.history['loss']valid_loss=history.history['val_loss']plt.plot(np.linspace(1,epochs,epochs),train_loss,label='train')plt.plot(np.linspace(1、epochs,epochs),valid_loss,label='valid')plt.legend(loc='upperright')plt.xlabel('Epoch')plt.ylabel('Loss')plt.show测试模型读取我们的字典由语音数据集生成,通过调用模型识别音频特征代码如下:wavs=glob.glob('A2_103.wav')print(wavs)withopen('dictionary.pkl','rb')asfr:[char2id,id2char,mfcc_mean,mfcc_std]=pickle.load(fr)mfcc_dim=13model=load_model('asr.h5')index=np.random.randint(len(wavs))print(wavs[index])audio,sr=librosa.load(wavs[index])energy=librosa。特征。rmse(音频)frames=np.nonzero(能量>=np.max(能量)/5)indices=librosa.core.frames_to_samples(frames)[1]audio=audio[indices[0]:indices[-1]]ifindices.sizeelsaudio[0:0]X_data=mfcc(audio,sr,numcep=mfcc_dim,nfft=551)X_data=(X_data-mfcc_mean)/(mfcc_std+1e-14)print(X_data.shape)pred=model.predict(np.expand_dims(X_data,axis=0))pred_ids=K.eval(K.ctc_decode(pred,[X_data.shape[0]],greedy=False,beam_width=10,top_paths=1)[0][0])pred_ids=pred_ids.flatten.tolistprint(''.join([id2char[i]foriinpred_ids]))yield(inputs,outputs)至此,我们的整体程序就搭建完成了。下面是我们程序的运行结果:源码地址:https://pan.baidu.com/s/1tFlZkMJmrMTD05cd_zxmAg提取码:ndrr数据集需要自行下载。