書籍『TinyML』 の要点メモ, その4

14章: Designing Your Own TinyML Applications

組込み機器以外で開発できることは、組込み機器以外(PC, Raspberry Piなど)で開発しよう、というお話。 TinyMLに限定されない、機械学習モデルを開発する際の考え方が書かれている。

  • 組込みシステムで開発するよりもLinuxのようなモダンOSが動くシステムでプログラミングする方が簡単で速い
  • 試験し易いデバイスを使ってプロトタイピングすることを推奨する
  • あなたの問題に似ている問題を扱っているものを参考にするのが有効である
  • arxiv.org のサーベイ論文から一般的なデータセットを探してみよう
  • あなたのセンサーデータの特徴をよく見て、同じようなチュートリアルを探してみよう。評価のベースラインにも使える。
  • モデル・アーキテクチャに対してすることよりも、データを集め、探索し、ラベル付けし、改善することに時間をかけよう
  • データをよく見よう
  • レーニングデータが不均衡な場合(クラスごとにデータ数が大きく異なる)は、推論後にキャリブレーション(事前知識に基づく重みづけ)が必要になるかもしれない
  • Wizard of Oz-ing。モックアップを作って、判断するソフトウェアを作る代わりに、少し引いてみてみよう。開発に時間を割く前に、仮定を検証してみよう

家にある機器を活用する方法

pc.watch.impress.co.jp

↑の記事でWindow10をサブディスプレイとして使えることを初めて知った。 使い方によっては便利そう。

最近、自宅からテレワークをするようになって家にある機器をどう使いこなすのが良いか考えているので、 ここで少し考えをまとめてみることにする。

まず、自宅には10インチ以上のモニタを備える(つながる)機器としては以下の4つがある。

  1. 自作デスクトップPC(メイン), Windows10 Pro, 26インチ TVモニター(REGZA), 1920x1080
  2. iPad 10インチ
  3. Chromebook, Lenovo S330, 14インチ, 1366x768
  4. ノートPC, Dell Inspiron 14R, Windows10 Home, 14インチ, 1366x768

これらの機器を組み合わせて使いこなす方法として以下の3つを考えた。

まず一番表示部が小さいiPadについて。 iPadをサブディスプレイにできるアプリもあるけど、画面サイズが他に比べると小さく、またちょっと調べ物をしたくなったり、気分転換したくなったときにさっと扱えるので、スタンドアローンがいいと思っている。これはほぼ確定。

次にマルチディスプレイとリモートデスクトップをどう使い分けるのがいいか?

リモートデスクトップ

リモートデスクトップを使う場合は、ログイン元のPCはシンクライアント的に表示をメインの役割とし、ログイン先のPCをサーバー的な役割で使うのが良いと思う。 その場合は、3. または 4. から1.にリモートログインすることになる。

メリット

  1. または 4. を家の中で持ち運べるので、いろんな環境で作業できる。 たとえば、座って作業するのに疲れたらスタンディングでやってみたり、寝転がってやってみたりできることかと思う。 実際、椅子にずっと座っていると腰は結構辛い。

デメリット

  1. と 4. を2台同時に使うケースがあまり想定できないので、1台余って機器を活用しきれていないように思える。

マルチディスプレイ

物理的に最も大きいモニタは1.のデスクトップPCなので、マルチディスプレイにするならこのデスクトップPCをメインにして、4.のノートPCをサブモニタにするのが良さそう。 また、4.のノートPCの水平幅とデスクトップPCにつながっているTVモニターの垂直幅がだいたい同じなので、ノートPCを縦にして、水平方向に表示範囲を広げるのが良い気がする。

メリット

当たり前だけど表示領域が広がる

デメリット

1.のTVモニターは古いもので上下左右の額縁が4 cmぐらいあり、マルチディスプレイにしてもモニタ間の隙間が広い。 3.のChromebookがやっぱり余る。

ということで、どちらもメリット・デメリットがある。 なので、2つの活用方法のハイブリッドも考えてみる どちらの活用方法もノート型PCである3.または4.を1台余らせてしまうのがもったいない。 リモートデスクトップで場所や人によってノート型PCを使い分けることも考えられるが、幸い?1Kひとり暮らしなのであんまりメリットを感じない。 結果、マルチディスプレイ方式で3.を家の中で遊軍的に使って、1.にリモートログインするのがいまのところの解かなと思う。

書籍『TinyML』 の要点メモ, その3

その2 の続きのその3。

Wake-Word ("OK, Google", "Alexa", "Hey, Siri") のような限定された単語を検出するアプリをつくる。

7章: Wake-Word Detection: Building an Application

単語 "yes", "no" を判別するサンプルのビルドとデプロイまでの流れとアプリケーションの説明。

  • 帯域、電力、プライバシーと音声UIのレスポンス面からエッジデバイスでオフラインでwake wordを検出する。そのあとはクラウドに任せる。
  • "yes", "no", unknown word, silent の4つに分類する
  • マイクから入力した音声波形をFFTして、横軸は時間、縦軸は周波数毎のパワーのグレイスケール画像(2次元のテンソル)として取り扱う
  • モデルのサイズは18 kB
  • 使用する演算のみモデルに含める。
    • モデルが使用している演算を確かめるには、ビルドを演算を含まない状態で行い、エラーに従い足していく
  • テンソル用のメモリ確保。トライ&エラーで増やす。
  • windowが"noted"の"no"だけにかかって、"no"と誤検出しないように、1秒間の検出結果から判断する
  • データフロー
    • Audio Provider(マイク録音) -> Feature Provider(FFT) -> TFL Interpreter(Model) -> Command Recognizer(Wake-Wordの判別) -> Command Responder(LED表示など)

8章: Wake-Word Detection:

7章の発展版。自分でモデルをトレーニングするところから実施する。

  • 手順
    • Google Colabでwake-wordを指定してモデルを訓練する
    • tocoを使ってTFモデルからTFLモデルに変換する
    • TFLからTFLMの形式のC言語配列にxddコマンドで変換する
  • モデル
    • FFT
      • ハン窓、480点サンプル(30ms)をゼロ埋めした512サンプルに対してFFTする)
      • 256ビンの周波数を40に減らす。メル周波数のスケールに合わせて減らす。高周波側を平均するので結果的にノイズ抑圧効果もある。
      • 奇数ビンと偶数ビンで傾向が違う。バグではなくそういう実装をしていてそのほうが精度がでる(??。
    • 特徴量としてスペクトラムの大きさだけ使う。位相は使わない。
    • 横軸:時間 49=1000ms/20ms - 1, 縦軸:周波数 40
    • ニューラルネットワーク: CNN-> max pooling -> Full connection -> Softmax
    • 1秒間の推論結果を使って最終的な予測結果を生成する
  • データを集めるのは大変だけど、"Visual Wake Words Dataset" というのがある
  • 転移学習は有望だけど音声認識ではまだ発展途上

書籍『TinyML』 の要点メモ, その2

4,5,6章の要点メモです。 その1の続きです。 (要点と私の意見が混在していますがご容赦ください)

4,5,6章はこのサンプルの詳細な説明です。 github.com

4章: The "Hello World" of TinyML: Building and Training a Model

  • ランダムノイズを加えた正弦波を入力、正弦波波形を教師データとして、正弦波を出力するモデルを訓練し、推論するサンプル
  • 環境の説明(Jupter Notebook, Google Colab, Python, TensorFlow, Numpy, Keras)
  • Kerasのモデル生成方法, fit, compile, predict メソッドを知っていれば、モデルの訓練までは斜め読みでok
  • TFモデルをFlatBufferフォーマットのTFLモデルに変換する。詳細は12章。
  • モデル変換時に最適化(量子化とか)も行う。詳細は13章。
  • 量子化するときは入力値のフルレンジ(最大値、最小値)を含むrepresentative datasetを使う。(ダイナミックレンジと分解能を決めるために)
  • xddコマンドを使ってCソースファイルに変換する

5章: The "Hello World" of TinyML: Building an Application

microcontroller上で推論アプリ(正弦波の推論)を動作させる。

  • 簡易的なテストフレームワーク(TF_LITE_MICRI_TESTS)がある
  • TFLモデルを読み込んで、型のテスト、shapeのテスト、スキーマのバージョンのテスト
  • テンソル(入力、出力、中間テンソル)の記憶領域確保 ** どれくらいの領域を確保すればいいかはトライ&エラー(本当?
  • インタープリタがTFLMの肝
  • 入力が多次元配列でも平坦化してから入力値を設定する
  • 組込みアプリではできるだけPCの環境でテストする。(組込みデバイスでのテストはPCのそれより大変なので)
  • 組込みデバイス毎にデバイス固有のソースファイルが供給される
  • このサンプルは↓の構造になっている
int main(int argc, char* argv[]) {
  setup(); /* モデル読み込み、モデルの形状や型の確認 */
  while (true) { /* このアプリ(OSもない)しか動作しないので無限ループ */
    loop(); /* 推論、期待値と出力を比較 */
  }
}
  • (細かく説明してくれているので理解はできると思うが、C/C++のコードを見たことがないとちょっと手間取るかも。)

6章: The "Hello World" of TinyML: Deploying to Microcontrollers

5章でビルドしたアプリを3つの組込みデバイス(Arduino Nano 33 BLE Sense, SparkFun Edge, STM32F746G Discovery kit)にデプロイする。 ただし、自分は SparkFun Edge を中心に読んだので、ほかの2つのデバイス固有の話は含まれません。

  • 組込みデバイス毎にハードウェア構成が異なるので、loop()から呼ばれるHandleOutput() (出力表示。LED点滅, LCD表示 etc.)を組込みデバイス毎に実装する
  • マイクロコントローラの全くの初心者向けに、マイクロコントローラとはなんぞや?、という説明
  • SparkFun Edge
    • 構成、特徴:Arm Cortex M4, LED x4, 1MiB Flash, Low Power
    • 推論値に応じて異なるLEDを点滅させるコードを HandleOutput() に実装
    • ビルドしたバイナリは暗号化キーで署名する必要がある
    • UART経由でバイナリをFlashに焼く
    • 実行するとLED点滅のほかにシリアルポートにもログが流れてくる
    • (私はWindows10のWSL(Ubuntu18.04)で試しました)

書籍『TinyML』 の要点メモ, その1

を読み始めたのでそのメモ。 書籍からの引用が多くなると著作権的にもあれなので、自分があとで読み返せるように要点だけ書きます。

TensorFlow Lite for Microcontroller に関する本です。TensorFlow Liteはスマホなどを対象にしているのに対して、TensorFlow Lite for Microcontroller は、もっとリソース制約の厳しい組込みデバイスで、DeepLearningの推論を実行するフレームワークです。

筆者の方は、Googleで TensorFlow Lite や TensorFlow Lite for Microcontroller を開発してる方のようです。

まだ読んでいる途中ですが、この本で紹介されているサンプルは www.tensorflow.org にあるものと同じもののようです。 21章構成で、1~3章が導入、4~12章で4つのサンプルを説明、13~21章は個々の話題について記載されているようです。 自分で読んだ順に書いているので行ったり来たり、読まない部分があるかもしれません。

また以下のように略記します。 * 機械学習 = ML * Deep Learning = DL * TensorFlow Lite = TFL * TensorFlow Lite for Microcontroller = TFLM

1章: Introduction

  • OK Google は 14kB で動いている
  • TinyML のアイデアや必要性に至った経緯
  • TinyMLの定義は消費電力が1mW以下

2章: Getting Started

  • 対象読者(コマンドラインとエディタが使える。ML/DL、組込み機器の知識は必須ではない)
  • 必要なハードウェア(STM32FやArduinoで動くサンプルもあるけども) www.sparkfun.com
  • 必要なソフトウェア。VSCodeとかTeraTermとか。
  • この本は、勾配とか誤差逆伝搬とかの話にフォーカスするのではなく、スクラッチからモデルを作って、エラーに対処して、新しいデータセットに取り組むモデルをつくるプロセスを示す。

3章: Getting Up to Speed on Machine Learning

機械学習Deep Learningの概要説明なので、上記をほかの書籍で理解している場合は読み飛ばして問題ないと思う(自分は読んでない)。

19章: Porting Models from TensorFlow to TensorFlow Lite

  • TensorFlow Lite はTensorFlowのサブセットなので実装されていない演算もある。all_ops_resolver.ccをみて実装されている演算を確認しよう。 tensorflow/all_ops_resolver.cc at master · tensorflow/tensorflow · GitHub
  • 一般的なDLモデルは、前処理(訓練、教師データの読み込みなど)、コアのニューラルネットワーク、後処理の3段階からなる。コアのニューラルネットワーク以外は無理にTFLで書かずに普通のコードで書くのが理にかなっている。
  • TFLのグラフ構造をみるのにnetronは便利 github.com
  • TFLに実装されていない演算を自分で実装する方法
  • TFLチームの優先度は特定のユースケースから決めているので、あなたのケースが最適化されていない場合がある。これは15章で扱う。
  • モデルを訓練する前にエクスポートしてTFLとの互換性をまず確認しよう

21章: Learning More

各種情報源。

械学習の前処理、EDA、モデル構築手順(分類問題)

自分用のメモです。 機械学習の手法について知っているが、実装方法をすぐに忘れてしまう人向けのメモです。 教師あり学習の分類問題を対象とします。ニューラルネットワークは対象としません。

分類問題

importするライブラリ

# データ解析のライブラリ
import pandas as pd
import numpy as np 
import pandas_profiling as pdp

# データ可視化のライブラリ
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
%matplotlib inline 

# Scikit-learn
# common
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV 
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import MinMaxScaler
from sklearn.cross_validation import cross_val_score
from sklearn.pipeline import Pipeline
# 評価指標
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score
from sklearn.metrics import f1_score, roc_curve
# 推定器
from sklearn.linear_model import LogisticRegression # ロジスティック回帰
from sklearn.ensembles import RandomForestClassifier # ランダムフォレスト
from sklearn.svm import SVC # SVM
from sklearn.naive_bayse import MultinominalNB, GaussianNB, BernoulliNB # ナイーブベイズ

# scipy
from scipy import stats

# XGBoost
import xgboost as xgb
from xgboost import XGBClassifier

# その他
from math import sqrt

EDA

とりあえず回帰問題と同じ

前処理

とりあえず回帰問題と同じ

ベースモデル

比較対象とする最初のモデル。とりあえずXGBoostで。

xgboost = XGBClassifier(random_state=1)
history = xgboost_base.fit(X_train, y_train)
y_pred_base = xgboos.predict(X_train)

モデル性能評価

# ロジスティック回帰
LR = LogisticRegression(random_state=1)
LR.fit(X_train,y_train)

# ランダムフォレスト
RF = RandomForestClassifier(random_state=1)
param_grid = [{
    'n_estimators':[5, 10, 50, 100],
    'min_samples_split': [2,5,10],
    'bootstrap':['Auto','sqrt'],
    'min_samples_leaf': [1,5,10],
    'max_depth':[10,50,90],
    'max_features':['auto','sqrt'],
    'random_state':[1]
}]
RF_CV = GridSearchCV(estimator = RF,param_grid = param_grid, cv = 5)
RF_CV.fit(X_train,y_train)

# サポートベクターマシン
SV = SVC(random_state=42)
param_grid = [{
    'C': [0.1, 1, 10],
    'gamma': [.01,0.1,1],
    'kernel':['rbf', 'poly', 'linear', 'sigmoid'],
}]
SV_CV = GridSearchCV(estimator = SV,param_grid = param_grid, cv = 5)
SV_CV.fit(X_train,y_train)

# ナイーブベイズ
NB = GaussianNB()
NB.fit(X_train, y_train)
NB_pred_test = NB.predict(X_test)

##  XGBoost 
xgboost = XGBClassifier(random_state=42)
param_grid = [{
    'n_estimators':[100,300,500],
    'max_depth':[6,10],
}]
xgboost_CV = GridSearchCV(estimator = xgboost,param_grid = param_grid, cv = 5)
xgboost_CV.fit(X_train,y_train)

アンサンブル学習

多数決

各分類器の予測を集め、多数決で決まったクラスを全体の予測とする

from sklearn.ensemble import VotingClassifier

log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC()

voting_clf = VotingClassifier(
estimators=[(’lr’, log_clf), (’rf’, rnd_clf), (’svc’, svm_clf)],
voting=’hard’)
voting_clf.fit(X_train, y_train)

バギング

すべての分類器で同じ訓練アルゴリズムを使いつつ、訓練セットから無作為に別々のサブセットをサンプリングして訓練する

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), 
    n_estimators=500,
    max_samples=100, 
    bootstrap=True, 
    n_jobs=-1,
    oob_score=True,
)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

ブースティング

ベースの分類器を訓練し、訓練セットを対象として予測のために使う。 そして、分類に失敗した訓練インスタンスの相対的な重みを上げる。 次に、更新された重みを使って第2 の分類器を訓練し、予測に使って、重みを更新す る。これを繰り返していく

from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120)
gbrt.fit(X_train, y_train)
errors = [mean_squared_error(y_val, y_pred)
for y_pred in gbrt.staged_predict(X_val)]
bst_n_estimators = np.argmin(errors)
gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators)
gbrt_best.fit(X_train, y_train)

スタッキング

1段階目は5つの手法(Random Forest, Extra Tree, AdaBoost, Gradient Boost, SVM)で評価 → それぞれの手法で特徴量の重要度が異なることが望ましい 2段階目は1段階目の手法の予測結果を入力として評価(XGBoost) www.kaggle.com

評価指標

from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score, roc_curve

# 混同行列
confusion_matrix(y_test, y_pred_test)
# 正解率(accuracy = (TP+TN) / (TP + TN + FP + FN)
accuracy_score(y_test, y_pred_test)
# 適合率(precision = TP / (TP + FP)
precision_score(y_test, y_pred_test)
# 再現率(recall = TP / (TP + FN)
recall_score(y_test, y_pred_test)
# F値 =2 * (適合率 * 再現率) / (適合率 + 再現率)
f1_score(y_test, y_pred_test)

# ROC曲線、AUC(Area Under Curve: ROC曲線の下側の面積。0以上1以下で1に近づくほど精度がよい)
# 横軸にFP(偽陽性率: 0を誤って1と予測してしまう確率)、縦軸にTP(真陽性率)をとる。予測確率に対してどこから陽性にするかという閾値を1から下げていった時の変化
roc_curve()
probas = model.predict_proba(X_train)
fp, tp, th = roc_curve(y_train, probas[:, 1])
auc_score = roc_auc_score(y, probas[:, 1])

fig, ax = plg.subplot()
ax.step(fp, tp)
plt.show()
 ```