0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

破いても自己修復する日章旗をセルオートマトンて作った

0
Last updated at Posted at 2026-07-26

面白かったらシェア、いいねよろしくお願いします!

国旗を破くと犯罪になるぞ

2026年7月、日本国旗の損壊などを罰する日本国旗損壊罪法が成立しました(7/17成立、7/24公布、8/13 施行)。だとすれば、いちばん安全な旗は、破いても自分で元に戻る旗でしょう。というわけで、破壊すると自己修復するデジタル日章旗を、ブラウザ上で動くように作りました。

クリックで破ける/放置で自己修復するライブデモはこちらです。

破いても自己修復する日章旗

何を作ったか

やったことはだいたい 4 つです。Python と PyTorch で Growing Neural Cellular Automata(以下 NCA)を学習し、目標は縦横比 2:3 の日章旗、自己修復する regenerating レジームで訓練しました。推論側は、学習済みの重みだけを書き出して、更新則を Rust から WebAssembly に移植してブラウザで走らせています。重み(約 33KB)は Cloudflare R2 に置き、ブラウザから fetch して毎フレーム canvas に描いています。

元ネタは Mordvintsev らの Growing Neural Cellular Automata(Distill, 2020)です。

Neural Cellular Automata とは

各ピクセルを 1 個の細胞とみなし、全細胞がまったく同じ小さなニューラルネットを共有します。学習パラメータは約 8,300 個しかありません。細胞が見られるのは自分と近傍 3×3 の状態だけ。それでも 1 個のシード細胞から画像全体が立ち上がり、一部を削っても再生します。中央集権的な設計図はどこにもなく、修復のルールは局所的な相互作用の中に分散して埋め込まれている、という点が面白いところです。

状態は [16, H, W] のグリッドです。先頭 4 チャンネルが可視の RGBA(premultiplied)、残り 12 が細胞の隠れ状態になります。1 ステップの更新則は、固定の depthwise 3×3(恒等・Sobel-X・Sobel-Y、これは学習しない)で近傍を知覚して 48 チャンネルにし、1x1 conv 48→128 と ReLU、1x1 conv 128→16(バイアス無し・ゼロ初期化)で差分を出し、各細胞を fire_rate=0.5 の確率でのみ更新し、最後に 3×3 近傍のアルファ最大が 0.1 を超える細胞だけを残す、というものです。境界は循環(トーラス)にしてあります。

知覚が固定で、学習されるのは 1×1 畳み込み 2 層だけ。つまり更新則はピクセルごとの小さな行列積に過ぎず、重い ML ランタイムは要りません。これがブラウザ移植を簡単にしてくれます。

学習レジーム

論文の Experiment 1-3 に対応する 3 種類があり、違いは「どの状態から CA を回し始めるか」だけです。

regime バッチの供給元 結果
growing 毎回シード 目標形状には到達するが、放置すると崩壊する
persistent サンプルプール 到達後も形状を保つ
regenerating プール + 円形の損傷 切り取られても再生する

外から破壊を注入する用途では regenerating が必須です。学習中にわざと形を壊して戻す経験を積ませて初めて、再生能力が身につきます。growing や persistent で訓練したモデルは、一度壊すと元に戻りません。

目標画像はアルファが教師になる

NCA の教師データは目標画像 1 枚だけです。しかもアルファチャンネルがそのまま「細胞が存在すべき領域」の教師になります。そのため不透明な RGB 画像は弾き、透過付き RGBA だけを受け付けるようにしました。

日章旗は正式には縦横比 2:3、日章の直径は縦の 3/5、中央配置です。正方形グリッドに 2:3 の旗をレターボックス配置し、旗の外側は透明にしました。こうすると NCA の正方形グリッドを保ったまま、旗そのものは正しい 2:3 になります。

from PIL import Image, ImageDraw

CANVAS = 360
flag_h = 200
flag_w = flag_h * 3 // 2   # 2:3
disc_d = flag_h * 3 // 5   # 日章 = 縦の 3/5

img = Image.new("RGBA", (CANVAS, CANVAS), (0, 0, 0, 0))  # 全面透明
draw = ImageDraw.Draw(img)
fx0, fy0 = (CANVAS - flag_w) // 2, (CANVAS - flag_h) // 2
draw.rectangle((fx0, fy0, fx0 + flag_w - 1, fy0 + flag_h - 1), fill=(255, 255, 255, 255))
r = disc_d / 2
draw.ellipse((CANVAS/2 - r, CANVAS/2 - r, CANVAS/2 + r, CANVAS/2 + r), fill=(188, 0, 45, 255))
img.save("hinomaru.png")

学習

学習は uv 管理の Python パッケージで回しました。GPU は RTX 5060、CUDA 12.8 です。

uv run nca train --target assets/hinomaru.png --checkpoint artifacts/hinomaru.pt
# regenerating, 8000 steps, 約16分, 最終 loss ~0.0001

損失は可視 4 チャンネルの二乗誤差だけで、隠れチャンネルには一切の教師を与えません。何を表現に使うかは CA に委ねられます。

ブラウザで動かす

重みをフラットバイナリに書き出す

.pt は PyTorch 専用の形式なので、学習される 3 テンソル(hidden.weight, hidden.bias, output.weight)だけを、自己記述ヘッダ付きの little-endian f32 として直列化します。知覚カーネルは固定値なので書き出しません。読み手側が同じ定義を持つ前提にして、二重管理を避けています。

offset  type       内容
0       4s         マジック "NCAW"
4       u32        フォーマット版
8       u32        channels (16)
12      u32        hidden (128)
16      f32        fire_rate
20      f32[H*C3]  hidden.weight  (行優先 [hidden, channels*3])
...     f32[H]     hidden.bias
...     f32[C*H]   output.weight  (行優先 [channels, hidden])

合計で約 8,320 float、およそ 33KB です。マジックと次元をヘッダに入れておくと、読み手が壊れた重みを黙って受け入れずに済みます。

更新則を Rust から wasm に移植する

PyTorch の 1 ステップを 1:1 で Rust に移植します。wasm 線形メモリを JS から読みます。

// 固定 Sobel(/8)。知覚は学習しないので Rust 側に定義を持つ。
let sobel_x = ((tr - tl) + 2.0 * (mr - ml) + (br - bl)) / 8.0;
let sobel_y = ((bl - tl) + 2.0 * (bc - tc) + (br - tr)) / 8.0;

// hidden = relu(W_h · perception + b_h) → delta = W_o · hidden
// 発火抽選は 1 細胞 1 回、noise <= fire_rate のときだけ更新。
// 生存マスクは、更新前(was_alive) と 更新後(is_alive) の両方で生きている細胞だけ残す。

wasm-pack build --target web でビルドし、ワークスペースパッケージとして website から import("nca-wasm") します。

描画

wasm から straight-alpha の RGBA を受け取り、putImageData で描くだけです。CA は成長・再生の余地として周囲の余白を必要とするので、計算はグリッド全体で回しつつ、表示だけ中央をクロップして旗を大きく見せています。canvas のクリックは、その点を円形に破壊するようにしました。学習時に与えている損傷と同じ操作です。

おわりに

固定の知覚と 1×1 畳み込み 2 層、約 8,300 パラメータの極小モデルが、破壊から自律的に形を取り戻す。設計図なしの分散的な修復という点が、生命っぽくて気に入っています。学習から重みエクスポート、Rust/wasm 推論まで一通り書いたので、興味があれば動かしてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?