qleap/shogi-balanced-positions-2025-extended
Shogi Balanced Positions 2025, extended to move 37 Balanced shogi (Japanese chess) opening positions, from move 32 to move 37. The starting point is yaneurao's Balanced Position Collection 2025, which contains 26,273 positions at move 32, each verified balanced by a 200M-node Suisho10 search (|eval| ≤ 50 centipawns). That collection stops there. This dataset walks it five plies further, keeping every position balanced, and ends with 6,045,847 positions at move 37. It comes in… See the full description on the dataset page: https://huggingface.co/datasets/qleap/shogi-balanced-positions-2025-extended.
Shogi Balanced Positions 2025, extended to move 37
Balanced shogi (Japanese chess) opening positions, from move 32 to move 37.
The starting point is [yaneurao's Balanced Position Collection 2025][src], which contains 26,273 positions at move 32, each verified balanced by a 200M-node Suisho10 search (|eval| ≤ 50 centipawns). That collection stops there. This dataset walks it five plies further, keeping every position balanced, and ends with 6,045,847 positions at move 37.
It comes in two selections, built the same way but scored at different depths: the original depth 9 pass, and a complete depth 16 rebuild (the _d16 configs). The depth-16 set is the better one — see the depth-16 rebuild.
The intended use is as a set of start positions for self-play or for supervised training, where a small opening set makes the early game badly over-represented.
[src]: https://github.com/yaneurao/YaneuraOu/releases/tag/BalancedPositions2025
Contents
Each row has one column, sfen: a position in SFEN notation, without the leading sfen keyword.
from datasets import load_dataset
ds = load_dataset("qleap/shogi-balanced-positions-2025-extended", "ply37")
print(ds["train"][0]["sfen"])
# +B1s2gsnl/2g2k1b1/pP2pp2p/2p3p2/4L4/2P1P4/P1+p+psPP1P/5G1R1/LN1GK1SNL b Rn3p 37sfens/start_sfens_ply{32..37}.txt hold the same positions as plain text, one per line, byte-for-byte in the format of the upstream files — the sfen keyword is kept and the line endings are CRLF, as upstream's are — so they drop straight into tooling that already reads the originals.
How it was built
One ply at a time, starting from move 32. For each position:
- Propose. A policy network (
b15c256, ONNX) suggests moves, top-p 0.90, at most 16 per parent. - Score. Each resulting child is searched by an NNUE engine ([attic-gensfen][ag], derived from YaneuraOu) at depth 9,
multipv 1, with thehalfka2304 / nagisa_v4evaluation function. - Keep or drop. A child survives only if its score, put back into its root's frame, is within ±50 centipawns of the score of its own move-32 ancestor.
[ag]: https://github.com/yaneurao/YaneuraOu
The anchor is per root, not global. Every position at every ply is measured against the move-32 position it descends from, and the sign is flipped when the ply parity differs. This means the criterion is drift — how far the game has wandered from a position already known to be balanced — rather than an absolute evaluation. Measuring drift also cancels most of the shallow search's own bias, and it lets the work be split across machines by root without changing the result.
The transposition table is cleared before every position, so a position's score depends only on the position, the depth, and the evaluation function — not on what other threads searched first. Without this the same position gets different scores on different machines.
The depth-16 rebuild
The tables above were selected at depth 9, which is shallow enough to be the dominant source of error in the keep-or-drop decision. The whole cascade was rebuilt from the move-32 roots at depth 16 with a 128 MB transposition table. It is complete, moves 32 to 37, under the _d16 configs and sfens/d16/, and holds 9,459,640 positions against the depth-9 set's 5,880,769.
Prefer the `_d16` configs for new work. The default config is still ply37 (depth 9) so that existing code keeps loading what it loaded before; the depth-9 files are unchanged and are kept for reproducibility, not because they are better.
This is a rebuild, not a filter of the depth-9 set. Re-scoring the depth-9 output at depth 16 could only ever return a subset of it, because positions that depth 9 wrongly rejected never enter the pipeline at all. Compared against the depth-9 set, the overlap falls away with every ply:
At move 37, 77% of the depth-16 positions do not appear in the depth-9 set at all (74% at move 36, 69% at move 35). The overlap falls monotonically, 44.7% to 16.4%, with no sign of levelling off.
Everything else is unchanged: the same policy proposals, the same per-root ±50 band, the same cleared transposition table. The only other difference is that the 407 contradictory upstream rows are excluded before expansion rather than pruned by lineage afterwards, which does not change the result.
Depth 16 rather than deeper: measured on 32 positions against a 200M-node Suisho10 reference, depth 9 is 86.9 centipawns off on average, depth 16 is 41.3, and depth 25 is 29.1. Going from 9 to 16 costs 78× the nodes and halves the error; going on to 25 costs another 23× to reach 29.1, which is about the size of the reference's own disagreement with the upstream selection.
Known limits
- The scores are not published, on purpose. Selection used depth 9, which is far shallower than the 200M nodes behind the upstream move-32 set. On a 2,000-position sample, depth 9 and depth 16 disagreed by 52 centipawns on average, and by more than 50 centipawns on 39.5% of positions. Those numbers are good enough to decide "has this drifted past 50 centipawns?" and not good enough to be used as training targets. Publishing them next to the positions would invite exactly that mistake. The depth-16 scores are withheld for the same reason: better, but still not a training target.
- ±50 is relative to each root, not to zero. The upstream roots are within ±50 of zero by construction, so a move-37 position can be up to roughly ±100 from even in the worst case. Re-reading the 25,866 roots at depth 16 gave a median of −28, a mean |score| of 46.6, a maximum of 293, and only 60.8% within ±50 — which is why the band is anchored per root rather than at zero. (An earlier revision of this card said −18 and 63.9%; those figures were wrong.)
- 407 upstream rows were dropped. In
start_sfens_ply32.txt, 407 of the 26,273 positions carry the side-to-movebtogether with the move number32, which contradict each other (move 32 impliesw). Those rows and everything descended from them were excluded, leaving 25,866 roots. - Positions are unique within a ply, and carry no game history, no repetition count, and no move sequence back to the root.
- The policy network limits each parent to 16 proposals, so this is a wide sample of balanced continuations, not an exhaustive enumeration of them.
License
MIT, inherited from the upstream collection. See LICENSE; it carries yaneurao's copyright notice as the MIT terms require. If you use this, please credit [the original collection][src] as well.
