TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

ジョイスティック入力の可視化ツール

久しぶりに格闘ゲームをプレイしたらコマンドがぜんぜん入らない。
スーパーファミコンスト2をプレイしてた頃は、一人で2P対戦をしてコマンドの練習した思い出がある。

今ではPCがあるので、入力中のコマンドを可視化して、コマンドが失敗した原因をつかめるようにしてみた。

XInputVisualizer

C#XInputのライブラリを使って、入力の可視化ツールを作成した。
入力の残像が残る形にして、入力されていない方向がないか、余計な方向入力をしていないかが見て分かるようにした。

youtu.be

ゲームをウィンドウモードにして、これをゲームプレイ中に表示していると、失敗の原因が分かるようになる。
また、練習中に見ながら入力すると、注意力が上がるのでコマンド成功率がかなり上がることがわかった。

自分がスマホアプリで公開している「ボーカル音程モニター」というソフトがあるが、これも歌声を可視化することで効果的に上達できる。

コマンド入力でも可視化が結構役に立つことがわかった。

ダウンロード

需要があるか不明なツールだが、オープンソースで公開する。
Releaseからビルド済みバイナリがダウンロードできる。
実行には.NET 5のランタイムが必要である。
GitHub - TadaoYamaoka/XInputVisualizer

追記

ピアノロール風のタイムライン表示を追加した。
f:id:TadaoYamaoka:20211219175240p:plain

Qugiyのビット演算を試す その2

昨日香車の利きと、飛車の縦方向の利き、歩の駒打ちについて、Qugiyのアピール文章のビット演算を実装して、速度を測定を行ったが効果がないことがわかった。

今回は、飛車と角の利きのビット演算を実装して、測定を行った。

飛車と角の利き

変更前

ZEN2を除いてCPUがBMI2に対応している場合はPEXTを使用し、ZEN2の場合はMagic Bitboardを使用している。
Magic Bitboardの解説は、やねうら王のブログを参照

変更後

Qugiyのアピール文章2の、飛車と角の利きのビット演算を実装した。
解説は、やねうら王のブログを参照
やねうら王ではBitboardのメソッドとして実装されており、非常に分かりやすかったので、流用させてもらった(感謝)。
Aperyの香車の利きテーブルの初期化処理は、飛車の利きを利用しており、飛車の利きのビット演算版では縦方向には香車の利きを利用しているため、作り直した。

doMoveでのcheckersBBの更新処理で、横方向の飛車の利きに、縦方向も合わせて利きを計算している処理は、横方向のみにできるようになったため、変更した。
やねうら王では別の方法で実装されていたが、修正が多くなるので、rookAttackRankを利用した。

case DirecRank:
    st_->checkersBB |= attacksFrom<Rook>(ksq) & bbOf(Rook, Dragon, us);

から

case DirecRank:
    st_->checkersBB |= rookAttackRank(ksq, occupiedBB()) & bbOf(Rook, Dragon, us);

に変更。

速度測定

香車の利きと飛車の縦方向の利きを変更したもので、変更前後のNPSを測定した。
floodgateからサンプリングした100局面を1秒思考した際のNPSを10回測定して平均を算出した。

GPU1枚(RTX3090)、Core i9 2スレッド
変更前 変更後 変更後/変更前
平均 29154 29074 99.6%
中央値 30302 30343 100.1%
最小値 18150 17386 95.8%
最大値 31735 31769 101.8%

平均値、中央値、最大値はほぼ変わらず誤差の範囲である。
最小値は低下している。
PEXTに対応したintelのCPUでは、速度改善の効果がないと判断してよさそうだ。

GPU8枚(A100)、ZEN2 4スレッド
変更前 変更後 変更後/変更前
平均 284701 287216 101.0%
中央値 287675 290034 100.9%
最小値 221218 230250 97.3%
最大値 335950 329870 106.2%

8GPU、ZEN2 4スレッドだと、平均値、中央値、最大値で速度が上がっている。
平均で1%程度の向上が確認できた。
変更後/変更前の最小値で下がっているのが気になるが、値の方の最小値は上がっているので、特定の局面で少し下がる場合があっても影響は少ないと考える。

まとめ

飛車と角の利きについてQugiyのビット演算を実装して、変更前後の速度を測定した。
結果、PEXTが使用できるintelのCPUでは速度は変わらず、ZEN2のCPUでは平均で1%NPSが向上することがわかった。

昨日の測定で効果のなかった香車の利きと、飛車の縦方向の利き、歩の駒打ちについては取り込まず、飛車と角の利きについてはmasterに取り込む予定である。

Qugiyのビット演算を試す

やねうら王の方で、Qugiyのビット演算の取り込みがされているので、dlshogiも追随したいと思う。

ひとまず実装が簡単な香車の利きと、飛車の縦方向の利き、歩の駒打ちについて対応を行い、速度の測定を行った。

香車の利き

変更前

dlshogiはAperyのコードを流用しており、変更前は以下のようになっている。

inline Bitboard lanceAttack(const Color c, const Square sq, const Bitboard& occupied) {
    const int part = Bitboard::part(sq);
    const int index = (occupied.p(part) >> Slide[sq]) & 127;
    return LanceAttack[c][sq][index];
}

sqの筋のoccupiedからインデックスに変換して、LanceAttackテーブルから表引きして利きのビットボードを求めている。
Slide[sq]は、

const int Slide[SquareNum] = {
    1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
    10, 10, 10, 10, 10, 10, 10, 10, 10,
    19, 19, 19, 19, 19, 19, 19, 19, 19,
    28, 28, 28, 28, 28, 28, 28, 28, 28,
    37, 37, 37, 37, 37, 37, 37, 37, 37,
    46, 46, 46, 46, 46, 46, 46, 46, 46,
    55, 55, 55, 55, 55, 55, 55, 55, 55,
    1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
    10, 10, 10, 10, 10, 10, 10, 10, 10
};

と定義されており、1段のoccupiedは値が0でも1でも結果は同じなので、インデックスを8bitにして節約している。

変更後

Qugiyのアピール文章を元に、やねうら王でビットボードのpartごとに処理を分けているのを参考にして実装した。
処理の解説は、やねうら王のブログで解説されているので省略する。

template <Color US>
inline Bitboard lanceAttack(const Square sq, const Bitboard& occupied) {
    if (US == Black) {
        if (Bitboard::part(sq) == 0) {
            const u64 se = lanceAttackToEdge(US, sq).p(0);
            u64 mocc = se & occupied.p(0);
            mocc |= mocc >> 1;
            mocc |= mocc >> 2;
            mocc |= mocc >> 4;
            mocc >>= 1;
            return Bitboard(~mocc & se, 0);
        }
        else {
            const u64 se = lanceAttackToEdge(US, sq).p(1);
            u64 mocc = se & occupied.p(1);
            mocc |= mocc >> 1;
            mocc |= mocc >> 2;
            mocc |= mocc >> 4;
            mocc >>= 1;
            return Bitboard(0, ~mocc & se);
        }
    }
    else {
        if (Bitboard::part(sq) == 0) {
            // 9段目が0、その他のマスが1になっているmask
            constexpr u64 mask = 0x3fdfeff7fbfdfeffULL;
            const u64 em = ~occupied.p(0) & mask;
            const u64 t = em + pawnAttack(US, sq).p(0);
            return Bitboard(t ^ em, 0);
        }
        else {
            // 9段目が0、その他のマスが1になっているmask
            constexpr u64 mask = 0x000000000001feffULL;
            const u64 em = ~occupied.p(1) & mask;
            const u64 t = em + pawnAttack(US, sq).p(1);
            return Bitboard(0, t ^ em);
        }
    }
}

飛車の縦方向の利き

変更前

LanceAttackテーブルを先手と後手について表引きして論理和をとっている。

inline Bitboard rookAttackFile(const Square sq, const Bitboard& occupied) {
    const int part = Bitboard::part(sq);
    const int index = (occupied.p(part) >> Slide[sq]) & 127;
    return LanceAttack[Black][sq][index] | LanceAttack[White][sq][index];
}
変更後

lanceAttackを先手と後手について求めて論理和をとっても良いが、同一の条件文が2回実行されるのを省略するため、1つの処理にする。
これも、やねうら王の実装を参考にした。

inline Bitboard rookAttackFile(const Square sq, const Bitboard& occupied) {
    if (Bitboard::part(sq) == 0) {
        // 先手の香の利き
        const u64 se = lanceAttackToEdge(Black, sq).p(0);
        u64 mocc = se & occupied.p(0);
        mocc |= mocc >> 1;
        mocc |= mocc >> 2;
        mocc |= mocc >> 4;
        mocc >>= 1;

        // 後手の香車の利き
        // 9段目が0、その他のマスが1になっているmask
        constexpr u64 mask = 0x3fdfeff7fbfdfeffULL;
        const u64 em = ~occupied.p(0) & mask;
        const u64 t = em + pawnAttack(White, sq).p(0);

        return Bitboard((~mocc & se) | (t ^ em), 0);
    }
    else {
        // 先手の香の利き
        const u64 se = lanceAttackToEdge(Black, sq).p(1);
        u64 mocc = se & occupied.p(1);
        mocc |= mocc >> 1;
        mocc |= mocc >> 2;
        mocc |= mocc >> 4;
        mocc >>= 1;

        // 後手の香車の利き
        // 9段目が0、その他のマスが1になっているmask
        constexpr u64 mask = 0x000000000001feffULL;
        const u64 em = ~occupied.p(1) & mask;
        const u64 t = em + pawnAttack(White, sq).p(1);

        return Bitboard(0, (~mocc & se) | (t ^ em));
    }
}

速度測定

香車の利きと飛車の縦方向の利きを変更したもので、変更前後のNPSを測定した。
floodgateからサンプリングした100局面を1秒思考した際のNPSを10回測定して平均を算出した。

GPU1枚(RTX3090)、Core i9 2スレッド
変更前 変更後 変更後/変更前
平均 29144 28990 99.3%
中央値 30260 30316 100.2%
最小値 18205 17384 94.9%
最大値 31713 31847 100.6%

平均値、中央値、最大値はほぼ変わらず誤差の範囲である。
最小値は低下している。
速度改善の効果がないと判断してよさそうだ。

GPU8枚(A100)、ZEN2 4スレッド
変更前 変更後 変更後/変更前
平均 304283 279886 92.0%
中央値 308605 283135 92.0%
最小値 229106 217205 87.6%
最大値 354780 328941 96.0%

8GPU、ZEN2 4スレッドだと、平均値、中央値、最小値、最大値すべてで低下した。

歩の駒打ちのビット演算

変更前

歩の駒打ちの二歩の回避は、変更前は以下の通り実装されている。

			// 一段目には打てない
			const Rank TRank1 = (US == Black ? Rank1 : Rank9);
			toBB.andEqualNot(rankMask<TRank1>());

			// 二歩の回避
			Bitboard pawnsBB = pos.bbOf(Pawn, US);
			Square pawnsSquare;
			foreachBB(pawnsBB, pawnsSquare, [&](const int part) {
					toBB.set(part, toBB.p(part) & ~squareFileMask(pawnsSquare).p(part));
				});

歩のあるすべての筋について、マスクを作成して論理積を計算している。

変更後

Qugiyのアピール文章では、二歩の回避をビット演算で求める方法を提示しているが、やねうら王の実装を参考にして、一段目を除外する処理と合わせて求めるようにした。

			// 二歩の回避
			Bitboard pawnsBB = pos.bbOf(Pawn, US);
			// 9段目が1のbitboard
			const Bitboard left(0x4020100804020100ULL, 0x0000000000020100ULL);
			Bitboard t = left - pawnsBB;
			// 一段目には打てない
			if (US == Black) {
				t = (t & left) >> 7;
				toBB &= left ^ (left - t);
			}
			else {
				t = (t & left) >> 8;
				toBB &= left.notThisAnd(left - t);
			}

速度測定

歩の駒打ちを変更したもので、変更前後のNPSを測定した。
floodgateからサンプリングした100局面を1秒思考した際のNPSを10回測定して平均を算出した。

GPU1枚(RTX3090)、Core i9 2スレッド
変更前 変更後 変更後/変更前
平均 29114 29086 99.9%
中央値 30172 30266 100.1%
最小値 18183 17947 95.8%
最大値 31673 31750 100.4%

平均値、中央値、最大値はほぼ変わらず誤差の範囲である。
最小値は低下している。
こちらも速度改善の効果がないと判断してよさそうだ。

GPU8枚(A100)、ZEN2 4スレッド
変更前 変更後 変更後/変更前
平均 304283 292713 96.2%
中央値 308605 295337 95.9%
最小値 229106 225959 92.1%
最大値 354780 337019 101.2%

8GPU、ZEN2 4スレッドだと、平均値、中央値、最小値で低下し、最大値はわずかに速くなった。

まとめ

香車の利きと、飛車の縦方向の利き、歩の駒打ちについてQugiyのビット演算を実装して、変更前後の速度を測定した。
結果、速度は変わらないかむしろ遅くなることがわかった。
表引きしていた部分をビット演算に置き換えており、処理する命令数は増えているため、表がキャッシュに載っていればかえって遅くなったのではないかと考える。

飛車と角の飛び利きについても別途実装して測定する予定である。

CUDAマルチストリーム対応

先日、CUDAのメモリの非同期転送に対応したことを記事にした。
その際に、マルチストリームに対応することで、転送だけではなく演算処理も並列化できることを書いた。

その後、マルチストリームの実装を行い、2GPUでは、初期局面でNPSが36%向上することを確認したが、8GPUではほとんど変わらないかかえって遅くなることがわかった。

原因を推測すると、8GPUのサーバは、CPUとGPU間は、PCI Expressで直結でなく、PCIeスイッチを介して接続されており*1、PCIeスイッチの帯域がボトルネックになっていのではないかと考えた。
PCIe4.0x16の帯域(256Gbps)の4倍までに制限されるため、1,2GPUだと速くなり、8GPUだと遅くなる理由としては納得できる。

対策

帯域がボトルネックとすると、以下の対策が有効である。
(1) GPUごとのストリーム数を、探索スレッド数と同じにするのではなく、2つに制限する
(2) CPUからGPUに転送する際のデータ型をFP16にする

測定

それぞれ対策した場合の8GPUでのNPSを測定した。
モデルは第2回電竜戦のdlshogiの15ブロックのモデルを使用した。

floodgateからサンプリングした100局面を1秒思考した際のNPSを3回測定して平均を算出した。
対策(1)のみは結果が良くなかったので1回のみ測定した。

シングルストリーム 対策(1) 対策(1)+(2)
平均値 290579 295309(101.5%) 344963(118.4%)
中央値 293918 294581(100.7%) 345824(117.5%)
最小値 207550 220396(85.7%) 240279(102.6%)
最大値 341644 361907(113.9%) 434519(135.2%)

※()内の%は、シングルストリームに対する比である。

対策(1)のみでは、平均、中央値ではNPSは変わっておらずマルチストリームの効果はでていない。
GPUあたりのストリームが2でも、帯域がボトルネックになっているようだ。

対策(1)+(2)では、平均で18.4%、中央値で17.5%NPSが向上した。
対策(2)を行うことで、CPUからGPUに転送前にFP16に変換して、また結果をFP16で転送してCPUでFP32にすることで、転送のデータ量が半分になる。
NPSが向上したことから、やはりCPU<->GPU間の帯域がボトルネックとなっていたと考えられる。

まとめ

マルチストリームに対応し、CPU<->GPU間をFP16で転送することで、NPSが平均で18.4%向上した。
レーティングがどれくらい向上するかは別途計測予定である。

マルチストリーム対応版のソース:
GitHub - TadaoYamaoka/DeepLearningShogi at feature/multi_stream
※実験用のためFP16決め打ちになっている

2つのストリームを複数スレッドで共有するため、セマフォ排他制御した。
C++セマフォは標準で対応しているのは、C++20からであったので、mutexとcondition_variableを使って実装した。
参考:c++ - C++0x has no semaphores? How to synchronize threads? - Stack Overflow

EfficientZeroを試す

NeurIPS 2021で提案されたEfficientZeroを試してみた。

EfficientZeroは、MuZeroのようなモデルベースの強化学習の手法で、サンプル効率が非常に高いことが特徴になっている。

DQNでは、5億フレーム(約38日間のリアルゲーム時間)が必要だったが、EfficientZeroでは、40万フレーム(2時間のリアルゲーム時間)で人間のパフォーマンスを上回っている。
サンプル効率は、現実世界に強化学習を適用しようとした場合に課題になるため、強化学習の応用範囲が広がることが期待できる。

オープンソースでコードが公開されているため、実際に試してみた。

環境には、BreakoutNoFrameskip-v4を使用した。

訓練

はじめ、CUDAが使えるようになったWSL2で試そうしたが、GPU1枚だと時間がかかりすぎるので、GPU(V100)8枚の環境で試した。
学習するフレーム数は、2時間のリアルゲーム時間であっても、学習時間は8GPUでも16時間かかった。
Pythonで実装されているため、MCTSのシミュレーションが遅いのが原因かもしれない。

訓練のパラメータは、用意されていたtrain.shを8GPUを使うように修正して、メモリが不足したので、object_store_memoryをデフォルトの半分にした。

$ cat train.sh
set -ex
export CUDA_DEVICE_ORDER='PCI_BUS_ID'
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7

python main.py --env BreakoutNoFrameskip-v4 --case atari --opr train --force \
  --num_gpus 8 --num_cpus 40 --cpu_actor 14 --gpu_actor 20 \
  --seed 0 \
  --use_priority \
  --use_max_priority \
  --amp_type 'torch_amp' \
  --info 'EfficientZero-V1' \
  --object_store_memory 68719476736

訓練のログは、resultsの下に出力される。

results/atari/EfficientZero-V1/BreakoutNoFrameskip-v4/seed=0/Mon Dec  6 14:40:26 2021/logs$ cat train.log
[2021-12-06 14:40:26,721][train][INFO][main.py><module>] ==> Path: /work/results/atari/EfficientZero-V1/BreakoutNoFrameskip-v4/seed=0/Mon Dec  6 14:40:26 2021
[2021-12-06 14:40:26,721][train][INFO][main.py><module>] ==> Param: {'action_space_size': 4, 'num_actors': 1, 'do_consistency': True, 'use_value_prefix': True, 'off_correction': True, 'gray_scale': False, 'auto_td_steps_ratio': 0.3, 'episode_life': True, 'change_temperature': True, 'init_zero': True, 'state_norm': False, 'clip_reward': True, 'random_start': True, 'cvt_string': True, 'image_based': True, 'max_moves': 27000, 'test_max_moves': 3000, 'history_length': 400, 'num_simulations': 50, 'discount': 0.988053892081, 'max_grad_norm': 5, 'test_interval': 10000, 'test_episodes': 32, 'value_delta_max': 0.01, 'root_dirichlet_alpha': 0.3, 'root_exploration_fraction': 0.25, 'pb_c_base': 19652, 'pb_c_init': 1.25, 'training_steps': 100000, 'last_steps': 20000, 'checkpoint_interval': 100, 'target_model_interval': 200, 'save_ckpt_interval': 10000, 'log_interval': 1000, 'vis_interval': 1000, 'start_transitions': 2000, 'total_transitions': 100000, 'transition_num': 1, 'batch_size': 256, 'num_unroll_steps': 5, 'td_steps': 5, 'frame_skip': 4, 'stacked_observations': 4, 'lstm_hidden_size': 512, 'lstm_horizon_len': 5, 'reward_loss_coeff': 1, 'value_loss_coeff': 0.25, 'policy_loss_coeff': 1, 'consistency_coeff': 2, 'device': 'cuda', 'debug': False, 'seed': 0, 'value_support': <core.config.DiscreteSupport object at 0x7f9b7e3794f0>, 'reward_support': <core.config.DiscreteSupport object at 0x7f9b7e379580>, 'weight_decay': 0.0001, 'momentum': 0.9, 'lr_warm_up': 0.01, 'lr_warm_step': 1000, 'lr_init': 0.2, 'lr_decay_rate': 0.1, 'lr_decay_steps': 100000, 'mini_infer_size': 64, 'priority_prob_alpha': 0.6, 'priority_prob_beta': 0.4, 'prioritized_replay_eps': 1e-06, 'image_channel': 3, 'proj_hid': 1024, 'proj_out': 1024, 'pred_hid': 512, 'pred_out': 1024, 'bn_mt': 0.1, 'blocks': 1, 'channels': 64, 'reduced_channels_reward': 16, 'reduced_channels_value': 16, 'reduced_channels_policy': 16, 'resnet_fc_reward_layers': [32], 'resnet_fc_value_layers': [32], 'resnet_fc_policy_layers': [32], 'downsample': True, 'env_name': 'BreakoutNoFrameskip-v4', 'obs_shape': (12, 96, 96), 'case': 'atari', 'amp_type': 'torch_amp', 'use_priority': True, 'use_max_priority': True, 'cpu_actor': 14, 'gpu_actor': 20, 'p_mcts_num': 8, 'use_root_value': False, 'auto_td_steps': 30000.0, 'use_augmentation': True, 'augmentation': ['shift', 'intensity'], 'revisit_policy_search_rate': 0.99, 'model_dir': '/work/results/atari/EfficientZero-V1/BreakoutNoFrameskip-v4/seed=0/Mon Dec  6 14:40:26 2021/model'}
[2021-12-06 14:43:36,680][train][INFO][log.py>_log] ==> #0          Total Loss: 49.282   [weighted Loss:49.282   Policy Loss: 7.685    Value Loss: 38.391   Reward Sum Loss: 31.993   Consistency Loss: 0.003    ] Replay Episodes Collected: 61         Buffer Size: 61         Transition Number: 2.012   k Batch Size: 256        Lr: 0.000
[2021-12-06 14:52:16,592][train][INFO][log.py>_log] ==> #1000       Total Loss: 0.142    [weighted Loss:0.142    Policy Loss: 8.076    Value Loss: 0.722    Reward Sum Loss: 0.326    Consistency Loss: -3.840   ] Replay Episodes Collected: 61         Buffer Size: 61         Transition Number: 2.012   k Batch Size: 256        Lr: 0.200
[2021-12-06 15:01:10,856][train][INFO][log.py>_log] ==> #2000       Total Loss: -0.803   [weighted Loss:-0.803   Policy Loss: 7.566    Value Loss: 1.042    Reward Sum Loss: 0.289    Consistency Loss: -4.619   ] Replay Episodes Collected: 61         Buffer Size: 61         Transition Number: 2.012   k Batch Size: 256        Lr: 0.200
(略)
[2021-12-07 06:21:44,635][train][INFO][log.py>_log] ==> #116000     Total Loss: -0.055   [weighted Loss:-0.055   Policy Loss: 6.586    Value Loss: 3.227    Reward Sum Loss: 0.584    Consistency Loss: -4.803   ] Replay Episodes Collected: 608        Buffer Size: 608        Transition Number: 100.193 k Batch Size: 256        Lr: 0.020
[2021-12-07 06:28:38,125][train][INFO][log.py>_log] ==> #117000     Total Loss: -0.144   [weighted Loss:-0.144   Policy Loss: 6.856    Value Loss: 3.196    Reward Sum Loss: 0.477    Consistency Loss: -4.851   ] Replay Episodes Collected: 608        Buffer Size: 608        Transition Number: 100.193 k Batch Size: 256        Lr: 0.020
[2021-12-07 06:35:18,312][train][INFO][log.py>_log] ==> #118000     Total Loss: -0.076   [weighted Loss:-0.076   Policy Loss: 6.861    Value Loss: 3.185    Reward Sum Loss: 0.514    Consistency Loss: -4.852   ] Replay Episodes Collected: 608        Buffer Size: 608        Transition Number: 100.193 k Batch Size: 256        Lr: 0.020
[2021-12-07 06:42:05,937][train][INFO][log.py>_log] ==> #119000     Total Loss: -0.055   [weighted Loss:-0.055   Policy Loss: 6.863    Value Loss: 3.213    Reward Sum Loss: 0.548    Consistency Loss: -4.847   ] Replay Episodes Collected: 608        Buffer Size: 608        Transition Number: 100.193 k Batch Size: 256        Lr: 0.020

TensorBoardでも確認できる。
f:id:TadaoYamaoka:20211207164218p:plain

スコアが上がっているのが確認できる。
f:id:TadaoYamaoka:20211207164600p:plain

テスト

訓練できたモデルを使用して、実際にゲームをプレイさせてみた。
テストは、WSL2+WSLgの環境でゲーム画面を確認しながら実行した。

WSL2のUbuntu20.04にAnacondaをインストールして、以下の通り環境構築した。

sudo apt update
sudo apt install build-essential cmake zlib1g-dev -y

conda create -n EfficientZero python=3.8
conda activate EfficientZero
pip install numpy==1.19.5 ray==1.0.0 gym==0.15.7 atari-py==0.2.6 cython==0.29.23
pip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html
pip install tensorboard opencv-python kornia tqdm

test.shを編集して、以下のコマンドを実行した。

python main.py --env BreakoutNoFrameskip-v4 --case atari --opr test --seed 0 --num_gpus 1 --num_cpus 20 --force --test_episodes 8 --load_model --amp_type torch_amp --model_path 'results/atari/EfficientZero-V1/BreakoutNoFrameskip-v4/seed=0/Mon Dec  6 14:40:26 2021/model.p' --info Test --render --save_video

パッケージ不足のエラーがでたので、以下のパッケージをインストールした。

sudo apt-get install ffmpeg
sudo apt-get install python-opengl


8個のウィンドウがわらわらと表示されて、テストの様子が確認できた。
引数--test_episodes 8が同時実行されるエピソードだったようである。
フレームがかくかくして、やはり遅いようである。
f:id:TadaoYamaoka:20211207165124p:plain

動画も保存される。
これは一番うまくプレイできていたエピソード。
youtu.be

まとめ

最新の強化学習手法であるEfficientZeroを試してみた。
たしかに、40万フレームの学習でブロック崩しを人間並みにプレイできるようになることが確認できた。
しかし、学習するゲーム時間は2時間分でも、実行が遅く学習時間はGPU8枚で16時間かかった。

OpenAI Gymのようなstepを操作できる環境であればよいが、リアルタイムで実行するゲームを学習・プレイするにはこの実行速度では厳しそうである。
Pythonで実装されているので、MCTS部分をC++で実装するなどの速度改善は必要そうである。

dlshogiの序盤にランダム性を加える

dlshogi同士で、平手開始局面から対局すると毎回ほとんど同じ棋譜になる。
以前の調査では、先手勝率が65.7%と偏った結果になった。

また、最新のモデルでは先手で角換わりになるため、角換わりに偏った勝率になる。

そのため、平手開始局面からの計測した勝率はあまり信用できない。
そこで、コンピュータ将棋開発者の間では、互角局面集を使って勝率を測定するということが行われている。

序盤にランダム性を加える

互角局面集を使うと、同一棋譜になるのを回避して、様々な戦型での強さを計測できるというメリットがあるが、25手目から開始するため、24手目までの序盤の強さが見られなくなるというデメリットがある。
そこで、序盤にランダム性を加えて、平手開始局面から対局して勝率が落ちない範囲で、局面をばらけさせることを検討した。

また、自己対局時にも初期局面集を使用しているが、序盤の局面が学習データに含まれなくなるため、ランダム性を加えることで序盤局面の勝率を学習させたい。

実現方法

AlphaZeroでは、MCTSの探索結果の訪問回数に応じた確率で選択しているが、訪問回数は最善手に偏るため、最善手以外の手はあまり選択されない。
平手開始局面で、1万ノード探索した際の訪問回数と各手の価値は次のようになる。
f:id:TadaoYamaoka:20211204224256p:plain

価値がほとんど変わらない手でも訪問回数に大きな差がついているのが分かる。
序盤では価値が最善手から少ししか下がらない場合は、その手を選択しても勝率への影響は小さいため、同じくらいの確率で選択して欲しい。

分布に温度パラメータを適用することで、選択されやすくすることができる。
すなわち、訪問回数Nに温度\tauを適用して、N^{1/\tau}とする。

しかし、温度パラメータを適用すると、価値が低い手も選択されやすくなるため、勝率が低下してしまう問題がある。

折衷案として、温度パラメータを適用した上で、価値が最善手から一定以上下がる手を除外することにする。

温度パラメータを10として、価値の閾値を0.02とした場合、平手開始局面では、以下の3手が残り、分布は以下の通りになる。
f:id:TadaoYamaoka:20211204225721p:plain

勝率測定

温度、価値の閾値、何手までランダムムーブを行うかの条件を変えて、持ち時間5分、1手2秒加算で勝率を測定した。
dlshogiは、2GPU、3スレッド。水匠5は30スレッド。

温度10、価値閾値0.02、16手まで
   # PLAYER             :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master             :    93.2   33.2   140.0     204    69     100  128   24   52    12
   2 random_cutoff20    :    -9.4   31.3    95.5     200    48     100   80   31   89    16
   3 suisho5-30th       :   -83.8   33.5    65.5     198    33     ---   56   19  123    10

White advantage = 33.01 +/- 20.40
Draw rate (equal opponents) = 13.26 % +/- 2.07

random_cutoff20がランダムムーブを行ったdlshogiで、masterがランダムを加えていないdlshogiである。

ランダムを加えた場合は、R-102.6になっている。

エンジン間の勝率は以下の通りである。

master vs random_cutoff20: 55-30-18 (62.1%)
Black vs White: 47-38-18 (54.4%)
master playing Black: 33-16-3 (66.3%)
master playing White: 22-14-15 (57.8%)
random_cutoff20 playing Black: 14-22-15 (42.2%)
random_cutoff20 playing White: 16-33-3 (33.7%)

master vs suisho5-30th: 74-22-6 (75.5%)
Black vs White: 46-50-6 (48.0%)
master playing Black: 36-12-3 (73.5%)
master playing White: 38-10-3 (77.5%)
suisho5-30th playing Black: 10-38-3 (22.5%)
suisho5-30th playing White: 12-36-3 (26.5%)

random_cutoff20 vs suisho5-30th: 50-34-13 (58.2%)
Black vs White: 52-32-13 (60.3%)
random_cutoff20 playing Black: 29-11-9 (68.4%)
random_cutoff20 playing White: 21-23-4 (47.9%)
suisho5-30th playing Black: 23-21-4 (52.1%)
suisho5-30th playing White: 11-29-9 (31.6%)

dlshogi間でも、水匠5に対しても、ランダムをムーブを行った方が勝率が下がっている。
ランダムの条件はもっと厳しくした方が良さそうである。

温度10、価値閾値0.015、16手まで
   # PLAYER             :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master             :    51.1   13.2   588.5     978    60      80  493  191  294    20
   2 random_cutoff15    :    41.4   13.6   568.0     976    58     100  483  170  323    17
   3 suisho5-30th       :   -92.6   13.9   306.5     972    32     ---  224  165  583    17

White advantage = 54.09 +/- 8.93
Draw rate (equal opponents) = 19.37 % +/- 1.15

ランダムを加えた場合は、R-9.7と、誤差の範囲に収まっている。

エンジン間の勝率は以下の通りである。

master vs random_cutoff15: 196-197-98 (49.9%)
Black vs White: 212-181-98 (53.2%)
master playing Black: 121-106-19 (53.0%)
master playing White: 75-91-79 (46.7%)
random_cutoff15 playing Black: 91-75-79 (53.3%)
random_cutoff15 playing White: 106-121-19 (47.0%)

master vs suisho5-30th: 297-97-94 (70.5%)
Black vs White: 234-160-94 (57.6%)
master playing Black: 173-36-35 (78.1%)
master playing White: 124-61-59 (62.9%)
suisho5-30th playing Black: 61-124-59 (37.1%)
suisho5-30th playing White: 36-173-35 (21.9%)

random_cutoff15 vs suisho5-30th: 286-127-72 (66.4%)
Black vs White: 256-157-72 (60.2%)
random_cutoff15 playing Black: 167-38-38 (76.5%)
random_cutoff15 playing White: 119-89-34 (56.2%)
suisho5-30th playing Black: 89-119-34 (43.8%)
suisho5-30th playing White: 38-167-38 (23.5%)

dlshogi間はほぼ互角で、水匠5に対しては少しだけ勝率が落ちている。

16手までで重複した棋譜を調べると、
水匠5とランダムムーブなしの場合は、69.2%
水匠5とランダムムーブありの場合は、1.8%
dlshogi間では、38.1%
であった。

水匠5との対局では重複がほとんどなくなっている。

また、dlshogiのランダムムーブ同士で対局した場合、181局で重複は0だった。

温度10、価値閾値0.015、32手まで

32手までランダムムーブを行った場合の結果は以下の通り。

   # PLAYER                   :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master                   :    59.6   18.4   356.0     573    62     100  310   92  171    16
   2 random_cutoff15_ply32    :    13.5   17.8   300.5     571    53     100  257   87  227    15
   3 suisho5-30th             :   -73.1   19.2   198.5     566    35     ---  157   83  326    15

White advantage = 39.69 +/- 11.37
Draw rate (equal opponents) = 16.10 % +/- 1.26

ランダムを加えた場合は、R-46.1となった。
手が進むほどランダムムーブの影響が大きくなると考えられる。

温度5、価値閾値0.015、16手まで

温度を5にした場合の結果は以下の通り。

   # PLAYER                   :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 random_cutoff15_temp5    :    46.1   31.1   119.5     203    59      51  103   33   67    16
   2 master                   :    45.2   30.4   120.5     205    59     100  103   35   67    17
   3 suisho5-30th             :   -91.3   32.4    64.0     200    32     ---   48   32  120    16

White advantage = 73.45 +/- 19.53
Draw rate (equal opponents) = 17.85 % +/- 2.27

ランダムを加えた場合は、R+0.9と、誤差の範囲になっている。

16手までで重複した棋譜を調べると、
水匠5とランダムムーブなしの場合は、44.6%
水匠5とランダムムーブありの場合は、0.0%
dlshogi間では、32.7%
であった。
温度10よりも重複が減っているのは、対局数が少ないことによる偶然と考えられる。

温度10から1度ずつ低下、価値閾値0.02、16手まで

温度10を徐々に低下させるパターンでも測定した。

   # PLAYER                          :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master                          :    58.0   30.0   135.5     221    61      99  116   39   66    18
   2 random_cutoff20_temp10_drop1    :    -7.2   30.2   105.5     218    48      95   91   29   98    13
   3 suisho5-30th                    :   -50.8   29.0    86.0     215    40     ---   74   24  117    11

White advantage = 97.37 +/- 19.02
Draw rate (equal opponents) = 15.08 % +/- 2.01

ランダムを加えた場合は、R-65.2となった。
温度の影響より、価値の閾値の影響が大きいようだ。

まとめ

価値の閾値を0.015(1.5%)、温度10度として、16手までランダムムーブを行うことで、勝率を低下させることなく、相手が水匠5またはランダムムーブのdlshogiの場合、重複をほぼなくせることが分かった。

戦型は、角換わり以外も含まれるようになった。
ただし、振り飛車は含まれないため、様々な戦型での勝率の確認には互角局面集が有効だと思う。

ビルド済みバイナリ

https://gist.github.com/TadaoYamaoka/519f7b63c77cdb89f43fb81ab9641213/raw/71f5b8c9852c7c0ecdcc615e9341376778eece2d/dlshogi-ramdom_move.zip

USIオプションRandom_Plyにランダムムーブを行う手数を設定するとランダムムーブが有効になる(16が推奨値)。
Random_Cutoffには、価値の閾値を千分率で設定する(デフォルト15(1.5%))。
Random_Temperatureには、温度を千分率で設定する(デフォルト10000)。
Random_Temperature_Dropには、温度の一手ごとの低下量を千分率で設定する(デフォルト1000)。

2022/1/2 追記

Random_Temperature_Drop=1.0とRandom_Temperature_Drop=0.0で、勝率がどれくらい変わるか比較した。

A100x1GPU、3スレッドで測定。持ち時間5分、1手2秒加算。

   # PLAYER          :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master          :    74.2   14.7   707.5    1090    65     100  596  223  271    20
   2 drop1           :    19.3   21.7   284.0     535    53      93  232  104  199    19
   3 drop0           :    -8.9   21.3   272.0     551    49     100  220  104  227    19
   4 suisho5-16th    :   -84.6   16.1   363.5    1078    34     ---  286  155  637    14

White advantage = 72.77 +/- 8.14
Draw rate (equal opponents) = 19.60 % +/- 1.04

上記の2GPUで測定した場合は、温度10、価値閾値0.015、16手まででR-9.7だったが、今回の測定(drop0)では、R-83.1(勝率38.26%)になった。
1手ずつ温度を1度下げた場合(drop1)は、R-54.9(勝率42.16%)になった。

前回測定よりもRが低下しているのは、今回は1GPUでの測定のため、探索ノード数が少なくなったことが関係していそうである。
短い持ち時間ではランダムの影響が大きくなるのではないかと考える。

ソフト間の勝率は以下の通り。

drop0 vs master: 85-125-69 (42.8%)
Black vs White: 125-85-69 (57.2%)
drop0 playing Black: 47-47-45 (50.0%)
drop0 playing White: 38-78-24 (35.7%)
master playing Black: 78-38-24 (64.3%)
master playing White: 47-47-45 (50.0%)

master vs suisho5-16th: 172-56-45 (71.2%)
Black vs White: 131-97-45 (56.2%)
master playing Black: 97-22-18 (77.4%)
master playing White: 75-34-27 (65.1%)
suisho5-16th playing Black: 34-75-27 (34.9%)
suisho5-16th playing White: 22-97-18 (22.6%)

drop0 vs suisho5-16th: 135-102-35 (56.1%)
Black vs White: 155-82-35 (63.4%)
drop0 playing Black: 86-33-17 (69.5%)
drop0 playing White: 49-69-18 (42.6%)
suisho5-16th playing Black: 69-49-18 (57.4%)
suisho5-16th playing White: 33-86-17 (30.5%)
drop1 vs master: 74-127-69 (40.2%)
Black vs White: 125-76-69 (59.1%)
drop1 playing Black: 40-42-52 (49.3%)
drop1 playing White: 34-85-17 (31.2%)
master playing Black: 85-34-17 (68.8%)
master playing White: 42-40-52 (50.7%)

master vs suisho5-16th: 172-56-40 (71.6%)
Black vs White: 140-88-40 (59.7%)
master playing Black: 101-17-16 (81.3%)
master playing White: 71-39-24 (61.9%)
suisho5-16th playing Black: 39-71-24 (38.1%)
suisho5-16th playing White: 17-101-16 (18.7%)

drop1 vs suisho5-16th: 158-72-35 (66.2%)
Black vs White: 143-87-35 (60.6%)
drop1 playing Black: 93-22-18 (76.7%)
drop1 playing White: 65-50-17 (55.7%)
suisho5-16th playing Black: 50-65-17 (44.3%)
suisho5-16th playing White: 22-93-18 (23.3%)

16手目での棋譜の重複率は、

vs master vs suisho5
drop0 40.5% 18.9%
drop1 42.2% 7.9%

であった。
dlshogiのmasterに対しては、想定通り、温度低下あり(drop1)の方が、重複が多くなった。
温度低下あり(dop1)の方が、水匠5に対しては重複が少なくなるという想定と異なる結果になった。
たまたま水匠5の手がばらけやすい手を選択していたのかもしれない。

2021/1/4 追記

Random_Temperature=5.0とRandom_Temperature_Drop=0.5で測定した。

A100x1GPU、3スレッドで測定。持ち時間5分、1手2秒加算。

   # PLAYER                :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 master                :    57.0   17.3   372.0     611    61     100  314  116  181    19
   2 random_temp5drop05    :    -8.0   17.7   294.0     607    48     100  242  104  261    17
   3 suisho5-28th          :   -48.9   17.7   246.0     606    41     ---  199   94  313    16

White advantage = 111.20 +/- 11.09
Draw rate (equal opponents) = 18.73 % +/- 1.41

R-65となった。
Random_Temperature=10.0とRandom_Temperature_Drop=1.0よりも、レーティングが低下した。
よりランダム性は低くなっているのに、低下したのは測定結果のブレが大きいためと思われる。

ソフト間の勝率は以下の通り。

master vs random_temp5drop05: 144-99-63 (57.4%)
Black vs White: 158-85-63 (61.9%)
master playing Black: 94-35-24 (69.3%)
master playing White: 50-64-39 (45.4%)
random_temp5drop05 playing Black: 64-50-39 (54.6%)
random_temp5drop05 playing White: 35-94-24 (30.7%)

master vs suisho5-28th: 170-82-53 (64.4%)
Black vs White: 174-78-53 (65.7%)
master playing Black: 112-20-21 (80.1%)
master playing White: 58-62-32 (48.7%)
suisho5-28th playing Black: 62-58-32 (51.3%)
suisho5-28th playing White: 20-112-21 (19.9%)

random_temp5drop05 vs suisho5-28th: 144-117-41 (54.5%)
Black vs White: 181-80-41 (66.7%)
random_temp5drop05 playing Black: 99-35-18 (71.1%)
random_temp5drop05 playing White: 45-82-23 (37.7%)
suisho5-28th playing Black: 82-45-23 (62.3%)
suisho5-28th playing White: 35-99-18 (28.9%)

WSL2+WSLgでOpenAI Gymを動かす

以前に、WSLでOpenAI Gymを動かす記事を書いた。
その際は、Windows側にX Window Serverの「VcXsrv」をインストールして行ったが、Windows 11では、WSLgがX Window Serverの役割を果たすので、Windows側のセットアップなしにLinuxGUIを扱えるようになった。

はじめ、WSLにDockerをインストールして、Dockerコンテナ上で動かそうとしたが、GUIアプリの起動はできるがウィンドウが表示されないため、あきらめて、WSLのUbuntu上に直接、環境構築した。
「wsl --export」でインストール直後のUbuntuの環境を保存しておくと、WSLに同じディストリビューションで複数環境を構築できるため、Dockerがなくてもそれほど困らないだろう。

OpenAI Gymの環境構築手順

Anacondaインストール
$ wget https://repo.anaconda.com/archive/Anaconda3-2021.11-Linux-x86_64.sh
$ bash Anaconda3-2021.11-Linux-x86_64.sh

で、Anacondaをインストールする。

OpenAI Gymインストール
$ pip install gym

実行できるかipythonで、以下のコードを入力して確認する。

import gym
env = gym.make('CartPole-v0')
env.reset()
env.render()

以下のエラーが表示される。

ModuleNotFoundError                       Traceback (most recent call last)
~/anaconda3/lib/python3.9/site-packages/gym/envs/classic_control/rendering.py in <module>
     14 try:
---> 15     import pyglet
     16 except ImportError as e:

ModuleNotFoundError: No module named 'pyglet'

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
<ipython-input-4-0fb217d1bc14> in <module>
      1 for _ in range(1000):
----> 2     env.render()
      3     env.step(env.action_space.sample()) # take a random action
      4 env.close()

~/anaconda3/lib/python3.9/site-packages/gym/core.py in render(self, mode, **kwargs)
    293
    294     def render(self, mode="human", **kwargs):
--> 295         return self.env.render(mode, **kwargs)
    296
    297     def close(self):

~/anaconda3/lib/python3.9/site-packages/gym/envs/classic_control/cartpole.py in render(self, mode)
    177
    178         if self.viewer is None:
--> 179             from gym.envs.classic_control import rendering
    180
    181             self.viewer = rendering.Viewer(screen_width, screen_height)

~/anaconda3/lib/python3.9/site-packages/gym/envs/classic_control/rendering.py in <module>
     15     import pyglet
     16 except ImportError as e:
---> 17     raise ImportError(
     18         """
     19     Cannot import pyglet.

ImportError:
    Cannot import pyglet.
    HINT: you can install pyglet directly via 'pip install pyglet'.
    But if you really just want to install all Gym dependencies and not have to think about it,
    'pip install -e .[all]' or 'pip install gym[all]' will do it.


pip install gym[all]をインストールしろということなので、実行すると、MuJoCoをインストールしろと言われる。

  Running setup.py clean for mujoco-py
Failed to build box2d-py mujoco-py
Installing collected packages: lockfile, importlib-resources, glfw, pyglet, opencv-python, mujoco-py, lz4, box2d-py, ale-py
    Running setup.py install for mujoco-py ... error
    ERROR: Command errored out with exit status 1:
     command: /home/xxxx/anaconda3/bin/python -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py'"'"'; __file__='"'"'/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-s0ldhh8m/install-record.txt --single-version-externally-managed --compile --install-headers /home/xxxx/anaconda3/include/python3.9/mujoco-py
         cwd: /tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/
    Complete output (56 lines):
    running install
    running build

    You appear to be missing MuJoCo.  We expected to find the file here: /home/xxxx/.mujoco/mjpro150

    This package only provides python bindings, the library must be installed separately.

    Please follow the instructions on the README to install MuJoCo

        https://github.com/openai/mujoco-py#install-mujoco

    Which can be downloaded from the website

        https://www.roboti.us/index.html

    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py", line 32, in <module>
        setup(
      File "/home/xxxx/anaconda3/lib/python3.9/site-packages/setuptools/__init__.py", line 153, in setup
        return distutils.core.setup(**attrs)
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/core.py", line 148, in setup
        dist.run_commands()
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/dist.py", line 966, in run_commands
        self.run_command(cmd)
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/dist.py", line 985, in run_command
        cmd_obj.run()
      File "/home/xxxx/anaconda3/lib/python3.9/site-packages/setuptools/command/install.py", line 61, in run
        return orig.install.run(self)
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/command/install.py", line 546, in run
        self.run_command('build')
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/cmd.py", line 313, in run_command
        self.distribution.run_command(command)
      File "/home/xxxx/anaconda3/lib/python3.9/distutils/dist.py", line 985, in run_command
        cmd_obj.run()
      File "/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py", line 28, in run
        import mujoco_py  # noqa: force build
      File "/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/mujoco_py/__init__.py", line 3, in <module>
        from mujoco_py.builder import cymj, ignore_mujoco_warnings, functions, MujocoException
      File "/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/mujoco_py/builder.py", line 502, in <module>
        mjpro_path, key_path = discover_mujoco()
      File "/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/mujoco_py/utils.py", line 93, in discover_mujoco
        raise Exception(message)
    Exception:
    You appear to be missing MuJoCo.  We expected to find the file here: /home/xxxx/.mujoco/mjpro150

    This package only provides python bindings, the library must be installed separately.

    Please follow the instructions on the README to install MuJoCo

        https://github.com/openai/mujoco-py#install-mujoco

    Which can be downloaded from the website

        https://www.roboti.us/index.html

    ----------------------------------------
ERROR: Command errored out with exit status 1: /home/xxxx/anaconda3/bin/python -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py'"'"'; __file__='"'"'/tmp/pip-install-p63nh23w/mujoco-py_a2ff8ae5e2664451813a1a75ca3ccbfd/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-s0ldhh8m/install-record.txt --single-version-externally-managed --compile --install-headers /home/xxxx/anaconda3/include/python3.9/mujoco-py Check the logs for full command output.

MuJoCoは、最近オープンソースになったが、バージョン1.5はライセンスが必要なため、gym[all]ではなくgym[atari]をインストールすることにした。

$ pip install gym[atari]

再び、動作を確認したところ、今度は、python-openglをインストールしろと言われた。

ImportError: Library "GLU" not found.

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
<ipython-input-4-d9761596d5d9> in <module>
----> 1 env.render()

~/anaconda3/lib/python3.9/site-packages/gym/core.py in render(self, mode, **kwargs)
    293
    294     def render(self, mode="human", **kwargs):
--> 295         return self.env.render(mode, **kwargs)
    296
    297     def close(self):

~/anaconda3/lib/python3.9/site-packages/gym/envs/classic_control/cartpole.py in render(self, mode)
    177
    178         if self.viewer is None:
--> 179             from gym.envs.classic_control import rendering
    180
    181             self.viewer = rendering.Viewer(screen_width, screen_height)

~/anaconda3/lib/python3.9/site-packages/gym/envs/classic_control/rendering.py in <module>
     27     from pyglet.gl import *
     28 except ImportError as e:
---> 29     raise ImportError(
     30         """
     31     Error occurred while running `from pyglet.gl import *`

ImportError:
    Error occurred while running `from pyglet.gl import *`
    HINT: make sure you have OpenGL installed. On Ubuntu, you can run 'apt-get install python-opengl'.
    If you're running on a server, you may need a virtual frame buffer; something like this should work:
    'xvfb-run -s "-screen 0 1400x900x24" python <your_script.py>'

そこで、python-openglをインストールする。

$ pip install python-opengl

再度、動作を確認したことろ、無事CartPoleの画面が表示できた。
f:id:TadaoYamaoka:20211205143820p:plain
初回は数十秒くらい時間がかかった。

まとめ

最新のOpenAI GymはWindowsでも動くようになっているが、強化学習のライブラリは、マルチプロセスで実行している場合が多く、Windowsはforkに対応していないため、spawnにコードを修正する必要がある場合があった。
WSLでGUIが扱えるようになると、Linuxと同等に動かせるので、Windowsでの強化学習の検証が行いやすくなった。

余談

自宅のメインで使っているPCは、CPUがSkylakeだったため、Windows 11にアップグレードできなかった。
そこで、将棋AIの開発用に買って、ThreadRipperに買い替えたので余っていたCore i9 7900xにマザーボードとセットで換装した。
OSの再インストールとかしたくなったので、SSDはそのままで交換したが特に問題なく起動できた。