TadaoYamaokaの開発日記

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

Hugging Face TrainerでMNISTを学習

PyTorchで画像分類モデルを学習するとき、学習ループやチェックポイント管理、TensorBoard対応などを毎回自前で実装するのはやや煩雑である。

Hugging Face Transformers の Trainer を使うことで、NLP用途だけでなくCNNのような画像モデルでも、シンプルかつ統一的な学習基盤を構築できる。

本記事では、MNISTを題材として、

  • CNNモデルの実装
  • Hugging Face Trainer を使った学習
  • 分散学習 (torchrun)
  • Resume training
  • TensorBoard ログ管理
  • Optimizer / Scheduler 設定

について紹介する。

ソース

github.com

特徴

  • jsonargparseで、YAML形式で設定管理
  • PyTorch Lightningと同様のインクリメンタルなバージョン管理
  • マルチGPU対応

学習実行方法

基本的な学習は以下で実行できる。

python train.py --config config.yaml

これだけで、

  • モデル保存
  • checkpoint保存
  • TensorBoardログ
  • evaluation
  • scheduler
  • optimizer

などが Trainer によって自動管理される。

分散学習 (torchrun)

GPUを複数枚使う場合は torchrun を利用する。

torchrun --nproc_per_node 2 train.py --config config.yaml

Trainer は DistributedDataParallel (DDP) に自然対応しているため、追加実装はほぼ不要である。

PyTorch Lightningに近い手軽さで分散学習を扱える。

出力ディレクトリ管理

デフォルト動作

training.output_dir を設定しない場合、デフォルトでは logs/ 以下に version 管理される。

例:

logs/version_0
logs/version_1
logs/version_2

実験ルート変更

CLIから experiment root を変更することも可能である。

python train.py --config config.yaml --experiment_root runs

すると以下のように生成される。

runs/version_0
runs/version_1

output_dir を直接指定

もし training.output_dir を設定した場合は、そのディレクトリを直接使用する。

python train.py \
  --config config.yaml \
  --training.output_dir runs/manual_experiment

この場合は version_* ディレクトリは作成されない。

Resume Training

学習途中から再開する場合は checkpoint を指定する。

python train.py \
  --config config.yaml \
  --training.resume_from_checkpoint logs/version_0/checkpoint-844

TensorBoard

TensorBoardでログを可視化できる。

tensorboard --logdir logs

Optimizer / Scheduler 設定

設定は config.yamltraining セクションで管理する。

例えば以下のように記述する。

training:
  learning_rate: 1e-3
  weight_decay: 0.01
  optim: adamw_torch
  lr_scheduler_type: cosine
  warmup_steps: 100

内部的には Trainer が optimizer / scheduler を生成する。

まとめ

今回紹介した構成では、

  • CNN
  • MNIST
  • Hugging Face Trainer
  • 分散学習
  • TensorBoard
  • Resume
  • Config管理

を非常に少ないコード量で実現できる。

Transformer以外のモデルでも Trainer を積極的に利用する価値は十分にある。

【dlshogi】torch.compileに対応したら学習が1.6倍速くなった件

torch.compile は、PyTorch 2.0 以降で導入された高速化機能で、既存の PyTorch コードをほとんど変更せずに JIT コンパイルして最適化できる仕組みである。
主に GPU 実行時のオーバーヘッド削減やカーネル融合によって性能を向上できる。

dlshogiの train.py と ptl.py を torch.compile に対応して、どれくらい学習が速くなるか測定した。

torch.compile対応の実装

「--use_compile」オプションで、torch.compileの有効無効を切り替えられるようにした。

Pytorchのtorch.compileの引数に対応して、以下のオプションを指定できる。

「--compile_backend」で、backendを指定できる。
指定しない場合はデフォルトで、inductorが使用されるが、Windows環境では動かないため、Windowsではデフォルトを「aot_eager」にした(triton-windowsをインストールすればinductorも使える)。

「--compile_mode」で、default / reduce-overhead / max-autotune などの最適化モードを指定できる。
「max-autotune」を指定するとエラーなった。原因は調べられていない。

「--compile_fullgraph」で、モデル全体を1つのグラフとしてコンパイルすることを要求する。
有効にすると、途中で Python 処理や未対応 op によってグラフが分断される場合、エラーになりやすいです。
一方で、全体がきれいにコンパイルできるモデルでは最適化しやすくなる。

「--compile_dynamic」で、入力 shape が変わるケースに対応しやすくする設定を有効にする。
dlshogiのモデルは特に指定しなくてよい。

測定結果

測定条件
  • dlshogiの最新のResNet+Transformerモデル(20ブロック256フィルタ)
  • バッチサイズ: 4096
  • 学習率: 0.04
  • use_amp: 有効
  • amp_dtype: bfloat16
  • Ubuntu 22.04 + PyTorch 2.3
比較対象
  • no compile: torch.compileなし
  • compile: torch.compileのデフォルト設定
  • fullgraph: --compile_fullgraphを指定

ステップあたりの学習時間は以下の通り。


torch.compileを有効にすることで、学習速度が1.58倍になっている。
さらに、fullgraphを有効になると、学習速度が1.64倍になっている。

まとめ

dlshogiの train.py / ptl.py を torch.compile 対応した。
また、backend、mode、fullgraph、dynamic などの各種 compile オプションも指定可能にした。
Ubuntu 22.04 + PyTorch 2.3 環境で最新ResNet+Transformerモデルの学習時間を測定した結果、torch.compile有効時は約1.58倍、さらにfullgraph有効時は約1.64倍まで高速化した。

AMP対応以来の大きな学習速度向上となった。

【dlshogi】TensorRTの推論処理の最適化でNPSが1.3倍になった件

先日の世界コンピュータ将棋選手権の会場で「あすとら将棋」さんから、GPUへのデータ転送を推論と並列化すると1割くらい速くなるという話を伺って、さっそく実験してみた。

データ転送の並列化

これまでは、一つのGPUを複数スレッドで共有して、1つのスレッドが推論中はロックして排他的に利用していた。
しかし、推論中にもデータ転送は行うことができるため、次のスレッドは先にデータ転送を行って、純粋に推論部分だけを排他すればよい。

github.com

また、GPT-5.5で実装したところ、圧縮した特徴量をfloat32にするunpack処理をCUDAで実装しているが、その部分も並列化して問題ないことに気づいた。
初期のCUDAのプログラミングモデルでは、同時に実行できるカーネルは一つだけだったが、Compute Capability 2.0以上では、ストリームが分かれていれば、複数カーネルをSMに分配して実行できるようになっている。

測定結果

benchmark.pyを使用して、floodgateから抽出した100局面で、1秒探索した際のNPSは以下の通り。

  • 5回測定した平均値の100局面の統計
  • H100 PCI 1枚、3スレッド
変更前 変更後
平均 10591 11751
中央値 10625 11818
最大値 12468 14168
最小値 9076 10678

NPSが平均で、1.11倍になった。

実行コンテキストによる非同期化

上記を実装する際、GPT-5.5が実行コンテキスト(IExecutionContext)を使うと、推論も排他不要である指摘をしてくれた。

NVIDIAのドキュメントにも、1 つの重みセットを複数の重複する推論タスクに使用できると記載がある。
Developer Guide :: NVIDIA Deep Learning TensorRT Documentation

IExecutionContextを使うことで、各スレッドは非同期で実行できる。
それにより、ロック処理を外すことができた。

github.com

IExecutionContextを使う場合、推論プロファイルの作成方法が変わるため、シリアライズファイルもスレッド数に応じて別になっている。

測定結果
変更前 変更後
平均 10591 13953
中央値 10625 13855
最大値 12468 16660
最小値 9076 13227

NPSが平均で、1.32倍になった。

強さ

NPSが強さに反映されるか連続対局で測定した。

  • 中終盤互角局面集を使用
  • 基準として氷彗8スレッド(hayabusa-8th)を加えている
  • H100 PCI x 2、3スレッド
   # PLAYER            :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 hayabusa-8th      :    34.3   31.7   125.5     221    57      85  120   11   90     5
   2 pre68_40x512_e    :     4.8   32.5   114.0     223    51      94  108   12  103     5
   3 pre68_40x512      :   -39.1   31.9    95.5     226    42     ---   91    9  126     4

pre68_40x512_eが実行コンテキストに対応した版で、pre68_40x512は変更前の版である。
測定数は少ないが速報値としては、R+33.9になっている。

2GPU、3スレッドで測定している。
変更前はスレッド数 3が最適だったが、実行コンテキスを使う場合、3よりも増やすことで、さらに強くできる可能性がある。

ただし、他のスレッドの結果を待たずに実行するため、ツリーの成長の仕方が変わりNPSが上がるだけで強くならない可能性もあるので、強さを計測してチューニングする必要がある。

まとめ

GPUへのデータ転送と特徴量unpack処理を推論と並列化し、H100環境でNPSが平均1.11倍向上した。
また、TensorRTのIExecutionContextを利用して推論自体も並列化し、ロック不要化によってNPSは平均1.32倍まで向上した。
連続対局の速報値では実行コンテキスト対応版が約+34 Elo強くなった。

この改良はすでにmasterブランチにマージしている。

【dlshogi】TransformerモデルのPython実装

将棋AIの大会で、DL系の開発者が減少傾向にあるため、dlshogiの成果物を少し共有したいと思います。

TransformerのPython実装

第5回電竜戦第35回世界コンピュータ将棋選手権で使用したResNet+TransformerモデルのPython実装をGitHubのmasterブランチに追加しました。

github.com


先日開催された第36回世界コンピュータ将棋選手権で使用したモデルは別ですが、ResNet+Transformerモデルのベースとして使用してください。

ResNetとハイブリッド構成にするため、通常の言語モデルのMulti Head Attentionとはテンソルの次元の扱いが異なっており、Linerは1x1のConv2Dで代替しています。
BatchNorm2dを使用しているのと、活性化の位置も通常のMulti Head Attentionから変更している部分があります。
変更の余地は多々あると思います。

モデルの訓練は、「--amp_dtype bfloat16」を指定しないと損失がnanになりやすいです。

入玉特徴量

第35回世界コンピュータ将棋選手権以降に使用している入玉特徴量をmasterブランチのVisual Studioプロジェクトで有効化しました。
使用しない場合は、プロジェクトのC++->プリプロセッサの定義から「NYUGYOKU_FEATURES」を削除してください。
また、unpack.cuのプロパティのカスタムビルドのコマンドラインから、「-DNYUGYOKU_FEATURES」を削除してください。

Linuxでビルドする場合は、デフォルトはOFFになっています。
有効にする場合は、makeの引数に「NYUGYOKU_FEATURES=1」を追加してください。

make NYUGYOKU_FEATURES=1

有効になっているかは、「usi」コマンドで、id nameが、「dlshogi NYUGYOKU_FEATURES」になっているかで確認できます。



また、モデルの訓練時に入玉特徴量を有効にするには、Pythonモジュールのインストール時に有効化が必要です。
環境変数に「NYUGYOKU_FEATURES=1」を追加してインストールしてください。

NYUGYOKU_FEATURES=1 pip install -e .

有効になっているかは、

from dlshogi.common import MAX_FEATURES2_NYUGYOKU_NUM
MAX_FEATURES2_NYUGYOKU_NUM

が31になっているかで確認できます。

まとめ

DL系の開発者の助けになるようにdlshogiの成果物を少し共有しました。
アピール文書で要点は書かれても実装が公開されていない状況だったので、実装を共有することでDL系の開発が活性化することを願います。

第36回世界コンピュータ将棋選手権 結果報告

5/3~5/5に開催された第36回世界コンピュータ将棋選手権に「dlshogi」というプログラム名で参加しました。

大会の概要

今回は、55チームが参加しました。
第1予選、第2予選を通過した上位8チームで総当たりのリーグ戦で決勝戦が行われました。

参加チーム数は過去最多ということです。

大会の結果

dlshogiは、第3位という結果でした。

決勝で、1位、2位とは0.5勝差でした。
先手番で水匠とあたり、あと少しで宣言勝ちできるところで320手ルールで引き分けになったのが影響しました。

昨年の電竜戦以降、NNUE系が棋力を大きく伸ばしており、今年は二次予選通過も危ういかと思っていましたが、3位という結果は上出来だったと思います。

今大会の特徴

定跡

やねうら王がFANBOXで1000万局面登録された新ペタショック定跡を配布しており、それを利用したチームが多かったです。
その結果、後手が角換わりを避ける対局が増えて、昨年のように水匠が先手定跡で勝ちまくるという展開にはならなかったです。

先手勝率

先手勝率が圧倒的に高く、決勝では、先手勝率83.9%だったようです。
先手で勝つのは当然で、後手でどれくら落とさないかが勝負になっています。

ここまで差がつくと、先手後手入れ替えなどルールを変えないと、後手でどのソフトに当たるかの運で優勝が決まってしまう懸念があります。

dlshogiの今大会に向けた工夫点

モデルアーキテクチャ

以前の日記で書いた通り、今大会でモデルアーキテクチャの改良を行いました。

  • Gated Attentionの導入
  • SwiGLU FFNの採用
  • ResNetの一部をTransformerに置換
  • 相対位置バイアスの導入
  • 絶対位置バイアスの導入
  • 入力層へのラージカーネル適用(7x7)
  • SEBlockの導入(間引き配置)

この内、効果が高かったのは、「SwiGLU」と、「入力層へのラージカーネル適用」です。

SwiGLUについては、nshogiの開発者の方も一番効果があったということでした。

訓練データ

60ブロック768フィルタのモデルから知識蒸留したデータと、dlshogiが評価を誤っていた局面からNNUE系で対局したデータを増やしました。

上記のモデルアーキテクチャの変更と、訓練データの追加のどちらが効果があったかは未検証ですが、2つ合わせて、終盤の互角局面集で連続対局して、電竜戦のモデルと比較して、R+51.2向上しています。
温度パラメータの調整も行っています。

   # PLAYER                  :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)     W    D     L  D(%)
   1 hayabusa-8th            :    52.4    8.4  1867.5    3109    60     100  1765  205  1139     7
   2 pre68_40x512_temp160    :    -0.6    8.5  1553.5    3112    50     100  1427  253  1432     8
   3 pre60_40x512            :   -51.8    8.7  1250.0    3121    40     ---  1136  228  1757     7
定跡

定跡は、前大会から手法は同じで、自動作成を続けていました。
483万局面を登録しています。

やねうら王の新ペタショク定跡と比較すると登録局面は少ないですが、新ペタショク定跡採用チームは先手番でも負けがあるのに対して、dlshogiは先手番での負けはありませんでした。
定跡の質に関してはdlshogiの方が高い可能性があります。

これは定跡の作成方法の違いにあり、dlshogiは、3年前くらいから、定跡の相手側の指し手は確率分布とNNUE系の指し手で選んでるため、大会で出現頻度の高い手順が深く掘られています。
一方、ペタショク定跡は以前はPVのみを延長しており、その後、PV周辺(評価値閾値)を中心に掘るように変わっています。
そのため、大会で相手が指さないような手も多く含まれていると予想しています。
ただし、FANBOXで少し古い定跡を配布しているため、それを採用しているチームが指す手は確実に準備済みなため効果的な定跡になるとも言えます。


大会に出るまで定跡の内容を確認していませんでしたが、後手は角換わり回避として、6手目に1四歩を採用していました。
このdlshogiが指した棋譜が、他のチームにも大会中の取り込まれたのか、他チームも6手目に1四歩を指すようになっていました。
新ペタショック定跡は、6手目3二金になっていたので、これはdlshogiオリジナルな定跡なはずです。

定跡手数

今大会の定跡手数と定跡を抜けた時の評価値は以下の通りでした。

二次予選
先手 後手 評価値 手数
dlshogi Gokaku 952 73
arishogi dlshogi 110 70
sojo dlshogi 110 62
Ryfamate dlshogi -189 22
dlshogi nshogi 669 95
dlshogi Shueso 735 69
dlshogi hisui 1113 117
dlshogi yaneuraou 461 33
tanuki dlshogi 30 70
決勝
先手 後手 評価値 手数
tanuki dlshogi 185 64
dlshogi suisho 443 47
dlshogi AobaZero 1473 83
dlshogi ponkotsu 2676 129
hisui dlshogi -120 58
Ryfamate dlshogi -120 58
dlshogi sojo 646 61


先手番は、定跡を抜けるまでの手数の中央値は73手、評価値は735でした。
後手番は、定跡を抜けるまでの手数の中央値は62手、評価値は30でした。

今大会は、後手番でも評価値プラスまで持っていくことができており、後手番定跡としても精度が高かったと言えます。

ハードウェア

昨年は、AWSでH200 SMX x 8を借りましたが、今年はNNUE系に勝つのは難しそうと優勝をあきらめていたので、2年前と同じH100 PCIx8を使用しました。
ハードウェア的には、他のトップチームが去年より高性能なマシンを使用していたのに対して、dlshogiは去年よりダウンしていました。

課題

320手ルール

今大会、2回先手番で320手ルールでほぼ勝ちの状況で引き分けになっているので、ルールが変わらない場合はその対策が必要になります。
大会に特化して、学習を変える必要があり、大会に勝つためだけの改良はモチベーションが上がらないです。
できればルール変更して欲しいとおもっています。

終盤

今大会ではNNUE系より終盤の粗が目立つことはなかったですが、まだNNUE系に比べると終盤が弱い認識です。
終盤のモデル精度は、学習データの質と量を増やすことで対策できると考えているので、今後検証するつもりです。

定跡

決勝で、氷彗とRyfameteには事前に対策されていたのか、後手番であっさり負けています。
電竜戦では他チームの棋譜の取り込みは禁止ですが、世界コンピュータ将棋選手権では予選での棋譜や当日の前の対局の棋譜が取り込まれる可能性があるので、毎対局ごとに定跡を変更する必要があります。
今回は、対策されることを見越した対策までしなかったので、ちょっと気を抜いていました(たけべカフェでまったりしてました)。
張り付いて手作業で更新するのはやりたくないので、前の対局を定跡に自動で取り込むような仕組みを検討します(コンピュータ将棋の強さとは無関係なのでできれば電竜戦と同じルールにして欲しいと思っています)。

まとめ

第36回世界コンピュータ将棋選手権に参加し、dlshogiは第3位という結果でした。

アーキテクチャ改良(特にSwiGLUやラージカーネル)と訓練データ強化により昨年より棋力が向上し、定跡の質でも優位性が見られました。
一方で320手ルール対応や終盤力、対局ごとの定跡更新などが今後の課題として残りました。

優勝した氷彗、おめでとうございます。
大会を運営してくださった主催者様、対局してくださったチームの方々、そして応援してくださった皆様にお礼申し上げます。

【バイブコーディング】レトロ戦略ゲームを作る その7(グラフベースマップ生成)

前回、制約ベースのWave Function Collapse (WFC)アルゴリズムでマップの自動生成を試したが、戦略的なマップは生成できなかった。

今回は、グラフベースの手法を試す。

戦略グラフ

グラフベースの手法は、本拠地、争奪エリアのようなノードと、ノードをつなぐエッジで構成される。 事前にゲームとして戦略的になるグラフを構築してから、制約ベースなどの手法で生成を行う。

あらかじめプレイヤーがどこで戦い、どう移動し、何を取り合うかを表現するため、ゲームとして公平で戦略的なマップが生成できる。

バイブコーディング

Pythonでグラフベースでマップ生成を行うスクリプトを生成した。

レトロターン制戦略ゲームのマップを自動生成するツールを作成したい。

マップのフォーマットは、mapsに実際のマップデータがある。

地形の種類には、
plain
mountain
forest
road
beach
river
bridge
sea
があり、建物には、
capital
factory
city
port
airport
がある。
建物には、ownerがあり、占領状態を表す。
capitalは、redとblueで、それぞれ1つずつという制約がある。

ゲームのルールは、design/deep-research-report.mdに記載している。

ゲームのマップは戦略性が重要なため、グラフベースの手法と制約ベースを組み合わせて実現したい。

参考として、生成手法の初期調査レポートは、design/map-generation.mdにある。

まずは、実現可能な実装方針を立ててください。

海の中に川ができたりしたため、いくつか制約を加えたことで、以下のようなマップが生成できるようになった。

改善

未拠点の拠点が少なく、首都が近距離で向かい合っているマップが多かったため、より戦略的なグラフが生成できるように、生成→確認→修正指示を繰り返した。

以下のようなマップが生成できるようになった。

争奪エリアが複数配置されており、ゲームとして遊べるマップになっている。 拠点間の導線も確保されている。

まとめ

グラフベース手法を用いて、戦略性を考慮したマップを生成する手法を試した。 制約ベースと組み合わせることで不自然な地形を改善し、実用的なマップ生成が可能になった。

空港のあるマップで未占領の空港がなかったり、島のマップの地形が単調だったりと、改良の余地はまだあるが、いったん遊べる形になったので、ゲームに組み込むつもりである。

以下は、Codexで生成したアルゴリズムの解説である。

マップ自動生成アルゴリズム実装詳細

概要

本プロジェクトのマップ生成器は、design/map-generation.md の方針に沿って、以下の4段階で動く。

  1. 既存 maps コーパスを分析して、サイズ分布・HQ配置帯・中立施設比率などの事前分布を作る。
  2. 生成対象の ARCHETYPECAPITAL_LAYOUT に応じて、戦略グラフ (StrategyGraph) を組み立てる。
  3. 戦略グラフをヘクスマップ上の骨格地形へ落とし込み、施設・道路・海域・障害地形を配置する。
  4. 後処理で制約違反を修復し、戦略ノードが失われないように安定化させ、最後に validator で検証する。

実装の中心は hf_map_gen/generator.py にあり、グローバル戦略性は戦略グラフ層、局所的な整形は builder / repair 層、既存マップ分布への追従は corpus.py が担当する。

主要モジュール

hf_map_gen/generator.py

生成アルゴリズム本体。以下を持つ。

  • 生成対象定義
    • ARCHETYPES
    • CAPITAL_LAYOUTS
  • 戦略グラフのデータ構造
    • StrategyNode
    • StrategyEdge
    • SeaRegionPlan
    • HQPositionBand
    • ObjectiveTheatre
    • StrategyGraph
  • グリッド編集用の GridBuilder
  • アーキタイプ別の graph 生成
  • stacked / side_by_side それぞれの骨格生成
  • 中立施設配置
  • 道路生成
  • 海・川・橋・港・海岸の修復
  • archipelago 系の専用安定化

hf_map_gen/corpus.py

既存 maps を統計化し、生成時の prior を作る。

主に次を集計する。

  • サイズ頻度
  • 地形比率
  • HQ の cross-axis / rear-depth 帯
  • レイアウト別の中立施設比率
  • 中立施設の帯域分布
  • 中立施設の front cluster profile

hf_map_gen/validate.py

完成マップの静的制約を検証する。

主な検証項目は以下。

  • red / bluecapital がそれぞれ1つずつ存在すること
  • 建物の capturePointsowner 整合
  • factory は必ず所有者を持つこと
  • beach は必ず sea に接すること
  • riversea に3辺以上接しないこと
  • port は十分大きい sea 連結成分に接すること
  • bridge の左右が許可地形集合に入ること
  • 高海率マップに応じた land component 数上限

hf_map_gen/metrics.py

生成結果の戦略的な要約指標を計算する。

  • 収入差
  • 中立都市までの歩兵距離差
  • 空港・港までの距離
  • capital 間の陸路有無
  • sea / mountain
  • chokepoint 数

hf_map_gen/cli.py

CLI の analyze, generate, validate を提供する。

データモデル

StrategyNode

戦略上意味を持つ地点を表す。kindcoord、必要なら side を持つ。

現在使っている代表的な node 種別:

  • hq
  • frontier_hub
  • contested_neutral_cluster
  • approach_neutral_cluster
  • strategic_objective
  • sea_region_anchor
  • beach_landing_zone
  • central_island_objective
  • secondary_island_objective
  • forward_staging_island
  • island_chain
  • naval_neutral_slot
  • home_island_anchor
  • home_neutral_cluster
  • lane_hub
  • open_field_pocket
  • cover_belt
  • soft_barrier
  • flank_pressure_zone
  • urban_core
  • city_belt
  • industrial_pocket
  • road_junction
  • bridge_chokepoint
  • pass_chain
  • highland_basin

StrategyEdge

ノード間の軍事的関係を表す。経路そのものではなく、経路の意味を持つ。

代表例:

  • support_relation
  • contested_front
  • secondary_route
  • advance_lane
  • screen_line
  • delay_barrier
  • flank_route
  • sea_lane

ObjectiveTheatre

中立施設や争奪地帯を、単なる座標1点ではなく「戦域」として扱うための構造。

保持する情報:

  • role
  • anchor
  • slot_coords
  • neutral_budget
  • land_radius
  • required_land_tiles
  • required_slot_count

この構造により、graph が要求する施設数に見合う land capacity を先に地形へ反映できる。

コーパス分析の使い方

生成器は完全ランダムではなく、既存マップから抽出した分布を prior として使う。

サイズ選択

choose_size() は、ユーザーが width / height を指定しない場合、コーパスで頻度が高いサイズから選ぶ。

HQ 位置帯

HQPositionBand は、capital の位置を固定座標ではなく帯域として表す。

  • stacked
    • 横方向のばらつき
    • 上下配置時の後方深度
  • side_by_side
    • 縦方向のばらつき
    • 左右配置時の後方深度

この帯は corpus.py で既存マップの 10〜90 パーセンタイルから求めている。

中立施設の prior

中立施設に関しては以下を使う。

  • neutral_ratio_by_layout
  • neutral_band_weights_by_layout
  • neutral_axis_weights_by_layout
  • neutral_front_cluster_profile_by_layout

これにより、単純な均等散布ではなく、既存 maps の front/approach/edge 分布に近い budget 配分ができる。

生成パイプライン詳細

1. エントリポイント

generate_map() が乱数 seed と引数を受け取り、まずサイズを決める。

その後、以下のどちらかへ分岐する。

  • _generate_stacked_map()
  • _generate_side_by_side_map()

2. 戦略グラフ生成

_build_strategy_graph() が各アーキタイプに応じた graph を組み立てる。

2.1 共通で決めるもの

  • naval_weight
  • naval_profile
  • port_policy
  • layout_policy
  • blue_hq
  • 中立施設 budget
  • contested cluster order / profile
  • objective theatre

2.2 layout_policy

現在の policy は以下。

  • strict_symmetric
  • offset_symmetric
  • balanced_asymmetric

ただし一部 archetype、特に island_invasion / stacked は専用 policy を使い、正面近距離対峙を避ける。

2.3 blue_hq の決定

red_hq を先にサンプルし、blue_hq は layout と policy に応じて決める。

ここで以下のルールを課している。

  • 近距離の軸正面対峙を避ける
  • stacked / side_by_side ごとに対角寄りの配置も許す
  • island_invasion / stacked では diagonal separation を強める

2.4 中立施設 budget

_neutral_budget() が、マップ面積と corpus 由来の neutral ratio から次を決める。

  • contested_neutral_budget
  • approach_neutral_budgets
  • neutral_factory_budget

注意点として、既存 maps に未占領 factory は存在しないため、現在は中立 factory を生成しない。

3. archetype ごとの graph

central_plains

陸戦主体。以下の node を重視する。

  • frontier_hub
  • contested_neutral_cluster
  • approach_neutral_cluster
  • lane_hub
  • open_field_pocket
  • cover_belt
  • soft_barrier
  • flank_pressure_zone

意図は、平地一色ではなく、突破路・遮蔽帯・緩い遅滞地形を graph として先に置くこと。

mountain_divide

中央山脈や分断地形を持つ陸戦型。主戦線を峠・谷で制御する。

river_bridge_chokepoints

川線と橋前線を主役にする。橋を局所 chokepoint として graph に持ち、骨格では river / bridge を先に固定する。

dense_city_war

都市帯と道路網を主役にする。urban_core, city_belt, industrial_pocket, road_junction を graph に持つ。

highland_passes

高地塊と複数の pass を作る。pass_chainhighland_basin が主役。

strait_invasion

海峡型海戦 map。単一固定ではなく variant を持つ。

  • naval_assault
  • coastal_pressure
  • blocked_strait

variant ごとに port_policynaval_weight が異なる。

coastal_logistics

海はあるが主戦場は沿岸補給線。port は存在しても、海軍主戦場に寄せすぎない。

island_invasion

最も専用処理が多い archetype。

graph 上で以下を持つ。

  • sea_region_anchor
  • beach_landing_zone
  • forward_staging_island
  • island_chain
  • central_island_objective
  • secondary_island_objective
  • naval_neutral_slot
  • home_island_anchor
  • home_neutral_cluster

ここでは、capital のある home island と、中央 objective island、側面 objective island を別の戦域として扱う。

4. objective theatre と coverage

strategic_objective は中央1点ではなく、複数の sector を使うように設計されている。

4.1 _default_objective_theatres()

land map 向けの標準 theatre を作る。

4.2 _build_island_objective_theatres()

island_invasion 専用。landing, central, flank, counter_flank に budget と必要 land capacity を与える。

4.3 _augment_objectives_with_coverage()

graph が map の一部しか使っていない場合、coverage candidate から追加 objective を補う。

この層により、特定 archetype だけではなく、全体として map の使用領域が広がるようにしている。

5. 骨格地形生成

戦略グラフを作った後、layout ごとの骨格 builder が地形に落とす。

stacked

_generate_stacked_map() が担当する。

大まかな順序:

  1. 戦略グラフ生成
  2. archetype 別 builder で初期骨格生成
  3. mirror / finalize
  4. 中央地形や archetype 装飾を適用
  5. blue 側 home cluster の再配置
  6. neutral / road / bridge / river / port 修復
  7. archipelago 系後処理

side_by_side

_generate_side_by_side_map() が担当する。

stacked と同じ責務だが、中央 strait や coast の向きが異なるため、side 専用 builder と修復を使う。

6. 地形 painter の役割

graph を tile へ落とすとき、いくつかの汎用 painter を使う。

  • _paint_terrain_disc()
  • _paint_terrain_strip()
  • _paint_island_region()
  • _draw_road_path()
  • _carve_center_strait()
  • _carve_center_vertical_river()
  • _carve_center_mountain_divide()

重要なのは、単なるノイズ埋めではなく、graph node / edge に対応する corridor や region を直接描いている点である。

7. neutral 配置

中立施設は後付けランダムではなく、graph budget から配置される。

通常 map

_place_neutral_facilities() が以下の順で置く。

  1. objective theatre に紐づく neutral
  2. contested cluster の neutral
  3. approach cluster の neutral
  4. まだ足りなければ contested cluster を再走査

island_invasion

archipelago_assault では、通常の contested / approach に加えて、objective theatre と home island 用の neutral を持つ。

  • _place_objective_neutral_facilities()
  • _place_archipelago_home_neutrals()

これにより、中央 island だけでなく home island にも neutral city を持たせられる。

8. road 生成

_paint_roads() は capital 間を安易に直結しないように設計されている。

基本方針:

  • HQ -> approach -> frontier -> contested lane を局所的につなぐ
  • 敵 HQ 側の幹線と無条件には合流させない
  • archetype によっては専用 road painter を使う

例:

  • dense_city_war は都市帯用の道路を追加
  • highland_passes は峠用道路を追加

9. archipelago 系の後処理

island_invasion は後処理の責務が重い。

9.1 目的

  • home port を残す
  • capital 間の land path を断つ
  • central / flank objective を残す
  • home island を痩せさせすぎない
  • neutral theatre を消さない

9.2 主な関数

  • _enforce_island_invasion_objectives()
  • _stabilize_island_home_ports()
  • _stabilize_stacked_naval_ports()
  • _stabilize_side_naval_ports()
  • _ensure_archipelago_capital_separation()
  • _stabilize_archipelago_strategic_fragments()
  • _materialize_archipelago_home_islands()
  • _restore_archipelago_home_rear()

9.3 recent fix の考え方

home island については、単なる early paint だと final sea-corridor stabilization に削られる。そこで現在は以下の二層に分けている。

  • graph が home_island_anchor / home_neutral_cluster を持つ
  • 後段で rear shelfrear-only restore を使い、port や中央海路を壊さずに home island の基底面積を戻す

この設計により、capital の島を広く保ちつつ、port と capital separation の制約も守れる。

10. repair の役割

repair は「戦略性を決める層」ではなく、「制約違反や局所破綻を除去する層」である。

代表的な処理:

  • _repair_bridges()
  • _repair_rivers()
  • _repair_beaches()
  • _repair_ports()
  • _repair_small_land_fragments()

graph が不十分な場合に repair だけで帳尻を合わせると壊れやすいので、最近の実装では objective theatre や home island などの戦略的要件は graph に上げ、repair は safety net にとどめている。

11. validation とメトリクス

cmd_generate() は生成後に必ず以下を行う。

  1. summarize_map() で地形・建物内訳を出力
  2. compute_metrics() で戦略指標を出力
  3. validate_map() で静的制約を検証

これにより、生成アルゴリズムは「map を作る」だけでなく、「ゲーム的に許容されるか」を同時にチェックする。

12. 現在の実装方針の要点

強み

  • タイル単位ランダム生成ではなく、戦略グラフ先行
  • コーパス prior を用いた HQ / neutral 分布調整
  • archetype ごとの専用 graph grammar
  • objective theatre による複数争奪点の管理
  • archipelago での home island / objective island の分離
  • validator による強い制約チェック

意図的な設計判断

  • 中立 factory は生成しない
  • port は graph が要求した海域でのみ成立させる
  • bridge は横断前提で左右地形制約を持つ
  • beach / river / port は既存 maps の観察を基に静的制約を持つ
  • 非対称配置は許すが、戦略グラフが追従する形にする

今後の改善余地

  • static metrics に加えて簡易 simulation を導入する
  • archetype ごとの reject / reroll 基準を明示的にする
  • income gap や objective distance による graph-level reject を強化する
  • code volume が大きい generator.py を archetype / repair / neutral / naval 単位に分割する

13. 実行フローのまとめ

最後に、generate コマンドの実行フローを簡潔にまとめる。

  1. cli.py がコーパスを分析する。
  2. generate_map() がサイズ、layout、archetype を決める。
  3. _build_strategy_graph() が HQ、frontier、objective、sea region、neutral budget を決める。
  4. layout 別 builder が graph をヘクス地形へ落とす。
  5. neutral, road, port, river, beach, bridge を graph-aware に整える。
  6. validate_map()compute_metrics() で結果を確認し、JSON を保存する。

この構成により、現行実装は「既存 maps に寄せた prior を持つ graph-based + constraint-aware generator」として整理できる。

GPS将棋(OpenShogiLib)のdf-pn(詰将棋)をUSI mateエンジンとして移植

以前に、GPS将棋(OpenShogiLib)のdf-pnをdlshogiのAperyベースに移植することを試みたが、変更が多すぎて途中で挫折した。

Aperyベースにするのは一旦保留して、単体のUSI mateエンジンとして移植することにした。

Windows向けの移植

元のOpenShogiLibは、古いLinux向けのソースになっているため、そのままだとWindows向けにビルドできない。
以前に手動で対応しようとしたが、df-pnに関連するソースを抜き出すだけでも大変なため断念した。

Codexを使えば、自動でdf-pnに関連するソースを抽出して、Windowsでビルドエラーを解消もできそうだと思い、移植を試みた。

Codexで移植

元のOpenShogiLibから、まずdf-pnに関連する処理の解説を作成し、その解説を参考資料として、移植を実施した。
USI エンジンとして実行できるようにするため、USIの仕様も与えた。

oslのソースを元に、Df-pnアルゴリズムによるUSI mateエンジンを実装してください。

- oslmateフォルダにあるWindowsのコマンドラインプロジェクトに実装する
- oslのソースからDf-pnに関連するソースのみを移植する
- oslとソースフォルダ階層、ソースファイル名は合わせる
- oslのソースはnamespaceも含め、基本的に変更しない
- oslは、古いC++で記述されているため、Windowsのコマンドラインプロジェクトでコンパイルエラーになる箇所は修正する
- oslには、USIプロトコルは実装されていないため、新規に実装が必要。USIプロトコルの仕様はreferences/usi.mdにある。usi,setoption,isready,isready,go mate,stop,quitのみ実装する。
- ビルドコマンド: msbuild oslmate.slnx /t:Build /p:Configuration=Debug /p:Platform=x64

### 参考
oslフォルダ: oslのソース(Df-pnに無関係のソースも含む)
references/gpsshogi-dfpn.md: oslのDf-pnに関連するソースの解説
references/usi.md: USIプロトコルの仕様

ビルドエラーが解消され、実行できるバイナリが生成された。

assert対応

探索を実行すると、assertでエラーになったため、Visual StudioのGitHub Copilotで原因分析したところ、16 バイト整列のエラーだったため、alignas(16) を追加して解消した。

その1か所修正したことで、実行できるようになった。

position sfen lnsgk1snl/1r4g2/p1pppp1pp/6pP1/1p7/2P6/PPGPPPP1P/6SR1/LN+b1KG1NL w bs 11
go mate
checkmate B*6h 5i5h 6h5g+ 5h5i 7i6h

SSE2対応

OpenShogiLibはSSEに対応しているが、古いC++用のコードになっているため、OSL_USE_SSEマクロを有効にしてもビルドできない。

Codexで、SSE2に対応したintrinsicを使用したコードに修正することで、ビルドできるようになった。

Windows向けバイナリ公開

ShogiGUIなどに詰将棋エンジンとして登録して使用できるように、GitHubでWindowsバイナリを公開した。

こちらからダウンロードできる。

github.com

まとめ

GPS将棋(OpenShogiLib)のdf-pnを単体のUSI詰みエンジンとして移植した。
Codexを活用して必要なソース抽出・Windows対応・USI実装を行い、ビルド・実行することができた。
GitHubでWindows向けバイナリを公開した。