语音是人类最自然的交互方式。计算机发明之后让机器能够“听懂”人类的语言、理解语言含义,并能做出正确回答就成为了人们追求的目标。这个过程主要采用了 3 种技术,即自动语音识别( automatic speech recognition, ASR )、自然语言处理( natural language processing, NLP )和语音合成( speech synthesis,SS )。语音识别技术的目的是让机器能听懂人类的语音,是一个典型的交叉学科任务。
语音识别系统模型由声学模型和语言模型两个部分组成,声学模型对应于语音到音素的概率计算,语言模型对应于音素到文字的概率计算。
一个连续语音识别系统大致可以由四个部分组成:特征提取,声学模型,语言模型和解码部分。具体过程是首先从语音数据中经过提取得到声学特征,然后经过模型训练统计得到一个声学模型,作为识别的模板,并结合语言模型经过解码处理得到一个识别结果。
声学模型是语音识别系统中关键的部分,它的作用是对声学单元产生的特征序列进行描述,对语音信号进行了分类。我们可以用声学模型来计算一段观测到的特征矢量属于各个声学单元的概率并根据似然准则将特征序列转化为状态序列。
本文数据集地址 清华大学 THCHS30 中文语音数据集。
详细代码教程 中文语音识别
神经网络不能将音频作为输入进行训练,所以第一步我们要对音频数据进行特征提取。常见的特征提取都是基于人类的发声机理和听觉感知,从发声机理到听觉感知认识声音的本质。
常用的一些声学特征如下:
(1) 线性预测系数( LPC ),线性预测分析是模拟人类的发声原理,通过分析声道短管级联的模型得到的。假设系统的传递函数跟全极点的数字滤波器是相似的,通常用 12 一 16 个极点就可以描述语音信号的特征。所以对于 n 时刻的语音信号,
我们可以用之前时刻的信号的线性组合近似的模拟。然后计算语音信号的采样值和线性预测的采样值,并让这两者之间达到均方的误差( MSE )最小,就可以得到 LPC。
(2) 感知线性预测( PLP ),PLP 是一种基于听觉模型的特征参数。该参数是一种等效于 LPC 的特征,也是全极点模型预测多项式的一组系数。不同之处是
PLP 是基于入耳昕觉,通过计算应用到频谱分析中,将输入语音信号经过入耳听觉模型处理,替代 LPC 所用的时域信号,这样的优点是有利于抗噪语音特征的提取。
(3)梅尔频率倒谱系数( MFCC ),MFCC 也是基于入耳听觉特性,梅尔频
率倒谱频带划分是在 Mel 刻度上等距划的,Mel 频率的尺度值与实际频率的对数分布关系更符合人耳的听觉特性,所以可以使得语音信号有着更好的表示。
(5)基于滤波器组的特征 Fbank(Filter bank),Fbank 特征提取方法就是相当
于 MFCC 去掉最后一步的离散余弦变换,跟 MFCC 特征,Fbank 特征保留了更多的原始语音数据。
(6)语谱图( Spectrogram ),语谱图就是语音频谱图,一般是通过处理接收的时域信号得到频谱图,因此只要有足够时间长度的时域信号就可。语谱图的特点是观察语音不同频段的信号强度,可以看出随时间的变化情况。
本文就是通过将语谱图作为特征输入,利用 CNN 进行图像处理进行训练。语谱图可以理解为在一段时间内的频谱图叠加而成,因此提取语谱图的主要步骤分为:分帧、加窗、快速傅立叶变换( FFT )。
第一步,我们需要找到利用 scipy 模块将音频转化为有用的信息,fs 为采样频率,wavsignal 为语音数据。我们的数据集的 fs 均为 16khz。
import scipy.io.wavfile as wav
filepath = 'test.wav'
fs, wavsignal = wav.read(filepath)
语音信号在宏观上是不平稳的,在微观上是平稳的,具有短时平稳性( 10 — 30ms 内可以认为语音信号近似不变为一个音素的发音),一般情况下取 25ms。
为了处理语音信号,我们要对语音信号进行加窗,也就是一次仅处理窗中的数据。因为实际的语音信号是很长的,我们不能也不必对非常长的数据进行一次性处理。明智的解决办法就是每次取一段数据,进行分析,然后再取下一段数据,再进行分析。我们的加窗操作指的就是汉明窗操作,原理就是把一帧内的数据乘以一个函数 并得到新的一帧数据。公式下文已给出。
怎么仅取一段数据呢?因为之后我们会对汉明窗中的数据进行快速傅立叶变换( FFT ),它假设一个窗内的信号是代表一个周期的信号(也就是说窗的左端和右端大致可以连续),而通常一小段音频数据没有明显的周期性,加上汉明窗后,数据就比较接近周期函数了。
由于加上汉明窗,只有中间的数据体现出来了,两边的数据信息丢失了,所以等会移窗的时候会有重叠的部分,当窗口取了 25ms 时,步长可以取 10ms (其中 a 的值一般取 0.46 )。
公式为:
代码为:
import numpy as np
x=np.linspace(0, 400 - 1, 400, dtype = np.int64)#返回区间内的均匀数字
w = 0.54 - 0.46 * np.cos(2 * np.pi * (x) / (400 - 1))
time_window = 25
window_length = fs // 1000 * time_window
# 分帧
p_begin = 0
p_end = p_begin + window_length
frame = wavsignal[p_begin:p_end]
plt.figure(figsize=(15, 5))
ax4 = plt.subplot(121)
ax4.set_title('the original picture of one frame')
ax4.plot(frame)
# 加窗
frame = frame * w
ax5 = plt.subplot(122)
ax5.set_title('after hanmming')
ax5.plot(frame)
plt.show()
语音信号在时域上比较难看出其特性,所以通常转换为频域上的能量分布,所以我们对每帧经过窗函数处理的信号做快速傅立叶变换将时域图转换成各帧的频谱,然后我们可以对每个窗口的频谱叠加得到语谱图。
代码为:
from scipy.fftpack import fft
# 进行快速傅里叶变换
frame_fft = np.abs(fft(frame))[:200]
plt.plot(frame_fft)
plt.show()
# 取对数,求 db
frame_log = np.log(frame_fft)
plt.plot(frame_log)
plt.show()
谈及语音识别,如果这里有一个剪辑音频的数据集和对应的转录,而我们不知道怎么把转录中的字符和音频中的音素对齐,这会大大增加了训练语音识别器的难度。如果不对数据进行调整处理,那就意味着不能用一些简单方法进行训练。对此,我们可以选择的第一个方法是制定一项规则,如“一个字符对应十个音素输入”,但人们的语速千差万别,这种做法很容易出现纰漏。为了保证模型的可靠性,第二种方法,即手动对齐每个字符在音频中的位置,训练的模型性能效果更佳,因为我们能知道每个输入时间步长的真实信息。但它的缺点也很明显——即便是大小合适的数据集,这样的做法依然非常耗时。事实上,制定规则准确率不佳、手动调试用时过长不仅仅出现在语音识别领域,其它工作,如手写识别、在视频中添加动作标记,同样会面对这些问题。
这种场景下,正是 CTC 用武之地。CTC 是一种让网络自动学会对齐的好方法,十分适合语音识别和书写识别。为了描述地更形象一些,我们可以把输入序列(音频)映射为 X=[x1,x2,…,xT],其相应的输出序列(转录)即为 Y=[y1,y2,…,yU]。这之后,将字符与音素对齐的操作就相当于在 X 和 Y 之间建立一个准确的映射,详细内容可见CTC 经典文章。
损失函数部分代码:
def ctc_lambda(args):
labels, y_pred, input_length, label_length = args
y_pred = y_pred[:, :, :]
return K.ctc_batch_cost(labels, y_pred, input_length, label_length)
解码部分代码:
#num_result 为模型预测结果,num2word 对应拼音列表。
def decode_ctc(num_result, num2word):
result = num_result[:, :, :]
in_len = np.zeros((1), dtype = np.int32)
in_len[0] = result.shape[1];
r = K.ctc_decode(result, in_len, greedy = True, beam_width=10, top_paths=1)
r1 = K.get_value(r[0][0])
r1 = r1[0]
text = []
for i in r1:
text.append(num2word[i])
return r1, text
模型主要利用 CNN 来处理图像并通过最大值池化来提取主要特征加入定义好的 CTC 损失函数来进行训练。当有了输入和标签的话,模型构造就可以自己进行设定,如果准确率得以提升,那么都是可取的。有兴趣也可以加入 LSTM 等网络结构,关于 CNN 和池化操作网上资料很多,这里就不再赘述了。有兴趣的读者可以参考往期的卷积神经网络 AlexNet 。
代码:
class Amodel():
"""docstring for Amodel."""
def __init__(self, vocab_size):
super(Amodel, self).__init__()
self.vocab_size = vocab_size
self._model_init()
self._ctc_init()
self.opt_init()
def _model_init(self):
self.inputs = Input(name='the_inputs', shape=(None, 200, 1))
self.h1 = cnn_cell(32, self.inputs)
self.h2 = cnn_cell(64, self.h1)
self.h3 = cnn_cell(128, self.h2)
self.h4 = cnn_cell(128, self.h3, pool=False)
# 200 / 8 * 128 = 3200
self.h6 = Reshape((-1, 3200))(self.h4)
self.h7 = dense(256)(self.h6)
self.outputs = dense(self.vocab_size, activation='softmax')(self.h7)
self.model = Model(inputs=self.inputs, outputs=self.outputs)
def _ctc_init(self):
self.labels = Input(name='the_labels', shape=[None], dtype='float32')
self.input_length = Input(name='input_length', shape=[1], dtype='int64')
self.label_length = Input(name='label_length', shape=[1], dtype='int64')
self.loss_out = Lambda(ctc_lambda, output_shape=(1,), name='ctc')\
([self.labels, self.outputs, self.input_length, self.label_length])
self.ctc_model = Model(inputs=[self.labels, self.inputs,
self.input_length, self.label_length], outputs=self.loss_out)
def opt_init(self):
opt = Adam(lr = 0.0008, beta_1 = 0.9, beta_2 = 0.999, decay = 0.01, epsilon = 10e-8)
self.ctc_model.compile(loss={'ctc': lambda y_true, output: output}, optimizer=opt)
统计语言模型是自然语言处理的基础,它是一种具有一定上下文相关特性的数学模型,本质上也是概率图模型的一种,并且广泛应用于机器翻译、语音识别、拼音输入、图像文字识别、拼写纠错、查找错别字和搜索引擎等。在很多任务中,计算机需要知道一个文字序列是否能构成一个大家理解、无错别字且有意义的句子,比如这几句话:
许多人可能不太清楚到底机器学习是什么,而它事实上已经成为我们日常生活中不可或缺的重要组成部分。
不太清楚许多人可能机器学习是什么到底,而它成为已经日常我们生活中组成部分不可或缺的重要。
不清太多人机可楚器学许能习是么到什底,而已常我它成经日为们组生中成活部不重可的或缺分要。
第一个句子符合语法规范,词义清楚,第二个句子词义尚且还清楚,第三个连词义都模模糊糊了。这正是从基于规则角度去理解的,在上个世纪 70 年代以前,科学家们也是这样想的。而之后,贾里尼克使用了一个简单的统计模型就解决了这个问题。从统计角度来看,第一个句子的概率很大,比如是 ,而第二个其次,比如是 ,第三个最小,比如是 。按照这种模型,第一个句子出现的概率是第二个的 10 的 20 次方倍,更不用说第三个句子了,所以第一个句子最符合常理。
假设 S 为生成的句子,有一连串的词 w1,w2,… wn 构成,则句子 S 出现的概率为:
由于计算机内存空间和算力的限制,我们明显需要更加合理的运算方法。一般来说,仅考虑与前一个词有关,就可以有着相当不错的准确率,在实际使用中,通常考虑与前两个词有关就足够了,极少情况下才考虑与前三个有关,因此我们可以选择采取下列这个公式:
而 P 我们可以通过爬取资料统计词频来进行计算概率。
拼音转汉字的算法是动态规划,跟寻找最短路径的算法基本相同。我们可以将汉语输入看成一个通信问题,每一个拼音可以对应多个汉字,而每个汉字一次只读一个音,把每一个拼音对应的字从左到有连起来,就成为了一张有向图。
声学模型测试:
语言模型测试:
由于模型简单和数据集过少,模型效果并不是很好。
项目源码地址:https://momodel.cn/explore/5d5b589e1afd9472fe093a9e?type=app
论文:语音识别技术的研究进展与展望
博客:ASRT_SpeechRecognition
博客:DeepSpeechRecognition
Mo(网址:momodel.cn)是一个支持 Python 的人工智能在线建模平台,能帮助你快速开发、训练并部署模型。
Mo 人工智能俱乐部 是由网站的研发与产品设计团队发起、致力于降低人工智能开发与使用门槛的俱乐部。团队具备大数据处理分析、可视化与数据建模经验,已承担多领域智能项目,具备从底层到前端的全线设计开发能力。主要研究方向为大数据管理分析与人工智能技术,并以此来促进数据驱动的科学研究。
目前俱乐部每周六在杭州举办以机器学习为主题的线下技术沙龙活动,不定期进行论文分享与学术交流。希望能汇聚来自各行各业对人工智能感兴趣的朋友,不断交流共同成长,推动人工智能民主化、应用普及化。