2019年8月3日 星期六

簡單語音指令辨識

完整的自然語言語音辨識很複雜; 但在很多應用場合中, 如果可以讓用戶以十來個簡單語音指令控制電器/電腦/apps, 就已經很方便了, 而想要訓練這樣的類神經網路, 門檻當然比完整的語音辨識低很多。 Simple Audio Recognition (以下簡稱 SAR 一文) 所介紹的 tensorflow 原始碼當中的 speech_commands 範例, 就是這樣的工具。 餵一段一秒鐘的聲音, 它會判斷這是 "yes", "no", "up"、 "down"、 "left"、 "right"、 "on"、 "off"、 "stop"、 "go" 當中的哪一個語音命令, 或是未知的聲音 (UNKNOWN) 或是無聲 (SILENCE) (其實可能是很小聲的背景噪音)。 假設讀者已經先照著 貴哥的 colab 初學筆記 認識了 colab 的基本操作, 今天這篇文章將接續著帶大家用 colab 把 speech_commands 的操作流程幾乎走一遍。

一、 訓練用的語音檔

請先下載 speech commands dataset ( 我的備份)。 這是 google 好心提供 的 65000 個一秒鐘聲音檔, 裡面包含上述十個英文字、 十個數字及其他字, 加起來總共 30 個字。 解壓縮要很久、 佔很大的空間。 因為資料量太大、 訓練耗時太久, 最後決定只擷取一小部分的檔案, 用來把流程走一遍就好, 沒有真的要訓練出有用的權重矩陣。 (後面還是會拿別人訓練好的來玩。)

請下載我寫的小程式 subsample.py, 進到上述資料目錄, 然後 subsample.py -f -n 300 * > ~/list.txt 這會產生一個文字檔 ~/list.txt , 裡面列出本層目錄下所有檔案 (因為有 -f) 包含 README.md、 LICENSE、... 等等, 以及每個第一層子目錄 (例如 one/ nine/ stop/ 、 ... 等等) 裡面隨機挑選的 300 個檔案的路徑 (一個子目錄下如果不到 300 個檔案, 就全數納入)。 如果需要 「可重複的亂數結果」, 就用 -s 指定亂數種子。 詳見原始碼。 再用 tar czf ~/dataset_speech_commands_v0.02-300.tgz $(cat ~/list.txt) 把這些檔案壓縮, 產生節錄版的訓練資料檔 ~/dataset_speech_commands_v0.02-300.tgz 。

二、 準備 google drive

以下是我在自己的 google drive 的 colab/ 子目錄裡面所做的準備:

My Drive/
    colab/
        tf-sprec.ipynb
        sprec_data/
            _background_noise_/
            backward/
            ...
            yes/
            zero/
        sprec_pre/
            conv_actions_frozen.pb
            conv_actions_labels.txt
        tensorflow/
            examples/
                speech_commands/
        tmp/
  1. 建一個新的 google colaboratory 文件, 命名為 tf-sprec.ipynb 。
  2. 把訓練資料檔 dataset_speech_commands_v0.02-300.tgz 解壓縮、 上傳到 sprec_data/ 子目錄底下。
  3. 抓回 已訓練好的權重矩陣檔, 解壓縮並上傳到 sprec_pre/ 子目錄。
  4. 抓回 tensorflow 的原始碼, 把其中的 tensorflow/examples/speech_commands/ 子目錄上傳到 colab/ 底下。
  5. 建一個空目錄 tmp/ , 等一下要放訓練過程的存檔資料。

上述三個 「解壓縮並上傳」 的動作, 實際上我都是先上傳壓縮檔, 再從 colab 文件裡面進 bash 去解壓縮。 因為這些準備工作只做一次, 所以在 tf-sprec.ipynb 裡面都沒保留下來。

三、 訓練、轉檔、測試

接下來就打開 tf-sprec.ipynb , 大致照著 SAR 一文做。 首先掛載自己的 google drive (需要照指示輸入授權碼)

from google.colab import drive
drive.mount('/content/drive/')

再來, 進入 !bash, 建立捷徑 /colab 指向 '/content/drive/My Drive/colab' 以便後續寫絕對路徑時比較簡短, 並且以 bind mount 的方式讓 /colab/tmp 的內容浮現在 /tmp 底下 (/tmp 原有的東西將暫時看不見, 一如一般的 mount point), 因為等一下程式碼會寫入 /tmp。 換個方式說, 做完那句 mount 指令後, /tmp 這個名字變成 /colab/tmp 這個人的外號, 凡是打 /tmp 的拳都會落在 /colab/tmp 的身上:

ln -s '/content/drive/My Drive/colab' /
mount --bind /colab/tmp /tmp

最後記得要 exit, 離開 bash, 回到 NB。

除非 特別指定平行處理, 否則在一般狀況下, jupyter NB 的一個 cell 還沒執行完, 不能執行另一個 cell。 所以接下來要在開始訓練之前就先 在 NB 裡面啟動 tensorboard

%load_ext tensorboard
%tensorboard --port 6005 --logdir /tmp/retrain_logs

/tmp/retrain_logs 是訓練程式等一下才會建立的目錄, 目前當然沒有資料; 等一下開始訓練之後, tensorboard 就會自動開始畫圖、 自動開始更新。 但我遇過幾次, 中途打斷訓練程式之後, tensorboard 也莫名地死掉了。 重新執行這個 cell 時, NB 卻又說已有一個 tensorboard 在執行, 可是用 ps 看其實真的沒有那個 process。 原來是 port 被佔住了。 此時用 --port 指定另一個 port 即可啟動一個新的 tensorboard。 至於它畫的東西有什麼意義呢... 以後再研究吧。

然後就是重點: 訓練類神經網路。 我們的目的只是看它成功開始執行的壯觀景象, 並沒有要等待全程完整訓練出精準權重矩陣的意思 (因為 colab 不會讓你執行那麼久) 所以調整了一些參數。 詳見 SAR 一文, 並對照 train.py 程式碼。

! time python3 /colab/tensorflow/examples/speech_commands/train.py \
  --data_url= --data_dir='/colab/sprec_data' \
  --wanted_words=left,right,stop,go \
  --window_stride_ms=20.0 \
  --feature_bin_count=20 \
  --how_many_training_steps=600,200 \
  --learning_rate=0.001,0.0001 \
  --save_step_interval=100 \
  --eval_step_interval=200
  1. 指定空的 --data_url 是為了阻止程式自動到官網去下載 speech commands dataset ; 再用 --data_dir 指定改用預先準備好的較小資料集。
  2. 用 --wanted_words 指定想要辨認的語音。 少少幾個字, 意思一下就好了。 其他的語音都會被視為 unknown。
  3. --window_stride_ms (浮點數) 設大一點、 --feature_bin_count (整數) 設小一點, 這樣網路會變小一點, 精準度會降低, 但每個步驟會快一些。 SAR 一文當中的 --dct_coefficient_count 應該就是 --feature_bin_count 的前身 (在舊版程式裡的名稱)。
  4. --how_many_training_steps 指定每一階段的訓練要執行多少步; --learning_rate 指定每一階段的訓練的 learning rate。 兩者的階段個數當然必須一致。 以我設的參數, 總共 800 步, 就快要開始挑戰 colab 的時間上限了。
  5. --save_step_interval 指定多久備份一次權重矩陣 (放在 /tmp/speech_commands_train/ 底下) ; --eval_step_interval 指定多久印一次 confusion matrix。

如果成功開始執行, 就可以切換分頁去 讀 FB 或 line 等等做一些耗時又不營養的事 爬文, 學習更多深度學習的知識, 等個一小時再回來收割。 印出的資訊當中, confusion matrix 每一橫列 (ground truth)、 每一直行 (prediction) 所對應到的各是哪些字? 可以查看 conv_labels.txt。

在 /tmp/speech_commands_train 底下產生出來的模型檔, 還需要經過轉檔手續, 轉成 .pb 格式:

! python3 tensorflow/examples/speech_commands/freeze.py \
  --start_checkpoint=/tmp/speech_commands_train/conv.ckpt-800 \
  --wanted_words=left,right,stop,go \
  --window_stride_ms=20.0 \
  --feature_bin_count=20 \
  --output_file=/tmp/graph-800.pb

注意: 剛剛訓練時若改變了類神經網路的形狀, 這裡也必須用相同的參數告知轉檔程式 freeze.py 。

最後就可以拿剛剛產生的 .pb 檔來讓 label_wav.py 判斷某個 .wav 語音檔是否正確:

! python3 tensorflow/examples/speech_commands/label_wav.py \
  --graph=/tmp/graph-800.pb \
  --labels=/tmp/speech_commands_train/conv_labels.txt \
  --wav='/colab/sprec_data/stop/f5341341_nohash_0.wav'

再改拿官方預先訓練好的權重矩陣來讓 label_wav.py 判斷, 準確度果然有差哦!

! python3 tensorflow/examples/speech_commands/label_wav.py \
  --graph=/colab/sprec_pre/conv_actions_frozen.pb \
  --labels=/colab/sprec_pre/conv_actions_labels.txt \
  --wav=/colab/sprec_data/stop/f5341341_nohash_0.wav

我的 NB 包含本文大部分的步驟, 但有一點小的差異, 沒力修改了, 請大家勉強參考看看。

沒有留言:

張貼留言