嵌入式人工智慧近年來發展迅速,已成為許多應用領域的核心技術。本旨在提供一個全面的嵌入式人工智慧開發實務框架,涵蓋從理論基礎到程式碼實作的各個環節。文章首先介紹 TinyML 的核心概念,並探討如何在資源受限的嵌入式裝置上佈署機器學習模型。接著,文章深入研究邊緣AI開發的挑戰,例如裝置的硬體限制、軟體相依性管理、以及分散式運算的需求。針對這些挑戰,文章提出了相應的解決方案,包含容器化技術、分散式運算框架、以及雲端服務的整合。最後,文章著重於資料處理與演算法開發,涵蓋資料擷取、儲存、管理、以及視覺化等重要環節,並提供程式碼範例和圖表說明,幫助讀者更好地理解和應用這些技術。

嵌入式人工智慧開發實務

理論基礎

對於想要深入研究嵌入式機器學習理論的人來說,本章節提供了豐富的資源。雖然這些知識對於成功的產品開發並非必要,但對於想要深入瞭解技術底層的人來說非常有價值。

  • The Scientist and Engineer’s Guide to Digital Signal Processing,作者:Steven W. Smith (California Technical, 1997):一本全面介紹數位訊號處理的,可免費取得或購買實體書。適合非DSP工程師深入瞭解DSP演算法。
  • Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow,作者:Aurélien Géron (O’Reilly, 2022):一本介紹實務機器學習概念和技能的優秀資源。適合非ML工程師學習ML演算法。
  • TinyML Foundation (YouTube 頻道):定期舉辦嵌入式ML相關的技術演講,反映了研究和工程的前沿技術。
  • TinyML papers and projects (GitHub 儲存函式庫):一個包含豐富論文和資源的寶函式庫。

開發工具

邊緣AI的故事是一個關於工具的故事。雖然大多數將人工智慧佈署到邊緣裝置所需的基本技術已經存在十多年了,但這些技術往往具有陡峭的學習曲線。

隨著時間的推移,我們的全球技術生態系統不斷演進,開發出旨在管理和提高這些具有挑戰性的技術的可用性的工具。開源和商業函式庫、框架和產品的豐富組合已經將邊緣AI納入一般嵌入式工程師的工具箱中。

端對端平台

端對端開發平台為邊緣AI專案提供了自動化的整合以及專為邊緣AI專案設計的整體設計,能夠大幅降低複雜度負擔,使開發更快、更安全。

軟體工程

邊緣AI涉及大量的軟體開發,因此現代軟體工程工具非常重要。以下是一些關鍵貢獻者:

作業系統

在開發過程中,作業系統的選擇將決定與構成邊緣AI生態系統的極為多樣化的軟體工具合作的難易程度。嵌入式工程傳統上常用Windows,而資料科學和機器學習工具通常最適合Unix相容環境,如Linux或macOS。

在佈署時,邊緣裝置本身有時會使用作業系統,通常是嵌入式Linux或實時作業系統(RTOS),或是完全沒有作業系統(這是微控制器的最常見情況)。

程式語言

邊緣AI最重要的兩種程式語言是Python和C++。Python是目前機器學習的首選語言,擁有龐大的開源數學和科學計算函式庫,以及近乎100%的機器學習研究社群採用率。

C++是現代嵌入式軟體工程中無處不在的語言,提供對底層硬體的巨大控制力。雖然需要熟練的工程師才能寫出良好的C++程式碼,但它可以比用更高階語言(如Python)編寫的等效程式碼快得多。

值得注意的是,大多數由Python函式庫完成的數學運算實際上是在底層用C++實作的:Python程式碼只是用作方便的包裝器。這為開發人員提供了兩全其美的好處。

在開發過程中,您還可能會使用指令碼語言,如Bash,用於連結和自動化複雜的工具和指令碼,以幫助構建應用程式並將其佈署到裝置上。

重點程式碼與詳細解析

// 示例C++程式碼,用於演示基本的嵌入式系統操作
#include <iostream>

int main() {
    // 初始化變數
    int sensorValue = 0;

    // 讀取感測器資料
    sensorValue = readSensor();

    // 處理資料
    processSensorData(sensorValue);

    return 0;
}

// 讀取感測器資料的函式
int readSensor() {
    // 假設這裡有讀取感測器的程式碼
    return 10; // 示例傳回值
}

// 處理感測器資料的函式
void processSensorData(int value) {
    // 假設這裡有處理資料的程式碼
    std::cout << "處理後的資料: " << value << std::endl;
}

內容解密:

  1. 初始化變數:首先,我們初始化一個變數sensorValue來儲存感測器的讀數。
  2. 讀取感測器資料:呼叫readSensor()函式來取得感測器的當前值。
  3. 處理資料:將讀取到的感測器資料傳遞給processSensorData()函式進行處理。
  4. readSensor()函式:這個函式模擬了從感測器讀取資料的操作,並傳回一個示例值10。
  5. processSensorData()函式:這個函式接收感測器資料,並進行相應的處理。在這個例子中,它只是簡單地將資料輸出到控制檯。

圖表說明

圖表翻譯: 此圖示展示了一個基本的嵌入式系統操作流程。首先,系統開始並嘗試讀取感測器資料。如果讀取成功,則處理資料;如果失敗,則進行錯誤處理。無論結果如何,最終都會輸出結果。

邊緣AI開發的挑戰與解決方案

邊緣裝置的限制

邊緣AI的開發需要在資源有限的裝置上執行複雜的AI模型。這些裝置通常比傳統的電腦系統更小、更便宜,並且能耗更低。然而,這些裝置的計算能力、記憶體和儲存空間都受到嚴格限制。由於大多數目標裝置需要C++,因此需要將任何在更高階語言(如Python)中開發的演算法移植到C++,以便佈署。雖然有一些工具可以簡化這個過程,但它並不總是簡單的。

相依性管理

現代軟體通常具有許多相依性,而AI開發將這種情況提升到了一個新的高度。資料科學和機器學習工具通常需要大量的第三方函式庫;安裝一個主要的深度學習框架(如TensorFlow)會帶來從網頁伺服器到資料函式庫的一切。嵌入式C++程式碼的編譯和佈署通常需要在機器上存在大量的相依性。管理這些相依性是邊緣AI開發中最具挑戰性的部分之一。

解決方案:使用Poetry等工具簡化相依性管理

對於Python,一個非常有用的工具是Poetry。它旨在簡化在單台機器上的多個環境中指定、安裝和隔離相依性的過程。其他必備工具包括作業系統特定的套件管理系統,如aptitude(Debian GNU/Linux)和Homebrew(macOS)。在整合系統的不同部分時,相依性管理可能會變得非常困難。例如,使用一個版本的深度學習框架訓練的模型可能與稍後發布的推理框架不相容。因此,在開發過程的早期階段對系統進行端對端的測試至關重要,以避免後期出現不愉快的驚喜。

容器化技術

容器化是使用作業系統級別的技術在稱為容器的沙盒環境中執行軟體。從內部看,容器似乎與執行它的機器完全不同。它可以具有不同的作業系統和相依性,並且對系統資源的存取受到限制。邊緣AI涉及許多不同的工具鏈,用於從機器學習到嵌入式開發等各種任務。這些工具鏈通常具有相互不相容的相依性。容器化是一種強大的工具,可以使這些不相容的工具鏈在單台機器上和平共處。

容器化的優勢

容器通常是無狀態且高度可移植的。這意味著您可以將一台經過精心組態的機器(以特殊的語法描述)視為一個命令列程式,該程式執行特定的任務。您可以將這些程式連結在一起以執行有用的工作,並且可以輕鬆地在不同的機器上執行它們,以實作分散式計算環境。也可以在嵌入式裝置上執行容器,通常是在SoC上的嵌入式Linux中。這可以是一種有趣的方式,用於封裝軟體及其相依性以進行分發,儘管會有一些開銷。

分散式運算

分散式運算是指在不同的機器上執行不同的程式,這些機器可能位於世界的任何地方,並透過網際網路連線。這是一種比使用單一的高效能主機和超級電腦更靈活的計算方法,並且它是現代計算的大部分基礎架構。分散式運算對於邊緣AI非常重要,因為邊緣AI本身就是分散式運算的一個例子!計算是在資料建立的邊緣執行的,結果要麼在本地使用,要麼透過網路傳送。

分散式運算的重要性

管理資料集、開發演算法和訓練機器學習模型可能需要大量的計算和儲存資源。這使得分散式運算非常適合這些任務。例如,通常會租用一台功能強大的遠端伺服器來訓練深度學習模型,而不是必須為辦公室購買和維護一台強大的機器。組織和控制分散式運算基礎設施的任務稱為協調。有許多開源的協調工具可用於不同的任務。Kubeflow是一個為跨多台機器執行機器學習工作負載而設計的協調框架。

雲端服務供應商

像Amazon Web Services、Google Cloud和Microsoft Azure這樣的企業提供了隨需應變的分散式計算資源,任何願意付費的人都可以使用。這種型別的分散式運算被稱為“雲端運算”,因為電腦網路圖通常使用雲符號來表示位於本地網路外部的資源。雲端服務供應商託管了世界上大多數網站。他們負責處理實體硬體和網路組態,使開發人員能夠專注於構建應用程式,而不是管理裝置。他們大量使用容器化技術,使許多不同的工作負載能夠在相同的基礎架構上並存。

雲端運算在邊緣AI中的應用

邊緣AI專案通常使用雲端運算來儲存資料集、訓練機器學習模型,並提供一個後端,邊緣裝置可以從中傳送和接收資料。在某些情況下,例如“級聯到雲端”,在雲端伺服器上執行的AI演算法與在邊緣裝置上執行的演算法協同工作,以提供服務。

// 示範如何使用C++進行簡單的分散式運算
#include <iostream>
#include <thread>
#include <vector>

void computeTask(int taskId) {
    // 模擬計算任務
    std::cout << "Task " << taskId << " is computing..." << std::endl;
    // 假設計算需要一些時間
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Task " << taskId << " completed." << std::endl;
}

int main() {
    // 建立多個執行緒來模擬分散式運算
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(computeTask, i);
    }

    // 等待所有執行緒完成
    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

內容解密:

  1. 引入必要的標頭檔:程式開始時引入了必要的C++標頭檔,包括<iostream>用於輸入輸出操作,<thread>用於多執行緒程式設計,以及<vector>用於使用向量資料結構。
  2. computeTask函式:定義了一個名為computeTask的函式,它接受一個整數引數taskId來表示任務ID。在函式內部,它模擬了一個計算任務,首先輸出任務正在計算的訊息,然後透過std::this_thread::sleep_for暫停執行2秒來模擬計算過程,最後輸出任務完成的訊息。
  3. main函式:在main函式中,建立了一個std::vector<std::thread>物件threads來儲存多個執行緒。透過迴圈建立了5個執行緒,每個執行緒都執行computeTask函式,並傳遞不同的任務ID。
  4. 等待執行緒完成:在建立所有執行緒之後,程式使用另一個迴圈遍歷threads向量,並對每個執行緒呼叫join方法,以等待所有執行緒完成其任務。
  5. 程式結束:當所有執行緒都完成後,main函式傳回0,表示程式成功結束。

此程式碼範例展示瞭如何使用C++11引入的多執行緒功能來模擬分散式運算的基本概念,即透過多個執行緒平行執行多個任務,以提高程式的整體效能和回應速度。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 嵌入式人工智慧開發實務

package "Docker 架構" {
    actor "開發者" as dev

    package "Docker Engine" {
        component [Docker Daemon] as daemon
        component [Docker CLI] as cli
        component [REST API] as api
    }

    package "容器運行時" {
        component [containerd] as containerd
        component [runc] as runc
    }

    package "儲存" {
        database [Images] as images
        database [Volumes] as volumes
        database [Networks] as networks
    }

    cloud "Registry" as registry
}

dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置

@enduml

圖表翻譯: 此圖表示範了一個簡單的多執行緒程式流程。首先,程式開始並建立多個執行緒(步驟A和B)。接著,為每個執行緒分配計算任務(步驟C)。然後,所有執行緒平行執行其計算任務(步驟D)。程式等待所有執行緒完成其任務(步驟E)。最後,當所有任務都完成後,程式結束(步驟F)。這個流程展示瞭如何利用多執行緒技術實作平行計算,提高程式效率。

邊緣AI的資料處理與演算法開發

在邊緣AI的應用中,資料扮演著至關重要的角色。為了有效地收集、儲存和處理這些資料,許多工具和技術被開發出來。本篇文章將探討資料擷取、物聯網裝置管理、資料儲存與管理、資料管道以及演算法開發等關鍵議題。

資料擷取

在遠端或野外環境中收集資料往往面臨連線受限的挑戰。資料記錄器(Data Loggers)和行動寬頻資料機(Mobile Broadband Modems)是兩種非常有用的工具。

  • 資料記錄器是一種小型裝置,專門用於捕捉和記錄由感測器收集的資料。它們通常具有大容量的持久儲存,能夠儲存大量的感測器讀數。資料記錄器的優點是可以立即開始收集資料,無需設計和構建自定義硬體。然而,其缺點是資料需要手動收集,透過實體連線到記錄器來完成。

  • 行動寬頻資料機提供無線網路連線,通常透過行動網路實作,雖然也支援衛星連線。它們幾乎可以在世界任何地方傳輸資料,但連線狀況取決於當地的可用性和條件。行動寬頻資料機提供了即時資料可用性的便利。然而,資料傳輸速率可能相當昂貴,而且無線通訊消耗大量能量,因此並非在所有情況下都可行。

物聯網裝置管理

許多平台允許與物聯網裝置進行通訊、管理其運作並從中收集資料。使用這些平台通常需要在嵌入式軟體中整合函式庫或API。軟體然後連線到雲端伺服器,您可以使用它來控制裝置。

這些平台對於收集感測器資料非常方便,特別是在已經佈署了裝置管理軟體的舊系統中。

資料儲存與管理

當您收集資料集時,您需要一個地方來儲存它。儲存解決方案可以簡單到硬碟上的逗號分隔檔案,也可以複雜到專門為儲存和查詢時間序列資料而設計的時間序列資料函式庫。

對於邊緣AI應用,通常以「批次」模式處理資料,因此效能並不是最重要的因素。相反,您應該旨在找到適合您所收集資料型別的簡單解決方案。

許多AI資料集直接儲存在檔案系統中,而不使用任何型別的資料函式庫。檔案系統針對這種型別的資料進行了最佳化,像Unix命令列中可用的檔案系統工具可以有效地操作這些資料。

雖然不需要花哨的資料函式庫,但以正確的格式儲存資料仍然很重要。感測器讀數本身應該儲存在高效、緊湊的二進位表示中,例如CBOR、NPY或TFRecord。關於讀數的元資料應該儲存在單獨的檔案(稱為清單檔案)或簡單的資料函式庫中。

程式碼範例:使用 NumPy 儲存感測器資料

import numpy as np

# 假設 sensor_data 是一個包含感測器讀數的 NumPy 陣列
sensor_data = np.array([1, 2, 3, 4, 5])

# 使用 NPY 格式儲存感測器資料
np.save('sensor_data.npy', sensor_data)

# 從 NPY 檔案載入感測器資料
loaded_data = np.load('sensor_data.npy')
print(loaded_data)

內容解密:

  1. 匯入 NumPy 函式庫import numpy as np 這行程式碼匯入了 NumPy 函式庫,並將其簡稱為 np,以便於後續使用。
  2. 建立感測器資料陣列sensor_data = np.array([1, 2, 3, 4, 5]) 這行程式碼建立了一個 NumPy 陣列 sensor_data,其中包含了範例感測器讀數。
  3. 儲存感測器資料np.save('sensor_data.npy', sensor_data) 使用 NumPy 的 save 函式將 sensor_data 陣列儲存到名為 sensor_data.npy 的檔案中,採用 NPY 二進位格式。
  4. 載入感測器資料loaded_data = np.load('sensor_data.npy') 這行程式碼從 sensor_data.npy 檔案中載入之前儲存的 NumPy 陣列,並將其儲存在 loaded_data 變數中。
  5. 列印載入的資料print(loaded_data) 最後,列印出載入的感測器資料,以驗證儲存和載入操作的正確性。

資料管道

資料管道是一種流程,它接收原始資料並將其轉換為可用於特定任務(如訓練機器學習模型)的形式。典型的資料管道可能會對原始感測器資料進行過濾、與其他資料合併,並將其寫入正確的格式以訓練機器學習模型。

許多工具可用於定義資料管道,有些比其他的複雜。邊緣AI的資料管道往往涉及大量相對簡單的資料,因此應避免使用為處理結構化資料(如儲存在關係型資料函式庫中的資料)而設計的工具。

演算法開發

演算法開發是大多數工具複雜性的所在。有很多軟體可用於幫助這一過程,有些比其他的更適合邊緣AI。

數學和科學計算函式庫

Python 社群在數學和數值分析領域開發了許多優秀的開源函式庫。其中一些最重要的函式庫包括:

  • NumPy:提供了高效能的多維陣列運算,是大多數 Python 科學計算的基礎。

  • pandas:提供了直觀的表格資料操作介面,能夠與 NumPy 無縫協作,非常適合用於探索和分析感測器資料。

  • SciPy:包含許多科學計算所需的快速演算法實作,在開發數位訊號處理(DSP)演算法時非常有用。

  • scikit-learn:根據 NumPy 和 SciPy,提供大量機器學習演算法的實作,以及用於餵養和評估這些演算法的工具。其API設計允許元件之間的互換,使得比較和組合不同的演算法變得容易。

資料視覺化

在處理資料時,視覺化是一種必不可少的工具,尤其是當涉及數位訊號時。圖表和圖形使我們能夠表示和解釋原本難以理解的數值資訊。Python 生態系統中有一些出色的函式庫可用於視覺化,例如 Matplotlibseaborn

程式碼範例:使用 Matplotlib 和 seaborn 進行資料視覺化

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

# 建立範例資料
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 使用 Matplotlib 繪製圖形
plt.plot(x, y)
plt.title('Sine Wave')
plt.show()

# 使用 seaborn 繪製分佈圖
sns.set()
data = np.random.randn(100)
sns.histplot(data, kde=True)
plt.title('Histogram with KDE')
plt.show()

圖表翻譯:

  1. 第一幅圖表展示了一條正弦波形,使用 Matplotlib 繪製。這種視覺化有助於理解訊號的時間序列特性。
  2. 第二幅圖表是一個包含核密度估計(KDE)的直方圖,使用 seaborn 繪製。這種視覺化方式能夠清晰地展示資料的分佈情況。