老張讓我用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:選擇和努力