2024年9月29日日曜日

IkaLogの開発で得た知見: ニューラルネットワークを用いたブキ分類器の思想と実装

以前IkaLogの開発で得た知見:大量の動画や画像を取り扱う際の Tips にて、 IkaLog が使用していたブキ認識において、いかに実ユーザ環境からの送信データを活用しながらいかに効率的に教師データを揃えることができたかを紹介しました。

最近は LLM や生成 AI などが話題をもっていきがちですが、独自のモデルを構築してアプリケーションに組み込もうというアイデアで取り組まれている方も増えているのではないでしょうか。

私自身が取り組んだのは 2015-2017年頭の話なので昔話でしかないとはいえ、もしかしたら新規アプリケーションを作られる方の参考になるかもしれない?と思ったので、当時どのような思考で作業をしていたか、もう少しまとめてみる事にしました。

今回は、 IkaLog のブキ認識において、最終的にシンプルなニューラルネットワークに行きついたかについて解説します。

バックグラウンド: IkaLog で目指した性能と、当時のハードウェア制約

精度99%では満足できなかった理由

スプラトゥーンでは、合計8名のプレイヤーがふたつのチームに分かれて勝敗を競います。このときひとりひとりのプレイヤーが100クラス以上のブキのなかからひとつ選んで勝負に挑みます。


ブキにはそれぞれシューター/ローラー/ブラスターなどのクラスの特徴があるほか、攻撃力や距離、連射力で差別化がされています。また、ブキには戦局を有利に進めたり、不利な場面を打開するために活用できるサブウェポンやスペシャルウェポンが用意されています。

IkaLog で処理される勝敗データでは、各プレイヤーがどのメインブキを使用しているか、またサブウェポンやスペシャルウェポンが何であるかを正しく記録することが、戦績データを扱うためのしくみとして重要だと考えていました。

※ この統計自体には IkaLog は使われていません


先述のとおり、一回の対戦において8名のプレイヤーがゲームに参加します。これは 99% の分類器があったとしても、対戦で使われたブキすべてが正しく分類できる確率は 0.99^8 = 92.2% にしかなりません。このため IkaLog ではブキ分類器で 99.9% 以上の精度が必要であろうと考えていました。

スプラトゥーンのリザルト画面からのブキ認識は今のAIの仕組みから考えれば、とても簡単な仕組みです。画面の同じ場所に同じ画像が表示されるだけ。ただ、バグありのビデオキャプチャデバイス、HDMIでさりげなく入るノイズ、ユーザーのセットアップなどの理由で、ソフトウェア観点からみると「同じ画像が毎回表示される」前提ではブキ分類器を実現できませんでした。


当時のハードウェア事情

また、 IkaLog を開発していた 2015 年頃はまだゲーミングPCらしくゲーミングPCを一般的なプレイヤー層は持っていることは想定できませんでした。

ましてや Wii U でスプラトゥーンをプレイしているユーザがとなりに AI で十分な性能を発揮できるようなハードウェアを持っておらず、ゲームをしている人が最新CPU搭載のコンピュータを所有しているかも怪しいです(今でも多くのスプラトゥーンプレイヤーでも余裕のあるPCを持っておらず、むしろスマホのほうが性能が高い可能性すらあるでしょう)。当時の事情でいえば、x86 CPUでの演算において SSE4.2 程度の命令セットしか期待できなかった時期での取り組みでした。私自身が認識できていたユーザには Core 2 Duo で IkaLog を常用している方もいらっしゃいました。

IkaLog がリアルタイムで画像認識をするというコンセプトであったため、当時の記録によると、8キャラクタ分のブキ分類を含めてリザルト画面の解析をおよそ3秒以内で実現できることをひとつの目安としていたようです。


IkaLogの開発においてたどった当初の分類器のしくみや遷移

K近傍法

IkaLog では、当初は OpenCV の K近傍法 の実装を用いて、入力画像に対してもっとも特徴量ベクトルが近い近傍があるクラスに分類するアプローチで実装していました。

開発開始当初はブキ数が30程度だったかと思いますが、アップデート終了までにクラス数が100を超えるほどに増えました。結果論ですが、単純なテンプレートマッチングやSVMなどといったアプローチをとっていたら、実行時間の観点から、より苦労していたと思います(スプラトゥーン2向けの実装では数字などのキャラクタ認識でSVMを利用することも検討し取り組みましたが、このワークロードではKNNほどコスパよくありませんでした)。

KNN の実装は当時の CPU でアイコン程度を分類するのは十分に高速だったほか、分類器の訓練も高速でしたので、開発初期からこのアプローチにたどり着いていたことはとても助かりました。データ量が少ない状態から半自動的に訓練データを集めようとしたときに、いまでもとりあえず使うことが多いです。


色相のヒストグラム

ブキの分類器を作り始めた当初、最初に実装したものは色相のヒストグラムからパターンを見つけて分類が可能かどうかを試していました。


ただ、スプラトゥーンのリザルト画面では、所属チームによりブキ画像の背景にチーム色が映り込み、場合によってはプレイヤーが選んだ装備品もブキに重なります。プレイヤーのキャプチャーデバイスの設定などにも依存することもあり、あまり実用的な精度は出せませんでした。

ラプラシアンフィルタ

輪郭抽出は古典的な画像の分類アプローチでよく使われる手法の一つかと思います。IkaLogでもラプラシアンフィルタを適用し、カラー画像から次元数を削減した特徴量を生成し、ここから分類を行う方法を試していました。一時期のバージョンの IkaLog では実際にこのアプローチで提供していたと思います。

IkaLogのブキ分類のワークロードにおいてラプラシアンフィルタはチームの背景色の影響を排除し、ブキの形状に基づいて分類するには良い方法に思えます。しかし、ユーザの映像キャプチャ環境において元画像が 720/1080p だったり、それを480pにリスケールした映像が投入されたりといったかたちで想定外の入力がされると精度を保てないという問題が生じました。

最終的なチャレンジはスプラトゥーンのアップデートそのものでも発生しました。スプラトゥーンではメインブキに対して、サブウェポン・スペシャルウェポン違いのバージョンとして「カスタム」「コラボ」といった亜種が登場します。これらの亜種ではブキ画像の右下に小さな追加マークが表示されるのですが、ラプラシアンフィルタを介して色相情報を落とした状態でこれらの特徴を合理的に見分けることはできませんでした。



ニューラルネットワーク導入の決断

IkaLog を作り始めて自然と機械学習的アプローチに関わるようになっていたことから、 Cousera の Andrew Ng 先生のコースなども一通り修了していた頃に「もうニューラルネットワークにカラー画像をそのまま入力したほうがいいんじゃないか」考えるようになってきていました。

とはいえ、AlexNet などの既存のニューラルネットワークは100MB以上の重みデータがありますが、さすがにこれは過剰ですし、ユーザがそんなもので推論できるようなプロセッサを持っていませんし、CUDAをユーザのプロセッサで実行できるわけでもありません。このため IkaLog で目的に合わせたニューラルネットワークを実装することを考え始めました。

IkaLog のブキ分類においてニューラルネットワークに画像をそのまま入力することによる一つのメリットは、ニューラルネットワークであれば背景色などを無視できることがあります。チームカラーによって何色になるかわからないようなピクセルに基づく入力値は、結果的に無視されるようになります。ブキの形状によって適切な重みが自動的に形成されることを想定できたので、おそらく簡単にうまくいくだろうと思いました。


HSV色空間

RGBとHSVでしっかり比較したわけではないのですが、ブキ分類器での分類対象ではHSV色空間で取り扱ったほうがよいだろうと判断したので、何も考えずにHSV色空間を特徴量として使用しています。

HSV色空間を利用しようと思った最大の理由は、ブキの「カスタム」「コラボ」といった亜種の特徴を表現する色相がピクセルあたりひとつのパラメータで表現されることになるので、おそらくRGB色空間で扱われるよりもいいだろう、ぐらいにしか考えていませんでした。背景の色相を無視するという観点でも重みが小さくなることで簡単に表現できるでしょう。ここについては「こうなってくれたらいいな」という思想でしかなくて、現時点でもそう思っているだけで、これによる差があったかどうかは何も検証していません。


ネットワーク構成をシンプルに

使用するレイヤとしては単純な全結合とReLUに制約することにしました。理由はふたつあり、一つ目は計算量、二つ目は再実装のしやすさです。

計算量の観点では、畳み込みフィルタなども考えましたが、当時 MacBook Pro (2014) とその上の GeForce チップ、また Haswell Refresh プロセッサで走るニューラルネットワークの速度を見ていると、 CNN やプーリングをエンドユーザのプロセッサで実行させることは現実的ではないだろうと感じていました。単純な全結合と ReLU 程度であれば、当時で型落ちとなっていたプロセッサ上でも NumPy やその下位のライブラリが現実的なスピードで動いてくれるだろうと期待しました。

再実装の観点では IkaLog に取り組んでいた当時でニューラルネットワークを動かそうと思うと、 Caffe を使うとか、もしくは Chainer を使うとか、そういったいくつかのフレームワークを利用する方法でした。 ONNX ランタイムみたいなものはまだ出てきておらず、想定するユーザ層が中学生・高校生・大学生や社会人で、主に Windows ユーザであろうことを考えると、既存のフレームワークを IkaLog のためにセットアップさせるのは不可能でしょう。

IkaLog は zip ファイルを展開して実行すれば使える状態の配布形態を維持することを心がけていたので、ブキの分類器のためにフレームワークへ依存を追加することにためらいました。このため、シンプルなネットワーク構成とすることで、IkaLog用に推論コードを作成するコストを最低限に抑えることにしました。


実装を進める前の事前確認を Azure Machine Learning で実行

それまでの取り組みである程度のデータ量は確保できていたので、まずは手元のデータセットを用いて最低限の作業でアプローチを検証するため、 Azure Machine Learning に想定する特徴量をアップロードして、 MLPで期待するようなモデルが実現するのかを確認しました。

Azure ML はこの程度のワークロードであればファイルをアップロードしポチポチするだけでいいですし、 confusion matrix などもさくっと出してくれるので、アプローチ上問題がないことを簡単に確認できましたしコードを具体的に書く前に最低限の労力で検証できたことはとても助かりました。なおこの Azure ML の体験談は 2016 年頃当時の話であることに留意してください。





学習済みモデルのインポート、推論

実際の学習は Chainer と GeForce GTX 1080 (後半は Tesla P100)で行いました。そもそもどれぐらいのノード数で性能が飽和するかを Chainer 上で検証し、ネットワークの隠れ層のサイズを決めました。

本番の学習は 24 時間などのオーダーで1000エポック以上回したような覚えがあります。 Chainer のチェックポイントとして得られたものをいくつか評価して使用するモデルをきめました。

Chainer フレームワークからネットワークの重み・バイアスを取り出して、単純な NumPy コード上で推論できることを確認できたので、モデルをただのマトリックスとして pickle してファイルに保存、そこからモデルを復元・推論することで、機械学習フレームワークへの依存を断ち切りました。

実際に生成できた実行用モデルファイルをみてみると15MBほどになっていました。この中には32ビット浮動小数点数が並んでおり、zipなどでの圧縮効果がほとんどありません。配布ファイルが大きくなることを嫌って、pickcleする際に16ビット浮動小数点数として扱うことによってファイルサイズを半分に抑えることにしました。このワークロードとモデルにおいて浮動小数点数のビット数を抑えても実用上の影響はほとんど感じられません。最終的に戦績共有サイト stat.ink に投稿されるブキ画像を 99.99% で分類できる精度が得られました。


推論の実装

先述の理由で、 IkaLog ではフルスクラッチかつ最低限のコード量で推論を再実装しました。ここで実装した内容は、のちに発売されるオライリーの「ゼロから作る Deep Learning」の最初の100ページで解説された内容そのものともいえるかと思います。

このブキ分類器は、すでに型落ちとなっていた IvyBridge 2.0 の MacBook Air でも 0.02 秒で実行できました。Core 2 Duo などでも十分な速度で動きましたし、 PYNQ (ARMコア搭載 FPGA)でも1回あたり200ms未満の実行速度で収まりました。

FPGAならPLで実行すりゃもっといけるだろとかそういうツッコミはいくらでも可能かと思いますが、技術的には可能ですが、非営利の独りプロジェクトでここまでやれば十分かなと思っています。


おまけ

当時やってみたかったこと

ネットワークの蒸留や枝切りをしてより配布ファイルのサイズを小さくできるのではないかと考えていましたが、着手しませんでした。

単に手が回っていなかったほか、ネットワークの規模が小さくなったときに、どのクラスにも該当しない画像を特定のクラスに分類してしまう可能性などを恐れていたように思います(実際取り組んだらどうなったかはわかっていません)。


最近のエコシステムに思うこと

ここまでの内容を2017年以前に取り組んだ後、こんなフレームワーク便利だなと思ったものが幾つかでてきました(現時点の選択肢として筋がいいと言いたいわけではありません)。

  • ONNX Runtime の登場によりホストプログラムが雑に使える推論ライブラリが出てきたという印象
  • Intel が OpenVINO や Movidius VPU を出してきて、 Windows PC でハードウェア支援が期待できるようになった。最悪 AMD CPU でも SSE 相当で動くっぽい
  • OpenCV に DNN に対する推論機能が強化されており、配布方法を工夫すれば GPU アクセラレーションなどをホストプログラムから呼び出せるかもしれない。

さらに、2017年の iPhone X から Neural Engine が搭載され、 Android にも同じように推論エンジンがハードウェアとして搭載されるようになりました。 Mac であれば M1 から、 Windows でも Copilot PC が出てきました。

ようやく OS レベルの推論の抽象化が進んできた

ライブラリの観点では、Windows であれば DirectML 、 Apple であれば CoreML 、Androidであれば MLKit などが普通に使えるようになってきて、私が IkaLog を作るときに困った「推論のための仕組みがない。ターゲットごとに実装してられない」という状況に大きな変化が起きているように感じています。特に個人的には DirectML は(ごくたまにしか試してませんが) Windows 環境において OS が推論ワークロードをハードウェアで実行してくれるという、まさに OS らしい抽象化をしてくれるようになりました。

私自身はふだんほとんどプログラムを書かないのですが、ローカルで推論をするアプリケーションの開発難易度は10年前から比べると大きく下がってきたなと感じています。

ローカル LLM の話題などもみかけますが、 Copilot PC の話題などをみつつ 2024 年は、ローカルで推論をするタイプのアプリケーションの開発が加速する年になるだろうなと思っています。2025 年になるとウイルス対策ソフトすら推論用アクセラレータにオフロードするような世界がくるのかもしれませんね。

2024年3月9日土曜日

BOSE SoundBar 900 のメンテナンスと覚え書き

暫く iOS アプリなどから連携不可能な状態になっていた BOSE SoundBar 900 を復旧したので覚え書き。まとまった情報がなかったため理解するまでにかなりの時間を要しました……。


問題

私が対応した個体では下記の問題がありました。

  • ルータの設定上の問題か、 BOSE 社サイトへの通信が通らずアクティベーションできない (iOSアプリにて連携できない)
    • ネットワーク構成の問題かもしれないが、一般的な IPv4 通信はできる環境のはずで、なぜうまくいかないのかわからない
  • アクティベーションされた際、最新ファームウェアへのアップデートが始まるが、完了しない
これに対しての対応方針をかきのように定めました。
  • シンプルなネットワーク上にサウンドバー本体を有線LAN接続しアクティベーションを完了させる
    • 現地のネットワーク構成に依存した問題が発生しているように見える
    • ファクトリーリセット状態からのアクティベーションは LTE ルータをゲートウェイとしたシンプルなネットワークを使用する
  • ファームウェアアップデートをアクティベーション前に済ませる
    • 600MB近いファームウェアのダウンロードを繰り返しさせたくない(遅い)
    • 確実にファームウェアを更新完了した状態でアクティエーションすることで失敗要素を減らす

この問題を解決するための手順は、最終的に、下記のとおりになりました。
  • DHCPでアドレス取得ができるネットワークに有線で接続
  • ファクトリーリセットのため Bluetooth + 早送り> ボタンを長押しする
  • BOSE社サイトからファームウェアファイル product_update.zip をダウンロードする
  • SoundBar 900 が掴んでいる IP アドレスを見つける
    • 今回はルータの DHCP リース状況からIPアドレスを特定
    • BOSE社サイトで案内されているMACの prefix がアテにならない
  • http://x.x.x.x:17008/update.html にアクセスする
  • 表示されたファームウェアアップロード画面にて product_update.zip をアップロード
    • ファームウェアアップロード画面をリロードしてファームウェアバージョンが上がっても我慢
    • 白いダッシュ(−)LEDが点滅しているうちはFWアップデートが続いているので1~2時間かかるつもりで放置
    • サウンドバー本体からファクトリーリセット時の起動音がしたらアップデート完了
  • DHCPでアドレス取得ができ、素直に通信できるインターネット環境に接続
    • 今回はLTE無線&有線ルータを上流として使用
    • SoundBar 900 を同ルータに接続して有線 DHCP でアドレス取得した状態とする
    • iOS端末を同ルータのL2セグメントに接続
  • iOS端末にて SoundBar 900 を登録、アクティベート
  • 本来のネットワークにイーサネットで接続し DHCP でアドレスを取得しなおす
    • 有線 -> 有線であれば WiFi のパラメータが絡まず、上流を置き換えても問題がおきない模様
普通の人に「これやりなよ」とカジュアルに言えるかというと、かなり微妙です。
知人が同じ問題に悩んでいても、面倒なので、教えてあげたり手伝ってあげたりする気には、正直なりません。
このようにファームウェアの挙動が常に判りづらく、デジタルガジェット的観点な感想として、かなり低めの評価になります。


ファームウェアアップデートについて

事前にファームウェアをアップデートする理由は、アクティベート成功後、自動的にファームウェアアップデートが開始されますが、iOS上でファームウェアアップデートが開始された後に失敗しても何も理由が表示されません。アップデートは一度で30分弱かかると書かれていますが、ファイルの転送段階でアボートしていても把握できないようです。このため手動でファームウェアアップデートを済ませて解決します。

LTE経由でファームウェアアップデートが走ったとしても、ここでダウンロードされるファイルが 600MB ほどになり、光回線などがあれば、LTEで転送することは合理的ではありません。PC等でファームウェアを事前ダウンロードしておけばリトライも簡単ですし、試行あたりの待ち時間や総転送量も減るでしょう。

実際にファームウェア更新ページで試してみるとわかるのですが、ファームウェアアップロード時、 SoundBar 900 のフラッシュストレージ上に過去の失敗ファイルが残っていることがあるようで、その場合にはファームウェア更新のワークエリアが十分にとれずに失敗することがあるようです。この問題を回避するためには Bluetooth ボタン + 早送り> ボタンを同時長押しして、あらかじめファクトリーリセットしておくと、ワークエリアがリセットされるのか、空き容量不足になるエラーが解消できました。

いちどファームウェアが更新できれば、LTE回線を上流としたネットワーク環境でも、アクティベート後の自動的なファームウェアアップデートも発生せず、期待通りデバイスが制御できるようになりました。

最近のファームウェアでは HDMI で接続されたソースが起動したときにこちらに入力を切り替えるような変更が入っているようですね。何かの拍子に入力ソースが切り替わった後に、テレビ等からの音が聞こえずモヤモヤするということは減るのかもしれません。(音が鳴らないと HDMI のリンクが落ちているのか、単に入力ソースの問題なのかという二択が頭によぎる時点でかなりUXが低い印象でしたが、これが改善しているかもしれません)


中身は Android なのね

サービス用の Type-C コネクタを介して疑似イーサネット?接続することも可能なようですが、手元ではすぐにイーサネットとして認識されなかったので、切り分けも面倒なので早々に諦めて有線ネットワーク経由でアクセスしました(このアップロードページは、これはこれでセキュリティ的にどうなんだという作りですが)。

Type-Cで接続したところ Android デバイスとして認識されたので、これ中身の OS は Android なんですね。ファームウェアファイルが600MBもあったのに驚きましたが、なんか納得というかんじです。 Raspberry Pi にのっているような STB 用のプロセッサ等で制御しているんでしょうね。


HDMIのリンク落ち問題

経験してみて判ったのですが BOSE SoundBar シリーズは HDMI で接続中に HDMI のリンクが落ちるとコールドスタート(電源ケーブル抜き差し)まで復旧しないようで、ネットで調べるとフラストレーションを感じている人が日本語でも他の言語でもいっぱいいます。私が弄った個体でもこの問題は発生していて、以下の対策をしています。

1)Ultra High Speed HDMI 認証のケーブルに交換で接続
2)簡単にリセットできるように電源ケーブルにスイッチを増設 :-(
3)リンクモードを拡張から標準にダウンブレード


Ultra High Speed HDMI 認証のケーブルに交換で接続

Ultra High Speed HDMI は、 8K 対応として売られているケーブルです。メーカーがズルをしていなければ、このロゴを付けられるケーブルは 8K のデータが転送できることを確認している(よりテクニカルには 48Gbps のデータ伝送で問題ないことが確認された製品)ということになります。Ultra High Speed HDMI はいまのところ HDMI シリーズでいちばんノイズ耐性をもとめる規格です。1m程度のケーブルは銅線などで単に接続されているパッシブタイプのケーブルが一般的ですが、高ノイズ耐性なケーブルであれば迂闊な HDMI の信号ロックはずれに頻度も減るだろうという狙いです(自分で HDMI 信号を扱ってきた際の体験談に基づいてのロジックです)。実際、体感的には、コレでかなり音が消える頻度が下がった気がします。

SoundBar 900 は eARC の製品ですが、どうせオーディオしか伝送しないだろうに、高いビットクロックでソースとシンクが HDMI 信号をロックしてて、ちょっとしたノイズで簡単に HDMI 信号のロックが外れると電源入れ直しまで復旧しないとか、そんな感じの挙動に見えます。ネット上のクレームを見る限り、おそらく付属のケーブルでも同期がとれなくなるケース多いのでしょうが、私みたいに古いケーブルなどをうっかり使い回すような人は、かなり痛い目にあうのでしょう。

簡単にリセットできるように電源ケーブルにスイッチを増設 :-(

HDMI信号ロックが外れたときのために SoundBar 900 の通電用ケーブルは簡単にリセットできるように電源スイッチを自分で追加する加工した電源ケーブルに置き換えました。


電源タップ側で簡単に電源オンオフできればそれでいいのですが、設置先の電源タップがアウトレットごとの独立制御に対応していなかったので、ケーブルを加工しました。電源ケーブルを抜き差しする手間がないだけでもかなりストレス軽減になるでしょうです。

リンクモードを拡張から標準にダウンブレード

テレビ側の設定を見ていたら HDMI ソース側(今回は SONY BRAVIA)の外部入力設定において HDMI リンクモードを拡張から標準に変更しました。HDMI のリンクモードを拡張・標準で選べるようなので、これを変更したときに具体的に何がおきるかは判っていないのですが(テレビ側もスピーカー側も何も教えてくれません)、これで HDMI のビットクロックを落とせたら安定性が増すかなと期待して標準モードに変更しました。結果として、この設定が安定性面で一番効果があったようです。


追記
BRAVIAのリンクモードを標準に落とした場合に何がおきるかわかっていないのですが、仮にリンクモードが落ちてARCにダウングレードされると、 5.1ch 圧縮までのサポートとなり 5.1 非圧縮や7.1のフォーマットを伝送できなくなるようです。
今回の設置場所では非圧縮5.1chなどのコンテンツに用はなさそうなのでARCにダウングレードされても十分でしたが、7.1期待で買っている人はどうしたらいいんでしょうね...