本文正在参加「金石计划 . 瓜分6万现金大奖」

一、老张的需求

我有朋友叫老张,他是做传统单片机的。他经常搞一些简略的硬件创造,比方他家窗布的翻开和封闭,便是他亲身设计的电路板操控的。他布线很乱,从电视柜的插座直接扯电线到阳台的窗户,电线就像蜘蛛网相同纵横交错。

老张的媳妇是个强迫症、完美主义者,没法忍耐乱扯的电线。可是,她又害怕影响自己的丈夫成为大创造家。于是,她就顺着电线绑上绿萝,这样就把电线隐藏起来了,丝毫不影响房间的美观。

上周,老张邀请我去他家里,观赏这个电动窗布。老张很激动,赶忙拿来凳子,站在凳子上,用拖把杆去戳一个赤色的按钮。他激动地差点滑下来。我问他,你为什么要把开关放那么偏僻。老张说,是为了防止3岁的儿子频繁地按开关。老张站在凳子上,像跳天鹅湖相同,垫着脚尖尽力去戳按钮,给我演示窗布的开和关。

我赶忙夸他的窗布十分棒。我不知道,就这种情况,假如他摔下来,从法律上讲我有没有连带责任。

我连忙搬运注意力:哎,你家的绿萝长得挺好。我不自觉地摸了一下叶子,感觉手指头麻了一下。我去!老张你家绿萝带刺!

老张说,不是带刺,可能是带电。

我并没有太惊讶,由于我知道老张10多年了。咱们先是高中3年同学,后来4年大学同学,后来又2年搭档。在老张这儿,什么古怪的工作都可能发生。我还记得,高中时,他晚上抱着半个西瓜插着勺子,去厕所蹲坑,上下同步进行。他丝毫没有为难的意思,并称之为旷达。

我回忆起往事,痛苦不堪。今日被绿萝电了,也会成为往事。我动身准备要走。老张说,我搞了好多年嵌入式板子,你知道为什么一直没有起色?

我说,什么?你对电路板还能起色?!

老张说,不。我意思是说,我搞嵌入式工作这么多年,一直是平平无奇。主要原因,我感觉便是没有结合高新技术,比方人工智能。而你,现在就在搞人工智能。

我说,我能让你起色吗?

老张说,是的。我研讨的巡逻小车,都是靠无线电操控的,我一按,就发送个电波。你帮我搞一个语音操控的。我一喊:跑哇!它就往前走。我一喊:站住!它就中止。我一喊:往左,往右!它就转弯。

我说,这个不难。可是,这有用吗?

老张说,是的,这很起色。据我所知,咱们车间里,还没有人能想出来我这个主意。而我,马上就能做出来了。

我说,能够。你这个不杂乱。可是,我也有个要求。那便是,我把你这个工作,写到博客里,也让网友了解一下,能够吗?

老张说:没问题!

二、我的研讨

人工智能有三大常用领域,视觉、文字和语音。前两者,我写过许多。这次,开端对语音领域下手。

以下代码,环境要求 TensorFlow 2.6(2021年10之后版) + python 3.9。

2.1 语音的解析

咱们所看到的,听到的,都是数据。体现到计算机,便是数字。

比方,咱们看到下面这张像素图,是4*4的像素点。图上有两个紫色的点。你看上去,是这样。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

其实,假如是黑白单通道,数据是这样:

[[255, 255, 255, 255],
[255, 131, 255, 255],
[255, 131, 255, 255],
[255, 255, 255, 255]]

假如是多通道,也便是五颜六色的,数据是下面这样:

[[[255, 255,255],[255, 255, 255],[255, 255, 255],[255, 255, 255]],
[[255, 255, 255],[198, 102, 145],[255, 255, 255],[255, 255, 255]],
[[255, 255, 255],[198, 102, 145],[255, 255, 255],[255, 255, 255]],
[[255, 255, 255],[255, 255, 255],[255, 255, 255],[255, 255, 255]]]

咱们看到,空白都是255。只是那2个紫色的格子有改变。五颜六色值是[198, 102, 145],单色值是131。能够说,一切皆数据。

语音是被咱们耳朵听到的。可是,实际上,它也是数据。

你要不信,咱们来解析一个音频文件。

# 根据文件途径,解码音频文件
import tensorflow as tf
audio_binary = tf.io.read_file("datasets\\go\\1000.wav")
audio, rate = tf.audio.decode_wav(contents=audio_binary)

使用tf.audio.decode_wav能够读取和解码音频文件,回来音频文件的数据audio和采样率rate

其间,解析的数据audio打印如下:

<tf.Tensor: shape=(11146, 1), dtype=float32, numpy=
array([[-0.00238037],
       [-0.0038147 ],
       [-0.00335693],
       ...,
       [-0.00875854],
       [-0.00198364],
       [-0.00613403]], dtype=float32)>

上面的数据,方式是[[x]]。这表明,这段音频是单声道(类比黑白照片)。x是声道里面某一时间具体的数值。其实它是一个波形,咱们能够把它画出来。

import matplotlib.pyplot as plt
plt.plot(audio)
plt.show()

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

这个波的巨细,便是推进你耳朵鼓膜的力度。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

上面的图是11146个采样点的形状。下面,咱们打印10个点的形状。这10个点就好比是推了你耳朵10下。

import matplotlib.pyplot as plt
plt.plot(audio[0:10])
plt.show()

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

至此,咱们能够看出,音频实际上便是几组带有序列的数字。

要辨认音频,就得首先剖析音频数据的特征。

2.2 音频的频谱

每个个别都有自己的组成成分,他们是独一无二的。就像你相同。

可是,多个个别之间,也有相似之处。就像咱们都是程序员。于是,咱们能够用一种叫“谱”的东西来描绘一个事物。比方,辣子鸡的菜谱。正是菜谱描绘了放多少辣椒,用哪个部位的鸡肉,切成什么形状。这才让咱们看到制品时,大喊一声:辣子鸡,而非糖醋鱼。

声响也有“谱”,一般用频谱描绘。

声响是振荡发生的,这个振荡的频率是有谱的。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

把一段声响剖析出来包括哪些固定频率,就像是把一道菜剖析出来由辣椒、鸡肉、豆瓣酱组成。再经过剖析食材,终究咱们判断出来是什么菜品。

声响也是相同,一段声波能够剖析出来它的频率组成。假如想要具体了解“频谱”的知识,我有一篇万字长文详解《总算,有人讲傅里叶变换了》。看完需求半个小时。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

我上面说的,谷歌公司早就知道了。因而,他们在TensorFlow框架中,早就内置了获取音频频谱的函数。它采用的是短时傅里叶变换stft

waveform = tf.squeeze(audio, axis=-1)
spectrogram = tf.signal.stft(waveform, frame_length=255, frame_step=128)

咱们上面经过tf.audio.decode_wav解析了音频文件,它回来的数据格式是[[-0.00238037][-0.0038147 ]]这种方式。

你可能猎奇,它为什么不是[-0.00238037, -0.0038147 ]这种方式,非要外面再套一层。回忆一下,咱们的紫色像素的比如,一个像素点表明为[[198, 102, 145]],这表明RGB三个色值通道描绘一个五颜六色像素。其实,这儿也相同,是兼容了多声道的情况。

可是,咱们只需一个通道就好。所以需求经过tf.squeeze(audio, axis=-1)对数据进行降一个维度,把[[-0.00238037][-0.0038147 ]]变为[-0.00238037, -0.0038147 ]。这,才是一个纯粹的波形。嗯,这样才干交给傅里叶先生进行剖析。

tf.signal.stft里面的参数,是指取小样的规矩。便是从总波形里面,每隔多久取多少小样本进行剖析。剖析之后,咱们也是能够像绘制波形相同,把剖析的频谱成果绘制出来的。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

看不懂上面的图没有关系,这很正常,十分正常,极端正常。由于,我即使用了一万多字,50多张图,专门做了具体的解释。可是依然,有20%左右的读者仍是不理解。

不过,此时,你需求理解,一段声响的特性是能够经过科学的方法抽取出来的。这,就够了。

把特性抽取出来之后,咱们就交给人工智能框架去练习了。

2.3 音频数据的预处理

上面,咱们现已成功地获取到一段音频的重要魂灵:频谱。

下面,就该交给神经网络模型去练习了。

在正式交给模型之前,其实还有一些预处理工作要做。比方,给它切一切毛边,叠一叠,整理成同一个形状。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

正如计算机只能辨认0和1,许多框架也是只能接纳固定的结构化数据。

举个简略的比如,你在练习古诗的时分,有五言的和七言的。比方:“床前明月光”和“一顿不吃饿得慌”两句。那么,终究都需求处理成相同的长短。要么前面加0,要么后边加0,要么把长的裁短。总归,有必要相同长度才行。

床前明月光〇〇
一顿不吃饿得慌
蜀道难〇〇〇〇

那么,咱们的音频数据怎么处理呢?咱们的音波数据经过短时傅里叶变换之后,格式是这样的:

<tf.Tensor: shape=(86, 129, 1), dtype=float32, numpy=
array([[[4.62073803e-01],
        ...,
        [2.92062759e-05]],
       [[3.96062881e-01],
        [2.01166332e-01],
        [2.09505502e-02],
        ...,
        [1.43915415e-04]]], dtype=float32)>

这是由于咱们11146长度的音频,经过tf.signal.stftframe_step=128切割之后,能够分红86份。所以咱们看到shape=(86, 129, 1)。那么,假如音频的长度改变,那么这个结构也会变。这样不好。

因而,咱们首先要把音频的长度标准一下。由于采样率是16000,也便是1秒钟记录16000次音频数据。那么,咱们无妨就拿1秒音频,也便是16000个长度,为一个标准单位。过长的,咱们就裁剪掉后边的。过短的,咱们就在后边补上0。

我说的这一系列操作,反映到代码上,便是下面这样:

waveform = tf.squeeze(audio, axis=-1)
input_len = 16000
waveform = waveform[:input_len]
zero_padding = tf.zeros([16000] - tf.shape(waveform),dtype=tf.float32)
waveform = tf.cast(waveform, dtype=tf.float32)
equal_length = tf.concat([waveform, zero_padding], 0)
spectrogram = tf.signal.stft(equal_length, frame_length=255, frame_step=128)
spectrogram = tf.abs(spectrogram)
spectrogram = spectrogram[..., tf.newaxis]

这时分,再来看看咱们的频谱数据结构

<tf.Tensor: shape=(124, 129, 1), dtype=float32, numpy=
array([[[4.62073803e-01],
        ...,
        [2.92062759e-05]],
...
        [0.00000000e+00],
        ...,
        [0.00000000e+00]]], dtype=float32)>

现在,不管你输入任何长短的音频,终究它的频谱都是shape=(124, 129, 1)。从图上咱们也能够看出,不足的就算后边补0,也得凑成个16000长度。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

下面,真的要开端构建神经网络了。

2.4 构建模型和练习

依照老张的要求……我现在不想提他,由于我的手指被绿萝电的还有点发麻。

依照要求……他要四种指令,分别是:行进、中止、左转、右转。那么,我就搞了四种音频,分别放在对应的文件夹下面。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

从文件夹读取数据、将输入输出结对、依照份额分出数据集和验证集,以及把datasets划分为batch……这些操作,在TensorFlow中现已很成熟了。而且,随着版别的更新,越来越成熟。体现在代码上,便是字数越来越少。此处我就不说了,我会把完整代码上传到github,供诸君参考。

下面,我重点说一下,本比如中,完成语音分类,它的神经网络的结构,以及模型练习的配置。

import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras import models
model = models.Sequential([
       layers.Input(shape= (124, 129, 1)),
       layers.Resizing(32, 32),
       layers.Normalization(),
       layers.Conv2D(32, 3, activation='relu'),
       layers.Conv2D(64, 3, activation='relu'),
       layers.MaxPooling2D(),
       layers.Dropout(0.25),
       layers.Flatten(),
       layers.Dense(128, activation='relu'),
       layers.Dropout(0.5),
       layers.Dense(4),
])
model.compile(
       optimizer=tf.keras.optimizers.Adam(),
       loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
       metrics=['accuracy']
)

其实,我感觉人工智能应用层面的开发,预处理和后处理比较难。中心的模型基本上都是有固定招式的。

第1层layers.Input(shape= (124, 129, 1))叫输入层,是练习样本的数据结构。便是咱们上一节凑成16000之后,求频谱得出的(124, 129)这个结构。

终究一层layers.Dense(4),是输出层。咱们搞了“走”,“停”,“左”,“右”4个文件夹分类,终究成果是4类,所以是4

头尾基本固定后,这个序列Sequential就意味着:吃音频文件,然后排出它是4个分类中的哪一种。

那么中心咱们就能够自己操作了。Normalization是归一化。Conv2D是做卷积。MaxPooling2D是做池化。Dropout(0.25)是随机砍掉必定份额(此处是25%)的神经网络,以保证其健壮性。快结束时,经过Flatten()将多维数据拉平为一维数据。后边给个激活函数,收缩神经元个数,准备降落。终究,对接到Dense(4)

这就完成了,将前面16000个音频采样点,经过一系列转化后,终究输出为某个分类。

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

终究,进行练习和保存模型。

model = create_model()
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath='model/model.ckpt',
       save_weights_only=True,
       save_best_only=True)
history = model.fit(
       train_ds,
       validation_data=val_ds,
       epochs=50,
       callbacks=[cp_callback]
)

filepath='model/model.ckpt'表明练习完成后,存储的途径。save_weights_only=True只存储权重数据。save_best_only=True意思是只存储最好的练习的成果。调用练习很简略,调用model.fit,传入练习集、验证集、练习轮数、以及练习回调就能够啦。

2.5 加载模型并预测

上一节中,咱们指定了模型的保存途径,调用model.fit后会将成果保存在对应的途径下。这便是咱们终究要的产品:

老张让我用TensorFlow识别语音命令:前进、停止、左转、右转

咱们能够加载这些文件,这样就让咱们的程序具备了多年功力。能够对外来音频文件做预测。

model = create_model()
if os.path.exists('model/model.ckpt.index'):
       model.load_weights('model/model.ckpt')  
labels = ['go', 'left', 'right', 'stop']
# 音频文件转码
audio = get_audio_data('mysound.wav')
audios = np.array()
predictions = model(audios)
index = np.argmax(predictions[0])
print(labels[index])

上面代码中,先加载了历史模型。然后,将我录制的一个mysound.wav文件进行预处理,方式便是前面说的凑成16000,然后经过短时傅里叶解析成(124, 129)结构的频谱数据。这也是咱们练习时的模样。

终究,把它输入到模型。出于惯性,它会顺势输出这是'go'分类的语音指令。虽然这个模型,从来没有见过我这段悦耳的嗓音。可是它也能辨认出来,我发出了一个包括'go'声响特性的声响。

以上,便是利用TensorFlow框架,完成声响分类的全过程。

音频分类项目开源地址:github.com/hlwgy/sound

再次提示大家:要求TensorFlow 2.6(2021年10之后版) + python 3.9。由于,里面用了许多新特性。旧版别是跑不通的,具体体现在TensorFlow各种找不到层。

三、咱们的协作

我带着成果去找老张。老张缄默沉静了一瞬间,不说话。

我说,老张啊,你就说吧。你不说话,我心里没底,不知道会发生啥。

老张说,兄弟啊,其实语音小车这个项目,没啥创意。我昨天才知道,咱们车间老王,三年前,自己一个人,就做出来过了。说完,老张又缄默沉静了。

我安慰他说,不要紧的。这个不可,你就再换一个呗。

老张突然抬起头,眼睛中闪着光,他说:兄弟,宇宙飞船相关的软件,你搞得定吗?!火星车也行。

我不紧不忙地封闭服务,并把电脑收进包里。

我穿上鞋,然后拿上包。翻开门,回头跟老张说了一句:兄弟,三个月内,咱们先不联系了吧。

我是ITF男孩,在是TF男孩,带你从IT视角看国际。