如何在MCU上快速部署TinyML

作者 : Nikolas Rieder和Rafael Tappe Maestro

本文介紹MCU上的機器學習——微型機器學習(TinyML)。請做好在剪刀石頭布遊戲中輸給ESP-EYE開發板的心理準備...

你對人工智慧(AI)和機器學習(ML)感到好奇嗎?你想知道如何在已經使用過的微控制器(MCU)上使用它嗎?本文將向介紹MCU上的機器學習。這一主題也稱為微型機器學習(TinyML)。請準備好在剪刀石頭布遊戲中輸給ESP-EYE開發板。你將瞭解資料收集和處理、如何設計和訓練AI以及如何讓它在MCU上運行。此示例並提供了從頭到尾完成自己的TinyML專案所需的一切。

我為什麼要關心TinyML

你肯定聽說過DeepMind和OpenAI等科技公司。他們憑藉專家和GPU能力在ML領域佔據主導地位。為了給人一種規模感,最好的AI,如Google翻譯所使用的AI,需要進行數月的訓練。他們平行使用數百個高性能GPU。TinyML透過變小來稍微扭轉局面。由於記憶體限制,大型AI模型不適合MCU。下圖顯示了硬體要求之間的差異。

相於在雲端中使用AI服務,MCU上的ML有哪些優勢?我們發現了七個主要優勢。

  • 成本:MCU的購買和執行成本低廉。
  • 友善環境:在MCU上執行AI消耗的能量很少。
  • 整合:MCU很容易整合到現有環境中,例如生產線。
  • 隱私和安全:資料可以在裝置本地端進行處理,不必透過網際網路發送。
  • 快速原型開發:TinyML有助於在短時間內開發概念驗證解決方案。
  • 自主可靠:微型裝置可以在任何地方使用,即使沒有基礎設施。
  • 即時:資料在MCU上處理,沒有延遲。唯一的限制是MCU的處理速度。

剪刀石頭布

你曾經在與AI的剪刀石頭布猜拳遊戲中輸過嗎?或者你想透過打敗AI來打動你的朋友嗎?你將使用TinyML對抗ESP-EYE開發板。為了使這樣的專案成為可能,必須學習五個步驟。以下部份提供必要步驟的概述。

收集資料

收集資料是ML的重要組成部份。為了讓事情得以順利運作,必須拍攝用你的手形成剪刀石頭布手勢的影像。圖片越獨特越好。AI將瞭解到你的手會處於不同的角度、位置或光線變化。資料集包含了所記錄的影像和每個影像的標籤。這被稱為監督學習。

最好使用與訓練AI相同的感測器和環境來執行AI。這樣能確保模型熟悉所傳入的資料。例如,由於製造差異,溫度感測器對於相同的溫度具有不同的電壓輸出。就我們的目的而言,這意味著使用ESP-EYE攝影機在統一背景上錄製影像是理想的。在部署期間,AI將在類似的背景下發揮最佳作用。還可以使用網路攝影機錄製影像,但可能會犧牲一些準確度。由於MCU容量有限,我們將記錄和處理96×96畫素的灰階影像。

收集資料後,將資料分成訓練集和測試集很重要。這樣做的目的在於瞭解模型如何辨識以前從未見過的手勢影像。該模型自然會對訓練期間已看到的影像表現良好。

itemis提供了一些示例影像,可以在該網站下載現成的資料集。

預處理數據

辨識資料中的模式不僅僅對人類來說很困難。為了讓AI模型更容易做到這一點,通常依賴預處理演算法。在我們的資料集中,使用ESP-EYE和網路攝影機記錄影像。由於ESP-EYE可以擷取96×96解析度的灰階影像,因此在這裡不需要做太多進一步的處理。然而,我們需要將網路攝影機影像縮小並裁剪為96×96畫素,並將它們從RGB格式轉換為灰階格式。最後,我們要標準化所有影像。下圖可以看到所處理的中間步驟。

設計模型

設計模型非常棘手!詳細的處理超出了本文的範圍。我們將描述模型的基本元件以及如何設計模型。在幕後,我們的AI依賴於神經網路,因此,可以將神經網路視為神經元的集合,這有點像我們的大腦。這就是為什麼在「僵屍末日」的情況下,AI也會被僵屍吃掉。

當網路中的所有神經元都相互連接時,這稱為完全連接或密集。我們可以將其視為是最基本的神經網路類型。由於我們希望AI能夠從影像中辨識手勢,因而使用更高層級且更適合影像的卷積神經網路(CNN)。卷積降低了影像的維數,提取了重要的模式並保留了畫素之間的局部關係。為了設計模型,我們使用了TensorFlow工具庫,它提供現成的神經網路元件,稱為「層」,可以輕鬆創建神經網路!

創建模型意味著堆疊層。它們的正確組合對於開發強韌且高精度的模型至關重要。下圖顯示我們正使用中的不同層。Conv2D代表一個卷積層。BatchNormalization層對上一層的輸出應用了一種標準化形式。接著將資料饋入啟動層,這會導致非線性並濾除不重要的資料點。接下來,最大池化類似於卷積來減小影像的大小。這個層塊重複幾次,合適的數量由經驗和實驗所決定。之後,我們使用扁平化層將二維影像縮減為一維陣列。最後,該陣列與代表剪刀石頭布類的三個神經元緊密相連。

def make_model_simple_cnn(INPUT_IMG_SHAPE, num_classes=3):
    inputs = keras.Input(shape=INPUT_IMG_SHAPE)
    x = inputs

    x = layers.Rescaling(1.0 / 255)(x)
    x = layers.Conv2D(16, 3, strides=3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Conv2D(32, 3, strides=2, padding="same", activation="relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Conv2D(64, 3, padding="same", activation="relu")(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Flatten()(x)
    x = layers.Dropout(0.5)(x)


    outputs = layers.Dense(units=num_classes, activation="softmax")(x)
    return keras.Model(inputs, outputs)

訓練模型

一旦設計了一個模型,就可以訓練它了。最初,AI模型將進行隨機預測。預測是與標籤相關的機率,在我們的例子中是剪刀、石頭或布。AI會告訴我們它認為一張影像是每個標籤的可能性有多大。因為AI一開始就在猜測標籤,所以它經常會把標籤弄錯。訓練是在將預測標籤與真實標籤進行比較後進行的。預測誤差會導致網路中神經元之間的更新。這種學習形式稱為梯度下降。因為我們的模型是在TensorFlow中所建構的,所以訓練就像一、二、三一樣簡單。下面,可以看到訓練期間所產生的輸出——準確性(訓練集)和驗證準確性(測試集)越高越好!

Epoch 1/6
480/480 [==============================] - 17s 34ms/step - loss: 0.4738 - accuracy: 0.6579 - val_loss: 0.3744 - val_accuracy: 0.8718
Epoch 2/6
216/480 [============>.................] - ETA: 7s - loss: 0.2753 - accuracy: 0.8436

在訓練過程中,可能會出現多種問題。最常見的問題是過度擬合。隨著模型一遍又一遍地接觸相同的例子,它會開始記住訓練資料,而不是學習潛在的模式。當然,我們從學校就記得理解勝於記憶!在某些時候,訓練資料的準確性可能會繼續上升,而測試集的準確性則不會。這是過度擬合的明顯指標。

轉換模型

經過訓練,我們得到了一個TensorFlow格式的AI模型。由於ESP-EYE無法解釋這種格式,我們將模型更改為微處理器可讀格式,就從轉換為TfLite模型開始。TfLite是一種更緊湊的TensorFlow格式,它使用量化來減小模型的大小。TfLite通常用於世界各地的邊緣裝置,例如智慧型手機或平板電腦。最後一步是將TfLite模型轉換為C陣列,因為MCU無法直接解釋TfLite。

部署模型

現在可以將我們的模型部署到微處理器上了。唯一需要做的就是將新的C陣列放入預期的檔中。替換C陣列的內容,不要忘記替換檔末尾的陣列長度變數。我們提供了一個腳本來簡化此過程。

嵌入式環境

讓我們回顧一下MCU上所發生的事情。在設置過程中,將編譯器配置為影像的形狀。

// initialize interpreter

static tflite::MicroInterpreter static_interpreter(
    model, resolver, tensor_arena, kTensorArenaSize, error_reporter);
interpreter = &static_interpreter;
model_input = interpreter->input(0);
model_output = interpreter->output(0);

// assert real input matches expect input
if ((model_input->dims->size != 4) || // tensor of shape (1, 96, 96, 1) has dim 4
    (model_input->dims->data[0] != 1) || // 1 img per batch
    (model_input->dims->data[1] != 96) || // 96 x pixels
    (model_input->dims->data[2] != 96) || // 96 y pixels
    (model_input->dims->data[3] != 1) || // 1 channel (grayscale)
    (model_input->type != kTfLiteFloat32)) { // type of a single data point, here a pixel
        error_reporter->Report("Bad input tensor parameters in model\n");
        return;
}

設置完成後,將擷取到的影像發送到模型,然後做出有關手勢的預測。
// read image from camera into a 1-dimensional array
uint8_t img[dim1*dim2*dim3]
if (kTfLiteOk != GetImage(error_reporter, dim1, dim2, dim3, img)) {
   TF_LITE_REPORT_ERROR(error_reporter, "Image capture failed.");
}

 // write image to model
 std::vector<uint8_t> img_vec(img, img + dim1*dim2*dim3);
 std::vector<float_t> img_float(img_vec.begin(), img_vec.end());
 std::copy(img_float.begin(), img_float.end(),  model_input->data.f);

 // apply inference
 TfLiteStatus invoke_status = interpreter->Invoke();
}

然後模型會返回每個手勢的機率。由於機率陣列只是一系列介於0和1之間的值,因此需要進行一些解釋。我們認為辨識出的手勢是機率最高的手勢。現在我們將辨識的手勢與AI的動作進行比較來處理解釋,並確定誰贏得了這一輪。你沒有機會!

// probability for each class
float paper = model_output->data.f[0];
float rock = model_output->data.f[1];
float scissors = model_output->data.f[2];

下圖說明MCU上的步驟。但基於我們的目的,不需要對MCU進行預處理。

展開示例

挑戰一下怎麼樣?想要實現新的人生目標?或是給老朋友留下深刻印象還是找到新朋友呢?只要再添加蜥蜴和史巴克,就可以讓剪刀石頭布的猜拳遊戲更上一層樓。你的AI朋友將是一項更接近世界霸權的技能。首先你應該看看我們的剪刀石頭布知識庫,並能夠複製上述步驟。下圖展示了遊戲的運作方式,還需要添加兩個額外的手勢和一些新的輸贏條件。

開始你自己的專案

如果想開始你自己的專案,本文也提供一個範本專案,它使用了與我們剪刀石頭布專案相同的簡單流水線。你可以在此網站找到該範本。不要猶豫,透過社群媒體向我們展示你的專案吧,我們很想知道你能創造什麼!

(參考原文:How to quickly deploy TinyML on MCUs,by Saumitra Jagdale)

本文同步刊登於EDN Taiwan 20232月號雜誌

活動簡介
TAIPEI AMPA & Autotronics Taipei X Tech Taipei 2023「智慧領航車聯網技術論壇」邀請來自產業的代表業者與專家齊聚一堂,透過專題演講、現場應用展示以及互動論壇,深人交流智慧交通與車聯網的融合應用,基礎設施以及安全測試與標準化等主題,帶來一系列迎接車聯網時代必須掌握的最新技術與市場趨勢,協助工程師進一步探索充滿無限可能的智慧移動大未來。
贊助廠商

加入LINE@,最新消息一手掌握!

發表評論