プログラム紹介

## 軽い導入
　今回、初めて機械学習に触れて、初めての画像分類から、物体検出モドキまでいろいろとやったので、そこら辺を共有しとこうかと思います。
　何分、こういった記事を書くのが初めてなもんで、ちょっと下手くそになって申し訳ないのですが、しばらくは温かい目で見てもらえればと思います。
 ## 使ったもの
  本機に搭載したカメラは OpenMVH7R1,2
  実験用で使用したのが raspberry Pi4 Model B RAM:4GBモデル
## 苦労した点
　しばしばX(旧Twitter)でボヤいていたように、何回やってもOpenMVのH7でtfliteのテンソルの割り当てに失敗するので、それを解決するのにかなり苦労しました。で、まぁ結局はどれだけファイルサイズが小さくても内部がすべてfloat32だから全部読み込む前にメモリが底をつくと、だから結局は演算するときのデータ型をInt8にしてあげないといけないし、そうやって量子化すると精度がかなり落ちるので、それなりに精度が高いモデルを作る必要があるよねっていうので最終的に解決することはできました。
## 画像分類
　正直ここに関してはもうええやろ、という気がしますが、一応書いておきます。
 　画像分類にはTensorflowのkerasを使用し、多値分類モデルを作成しました。
  以下レイヤー
  ```diff_python
#classification.py
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D,InputLayer,Flatten,Dropout
F_filter=16
model=Sequential([
    InputLayer(60,80,3),
    Conv2D(filters=F_filter,kernel_size=(3,3),padding="same",activation="relu"),
    MaxPooling2D(pool_size=(5,5)),
    Conv2D(filters=2*F_filter,kernel_size=(3,3),padding="same",activation="relu"),
    MaxPooling2D(pool_size=(4,4)),
    Conv2D(filters=4*F_filter,kernel_size=(3,3),padding="same",activation="relu"),
    Dense(4*F_filter,activation="relu"),
    Flatten(),
    Dropout(rate=0.25),
    Dense(num_classes,activation="softmax")
])
  ```
このモデルを実行するとこのようになります
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZF8ZRrbQie8?si=UKT4G5ivf0u70pEV" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

　ちょっとした工夫的なものとしては、H,S,Uの画像を学習させるとき、バッチサイズを3にし、ちょうどH,S,Uの画像がすべて1バッチに来るようにし、精度の向上を図りましたが、正直よくわかりません。
## 物体検出モドキ
　これ自体はかなり最近作ったものなので、もしかしたらX(旧Twitter)で見てる人もいるかもしれません。
　仕組みは、Edge ImpulseのFOMOモデルを参考にしました。
　簡単に説明すると、画像を特定の大きさのグリッドで分けて、その中に何があるのかを分類して、それから作成されたヒートマップをもとに、ボックスを追加するという仕組みになっています。
　多分回帰させたらできるんですが、出力層を[[cls,width,height,x,y],...]という可変的な形にする方法がわからなかったので、いったんヒートマップを生成するとこまでができるモデルを作成しました。
以下レイヤー
```diff_python
#detection.py
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense,Conv2D,Dropout,MaxPooling2D
F_filter=3
model=Sequential([
    InputLayer(60,80,3),
    Conv2D(filters=2*F_filter,kernel_size=(10,10),padding="same",activation="leaky_relu"),
    Conv2D(filters=2*F_filter,kernel_size=(10,10),padding="same",activation="leaky_relu"),
    MaxPooling2D(pool_size=(2,2)),
    Dense(4*F_filter,activation="leaky_relu"),
    Conv2D(filters=4*F_filter,kernel_size=(5,5),padding="same",activation="leaky_relu"),
    Conv2D(filters=6*F_filter,kernel_size=(5,5),padding="same",activation="leaky_relu"),
    MaxPooling2D(pool_size=(2,2)),
    Dense(8*F_filter,activation="leaky_relu"),
    Conv2D(filters=8*F_filter,kernel_size=(3,3),padding="same",activation="leaky_relu"),
    Conv2D(filters=16*F_filter,kernel_size=(3,3),padding="same",activation="leaky_relu"),
    Dense(num_classes*16,activation="leaky_relu"),
    Dropout(rate=0.25),
    Dense(num_classes,activation="softmax"),
])
```
動画<iframe width="560" height="315" src="https://www.youtube.com/embed/Hs52b8PgUOE?si=IWiYTzwCtQTLp1Vq" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
　この場合は、出力層は(15,20,4)の三階テンソルとなり、axis=2の4つのインデックスのうちの3つがH,S,Uとなり、ヒートマップから検出を行うときは、それぞれ別で取り出す必要があります。
例:Hのヒートマップ(白に近いほど、Hである信頼値が高い)
![heatmap.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049420/96e55cf2-7d0c-4b4f-89fb-e715375cca53.jpeg)
一応重ねるとこうなります。
![heatmap.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049420/0eae7f34-86c7-4ca8-aa68-7af409d7c9ef.jpeg)
ちょっと見にくいけど、しっかりHの端は信頼値が低くて真ん中のほうほどしっかり検出できています。
　ちなみに、活性化関数がLeaky ReLUになっているのは、普通のReLU関数は負の値を考えないことで、学習中のニューロンの死が起こります。実際このモデルでも、ニューロンの死が発生し、通常のReLU関数を使用すると、テストデータの正解率が8割ほどまで下がり、損失率も1を超えるようになってしまいました。GeLU関数など、負の値をある程度考えるものもありますが、一番単純な構造で負の値まで扱えるので、Leaky ReLUを採用しました。
　あと、softmaxによる多値分類の後にさらに畳み込みを行うことで、変な検出(背景から間違えて検出してしまう)ことを防げるのでは？と考え、やってみたところ、テストデータの正解率が、0.7760から一切動かず断念しました。
## アノテーションの効率化
　上の物体検出モドキを思いつく前、YOLO使ったら物体検出できるじゃん、と思ってUltralyticsをインストールして学習させたところ、「Int8量子化済みで2.63MB！う～んヒープがそもそも足りん！」ってなわけでテンソルの割り当て以前にそもそもファイルを読めないってことになるわけですね。
　でも、せっかく作ったので、これをどうしようかと悩んだ末に思いついたのが、「推論結果からさらに学習データとれるからそれで強化学習すればええやん。」というなんか最近のAIがやってそうなことを手動でやろうってなわけです。
　Ultralytics自体は非常に簡単なAPIで使えるため、やり方さえわかれば心配になってくるくらいにすぐできます。
```diff_python
from ultralytics import YOLO
model=YOLO("yolo11n.yaml")
model=YOLO("yolo11n.pt")
model=YOLO("yolo11n.yaml").load("yolo11n.pt")
#.loadの引数に自作済みの.ptファイルのパスを渡すことで、再学習が可能
model.predict("coco.yaml",epochs=3,imgsz=640)
model.export(format="onnx") #"tflite"と記述して.tfliteを出力することも可能
```
本当にこれだけで学習が終わるんです！あら簡単。
詳しい内容は[ultralytics公式ドキュメント](https://docs.ultralytics.com/ja "Ultralytics Docs")を見てください。
　話を戻して、まぁこれで作ったやつはOpenMVじゃ動かんわけです。それなら物体検出モドキを学習させるときの学習データのために使おう！ってので機械学習をするための機械学習としてこのYOLOを採用しました。
↓実際にYOLOを使って行った推論<iframe width="560" height="315" src="https://www.youtube.com/embed/2IBoHptmt7U?si=ew1oARGn5I151RmJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
　手動でアノテーションをするとなったら、さすがに学習データだけで１万枚もあるのにそれを一つ一つ枠で囲ってラベルを付けてってなると途方もない時間がかかるのは当たり前です。
　それなら、確認しながらYOLOで物体検出をしてデータを取りつつYOLO用のデータもとるというのが賢明でしょう。
　こうすることで、おそらく数時間では済まないようなアノテーションという作業をH,S,Uが書かれたタブレットなり紙なりを置くか持つかして、あとはラズパイかPCのカメラに写るようなテキトーな位置でだしてあげれば、ほんの数分で1000枚くらいはアノテーション+画像のデータができるわけです。
　これのおかげであとは機械学習を待つだけになり、かなり効率化できました。
## 最後に
　今回は、機械学習のプログラムを紹介しましたが、今後もネタが尽きない限りこんな感じで紹介などを書いていこうかと思っています。
　それから、レイヤーなどをわかりやすくするためにいろいろとグラフでも作りたかったのですが、いろいろとごちゃごちゃになってわかりにくかったので諦めました。