Translate

2022年4月26日火曜日

Raspberry Pi4B上でPyAudioを使っていたら、途中 OSError [Errorno -9981] で落ちる

 Raspberry Pi4B にUSBマイクを刺し、

マイク入力をPython で扱いたくて PyAudio で以下のようなコードを記述した。

 

import pyaudio

# PyAudio インスタンス化
audio = pyaudio.PyAudio()

# 引数情報に従って、PyAudio ストリーム生成・開始
stream = audio.open(
format=pyaudio.paInt16, # 振幅:ビット解像度
rate = 44100, # サンプリング周波数
channels = 1, # モノラル
input_device_index = 1, #USBインデックス1にUSBマイク
input = True, # マイク入力
frames_per_buffer=4096) # チャンクサイズ:1フレーム4096バイト
stream.start_stream()

# 指定秒数の音声をchunkサイズごとに取得し、配列framesへ追加
for _ in range(0, 3600): # 6分間読み込み
# チャンクサイズ分読み込み
buf = stream.read(4096)
:

:

# クローズ
stream.stop_stream()
stream.close()
audio.terminate()  


Windows10 上で実行すると問題なく動作するのだけど、

Raspberry Pi4B(8GBmem/bullseye64bit)で動かすと、

buf = stream.read(4096) の行で

OSError: [Errorno -9981] Input overflowed

というエラーで止まってしまった。

発生する箇所は、毎回違うが4分以降5分半くらいでだいたい止まってしまう..

 

エラーの内容が Input overflowed となっており、例外が出ているので

overflow時に例外を発生させないように

buf = stream.read(4096, exception_on_overflow=False)

として実行したら発生しなくなった。


p.s.

あとで調べたら、StackOverflowにも同じ内容が出てました。

2022年4月14日木曜日

2022/4/4 版 Raspberry Pi OSからイメージコピーから直接SSH接続できなくなった

 2022/4/4 にRaspberry Pi OS 64bit版が更新された。

この更新でデフォルトで用意されていたpiアカウントがなくなった。

で、この更新で困るのがセットアップ。

 これまで

  1. SDカードをフォーマット
  2.  Image Writer でRaspberry Pi OS イメージをコピー
  3. SDカードにwpa_supplicant.confを作成し、WiFi設定を記述
  4. SDカードに空のsshファイルを作成
  5. SDカードをRaspberry Pi本体に刺してACアダプタへ接続
  6. 同じWiFiにつながっている新規デバイスをpingで総当りしてIPアドレスを特定
  7. PuttyなどからpiユーザでSSH接続

という手順でセットアップしていたが、できなくなった。


この方法を使っていたのはUSBキーボードやモニタをつながなくていいこと。

とくにRaspberry Pi 4になってからはHDMIコネクタがmicroなので、変換コネクタが必要になってめんどくさい。

なので、この方法でやっていたのだけど..

 

それにしても..みんなびっくりしてないなあ..

コロナ→ロシア情勢などで品薄が続いていて新規のRaspberry Piセットアップが途絶えているのだろうか..

 

この対処法のひとつとして、Raspberry Pi Imagerを使うこと。

 

このImagerをPCにインストールして、このアプリケーションを使ってSDカードを作成する。

 

ただし、注意が1点。

SDカードに書き込む前に歯車ボタンを押して、SSIDやデフォルトユーザ情報を設定しておくことだけ。

これでとりあえず対処した。

 

あと..これは自分だけかもしれないけど、プロクシ設定をのこしたまま、プロクシなし環境でImagerを使うとプロクシにつながらないというエラーが出る。

最近ではリモート勤務もおおくなりProxyを設定したりはずしたりして使用しているPCでは注意が必要だ。

Windows10の場合、環境変数HTTP_PROXY/HTTPS_PROXY、OS側のインターネット接続設定変更だけではだめだった。

Proxyを使ったりやめたりしている人はProxy環境でImagerを使わないといけないらしい(自分は別のPCで実行している)。

 


2022年4月6日水曜日

Raspberry Pi に接続したUSBマイクからの音声入力をpyaudioで操作する

Raspberry Piで音声を入力としたシステムを組もうとしたが、 音声出力用ジャックコネクタはあるけどマイクがないことにきづいた。

なのでUSBコネクタに「Sound Blaster Play! 3」をさしてコネクタにピンマイクをつけてみた。

このピンマイクから音声入力するとき、どうやってデバイスを認識するのか、ちょっとしらべてみた。

ここでの前提は Python プログラムで、とする。

まずPythonからこのマイクの入力を操作するには pyaudio パッケージがあるのでこれをインストールする。

ただし pyaudio は portaudio というライブラリを使っているらしいので先にapt-getしておく必要がある。

sudo apt install libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev
pip install pyaudio


pyaudio でマイクデバイスを指定するには input_device_index が必要になる。

なので Raspberry Pi にSound Blaster Play! 3を刺して以下のコードを実行する。

import pyaudio
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
    print(audio.get_device_info_by_index(i))

するとずらずらっと、USB接続されたデバイスが表示される。以下実行例:

{'index': 0, 'structVersion': 2, 'name': 'bcm2835 Headphones: - (hw:0,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 8, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
{'index': 1, 'structVersion': 2, 'name': 'Sound Blaster Play! 3: USB Audio (hw:1,0)', 'hostApi': 0, 'maxInputChannels': 2, 'maxOutputChannels': 2, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
:


上記の例だと、index1であるデバイスに「Sound Blaster Play! 3: USB Audio (hw:1,0)」とあるので、input_device_index1を指定すれば良い。

あとはいろんなブログ記事に乗っかっているサンプルを少し加工して実行してやれば良い。

import pyaudio
import wave
import sys
import time
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 2**11
MIC_INDEX = 1 # ここに指定
RECORD_SECONDS = 5 # 録音秒数

try:
    WAVE_FILE = sys.argv[1]
except:
    print('no output file name')
    sys.exit(1)

audio = pyaudio.PyAudio()

frames = []

def callback(in_data, frame_count, time_info, status):
    frames.append(in_data)
    return(None, pyaudio.paContinue)
stream = audio.open(
    format=FORMAT,
    channels=CHANNELS,
    rate=RATE,
    input=True,
    input_device_index=MIC_INDEX,
    frames_per_buffer=CHUNK,
    start=False,
    stream_callback=callback
)

if __name__ == '__main__':
    stream.start_stream()
    time.sleep(RECORD_SECONDS)
    stream.stop_stream()
    stream.close()
    audio.terminate()
    wf = wave.open(WAVE_FILE, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(audio.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()



マイクに「あー」といいながら以下のコマンドが終了するまでいいつづける。

python sample.py ah.wav


できあがったah.wavを再生し録音できていればOK。

 

#一応、Pi4/Bullseye 64bit で動作確認しました

2022年3月16日水曜日

DonkeycarにIntel(R) Realsense T265 を使った経路追跡機能がついたらしい

Donkeycarのバージョンが4.3.6になったので、すごく久しぶりに公式ドキュメントを読んでみた。

 すると

 

というページができていなので、とりあえず勝手に翻訳してみた。

 以下、翻訳した内容である。

参照される方は at your own risk でお願いします。

--------

Intel (R) Realsense T265センサによる経路追跡


Donkeycarは、標準的なカメラを使ってネットワークを学習させながら運転するのではなく、Intel(R) Realsense T265「トラッキングカメラ」を使って経路を追跡することをサポートしています。このアプリケーションでは、手動で一度だけ道を走れば、Donkeycarはその道を「記憶」し、自律的にそれを繰り返すことができます。

Intel T265は、ステレオカメラと内蔵の慣性計測ユニット(IMU)、そして独自のMyriad Xプロセッサを組み合わせて、視覚的慣性オドメトリを行います。これは、移動しながら周囲の光景を見て自分の位置を把握し、それをIMUのセンシングと関連付けることで自分の位置を特定し、GPSセンサと同様にX、Y、Z位置をドンキーに出力するという凝った方法です(ただし理想的にはもっと正確に、センチメートルの精度で出力されることが必要です)。
 

注意

Realsense T265は Nvidia Jetson Nanoでも使用できますが、Raspberry Pi(メモリ4GB以上のRPi 4を推奨)を使用すると、もう少し簡単にセットアップが可能です。

また、Intel Realsense D4XXシリーズは、通常のカメラとしてDonkeycarでも使用できますので(深度センシングデータの利用は近日中)、準備ができ次第その手順も追加します。

ステップ1:Ubuntuマシン上にLibrealsenseをセットアップ


RPi上で最新版のRaspberry OS (Busterでテスト済) を使う場合、こちら の手順でIntelのRealsenseライブラリ(Librealsense)と依存関係を設定します。これらの手順は、別のRealsenseセンサについて述べていますが、T265でも同様に動作します。また、ビデオによる説明 もあります。

ステップ2:Donkeycarのセットアップ

こちらの標準的な手順 に従ってください。経路追跡では、Tensorflowをインストールする必要はありませんが、numpy/upgradeをインストールしてから `pip install -e .[pi]` を実行します。

ステップ3:Donkeycarの経路追跡アプリを作成


donkey createcar --path ~/follow --template path_follow

ステップ4:config 設定の変更/確認


cd ~/follow
sudo nano myconfig.py


デフォルト値のまま同意するか、好みに合わせて調整してください(例:"throttle"や"steering"、PIDなど)。
変更した行をアンコメント(#を削除)します。
nano で cntrl-o を押してファイルを保存し、cntrl-x を押して終了します。

ステップ 5:Donkeycar 経路追跡アプリの実行


以下のコマンドを実行します。

ssh pi@<your pi’s IP address or "raspberrypi.local">
cd ~/follow
python3 manage.py drive


アプリの実行中に表示される出力を見るために、ターミナルを開いておいてください。

T265が見つからないというエラーが表示された場合は、センサを一旦抜き、挿し直して再試行してください。
ゲームパッドがオンになっており接続されていることも確認してください(青いランプがコントローラについています)。

起動したら、ノートパソコンでブラウザーを開き、URLバーに次のように入力します: `http://:8890`

運転すると、Webインターフェースに経路が赤い線で、ロボットの位置が緑の円で描かれます。もし、緑の点は表示されるが、赤い線が表示されない場合は、パスファイルがすでに書き込まれていることを意味します。
`donkey_path.pkl` を削除( `rm donkey_path.pkl` )、再起動すると、赤い線が表示されるはずです。

PS4 ゲームパッドの場合、次のような操作を行います:
 
control action
share toggle auto/manual mode
save_path
erase_path
× emergency_stop
L1 increase_max_throttle
R1 decrease_max_throttle
options toggle_constant_throttle
reset_origin
L2 dec_pid_d
left_stick_horz set_steering
right_stick_vert set_throttle

ステップ6:運転上の注意事項

  • ロボットのスタート地点に印をつけてください。スタートするたびに、必ずそこに戻してください。
  • 車をループ状に走らせます。赤い線が道を示しています。
  • PS3/4コントローラの○を押して、経路を保存します。
  • ロボットをスタート地点に戻します。
  • 次に、PS3コントローラの "select "ボタン、またはPS4コントローラの "share "ボタンを2回押し、パイロットモードにします。これで道なりに走り出します。速くしたり遅くしたりしたい場合は、`myconfig.py` ファイルの次の行を変更します:


THROTTLE_FORWARD_PWM = 400

`myconfig.py` の下の方に、微調整するための設定項目があるので確認してみてください。
PID値、マップオフセット、スケールなどです。
私のリポジトリにある `myconfig.py` をダウンロードして使うことから始めるといいかもしれません。

ヒント:

  • 起動すると、緑の点がボックスの左上隅に表示されます。中央の方がいいかもしれません。その場合は、`myconfig.py` ファイルの `PATH_OFFSET = (0, 0)` を `PATH_OFFSET = (250, 250)` に変更します。
  • 小さなコースの場合、パスが小さすぎてよく見えないということがあるかもしれません。その場合は、`PATH_SCALE = 5.0` を `PATH_SCALE = 10.0` に変更します(必要であれば、それ以上の値でもかまいません)。
  • 自動モードで実行しているときは、緑のドットが青に変わります。
  • デフォルトでは0.3mごとにパスポイントを記録するようになっています。より滑らかにしたい場合は、`myconfig.py` のこの行でより小さな数値に変更することができます: `PATH_MIN_DIST = 0.3`

-------

経路追跡なので、自動運転だけど機械学習ではなくあくまで手動運転したときの座標移動になるようにPID制御するようだ。

機械学習、AIの勉強を目的とした人は、使うことはないかもしれない。

それになによりIntel(R) Realsense T265 の販売価格が全然安くないこと。初売り価格でたしか5万円弱で、執筆時点のネット通販価格が8万円台が一般的のようだ。

Donkeycar本体が3万円くらいでつくれるので、さらに同額以上をカメラデバイスにかけるのか..と考えると、あまりやろうとする人は少ないかもなあ..

 

2022年2月10日木曜日

手っ取り早く異常検知をAIでためす方法としてPyCaretのチュートリアルAO101を翻訳してみた

 異常検知を行うAIを、誰でもかんたんに手っ取り早く実装したい。しかもOSS(ただ)で。

 という、わがままボディな要求があり、そんなものあるわけねえだろ..とおもって調べてみると..見つかった。

PyCaret というPythonパッケージだ。

PyCaret Home: https://pycaret.org/ 


どうもKaggle界では有名なAutoMLパッケージの一つで、極小の手数で複数のモデルを一気に並行評価でき、しかも(AutoMLなので)ハイパーパラメータまで勝手にチューニングしてくれるという..

 kaggleでPyCaretを使っても成績上位にはならないのだけど、どうも就職活動生は人事がわからないだろうことを見越して、「Kaggleに参加している」「成績は上位ではないが、(PyCaretを使って)12のモデルを評価してスコアを出した」とかいってごまかす定番のツールで、これを知ってる知ってないでIT企業人事部の民度が学生にバレるのだという、恐ろしや..

 

とはいえ、機械学習の勉強をあまりしなくても使えるほど、実装のための手数が少ないので、このPyCaretをためすことにした。

 

おあつらえ向きにPyCaretチュートリアルに異常検知に関するチュートリアルがあったので翻訳してみた。

以下、翻訳文であるが、参照の際は At your own riskでお願いします。

-----

異常検知チュートリアル AN0101(Anomaly Detection Tutorial ANO101) - レベル初級

  • PyCaret 2.2を使用
  • 更新日 2020年11月25日

1.0 チュートリアルの目的


異常検知チュートリアル(ANO101) へようこそ。
このチュートリアルは、あなたがPyCaretの初心者で、`pycaret.anomaly` モジュールを使って異常検知を始めようとしていることを想定しています。


このチュートリアルでは、以下のことを学びます:

  • データの取得 :PyCaret リポジトリからデータをインポートするには?
  • 環境のセットアップ :PyCaret で実験環境をセットアップして異常検知モデルの構築を始めるには?
  • モデルの作成 :モデルを作成し、分析に必要なアノマリーラベルを元のデータセットに割り当てるには?
  • モデルのプロット :様々なプロットを使ってモデルの性能を分析する方法
  • モデルによる予測 :学習されたモデルに基づいて、新しいデータセットや未知のデータセットに異常ラベルを割り当てる方法。
  • モデルの保存/読込 :モデルを保存する。将来使用するためにモデルを保存/ロードする方法


読了までの時間:約25分

1.1 PyCaret のインストール


PyCaret を使い始めるための最初のステップは、PyCaret をインストールすることです。PyCaret のインストールは簡単で、数分しかかかりません。以下の手順に従ってください。

ローカルの Jupyter Notebook に PyCaret をインストールする。

pip install pycaret

Google ColabまたはAzureノートブックへのPyCaretのインストール


!pip install pycaret

1.2 前提条件


  • Python 3.6 以上
  • PyCaret 2.0 またはそれ以上
  • PyCaret のリポジトリからデータを読み込むためのインターネット接続環境
  • 異常検知の基礎知識

1.3 Google Colaboratory 使用者への追記


Google Colabで実行している場合、インタラクティブなビジュアルを表示するために、以下のコードをノートブックの先頭で実行してください。

from pycaret.utils import enable_colab
enable_colab()

1.4 参考

Anomaly Detectiom Tutorial (ANO102) - Level Intermediate

https://github.com/pycaret/pycaret/blob/master/tutorials/Anomaly%20Detection%20Tutorial%20Level%20Intermediate%20-%20ANO102.ipynb

Anomaly Detection Tutorial (ANO103) - Level Expert

https://github.com/pycaret/pycaret/blob/master/tutorials/Anomaly%20Detection%20Tutorial%20Level%20Expert%20-%20ANO103.ipynb

訳者注:2022年2月10日時点での上記リンク先はともに未作成。

2.0 異常検知とは?


異常検知とは、データの大部分と大きく異なることによって疑いを抱かせるような、稀な項目、イベント、または観察を特定するタスクをさします。
通常、異常な項目は、銀行詐欺、構造的欠陥、医療問題、テキストの誤りなど、何らかの問題に変換されます。
異常検知技術には大きく分けて3つのカテゴリが存在します。

  • 教師なし異常検知 :ラベル付けされていないテストデータセットから、データセット内の大部分のインスタンスが正常であるという仮定のもと、残りのデータセットに最も適合しないと思われるインスタンスを探し、異常を検出する技術。
  • 教師あり異常検出 :この手法では、「正常」と「異常」のラベルが付けられたデータセットが必要で、分類器を学習させる。
  • 半教師付き異常検知:与えられた正常な学習データセットから正常な振る舞いを表すモデルを構築し、学習したモデルによって生成されるテストインスタンスの尤度をテストする手法です。


`pycaret.anomaly` モジュールは教師なしおよび教師ありの異常検知技術をサポートします。このチュートリアルでは、教師なし異常検知技術のみを取り上げます。

3.0 PyCaret 異常検知モジュール概要


PyCaret 異常検知モジュール `pycaret.anomaly` は、教師なし機械学習モジュールで、データの大部分と大きく異なることによって疑いを持たれるような、稀な項目、イベント、観測を識別するタスクを実行します。

PyCaret 異常検知モジュールは、`setup()` 関数でセットアップを初期化する際に設定できるいくつかの前処理機能を提供します。
12以上のアルゴリズムと、異常検知の結果を分析するためのいくつかのプロットを備えています。
また、PyCaret の異常検知モジュールには `tune_model()` というユニークな関数が実装されており、分類のための `AUC` や回帰のための `R2` など、教師あり学習の目的を最適化するために異常検知モデルのハイパーパラメータを調整することが可能です。

4.0 チュートリアル用データセット


このチュートリアルでは、UCI の Mice Protein Expression というデータセットを使用します。
このデータセットは、大脳皮質の核画分に検出可能なシグナルを生成した77個のタンパク質/タンパク質修飾の発現レベルから構成されています。
このデータセットには、1つのタンパク質につき合計1080の測定値が含まれています。
各測定値は、独立したサンプル/マウスとみなすことができます。
データセットの詳細については、こちらをご覧ください。

データセットへの謝辞

  • Clara Higuera インフォマティクス学部ソフトウェア工学・人工知能学科、コンプルテンセ大学化学部生化学・分子生物学科、スペイン、マドリッド( mailto:clarahiguera@ucm.es )
  • Katheleen J. Gardiner、タンパク質発現データの作成者および所有者は、現在Linda Crnic Institute for Down Syndrome, Department of Pediatrics, Department of Biochemistry and Molecular Genetics, Human Medical Genetics and Genomics, and Neuroscience Programs, University of Colorado, School of Medicine, Aurora, Colorado, USAに在籍( mailto:katheleen.gardiner@ucdenver.edu)
  • Krzysztof J. Ciosは現在、米国バージニア州リッチモンドのバージニア・コモンウェルス大学コンピューターサイエンス学部、およびポーランドのIITiSポーランド科学アカデミーに在籍。( mailto:kcios@vcu.edu )


オリジナルのデータセットとデータディクショナリーは、ここで参照可能。

5.0 データの取得


データをダウンロードし、`pandas` を使って読み込むか、PyCaret のデータレスポジトリを使って `get_data()` 関数で読み込むことができます(インターネット接続が必要)。

from pycaret.datasets import get_data
dataset = get_data('mice')


未見データに対する `predict_model()` 関数のデモを行うため、実験終了時にオリジナルデータセットから5%のサンプル(54サンプル)を取り出し、予測に使用します。
これは、train/test の分割と混同しないように注意する必要があります。
この特別な分割は、実生活のシナリオをシミュレートするために行われます。
別の見方をすれば、この 54 個のサンプルは、この実験が行われた時点では利用できません。

data = dataset.sample(frac=0.95, random_state=786)
data_unseen = dataset.drop(data.index)

data.reset_index(drop=True, inplace=True)
data_unseen.reset_index(drop=True, inplace=True)

print('Data for Modeling: ' + str(data.shape))
print('Unseen Data For Predictions: ' + str(data_unseen.shape))

6.0 PyCaretの環境設定


`setup()` 関数は、PyCaret の環境を初期化し、モデリングとデプロイのためのデータを準備する変換パイプラインを作成します。
`setup()` は PyCaret の他の関数を実行する前に呼び出す必要があります。
この関数は 1 つの必須パラメータである pandas dataframe を受け取ります。
他のパラメータはオプションで、前処理パイプラインをカスタマイズするために使用されます(後のチュートリアルで紹介します)。

`setup()` が実行されると、PyCaret の推論アルゴリズムが、特定のプロパティに基づいて全ての特徴のデータ型を自動的に推論します。
しかし、ほとんどの場合、データ型は正しく推論されますが、常にそうとは限りません。
そのため、`setup()`が実行された後、PyCaret は素性とその推定されたデータ型を含むテーブルを表示します。
この段階で、すべてのデータ型が正しく推論されているかどうかを確認し、Enter キーを押して実験を続行するか、 quit キーを押して実験を終了することができます。
PyCaret は、機械学習実験を行うために必要ないくつかの前処理を自動的に行うため、データ型 を正しく特定することは基本的に重要です。
これらの前処理タスクは、データ型ごとに異なる方法で実行されます。
そのため、データ型が正しく設定されていることが非常に重要です。

後のチュートリアルでは、`setup()` の `numeric_features` と `categorical_features` パラメータを使って PyCaret が推論したデータ型を上書きする方法を学びます。

from pycaret.anomaly import *

exp_ano101 = setup(data, normalize = True,
                   ignore_features = ['MouseID'],
                   session_id = 123)


セットアップが正常に実行されると、いくつかの重要な情報を含むグリッドが表示されます。
情報の多くは `setup()` 実行時に構築される事前処理パイプラインに関連するものです。
これらの機能の多くは、このチュートリアルの目的からは外れています。
しかし、この段階で注意すべきいくつかの重要なことがあります。

  • セッションID session_id :後々の再現性のために、すべての関数でシードとして配布される擬似乱数。`session_id` が渡されない場合、乱数は自動的に生成され、すべての関数に配布されます。本実験では、後日の再現性を考慮して、`session_id` を `123` としています。 
  • 欠測値:元データに欠損値がある場合、 `True` と表示されます。上の情報グリッドで欠損値が `True` になっているのは、データに欠損値が含まれているためで、数値特徴の場合は平均値、カテゴリー特徴の場合は定数を用いて自動的にインプットされます。代入(インピュテーション)方法は `setup()` の `numeric_imputation` と `categorical_imputation` パラメータで変更することができます。
  • オリジナルデータ:データセットの元の形状を表示します。本実験では、`(1026, 82)` で、 1026 個のサンプルと 82 個の特徴を意味しています。
  • 変換後データ :変換後のデータセットが表示されます。元のデータセット `(1026, 82)` の形状が `(1026, 91)` に変換されていることに注意してください。データセットにカテゴリ特徴(後述)が含まれているため、特徴の数が増えています。
  • 数値特徴量 :数値として推定された特徴量の数。本データセットでは、82 特徴中77特徴が数値として推定されました。
  • カテゴリ特徴量 :カテゴリ(分類)として推定された特徴の数。本データセットでは、82特徴中5特徴がカテゴリとして推論されました。また、`ignore_feature` パラメータを用いて、1つのカテゴリ特徴である `MouseID` を無視していることに注目してください。


このように、欠損値の代入(インピュテーション)やカテゴリカルエンコーディングなど、モデリングを行う上で必須となるいくつかのタスクが自動的に処理されることに注目してください。
`setup()` の他のパラメータのほとんどはオプションであり、前処理パイプラインをカスタマイズするために使用されます。
これらのパラメータはこのチュートリアルの対象外ですが、中級者、上級者になれば、もっと詳しく説明する予定です。

7.0 モデルの作成


PyCaret で異常検知モデルを作成するのは簡単で、PyCaret の supervised モジュールでモデルを作成する方法と似ています。
異常検知モデルは `create_model()` 関数を用いて作成します。
この関数では、モデルの名前という必須パラメータを文字列で受け取ります。
この関数は学習済みのモデルオブジェクトを返します。
以下の例をご覧ください:

iforest = create_model('iforest')
print(iforest)


`create_model()` で Isolation Forest モデル を作成しました。
`contamination` (混成)パラメータは `0.05` に設定されていますが、これは `create_model()` で `fraction` (割合)パラメータを渡さない場合のデフォルト値です。
`fraction` パラメータはデータセット中の外れ値の割合を決定します。
以下の例では、`0.025` の割合で1クラスサポートベクターマシンモデルを作成します。

svm = create_model('svm', fraction = 0.025)
print(svm)


`create_model()` の引数を `iforest` から `svm` に置き換えるだけで、1クラスサポートベクターマシンモデル(OCSVM)の異常検知モデルを作成することができました。
`pycaret.anomaly`モジュールでは12のモデルが用意されています。
完全なリストを見るには、`docstring` を見るか、`models` 関数を使用してください。

models()

8.0 モデルの割り当て


モデルを作成したので、データセット(1080サンプル)に異常ラベルを付与し、結果を分析したいと思います。
これを実現するには、`assign_model()` 関数を使用します。

以下の例をご覧ください:

iforest_results = assign_model(iforest)
iforest_results.head()


`Anomary` と `Anomary_Score` の2列が最後に追加されていることに注目してください。
`Anomary` 値 `0` は範囲内の値、`1` は孤立値/Anomalyを表します。
`Anomary_Score` はアルゴリズムによって計算された値で、外れ値にはより大きな異常値スコアが割り当てられます。

`iforest_results` には、`setup()` で削除した `MouseID` 特徴も含まれていることに注意してください。
これはモデルには使用されず、`assign_model()` を使用したときのみデータセットに追加されます。
次のセクションでは、`plot_model()` を使って異常検知の結果を分析する方法を見ていきます。

9.0 モデルのプロット


`plot_model()` 関数は、異常検知モデルを様々な角度から分析するために使用することができます。
この関数は,学習済みモデルオブジェクトを受け取り、プロットを返します。
以下の例を参照してください:

9.1 T分散確率的近傍埋込み法(t-SNE)

plot_model(iforest)



9.2 一様多様体近似と射影

plot_model(iforest, plot = 'umap')

METADATAファイルがないというエラーが出たが..パッケージ導入ミス?!

10.0 未知データに対する予測


`predict_model()` 関数は、新しい未閲覧のデータセットに異常ラベルを割り当てるために使用されます。
ここでは、`data_unseen` に格納されたデータを予測するために `iforest` モデルを使用します。
これは実験の最初に作成されたもので、これまでPyCaretに公開されていなかった54個の新しいサンプルが含まれています。

unseen_predictions = predict_model(iforest, data=data_unseen)
unseen_predictions.head()


`Anomary` 欄は外れ値(1=外れ値、0=非外れ値)を示しています。
`Anomary_Score` 欄はアルゴリズムによって計算された値で、外れ値にはより大きな異常値スコアが割り当てられます。
`predict_model()` 関数を使用して、学習データにラベルを付けることもできます。
以下の例を参照してください:

data_predictions = predict_model(iforest, data = data)
data_predictions.head()

11.0 モデルの保存


ここまでのチュートリアルでは、`iforest` モデルを使って未見データの外れ値ラベルを予測する実験が終了しました。
これで実験は終わりですが、まだ一つの疑問が残っています。
予測すべき新しいデータが増えたらどうなるのだろうか?
もう一度、この実験をやり直さなければならないのだろうか?
答えは「いいえ」です。

実験全体を再実行し、新しいデータに対して予測を生成するためにパイプラインを再構築する必要はありません。PyCaret の組み込み関数 `save_model()` は、変換パイプライン全体と共にモデルを保存し、再利用することができます。

save_model(iforest,'Final IForest Model 25Nov2020')

12.0 保存されたモデルのロード


保存したモデルを将来同じ環境または異なる環境でロードするには、PyCaret の `load_model()` 関数を使用し、保存したモデルを新しい未知のデータに簡単に適用して予測します。

saved_iforest = load_model('Final IForest Model 25Nov2020')

一旦モデルが環境にロードされると、同じ `predict_model()` 関数を使用して新しいデータに対して予測するためにそれを単純に使用することができます。
以下では、上記のセクション10で使用したのと同じ `data_unseen` を予測するために、ロードされたモデルを適用しています:

new_prediction = predict_model(saved_iforest, data=data_unseen)
new_prediction.head()

`unseen_prediction` と `new_prediction` の結果は同じであることに注意してください。

13.0 まとめ/次のステップへ?


このチュートリアルでは、`pycaret.anomaly` の基本的な部分のみを取り上げました。
次のチュートリアルでは、データサイエンティストなら必ず知っておくべき、機械学習パイプラインを完全にカスタマイズできる高度な前処理技術に深入りしていきます。

それでは、次回のチュートリアルでお会いしましょう。Anomaly Detection Tutorial (ANO102) - Level Intermediate へのリンクをたどってください。

------

AO102以降のリンクは、本記事執筆時点では未作成だった。

上記チュートリアルには教師なし学習でのクラスタリングしか書かれていないが、分類についても別のチュートリアル(異常検知データセットではないが)があるので、すぐに理解できると思う。

AutoMLか..むかし昭和一桁の親父が安物カメラをくれたとき、「バカ○○○だからだれでも使えるぞ」といっていたが、AutoMLがまさにそうだ。

情報という科目はAIやデータ分析のちからを養うために入試化されるそうだけど、使うほうがどんどん簡単になるので、なくてもいいかもなあ...



2022年1月12日水曜日

富岳上でTensorFlowサンプルを動かす



前記事

「富岳上に tkinter が使用可能な TensorFlow 2.2.0 をインストールする方法 」
https://fight-tsk.blogspot.com/2021/12/tkinter-tensorflow-220.html


では、TensorFlow2.2.0が動作する環境をPythonともども富士通提供のソースコードからコンパイルして構築した。

富士通提供の富岳でも動作する TensorFlow ソースコード(a64fxブランチ)
https://github.com/fujitsu/tensorflow/tree/fujitsu_v2.2.0_for_a64fx



では、ビルドした環境が正常に動作するのかを確認するにはどうすればいいか。

そのために(?)提供されているのが fcc_build_script/sample_script にあるサンプルコード群である。



ここには

  • 01_resnet
  • 02_OpenMNT
  • 03_Bert
  • 04_Mask-R-CNN

の4つのサンプルコードが用意されている。

これらのディレクトリ内にはバッチスクリプトが提供されており、順番に富岳の計算ノード上で動かせば実行できる


..と思ったのだが、なかなか動作しない。

ここでは、2022/1/12時点の環境で実行するために修正をいれた内容を紹介する。


全体共通


env.src の編集


サンプルコードのスクリプトは基本的に fcc_build_script/env.src を使用する。
このため、env.srcが富岳の環境に合致するように環境変数を変更しておく必要がある。

以下、前記事でも書いた2022/1/12時点で動作した修正箇所である。

PREFIX=${HOME}/.local/aarch64
TCSDS_PATH=/opt/FJSVxtclanga/tcsds-1.2.33
VENV_PATH=${HOME}/.local/aarch64/venv/tensorflow
PREFIXVENV_PATHは各自のホームディレクトリの使い方で異なるので、それぞれに合わせて変更しておく。

01_resnet

10_setup_resnet.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "proc=1"
#PJM -S

11_train_resnet-single.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "proc=1"
#PJM -S
富岳のスレッドは8でそのうち0から3の4つは使用できないので、NUMA_NODEを修正する。
NUMA_NODE=4

12_train_resnet-4process.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。
また、複数プロセスの実行となるのでmpiオプションなどが異なっていることに注意のこと。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S
#PJM -L "node=1:noncont"
#PJM --mpi "shape=1,proc=4"
#PJM -j

ステップジョブ実行

ログインノードで 01_resnet ディレクトリ上にてステップジョブ実行する。

chmod +x ./*.sh
pjsub --step 10_setup_resnet.sh
pjsub --step --sparam "jid=XXXXXXX" 11_train_resnet-single.sh
pjsub --step --sparam "jid=XXXXXXX" 12_train_resnet-4process.sh

02_OpenNMT

20_setup_OpenNMT の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "proc=1"
#PJM -S

21_train_OpenNMT_Transformer-single.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -L "node=1"
#PJM -S
富岳のスレッドは8でそのうち0から3の4つは使用できないので、NUMA_NODEを修正する。
NUMA_NODE="4,5"

22_train_OpenNMT_Transformer-2process.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "shape=1,proc=2"
#PJM -S
環境変数MPIには、並列プログラム実行プレフィックスが格納されている。
デフォルトではなぜか富岳上のmpirunではサポートされていないオプション --display-map 、 --display-allocation 、 --map-by が付いているのでこれらを除去する。

  MPI="mpirun -np 2"

ステップジョブの実行

ログインノードで 02_OpenNMT ディレクトリ上にてステップジョブ実行する。

chmod +x ./*.sh
pjsub --step 20_setup_OpenNMT.sh
pjsub --step --sparam "jid=XXXXX" 21_train_OpenNMT_Transformer-single.sh
pjsub --step --sparam "jid=XXXXX" 22_train_OpenNMT_Transformer-2process.sh

03_Bert

300_setup_bert.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

311_create_pretraining_data.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

312_run_pretraining.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

numactl のオプションは変更しなくても良い(使われていないので)。

313_run_pretraining-2process.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "shape=1,proc=2"
#PJM --mpi "max-proc-per-node=4"
#PJM -S
環境変数MPIから富岳上のmpirunに実装されていないオプションを除去する。
MPI="mpirun -np 2"

321_create_finetuning.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

322_run_finetuning.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S
環境変数NUMAは使用されていないのでそのままでもよい。

323_run_finetuning-2process.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#!/bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM --mpi "shape=1,proc=2"
#PJM --mpi "max-proc-per-node=4"
#PJM -S
環境変数MPIから富岳上のmpirunに実装されていないオプションを除去する。
MPI="mpirun -np 2"

ステップジョブの実行

ログインノードで 03_bert ディレクトリ上にてステップジョブ実行する。

chmod +x ./*.sh
pjsub --step 300_setup_bert.sh
pjsub --step --sparam "jid=XXXXX" 311_create_pretraining_data.sh
pjsub --step --sparam "jid=XXXXX" 312_run_pretraining.sh
pjsub --step --sparam "jid=XXXXX" 313_run_pretraining-2process.sh
pjsub --step --sparam "jid=XXXXX" 321_create_finetuning.sh
pjsub --step --sparam "jid=XXXXX" 322_run_finetuning.sh
pjsub --step --sparam "jid=XXXXX" 323_run_finetuning-2process.sh

04_Mask-R-CNN

libxml2/libxsltのコンパイル

Mask R CNN のトレーニング処理を実行するためには、contextlib2 というPythonパッケージが必要になる。その前提としてlibxml2/libxsltライブラリが必要となるため、先にホームディレクトリ内でコンパイル・インストールする。
http://xmlsoft.org/downloads.html からlibxml2とlibxsltのソースコードをダウンロード
富岳上へソースをコピーし展開(以下では ${HOME}/.tmp 直下に2つとも展開し${HOME}/.local/aarch64へインストールする前提での記述となる)する。
 
cd ${HOME}/.tmp/libxml2
make clean
source ${PYTHONHOME}/bin/activate
./autogen.sh --prefix=${HOME}/.local/aarch64 --with-python-install-dir=${HOME}/.local/aarch64/venv/tensorflow
make
make install
cd ../libxslt
./autogen.sh --prefix=${HOME}/.local/aarch64 --with-python-install-dir=${HOME}/.local/aarch64/venv/tensorflow
make
make install

40_setup_mask-r-cnn.sh の編集

スクリプト先頭のPJMコメントを以下のように修正する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

41-0_download_traindata.sh の編集

スクリプト先頭のPJMコメントを以下のように追加する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

41-1_setup_traindata.sh の編集

スクリプト先頭のPJMコメントを以下のように編集する。
なお rscunit は各自の環境に合わせて編集すること。 

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

42_train_maskrcnn_single.sh の編集

スクリプト先頭のPJMコメントを以下のように編集する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -S

43_train_maskrcnn_multi.sh の編集

スクリプト先頭のPJMコメントを以下のように編集する。
なお rscunit は各自の環境に合わせて編集すること。

#! /bin/bash
#PJM --rsc-list "node=1"
#PJM --rsc-list "rscunit=rscunit_ft99"
#PJM --rsc-list "rscgrp=small"
#PJM --rsc-list "elapse=72:00:00"
#PJM -L "node=1:noncont"
#PJM --mpi "shape=1,proc=2"
#PJM -S
環境変数MPIRUNから富岳上のmpirunに実装されていないオプションを除去する。
MPIRUN="mpirun -np 2"

ステップジョブの実行

ログインノードで 04_Mask-R-CNN ディレクトリ上にてステップジョブ実行する。

chmod +x ./*.sh
pjsub --step 40_setup_mask-r-cnn.sh
pjsub --step --sparam "jid=XXXXX" 41-0_download_traindata.sh
pjsub --step --sparam "jid=XXXXX" 41-1_setup_traindata.sh
pjsub --step --sparam "jid=XXXXX" 42_train_maskrcnn_single.sh
pjsub --step --sparam "jid=XXXXX" 43_train_maskrcnn_multi.sh

以上

2021年12月9日木曜日

富岳上に tkinter が使用可能な TensorFlow 2.2.0 をインストールする方法

 2021/12/09 時点における富岳環境では、TensorFlow 2.2.0 が使用可能であるが、有効にした場合の Python は tkinterが使用できない。

富岳上で動作する TensorFlow 2.2.0 のソースコードは、以下のリポジトリにて管理されている TensorFlow ブランチとして公開されている。
 


上記サイトのWikiに、日本語のセットアップ手順つきで提供されている。
この手順通りに実行することで、 tkinter が無効な TensorFlow 2.2.0 実行環境をホームディレクトリ上に構築することができる。

注意

上記サイトをそのまま実行する場合、以下の項目に注意すること。

  • 必ず富岳計算ノード上で実行する
  • 会話ジョブで実行する場合、オプションなしスクリプト実行は、各スクリプトごとに会話ジョブを終了・立ち上げる(実行に時間がかかるのでタイムアウトで失敗しないように)
  • 失敗したら、使ったリポジトリは削除して、git checkoutからやりなおす
  • 全スクリプトを download オプション付きで実行しておく(これもタイムアウト対策)
  • spack は使わない(ガイドどおりにセットアップしても現時点で動作しない、富岳は環境が頻繁に変わるしサポートも弱い)

そこでこの記事では、上記サイト上のスクリプトを利用して tkinter が有効な Python 管理下での TensorFlow 2.2.0 実行環境を構築する手段を紹介する。
 

注意
本リポジトリは、2021/12/09 時点の富岳環境での動作を確認している。
富岳は、随時かつ頻繁に環境更新がおこなわれているため、本リポジトリの情報がすでに使用できない可能性がある。使用の際は、各自の責任にて判断のこと。

インストール手順

  • 計算ノードへログイン
  • 以下のコマンドを実行

  cd ${HOME}
  mkdir projects
  cd projects
  git clone https://github.com/coolerking/fugaku_sample
  cd fugaku_sample
  git checkout master
  cd tensorflow

  • env.src_fugakuを確認する。env.src は、デフォルトの設定から以下の環境変数を変更している。必要に応じて値を変更する。 
    • PREFIX : バイナリおよびライブラリの配置先ディレクトリ 
    • TCSDS_PATH : 富士通コンパイラのベースディレクトリ(定期的にバージョンが更新される) 
    • VENV_NAME : Python venv環境の配置先ディレクトリ

    PREFIX=${HOME}/.local/aarch64
    TCSDS_PATH=/opt/FJSVxtclanga/tcsds-1.2.33
    VENV_PATH=${HOME}/.local/aarch64/venv/tensorflow

  • 01_tensorflow.sh を確認する。有効なメールアドレスへの書き換えを行う。

  #PJM --mail-list "hogehoge@fugafuga.slack.com"
 

  • 以下のコマンド実行

  chmod +x 01_tensorflow
  pjsub 01_tensorflow.sh

注意

試行時は、ジョブ開始から完了まで7時間半ほどかかった。


TensorFlow 2.2.0 の利用


以下のコマンドを計算ノード上で実行し、venv環境 tensorflow を有効化する。

source ${HOME}/.local/aarch64/venv/tensorflow/bin/activate
動作を確認したい場合は、計算ノード上で以下のコマンドを実行する。
python -c "import tkinter"
python -c "import tensorflow"

備考

  • インストールされる tcl/tk は、富士通コンパイラではなく、gccコンパイラによりビルドされている(Python/TensorFlowは富士通コンパイラでビルドされる)
  • ビルドされるTensorFlowはGPUは使用できない(計算ノードにはGPUが搭載されていない)かわりにoneDNNを使用したCPU高速化が行われている
  • 上記手順を実行するとPython上でMPI相当の実装が可能になる horovod パッケージもあわせて構築される
  • TensorFlow 2.2.0 には、mesh-tensorflow が未実装のバージョンのため使用できない

参考

2021年9月21日火曜日

勝手日本語翻訳→「HuggingFace と Unity を使ってスマートロボットAIを構築する~あなたの命令に従うロボット」

タイムラインにちょっとおもしろそうなハンズオンのリンクが回ってきた

どうもHugging Face上のAPIとUnityで作ったロボットを連携させて、指示文書を英語で入力すると、それに合わせた動作をUnity内のロボットが動いてくれるというプログラムをつくることができるらしい。

ただ..AIはAPIまかせだし、Unity部分はそっくりコピペなので、いちから手順を学べるかというとそうではない。

ただ手順通りにすれば、すぐに動かすことはできそうな状態のようによめたので、シルバーウィークの暇な時間を潰すのにはいいかもしれない。

以下、勝手に日本語化した翻訳文だけど、参照は at your own riskでお願いします。

なお画像については原文画像へのリンクで貼り付けているので、別ウィンドウで開いてみてください

-----

HuggingFace と Unity を使ってスマートロボットAIを構築する~あなたの命令に従うロボット


今日は、プレイヤのテキスト入力に基づいてアクションを実行するこの愛らしいスマートロボットを構築します。


深層言語モデルを使ってテキスト入力を解釈し、行動リストのなかから最も適切なアクションを見つけます。

このシステムで興味深いのは、従来のゲーム開発とは対照的に、すべてのインタラクションをハードコーディングする必要がないことです。
代わりに、与えられたユーザ入力に最も適切なロボットの可能なアクションを選択する言語モデルを使用します。


このプロジェクトの構築には、以下のものを使用します:

  • Unity Game Engine (2020.3.18.f1 and +)
  • (MixとJamによって作成された)Jambo Robot アセット
  • Hugging Face


この記事は、Unity に関する基礎的なスキルをすでに持っている人を対象としています。
ロボットとの対話をためしてみたいだけの場合は、プロジェクトページに移動し、圧縮されたファイルをダウンロードして、ドキュメントに従ってください。
 


では、はじめましょう!

文章類似(Sentence Similarity)判定能力


いきなり実装に飛び込む前に、プロジェクトがどのように機能して、文章類似判定が何であるかを理解する必要があります。

プロジェクトはどのように機能するのか?


このプロジェクトでは、プレイヤにより多くの自由を与えたいと思っています。
ボタンをクリックするだけでロボットに注文を出すのではなくテキストを介してロボットと対話してもらいたいのです。
ロボットにはあらかじめ行動リストをもっていて、プレイヤの指示に最も近いアクション(存在する場合は)を選択する文章類似判定モデルを使用します。


たとえば、「ちょっと赤い箱をつかんで (hey grab me the red box)」と書いた場合、ロボットは「ちょっと赤い箱をつかんで」が何を意味しているのかについてプログラムされていません。
しかし、文章類似判定モデルモデルは、この指示と「赤い立方体を持ってくる」というアクションを結び付けます。


文章類似判定モデルに仕事をまかせるという手法のおかげで、プレーヤによる入力とロボットの行動可能なすべてのインタラクションをを手作業でマッピングするという面倒なプロセスを必要とせずに、信頼性の高いキャラクタAIを構築することができます

文章類似判定とは何か?


文章類似判定は、ソースとなる文章と文章リストが与えられた場合に、文章リストの各文がソースとなる文章とどのくらい類似しているかを計算できる言語モデルでおこないます。
たとえば、ソースとなる文章が「やあ、ここだよ! (Hey there)」の場合 「こんにちは! (Hello)」という行動をあらわす文章がとても近いと判断します。


ここでは、文章変換モデル all-mpnet-base-v2 を使用します。
このモデルを使用すると、入力テキストを指定して、実行するのに最も適切なアクションをロボットに「決定」させることができます。
モデルはすでにトレーニングされているため、すぐに使用することができます。

ステップ1:文章類推判定モデルを選択する

HuggingFace 入門


HuggingFace には、多くのすばらしい言語モデルと、それらをプロジェクトに直接プラグインするためのAPI(Accelerated Inference API)が含まれています。
ただし、最初に アカウントを作成 する必要があります。


アカウントを作成したら、Accelerated Inference APIダッシュボードに移動し、プロファイル(右上隅)とAPIトークンをクリックします。


APIトークンをコピーします。
これは、APIを使用できるようにするために必要なキーです。

セキュリティ上、このキーは共有しないでください。これは秘密キーです。

Accelerated Inference API


APIキーを取得したら、次はモデルを選択します。

ここでは、文章類推判定モデル all-mpnet-base-v2 を選択しました:


他の文章類推判定モデルを自由に試してもかまいません。

素晴らしいのは、Hosted Inference API を使用してWebサイトでモデルを直接試すことができることです。
テストをしてみましょう:


「赤いブロックをつかんできなさい (fetch the red block)」はロボットのアクション「赤い立方体を持ってくる (bring me the red cube)」に近いので、モデルは正しく機能していることがわかります。

このモデルを選択することにしたので、API URL を取得します。
それには、「Deploy」>「Accelerated Inference」をクリックして、


API_URLがコピーできるモーダルを開きます。

これで API を Unity に接続する準備が整いました。

ステップ2:Hugging Face API を Unity に接続する


次に、Hugging FaceモデルAPIをUnityに接続し、使用可能にする必要があります。

Scenes/Tutorial にある Scripts/Tutorial_ を開きます:


シーンは次のようになります。
ロボットはさまざまなオブジェクト(BlueCube、RedCube、RedPillarなど)に囲まれています。


Scripts/Tutorial にて `HuggingFaceAPI_Tutorial.cs` を開きます:


このスクリプトはPOSTリクエストを処理し、プレーヤの入力が行動リスト内の各ロボットアクション文の類似性スコアを返すようにAPIに要求し、そこから最も高い類似性スコアを持つアクションを選択できるようになります。

`HFScore()` :APIの呼び出し


APIを呼び出して結果を処理するには、コルーチン関数を使用します。
このタイプの関数は実行を待機できるため、実行を続行する前に、そのAPIが応答を返すのを待機する必要があります。

  • 最初に、つぎのようなPOSTリクエストのJSONを作成する。


`HuggingFaceAPI_Tutorial.cs`:


public IEnumerator HFScore(string prompt)
{
        // Form the JSON
        var form = new Dictionary<string, object>();
        var attributes = new Dictionary<string, object>();
        attributes["source_sentence"] = prompt;
        attributes["sentences"] = sentences;
        form["inputs"] = attributes;

        var json = Json.Serialize(form);
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json);
}

  • 次に、Webリクエストを行い、応答する。


`HuggingFaceAPI_Tutorial.cs`:

public IEnumerator HFScore(string prompt)
{
  ...
 
  // Make the web request
        UnityWebRequest request = UnityWebRequest.Put(model_url, bytes);
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("Authorization", "Bearer " + hf_api_key);
        request.method = "POST"; // Hack to send POST to server instead of PUT

        yield return request.SendWebRequest();

        // If the request return an error set the error on console.
        if (request.isNetworkError || request.isHttpError)
        {
            Debug.Log(request.error);
            Debug.Log(request.downloadHandler.data);
            yield return request.error;
        }
        else
        {
            JSONNode data = request.downloadHandler.text;
            // Process the result
            yield return ProcessResult(data);
        }
    }

  • 応答は `[0.7777,0.19,0.01]` のような文字列となる。これを操作するには、`float` 配列に変換する必要がある( `ProcessResult(data) にて処理)。

`ProcessResult()` :結果を `float` 配列に変換し、最大スコアと最大スコアインデックスを発見する


APIは文字列 `[0.77, 0.18, 0.01,…]` を返しますが、ここでは最高のスコアとそのインデックスを見つける必要があります。

したがって、結果文字列を浮動小数点数の配列 `[0.77、0.19、0.01]` に変換し、最大スコアと最大スコアインデックスを見つける必要があります。

この処理は `ProcessResult()` 関数でおこないます。

`HuggingFaceAPI_Tutorial.cs`:

/// <summary>
    /// We receive a string like "[0.7777, 0.19, 0.01]", we need to process this data to transform it to an array of floats
    /// [0.77, 0.19, 0.01] to be able to perform operations on it.
    /// </summary>
    /// <param name="result">json return from API call</param>
    private IEnumerator ProcessResult(string result)
    {
        // The data is a score for each possible sentence candidate
        // But, it looks something like this "[0.7777, 0.19, 0.01]"
        // First, we need to remove [ and ]
        string cleanedResult = result.Replace("[", "");
        cleanedResult = cleanedResult.Replace("]", "");

        // Then, we need to split each element of the array and convert to float
        string[] splitArray = cleanedResult.Split(char.Parse(","));
        float[] myFloat = splitArray.Select(float.Parse).ToArray();

        // Now that we have a array of floats, we can find the max score and the index of it.
        // We don't need to return these 2 variables, we'll access them directly since they are public.
        maxScore = myFloat.Max();
        maxScoreIndex = myFloat.ToList().IndexOf(maxScore);

        yield return null;
    }



この関数により、正しいアクションを選択するために `RobotBehavior` で呼び出す `maxScoreIndex` 変数と `maxScore` 変数を更新しました。

インスペクタへ入力する


ロボットの動作に取り組む前の最後のステップは、APIキーとモデルのURLをインスペクタに入力することです。
シーン内の `HuggingFaceAPI` オブジェクトをクリックし、インスペクタで `Model_url` と `Hf_api_key` を更新します。

APIキーを定義する場合は、プロジェクトを共有しないでください。

ステップ3:ロボットの動作を構築する


Unityプロジェクトを HuggingFace Model API に接続したので、ロボットの動作を定義する必要があります。
ロボットにはさまざまなアクションがあり、アクションの選択はAPI出力に依存するという考え方です。

まず、有限ステートマシンを定義する必要があります。
これは、各状態が特定の動作を定義する単純なAIです。

次に、状態を選択するユーティリティ関数を作成し、実行する一連のアクションを作成します。

ステートマシンを定義する


ここでのステートマシンは、各状態は動作を表します。
状態の例として、整列して「こんにちは」と言う、などになります。

エージェントの状態に基づいて、一連のアクションを実行します。
ここでは、7つの状態を定義します。


まず、すべてのアクション可能な状態を `State` という列挙型として作成します。

`JummoBehavior_Tutorial.cs` :

/// <summary>
/// Enum of the different possible states of our Robot
/// </summary>
private enum State
{
  Idle,
  Hello, // Say hello
  Happy, // Be happy
  Puzzled, // Be Puzzled
  MoveTo, // Move to a pillar
  BringObject, // Step one of bring object (move to it and grab it)
  BringObjectToPlayer // Step two of bring object (move to player and drop the object)
}


常に状態をチェックする必要があるため、それぞれのケースが状態であるスイッチシステムを使用して、ステートマシンを `Update()` メソッドに定義します。

状態のケースごとに、エージェントの動作を定義します。

たとえば、状態 `Hello` では、ロボットはプレーヤに向かって移動し、プレーヤに正しく向き合ってから、`hello` アニメーションを起動してから、アイドル状態に戻る必要があります。


すべてを定義しましょう:

`JummoBehavior_Tutorial.cs` :

private void Update()
    {
        // Here's the State Machine, where given its current state, the agent will act accordingly
        switch(state)
        {
            default:
            case State.Idle:
                Debug.Log("STATE IDLE");
                break;

            case State.Hello:
                agent.SetDestination(playerPosition.position);
                if (Vector3.Distance(transform.position, playerPosition.position) < reachedPositionDistance)
                {
                    RotateTo();
                    anim.SetBool("hello", true);
                    state = State.Idle;
                }
                break;

            case State.Happy:
                agent.SetDestination(playerPosition.position);
                if (Vector3.Distance(transform.position, playerPosition.position) < reachedPositionDistance)
                {
                    RotateTo();
                    anim.SetBool("happy", true);
                    state = State.Idle;
                }
                break;

            case State.Puzzled:
                Debug.Log("CASE STATE PUZZLED");
                agent.SetDestination(playerPosition.position);
                if (Vector3.Distance(transform.position, playerPosition.position) < reachedPositionDistance)
                {
                    RotateTo();
                    anim.SetBool("puzzled", true);
                    state = State.Idle;
                }
                break;

            case State.MoveTo:
                agent.SetDestination(goalObject.transform.position);
                
                if (Vector3.Distance(transform.position, goalObject.transform.position) < reachedPositionDistance)
                {
                    state = State.Idle;
                }
                break;

            case State.BringObject:
                // First move to the object
                agent.SetDestination(goalObject.transform.position);
                if (Vector3.Distance(transform.position, goalObject.transform.position) < reachedObjectPositionDistance)
                {
                    Grab(goalObject);
                    state = State.BringObjectToPlayer;
                }
                break;

            case State.BringObjectToPlayer:
                agent.SetDestination(playerPosition.transform.position);
                if (Vector3.Distance(transform.position, playerPosition.transform.position) < reachedObjectPositionDistance)
                {
                    Drop(goalObject);
                    state = State.Idle;
                }
                break;
        }
    }


これで、さまざまな状態ごとの動作を定義できました。

ここでの魔法は、どの状態がプレーヤ入力に最も近いかを定義する言語モデルであるということから生まれました。
そして、ユーティリティ関数では、この状態を呼び出します。

ユーティリティ関数を定義する


アクションリストは次のとおりです:

 

  • SentenceがAPIに供給される
  • Verbは状態をあらわしている
  • Noun(存在する場合)は、対話するオブジェクト(柱、立方体など)をさす


このユーティリティ関数は、プレーヤの入力テキストとの類似性スコアが最も高い文に関連付けられている動詞と名詞を選択します。

しかし最初に多くの奇妙な入力テキストを取り除くために、類似性スコアのしきい値を設定する必要があります(しきい値を `0.20` に設定していますが、このしきい値はアクション数に比例した値を設定する必要があります)。

たとえば、「すべてのウサギを見て (Look all the rabbits)」と言った場合、考えられるアクションはどれも関係ありません。
このため、スコアが最も高いアクションを選択するのではなく、State Puzzled という困惑したアニメーションでロボットをうごかします。


スコアが高い場合は、`state` に対応する `verb` と、(存在する場合は)`noun`( `goalObject` )を取得します。

`verb` に対応する `state` を設定します。
これにより、対応する動作が有効になります。

`JummoBehavior_Tutorial.cs` :


/// <summary>
    /// Utility function: Given the results of HuggingFace API, select the State with the highest score
    /// </summary>
    /// <param name="maxValue">Value of the option with the highest score</param>
    /// <param name="maxIndex">Index of the option with the highest score</param>
    private void Utility(float maxScore, int maxScoreIndex)
    {
        // First we check that the score is > of 0.3, otherwise we let our agent perplexed;
        // This way we can handle strange input text (for instance if we write "Go see the dog!" the agent will be puzzled).
        if (maxScore < 0.20f)
        {
            state = State.Puzzled;
        }
        else
        {
            // Get the verb and noun (if there is one)
            goalObject = GameObject.Find(actionsList[maxScoreIndex].noun);

            string verb = actionsList[maxScoreIndex].verb;

            // Set the Robot State == verb
            state = (State)System.Enum.Parse(typeof(State), verb, true);
        }
    }



これで、ロボットと対話する準備が整いました。

ステップ4:ロボットと対話する


このステップでは、エディタの再生ボタンをクリックするだけです。
そうすれば、あなたはいくつかの指示をだして結果を見ることができます。

指示の入力が終了したら、Enterキーを押すことを忘れないでください。

いくつかのバグ、特にモデルの読み込みのバグがある可能性があります。

ロボットがアイドルモードのままになっている場合は、初回モデル読込中の可能性があるため、45秒ほど待ちます。
コンソールでエラーメッセージを確認してください。

次は?


アクションを追加するにはどうすればよいですか?

例を見てみましょう:

  • `YellowPillar` ゲームオブジェクトをコピーし移動
  • 名前を `GreenPillar` に変更
  • 新しいマテリアルを作成し、それを緑に設定 (RGB:)
  • `GreenPillar` にマテリアルを配置


新しいゲームオブジェクトを配置したので、文を追加する必要があります。
`Jammo_Player` をクリックします。
アクションリストで、プラスボタンをクリックして、次の新しいアクション項目を入力します:

 

  • Move to green pillar
  • Move To
  • GreenColumn


以上になります。



今日はこれですべてになります。

指示に基づいてアクションを実行できるロボットを作成する手順を紹介しました。

これはすばらしいことです。

次にできることとしては、Scripts/Final にあるコードを深く掘り下げ、Scenes/ Finalの最終シーンを確認することなどです。

必要な部分についてはすでにコメントしているので、それは比較的簡単にできるとおもいます。

言語モデルとトランスフォーマについてもっと知りたい場合は、Hugging Face コースをチェックしてください(無料です):

2021年8月17日火曜日

OpenAI Gym 独自Envクラス内でmlflowを使う

OpenAI Gym の独自Envで強化学習を行う場合、評価したいパラメータは独自Envクラス内に埋まってしまう。

このため mlflow を使って実験を管理する場合、独自Envクラス内に実装しないといけなくなる。

たとえば以下のような gym.Env 継承クラスの実装をおこなう。


import gym
import mlflow
from mlflow.tensorflow import autolog # tensorflow実装の場合

class HogehogeTrainEnv(gym.Env):

    def __init__(self):
        :
        # 独自環境クラス初期化処理
        self.step_count = 0
        self.total_reward = 0.0

        :
        # mlflow実行開始
        mlflow.start_run()
        # パラメータとしてenv_idを書き込む
        mlflow.log_param('env_id', 'HogehogeEnv-v0')
        autolog()

    def step(self, action):
        # 累積ステップ数
        self.step_count += 1
        :
        # 観測データ obs 更新
        # エピソード完 done 判定
        # 当該ステップの報酬値 reward 算出
        # その他情報 info の格納

        # エピソード内報酬値合計
        self.total_reward += reward
        :
        # エピソード完了時に報酬値合計を書き込む
        if done:
            ml_metric('total_reward', self.total_reward, step=self.step_count)
            self.total_reward = 0.0
        return obs, reward, done, info

    def close(self):
        :
        # mlflow 実行終了
        mlflow.end_run()


mlflow.log_params() / mlflow.log_param() は同一キーの上書きをする場合エラーを発生させることがある。
 

mlflow.log_metrics() / mlflo.log_metric() は毎step実行すると大量のデータをグラフ化できなくなるので実行タイミングを独自環境ごとに工夫する必要がある。上記スードコードの例ではエピソード完了時のみ書き込みを行っているが、total_timestepsをおおきくすればするほどログ出力が増え、トレーニング処理が増大する。

ログ出力が膨大な場合は mlflow ui するとグラフ表示できなくなることがある。tensorboard を使う場合は mlflowでは指標は書き込まずにパラメータのみ管理するなどの切り分けをして、余計なログを取らないようにする。

上記のクラスはあくまでトレーニング処理用の独自Envクラスとして設計している。評価用Envクラスと共用する場合は、評価時にもmlflowログが出力されることに注意。

上記の例では mlflow.start_run() / mlflow.end_run() を各テンプレートメソッドに埋め込んでいるが、これは Stable Baselines を使用する場合を想定している。Stable Baselinesを使う場合は autolog はどうも機能しないようなので注意。


2021年7月30日金曜日

mlflow ui を Google Colaboratory上で動かす

 
MLflowによるトレーニング結果リストをColab上の実験で使いたい場合、pyngrokを使うと便利。

試していないが、tensorboardなどにも利用可能。

!pip install mlflow

!pip install pyngrok
ngrok はローカルPC上のnginxなどのサービスを外部へ公開することのできるsshトンネリングツール。pygrokはPythonから使用できるPythonパッケージ。
 

  • mlflow ui をバックグラウンド起動

get_ipython().system_raw("mlflow ui --port 5000 &")


ローカルPCの場合 http://127.0.0.1:5000/ をブラウザで開くとmlflow uiが表示されるが、Colab の場合グローバルURLがないと呼び出せない。

  • pyngrok を使って5000番ポートのHTTPサービスをngrokへ登録

from pyngrok import ngrok
ngrok.kill()
ngrok_tunnel = ngrok.connect(addr="5000", proto="http", bind_tls=True)

  • パブリックURLを取得

print("MLflow UI ", ngrok_tunnel.public_url)


実行すると以下のようなURLが出力される。
MLflow UI  https://xxxxxxxxxxxxx.ngrok.io

  • ブラウザの別タブで上記URLを開く


mlflow uiが表示される。
 


以下のコードは、ここのサンプルコードのif __name__ == "__main__":を関数 train() 化しているだけ。

import os
import warnings
import sys

import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import ElasticNet
from urllib.parse import urlparse
import mlflow
import mlflow.sklearn

import logging

logging.basicConfig(level=logging.WARN)
logger = logging.getLogger(__name__)


def eval_metrics(actual, pred):
    rmse = np.sqrt(mean_squared_error(actual, pred))
    mae = mean_absolute_error(actual, pred)
    r2 = r2_score(actual, pred)
    return rmse, mae, r2


def train(alpha=0.5, l1_ratio=0.5):
    warnings.filterwarnings("ignore")
    np.random.seed(40)

    # Read the wine-quality csv file from the URL
    csv_url = (
        "http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
    )
    try:
        data = pd.read_csv(csv_url, sep=";")
    except Exception as e:
        logger.exception(
            "Unable to download training & test CSV, check your internet connection. Error: %s", e
        )

    # Split the data into training and test sets. (0.75, 0.25) split.
    train, test = train_test_split(data)

    # The predicted column is "quality" which is a scalar from [3, 9]
    train_x = train.drop(["quality"], axis=1)
    test_x = test.drop(["quality"], axis=1)
    train_y = train[["quality"]]
    test_y = test[["quality"]]

    with mlflow.start_run():
        lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)
        lr.fit(train_x, train_y)

        predicted_qualities = lr.predict(test_x)

        (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)

        print("Elasticnet model (alpha=%f, l1_ratio=%f):" % (alpha, l1_ratio))
        print("  RMSE: %s" % rmse)
        print("  MAE: %s" % mae)
        print("  R2: %s" % r2)

        mlflow.log_param("alpha", alpha)
        mlflow.log_param("l1_ratio", l1_ratio)
        mlflow.log_metric("rmse", rmse)
        mlflow.log_metric("r2", r2)
        mlflow.log_metric("mae", mae)

        tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme

        # Model registry does not work with file store
        if tracking_url_type_store != "file":

            # Register the model
            # There are other ways to use the Model Registry, which depends on the use case,
            # please refer to the doc for more information:
            # https://mlflow.org/docs/latest/model-registry.html#api-workflow
            mlflow.sklearn.log_model(lr, "model", registered_model_name="ElasticnetWineModel")
        else:
            mlflow.sklearn.log_model(lr, "model")

Scikit Learnを使ったトレーニングサンプル。MLflowのチュートリアルなので mlflow パッケージを使って実行ディレクトリのしたに作成される mlflowディレクトリにlog_xxx()関数で指定されたパラメータ情報やモデルを格納する。

train()を実行するごとにトレーニング処理を実行する。


  • 1回目のトレーニングを実行

train(alpha=0.5, l1_ratio=0.5)


実行結果例:
Elasticnet model (alpha=0.500000, l1_ratio=0.500000):
  RMSE: 0.7931640229276851
  MAE: 0.6271946374319586
  R2: 0.10862644997792614

  • 別タブで開いたままのmlflow ui の左上のアイコン(もしくはRefreshボタン)を押下


mlflow ui 上に1回目の結果が表示される。



  • 2回目のトレーニングを実行


パラメータを変えて実行。

train(alpha=0.4, l1_ratio=0.51)

実行結果例:
Elasticnet model (alpha=0.400000, l1_ratio=0.510000):
  RMSE: 0.7769749651758281
  MAE: 0.6101625601336331
  R2: 0.14464227762391824

  • 別タブで開いたままのmlflow ui の左上のアイコン(もしくはRefreshボタン)を押下


mlflow ui 上に2回目の結果が追加される。

ngrok は、sshトンネリングを使うため、社内LANなど管理統制下の環境から使用する場合は、各々のセキュリティポリシーに抵触しないか各自で確認のこと。

 

以上

GitHub Actions で実行させているClaude Codeにテスト実行などをさせるためのBash権限を追加する方法

先の記事 でも書きましたが、Claude Code をIssueやPull RequestのリアクションをClaude Codeにやらせる場合、一旦ローカルPCにClaude Codeをインストールして、 git clone したGitHubリポジトリ内でclaudeコマンドで実...