Chapter 7 Micro_speech 範例程式-喚醒詞偵測:訓練模型

本章節對應到 TinyML TensorFLow Lite 機器學習中文版 的第八章, 在這一章裡, 也介紹了 TensorBoard 來監看訓練過程, 可以進一步了解可視化機器學習. 本章最後一小部分, 也再說明語音特徵生產的程序, 這部分, 我有機會再於 §附錄 D 音頻聲譜的說明與模型分析說明.

類似 hello_world 的範例程式的模型訓練是於 Google colab 上執行, 此 micro_speech 範例也是在 colab 上執行另一個模型訓練程式, 我們先不做任何的修改, 於下一段的 colab 訓練客製化可以修改原始辨識 ‘yes’ 與 ‘no’ 的模型, 改為辨識其它喚醒詞.

7.1 在 Google colab 上建立及訓練 micro_speech 的模型 (原始程式及設定)

github 的 Train a Simple TensorFlow Lite for Microcontrollers model 會同時看到 “在 Google colab 上執行” 及 “github 原始碼” 的圖示如下, 我們點選 “在 Google colab 上執行,” 就會在 browser 上新開一個畫面, 執行 colab 上的 training 程式.

colabandgithub colabandgithub

7.1.1 colab 執行 training 程式前的準備工作

機器學習需要大量的運算, 尤其是浮點運算, 所以 NVidia 的 GPU 比 intel 的 CPU 更適合做機器學習的訓練工作, 可以縮短大量的訓練時間. Google 的 colab 提供了 3 種 runtime type 的選擇, 到選單 ‘Runtime’ 下的 “Change runtime type” 選擇 GPU. (如果選擇 None, 就會使用 CPU, 選擇 TPU, 會使用 Google 特殊設計的機器學習加速晶片, 截至目前為止, GPU 還是機器學習訓練速度最快的選項. 只是, 本次訓練的模型不大, 不管選擇哪一種, 時間都差異不大)

7.1.2 colab 執行 train micro_speech model.ipynb 程式

到選單 “Runtime” 選擇 “Run all,” 整個執行過程所需時間比 hello_world 的訓練長出許多, 我曾經有跑過 4 個小時以上 (可能是我同時在 colab 有跑其它應用有關, 同學們可以試試看不同的 runtime type 所需的時間各是多少), 原始程式也提供一個縮短訓練時間的方式, 把下面最底下 2 行的指令 # 拿掉 (!curl 跟 !tar 開頭那 2 行) , 直接下載已經訓練好的模型, 我在這樣條件下執行的時間約為 3.5 個小時.

Skipping the training

If you don't want to spend an hour or two training the model from scratch, you can download pretrained checkpoints by uncommenting the lines below (removing the '#'s at the start of each line) and running them.
#!curl -O "https://storage.googleapis.com/download.tensorflow.org/models/tflite/speech_micro_train_2020_05_10.tgz"
#!tar xzf speech_micro_train_2020_05_10.tgz

7.1.3 將模型上傳至開發板

類似 hello_world 的作法, 將 colab 訓練結果複製貼上到開發板的 app, 我們回到開發環境, 需要注意 3 個地方

7.1.3.1 更換模型 - 將 colab 訓練結果的 g_model[ ] 內容複製貼上到 model.cc

colab 訓練最後一行的 g_model[ ] 的內容 (書上寫的 g_tiny_conv_micro_features_model_data[ ] 資料太舊, 已經更新為 g_model[ ]), 複製貼上到 , tensorflow/lite/micro/examples/micro_speech/micro_features/model.cc 內容的 g_model[ ]. 並確認 g_model_len = 18712 的數字部份與 colab 的 g_model_len 一致, 如有不同, 採用 colab 的數字.

!cat {MODEL_TFLITE_MICRO}

const unsigned char g_model[] DATA_ALIGN_ATTRIBUTE = {
  0x20, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x12, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00,
  0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x12, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x94, 0x48, 0x00, 0x00, 0x34, 0x42, 0x00, 0x00,
  0x1c, 0x42, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  ......
  0x06, 0x00, 0x00, 0x00, 0x00, 0x16, 0x0a, 0x00, 0x0e, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
  0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
  0x03, 0x00, 0x00, 0x00};
const int g_model_len = 18712;

7.1.3.2 確認 micro_model_settings.cc

打開 examples/micro_speech/micro_features/micro_model_settings.cc, 這個檔案有關類別標籤陣列, 確定跟 colab 裡面的是一致的.

const char* kCategoryLabels[kCategoryCount] = {
    "silence",
    "unknown",
    "yes",
    "no",
};

7.1.3.3 確認 command-responder.cc - 以 DISCO_F746NG 為例

DISCO_F746NG 指令回應器檔案位於 examples/micro_speech/disco_f746ng/command_responder.cc, 它會根據聽到的指令來顯示不同的單字, 在檔案裡面可以找到這個 if 陳述式:

    if (*found_command == 'y') {
      lcd.Clear(0xFF0F9D58);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard yes!", CENTER_MODE);
    } else if (*found_command == 'n') {
      lcd.Clear(0xFFDB4437);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard no", CENTER_MODE);
    } else if (*found_command == 'u') {
      lcd.Clear(0xFFF4B400);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard unknown", CENTER_MODE);
    } else {
      lcd.Clear(0xFF4285F4);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard silence", CENTER_MODE);
    }

7.1.4 接續執行 make 及程式上傳

接下去再回頭執行上一章介紹的推論部份的動作 §6.1.2.2 修改 disco_f746ng 目錄繼續執行.

7.2 在 Google colab 上建立及訓練客製化 micro_speech 的模型

我們接下來進行的客製化的過程, 可以讓你選擇的開發板, 不僅是辨識此範例中的 ‘yes’ 與 ‘no,’ 而是有其它選擇, 甚至超過 2 個以上的單字當作喚醒詞辨識. 因為範例中的模型設計, 可以選取的單字有下列, 我們會選用 ‘on’ 與 ‘off’ 當作此次的範例.

  • 常見指令: yes, no, up, down, left, right, on, off, stop, go backward, foward, follow, learn
  • 0 - 9 數字: zero, one, two, three, four, five, six, seven, eight, nine
  • 隨機單字: bed, bird, cat, dog, happy, house, Marvin, Sheila, tree, wow

需要修改的有 4 個部份, 1 個在 colab 訓練程式, 3 個在開發板開發環境的程式內容

  • 修改 colab 訓練程式
  • 更換模型
  • 修改 micro_model_settings.cc
  • 修改 command_responder.cc (⚠️ 注意: 如果是選用其它開發板, 如 DISCO_F746NG 時, Makefile 會選用其它目錄下的 /micro_speech/disco_f746ng/command_responder.cc, 這時候需要修改的檔案就是 disco_f746ng 目錄下的)

7.2.1 修改 colab 訓練程式

Google colab 的 train_micro_speech_model.ipynb 下尋找 WANTED_WORDS, 可以看到下面這行

WANTED_WORDS = "yes,no"

我們接下來的練習用 ‘on,’ ‘off’ 來取代

WANTED_WORDS = "on, off"

到選單 “Runtime” 選擇 “Run all”

7.2.2 更換模型 - 將 colab 訓練結果的 g_model[ ] 內容複製貼上到 model.cc

等 colab 訓練完成後, 將最後一行的 g_model[ ] 的內容 (書上寫的 g_tiny_conv_micro_features_model_data[ ] 資料太舊, 已經更新為 g_model[ ]), 複製貼上到 , tensorflow/lite/micro/examples/micro_speech/micro_features/model.cc 內容的 g_model[ ]. 並確認 g_model_len = 18712 的數字部份與 colab 的 g_model_len 一致, 如有不同, 採用 colab 的數字.

!cat {MODEL_TFLITE_MICRO}

const unsigned char g_model[] DATA_ALIGN_ATTRIBUTE = {
  0x20, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x12, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00,
  0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x12, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x94, 0x48, 0x00, 0x00, 0x34, 0x42, 0x00, 0x00,
  0x1c, 0x42, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  ......
  0x06, 0x00, 0x00, 0x00, 0x00, 0x16, 0x0a, 0x00, 0x0e, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
  0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
  0x03, 0x00, 0x00, 0x00};
const int g_model_len = 18712;

7.2.3 修改 micro_model_settings.cc

打開 tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc, 這個檔案有關類別標籤陣列:

const char* kCategoryLabels[kCategoryCount] = {
    "silence",
    "unknown",
    "yes",
    "no",
};

我們為新模型進行調整, 將 ‘yes’ 與 ‘no’ 換成 ‘on’ 與 ‘off,’ 我們讓標籤的順序與模型輸出張量 (tensor) 的元素順序一樣, 讓它們的順序與訓練腳本裡面的相同非常重要. (如果用來訓練模型的標籤超過 2 個, 只要將它們都加入這個串列即可)

const char* kCategoryLabels[kCategoryCount] = {
    "silence",
    "unknown",
    "on",
    "off",
};

7.2.4 修改 command_responder.cc

最後一個步驟是修改使用這些標籤的輸出程式碼, 我們以 STM32 DISCO_F746NG 為例. STM32 DISCO_F746NG 指令回應器位於 examples/micro_speech/disco_f746ng/command_responder.cc, 它會根據聽到的指令來顯示不同的單字, 在 command_responder.cc 裡面找到以下這個 if 陳述式:

    if (*found_command == 'y') {
      lcd.Clear(0xFF0F9D58);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard yes!", CENTER_MODE);
    } else if (*found_command == 'n') {
      lcd.Clear(0xFFDB4437);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard no", CENTER_MODE);
    } else if (*found_command == 'u') {
      lcd.Clear(0xFFF4B400);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard unknown", CENTER_MODE);
    } else {
      lcd.Clear(0xFF4285F4);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard silence", CENTER_MODE);
    }

將它改成回應 ‘on’ 與 ‘off’ 不算太難, 只是之前只要判斷第一個字母的差異 (*found_command 來區分 yes 第一個字母的 ‘y’ 與 no 的 ‘n’), 現在必須用 2 個字母來區別 (found_command[0] 與 found_command[1] 來區分 on 的 ‘on’ 與 off 的 ‘of’)

    if (found_command[0] == 'o' && found_command[1] == 'n') {
      lcd.Clear(0xFF0F9D58);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard on!", CENTER_MODE);
    } else if (found_command[0] == 'o' && found_command[1] == 'f') {
      lcd.Clear(0xFFDB4437);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard off", CENTER_MODE);
    } else if (*found_command == 'u') {
      lcd.Clear(0xFFF4B400);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard unknown", CENTER_MODE);
    } else {
      lcd.Clear(0xFF4285F4);
      lcd.DisplayStringAt(0, LINE(5), (uint8_t*)"Heard silence", CENTER_MODE);
    }

7.2.5 接續執行 make 及程式上傳

接下去再回頭執行上一章介紹的推論部份的動作 §6.1.2.2 修改 disco_f746ng 目錄繼續執行.