Arduinoで機械学習を始める
i
このチュートリアルは、すでに製品ライフサイクルの終了(EOL)を迎えた製品を対象としています。

Arduino Nano 33 BLE Senseで、機械学習の訓練し、使用する方法を学びます。


Author:Sandeep Mistry and Dominic PajakLast revision:2024/07/17

i
この記事は、当初Sandeep MistryとDominic Pajakにより、TensorFlowブログに投稿されたものです。
i
重要な注意事項です。TensorFlow Lite Micro Libraryは、Arduinoのライブラリマネージャからは利用できなくなりました。このライブラリは自分でダウンロードしIDEにインストールする必要があります。

はじめに

Arduino、誰もが使えるように機械学習を十分容易にしようとしています。過去数か月間TensorFlow Liteチームと協力し、これまで共同で実施してきた、TensorFlow Lite MicroのArduino Nano 33 BLEへの導入を紹介することをうれしく思います。この記事では、Arduinoライブラリマネージャから利用可能な、新しいTensorFlow Lite Microのインストールと実行方法を紹介します。

以下の最初のチュートリアルでは、Arduinoボードにニューラルネットワークをインストールし、簡単な音声コマンドを認識させます。

次に、ColabのTensoFlowを使い、Arduino用のカスタムジェスチャー認識モデルの訓練に利用できる、より深いチュートリアルを紹介します。この題材は、Sandeep MistryとDon Colemanにより開催された実践的なワークショップに基づいていて、現在はオンラインで公開されています。

Arduinoを経験した人であれば、数時間でこのチュートリアルを動作させることができるでしょう。マイクロコントローラーの完全な初心者でれば、もう少しかかるでしょう。

最初の例とチュートリアルを共有し、これから何を作るのかを知るのが楽しみです。さあ始めましょう。

注意: 以下のプロジェクトは、TensorFlow Lite for Microcontrollersに基づいていて、現在は、TensorFlow repoで実験中です。これはまだ新しくて発展している分野です。

目的

必要なハードウェアとソフトウェア

Arduino Nano 33 BLE Senseには様々なセンサーが搭載されているので、すばらしいTinyMLアプリケーションを作成できる可能性があります。

  • 音声: デジタルマイク
  • モーション: 9軸IMU(加速度、ジャイロスコープ、磁気センサー)
  • 環境: 温度、湿度、気圧
  • 光: 照度、色、物体近接

従来のArduino Unoとは異なり、ボードにはマイクロコントローラーとセンサーが搭載されています。このため、追加のハードウェアや配線を行わないで、様々なユースケースに適用することができます。また、ボードは充分小さいので、ウェアラブルのようなアプリケーションデモ利用できます。その名前が示すように、Bluetooth® Low Energy接続ができるので、データ(や推論結果)をラップトップやモバイルアプリ、他のBluetooth® Low Energyボードや機器に送信することができます。

ヒント: USB経由でBLE Senseボードを接続するのは、追加の配線やハードウェア不要でシングルボードコンピューターにデータ取得機能や複数のセンサーを追加する簡単な方法です。例えば、Raspberry Piへの素晴らしい追加機能です。

マイクロコントローラーとTinyML

Arduinoボードで使われているマイクロコントローラーは、低価格、シングルチップで自己完結したコンピューターシステムです。例えば、ウェアラブルやドローン、3Dプリンター、おもちゃ、炊飯器、スマートプラグ、電動スクーター、洗濯機など、日常にある何十億もの機器に搭載された見えないコンピューターです。これらのデバイスに接続する流れは、IoTと呼ばれるものの一部です。

Arduinoは、オープンソースのプラットフォームとコミュニティで、マイクロコントローラーアプリケーション開発を、全ての人に利用できるようにしようとしています。ここで使うボードには、Arm Cotex-M4マイクロコントローラーを搭載していて、1MBのフラッシュメモリと256kBのRAMを搭載し、64MHzで動作しています。クラウドやPC、スマートフォンに比べれば貧弱ですが、マイクロコントローラの標準としては妥当なものです。

MLをマイクロコントローラーに搭載することには、以下のような実用的な理由があります。

  • 機能: スマートデバイスを素早くかつローカルで(インターネット接続なしで)動作させたい
  • 価格: 単純・低価格なハードウェアで実現する
  • プライバシー: 全てのセンサーデータを外部と共有したくない
  • 効率: 小型デバイス、環境発電、長時間の電池寿命

これらの達成に向けて構築する重要な最終目的があります。

  • 機械学習は、組込み環境の経験がない開発者でも、マイクロコントローラーを利用できるようにします。

機械学習には、ニューラルネットワークをマイクロコントローラーのようなメモリに制約のあるデバイスに適合させるのに利用可能な技術があります。鍵となるステップの一つは、浮動小数点数から8ビット整数への重みの量子化です。これには、推論計算を高速化し、低クロックレートデバイスにより適用しやすくなるという効果もあります。

TinyMLは新しい分野で、やるべきことがまだあります。しかし、興味があるのは、そこには、広大な未開のアプリケーション空間があるということです。全ての場所の全ての種類のセンサーが、何十億ものマイクロコントローラーと接続することで、非常に創造的で価値のあるTinyMLアプリケーションが、将来生まれる可能性があります。

TensorFlow Lite for Microcontrollersの例

TensorFlow Lite for Microcontrollersのインターフェイスの例はパッケージ化され、Arduinoライブラリマネージャを通じて利用可能です。これにより、Arduinoで、数クリックでインストールし、実行できます。このセクションではこれらの実行方法を紹介します。

  • micro_speech: 内蔵マイクを使った音声認識
  • magic_wand: 内蔵IMUを使ったジェスチャー認識
  • person_detection: 外付けArduCamカメラを使った人検出

例に関する背景を知りたければ、TensorFlowリポジトリのソースコードをみてください。これらの例のモデルは事前に訓練されています。以下のチュートリアルでは、Arduinoにデプロイして実行する方法を紹介します。次のセクションでは、訓練について議論します。

Arduino Create Cloudエディタを使って例を実行する方法

Arduino Nano 33 BLE SenseをUSBケーブルでPCに接続すると、Arduino Createクラウドエディタを使い、以下のTensorFlowの例をコンパイルして、ボードで実行することができます。

音声認識の例に注目する

Arduinoボードでの最初のステップの一つは、LEDの点滅です。ここでは、少しひねって、TensorFlow Lite Microを使い、音声キーワードを認識させます。ここでは、“yes"と"no"という簡単な語彙を使います。このモデルは、256kBのRAMしかないマイクロコントローラー上で動作していることに注意してください。このため、インターネット接続があり、2000倍以上のRAMが利用可能な、商用の音声アシスタントのようなレベルの精度を求めないでください。

ボードはバッテリーによる給電も可能です。このため、Arduinoをモーターやアクチュエーターなどの様々なデバイスに接続して、音声制御プロジェクトの可能性が広がります。

Arduino IDEを使って例を実行する方法

同じ推論の連をArduino IDEアプリケーションを使っても試すことができます。

まず、後のセクションのArduino IDEの設定に従ってください。

Arduino IDEの、ファイル > スケッチ例 > Arduino_TensorFlowLiteメニューにスケッチ例があります。

例を選ぶと、スケッチのが開きます。例をコンパイルし、ボードにアップロードし、実行するには、矢印アイコンをクリックします。

コマンドラインの方がいいという上級者向けには、arduino-cliも用意されています。

TensorFlow Lite Micro Model For Arduinoを訓練する

次に、Arduinoボードがジェスチャーを認識できるようにMLを使います。Arduino Nano 33 BLE Senseボードからモーションデータを取得し、モデルを訓練するためにTesorFlowにインポートし、その結果得られた分類器をボードにデプロイします。

このチュートリアルの考えは、Charlie Gerardによる素晴らしい、Play Street Fighter with body movements using Arduino and Tensorflow.jsを基にしています。Charlieの例では、ボードはArduinoからの全てのセンサーデータを、TensorFlow.jsでジェスチャーの分類を行う機器に流します。これをさらに進め、Arduinoボード自身でジェスチャーの分類を行うことで、TinyML化します。我々が利用しているArduino Nano 33 BLE Senseボードには、より強力なArm Cortex-M4が搭載されているので、これはより簡単になります。

以下のチュートリアルを改良したので、追加のハードウェアは不要です。サンプリングは、ボードの移動を検出すれば開始されます。オリジナルのチュートリアルはブレッドボードと物理ボタンを追加し、ボタンを押すことサンプリングを開始します。ハードウェアに少し興味があれば、こちらのバージョンを代わりに使うこともできます。

IDEの設定

  1. Nano 33 BLE ボード用のドライバーがインストールされていることを確認します。左のパネルのボードマネージャを選択し、“Nano 33 BLE"を検索し、“Arduino Mbed OS Nano Boards"をインストールします。
  1. 必要なライブラリが全てインストールされていることも確認します。IDEのライブラリマネージャを使いArduino_LSM9DS1をインストールします。
  1. 最後に、TensorFlow Lite Microライブラリをリポジトリからダウンロードする必要があります。ライブラリマネージャからは利用できないので、手動インストールする必要があります。

.zipをダウンロードしたら、Arduino IDEで、スケッチ > ライブラリをインクルード > .ZIP形式のライブラリをインストール を選択し、ファイルを選択します。

その後IDEを再起動する必要があります。

Arduinoボードからセンサーデータを流し込む

まず訓練用のデータを取得する必要があります。PCとボードを接続しプログラムするのに使っているのと同じUSBケーブル経由で、Arduinoボードからセンサーデータのログを取得することができます。

Arduinoボードでは、スケッチとも呼ばれる.ino形式のArduinoのソースコードからコンパイルし、Arduino IDEやArduino Createを使ってボード上にプログラムした小さいアプリケーションを実行します。

以下のことを行うスケッチを作成します。

  • ボードの加速度センサーとジャイロスコープを監視する
  • ボードの大きな直線加速度を検出しサンプルウインドウを起動する
  • 119Hzで1秒間サンプリングし、USB経由でCSV形式のデータを出力する
  • 次のジェスチャーを監視するためにループを繰り返す

ボードから読むのに選択したセンサー、サンプリングレート、トリガーとなる閾値、ストリームデータ出力をCSVかJSON、バイナリ形式、他のフォーマットを使うかは全て、Arduinoで実行するスケッチでカスタマイズできます。データをログ出力する前に, 信号の前処理を実行する範囲とデバイスでフィルタリングするかもカスタマイズできます。これについては、別のブログで紹介します。今は、スケッチをアップロードしサンプリングできます。

完全なスケッチは以下の通りです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
  IMU Capture
  This example uses the on-board IMU to start reading acceleration and gyroscope
  data from on-board IMU and prints it to the Serial Monitor for one second
  when the significant motion is detected.
  You can also use the Serial Plotter to graph the data.
  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 BLE Sense board.
  Created by Don Coleman, Sandeep Mistry
  Modified by Dominic Pajak, Sandeep Mistry
  This example code is in the public domain.
*/

#include <Arduino_LSM9DS1.h>

const float accelerationThreshold = 2.5; // threshold of significant in G's
const int numSamples = 119;

int samplesRead = numSamples;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }

  // print the header
  Serial.println("aX,aY,aZ,gX,gY,gZ");
}

void loop() {
  float aX, aY, aZ, gX, gY, gZ;

  // wait for significant motion
  while (samplesRead == numSamples) {
    if (IMU.accelerationAvailable()) {
      // read the acceleration data
      IMU.readAcceleration(aX, aY, aZ);

      // sum up the absolutes
      float aSum = fabs(aX) + fabs(aY) + fabs(aZ);

      // check if it's above the threshold
      if (aSum >= accelerationThreshold) {
        // reset the sample read count
        samplesRead = 0;
        break;
      }
    }
  }

  // check if the all the required samples have been read since
  // the last time the significant motion was detected
  while (samplesRead < numSamples) {
    // check if both new acceleration and gyroscope data is
    // available
    if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
      // read the acceleration and gyroscope data
      IMU.readAcceleration(aX, aY, aZ);
      IMU.readGyroscope(gX, gY, gZ);

      samplesRead++;

      // print the data in CSV format
      Serial.print(aX, 3);
      Serial.print(',');
      Serial.print(aY, 3);
      Serial.print(',');
      Serial.print(aZ, 3);
      Serial.print(',');
      Serial.print(gX, 3);
      Serial.print(',');
      Serial.print(gY, 3);
      Serial.print(',');
      Serial.print(gZ, 3);
      Serial.println();

      if (samplesRead == numSamples) {
        // add an empty line if it's the last sample
        Serial.println();
      }
    }
  }
}

Arduinoボードからのライブセンサーデータログの可視化

ここまで完了したら、ボードから来たデータを可視化することができます。まだデータを取得していませんが、センサーデータがどのように取得され、どのように取得開始され、サンプリングウインドウがどのくらいなのかを知るためです。これは、訓練のサンプルを収集する際に役立ちます。

  • Arduino IDEで、ツール > シリアルプロッタを選び、シリアルプロッタを開く。
  • シリアルプロッタを開けませんでした、というエラーが出たら、ツール> ポート > ポート名(Arduino Nano 33 BLE)を選択する。
  • ボードを持ち、パンチの練習や曲げるジェスチャーをする
  • 1秒間のサンプルウインドウが表示され、次のジェスチャーを待つ
  • センサーデータを取得するグラフが表示される

終わったらシリアルプロッタのウインドウを閉じてください。そうしないと、次のステップが動作しません。

訓練データを取得する

TensorFlowにアップロードするために、CSVログとしてデータを取得するには、Arduino IDDで、ツール > シリアルモニタ を選びデータを表示し、PCにエクスポートします。

  • 上部の小さい白いボタンを押してボードをリセットする
  • 片手でボードを持ち上げる(後で持ち上げるとサンプリングが開始される)
  • Arduino IDEで、ツール > シリアルモニタを選択し、シリアルモニタを開く
  • ボードが利用できないというエラーが出たら、ポートを選びなおす
  • ツール > ポート > ポート名(Arduino Nano 33 BLE)
  • ボードを持ちパンチのジェスチャーをする(このとき注意すること)
  • 取得開始するために、十分な速さで外向きにパンチする
  • 再度取得開始しないよう、ゆっくり自然な位置にもどる
  • データをさらに取得するため、ジェスチャーキャプチャの手順を10回以上行う
  • シリアルコンソールのデータを、punch.csvという新しいテキストファイルにコピー・ペーストする
  • コンソールウインドウ出力をクリアし、今回は曲げる動作をし、flex.csvに対して、上記の手順を繰り返す。
  • 内向きの曲げは取得開始するために十分速く、戻るときは毎回ゆっくりと

注意: 2つのCSVファイルの1行目は、aX,aY,aZ,gX,gY,gZという値にしてください。

i
コマンドラインを使い、Arduinoからのセンサーログを.csvファイルに直接リダイレクトしたい場合は、シリアルプロッタ・シリアルモニタウインドウを閉じ、以下のコマンドを使ってください。
1
$ cat /dev/cu.usbmodem[nnnnn] > sensorlog.csv

TensorFlowでの訓練

前のセクションで収集したArduinoボードからのデータを使い、機械学習モデルの訓練を行うのに、Google Colabを使います。Colabを使では、Jupyter notebookが提供され、TensorFlowの訓練をWebブラウザで実行することができるようになります。

  • Python®環境の設定
  • punch.csvとflex.csvのアップロード
  • データの解析と準備
  • モデルの構築と訓練
  • 訓練したモデルのTensorFlow Liteへの変換
  • モデルのArduinoヘッダファイルへのエンコード

Colabの最後の手順では、model.hを生成します。これをダウンロードし、次のセクションで、Arduino IDEのジェスチャー分類器プロジェクトでインクルードします。

Colabでnotebookを開き、セル内の手順を実行します - arduino_tinyml_workshop.ipynb

IMUデータの分類

前のセクションで訓練してColabからダウンロードしたmodel.hを、Arduino IDEプロジェクトで使います。

新しいスケッチを始めます。以下にコードをしめします。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
  IMU Classifier
  This example uses the on-board IMU to start reading acceleration and gyroscope
  data from on-board IMU, once enough samples are read, it then uses a
  TensorFlow Lite (Micro) model to try to classify the movement as a known gesture.
  Note: The direct use of C/C++ pointers, namespaces, and dynamic memory is generally
        discouraged in Arduino examples, and in the future the TensorFlowLite library
        might change to make the sketch simpler.
  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 BLE Sense board.
  Created by Don Coleman, Sandeep Mistry
  Modified by Dominic Pajak, Sandeep Mistry
  This example code is in the public domain.
*/

#include <Arduino_LSM9DS1.h>

#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/micro_error_reporter.h>
#include <tensorflow/lite/micro/micro_interpreter.h>
#include <tensorflow/lite/schema/schema_generated.h>
#include <tensorflow/lite/version.h>

#include "model.h"

const float accelerationThreshold = 2.5; // threshold of significant in G's
const int numSamples = 119;

int samplesRead = numSamples;

// global variables used for TensorFlow Lite (Micro)
tflite::MicroErrorReporter tflErrorReporter;

// pull in all the TFLM ops, you can remove this line and
// only pull in the TFLM ops you need, if would like to reduce
// the compiled size of the sketch.
tflite::AllOpsResolver tflOpsResolver;

const tflite::Model* tflModel = nullptr;
tflite::MicroInterpreter* tflInterpreter = nullptr;
TfLiteTensor* tflInputTensor = nullptr;
TfLiteTensor* tflOutputTensor = nullptr;

// Create a static memory buffer for TFLM, the size may need to
// be adjusted based on the model you are using
constexpr int tensorArenaSize = 8 * 1024;
byte tensorArena[tensorArenaSize] __attribute__((aligned(16)));

// array to map gesture index to a name
const char* GESTURES[] = {
  "punch",
  "flex"
};

#define NUM_GESTURES (sizeof(GESTURES) / sizeof(GESTURES[0]))

void setup() {
  Serial.begin(9600);
  while (!Serial);

  // initialize the IMU
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }

  // print out the samples rates of the IMUs
  Serial.print("Accelerometer sample rate = ");
  Serial.print(IMU.accelerationSampleRate());
  Serial.println(" Hz");
  Serial.print("Gyroscope sample rate = ");
  Serial.print(IMU.gyroscopeSampleRate());
  Serial.println(" Hz");

  Serial.println();

  // get the TFL representation of the model byte array
  tflModel = tflite::GetModel(model);
  if (tflModel->version() != TFLITE_SCHEMA_VERSION) {
    Serial.println("Model schema mismatch!");
    while (1);
  }

  // Create an interpreter to run the model
  tflInterpreter = new tflite::MicroInterpreter(tflModel, tflOpsResolver, tensorArena, tensorArenaSize, &tflErrorReporter);

  // Allocate memory for the model's input and output tensors
  tflInterpreter->AllocateTensors();

  // Get pointers for the model's input and output tensors
  tflInputTensor = tflInterpreter->input(0);
  tflOutputTensor = tflInterpreter->output(0);
}

void loop() {
  float aX, aY, aZ, gX, gY, gZ;

  // wait for significant motion
  while (samplesRead == numSamples) {
    if (IMU.accelerationAvailable()) {
      // read the acceleration data
      IMU.readAcceleration(aX, aY, aZ);

      // sum up the absolutes
      float aSum = fabs(aX) + fabs(aY) + fabs(aZ);

      // check if it's above the threshold
      if (aSum >= accelerationThreshold) {
        // reset the sample read count
        samplesRead = 0;
        break;
      }
    }
  }

  // check if the all the required samples have been read since
  // the last time the significant motion was detected
  while (samplesRead < numSamples) {
    // check if new acceleration AND gyroscope data is available
    if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
      // read the acceleration and gyroscope data
      IMU.readAcceleration(aX, aY, aZ);
      IMU.readGyroscope(gX, gY, gZ);

      // normalize the IMU data between 0 to 1 and store in the model's
      // input tensor
      tflInputTensor->data.f[samplesRead * 6 + 0] = (aX + 4.0) / 8.0;
      tflInputTensor->data.f[samplesRead * 6 + 1] = (aY + 4.0) / 8.0;
      tflInputTensor->data.f[samplesRead * 6 + 2] = (aZ + 4.0) / 8.0;
      tflInputTensor->data.f[samplesRead * 6 + 3] = (gX + 2000.0) / 4000.0;
      tflInputTensor->data.f[samplesRead * 6 + 4] = (gY + 2000.0) / 4000.0;
      tflInputTensor->data.f[samplesRead * 6 + 5] = (gZ + 2000.0) / 4000.0;

      samplesRead++;

      if (samplesRead == numSamples) {
        // Run inferencing
        TfLiteStatus invokeStatus = tflInterpreter->Invoke();
        if (invokeStatus != kTfLiteOk) {
          Serial.println("Invoke failed!");
          while (1);
          return;
        }

        // Loop through the output tensor values from the model
        for (int i = 0; i < NUM_GESTURES; i++) {
          Serial.print(GESTURES[i]);
          Serial.print(": ");
          Serial.println(tflOutputTensor->data.f[i], 6);
        }
        Serial.println();
      }
    }
  }
}
  • 新しいタブを作成し、model.hという名前にする
  • model.hタブを開き、Colabからダウンロードしたバージョンをペーストする
  • スケッチのアップロード: スケッチ > アップロード
  • シリアルモニタを開く: ツール > シリアルモニタ
  • ジェスチャーを行う
  • 各ジェスチャーの信頼度がシリアルモニタに表示される(0=信頼度低、1=信頼度高)
  • Arduino用の最初のMLを訓練しました

より楽しくするため、Emoji_button.inoの例では、LinuxとmacOSで絵文字を表示するUSBキーボードの作成方法を紹介しています。Emoji_button.inoの例とIMU_Classifier.inoのスケッチを組み合わせ、ジェスチャーで制御する絵文字キーボードを作成してみましょう。

おわりに

TinyMLには学んだり探求したりすることが多く、わくわくします。このブログが、TinyMLの可能性のあるアイデアや、プロジェクトに適用する出発点となったことを願っています。作成したものを知らせ、Arduinoコミュニティに共有してください。

TinyMLとこの記事のアプリケーション例の包括的な背景を知るには、Pete WardenとDaniel Situnayakeの新しいO’RellyのTinyML: Machine Learning with TensorFlow on Arduino and Ultra-Low Power Microcontrollersを推薦します。

オリジナルのページ

https://docs.arduino.cc/tutorials/nano-33-ble-sense/get-started-with-machine-learning/

最終更新日

August 16, 2026

inserted by FC2 system