本文介紹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有哪些優勢?我們發現了七個主要優勢。
剪刀石頭布
你曾經在與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)
加入LINE@,最新消息一手掌握!