老張讓我用TensorFlow識別語音命令:前進、停止、左轉、右轉
theme: healer-readable
本文正在參加「金石計劃 . 瓜分6萬現金大獎」
一、老張的需求
我有朋友叫老張,他是做傳統單片機的。他經常搞一些簡單的硬件發明,比如他家窗簾的打開和關閉,就是他親自設計的電路板控制的。他佈線很亂,從電視櫃的插座直接扯電線到陽台的窗户,電線就像蜘蛛網一樣縱橫交錯。
老張的媳婦是個強迫症、完美主義者,沒法忍受亂扯的電線。但是,她又害怕影響自己的丈夫成為大發明家。於是,她就順着電線綁上綠蘿,這樣就把電線隱藏起來了,絲毫不影響房間的美觀。
上週,老張邀請我去他家裏,參觀這個電動窗簾。老張很激動,趕緊拿來凳子,站在凳子上,用拖把杆去戳一個紅色的按鈕。他激動地差點滑下來。我問他,你為什麼要把開關放那麼偏僻。老張説,是為了避免3歲的兒子頻繁地按開關。老張站在凳子上,像跳天鵝湖一樣,墊着腳尖努力去戳按鈕,給我演示窗簾的開和關。
我趕緊誇他的窗簾非常棒。我不知道,就這種情況,如果他摔下來,從法律上講我有沒有連帶責任。
我連忙轉移注意力:哎,你家的綠蘿長得挺好。我不自覺地摸了一下葉子,感覺手指頭麻了一下。我去!老張你家綠蘿帶刺!
老張説,不是帶刺,可能是帶電。
我並沒有太驚奇,因為我認識老張10多年了。我們先是高中3年同學,後來4年大學同學,後來又2年同事。在老張這裏,什麼奇怪的事情都可能發生。我還記得,高中時,他晚上抱着半個西瓜插着勺子,去廁所蹲坑,上下同步進行。他絲毫沒有尷尬的意思,並稱之為豁達。
我回憶起往事,痛苦不堪。今天被綠蘿電了,也會成為往事。我起身準備要走。老張説,我搞了好多年嵌入式板子,你知道為什麼一直沒有起色?
我説,什麼?你對電路板還能起色?!
老張説,不。我意思是説,我搞嵌入式工作這麼多年,一直是平平無奇。主要原因,我感覺就是沒有結合高新技術,比如人工智能。而你,現在就在搞人工智能。
我説,我能讓你起色嗎?
老張説,是的。我研究的巡邏小車,都是靠無線電控制的,我一按,就發送個電波。你幫我搞一個語音控制的。我一喊:跑哇!它就往前走。我一喊:站住!它就停止。我一喊:往左,往右!它就轉彎。
我説,這個不難。但是,這有用嗎?
老張説,是的,這很起色。據我所知,我們車間裏,還沒有人能想出來我這個想法。而我,馬上就能做出來了。
我説,可以。你這個不復雜。但是,我也有個要求。那就是,我把你這個事情,寫到博客裏,也讓網友瞭解一下,可以嗎?
老張説:沒問題!
二、我的研究
人工智能有三大常用領域,視覺、文字和語音。前兩者,我寫過很多。這次,開始對語音領域下手。
以下代碼,環境要求 TensorFlow 2.6(2021年10之後版) + python 3.9。
2.1 語音的解析
我們所看到的,聽到的,都是數據。體現到計算機,就是數字。
比如,我們看到下面這張像素圖,是4*4的像素點。圖上有兩個紫色的點。你看上去,是這樣。
其實,如果是黑白單通道,數據是這樣:
[[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
。可以説,一切皆數據。
語音是被我們耳朵聽到的。但是,實際上,它也是數據。
你要不信,我們來解析一個音頻文件。
``` python
根據文件路徑,解碼音頻文件
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
是聲道里面某一時刻具體的數值。其實它是一個波形,我們可以把它畫出來。
python
import matplotlib.pyplot as plt
plt.plot(audio)
plt.show()
這個波的大小,就是推動你耳朵鼓膜的力度。
上面的圖是11146個採樣點的形狀。下面,我們打印10個點的形狀。這10個點就好比是推了你耳朵10下。
python
import matplotlib.pyplot as plt
plt.plot(audio[0:10])
plt.show()
至此,我們可以看出,音頻實際上就是幾組帶有序列的數字。
要識別音頻,就得首先分析音頻數據的特徵。
2.2 音頻的頻譜
每個個體都有自己的組成成分,他們是獨一無二的。就像你一樣。
但是,多個個體之間,也有相似之處。就像我們都是程序員。於是,我們可以用一種叫“譜”的東西來描述一個事物。比如,辣子雞的菜譜。正是菜譜描述了放多少辣椒,用哪個部位的雞肉,切成什麼形狀。這才讓我們看到成品時,大喊一聲:辣子雞,而非糖醋魚。
聲音也有“譜”,一般用頻譜
描述。
聲音是振動發生的,這個振動的頻率是有譜的。
把一段聲音分析出來包含哪些固定頻率,就像是把一道菜分析出來由辣椒、雞肉、豆瓣醬組成。再通過分析食材,最終我們判斷出來是什麼菜品。
聲音也是一樣,一段聲波可以分析出來它的頻率組成。如果想要詳細瞭解“頻譜”的知識,我有一篇萬字長文詳解《終於,掘金有人講傅里葉變換了》。看完需要半個小時。
我上面説的,谷歌公司早就知道了。因此,他們在TensorFlow
框架中,早就內置了獲取音頻頻譜的函數。它採用的是短時傅里葉變換stft
。
python
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
裏面的參數,是指取小樣的規則。就是從總波形裏面,每隔多久取多少小樣本進行分析。分析之後,我們也是可以像繪製波形一樣,把分析的頻譜結果繪製出來的。
看不懂上面的圖沒有關係,這很正常,非常正常,極其正常。因為,我即便用了一萬多字,50多張圖,專門做了詳細的解釋。但是依然,有20%左右的讀者還是不明白。
不過,此時,你需要明白,一段聲音的特性是可以通過科學的方法抽取出來的。這,就夠了。
把特性抽取出來之後,我們就交給人工智能框架去訓練了。
2.3 音頻數據的預處理
上面,我們已經成功地獲取到一段音頻的重要靈魂:頻譜。
下面,就該交給神經網絡模型去訓練了。
在正式交給模型之前,其實還有一些預處理工作要做。比如,給它切一切毛邊,疊一疊,整理成同一個形狀。
正如計算機只能識別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.stft
的frame_step=128
分割之後,可以分成86份。所以我們看到shape=(86, 129, 1)
。那麼,如果音頻的長度變化,那麼這個結構也會變。這樣不好。
因此,我們首先要把音頻的長度規範一下。因為採樣率是16000
,也就是1秒鐘記錄16000
次音頻數據。那麼,我們不妨就拿1秒音頻,也就是16000
個長度,為一個標準單位。過長的,我們就裁剪掉後面的。過短的,我們就在後面補上0。
我説的這一系列操作,反映到代碼上,就是下面這樣:
python
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長度。
下面,真的要開始構建神經網絡了。
2.4 構建模型和訓練
依照老張的要求……我現在不想提他,因為我的手指被綠蘿電的還有點發麻。
依照要求……他要四種命令,分別是:前進、停止、左轉、右轉。那麼,我就搞了四種音頻,分別放在對應的文件夾下面。
從文件夾讀取數據、將輸入輸出結對、按照比例分出數據集和驗證集,以及把datasets
劃分為batch
……這些操作,在TensorFlow
中已經很成熟了。而且,隨着版本的更新,越來越成熟。體現在代碼上,就是字數越來越少。此處我就不説了,我會把完整代碼上傳到github
,供諸君參考。
下面,我重點説一下,本例子中,實現語音分類,它的神經網絡的結構,以及模型訓練的配置。
``` python 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
個音頻採樣點,經過一系列轉化後,最終輸出為某個分類。
最後,進行訓練和保存模型。
python
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
後會將結果保存在對應的路徑下。這就是我們最終要的產物:
我們可以加載這些文件,這樣就讓我們的程序具備了多年功力。可以對外來音頻文件做預測。
``` python
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([audio]) predictions = model(audios) index = np.argmax(predictions[0]) print(labels[index]) ```
上面代碼中,先加載了歷史模型。然後,將我錄製的一個mysound.wav
文件進行預處理,方式就是前面説的湊成16000
,然後通過短時傅里葉解析成(124, 129)
結構的頻譜數據。這也是我們訓練時的模樣。
最後,把它輸入到模型。出於慣性,它會順勢輸出這是'go'
分類的語音指令。儘管這個模型,從來沒有見過我這段動聽的嗓音。但是它也能識別出來,我發出了一個包含'go'
聲音特性的聲音。
以上,就是利用TensorFlow
框架,實現聲音分類的全過程。
音頻分類項目開源地址:https://github.com/hlwgy/sound
再次提醒大家:要求TensorFlow 2.6(2021年10之後版) + python 3.9。因為,裏面用了很多新特性。舊版本是跑不通的,具體體現在TensorFlow各種找不到層。
三、我們的合作
我帶着成果去找老張。老張沉默了一會兒,不説話。
我説,老張啊,你就説吧。你不説話,我心裏沒底,不知道會發生啥。
老張説,兄弟啊,其實語音小車這個項目,沒啥創意。我昨天才知道,我們車間老王,三年前,自己一個人,就做出來過了。説完,老張又沉默了。
我安慰他説,沒關係的。這個不行,你就再換一個唄。
老張猛然抬起頭,眼睛中閃着光,他説:兄弟,宇宙飛船相關的軟件,你搞得定嗎?!火星車也行。
我不緊不忙地關閉服務,並把電腦收進包裏。
我穿上鞋,然後拿上包。打開門,回頭跟老張説了一句:兄弟,三個月內,我們先不聯繫了吧。
我是ITF男孩,在掘金是TF男孩,帶你從IT視角看世界。
- 60分鐘的文心一言發佈會:我帶你5分鐘看完
- 程序員寫小説:我甘心當韭菜
- docx格式文檔詳解:xml解析並用html還原
- 評論二則:年齡大了能否寫代碼、上大學不喜歡IT專業咋整
- ChatGPT火了,我連夜詳解AIGC原理,並實戰生成動漫頭像
- 咱不吃虧,也不能過度自衞
- 兔年了,利用AI風格化實現剪紙兔、年畫兔、煙花兔
- 傻嗎?談男人們飯桌的拼酒現象
- 他扔出一張對話截圖:王總説的
- 老張説:快過年了,搞個AI作曲,用TensorFlow訓練midi文件
- 為什麼大家都看中學歷?
- 年底了,裁兄弟當職員那哥們兒,如今咋樣了?
- 老張讓我用TensorFlow識別語音命令:前進、停止、左轉、右轉
- 在掘金第一次記錄失眠
- 十五分鐘簡介人工智能,以聽懂為目的
- 認知史 12:積累階段,沒資格攀比
- 認知史 16:人過留名,雁過留聲
- 認知史 4:普遍共識
- 一文講通OCR文字識別原理與技術全流程(科普版)
- 認知史 7:選擇和努力