DOOMゲームでニューラルネットワークを構築しました
I built a neural network using DOOM games (github.com)
要約
このプロジェクト「DOOMFORMER」は、古典的なFPSゲーム「DOOM」のゲームメカニクス(ヴードゥー人形、コンベアベルト、溶岩、ドアなど)を利用してニューラルネットワークを実装したものです。プレイヤーの操作やゲーム内の要素が、ニューラルネットワークの計算プロセス(行列演算、活性化関数、アテンションなど)を表現します。これにより、ゲーム内でニューラルネットワークの推論を実行し、その結果を検証することが可能です。
全文翻訳
[画像: DOOMFORMER: マリーンが悪魔の大群と地獄のような空の下で戦う]
DOOMのゲームメカニクスで動作するニューラルネットワーク:ヴードゥー人形、コンベアベルト、溶岩、ドア。
動画を見る · Colabで試す: 数字を描くとDOOMがそれを読み取ります。インストール不要。
[画像: テスト] [画像: ビット単位で正確:許容誤差0] [画像: 速度:NumPyより約7万倍遅い] [画像: 悪魔:思考中はフリーズ] [画像: MIT]
数字はコンベアベルト上のヴードゥー人形の群れです。活性化関数は、彼らが登らなければならない階段です。行列演算は、各線が部屋の明るさを上げる溶岩の線の上を走る人形です。アテンションは、別の数字が下げた天井の下をすり抜ける人形です。人形が止まると、ゲームから明るさが読み出され、独立した整数参照と比較されます。1つでも値がずれると、実行は失敗します。
マリーンの脳は、彼が戦っているレベルにコンパイルされます。右側の点は、彼の1,345体のヴードゥー人形です。彼らは彼の変換器を実行し、次の行動を選択します。レベルは悪魔を感知し、引き金を引きます。動画を見る。
数字を歩かせると、DOOMがそれを読み取る
入力はレベル内の28×28の床です。FIREを押し続けながらマリーンが歩いたタイルはインクで染まり、レベルはそのピクセルを脳に書き込みます。彼はREADを押します。1,355体のヴードゥー人形がMNISTネットワークを実行し、45ペアの数字スコアすべてを比較し、勝者を数えます。勝者の人形だけが通過し、その数字を壁に点灯させます。Pythonはクロックを進め、784ピクセル、隠れ層、すべての比較、そして答えを読み出し、NumPyで計算された値とすべて比較します。
doomformer draw # ウィンドウを開く:矢印キーで移動、Ctrlを押しながら描画、SpaceでREADスイッチ
doomformer draw --digit 7 # ヘッドレス:マリーンが7を描く(Pythonがプレイヤーの代わりにキー操作を行う)
▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▄████████████████
████▀▀ ▀████▀
▀▀▀ ▄███▀
████
███
███
███
████
███▀
████
▀███
DOOMが読み取った数字:7 (右) -- 勝者の人形が7セグメントディスプレイに点灯させた
EXACT:DOOMが計算したすべての248の値(隠れ層、45回の比較、10回の勝者数、それぞれにペアがあり、ワンホットエンコーディングされた答え)は、DOOMが持つ784ピクセルのポートから計算されたNumPy整数参照と一致します(許容誤差0)。1つのディスプレイが点灯
pixels:784ポートはレベルのキャンバス(ペンがインクを付けたタイル)と一致します
READパス:5,705ゲームティック = DOOMで実質時間79秒(35ティック/秒)、Pythonは58コマンド、各読み出しスクリプトの後に確認スクリプトを送信しました(0は1回の進捗以上待機)、その他なし(0は拒否)
0から9までの各数字を3つのバリエーションで歩かせた結果:30個すべて正しく読み取られ、すべて正確でした。DOOMがリアルタイムで実行されるウィンドウでは、READには2分45秒かかります。ヘッドレスでは76~99秒です。仕組み · 動画
スキルレベルを選択
pip install git+https://github.com/mottopanikeiku/doomformer # Python 3.11+, Linux または macOS
初回実行時にDOOMのスクリプトコンパイラがビルドされるため、git、cmake、Cコンパイラが必要です。または、すべてスキップできます。Colabノートブックはそれをインストールし、DOOMに読み取らせる数字を描画でき、softmaxデモを実行します。
| スキル | コマンド | DOOMの動作 | 時間 |
|---|---|---|---|
| I'm too young to die | doomformer hello | 小さなニューラルネットワークを実行 | 3秒 |
| Hey, not too rough | doomformer mnist | 手書き数字を読み取る | 約2分 |
| Hurt me plenty | doomformer demo | DOOMのレベル名を生成 | 20秒 |
| Ultra-Violence | doomformer ask "what does iddqd do" | 質問に答える | 7~11分 |
| Nightmare! | doomformer demo --nightmare | 106マップ、56,390体のヴードゥー人形 | 2~4分 |
時間は、8つのDOOMゲームを同時に実行する16コアのLinuxマシンからのものです。
I'm too young to die: 見られるもの
小さな整数MLP 4-6-5-3を1つのDOOMマップにコンパイル:60体のヴードゥー人形、1222セクター
入力 1/5 x=[-71, -82, 64, 37] -> y=[11, 4, -15] EXACT (3つの出力 + 22の隠れ読み出しが整数参照と一致;488ゲームティック)
...
5/5 EXACT
Hey, not too rough: DOOMが手書き数字を読み取る
[画像: 8つのMNISTテスト数字とDOOMが読み取った結果:7 2 1 0 4 9 6 3、すべて正確]
784ピクセル → 64 → 45回の比較 → 10回の勝者数 → 答え、すべて1つのDOOMマップ内:1,355体のヴードゥー人形と158,550セクター。各インクピクセルは、隠れ層の重み線の上を走る人形を解放します。人形は次に各数字ペアのスコアを比較し、9回の比較すべてに勝った数字だけが人形を通過させます。DOOMが数字を選択し、Pythonがそれをチェックします。DOOMでは、ネットワークは最初の100個のMNISTテスト数字のうち99個を正しく読み取り、10,000個からサンプリングされた1,000個のうち966個を読み取り、すべて正確でした。10,000個すべてで同じ整数ネットワークが97.16%の精度を出します。
$ doomformer mnist --digits 2 --seed 1
███
████ ▄▄▄█████████▄▄
████ ▀█████▀▀▀▀▀████
████ ▄▄███▀
███▄ ▄▄▄███████▄▄
████ ████▀▀▀▀▀▀█████
████ ▀███
████ ▄▄ ▄██▀
▀████ █████▄▄██████▀
▀██ ▀▀▀▀▀▀▀▀▀▀
DOOMが読み取った数字:1 (右) DOOMが読み取った数字:3 (右)
テスト #46 テスト #51
2/2 正しく読み取られました | 2/2 EXACT:DOOMが計算したすべての値(数字あたり248個:隠れ層、比較、勝者数、
ワンホットエンコーディングされた答え)はNumPy整数参照と一致します(許容誤差0)。Pythonのみが比較しました | 数字あたり5,696ゲームティック | 88秒
写真や描画から自分の数字を読み取るには:doomformer mnist --image my7.png。
Hurt me plenty: トランスフォーマーがレベル名を生成する
136個の公式DOOMレベル名でトレーニングされたトランスフォーマーが、38個の小さなマップに分割され、DOOMゲームのプールがそれを実行します。Pythonは整数をゲーム間でコピーし、各文字をDOOMが計算したロジットから選択します。
DOOMが記述:'GHEER' (トレーニングコーパスにはない:モデルが生成した)
8つの並列DOOMゲームで174回のタイル実行 | タイルマップに10,462体のヴードゥー人形 |
6,902の値が読み出され、NumPy整数参照と比較されました(許容誤差0) | 15秒
EXACT:DOOMが公開したすべての値が整数参照と一致し、参照は同じ名前を書き込みます
--seed 3、--seed 4などを指定すると、他の名前が得られます。--one-mapは、レイヤー間にPythonを挟まずに、トランスフォーマー全体を単一のマップに配置します(埋め込み、アテンション、MLP、ロジット)。1文字あたり約90秒かかります。
--softmaxは、softmaxアテンションでトレーニングされた同じモデルを実行します。人形は指数関数(対数間隔の階段)を計算し、実行時にそれらの合計で割ります(比較回数)。その仕組みはdocs/HOW_IT_WORKS.mdにあります。
DOOMが記述:'FINAL INEALL' (トレーニングコーパスにはない:モデルが生成した)
8つの並列DOOMゲームで494回のタイル実行 | タイルマップに4,886体のヴードゥー人形 |
106,834の値が読み出され、NumPy整数参照と比較されました(許容誤差0) | 59秒
EXACT:DOOMが公開したすべての値が整数参照と一致し、参照は同じ名前を書き込みます
--one-map --softmaxは、レイヤー間にPythonを挟まずに単一マップ内で実行します:4,209体の人形、1文字あたり約2分。
--model doomnames_modernは、RMSNormとゲート付き(ReGLU)MLPをsoftmaxアテンションに追加します。人形は平方和(奇数の和)と整数平方根(d·k²の階段)およびその除算も計算します。MILLを170回のタイル実行で書き込み、62,000の値すべてが正確でした。
それは自律的に書き込みます。 --one-map --loopは、Pythonがループ内にない単一マップをビルドします。各位置で人形は30個のロジットすべてを比較し、勝者の人形が次の位置のトークン入力をその文字に移動させます。次にレベルは巻き戻り、自身で次のパスを実行します。6トークンのコンテキストで5パスです。Pythonはクロックを進め、読み出すだけです。各パスのすべての値がチェックされ、最終的なテキストは貪欲な生成と一致する必要があります。DAN(5,875体、パスあたり約3分)を書き込み、--softmaxではDAGEを書き込みました。
Ultra-Violence: DOOMに質問する
DOOMに関する189個の事実でトレーニングされた2層トランスフォーマー。数千のDOOMゲームインスタンスを横断するヴードゥー人形がすべての値を計算し、すべての値がチェックされます。最初の文字は数分かかり、残りは約20秒ごとに到着します。DOOMではすべて正確な回答の一部:
| あなたの質問 | DOOMの回答 |
|---|---|
| what does iddqd do | GOD MODE |
| what is love | INVULNERABILITY |
| what is a voodoo doll | BERSERK |
| can it run doom | SIXTY SECONDS |
チートコードを知っています。それ以外のことを尋ねると、自信に満ちたDOOMのナンセンスが返ってきます。
Nightmare!: 大きな脳
11,341個の実際のDOOMレベル名でトレーニングされた2層、24,279パラメータのトランスフォーマーが、106個のマップにコンパイルされ、56,390体のヴードゥー人形と785,460セクターを使用します。これらの設定でDEMON CANYONと書き込みます。これは12,934個のトレーニング名には含まれていません:
106個のタイルマップ(56,390体のヴードゥー人形、785,460セクター)が6秒でビルドされました;8つのDOOMゲームプロセスで実行中...
DOOMが記述:'DEMON CANYON'
8つの並列DOOMゲームで1,690回のタイル実行 | タイルマップに56,390体のヴードゥー人形 |
120,666の値が読み出され、NumPy整数参照と比較されました(許容誤差0) | 135秒
EXACT:DOOMが公開したすべての値が整数参照と一致し、参照は同じ名前を書き込みます
1つのニューロン(L1.mのユニット33)の前にあるドアを閉じると、同じ脳がDEMON DEATHと書き込みます。
仕組み
| [画像: 各ヴードゥー人形が溶岩の重み線の上を横切る] | [画像: ヴードゥー人形がアテンションゲートの下に押し込められている] |
| 行列演算。各人形は活性化の1単位であり、各溶岩線が横切ると部屋の明るさに重みが加算されます。 | アテンション。人形は、別のライブ値によって下げられた天井ゲートの下をすり抜けます。誰が通過したかを数えるのは乗算です。 |
| [画像: 7体のヴードゥー人形が閉まったドアに止められている] | [画像: レベルDEMON CANYON] |
| 除去はドアです。スイッチを撃つとドアが閉まり、1つのニューロンの人形は決して到着しません。 | DEMON CANYON、DOOM脳が書いた名前から作られたレベルで、DOOM上で実行されるマリーンによってプレイされます。 |
flowchart LR
NET["あなたのネットワーク<br/>(PyTorch / NumPy)"] --> INT["整数<br/>(量子化)"]
INT --> MAP["DOOMマップ<br/>レーン、ゲート、溶岩線、ドア"]
MAP --> RUN["ViZDoomが実行<br/>ヴードゥー人形が歩く"]
RUN --> READ["読み出し<br/>セクターの明るさレベル"]
READ --> CHECK{"== NumPy整数<br/>参照?"}
CHECK -->|"すべての値"| EXACT["EXACT"]
CHECK -->|"1つずれている"| ERR["MismatchError"]
ロード
DOOMは、追加のプレイヤー1スタートをすべてヴードゥー人形に変換します。プレイヤーのコピーであり、コンベアに乗って横切るすべての線にトリガーをかけます。活性化値5は5体の人形を意味します。階段は、値がそのステップの高さをクリアした場合にのみ人形を通過させ、これによりReLU、丸め、クランプが1つの形状で実現されます。合計はセクターの明るさレベルに蓄積され、これは飽和するint16値であることが判明します。
正確な結果を得るためには、エンジンを測定する必要がありました。同じティックで1つのセクターで2回の床移動:1つは静かに失われます。テレポートラインは、同じティックで以前に横切った線をキャンセルします。約34,000ユニットより広いマップは、物を間違った部屋に入れます。完全なリストはdocs/HOW_IT_WORKS.mdおよびdocs/engine_limits.mdにあります。
名誉の殿堂
より大きな実験、すべて正確です。コードはdemos/にあり、トレーニングはtraining/に、トレーニング済みのすべてのモデルはmodels/にあります。各実験の数値と注意点はdocs/DEMOS.mdおよびdocs/FLY.mdにあります。
| [画像: DOOM脳がDOOMゲームの壁にDEMON CANYONと書き込んでいる] | DOOM脳がDOOMレベルを生成。 DEMON CANYON、1,594個のDOOMタイルインスタンスにまたがって記述。動画 |
| [画像: DOOMがDEMON CANYONをプレイ] | DOOMがDOOMをプレイ。 ポリシーが55個のDOOMゲームで実行されるマリーンが、見たことのないDOOMが命名したレベルを歩く:ドア、2回のキル、出口。102回の決定、すべて正確。動画 |
| [画像: 1つのDOOMゲーム:空の脳がゲーム内のエイムボットから学習] | DOOMが学習。 重みは天井の高さ、学習はヴードゥー人形がそれらを上げ下げすることです。空の脳がゲーム内のエイムボットによって教えられ、最初の人生で4キル、次に24、26、22、24を記録。931/931の決定が正確。動画 |
| [画像: THE FLY IS ALIVE: ハエ回路のMN9ニューロンがスパイクし、マリーンがヘルスボーナスに突進してそれを食べる] | ショウジョウバエ。 FlyWireコネクトームからの255ニューロン回路が、1つのマップ内からマリーンを操作:食べる、拒否する、グルーミングする、逃げる。169/169ステップ正確。動画 · コード |
| [画像: 1つのマップ、1つのトランスフォーマー:すべての点がヴードゥー人形;それらはレイヤーごとに1回のフォワードパスを進み、モデルはHと書く] | 1つのマップに収まるトランスフォーマー。 埋め込み、アテンション、MLP、ロジットがPythonなしでレイヤー間に配置:3,548体の人形がSHILと書く。動画 |
- 彼の脳はレベルそのもの。 レベルスクリプトが最も近い悪魔を感知し、マリーンの1,345体のヴードゥー人形が彼のトランスフォーマーを実行して行動を選択し、レベルが引き金を引きます。Pythonはクロックを進め、読み出します。50/50の決定が正確。動画 · コード
- ロボトミー。 彼のDOOM脳への7発のショットガンブラストが彼をキル数0の平和主義者に変えます。この病変は3,000回のランダムブラストのスキャンから選ばれました。動画
- ビッグワン。 4層トランスフォーマーが10,166個のDOOMタイルインスタンスと190万ゲームティックにわたってUNHOLY STRONGHOLDと書き込みます。動画
- ショーリール。
あなた自身のビルド
独自のネットワークをDOOMマップにコンパイルし、その出力でドアを開けたり、ライトを点滅させたり、マリーンを押し出したりできます。
import doomformer as df
net = df.MLP.from_torch(my_torch_mlp, calib_x, levels=8) # または手動でdf.MLP(...)、またはdf.MLP.from_numpy(...)
print(net.quantization_report(test_x)) # 整数モデルが浮動小数点モデルにどれだけ近いか
with df.compile(net, actions={"y0": [df.act.door_open(tag=7)]}, world=my_room) as brain:
y = brain(net.encode(x)) # ヴードゥー人形によって計算され、正確に検証される
brain.record([net.encode(x)], "dolls.gif") # 人形がそれを行う様子を撮影
スパイクネットワーク(df.SpikingNet)、トランスフォーマー(df.Transformer.load("doomnames"))、畳み込みネットワーク(from_torchはConv1d/2dを正確に低下させます)、およびマップ内で重みを更新する学習者(df.Perceptron)は同じように機能します。df.with_argmaxは人形が勝者出力を選択できるようにします。df.Agentは、センスと筋肉もレベルに配置します。1つのマップに大きすぎるモデルは、mode="tiles"を使用してDOOMゲームのプールに分散され、doomformer planがどのマップが必要かを示します。
ガイドはdocs/BUILD_YOUR_OWN.mdにあり、12個の小さな実行可能な例があります。呪われたものを作りましたか?ショー&テル。
その他のDOOM
1つのマップは1つのゲームであり、1つのゲームは1つのコアを使用します。df.run_manyは、各入力を次のアイドル状態のゲームに渡し、各パスをチェックし、出力を順序通りに返します。ゲームはあなたのマシンまたはあなたが開始した任意のRayクラスターで実行されます。
1つの16コアマシンで14個のDOOMゲームで1,000個のMNISTテスト数字を処理するのに2時間かかりました:966個が正しく読み取られ、1,000個が正確で、5,696,000ゲームティック(DOOMの実質時間で45時間)でした。各数字の結果はmodels/mnist/doom_1000.jsonにあります。
ys = df.run_many(net, xs, workers=8) # このマシンで8個のDOOMゲーム
ys = df.run_many(net, xs, backend="ray") # あなたのRayクラスターのすべてのCPU
pip install "doomformer[ray]"
ray start --head # 次に他のマシンで `ray start --address=HEAD:6379`
doomformer mnist --all --ray --record mnist.json # すべての10,000個のMNISTテスト数字、すべての結果を1つのファイルに
MNISTマップの場合、ゲームあたり1コアと1.25GBのRAMを予算計上してください。詳細はdocs/scaling.mdにあります。
箱の中身
doomformer/ | ツールキット:マップコンパイラ、DOOMランタイム、正確なチェック、CLI |
demos/ | 研究コード:彼の脳はレベル、ウォーカーとレベルジェネレーター、ハエ回路、学習アリーナ、カメラ |
training/ | 各モデルの量子化対応トレーニング、およびレベル名とDOOM事実コーパスとそのソース |
models/ | トレーニングログと評価を含むすべてのトレーニング済みモデル |
examples/ | 12個の単一ファイル例、ほとんどが15秒未満 |
tests/ | 187個のテスト、うち88個は実際のヘッドレスDOOMで実行、失敗しなければならない改ざんコントロールを含む |
docs/ | 仕組み、各デモとその数値、エンジンの事実、ツールキットガイド |
チートコード(FAQ)
IDCLIP これは本当にDOOMですか?
これはViZDoom 1.3.2で、ZDoomベースのエンジンにFreedoomの無料アセットを使用し、ヘッドレスで実行されます。マップはZDoomのUDMFフォーマットとACSレベルスクリプトを使用します。ACSは入力ゲートを移動させ、人形をリセットし、結果をコピーアウトします。クローズドループレベルでは、センス(最も近い悪魔、マリーンが歩いたキャンバスのタイル)を行い、人形が選択したアクションを実行します。すべての乗算加算と活性化は、線の上を横切る人形であり、DRAWPAD、mnist、および彼の脳はレベルそのものでは、最終的な選択も同様です。
IDKFA Pythonは何をするのですか?
マップをコンパイルし、入力を書き込み、待機し、結果を読み取り、チェックします。DRAWPADと彼の脳はレベルそのものでは、レベルは独自の入力を書き込みます。多くのマップに分割されたモデルの場合、Pythonはゲーム間で整数をコピーし、テキストの場合は、demo --one-map --loopを除く、DOOMが計算したロジットから各文字を選択します。この場合、人形がすべての文字を選択し、自分で書き戻します。アテンションはReLUスタイルの整数アテンション、または人形によって計算されるsoftmaxアテンション(demo --softmax)です。doomnames_modernはRMSNormとゲート付きMLPを追加します。トレーニングは、マップ内で独自の重みを更新する学習者を除き、オフラインでPyTorchで行われます。
IDDQD DOOMが計算したこと、Pythonではないことをどうやって知るのですか?
すべての値は実行中のゲームから読み出され、マップを見ることのないNumPy整数参照と比較され、許容誤差は0です。テストでは、それを壊そうともします。コンパイルされたマップの1つの重み線を5から6に上げると、チェックはその線に人形が交差する入力でのみ正確に+1で失敗します。読み出しがPythonが書き込んだ値をエコーするようにし、エンジンが必要とするバリアなしでマップをビルドし、settleチェックがViZDoomの4,096オブジェクトリストを信頼できるようにします。これらのそれぞれは捕捉されなければなりません。
IDSPISPOPD 速いですか?
いいえ。1回の小さなMLPパスは、DOOMでは726ミリ秒、NumPyでは11マイクロ秒かかり、約70,000倍遅いです。手書き数字1つを読み取るのに5,705ゲームティックかかります:ヘッドレスで76~99秒、DOOMの実質時間で2分45秒。悪魔はマリーンが思考している間フリーズしています。速度は決して目標ではありませんでした。
IDBEHOLD マリーンは実際にプレイを学習しますか?
彼は正確な悪魔の位置を見るスクリプト化されたエイムボットを模倣します。これは86個の可能な観測値にビン付けされます。これは教師あり模倣です。報酬駆動型RLやピクセルからの学習はありません。ここで重要なのは、ネットワークがどこで実行されるかです。
IDDT なぜ?
人々は電卓、妊娠検査薬、芝刈り機でDOOMを実行しました。これは逆方向です:DOOMがコンピュータなのです。
ライセンス
コードとドキュメント:MIT。出荷されたレベル名リスト(Doom Wiki、CC BY-SA 4.0;id Softwareのソース、GPL-2.0)および100個のサンプルMNIST数字(CC BY-SA 3.0)は、それらのライセンスを保持します。詳細はTHIRD_PARTY.mdを参照してください。FreedoomグラフィックスはBSDライセンスです。DOOMはid Software / ZeniMaxの商標であり、このプロジェクトはそれらとは提携していません。