カチャカで転移学習

こんにちは。Preferred Roboticsの村瀬です。

今回は、[今月(2024年4月)のソフトウェアアップデート](https://kachaka.zendesk.com/hc/ja/articles/9366730970383-%E3%82%AB%E3%83%81%E3%83%A3%E3%82%AB%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E3%81%AE%E3%81%8A%E7%9F%A5%E3%82%89%E3%81%9B-Ver-2-7-8)から利用可能な**転移学習**に関する記事です。


カチャカ体内では、深層学習技術を用いた認識器が動いており、様々な障害物や、人・充電器・カチャカシェルフなど、タスクをこなすのに必要な対象を、ロバストに(照明や背景の変化などに対し頑健に)認識することができます。

カチャカAPIでも、[Object Detectionのサンプル](https://github.com/pf-robotics/kachaka-api/blob/main/python/demos/get_object_detection.ipynb)を用意しており、現時点では、人・充電器・カチャカシェルフ・ドアの4クラス分の認識結果を使うことができます。

ただ、皆さんのご利用環境で、**もっと別のモノも認識させたい！** という声を頂くことが多く(例えば社員からも **カチャカが家のペットを認識できると嬉しい**という要望がありました)

一方で **「別の計算機を用意したくない、カチャカ体内の計算資源だけで動いてほしい」** **「実用的な速度で認識して」** **「認識器を作る手間はなるべく小さく」** という意見もセットで出ていました。


このような要望にお応えするのに、転移学習は相性が良いです。この後詳しく説明していきます。


※古典的な手法による認識で十分そうであれば、ぜひ前回の記事も参考にしてみて下さい。こちらの方が簡単に試すことができます。

https://qiita.com/KazutoMurase/items/de4fcf789a4767062350#%E7%89%B9%E5%BE%B4%E7%82%B9%E3%83%9E%E3%83%83%E3%83%81%E3%83%B3%E3%82%B0%E3%82%92%E4%BD%BF%E3%81%86

# なぜ転移学習なのか

カチャカのObject Detectionモデルの学習には、例に漏れず、大量のデータを年単位で集めたデータセットが使われています。新しいクラスを学習させる為に、同規模のデータを取得・アノテーションを行い再学習を行うのは大変です。その代わりに、学習済モデルの中間層の特徴量を抽出し、利用します。

**既に組み込まれた大量の知識を利用** することで、イチからやるよりも学習も推論の手間も少なくできる、というわけです。

学習済モデルはそのまま(重みに変化なし)で、抽出した特徴量を入力とした、新規の薄いニューラルネットワークを構築し、新しいクラスを推論できるよう学習させます(この方法が転移学習と呼ばれるものに属します)。

元のモデルに手を加えないため、人やシェルフなどそのまま認識できる能力をちゃんと残せて、また新規ネットワーク部分の層数は少なくできるので、学習に必要なデータ数、要学習時間はかなり小さく済みます。学習後、カチャカ体内に新規モデルを置き利用しても、新たなメモリ消費は非常に少なく、推論(認識)の速度も出せます(※)。

※画像を取得してから最終的な結果を得るまで、おおよそ元モデルの推論時間+新モデルの推論時間となるため、多少の遅延は発生します

![Screenshot from 2024-04-25 23-10-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/c1486247-53f4-5987-b63b-085ced1bdad7.png)


# カチャカにおける転移学習の概要

※転移学習をとにかく試したい！という方はこの章は読み飛ばしても構いません。

カチャカにおける転移学習をもう少し詳しく解説します。

カチャカのObject Detectionモデルは、[FCOS](https://arxiv.org/abs/1904.01355)がベースになっています。論文の詳細は省きますが、その出力は、特徴グリッド毎に、分類クラス別スコア / 物体の領域(Bounding Box)に変換できる4次元ベクトル / 物体の中心に近い程値が大きくなる"Centerness"と呼ばれるものの３種類となります。
また、前半部がFPN(Feature Pyramid Network)であり、各解像度毎の特徴マップから、2種類のHead(Classification HeadとRegression Head)に接続され、上述の最終出力につながります。

![Screenshot from 2024-04-25 23-54-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/09d30b0d-4c62-c51b-89de-b381bf959f13.png)

カチャカにおいては、FCOSの構造はそのままでなく、[NAS (Neural Architecture Search)](https://tech.preferred.jp/ja/blog/nas-semseg/)を利用し、限られた計算資源内で、精度を下げずに推論時間を極力短くできるネットワーク構造を探索したものを使っています。

一例として、論文では引用図のように、FPNから5層分の特徴マップが作られますが、カチャカではこれが3層分になっています。

転移学習用の中間層出力として、この**3層分のFeature Pyramid Networkの出力**がAPIで取得できます。

この出力を入力とした、新規クラス用の推論を行う小さなHeadを学習させます。今回サンプルとして用意したHeadの実装は[こちら](https://github.com/pf-robotics/kachaka-transfer-learning/blob/main/fcos_transfer/model.py)になります。スコア用のHead(コード上はTowerと名付けています)と物体領域用のHeadがあり、非常にシンプルで小さな構造になっているのがご覧頂けると思います。
また各層で重みは共有なので、学習パラメータ数は非常に少なくなっています。

このHeadを用いたモデル全体の実装は[こちら](https://github.com/pf-robotics/kachaka-transfer-learning/blob/main/fcos_transfer/predictor.py)になります。元のFCOSと同様に、スコア、物体の領域、CenternessをHeadが出力し、そこから最終スコア(元のスコアとCenternessを掛けた値)が高い推論結果を抽出する後処理が含まれています。

# 学習方法

ここからは実際に転移学習を行う方法について説明していきます。　

まず、[以前の記事](https://qiita.com/ksyundo/items/f10f9a6c704442bb3a10#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A8kachaka-api%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89) を参考に、Jupyter Notebookにサンプルコードとkachaka-apiのダウンロードまでを実施して下さい(※**2024年4月10日以前**にサンプルコードをダウンロードした場合は、最新のコードとkachaka-apiに更新する必要があります)。また、[Playgroundにsshでログインする](https://github.com/pf-robotics/kachaka-api#playground%E3%81%ABssh%E3%81%A7%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%99%E3%82%8B) を実施頂き、パソコンからカチャカにsshログインできる状態にして下さい。

次に学習させたい対象物を用意して下さい。

前回の記事で取り扱った古典的な手法だと認識が難しい、柔軟物かつ特徴点も出しづらい、紐をひとまず対象にしてみます。

![output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/88518ad9-cf32-a1ef-c694-a9439d61b3c5.jpeg)

### データ抽出

まずは中間層出力を取り出したデータセットを作成していきましょう。

Jupyter Notebookでkachaka-api/python/demos以下にある[save_object_detection_features.ipynb](https://github.com/pf-robotics/kachaka-api/blob/main/python/demos/save_object_detection_features.ipynb)を開いて下さい。

対象物をカチャカのフロントカメラの前に置き、上部メニューの「▶▶」をクリックして下さい。
以下のように撮影したカメラ画像が表示されます。

![Screenshot from 2024-04-26 10-59-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/61ee853c-bebd-5c5c-046a-8af39de63000.png)

また、demos以下に**data**ディレクトリが作られ、中身を覗くと"0.pkl" / "0.png"というファイルが存在することが確認できます。それぞれ中間層出力と画像ファイルに該当します。

どんどんデータを増やしていきましょう。
対象物の置き方や、カチャカの位置(あるいは背景)を変えるなどして、**最後のセルのみ**再度実行していきましょう(最後のセルをクリックした後、上部メニューの「▶」をクリック)。dataディレクトリ以下に、連番でファイルが保存されていきます。

![Screenshot from 2024-04-26 11-18-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/51b1dc36-cc47-1f9c-19f9-c44da7b485e9.png)

### アノテーション

ある程度データが集まったら、今度はアノテーションをしていきます(物体のクラスラベルと、領域の正解データを作成する作業です)。

アノテーションと転移学習自体は、お手元のPCで実施して頂く手順になっていますが、リッチなGPUが搭載されているものを用意して頂く必要はなく、手頃なスペックのCPUで十分学習させられます。pythonやdockerが動く環境は用意して頂く必要があります。

アノテーションツールは様々ありますが、サンプルとして[COCO annotator](https://github.com/jsbroks/coco-annotator)を使った手順を用意しています(※)。

※お好みのアノテーションツールに対応させて貰うことも難しくないと思います。

COCO annotatorはdockerで簡単に立ち上げられるようになっています。
```bash
git clone https://github.com/jsbroks/coco-annotator.git
# ubuntu 20.04標準のdockerバージョンで実施する場合: 最新dockerバージョンでは"docker compose up"
cd coco-annotator && sudo docker-compose up
```

ブラウザベースのツールになりますので、上記実行後、お好みのブラウザでアクセスして下さい(ポート被りが無ければデフォルト5000なので、http://localhost:5000/ でアクセスできると思います)。

適当なアカウントを作成し、

![Screenshot from 2024-04-26 11-43-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/3b4bed3f-6ac8-e419-74e2-ab8992a9bc1a.png)

ログインしたら"Create"でデータセットを作成します。今回は"travel_rope"というカテゴリを追加してみます(複数クラスの学習をさせたい場合は、その分だけ追加して下さい)。

![Screenshot from 2024-04-26 11-42-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/d4a920ed-0369-32a1-5556-d24a61580329.png)

![Screenshot from 2024-04-26 11-45-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/bac31e53-db5f-e533-6855-450954adc8e1.png)

以下のようにデータセットが作成され、先程ダウンロードしたcoco-annotatorディレクトリ以下に、"datasets/(データセット名)" というディレクトリが作られています。

![Screenshot from 2024-04-26 11-45-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/5ab79172-18a6-6a98-3704-1626dfe4ff0d.png)

このデータセット名ディレクトリ以下に、先程カチャカ体内で生成したデータをコピーします。

```bash
# 先程sudo実行した場合はディレクトリにコピーできる権限を与えておく
sudo chmod 777 coco-annotator/datasets/(データセット名)/
# コピー
scp -P 26500 kachaka@(カチャカのIPアドレス):kachaka-api/python/demos/data/* coco-annotator/datasets/(データセット名)/
```

コピーしたデータが反映されたら、順次アノテーションを実行して下さい(もしコピーしたデータがなかなか反映されない場合は、少し時間を置いてdockerコンテナを立ち上げ直すなど実施してみて下さい)

BBox Toolを使って以下のように領域を指定していきます。

![Screenshot from 2024-04-26 12-02-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/fcd658dc-6086-b372-731c-05e19c5ca06d.png)

アノテーションが終わったら、"Export COCO"で、対象カテゴリを指定し、jsonファイルを出力して下さい。(データセットディレクトリの.exports以下にファイルが生成され、適切にエクスポートできていると”annotations”のキーが存在します)

![Screenshot from 2024-04-26 12-16-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/3021c862-47b6-817c-f377-eb720bba377a.png)

### 学習

いよいよ学習を行う段階です。
まず学習用スクリプトなどを含んだ[カチャカ転移学習レポジトリ](https://github.com/pf-robotics/kachaka-transfer-learning)をお手元のPCにダウンロードし、環境を構築します。
```bash
git clone https://github.com/pf-robotics/kachaka-transfer-learning

# 必要なモジュールをインストール
cd kachaka-transfer-learning
python3 -m venv venv
source venv/bin/activate

pip install -r requirements.txt
```

学習を100エポック程度回してみましょう。私の手元PCのCPUはIntel Core-i7 11世代ですが、数十秒程度で終わります。
```bash
./train.py --dataset (coco-annotatorディレクトリへのパス)/datasets/(データセット名)/ --epoch 100
```

評価用スクリプトで、結果画像(カメラ画像にクラスラベルとBounding Box、スコアが重畳表示されたもの)を出力できます。本来はテストデータセットを用意するべきですが、ひとまず学習に使った画像をそのまま与えてみます。

```bash
./eval.py --checkpoint latest_checkpoint.pt --indir (coco-annotatorディレクトリへのパス)/datasets/(データセット名)/ --outdir output
```

とりあえず学習は出来ていそうなことが確認できました。

![Screenshot from 2024-04-26 13-20-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/b35bf21a-b7fc-c0df-9b04-1dc094270a8f.png)

# カチャカ体内で認識を実行

学習がうまくいったら、今度はカチャカ体内で動かしてみましょう。

以下を実行し、onnxファイルを生成します。
```bash
./create_onnx.py --checkpoint latest_checkpoint.pt --out predictor.onnx
```

生成されたonnxファイルをカチャカ体内のdemos以下にコピーします。
```
scp -P 26500 predictor.onnx kachaka@(カチャカのIPアドレス):kachaka-api/python/demos/
```


Jupyter Notebookに戻って[run_custom_object_detection.ipynb](https://github.com/pf-robotics/kachaka-api/blob/main/python/demos/run_custom_object_detection.ipynb)を開き、上部メニューの「▶▶」をクリックして下さい。
カチャカ体内で、作成した新規モデルを使って対象物の認識をしている様子を確認することができます。

![Screenshot from 2024-04-26 13-36-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551656/eb09dd1c-0f33-96a9-b566-3d1c476b5dba.png)

# まとめ

転移学習を使って、カチャカに新しい対象を認識させることが出来るようになりました。認識のロバスト性は、どういうデータを用意するかで決まってくるので、認識が安定しない場合は、色々環境や対象物の置き方を変えてデータを追加してみて下さい。

本記事では1クラスのみ追加しましたが、複数クラスの追加もサポートしています(coco-annotatorでカテゴリを追加しアノテーション、また各スクリプトで`--class-num`指定)。スクリプトは他にも様々なオプションを用意しています(`--help`で確認できます)。

モデル自体も、層を増やすなど色々カスタマイズの余地があると思います。

是非カチャカを賢くするのに役立てて、カチャカのタスクの幅を広げる一助になれば幸いです。
読んで下さりありがとうございました。