Translate

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 になった。

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

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=[<方策ネットワークレイヤサイズ>]) のような形式。pi や vf のいずれかのキーが欠落している場合、非共有レイヤ(空のリスト)は想定されません。


要約すると、 [<共有レイヤ数>, 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


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

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

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


2020年9月14日月曜日

Stable Baselinesのコールバックの書き方を調べてみる

Donkeycar の OpenAI Gym 環境 gym-donkeycar を理解するためには OpenAI Gymのフレームワークを手っ取り早く把握する必要がある。

 ということで書店でみつくろい、比較的カラフルで分厚い「OpenAI Gym/Baselines 深層学習 強化学習 人工知能プログラミング 実践入門」という書籍を買った。

 

過去に一度さらっとTensorflo(v1の頃だけど)強化学習のチュートリアルをやって、だいぶ時間がたっていたが、必要な箇所しか説明していなかったので、すんなりすすめることができた。

 本に書いているコードに多少誤り(breakぬけとか)があり、治す必要はあるが Python を知っている人だったら問題ない程度..

 

..だったのだけど、Stable Baselinesのコールバックを使うところで、サンプルコードが動かな書くなった..

..動かなくなった時点でピンときたのが、後付で読んだアマゾンのレコメンド、情報が古い云々..ああ、Stable Baselinesのコールバック実装が変わったんじゃないの、とピンときた。

 

で公式ドキュメントの Callbacks を読んでみたら、案の定"レガシ"になってた..

 

以下、勝手翻訳した内容です。

(ただしレガシとかかれた旧呼び出しに関する箇所はomitしました)

参照はat your own riskでお願いします。

 ------

コールバック


コールバックは、トレーニング手順の特定の段階で呼び出される関数のセットです。トレーニング中にコールバックを使用して強化学習(RL)モデルの内部状態にアクセスできます。監視、自動保存、モデル操作、進行状況バーなどの機能を提供することができます。

独自 Callback


独自の Callback クラスを構築するには、BaseCallback を継承したクラスを作成する必要があります。これにより、イベント( _on_training_start 、_on_step )や便利な変数(RLモデルの self.model など)にアクセスできます。

ここではカスタムコールバックの2つのサンプルを紹介します。1つはトレーニングの報酬に応じて最適なモデルを保存するためのもの(セクション「サンプル」を参照)と、Tensorboardを使用して追加の値をログに記録するためのもの(「Tensorboard」セクションを参照)です。

from stable_baselines.common.callbacks import BaseCallback


class CustomCallback(BaseCallback):
    """
    BaseCallback クラスを継承したカスタムコールバック

    :param verbose: (int) Verboseレベル 0:出力なし 1: infoのみ 2: debugレベルまで
    """
    def __init__(self, verbose=0):
        super(CustomCallback, self).__init__(verbose)
        # 以下の変数にはコールバック内でアクセス可能
        # (ベースクラス上で定義済み)
        # 強化学習モデル
        # self.model = None         # タイプ: BaseRLModel
        # トレーニングに使用される環境のエイリアス
        # self.training_env = None  # タイプ: Union[gym.Env, VecEnv, None]
        # コールバックが呼び出された回数
        # self.n_calls = 0          # タイプ: int
        # self.num_timesteps = 0    # タイプ: int
        # ローカル変数辞書、グローバル変数辞書
        # self.locals = None        # タイプ: 辞書[文字列, Any]
        # self.globals = None       # タイプ: 辞書[文字列, Any]
        # レポートに使用されるロガーオブジェクト
        # self.logger = None        # タイプ: logger.Logger
        # # イベントコールバックでは、親オブジェクトにアクセスできると
        # # 便利な場合がある
        # self.parent = None        # タイプ: オプション[BaseCallback]

    def _on_training_start(self) -> None:
        """
        このメソッドは最初のロールアウトが始まる前に呼び出される。
        """
        pass

    def _on_rollout_start(self) -> None:
        """
        ロールアウトは、現在のポリシを使用した環境対話の集合である。
        このイベントは、新しいサンプルを収集する前にトリガされる。
        """
        pass

    def _on_step(self) -> bool:
        """
        このメソッドは env.step() が呼び出されるたびに
        モデルによって呼び出される。

        (EventCallback の) 子コールバックの場合、イベントがトリガされた
        ときに呼び出される。

         :return:(真偽値) コールバックが False を返した場合、トレーニングは早期中止される.
        """
        return True

    def _on_rollout_end(self) -> None:
        """
        このイベントは、ポリシを更新する前にトリガされる。
        """
        pass

    def _on_training_end(self) -> None:
        """
        このイベントは learn() メソッド終了前にトリガされる。
        """
        pass


注意
self.num_timesteps は、環境で実行されたステップの総数に対応する。つまり環境の数に env.step() が呼び出された時間の数を掛けたものである。
PPO1および TRPO は、 MPI に依存しているため、(各ステップではなく)各ロールアウト後に self.num_timesteps を知って更新する。
その他のアルゴリズムの場合、self.num_timesteps は n_envs、env.step() を呼び出すたびに、(環境数)ずつ増加する。


注意
SAC、DDPG、TD3、DQNなどのオフポリシアルゴリズムの場合、rollout の概念は、2つの更新の間の環境で実行されるステップに対応する。



EventCallback


Kerasと比較すると、Stable Baselinesは EventCallback というイベントをトリガするための BaseCallback のセカンドタイプを提供します。イベントがトリガされると、子コールバックが呼び出されます。

例として、EventCallbackのサブクラスのEvalCallback (後述セクション参照)は、新しい最適なモデルがある場合、その子のコールバックをトリガします。子コールバック StopTrainingOnRewardThreshold の場合、強化学習モデルによって達成された平均報酬がしきい値を超えた場合にトレーニングを停止します。

注意
EvalCallback と StopTrainingOnRewardThreshold のソースコードを確認して、この種のコールバックで実現できることの概要を理解することを推奨。


class EventCallback(BaseCallback):
    """
    イベント発生時にコールバックをトリガするためのベースクラス。

    :param callback: (オプション[BaseCallback]) イベントがトリガーされた時に呼び出されるコールバック
    :param verbose: (int)
    """
    def __init__(self, callback: Optional[BaseCallback] = None, verbose: int = 0):
        super(EventCallback, self).__init__(verbose=verbose)
        self.callback = callback
        # 親にアクセスする権限を与える
        if callback is not None:
            self.callback.parent = self
    ...

    def _on_event(self) -> bool:
        if self.callback is not None:
            return self.callback()
        return True


Callbackコレクション


安定したベースラインは、以下のような共通コールバックセットを提供します。
 

  • モデルを定期的に保存する (CheckpointCallback)
  • モデルを定期的に評価し最適なモデルを保存する (EvalCallback)
  • コールバックの連鎖 (CallbackList)
  • イベントのコールバックのトリガ(イベントコールバック、EveryNTimesteps)
  • 報酬のしきい値に基づいてトレーニングを早期に停止する (StopTrainingOnRewardThreshold)


CheckpointCallback


save_freq ステップごとにモデルを保存する Callback クラスです。ログフォルダ( save_path ) を引数に指定します。またオプションとしてチェックポイントファイルの接頭文字列を指定できます( デフォルトは rl_model )。

from stable_baselines import SAC
from stable_baselines.common.callbacks import CheckpointCallback
# 1000ステップごとにチェックポイントを保存する
checkpoint_callback = CheckpointCallback(save_freq=1000, save_path='./logs/',
                                         name_prefix='rl_model')

model = SAC('MlpPolicy', 'Pendulum-v0')
model.learn(2000, callback=checkpoint_callback)



StopTrainingOnRewardThreshold


一時的な報酬(評価に対する平均エピソード報酬)のしきい値に達したら(つまりモデルが十分に良いときに)、トレーニングを停止します。EvalCallback と一緒に使用し、新しい最良のモデルによってトリガされるイベントを使用する必要があります。

import gym

from stable_baselines import SAC
from stable_baselines.common.callbacks import EvalCallback, StopTrainingOnRewardThreshold

# 評価用環境
eval_env = gym.make('Pendulum-v0')
# モデルが報酬のしきい値に達したときにトレーニングを停止する
callback_on_best = StopTrainingOnRewardThreshold(reward_threshold=-200, verbose=1)
eval_callback = EvalCallback(eval_env, callback_on_new_best=callback_on_best, verbose=1)

model = SAC('MlpPolicy', 'Pendulum-v0', verbose=1)
# タイムステップの数はほぼ無限(1e10)だが、
# 報酬のしきい値に達するとすぐにトレーニングを停止する
model.learn(int(1e10), callback=eval_callback)



EveryNTimesteps


n_steps ステップごとに子コールバックをトリガするイベントコールバック。

注意
PPO1やTRPOはMPIに依存するため、s_stepは2つのイベント感の下限となる。


import gym

from stable_baselines import PPO2
from stable_baselines.common.callbacks import CheckpointCallback, EveryNTimesteps

# これは CheckpointCallback (save_freq = 500) を定義することと同等
# checkpoint_callback は 500 ステップごとにトリガされる
checkpoint_on_event = CheckpointCallback(save_freq=1, save_path='./logs/')
event_callback = EveryNTimesteps(n_steps=500, callback=checkpoint_on_event)

model = PPO2('MlpPolicy', 'Pendulum-v0', verbose=1)

model.learn(int(2e4), callback=event_callback)



レガシ:機能的アプローチ


警告
以下書かれているコールバックの実装方法は、オブジェクト指向アプローチを支持するため
廃止となった。
(以下略)


----


本読んだときのコールバック、たしかに実装がダサかった。

OpenAI Gymの環境クラスって、ビジネスアプリケーションの設計アプローチで書かれている。理由はシミュレータが業務アプリと科学技術計算のファサードだから。

そこが理解できるかできないかで、強化学習をさくっと一般業務アプリケーションへ適用できるかどうかのスピードが変わる。

 とはいえトレーニング実装は、いわゆる機械学習ライブラリ側の責務なので、無理にオブジェクト指向にシなくてもいいと思っている。..けど、実装しやすいPyTorchにTensorFlowが押されているのって、科学技術計算指向脳で設計されいたライブラリだからなので、今の風潮はこっちが正解なのかもしれない..

 

..まあ、使わせて頂く見としては、どうでもいいけど..

2020年8月11日火曜日

SONY VAIO VPCZ1 (SSD)にUbuntu 20.04 をインストールする



..簡単に読めるが、かなりてこずった...

ぶっちゃけ、1週間弱かかった


DVD/CD-ROMドライブが付属しているので
ISOイメージをDVDに焼いて刺して起動すれば
インストールできる..と思っていたのだけど

Ubuntu Server 20.04 LTS ISO イメージを使うと
インストール中にキーボードが反応せず
タイムアウトになり先に進まない。

Ubuntu Desktop 日本語Remix 20.04 ISOイメージを使うと
なぜかインストール作業中に電源オフする。


VAIO VPCZ1 のSSD構成は
ハードRAID構成で提供されている。

ので上記インストールを

  • RAID-0
  • 1パーティション遺し残りをRAID-0
  • RAIDなし(つまり複数のSCSIディスク)

でインストールを試したがどれもだめ。

で、結局やったのが
18.04LTSをインストールしてマイグレーションする方法。


  • Ctrl+IでRAIDなし構成にした状態で
  • Ubuntu Server 18.04 LTS ISOイメージで焼いたDVDをさし
  • Live Ubuntu OS を起動
  • 18.04インストールアイコンをダブルクリックしてインストール
  • 再起動
  • sudo apt update && sudo apt upgrade -y
  • 再起動
  • sudo do-release-upgrade -d
  • 再起動


これでようやく最初のディスクに20.04がインストールできた。

でも..理由がわからない..本当にRAIDだけの問題なのだろうか..

上記の問題やうまくいった手順は自分のVAIOだけなのだろうか..

他に持ってる人がいないので、調べようがない

syslogや/var/logも見たけど、よくわからなかった。

なので、上記はすべてたまたまうまくいった手順を書き留めただけなのかもしれない..


 

p.s

2020/8/18 追記:

GNOMEをインストールしたら矢印アイコンだけ表示され

それ以外一切何も出てこない..


なのでKDEをインストール(sudo apt kubuntu-desktop -y)しました..

2020年6月17日水曜日

aws-iot-device-sdk-js を使っている人へ



こんなトラップに引っかかるのは
私だけかもしれませんが..



AWS IoT Core をJavaScriptから使いたくて

(2020/6/17 本記事執筆段階の)公式ドキュメント

AWS IoT Device SDK for JavaScript をインストールする
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-device-sdk-node.html


にかかれている

git clone https://github.com/aws/aws-iot-device-sdk-js.git

を実行して JavaScript SDK をつかっていた。


実は
この記事を書く1年前ほどから
この JavaScript SDK を使っていたのだけど、

なんとなく
機能がしょぼいなあ..
とか
デバイスシャドウを使うとき
pub/subするためのデバイスのコネクションが一旦切れる
(デバイスシャドウを使い終わるとreconnectする)
なあ..
とか
なんか洗練されていないなあ
ださいなあ..


とおもいながら
使っていた。

で、
昨日重大なことに気づいた。

先程書いた GitHub サイトを
ブラウザで開くと
README.mdの先頭に


New Version Available
A new AWS IoT Device SDK is now available. It is a complete rework, built to improve reliability, performance, and security. We invite your feedback!

This SDK will no longer receive feature updates, but will receive security updates.



..


....


.....最新バージョンじゃないじゃん....


しかも
最新バージョンは
別のリポジトリだと...


公式ドキュメント(これを書いている時点)も
ふるいほうのリポジトリじゃん..

公式ドキュメントの手順だと
このサイトのREADME.mdひらかず
いきなり git clone しろって
書いてあるじゃん..




v2のリポジトリはこちら

[GitHub] aws/aws-iot-device-sdk-js-v2
https://github.com/aws/aws-iot-device-sdk-js-v2




p.s.

v2のサンプル、読みづらい。

jsってかいてるけど
TypeScriptでサンプル書かれてるし..
しかも非同期 async や Promise つかってるし..
yield とかつかってるし..
リファレンスマニュアル全然読みにくいし..


つかってもらおうっていう
おもてなしの心が
まったく感じられん...


Google Colab相当の環境をローカルPC上のDocker Composeで実現する

 Docker Composeの有料版に、お金を払いたくない ^H^Hが使えない ため古いゲーミングPC(GeForce3080)にDocker Composeを入れてその上のコンテナでGoogle Colab相当のノート操作ができないかためしてみた: コンテナイメージとして a...