[语音识别]用mindspore实现元音分类器的尝试
前言:
自从接触到MindSpore,就一直想要用mindspore实现语音识别。在官网的教程上,看到有机器视觉的(CV),也有自然语言处理的(NLP),可是没有语音识别(ASR),心中一直有个缺憾,想补齐这个能力。于是调研了其他的框架的入门例子,可以理解为机器视觉的MNIST:
1.Tensorflow的语音识别教程,google提供了一个含30个词的65000条语音,可以训练一个简单的语音识别网络,可以识别yes, no, up, down等等简单词。源代码可见于
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/speech_commands
2.但是做语音识别,更专业的会用kaldi,最简单的例子为yesno, 60个音频文件(每个文件含8个单词,单词为希伯来语的“是”,“否”,我也不知道为什么用这种语言入门,不过刚好笔者业余学过hhh,是以色列的官方语言),音频数据训练测试各半分。https://github.com/kaldi-asr/kaldi/tree/master/egs/yesno
但是tf我是没有成功,各种条件不足;kaldi实现过,在ubuntu上,配置好各种依赖,编译完成,可以训练成功,再放到voxforge目录下进行推理,效果不错。不过,不好意思,入门的例子还是用传统的方法,没有涉及深度神经网络。
尝试阅读代码理解整个流程,还是门槛太高,不太懂机器学习,自觉才疏学浅。不过读大学时,自个钻研过语音识别,想出一个架构,后来还做成了毕业设计,所以还是有自己的一些认知和见解。最近藉着MindSpore有幸入门深度学习领域,然后结合自己对语音识别的理解,做一点分享。这个其实也是受官网教程“线性拟合”https://www.mindspore.cn/tutorial/training/zh-CN/master/quick_start/linear_regression.html的启发,越基本对新手越友好。
先把目标和结果写上来吧
目标:用一些a, o, e, i, u, v的音频文件做训练,期望得到一个网络,能够对音频做推理,判断声音中是什么元音。即标题说的,“元音分类器”
结果:最后由于模型设计太简单,没有收敛。所以一方面是分享,另一方面希望大家对模型的设计,调参等给出建议,
语音的原理
声音的本质是简谐运动,机械波(纵波,传播方向与振动方向一致)。所以声音会使介质的粒子(比如空气)振动,并传播,波峰处密,波谷处疏。一般场景,就是空气中传播,那么就是导致不同的压强。
接着通过公式y=A sin(wx)再看声音的几个性质,振幅就体现于声音的响度,频率体现于声音的高低。而语音上表现为轻声、重音;声调高低。这些是结合常识就可以理解的。
但是音色,或者就说/a/, /o/, /e/,这些是从公式的什么地方体现的呢?原来语音不是单纯一个音,而是混合了多种频率的,非常复杂的。
比如y1=2sin(x)+4sin(3x), y2=sin(x)+3sin(3x),这里两种不同频率的正弦波,按照不同的1:2,1:3组合,就形成了不同的音色。简而言之,音色由不同频率的强度占比决定。
声音的记录
我们常听说mp3,m4a等音频格式,这些是怎么来的呢?我们从头说起,声音振动影响气压,记录声音,绝大多数设备就是根据空气的压强。麦克风的传感器(比如压电传感器)将其转换为电压(模拟值),然后做模数转换,就存储为二进制的数据。不论是实时语音语音,还是录音文件的识别,都要经过这一步。
而录音文件,最简单的格式是wav,mp3,m4a则是在不影响人耳感知的前提下做了压缩。
音频文件的读取
一般做语音识别,就是用wav格式的音频做训练。音频文件一般由文件头和数据部分组成。wav是微软定的一个格式,满足数据交换。这个网上很多介绍了,直接看代码附件吧,用C++二进制读取。
频谱分析
由于音频文件记录了时域信息,我们需要频域信息,比较好做分析。所以要用傅里叶变换,这样本来是波形的语音数据,就可以转化为频率的强度分布。代码附件tool.zip。
但是由于语音是不断变化的,计算机又是要离散的,所以做离散的短时傅里叶变换。
自制工具编译命令
g++ -c Complex.cpp -o Complex.o g++ -c Transform.cpp -o Transform.o g++ -c SoundToSpectrum.cpp -o main.o g++ main.o Transform.o Complex.o -o spectrum_tool.exe ./spectrum_tool.exe a0.wav > a0.txt
共振峰
短时傅里叶变换有个约束,帧的长度越小,频率的分辨率越低。经过研究发现,5ms左右的帧,能够得到语音频谱的包络面,而且会显示出几个峰值,叫做共振峰(formant),而这些峰值的频率就可以对应元音。
准备数据
当然最先想到的是录音,不过自己合成也可以。介绍一个开源的软件praat,可以合成哦。于是构造了每种元音各10个音频。然后把文件读取和频谱分析的代码编译后,得到一个频谱计算工具。最后写了一个脚本批量处理这些音频,将频谱信息保存在txt文件中,文件以元音名称和文件序号命名。附件audio.zip为音频文件,spec.zip为频谱数据。
预备工作完成!开始构建网络
首先是读取数据集,自己写了一个很土的读取方法get_data()
然后网络中,先是op.ArgMaxWithValue()这个算子真好用hhh,直接帮我提取频谱中峰值和对应频率。然后就不知道怎么利用好这两个数据了,乘起来?试试,后面开始遍历每个数据文件,设好优化器,学习率,损失函数……用每个数据训练一轮。
import numpy as np from mindspore import nn from mindspore import Parameter from mindspore.nn import WithLossCell from mindspore.nn import TrainOneStepCell from mindspore import context import mindspore import mindspore.ops.operations as op from mindspore import context context.set_context(mode=context.GRAPH_MODE, device_target="GPU") vowel_dict = {"a":100, "o":200, "e":300, "i":400, "u":500, "v":600} def get_data(vowel, file_num): filename=vowel+file_num+".txt" spectrum = np.loadtxt(filename).astype(np.float32) vowel_num = vowel_dict[vowel] label_np = np.array([vowel_num]).astype(np.float32) return spectrum, label_np class Net(nn.Cell): def __init__(self): super(Net, self).__init__() self.arg = op.ArgMaxWithValue() self.mul = op.Mul() weight_np = np.full((1,), 1, dtype=np.float32) sekf.weight = Parameter(Tensor(weight_np), name="weight") def construct(self, input_x): freq, value = self.arg(x) product = self.mul(freq, value) output = self.mul(product, self.weight) return output if __name__ == "__main__": lr = 0.0005 momentum = 0.1 net = Net() loss = nn.loss.MSELoss() opt = nn.Momentum(net.trainable_params(), lr, momentum) net_with_criterion = WithLossCell(net, loss) train_net = TrainOneStepCell(net, opt) train_net.set_train() vowel_list = ["a", "o", "e", "i", "u", "v"] train_list = ["0", "1", "2", "3", "4"] test_list = ["5", "6", "7", "8", "9"] #train for vowel in vowel_list: for file_name in train_list: input_np, label_np = get_data(vowel, file_name) train_net(Tensor(input_np), Tensor(label_np)) #test for vowel in vowel_list: for file_name in train_list: input_np, label_np = get_data(vowel, file_name) predict = net(Tensor(input_np)) error = predict.asnumpy() - label_np print(loss)
然后就可以推理了,但是怎么把字符和数值对应呢?用了一个dict,当然训练也用到这个做为label。然后把推理结果和标签比较一下,还是不打印了,结果很差。如果把机器学习比喻为炼丹,那我这个明显是炼丹炉炸了hhh。“药材”,“火候”,还是要把控好的。
总结与不足之处
1.这里十二分地简化了语音识别的场景,仅仅识别元音。而且训练数据也是不真实的,如果打开听,可以发现是很稳定的声音,所以提取频谱也只对某一时刻做了操作,推理也只是看某一个时刻的。
2.数据的使用太简单。mindspore有自己的数据读取方式,但是还是主要做图片和文本的。我只好自己处理,自己读取为numpy的格式。
3.模型的设计过于简单,连全连接层都没用上。其实本来要提取两个共振峰的频率,就是第一共振峰,第二共振峰,然后就能大致判断元音的。但其实有时候第一、二共振峰会发生融合,就难说了。所以处理频谱时,先做归一化,每个频率强度除第一个频率的,这样就算声音大小不同,这个比值应该还是稳定的,然后就可以用最大值。
4.所以正如标题说的,这是一个尝试,突发奇想。希望mindspore能够有一天推出ASR的实践,我很乐意去验收教程的,哈哈哈,身份是不是明显了?