Translate

2021年6月30日水曜日

富岳上で実行したジョブの開始・終了・ステータスをSlackで受ける

富岳上でジョブを実行する際に pjsub コマンドを使います。


pjsub には様々なオプションがあるのですが、このなかに指定イベントごとにメールを送信するオプションがあります。


オプションの詳細は man pjsub を参照してください。たくさんあります。



これと SlackアプリのEmailを組み合わせれば、メールではなく直接Slackへのメッセージとして受け取ることができます。

Slack アプリ Email
https://slack.com/apps/A0F81496D-email?tab=more_info
 

Slackアプリサイトにてインストールボタンを押して、どのチャネルへ送信するかを指定すると、送信先メールアドレス(ex. hogehoge@workspace_name.slack.com)を受け取ることができます。

pjsub の引数に -m b,e,s --maillist "hogehoge@workspace_name.slack.com" と指定すればOK。

引数にいちいち指定するのが面倒くさい場合は、ジョブスクリプト上部のPJMオプション指定箇所に、以下のように指定すればOKです。

#!/bin/bash
:
#PJM -m b,e,s
#PJM --mail-list "
hogehoge@workspace_name.slack.com"
#PJM -S
:

-mの後ろがメール送信が発生するイベントを指定しています。ここでは開始、終了、ステータスを指定していますが、ほかのイベントについては man pjsub-m の箇所を読んでください(-Sは、ステータスを表示させるために必要)。

p.s.

実行ジョブ監視にログインノード上で watch pjstat -E している人も多いと思うのですが、おそらく私のネットワーク環境の問題だと思いますが、SSH接続がたまにぷつんと切れてしまうので..メールへ送ってましたが..ジョブスクリプト開発中はやたらめったらpjsubするので、ほぼスパムメール状態に..

 

あと、たくさんの種類のジョブを投げる人はジョブ名をつけたほうがよいとおもいます。

pjコマンドは宣言的な設計になっており、APIもあるのでIPリーチャブルな環境にあるCIサーバ(BLUE WATERのWebinerで紹介していたjenkinsみたいなやつ、スパコンの場合はslumというのがあるそうですが..よく知りません)があれば、CI管理画面上で完結できるので、たぶんそちらが正解なのかもしれませんが..


2021年6月29日火曜日

富岳でホームディレクトリの.sshを削除してしまった件

今回の情報は..役に立つ人がいるのか、わかりませんが、忘備録としてのこしておきます。



スーパーコンピュータ富岳上でいろんなジョブを実行していたのですが、
うっかりそのジョブの中で~/.sshを消してしまいました。

そうするとどうなるかというと..

次にSSH接続するときにエラーになって接続できなくなります。


でヘルプデスクへ問い合わせたところ公開鍵を再登録してくれという返事が..


富岳では、以下の2つの手順をおこないます。
・クライアントPC側のPuTTY Genなどで鍵ペアを作成
・利用者ポータルにて公開鍵登録


すでに鍵ペアは作成済みであるので、2番めの項目のみを実行します。

・クライアントPC側のブラウザから富岳ポータルを開く
・利用者ポータルリンクをふむ
・publickey registration リンクをふむ
・鍵ペア作成時に取得した公開鍵文字列をコピー&ペースト
・Registerボタン押下


これでホームディレクトリの.sshディレクトリ内に公開鍵に関する情報が登録されます。
あとは再度クライアントPCからユーザID、パスフレーズをつかってSSH接続しなおせばOKです。

 

ほかの公開鍵で接続する場合もにたようなポータルサイトのような機能をもつことがおおいので、富岳以外の場合もあせらずまずポータルに公開鍵登録機能がないかしらべてみてください。

2021年5月25日火曜日

富岳にdonkeycarパッケージをインストールする

 表題の手順をgithub上に公開しました。

GitHub: Donkeycar on Fugaku
https://github.com/coolerking/donkeycar_on_fugaku



去年あたりからニュース画像でもおなじみになってきたスーパーコンピュータ富岳ですが、OSSを使った計算を行う場合は多少厄介になります。

すでにインストール済みのものであればよいのですが、未インストールであり、さらに依存関係のあるOSパッケージ管理ツール管理化のパッケージを使用する場合、一般ユーザの許可されている権限でなんとかしないといけないので、いろいろややこしくなります。

自分が思いつく方法は、以下の2種類になります。
 

  • ホームディレクトリ上でソースからコンパイル
  • spack を使ってコンパイル&インストール


ホームディレクトリ上にソースコードを展開して、手動でビルドするときに、`--prefix` オプションでホームディレクトリ上のサブディレクトリ(ex. `~/local/aarch64fx` )にして`make install`したときにこのディレクトリに書き込まれるようにする方法です。

自分は Slackware 時代からのLinuxユーザなので、この手の手順にはなれていますが、依存関係のあるパッケージをたどっていくと..やがて LLVM まで到達してしまい、この方法であっているのか?..と。

で、富岳ポータルサイト(登録ユーザのみが参照可能)のコンテンツを調べ直したところ、Spackを使うことを推奨しているようです。

Spackは、スーパーコンピュータ上でOSSをインストールするためのツールで、Pythonで動作します(富岳にも最初からインストールされている)。なので、http://github.com/spack/spack.git をチェックアウトするだけで動作します。

Spackはpyenvやvirtualenv、condaなどのように"環境"を作ることができるので、デフォルトの環境ではなく、用途に合わせて個々の"環境"を作ることができます(削除もコマンド1発)。この"環境"の中にインストールすればOKです。最初の環境であるルート環境に入るには、用意されたセットアップシェルを実行します。

# インストール
git clone https://github.com/spack/spack.git ~/spack
cd ~/spack
git checkout releases/<使いたいバージョン>

# ルート環境に入る
. ~/spack/share/setup-env.sh


これでspackコマンドが使用可能になります。

独自の"環境"を作る場合も、spackコマンドを使用します。

# 環境 hogehoge を作成する
spack env create hogehoge
# 環境 hogehoge に入る
spack env activate hogehoge
# インストール可能なOSSパッケージをリスト
spack list
# インストール済みパッケージのリスト
spack find
# openblasパッケージをインストール
spack install openblas
# 環境 hogehoge から抜ける(ルート環境にもどる)
spack env deactivate
# 環境 hogehoge を削除する(確認なし)
spack env remove hogehoge


spack install で指定する引数によっては、OSSバージョン、使用するコンパイラおよびバージョン、リンクオプションなどを指定可能です。

# 富岳ではterminfoをリンクする場合-ltinfoを使う
spack install mesa ldlibs="-ltinfo"


spack install されると、~/spack/var/spack/environments/<Spack環境名>/.spack-env/view以下のbinlibincludeへインストールされます。これらへのパスなどの環境変数はspack env activate時にセットされます。

ただし、pipからフォークされるcmakeなどのオプションにまで到達しない場合があるので、個別にセットする必要があるケースもあります(Pythonパッケージをpipでインストールする際には内部でcmakeすることがあります)。このあたりはトライ&エラーでみつけていくしかありません。

Pythonパッケージの一部もspackからインストールできます。もちろんpipも可能ですが、一般ユーザではグローバルインストールはできないので、--userオプションを付けないと動きません。


富岳は会話型とジョブ型の2種類の実行方法がありますが、spack installが会話型だと制限時間内に終わらないことがあるため、それなりのボリュームのパッケージはジョブ型で実行(pjsubコマンドでシェルを実行)する必要があります。


あと、TensorFlow2.2.0/PyTorch1.7.0はあらかじめインストールされたパスが公開されているので、これらへパスを変更することで使用可能になります。Spackでもインストールできそうですが、富岳管理者が提供しているので、コンパイラ最適化オプションをきちんとつけて作成している(..とおもいます)はずなので、こちらをつかうほうがよいでしょう。

執筆時点のTensorFlow2.1.0は、pipが移動元パスになっているので、使用するには工夫が必要。


なのでvirtualenvなどのPython環境と、Spack環境を併用するのがよいと、私は判断しました。最初に紹介したGitHub上のスクリプトも、それを前提に作成しています。

細かい使い方はSpackのドキュメントを参照してみてください。チュートリアルもありますし、Youtubeに英語ですが概要紹介のセッション動画も投稿されています(字幕翻訳させれば内容はわかるとおもいます)。



 

 

ご参考まで。

2021年4月21日水曜日

kubectl run --restart=Always を実行してもDeploymentが作成されない

 

Udemyの某k8s研修を受講していたのだけど、
そのなかで kubectl run --restart=Always でpodを作成すると
自動的にdeploymentも作成される
とあり、
そのとおりにminikube上で動かしたところ..deploymentが作成されなかった(ただpodは正常に作成できた)。

で、調べてみると、以下の記事を見つけた

Kubernetes 1.18 broke “kubectl run”, here’s what to do about it
https://alexellisuk.medium.com/kubernetes-1-18-broke-kubectl-run-heres-what-to-do-about-it-2a88e5fb389a


上記を読むと、1.18で仕様が変わっているらしい
で minikube versionを実行すると..

minikube version: v1.19.0

..どうもなくなったバージョンのminikube環境だったようだ..

対処法も一応上記の記事には書かれている。

1. REST APIとclient-go(OpenFaaS)などのクライアントの使用
2. YAMLファイルを手作りするか、より現実的には、StackOverflowまたは既知の例からコピーして貼り付ける
3. kubectl run -o yaml --dry-run=client ~を使って一旦YAMLファイル化する




..最初の方法は研修環境minikubeでは無理。
2と3は、Deployment 設定がかかれたYAMLファイルを作ってkubectl apply -f しろということ。で、3は既存のkubectl runしたときpodを作成せずに一旦標準出力にYAML形式で出力してくれるので、これをもとにDeployment設定を書きなさいよ、ということらしい..

上記記事にはDeployment設定の入ったYAMLサンプルがはいっている(以下)。

apiVersion: apps/v1
kind:
Deployment
metadata:
  name: nginx-1
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80



kubectl run --dry-run=client した形式を2番めのspecタグ内に書き込んで、おおもとのkindはDeploymentにして、Deployment用のspecを1番めにちょこちょこなおしていくしかない..

つまりはYAMLファイルの書き方をきっちり勉強しないとだめってことだ。

..なんか、易しくないな..

追記)

..もうすこし kubectl を調べてみると

kubectl create deployment --image <イメージ先> <deployment名>

を実行することで、Deploymentを作れることが判明した。

#しかも、Udemy講座の先のセクションで上記の説明もあった..

 

..とはいえホットデプロイを運用でやるなら
結局はコマンドではなくYAML定義ファイルを
使いこなしたほうがいいのだとおもう。

2021年4月14日水曜日

DockerfileのCMDを環境変数で処理を分岐させる

小ネタシリーズ。

Dockerfileをビルドしたイメージをdocker run する際に
-e オプションで環境変数を渡して
CMDに指定したコマンドの実行を切り分けたい場合、
if else fi を使えば対応できます。

たとえば以下のようなDockerfileを書いて

FROM ubuntu:20.04

ENV type="fufufu"

# 環境変数 type が "fufufu" か "hehehe" の場合と
# それ以外の場合のかき分け
CMD if [ ${type} = "fufufu" -o ${type} = "hehehe" ]; then \
        echo "type is ${type}"; \
        echo "hehehe"; \
  else \
        echo "type ${type} is unknown"; \
    fi;



docker build -t test/test:latest .

を実行したあとで

docker run -e type="hehehe" test/test:latest

を実行すると

type is hehehe
hehehe


と表示され、

docker run -e type="hohoho" test/test:latest

を実行すると

type hohoho is unknown


と表示されます。

2021年3月17日水曜日

OpenAI Gym カスタム環境のinfoに"episode"というキーで値を入れたら1回目のミニバッチ終了時にTypeErrorが発生する件

OpenAI Gym のカスタム環境クラスをつくったのだが、
環境クラス内部でエピソード番号を表示させたくなった。

そこで
step()の戻り値の一つinfoが特に型が決まっていないので
__init__()内で辞書self.infoを作ってその中にカウンタ用の
整数を入れてreset()が呼び出されるたびに加算するように
実装した。

以下のコードは、独自環境クラスの例である。

class TestEnv(gym.Env):
    def __init__(self):
            super().__init__()
            self.action_space = gym.spaces.Box(
                low= -1, high=+1, shape=(3,), dtype=np.float32)
            self.observation_space = gym.spaces.Box(
                low=0, high=255, shape=(120, 160, 3), dtype=np.uint8)
            self.info = {
                'episode': 1,
            }

    def reset(self):
        self.info['episode'] += 1
        return np.zeros(shape=(120, 160, 3))

    def step(self, action):
        return np.zeros(shape=(120, 160, 3)), 1.0, True, self.info


そして、以下のように強化学習トレーニングを実行しようとした。

env = TestEnv()
from stable_baselines3 import SAC
model = SAC('CnnPolicy', env, verbose=1, tensorboard_log='./logs')
model.learn(total_timesteps=4096)


すると..

C:\Users\XXXXX\Anaconda3\envs\gym_donkey\lib\site-packages\torch\cuda\__init__.py:52: UserWarning: CUDA initialization: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx (Triggered internally at  ..\c10\cuda\CUDAFunctions.cpp:100.)
  return torch._C._cuda_getDeviceCount() > 0
Using cpu device
Wrapping the env in a DummyVecEnv.
Wrapping the env in a VecTransposeImage.
Logging to ./logs\PPO_1
Traceback (most recent call last):
  File "tests\test_gym.py", line XX, in <module>
    test_learn_ppo()
  File "tests\test_gym.py", line XX, in test_learn_ppo
    model.learn(total_timesteps=4096)
  File "C:\Users\XXXXXX\Anaconda3\envs\gym_donkey\lib\site-packages\stable_baselines3\ppo\ppo.py", line 264, in learn
    reset_num_timesteps=reset_num_timesteps,
  File "C:\Users\XXXXXX\Anaconda3\envs\gym_donkey\lib\site-packages\stable_baselines3\common\on_policy_algorithm.py", line 234, in learn
    if len(self.ep_info_buffer) > 0 and len(self.ep_info_buffer[0]) > 0:
TypeError: object of type 'int' has no len()

(gym) D:\projects\hogegoge>


そこでカスタム環境クラスのself.infoで扱う'episode'キーワードを
別のラベルに変更すると..正常動作するようになった...

どうも、親クラスでself.infoを使っていて
しかもOpenAI Gym内部で'episode'キーワードを使っているらしく
想定外の型(上記例ではstr)が入っていてエラーになってしまったらしい..


Stable Baselines3 はドキュメントが充実しているのが売りだったはずなのに..

..公式ドキュメントに、書いててほしかった..


2021年1月27日水曜日

Donkeycar V4.1.0 のデータセット構成を調べる

 Donkeycar の github 上の master ブランチが 4.1.0 になった。


V4はでていたが、masterブランチは3.1.xのままだったので、V4についてはとくに調べることをせずに放置していたのだが..ひさしぶりにインストールしようとしたら..エラー乱発..それでmasterブランチがV4にマージされたことを知った。

Discordにもはいっていたが、やたらPCから音が出るのでアンインストールしていたので、アナウンスが多分あったのだとは思うが、知らなかった。

 V4で何が変わったかというと、一番がTubデータの構造だ。

V3までのTubデータはトップディレクトリdataの下にtub_<1からの連番>_YY-MM-DDというサブディレクトリが運転を実行するたびに作成されこの下に1レコードにつきJSONファイル1つとJPEGファイルが1つ作成されるという構成だった。

V3の構造で最も問題になっていたのが、トレーニング処理がレコード件数に比例して時間がかかるというもの。

1レコード2ファイルのI/O処理がはいるこの構造は、正直機械学習向きではない。とはいえDonkeycarの機械学習モデルはせいぜいが5層で深層学習と言えるほど深くない。ためしていないが数理最適化アルゴリズムのほうがよいかもな..とも思えるくらいの小規模モデルだ。なのでトレーニング時間はそれほど長くはないので、これでも問題なく利用されていた。

また、手動走行中にミスが有りデータを削除する場合、連番に飛びが発生するが、そのままではトレーニング処理でエラーが出てしまうという問題もあった。どうしてもクラッシュしたデータレコードを削除したくなると、歯抜けのTubデータになってしまい、連番をつけなおすスクリプトを作って対処していた。

どうもこれらの問題を解決するためにV4ではdatastore自体を見直したようだ。

で、ソースコードをよんだり、実際にデータを出力させてみた結果、以下の構造をしていることがわかってきた。


 

V3でJSONファイルになっていた数値データは、Catalogファイルに格納される。Catalogファイルは、デフォルト1000レコードごとに切り出されており、Catalogファイルとしての連番がつくようになっている(1000件未満の場合は1ファイルになる)。

 Catalogファイルが複数になる場合があるため、各Catalogファイルに対応したマニフェストファイルが作られている。この中にはCatalogファイルの先頭レコードがレコード連番の南蛮から始まるかや、1行のバイト長リストなどがはいっている。いわばCatalogファイルごとのメタ情報が入っているファイルだ。バイト帳リストは、コード内でランダムアクセス操作をするために使われている(詳細はSeekableクラスを参照してもらえばわかる)。 Catalogマニフェストファイルといえばいいのかな。

そして複数のCatalogファイルをまたいだメタ情報をまとめたのがmanifest.jsonというファイル。ここに入力データのラベルリストや型リストが入っている。また削除対象のインデックス番号リストを格納できるようになっており、ここに削除したいレコードのインデックスをリスト形式で書いておけば削除したものとみなして処理してくれる仕様だ。

 上記のファイルのI/O操作はdatastore_v2.pyにかかれている。Seekableクラスもここに格納されている。

 V3のデータをコンバートするツールも提供されているので、これまでの学習データが無駄になることはないが、ひと手間確実に面倒になることに注意。

 また、Donkeycar上で、python manage.py drive を複数回実行すると、V3の頃はdataの下にサブディレクトリができるが、V4ではインデックス番号はつづきからふられるため、分離できないことも注意が必要だ。dataディレクトリを削除してしまうか移動させておかないと各レコードの_created_atで調整しなくてはならなくなる。

 

 

p.s.

運転する際は、Tubデータを書き込むだけなので、ここまでの内容を理解していれば問題はない。

しかしもし機械学習モデルの入力データ項目にIMU情報はその他の情報をくわえたり、出力データ項目を増やしてモータ数を増やそうとしている場合は、機械学習モデルにデータセットとして渡す処理donkey.parts.pipelineパッケージ以下のコードを理解する必要がある。


2021年1月26日火曜日

Donkeycar の Mobile App を試す

Donkeycarの公式ドキュメントに Mobile App というセクションが増えていた。

Donkeycar: Mobile App
http://docs.donkeycar.com/guide/mobile_app/


OSSであるDonkeycarをそのまま使う場合、PCやRaspberry Pi上でpythonをコマンドラインで動かす必要があったが、このスマホアプリとDonkeycarのOSイメージを使うとそれが不要になるらしい。


ということで試してみた。

必要なものを準備

使用前提は、Raspberry Piに接続するモータドライバの種類がPCA9685もしくはRoboHAT MM1 のどちらかであることと、搭載しているマイクロコンピュータがRaspberry Pi4Bのみということだったので、昔Robocar Storeで購入した標準仕様のDonkeycarのRaspberry Pi3B+を4Bに取り替えて使った。

4Bのメモリサイズは書かれていなかった。2GBのものを使ったが今の所動作している。


他に必要なものはインターネットに接続できるWiFiルータ、SDカード書き込み可能なPC、それにAndroidもしくはAppleのスマートフォン。
F710 Wireless Gamepad も動作するが、なくてもアプリ上から操作が可能になっている。

RoboHAT MM1を使うと電源の電圧状況がアプリからチェックできるらしいが、持っていないので試していない。Raspberry Pi上に乗せるタイプのHAT基板なので初心者はこっちのほうがピンコネクタの接続に迷わなくてすむ。香港のRobocar Storeでもかえるが、スイッチサイエンスでもとりあつかっているようだ。


スイッチサイエンス:Robo HAT MM1
https://www.switch-science.com/catalog/6381/?gclid=CjwKCAiA9bmABhBbEiwASb35V_3CvCwUEIQk95rCpQly56jUx6-lM61jeg6__svzRFkDHcZJ3PwxZBoCvAoQAvD_BwE

あと余談だが、どうも自分の持っているバッテリがへたったのだとおもうが、4BになってしまったのでラジコンバッテリでのRaspberry Piの起動ができなくなっていた。のでスマホ用モバイルバッテリをくくりつけてそちらから給電させて動かした。

 


 

PCを使ってイメージをSDカードへ書き込みDonkeycarへさす(以降PC不要)


イメージをダウンロードしてきてEtcherなどでフォーマット済みのSDカード(自分は32GBを使った)へ書き込む。
wpa_supplicant.confの書き換えとかは不要だ。

なおF710を使いたい場合は、起動前にドングルを指しておくこと。

Donkeycarを起動して2分ほど待つ


初期セットアップがかかるのだとおもうが、まつようにドキュメントに書かれていた。Raspberry Piのランプが安定したら、スマホのWiFi設定画面で接続先として「donkey-<MACアドレス後半>」という名前が出てきたら準備完了だ。

スマホにRobocar Controllerをインストール


セットアップ中にApple Storeで「Robocar Controller」で検索すると、コミュニティロゴであるロバマークのついたアプリがあるのでこれをインストールする。

スマホのWiFi設定をSSID 「donkey-<MACアドレス後半>」 に変更


Donkeycarが起動すると本体がWiFiルータの接続ポイントのようにアクセスポイントが見えるようになる。初回の場合は、まずこれにつないでDonkeycarの設定を色々変更することになる。

アプリで初期設定をおこない反映させる


Robocar Controllerを起動すると初期設定画面がでてくるので、国をJapanに、コントローラタイプをPCA9685(もしくはMM1)に、ホスト名を適当に、WiFi設定をインターネットへ接続可能なSSIDに変更する。走行会の場合はホスト名とパスワードはかならず設定しておくこと。

ちなみにここでSSIDを変更しないでおくと、トレーニング処理を実行できない。ただ、手動運転までは可能なので、外につなぎたくないとか手動運転だけで遊びたい人はやらなくても良い(ただし、スマホはDonkeycar側につなぐので使用中はインターネット接続できなくなる)

 

アプリから一旦ぬけ、スマホのWiFi設定をDonkeycarに指定したSSIDと同じアクセスポイントに変更。

反映させると、ぐるぐる回る画面が永遠に続いてしまうので、アプリを早々にとじ、WiFi設定変更画面でSSIDを変更して、Donkeycarと同じセグメントのLANにつないでおく。




アプリを再度起動、先ほど指定したホスト名を選択


出てこない場合は何度かRefreshする。星をつけていても、ここで認識していないと接続することはできない。星は走行会でたくさんのDonkeycarが動いているとき自分の車かどうかを判別させるためだとおもう。

キャリブレーションを実行


標準Donkeycarの車体は意外とずれているので最初にしておく。ステアリングがフリー状態で中央にきていること、フルで右左にした時の状態はとくに綿密に設定する。
ステアリングも最大スピードがだせるかを確認する。走行会ではない場合はそれなりの速度しか出ないように設定してもいい。

よさそうなら保存する。

ドライブを選択し手動走行を実行


F710で操作したい場合は「Physical Joystick」を、スマホの場合は「Virtual Joystick」を選択する。

F710の場合は、前方カメラ画像は見ることができないが操作しやすい。ただ、走行会だと混線するのでVirtualを選択せざるを得ないかもしれない。

右上の白丸を押すとTubデータのレコーディングが開始される。ある程度慣れたら、レコーディングを開始して、学習データを収集する。



 

終了は左端のバツを押す。すると取得したデータの統計情報が表示される。データ自体はDonkeycar本体に保存される。削除したい場合はアプリから実行できる。


 

 1万件くらいで十分なのでとりあえず頑張って取得する。

トレーニングを実行


トレーニングアイコンを押すと何も出てこないので+を押してジョブを追加する。トレーニングジョブは、1日1車両5回までしかできない。Tubデータは複数選択可能だが、100MBまでという制約があるので注意。またトレーニング処理は最大15分までで強制終了になるため、そのあたりは気をつけて使用する。

15分ほど放置し、再度アプリに接続しトレーニングが終了していることを確認する。

Donkeycarを走行位置において、自動運転開始


トレーニングが完了したジョブを選択すると、AutoPilotが選択できるようになっているはずなので、これを押すと自動運転が開始される。

----


PCは最初のSDカードを作成するときだけしか使用しない。トレーニングはアプリ側が用意したAWSインスタンスを使用する。つまり無料だ(いまのところ?)。太っ腹ではあるが、複数同時にジョブキックされたらどうなるかとかは試していない。おそらく失敗になって再度キックしないとだめなんじゃないかとおもう。

4B前提ということなので、今から始める人は Robocar Store の Donkeycar S1 というモデルを選択することになるとおもう。こちらも試していないので、わからないが、おそらくMM1が搭載されているのではないだろうか。


たぶんこれを作ったのは、Pythonを知らなくてもDonkeycarを扱えるようにしたかったのだとおもう。独自のDonkeycarを作っている人はモータコントローラとRaspberryPiの種類に気をつければだいじょうぶだとおもう。


2020年12月15日火曜日

pigpiod を systemctl で起動しようとしたら 8888 番ポートがすでに使われていて動作しないと怒られた件

注意:2021/1/13追記までできれば読んでください。

 

久しぶりに Raspberry Pi3B+をセットアップする要件があり、12月2日版busterを入れ、pigpioを以下の手順でインストールした。

sudo apt update && sudo apt upgrade -y
sudo apt install pigpio pigpiod -y
sudo systemctl enable pigpiod
sudo systemctl start pigpiod



そしてsudo systemctl status pigpiod してみると..

20.12.09 10:30:11 (+0800)  pigpiod  2020-12-09 10:30:11 initInitialise: bind to port 8888 failed (Address already in use)
20.12.09 10:30:11 (+0800)  pigpiod  Can't initialise pigpio library


なるエラーが出てデーモンが起動しない..

昔はこの手順で動いたと思ったのだけど..

いろいろしらべたら、手動でsudo pigpiodしたら動作したので、/lib/systemd/system/pigpiod.serviceの以下の赤字部分を削除してみた

[Unit]
Description=Daemon required to control GPIO pins via pigpio
[Service]
ExecStart=/usr/bin/pigpiod -l
ExecStop=/bin/systemctl kill pigpiod
Type=forking
[Install]
WantedBy=multi-user.target


sudo rebootして再度sudo systemctl status pigpiodしたら..動作していた。

以前のpigpiod.service がどうなっていたかは確認していないのだけど-lオプションってremote socket をdisableにする設定だからポート番号8888のlistenでエラーになるのはおかしいと思うんだけどなあ..ドキュメントが間違ってるのかもしれないなあ..

pigpiod ..なにかあったのかな..

 

2021/1/13 追記:

sudo raspi-config して「3 Interface Options」>「P8 Remote GPIO」というメニューが新設されていて、これを先にenableするとsudo apt install pigpio -y && sudo systemctl start pigpiod で Active になった。

おそらく、このオプションを有効にしないとpigpiodsystemctl で起動できなくなったようだ。

IoTデバイスのセキュリティポリシー厳格化の風潮に合わせたアップデートなのだとおもう。

想像だけど。

2020年12月2日水曜日

Stable Baselines の使いやすいインターフェイスを使ったままで、独自の方策ネットワークを定義する

 Stable Baselines の使いやすいインターフェイスを使ったままで、独自の方策ネットワークを定義することも可能です。

以下、Stable Baselinesドキュメントの該当箇所の翻訳です。

-----

本ドキュメントは、Stable Baselines3 ドキュメントの Custom Policy Network を翻訳したものです。

カスタム方策ネットワーク


Stable Baselines3 は、画像(CnnPolicy)及びその他タイプの入力機能(MlpPolicy)の方策ネットワークを提供します。
 

警告
A2CおよびPPOの場合、トレーニングやテスト中に(範囲外のエラーを回避するため)継続的な行動がクリップされます。SAC、DDPG、TD3は、`tanh()` 境界をより正確に処理する変換を使って、行動を押しつぶします。

カスタム方策アーキテクチャ


カスタム方策アーキテクチャをカスタマイズするひとつの方法として、モデルを生成する際の引数に policy_kwargs パラメータを渡すことです。


import gym
import torch as th

from stable_baselines3 import PPO

# 活性化関数にRelu を使う、サイズが32である2つのレイヤを備えたカスタムMLP(多層パーセプトロン)Policy
policy_kwargs = dict(activation_fn=th.nn.ReLU, net_arch=[32, 32])
# エージェントの生成
model = PPO("MlpPolicy", "CartPole-v1", policy_kwargs=policy_kwargs, verbose=1)
# 環境の取得
env = model.get_env()
# エージェントのトレーニング
model.learn(total_timesteps=100000)
# エージェントの保存
model.save("ppo-cartpole")

del model
# policy_kwargs も自動的にロードされる
model = PPO.load("ppo-cartpole")


方策(もしくは価値)ネットワークのカスタムアーキテクチャも簡単に定義することができます。
 

注意
カスタム方策クラスを定義することと、引数として policy_kwargs を渡すことは同じ意味をさしていますが、ポリシーに名前をつけることができるため、コード上では意図が明確に示しやすくなります。policy_kwargs は、ハイパーパラメータ検索を行う場合には便利です。

カスタム特徴抽出機(Feature Extractor)


(画像を使用する際のカスタムCNNなど)カスタム特徴抽出器が必要な場合は、 BaseFeatureExtractor を継承したクラスを定義して、トレーニング処理にてモデルを生成する差異に引数として渡します。


import gym
import torch as th
import torch.nn as nn

from stable_baselines3 import PPO
from stable_baselines3.common.torch_layers import BaseFeaturesExtractor


class CustomCNN(BaseFeaturesExtractor):
    """
    :param observation_space: (gym.Space) 観測空間
    :param features_dim: (int) 抽出される特徴量の次元(最後のレイヤのユニット数に相当)
    """

    def __init__(self, observation_space: gym.spaces.Box, features_dim: int = 256):
        super(CustomCNN, self).__init__(observation_space, features_dim)
        # (チャネルが最初にくる) C×H×W イメージを想定
        # 並べ直しは事前処理もしくはラッパクラスにより実行されることを想定
        n_input_channels = observation_space.shape[0]
        self.cnn = nn.Sequential(
            nn.Conv2d(n_input_channels, 32, kernel_size=8, stride=4, padding=0),
            nn.ReLU(),
            nn.Conv2d(32, 64, kernel_size=4, stride=2, padding=0),
            nn.ReLU(),
            nn.Flatten(),
        )

        # 観測空間のサンプルを1件取得して形式を算出
        with th.no_grad():
            n_flatten = self.cnn(
                th.as_tensor(observation_space.sample()[None]).float()
            ).shape[1]

        self.linear = nn.Sequential(nn.Linear(n_flatten, features_dim), nn.ReLU())

    def forward(self, observations: th.Tensor) -> th.Tensor:
        return self.linear(self.cnn(observations))

policy_kwargs = dict(
    features_extractor_class=CustomCNN,
    features_extractor_kwargs=dict(features_dim=128),
)
model = PPO("CnnPolicy", "BreakoutNoFrameskip-v4", policy_kwargs=policy_kwargs, verbose=1)
model.learn(1000)

 

オンポリシーアルゴリズム

共有ネットワーク


A2CやPPOの方策パラメータ `net_arch` を使用すると、非表示レイヤ数やサイズ、方策ネットワークと価値ネットワーク間で共有されるレイヤ数を指定することができます。これは次のようなリストであると想定されています。
 

  1. 0を含む任意の共有レイヤのユニット数を指定する任意の長さをあらわす整数(0を含む)。intの数が0の場合、共有レイヤはありません。
  2. 価値ネットワークや方策ネットワークの非共有レイヤを指定するための辞書(オプション)。dict(vf=[<価値ネットワークレイヤサイズ>], pi=[<方策ネットワークレイヤサイズ>]) のような形式。pivf のいずれかのキーが欠落している場合、非共有レイヤ(空のリスト)は想定されません。


要約すると、 [<共有レイヤ数>, dict(vf=[<非共有価値ネットワークレイヤ数>], pi=[<非共有方策ネットワークレイヤ数>])] という形式のリストを指します。
 


サイズが128である2層の共有レイヤの場合:net_arch=[128, 128]


    観測
     |
   <128>
     |
   <128>
   /   \
行動    価値


方策ネットワークより価値ネットワークのほうが層が厚く、第1層を共有する場合:net_arch=[128, dict(vf=[256, 256])]

    観測
     |
   <128>
   /   \
行動  <256>
        |
      <256>
        |
       価値


第1層を共有し、第2層目から分岐する場合:net_arch=[128, dict(vf=[256], pi=[16])]

    観測
     |
   <128>
   /   \
 <16> <256>
  |     |
行動   価値

 

高度な例


方策/価値アーキテクチャをさらに細かく制御する必要がある場合は、方策を直接再定義することができます。


from typing import Callable, Dict, List, Optional, Tuple, Type, Union

import gym
import torch as th
from torch import nn

from stable_baselines3 import PPO
from stable_baselines3.common.policies import ActorCriticPolicy


class CustomNetwork(nn.Module):
    """
    方策および価値関数のカスタムネットワークをあらわすクラス。
    特徴抽出器(Feature Extractor)によって抽出された特徴量を入力として受け取る。

    :param feature_dim: feature_extractor で抽出された特徴量の次元(例:CNNにより抽出された特徴量)
    :param last_layer_dim_pi: 方策ネットワーク最終層のユニット数(int)
    :param last_layer_dim_vf: 価値ネットワーク最終層のユニット数(int)
    """

    def __init__(
        self,
        feature_dim: int,
        last_layer_dim_pi: int = 64,
        last_layer_dim_vf: int = 64,
    ):
        super(CustomNetwork, self).__init__()

        # 重要:
        # 確率分布の作成に使用される出力層次元数を保存
        self.latent_dim_pi = last_layer_dim_pi
        self.latent_dim_vf = last_layer_dim_vf

        # 方策ネットワーク
        self.policy_net = nn.Sequential(
            nn.Linear(feature_dim, last_layer_dim_pi), nn.ReLU()
        )
        # 価値ネットワーク
        self.value_net = nn.Sequential(
            nn.Linear(feature_dim, last_layer_dim_vf), nn.ReLU()
        )

    def forward(self, features: th.Tensor) -> Tuple[th.Tensor, th.Tensor]:
        """
        :return: (th.Tensor, th.Tensor) 指定ネットワークの latent_policy(th.Tensor形式), latent_value(th.Tensor形式) 。
            すべての層が共有の場合、``latent_policy == latent_value`` となる。
        """
        return self.policy_net(features), self.value_net(features)


class CustomActorCriticPolicy(ActorCriticPolicy):
    def __init__(
        self,
        observation_space: gym.spaces.Space,
        action_space: gym.spaces.Space,
        lr_schedule: Callable[[float], float],
        net_arch: Optional[List[Union[int, Dict[str, List[int]]]]] = None,
        activation_fn: Type[nn.Module] = nn.Tanh,
        *args,
        **kwargs,
    ):

        super(CustomActorCriticPolicy, self).__init__(
            observation_space,
            action_space,
            lr_schedule,
            net_arch,
            activation_fn,
            # 残りの引数群はベースクラスに渡される
            *args,
            **kwargs,
        )
        # 直行初期化の無効化
        self.ortho_init = False

    def _build_mlp_extractor(self) -> None:
        self.mlp_extractor = CustomNetwork(self.features_dim)


model = PPO(CustomActorCriticPolicy, "CartPole-v1", verbose=1)
model.learn(5000)


 

オフポリシーアルゴリズム


SACやDDPG、TD3を使用する際に行動器(actor)と評価器(critic)が異なるネットワークアーキテクチャが必要な場合、次のような形式の辞書を引数に渡します: dict(qf=[<評価器ネットワークアーキテクチャ>], pi=[<行動器ネットワークアーキテクチャ>])

例えば、行動器ネットワーク(別名pi)と評価器ネットワーク(別名qf)を別のアーキテクチャにする場合、net_arch=dict(qf=[400, 300], pi=[64, 64]) のように指定します。

行動器ネットワークと評価器ネットワークを同一アーキテクチャを使う場合は、net_arch=[256, 256] のように指定します(ここでは256ユニットの2つの隠れ層を指定しています)。
 

注意
オンポリシーとは対照的に、行動器と評価器の間の(特徴抽出器以外の)共有ネットワークは許可されていません。


from stable_baselines3 import SAC

# それぞれ64ユニットの2層を持つカスタム行動器アーキテクチャ
# 400ユニットと300ユニットの2層を持つカスタム評価器アーキテクチャ
policy_kwargs = dict(net_arch=dict(pi=[64, 64], qf=[400, 300]))
# エージェントの生成
model = SAC("MlpPolicy", "Pendulum-v0", policy_kwargs=policy_kwargs, verbose=1)
model.learn(5000)

-----
カスタムSIで独自モデルを作ることは殆どないとは思いますが、ご参考まで。

p.s.
Donkeycar のモデルを強化学習化するには、最後の「高度な例」セクションの手段を取れば可能になります。

..というか、gym-donkeycar使うか..ふつー..


2020年11月24日火曜日

OpenAI Gym 環境クラスを独自実装してみる

強化学習を行う場合、Stable BaselinesやTensorFlowなどのAPIではOpenAI Gym準拠の環境クラスを使用することができる。

ので、個別案件に対応する独自環境クラスを作りたい..

ということで実際に作ってみた。

独自環境クラスを実装するには、まずOpenAI Gymが提供する gym.Env という基底クラスを継承し、以下の表にあるプロパティ(インスタンス変数、つまりself.~)やメソッドをオーバライドすればよい。



 

 

サンプルとして強化学習AIでじゃんけん対戦を実現する。
以下、作ったサンプルコードのリポジトリリンク

[GitHub] coolerking/rock-paper-scissors
https://github.com/coolerking/rock-paper-scissors


実装してみて思ったのが、報酬関数の仕様は機能要件上には登場しないので、(業務用件によっては)設計者が発明しなくてはならないこと。

そして、状態と観測の違いが理解できない人(意外と多い)は、もう「状態=観測」で割り切って設計すること。

軽く触った程度なので、もうすこしほってみないとアレだけど..とりあえず現時点での感想です。


【ハーネスエンジニアリングを始める前に】gpt-oss:20b の入出力データから、LLMの動作を理解する

  1. はじめに 最近は、 Cursor や Antigravity などのAIエージェント前提の統合開発環境や、 Claude Desktop や Codex アプリ などのPC上のオブジェクト操作が可能なデスクトップアプリ、 Open Claw といったPC全体の...