NNUE — 浅いMLPが切り開いたCPU高速評価の革命
NNUE(Efficiently Updatable Neural Network)は、2018年に日本の将棋AI開発者 那須 悠(Yu Nasu)氏が発表した 「差分更新できる浅いニューラルネットワーク評価関数」。 CPU上で 秒間数百万局面 の評価を可能にし、将棋AIの主流を一夜にして塗り替え、 やがてチェスの世界最強エンジン Stockfish にも移植されて世界標準になりました。
NNUE とは何か — 1行で答えると
💡 1行答え: NNUE は「差分更新ができるように設計された、浅いMLP評価関数」。 GPU不要・CPUだけで爆速に局面評価できることが最大の強み。
正式名称は Efficiently Updatable Neural Network =「効率的に更新できるニューラルネット」。 なぜ「更新」が名前に入っているのか? それは 「1手指したときに、評価関数の中身を全部計算し直さない」 という設計思想こそが、NNUEの本質だからです。
一般的なディープラーニング評価関数(例:dlshogi)は、毎回 GPU で大きな CNN を頭から走らせます。 これに対して NNUE は:
- 浅い MLP(4層のみ、内部は
HalfKP_256x2-32-32)を使う - 入力層の活性化を差分計算する(1手前の状態 + 変化分だけ加減算)
- 重みを int8/int16 に量子化して整数SIMD演算に乗せる
- 結果として CPU 1コアで秒間数百万局面を評価でき、やねうら王 のような高速α-β探索と完璧に噛み合う
第7章 §2「NNUEの中身」 と 第3章「NNの基礎」 を先に読んでいる人は、 このページではさらに 「なぜそれがCPUで動くのか」「歴史的にどう生まれたのか」「学習はどうやるのか」 という詳細を見ていきます。
歴史
2018年、那須 悠氏が将棋向けに発表。 Bonanzaメソッドの後継として 「NN化しつつCPU動作」を実現する制約から生まれた。
仕組み
HalfKP_256x2-32-32 という浅いMLP。差分更新・量子化・ClippedReLUの3つの工夫で CPU爆速。
影響
将棋AIの主流を一新し、チェスのStockfishにも輸出されて世界標準に。 日本発の技術が世界を変えた稀有な例。
1. 歴史 — 那須 悠(Yu Nasu)氏の発明
1.1 2018年、ある電竜戦向け論文から
NNUE は 2018年、将棋AI開発者の 那須 悠(Yu Nasu)氏によって発表されました。 正確には「Efficiently Updatable Neural-Network based Evaluation Functions for Computer Shogi」というタイトルの論文が世界コンピュータ将棋選手権の場で配布されたのが最初のお披露目です。
当時、将棋AI界はちょうど 転換点 にありました:
- 古典側:Bonanzaメソッド(2005年〜)を引き継ぐ
KPP / KPPT / KKPT型の評価関数が成熟し、伸び代が薄くなっていた - DL側:2017年に AlphaZero が登場し、「深いCNN + MCTS + GPU」という新パラダイムが提示されていた
- 悩み:「ニューラルネットを使いたい、でもGPU依存にはしたくない」「探索部はα-βのまま強くしたい」
この 「ニューラルネット化したい、でもCPUで動かしたい」 という一見矛盾する制約から、NNUEは生まれました。
🏔️ 補足:那須氏のオリジナル設計思想(クリックで展開)
那須氏は元々 tanuki-(タヌキ) プロジェクトのメンバーで、Bonanzaメソッド系の評価関数を改良してきた経歴を持ちます。
NNUE の論文(英語)には、当時の問題意識として次の記述があります(要約):
- 従来の KPP 型は「玉と他の2駒」の3項間関係を全部数えていて、特徴次元が爆発する
- これをニューラルネットの埋め込みに置き換えれば、より少ないパラメータで高精度になるはず
- ただし将棋AIは 1手ごとに評価を呼び出すため、毎回ネットを全実行すると遅すぎる
- → 解決策:「差分更新できる構造」 にネット側を設計する
つまり NNUE は「NN を将棋AI に持ち込む」ではなく、「将棋AI の制約に合わせて NN を再設計する」発想で生まれた発明です。
1.2 名前の由来 — ƎUИИ
「NNUE」という名前自体に、エンジニアの遊び心が込められています。
NNUE = Efficiently Updatable NN を「逆さに書いた」もの
NN-UE ⇄ ƎU-NN
「効率的に更新できるニューラルネット」を逆さに読むと「ƎUИИ」(≒UNN)になる、というジョーク。 Stockfish プロジェクトでも、この名前と「ƎUИИ」記号が公式ドキュメントに使われています。
1.3 2020年、チェス世界へ輸出される
2018年〜2019年に将棋界で NNUE が爆発的に普及した(後述)あと、2020年、もうひとつ大きな事件が起きました。
日本のチェスエンジン開発者 野田久順(Hisayori Noda、通称 nodchip)氏らが、NNUE を Stockfish(当時世界最強のチェスエンジン)に移植したのです。 これが 「Stockfish NNUE」 として公開され、すぐにレーティング +R80 程度の大幅向上を見せました。
その後、Stockfish 12(2020年9月)で NNUE が 本家の標準 として取り込まれ、現在に至るまで Stockfish は NNUE を評価関数として使い続けています。 つまり今や「世界最強のチェスエンジンの中身は、日本発の将棋AI技術」という状況です。
📚 補足:将棋発の技術がチェスを変えた事例の希少性(クリックで展開)
歴史的に、ボードゲームAIの技術は チェス → 将棋 の方向で流れることが多かった:
- α-β 探索(1958, McCarthy)→ 将棋AIへ
- 反復深化(1975, Slate & Atkin)→ 将棋AIへ
- 置換表(1970s)→ 将棋AIへ
- MCTS(2006, Coulom)→ 囲碁・AlphaGo → 将棋AIへ
NNUE は、この長い歴史のなかで 初めて「将棋 → チェス」へ流れた 主要技術と言われています。 「将棋AIの強さは局所最適化の集合だ」と長く言われてきた中で、世界に通用する一般技術 を生んだ事例として、NNUE はコンピュータ将棋史でしばしば特別扱いされます。
2. NNUE の1局面処理フロー — 図で追う
NNUEがやることは 「局面を1つの数字(評価値)に変換する」 だけ。中の処理は 4ステップ:
ポイント:NNUEは たった4層の浅いMLP。「深いCNN」ではなく 「巨大なスパース入力 + 浅い密接続」がCPU向きの設計です。 続く §3〜§5 で、各ステップの中身を順番にほぐしていきます。
2.1 💭「巨大なスパース入力」とは?
「スパース(sparse)」 = 「中身がほとんどゼロ」 という意味の数学・コンピュータ用語。日本語だと 「疎(そ)」 とも訳されます。
NNUEの入力ベクトル(41,024次元)は 巨大だけど、実際に1になっているのは数十個だけ。残りの 40,000個以上は全部0 — これが「スパース」。
なぜスパースになるのか
41,024個の特徴ビットは「玉の位置 × 他の駒の(位置×種類×持ち主)」の 全ての組み合わせ を用意したもの。 でも実際の1局面で実現するのは 盤上にある駒の数(〜40個) だけ。
- 盤上に存在する駒は最大 40個(最初の局面)、終盤なら 20個程度
- 各駒について、玉とのペア特徴が 1個ずつ 立つ
- つまり 1 になるのは ~40個、残り ~40,984個 は全部0
- 密度 ≈ 40 / 41,024 = 0.1% ← めちゃくちゃ疎
スパースだと何が嬉しいのか — 計算の節約
普通に「$h_1 = W_1 \cdot x + b_1$」(行列ベクトル積)を計算すると、$W_1$ の全要素にアクセスする必要があり、$256 \times 41{,}024 = 約1{,}050万回 の掛け算が必要。
でも入力 $x$ がスパース(ほとんど0)なら、「0との掛け算 = 0」 なので無視できる。実質的に計算が必要なのは 1 が立っている数十箇所だけ:
// 通常の行列ベクトル積(密ベクトル想定)
for (let i = 0; i < 256; i++) {
h1[i] = b1[i];
for (let j = 0; j < 41024; j++) {
h1[i] += W1[i][j] * x[j]; // ★ 1,050万回の計算
}
}
// スパース版(NNUEが採用している方式)
const activeIndices = [3, 12, 18, ...]; // 1が立っているインデックスだけ抽出(~40個)
for (let i = 0; i < 256; i++) {
h1[i] = b1[i];
for (const j of activeIndices) {
h1[i] += W1[i][j]; // ★ x[j]=1なので掛け算不要、~256×40=1万回だけ
}
}
1,050万回 → 1万回= 約1000倍の高速化。これがNNUEがCPUで秒間数百万局面を評価できる秘密の1つです。 さらに 差分更新(§5)と組み合わせると、もっと劇的に速くなります。
💡 1行まとめ: 「巨大なスパース入力」=「次元数は大きいけど、実際に1が立つのはごく一部」。 NNUEは 0との掛け算をスキップ することで、巨大な入力を扱いながらも高速に動作する。
3. アーキテクチャ — HalfKP_256x2-32-32
NNUE の標準構成は HalfKP_256x2-32-32 と呼ばれる、たった5層(入力含む)の浅いMLPです。
第3章「NNの基礎」で学んだMLPが、ここで具体化されます。
3.1 構造図と名前の対応
3.2 別表示の構造図(線形ビュー)
同じアーキテクチャを、入力→出力の 1直線で描き直したのが下の図。差分更新対象(★)が入力層と第1層の間にあることもわかります。
3.3 仕様(テキスト表現)
入力: HalfKP特徴ベクトル(41,024次元、スパース2値)
「玉の位置 × 玉以外の各駒の(位置, 種類, 持ち主)」を全パターン列挙
↓ W1(256次元 × 2 = 512次元) ← Accumulator
↓ ClippedReLU(0, 127)
中間層1: 512次元
↓ W2(32次元)
↓ ClippedReLU
中間層2: 32次元
↓ W3(32次元)
↓ ClippedReLU
中間層3: 32次元
↓ W4(1次元)
出力: 評価値(スカラー)
3.4 「256x2」の "x2" は何か — 自分玉だけ?相手玉との関係は?
HalfKP_256x2-32-32 の「x2」は 「先手視点・後手視点を別々に持つ」 という意味です。
鋭い質問は:「HalfKP は『自分の玉 × 他の駒』というペアしか作っていないので、『自分の玉と相手玉の関係性』が抜けているのでは?」 — 実は抜けていません。秘密はこの "x2" にあります。
✅ NNUE は HalfKP 特徴ベクトルを「2つ」用意する ことで両玉の関係を表現している。
① 先手玉の視点 → 先手玉 × 他駒 の組合せ(41,024次元)
② 後手玉の視点 → 後手玉 × 他駒 の組合せ(41,024次元)
各視点を独立に 256次元 にニューラルネット で埋め込み、最後に 連結して 512次元 として続く層に渡します。これが "256 × 2 = 512" の意味:
なぜ「自分玉 × 自分玉」じゃダメで「2セット」なのか
将棋の評価は 両側の玉の安全度の差 で決まります。「自分の玉だけ安全」では足りない — 「相手玉も詰みに近いほど自分有利」を測る必要がある。 HalfKP を 2セット用意することで:
- ① 先手玉視点:自分玉の周りはどうなっているか(守りの形、攻めの当たり)を 256次元に圧縮
- ② 後手玉視点:相手玉の周りはどうなっているか(相手の囲い、相手玉への攻め)を 256次元に圧縮
- ③ 連結後の MLP:両者を比較して「差し引きでどっちが有利か」を学習
つまり 「両玉の関係」は中間層で暗黙的に学習 される設計。直接「玉×玉」というペアの特徴は作らないけれど、両視点を持ち寄ることで実質的に網羅できているわけです。 = 「玉ごとに別個の表現」を学習している。先手と後手で守備の形が違うのは将棋として自然な対称性で、これがアーキテクチャに直接組み込まれているわけです。
📚 補足:派生アーキテクチャ — HalfKAv2 など(クリックで展開)
NNUEは公開後、多くの派生形が研究されています:
- HalfKA:玉と他の駒(玉自身を含む)のペア。Stockfish が採用
- HalfKAv2:HalfKA の改良版。Stockfish 14 以降
- HalfKAv2_hm:水平鏡映で重みを半分にできる版(Stockfish 15)
- 各種拡張:256→512次元化、層を深くする変種など
将棋側でも 水匠 や Háo が独自に拡張しており、各エンジン作者が「自分のセンス」で構造を改良し続けています。
3.5 💭 中間層の次元はどうやって決めたの?(256・32・32 の根拠)
「HalfKP_256x2-32-32」の数字、なぜ 256・32・32 という具体的な値になっているのか。
実は 厳密な数学的根拠はなく、3つの実用制約のバランス から決められています。
✅ 結論:256・32・32 は 「SIMD整数演算と相性が良い数」「計算量と表現力のバランス」「実験で経験的に最適だった値」 という3つの理由で選ばれた組合せ。
❌ 「理論的にこの値が最適」という美しい根拠ではなく、泥臭い経験則の集大成。
🎯 制約①:SIMD整数演算(CPU高速化)との相性
NNUE はCPUで秒間数百万局面を評価するため、SIMD(Single Instruction Multiple Data)整数演算に最適化されています。 これらの命令は 「データを8/16/32個まとめて並列処理」 するので、次元数は SIMDレジスタ幅の倍数 が望ましい:
| SIMD命令セット | レジスタ幅 | int8で一度に処理 | int16で一度に処理 |
|---|---|---|---|
| SSE2 | 128 bit | 16要素 | 8要素 |
| AVX2 | 256 bit | 32要素 | 16要素 |
| AVX-512 | 512 bit | 64要素 | 32要素 |
- 256次元:AVX2 int16 で 16並列 ÷ 16 = 16サイクル でぴったり処理できる
- 32次元:AVX2 int8 なら 1サイクル、SSE2 でも 2サイクルで終わる
- これらは 「16の倍数」なので、どのCPUアーキテクチャでも端数なく処理できる
つまり 「次元数 = 計算速度の整数倍」 という制約が、まず候補を絞り込む。 「256・32」は 「SIMDで割り切れる数の中で性能が出る値」として選ばれた。
⚖️ 制約②:計算量と表現力のトレードオフ
各層の次元を増やすと 表現力(精度)は上がる が、計算量も増える。NNUE の場合は特殊で、層によって性質が違う:
| 層 | 入力 | 計算の重さ | 設計のポイント |
|---|---|---|---|
| W₁(埋め込み) | 41,024 (スパース) → 256 | スパースなので軽い(数十個のindex分だけ計算) | 大きめに取れる(256)。差分更新でさらに速い |
| W₂ | 512 → 32 | 密 = 512×32 = 16,384回の積和 | 後段は密なので 小さく抑える |
| W₃ | 32 → 32 | 密 = 1,024回の積和 | 小さく |
| W₄ | 32 → 1 | 32回の積和 | 最終出力 |
NNUEの設計思想:
- 第1層は太く(41,024→256):スパース入力+差分更新で実質計算量は数百回程度
- 後段は細く(256→32→32→1):密で重いから、小さくしないと爆発する
- この 「太→細→細」のボトルネック構造が、NNUEがCPUで動く秘密
🧪 制約③:実験で見つけた「最適なバランス」
那須 悠氏の元論文(2018年)と、その後の Stockfish の fishtest(大規模A/Bテスト基盤)で 何百もの組合せ を試した結果:
- 256次元:これより小さい(128)と評価精度が落ちる、これより大きい(512)と計算が遅くなる代わりに精度向上が頭打ち
- 32次元:8や16では表現力不足、64や128では計算が重くなる割に精度向上が小さい
- 2層の中間層(32→32):1層だと表現力不足、3層以上だと過学習&速度低下
つまり 「数千回の対戦テストで、勝率が最も高くなる組合せ」として 256-32-32 が選ばれた。 機械学習の世界は「美しい理論」より「Aがいいか、Bがいいか、片っ端から戦わせて確かめる」という泥臭い世界です。
📚 派生形:他の次元設計
標準の 256-32-32 以外にも、研究・実装によって様々なバリエーションがあります:
| アーキテクチャ名 | 次元設計 | 採用例 |
|---|---|---|
| HalfKP_256x2-32-32(標準) | 256-32-32 | Stockfish 12〜、初期のNNUE系将棋AI |
| HalfKAv2_hm_512x2-16-32-1 | 512-16-32-1(深く・厚く) | Stockfish 15以降 |
| HalfKAv2_hm_1024x2-... | 1024次元化(さらに大きく) | 2024〜2025年の最新Stockfish |
| 水匠/Háo の独自設計 | 各エンジンで微調整(公開されない) | 将棋トップ評価関数 |
現代では SIMD命令の進化(AVX-512)+ CPUのキャッシュ容量増加 で、より大きい次元(512、1024)が実用化されてきています。 「256で十分」だった時代から「もっと大きくしても割に合う」時代へと移行中。
🤔 「数学的に最適」な次元はないのか?
理論的には 「万能近似定理(Universal Approximation Theorem)」により、十分大きい次元の隠れ層があれば任意の連続関数を近似できることが保証されています。 ただし「十分大きい」がいくつかは 関数によるし、データ量によるし、計算資源にもよる。
なので実務では 「制約の中での最適化」 = SIMD + 計算量 + 経験的精度のバランス、というアプローチに落ち着く。 これは深層学習全般に共通する「アーキテクチャ設計はサイエンスでもありアートでもある」という事実です。
💡 1行まとめ: NNUEの中間層次元(256・32・32)は 「SIMDの倍数」「ボトルネック構造」「実験で勝った値」 の3つの制約から選ばれた経験則。 理論的な美しさではなく、CPUとデータと計算量の現実的なバランスの産物。
4. HalfKP特徴量 — 「玉と他の駒」のペア
NNUE の 入力 41,024次元 がどこから来るのか。これを理解するのが NNUE理解の核心です。
4.0 そもそも「HalfKP」って何の略?
HalfKP = Half-King-Piece。3つの単語の頭文字を取ったものですが、それぞれが具体的な意味を持っています:
📖 「Half」をもう少し詳しく
「Half = 半分」というのは、「片方のプレイヤー(玉)の視点」 という意味です。 将棋の局面は2人のプレイヤーから見ることができ、それぞれの「玉中心の見え方」が違います:
| 視点 | 玉の位置 | 「他の駒」たち |
|---|---|---|
| 先手玉視点(片方の Half) | 先手玉の場所(例:58) | 盤上の 玉以外の全駒(自分の金・銀・歩、相手の駒も含む) |
| 後手玉視点(もう片方の Half) | 後手玉の場所(例:22) | 盤上の 玉以外の全駒(こちらは後手玉から見たエンコード) |
2つの "Half" を独立にエンコードして連結するのが NNUE の入力設計(前述の "256×2 = 512" の正体)。 「Half」というネーミングは、「片側だけ計算しているけれど、もう片側を組み合わせれば完全な局面表現になる」という意味合いです。
📚 歴史的な背景:「Full KP」と「KPP」
HalfKP の理解のために、Bonanza時代(2005〜)の 関連特徴設計 を並べておきます:
| 特徴設計 | 意味 | 次元数 | 採用例 |
|---|---|---|---|
| KP | K(玉) と P(駒) のペア — 1組 | 81 × 約506 ≈ 41,024 | HalfKPの基本概念 |
| HalfKP | KPを 玉視点ごとに別々 に作る | 41,024 × 2(先手用+後手用) | NNUE 標準 |
| KPP | K(玉) と P-P(2枚の駒)の三項組合せ | 81 × 506 × 506 ≈ 2,000万 | Bonanza(旧時代) |
| HalfKA | KP の派生。"P"を"A"(All — 玉も含む)に拡張 | HalfKPより少し大きい | Stockfish 2020〜 |
| HalfKAv2_hm | HalfKAの改良版+左右対称(horizontally mirrored) | さらに最適化 | 2025年最先端 |
つまり HalfKP = 「玉視点を片方ずつ作る、KP(玉と駒のペア)の特徴設計」というのが完全な意味になります。
4.0.2 持ち駒は HalfKP でどう表現されているか?
鋭い質問です。HalfKP の説明では「盤上の駒」を中心に解説していましたが、将棋には 持ち駒 という独自要素もあります。これはどう特徴量化されているのか?
結論:持ち駒は HalfKP の 「他の駒の(位置 × 種類 × 持ち主)」 の中に、位置を「持ち駒index」 として埋め込む形で表現されています。 盤上駒と同じ枠組みで、ただし「位置」のとり方が違うだけ。
📦 持ち駒の特徴ビットの作り方
盤上の駒は 9×9=81マスの位置index を持っていましたが、持ち駒は盤上にないので、「持ち駒index」 を別途用意します:
- 歩は最大 18枚持てる(盤上 + 持ち駒 = 18)
- 香・桂・銀・金は最大 4枚ずつ
- 角・飛は最大 2枚ずつ
- ※ 玉と成駒は持ち駒に存在しない(玉は取れない、成駒は持ち駒に戻ると元の駒になる)
「持ち駒として何枚持っているか」を 枚数ごとに1ビット立てる形で表現されます:
📐 HalfKP の中の「持ち駒の位置」
盤上の81マスの位置index に 持ち駒index を続けて並べるのが標準実装:
「他の駒」の位置index:
index 0 〜 80 → 盤上 9×9 = 81マス
index 81 〜 98 → 先手の歩 1〜18枚目
index 99 〜 102 → 先手の香 1〜4枚目
...
index ... 〜 156 → 後手の飛 1〜2枚目
合計:81(盤上)+ 76(持ち駒、双方)= 約 157 種類の「位置」index
× 駒種 14 × 持ち主 2 ≈ 506 ※ 既に駒種と持ち主を含むのでさらに調整あり
つまり、HalfKP特徴には 盤上の駒も、持ち駒も、両方が同じビット空間に入っている ということ。 「玉の位置58 × 持ち駒の歩3枚目」のような 持ち駒との関係もエンコード されています。
💡 持ち駒の特徴量化が将棋AIに効く理由
将棋特有の要素である「持ち駒(再投入できる駒)」を入力に含めることで、NNUEは以下のような戦略を学べます:
- 「相手の玉=22 × 自分の持ち駒の桂2枚目」 → 桂打ちでの王手脅威
- 「自分の玉=58 × 相手の持ち駒の銀1枚目」 → 銀打ちで囲いが崩される警戒
- 「自玉=58 × 自持ち駒の金2枚目」 → 守りに金を打って囲い強化できる
持ち駒も「玉中心の関係性」として符号化されるので、攻めにも守りにも、駒の取り合いにも反応できる評価関数になります。 dlshogi も同様に持ち駒チャンネルを入力に持っています。
💡 1行まとめ: 持ち駒は HalfKP の「他の駒の位置」に 「持ち駒index」として埋め込まれる。 盤上駒と持ち駒が同じ枠組みで扱われ、「玉×手駒のペア」として攻め・守りの両面が表現される。
4.1 数えてみる
4.1.5 各パラメータを1つずつ図解する
「81 × 81 × 14 × 2 ≈ 41,024」 の各数字が何を意味しているか、4つのパラメータすべてを図で解きほぐします。
🔵 パラメータ① 「81」 = 玉が置かれうるマスの数
将棋盤は 9筋 × 9段 = 81マス。玉はそのうちの 1マス に必ずいます(取られたら詰み)。 「玉の位置」は 81通り考えられる、ということ:
🟢 パラメータ② 「81」 = 他の駒が置かれうるマスの数
これは①と同じ 9×9 = 81マス。「玉以外のもう1個の駒」が、どこにいるかを表します:
玉=58 だけが盤に置かれた状態を想像してください。他の駒(例えば自分の金、相手の銀など)は、残りの80マスのどれかに置かれる可能性がある。 厳密には「玉の位置と被らない」という制約がありますが、HalfKP では シンプルさのために81通り全部を用意し、玉と重なるindexは使われないだけで存在はする設計。
🟡 パラメータ③ 「14」 = 駒の種類
将棋の駒は 全14種類。8種類の通常駒 + 6種類の成駒:
🟣 パラメータ④ 「2」 = 持ち主(先手 / 後手)
将棋には 2人のプレイヤーがいて、それぞれが駒を持っています。同じ「銀」でも、「先手の銀」と「後手の銀」は別物 として扱う必要があります:
🧮 4つを掛け合わせる — 「組合せの全数え」
ここまでの4パラメータを すべて掛け合わせる と、「玉と他駒のペア」の全パターン数になります:
🔍 「約506」と「約41,024」の正体
元の式 「81 × 506 ≈ 41,024」 の「506」は、上の 「② × ③ × ④」 をまとめた数です:
…のはずですが、実装では 以下の調整で約 506 に圧縮されます:
- 玉自身を除外:玉の位置と「他の駒の位置」が同じになるindexは使わない
- 成駒は盤上のみ存在:持ち駒には成駒がないので、(盤上 + 持ち駒) を分けてカウント
- 「玉⑧」は他駒リストから除外:HalfKP はそもそも「玉と他の駒1個のペア」なので、「他の駒」に玉は含めない(実装により例外あり)
- 持ち駒のindex化:歩×18、香×4、桂×4、銀×4、金×4、角×2、飛×2 を別途エンコード
細かい引き算と足し算の結果、「玉=固定」での他駒index数が約506 になり、玉位置 81 × 506 ≈ 41,024 という最終次元数が決まります。
💡 覚えるべき本質: 「玉の位置 (81)」 × 「他駒の(位置 81 × 種類 14 × 持ち主 2)」。 これがHalfKPの全パターン数を決める 4パラメータの正体。 細かい数字(506や41,024)は実装の都合で変わるが、「4要素の掛け算」という本質は不変。
4.2 📝「玉=58 ×(位置43, 銀, 後手)」を1単語ずつ解読する
上の図の例「玉=58 ×(位置43, 銀, 後手)」は将棋AI界の独特な記法。1個ずつほぐすと:
| 記号 | 意味 | 取りうる値 |
|---|---|---|
| 玉=58 | 先手の 自分の玉が、5筋8段目 にいる | 81通り(盤上のどこか1マス) |
| 位置43 | もう1つの駒が 4筋3段目 にある | 盤上81通り + 持ち駒38通り = 約 119 通り(下の補足参照) |
| 銀 | その駒の 種類が銀将 | 歩・香・桂・銀・金・角・飛・玉・成駒など 約12種 |
| 後手 | その駒は 相手(後手)の持ち物(自分の駒ではない) | 先手 or 後手の2通り |
この4要素の組合せ「玉の位置 × (もう1個の駒の位置, 種類, 持ち主)」が、HalfKP特徴の 1ビットに対応。組合せ総数は:
これが「41,024次元の特徴ベクトル」の正体。
💭 補足:持ち駒は表のどこに入っている?
鋭い質問です。表の 「位置43」の行を見ると "盤上のどこか1マス" という説明ですが、これだけだと 持ち駒(盤外で待機している駒) はどこにも入っていないように見える。
答え:「位置」のindex空間を拡張する。
盤上81マスのあと、「持ち駒の枚数」を別のindexとして連続的に並べる。「位置」のとり得る値は実際には81通りではなく、約157通り(=81 + 持ち駒76)になります。
📦 持ち駒は「枚数index」になる
持ち駒は盤上に存在しないので、座標(筋・段)を持たない代わりに 「○枚目」のindex として連続的にエンコードされます:
「位置」のindex空間(拡張版):
index 0 〜 80 → 盤上 9×9 = 81マス
───────────── (ここから持ち駒の世界) ─────────────
index 81 〜 98 → 先手の歩 1〜18枚目 (歩は最大18枚)
index 99 〜 102 → 先手の香 1〜4枚目
index 103 〜 106 → 先手の桂 1〜4枚目
index 107 〜 110 → 先手の銀 1〜4枚目
index 111 〜 114 → 先手の金 1〜4枚目
index 115 〜 116 → 先手の角 1〜2枚目
index 117 〜 118 → 先手の飛 1〜2枚目
index 119 〜 156 → 後手の持ち駒(同じ並び)
↑ 合計 約157 種類の「位置」
つまり 「位置43」とあれば盤上の4筋3段目、「位置85」のように81以上なら『持ち駒の何枚目か』 という解釈になります。
🎯 具体例:先手が持ち駒に銀を1枚、歩を3枚持っているとき
// 玉=58 を軸にした特徴ビット(持ち駒も含む)
// 1) 盤上の駒たち(前述)
特徴ビット[ 玉=58 × (位置68, 金, 先手) ] = 1 ← 自分の金(盤上68)
特徴ビット[ 玉=58 × (位置43, 銀, 後手) ] = 1 ← 相手の銀(盤上43)
// 2) 持ち駒たち(位置index が81以上)
特徴ビット[ 玉=58 × (位置107, 銀, 先手) ] = 1 ← 自分の持ち駒の銀1枚目
特徴ビット[ 玉=58 × (位置81, 歩, 先手) ] = 1 ← 自分の持ち駒の歩1枚目
特徴ビット[ 玉=58 × (位置82, 歩, 先手) ] = 1 ← 自分の持ち駒の歩2枚目
特徴ビット[ 玉=58 × (位置83, 歩, 先手) ] = 1 ← 自分の持ち駒の歩3枚目
... (持ち駒の各駒について、枚数ぶんビットを立てる)
📐 「506」の正確な内訳
「81 × 12 × 2 ≈ 506」と書いていた 「他駒のindex数 ≈ 506」 の正体は、もう少し正確には:
| 区分 | 計算 | 内訳 |
|---|---|---|
| 盤上の駒 | 81 × 14 × 2 ≒ 約 430 | 81マス × 14駒種 × 先手/後手(玉除外などで調整) |
| 持ち駒 | 38 × 2 = 76 | (歩18+香4+桂4+銀4+金4+角2+飛2) × 先手/後手 |
| 合計 | ≒ 506 | 玉自身を除外する調整などで微減 |
つまり 「位置」「種類」「持ち主」の3要素を掛け算する空間の中に、盤上の駒も持ち駒も全部含まれている。これがHalfKPの設計の巧妙さ。
💭 よくある計算ミス:「81 × (81×14 + 76) × 2」だと持ち主が二重
ここまで読んで「じゃあ式は 81 × ((81 × 14) + 76) × 2 ってこと?」と思った人は鋭い。 ただし 1点だけ持ち主の "× 2" が二重カウント になっています:
❌ よくある誤り:
$81 \times \left((81 \times 14) + 76\right) \times 2 = 81 \times 1{,}210 \times 2 = \mathbf{196{,}020}$
内側の「81 × 14」を片側ぶん(持ち主未込み)と解釈し、「76」が両側ぶん(先手38+後手38)と前提が混ざっている。最後の「× 2」を掛けると、持ち駒の先手・後手が4倍カウントされてしまう。
✅ 正しい2つの書き方
方式A:持ち主を「外側」に出す(片側ぶんで内側を組み立てる)
方式B:持ち主を「内側」に含める(両側ぶんで内側を組み立てる)
どちらも同じ 189,864。「持ち主は内側か外側、どちらか一回だけ掛ける」が鉄則。
📐 ズレの正体
$196{,}020 - 189{,}864 = 6{,}156 = 81 \times 76$ — これがちょうど 持ち駒の持ち主ぶんの二重カウント。
🤔 「189,864 vs 実装値 41,024」のさらなるギャップは?
正しく計算しても 189,864 と実装値 41,024 はまだ 約 4.6倍 違う。これは 実装上の調整 によるもの:
- 玉自身を除外:玉の位置と「他の駒の位置」が重複するindexは使わない
- 駒種の重複削除:成駒は盤上のみ(持ち駒には存在しない)など、不可能な組合せを除く
- 「玉」を他駒の駒種から除外:HalfKP の「他駒」に玉を含めない実装も多い(14→約9-10種)
- 後手玉の特殊扱い:相手の玉も別途エンコードする実装、しない実装で差がある
- 位置の対称化:左右対称・色反転で実質的なindex数を半減
これら全てを反映した 標準HalfKP(Stockfish系)の実装値は約 506 × 81 ≈ 41,024。 つまり 「素直な掛け算 189,864」→「実装で約4.6倍コンパクト化 → 41,024」という流れ。
💡 1行まとめ: 持ち主は 「内側か外側、どちらか一回だけ」 掛ける。 正しく組むと 81 × 2,344 = 189,864通り。さらに実装上の重複削除で 約 41,024 に圧縮される。
🔗 持ち駒のさらに詳しい説明は §4.0.2「持ち駒は HalfKP でどう表現されているか?」 を参照。
🎯 局面 → 特徴ビット への翻訳例
例えば現在の局面が以下のようだったとします:
- 先手の玉が 5筋8段目(58)
- 後手の銀が 4筋3段目(43)
- 先手の金が 6筋8段目(68)
- 後手の歩が 2筋4段目(24)
- ...(他の駒も同様)
「玉が 58 にいる」ことを軸に、他の駒1個ずつとペアを作って、対応する特徴ビットを 1 にしていきます:
// HalfKP 特徴の翻訳(先手側、玉が58のとき)
特徴ビット[ 玉=58 × (位置68, 金, 先手) ] = 1 ← 自分の金との関係
特徴ビット[ 玉=58 × (位置43, 銀, 後手) ] = 1 ← 相手の銀との関係(脅威)
特徴ビット[ 玉=58 × (位置24, 歩, 後手) ] = 1 ← 相手の歩との関係
... (盤上の駒全部について同様、30〜40個のビットが1になる)
それ以外の40,000個以上のビットは全部 0
→ 「スパースな2値ベクトル」になる
🔄 玉が動くと、すべての特徴ビットが切り替わる
ここが重要な性質:玉の位置が1マスでも動くと、その玉に関するすべての HalfKP 特徴ビットが切り替わる。
- 玉が 58 → 68 に動く
- 「玉=58 × ...」の特徴は全部 0 になる
- 「玉=68 × ...」の特徴が新しく 1 になる
- つまり 玉以外の駒の配置は同じでも、特徴ベクトル全体が大きく変わる
これが「玉中心の局面表現」の意味。玉の位置によって、まったく違う特徴になるのがHalfKPの設計思想です。 将棋では「玉の周りがどうなっているか(囲い、王手の脅威、安全度)」が勝敗を左右するので、玉の位置を軸にして他の駒との関係をエンコードするのは 非常に自然。
💡 HalfKP の "Half" って?
"Half" は「半分」。先手用と後手用で 独立した41,024次元の特徴ベクトルを2セット用意するから "Half"(各プレイヤー視点での「半分」の意味)。
両者を NN の入力で連結して 256×2=512次元に埋め込むのが §3 で見た構造です。
4.3 なぜ「玉」を基準にするのか
将棋において 玉は他のどの駒とも違う特別な存在です:
- 玉を詰まされたら負け=玉の安全度が局面評価の中心
- 囲い(美濃・矢倉・穴熊など)は 玉とその周辺の駒の配置パターン
- 攻め筋も「相手玉に対してどこから手を作るか」で決まる
- =玉の位置を基準にすると、駒の意味が定まる
HalfKP は 「玉の位置」と「他の駒1つ」のペア を全部列挙することで、この 「玉中心の局面表現」 を機械的に符号化したものです。 「囲いを学習」「攻めの形を学習」と人間が言わなくても、ニューラルネット側がデータからパターンを抽出してくれる。
📚 補足:KPP・KPPT との関係(クリックで展開)
NNUE 以前の Bonanza系評価関数では:
- KPP:King-Piece-Piece — 玉と「他の2駒」の3項関係。次元はもっと大きい(数千万)
- KPPT:KPPに「手番(Turn)」を加える
- KKPT:「両方の玉」と1駒、+手番
HalfKP は「玉と他の1駒」だけに減らした分、次元が4万程度に収まり、ニューラルネットの埋め込み層で扱いやすくなりました。 「2駒関係はネット内部で自動的に学習される」という割り切りが、NNUE の設計哲学の核心です。
4.4 💭 鋭い疑問:HalfKP の限界 — 銀と金の位置関係や、シンプル化の余地
HalfKPの設計を学んでくると、自然と2つの疑問が湧きます。両方とも NNUE設計のトレードオフの本質 に関わる重要な質問です:
疑問①:HalfKPで「美濃囲い」のような囲い(=銀と金の位置関係)は表現できる?
✅ 直接の入力としては表現していません。 HalfKPは「玉 × 他の駒1個」のペアだけを並べるので、「銀=77 × 金=68」のような 「玉以外の駒同士のペア」 は入力ビットに存在しない。
ではどうやって 美濃囲い・矢倉などの「複数駒で構成される囲い」 を認識しているか?答えは 「中間層のニューラルネットが暗黙的に合成して学習している」:
- 入力時点:「玉=58 × 銀=77」と「玉=58 × 金=68」が 別々のビット として両方1になる
- W₁ 埋め込み:それぞれが256次元のベクトルに変換される(ベクトル足し算で「合成」される)
- ClippedReLU + W₂, W₃ の非線形変換:「銀@77 と 金@68 が 両方 立っている時の特別な意味(=美濃囲い)」を組み合わせとして学習
- 結果:囲いの完成パターンを認識し、そのぶん評価値を上げる
つまり 「玉×単一駒」を片っ端から並べておけば、その組み合わせ(=複数駒の関係)は MLP が学習で発見してくれる という設計思想。これがニューラルネットの「合成可能性(compositionality)」の力です。
疑問②:それなら全部MLPに任せて、入力は「81×14×2 = 2,268次元」のシンプルな盤面表現でいいのでは?
これは将棋AI界の本質的な分岐点で、答えは「アーキテクチャ次第」。
✅ 深いCNNを使う dlshogi は実際にそれに近い表現を採用(9×9×100 ≈ 81×100)。
❌ 浅いMLPの NNUE では関係性を学習しきれないので、人手で「玉中心の組合せ」を入力に組み込む必要がある。
3つの選択肢の比較
| 入力表現 | 次元数 | 関係性の表現 | 必要なネット | 採用例 |
|---|---|---|---|---|
| 生の盤面 (81 × 14 × 2) |
2,268(小さい) | ❌ ゼロ(ただの位置情報) | ⚠️ 深いCNN(数十層) | dlshogi の9×9×100 |
| HalfKP (玉 × 他駒) |
41,024(中規模) | △ 玉中心の関係性のみ (残りはMLPに任せる) |
✅ 浅いMLP(4層)でOK | NNUE(採用中) |
| KPP (玉 × 駒 × 駒) |
2,000万+(巨大) | ✅ 駒3つの組合せまで明示 | 線形評価関数で十分 | Bonanza(旧時代) |
なぜNNUEは「中間サイズ」のHalfKPを選んだのか
- 生の盤面(2,268次元):シンプルすぎて浅いMLPでは情報が足りない。深いCNNが必要だが、CPUで高速動作させるのが困難(dlshogi系がGPU必須なのはこのため)
- KPP(2,000万次元):表現力は最強だが、次元数が大きすぎてメモリ・計算量が破綻。Bonanza時代でも完全実装は難しく、サンプリングで誤魔化していた
- HalfKP(41,024次元):絶妙なバランス。「玉中心」という人類の経験知で関係性のうち最重要なものを明示し、残りはMLPに任せる。CPUで秒間数百万局面を達成する唯一の現実解
2025年の最先端:HalfKAv2_hm
実は2025年現在、最強ソフトは HalfKP のさらに改良版 HalfKAv2_hm を使っています:
- HalfKA:玉も含めて全駒の組合せに拡張("P" → "A" = All pieces)
- v2:バージョン2の改善
- hm:horizontally mirrored(左右対称性を活用)
- 次元数はさらに増えるが、現代CPUのSIMD性能向上で吸収できるようになった
つまり 「もっと豊かな関係性を直接表現するほうが強い」 という方向に進化しつつあります。HalfKPは「最初の実用解」で、今後さらに高度化していくでしょう。
💡 1行まとめ: HalfKPは 「銀×金の直接表現はないが、玉中心のペアを並べておけば中間層が組み合わせを学習する」 という設計。 生の盤面では浅いMLPに荷が重く、KPPでは次元数爆発 — その中間で実用的なバランスを取ったのがHalfKP。 2025年は HalfKAv2_hm など改良版に進化中。
4.5 💭 よくある疑問:HalfKP特徴って、誰がどうやって作ったの?自動学習されるのでは?
ここはNNUEの本質を理解するうえで とても重要な疑問 です。結論から:
❌ 自動では作られません。
✅ HalfKP特徴は、人間(将棋AI開発者)が「将棋にはこういう情報が大事」と考えて設計した特徴量 です。
自動で決まるのは「特徴量にかける 重み W」のほうだけ。
2つの「決めごと」を切り分ける
機械学習には、「人間が決めるもの」と「機械が学習で決めるもの」 がはっきり分かれています:
| 誰が決める? | NNUEでの例 | |
|---|---|---|
| ① 特徴量の設計 (何を入力として渡すか) |
👤 人間 | HalfKP特徴:玉と他の駒の組合せを並べる、と人間が決めた |
| ② 重みの最適化 (特徴をどう組み合わせて評価値にするか) |
🤖 学習で自動 | W₁, W₂, W₃, W₄ の数百万個の数値が、勾配降下法で自動決定 |
「ニューラルネットは何でも自動学習する」というイメージは 深層学習(深いCNNなど)の世界の話で、NNUEは違います。
HalfKPは「Bonanzaメソッド時代からの設計遺産」
HalfKP は2018年の NNUE 発明と同時に生まれたわけではなく、2005年の Bonanza メソッド時代から続く「人間が考え抜いた特徴設計」の発展形です。
- 1990〜2005年:人間が「玉の周りの駒配置パターン」を手で点数化(職人技)
- 2005年(Bonanza):「KPP(玉×駒×駒の三項組合せ)」という特徴を人間が設計し、その重みは機械学習で自動決定 — これがブレイクスルー
- 2018年(NNUE):Bonanzaの KPP に近い思想を HalfKP(玉×単一駒の二項組合せ)として再設計し、NN化+差分更新を可能に
つまり HalfKP は 「玉中心の局面表現が将棋AIには効く」という人類の経験知を、ニューラルネットの入力に最適な形に整理したもの。「玉」「駒の位置」「駒の種類」「持ち主」 という4つの概念を全部人間が定義しています。
対比:dlshogi(深いCNN)は本当に「特徴を自動学習」する
一方、dlshogi や AlphaZero 系は、入力をほぼ 「生の盤面」のままCNNに渡します:
| NNUE(浅いMLP) | dlshogi(深いCNN) | |
|---|---|---|
| 入力 | HalfKP特徴ベクトル (41,024次元、玉と駒のペア) |
9×9×100テンソル (駒種ごとのバイナリマップ) |
| 「玉と駒のペア」概念は… | 👤 人間が 明示的に入力で与える | 🤖 CNNが 畳み込みで自動発見する |
| 美濃囲い・矢倉の認識は… | HalfKPの組合せでネットが暗黙的に学習 | 中間層のCNNが「囲いパターン」を自動抽出 |
| 必要なネットの深さ | 4層(特徴が下準備済みなので浅くてよい) | 20〜40層(生データから抽出するので深く必要) |
| 動作環境 | CPU(軽い) | GPU必須(重い) |
なぜNNUEは「自動特徴抽出」を諦めたのか
NNUE は あえて深層学習の流儀に逆らって、人間設計の特徴を採用しています。理由は 「CPU上で動く速さ」:
- 深いCNN:自動特徴抽出は強いが、GPUがないと推論が遅すぎる(1局面あたり数ミリ秒)
- NNUE:人手の特徴で「下準備」しておけば、その後のMLPは浅くて済む。CPU+SIMDで1局面マイクロ秒オーダーの評価が可能
- α-β探索は 大量の局面を読む ので、1局面の評価が速いことが死活問題
つまりNNUEは 「人間が頑張って特徴を設計するかわりに、何百万局面でも秒間で評価できる」 というトレードオフを選んだ設計。dlshogi はその逆で「特徴抽出はGPUに任せて、1局面の深い読みで勝負」を選んだ。
💡 1行まとめ:
NNUEのHalfKP特徴は 「人間が設計した将棋AI界の伝統的特徴」。
自動で決まるのは特徴量を組み合わせる重みのほう。
「特徴を自動学習する」深層学習の流儀は dlshogi 側の話で、NNUEはあえてその逆を行くことで CPU高速評価を実現している。
5. 3つの工夫 — なぜCPUで秒間数百万局面が動くか
NNUE が CPU でほぼ古典評価関数並みに高速なのは、3つの工夫の合わせ技です。
5.1 工夫① 差分更新(Efficient Update)
最大の発明がこれ。「1手指したとき、入力層から第1層への計算を、変化分だけにする」。
効果:1手進めるごとの第1層計算が 「数万次元の行列積」→「ベクトル数本の加減算」 になり、1000倍以上高速化します。
探索中、1秒間に 数百万局面を読むα-β探索でも、評価関数の呼び出しがボトルネックにならない理由がここにあります。
擬似コードで見る:全再計算 vs 差分更新
将棋の特徴:1手指しても、盤面のほとんどは変わらない。動くのは1〜2個の駒だけ。 つまり、特徴ベクトルの中で 変化するビットも数個だけ。 なら、前局面の埋め込み(256次元)を そのまま再利用し、変化した特徴ビットぶんだけ加減算すればいい:
// 全再計算(重い)
function evaluateFromScratch(position) {
const accumulator = new Array(256).fill(0);
const activeIndices = extractHalfKPFeatures(position); // 30〜40個
for (const idx of activeIndices) {
for (let k = 0; k < 256; k++) {
accumulator[k] += W1[k][idx]; // 30×256 ≈ 7,680回
}
}
return accumulator;
}
// 差分更新(爆速)
function evaluateDifferential(position, prevAccumulator, move) {
const accumulator = [...prevAccumulator]; // 前局面のをコピー
const diff = computeDiff(move); // 動いた駒だけ抽出(通常1〜2個)
for (const idx of diff.added) for (let k = 0; k < 256; k++) accumulator[k] += W1[k][idx];
for (const idx of diff.removed) for (let k = 0; k < 256; k++) accumulator[k] -= W1[k][idx];
return accumulator; // ★ 256 × 2〜4 ≈ 500〜1,000回だけ
}
- 全再計算:1局面あたり 約 7,680回(スパース版)
- 差分更新:1局面あたり 約 500〜1,000回
- → 10〜15倍の高速化。これが「秒間数百万局面」を成立させる秘密
💡 1行まとめ: 差分更新=「前局面の計算結果を覚えておいて、変化したぶんだけ加減算する」。 将棋の「1手で盤面がほぼ変わらない」性質を活用した、極めてプログラマ的な最適化。
🧠 補足:なぜ加減算で済むのか — 線形性(クリックで展開)
第1層は 線形変換(W₁ ⋅ x + b)。線形だから、入力ベクトル x が「変化分 Δx」だけ動いたら、出力も「W₁ ⋅ Δx」だけ動く。
そして「Δx」は 消えるビット数個 + 増えるビット数個 しかないので、対応する W₁ の列を 足し引きするだけでOK。
第2層以降は活性化関数(ClippedReLU)が入って 非線形になるので、差分更新は使えない。 でも第2層以降は 32次元 × 32次元 のような小さい行列なので、毎回フル計算しても全然問題ない。
=「大きい層だけ差分、小さい層はフル計算」というメリハリが NNUE の天才的な設計です。
5.2 工夫② 量子化(Quantization)
通常のニューラルネットは float32(4バイト)で重みを保存し、計算します。NNUE はこれを int8 / int16(1〜2バイト)に圧縮します。
| 箇所 | 型 | 備考 |
|---|---|---|
| 第1層の重み W₁ | int16 | 大規模なので精度を残す |
| 第2〜4層の重み | int8 | 小さいので8bit で十分 |
| Accumulator | int16 | 差分加算の累積誤差を抑える |
| 活性化(ClippedReLU後) | uint8 (0〜127) | SIMDベクトル演算に最適 |
効果:
- モデルサイズが float32比で 1/2〜1/4(50MB → 25〜12MB)
- CPU の SIMD命令(AVX2、AVX-512)で 1命令で16〜64要素を並列計算できる
- L1/L2キャッシュへの収まりが良くなり、メモリ帯域でも有利
5.3 工夫③ ClippedReLU
活性化関数として、普通のReLUではなく ClippedReLU を使います。
ClippedReLU(x) = clamp(x, 0, 127)
= min(max(x, 0), 127)
つまり「0未満なら0、127超なら127、それ以外はそのまま」。出力が必ず 0〜127 の整数範囲 に収まるので:
- uint8 として表現でき、量子化と相性が完璧
- SIMD命令の 飽和加算(saturated arithmetic)と直接対応
- 勾配は ReLU と同じ(0〜127区間で 1、それ以外で 0)なので学習も問題ない
ふつうのReLUとClippedReLUの違い(図で比較)
なぜ「127」?— int8 の都合
- int8 の範囲は −128〜+127
- ReLU は出力が常に 0以上 なので、実質 0〜127 が使える
- ClippedReLU は 上限を 127 にすることで、活性化を int8 でそのまま表現できる
- もし 200 や 1000 を許すと int8 に収まらず、より大きな型(int16 や float)が必要になり、SIMDの並列数が半分になってしまう
📚 補足:通常のReLUとClippedReLUの違い(数式)(クリックで展開)
通常のReLU:
$$\mathrm{ReLU}(x) = \max(0, x)$$
ClippedReLU:
$$\mathrm{ClippedReLU}(x) = \min(\max(0, x), 127)$$
上限を設けるだけ。これだけでハードウェア(CPU SIMD)と相性が劇的に良くなる。 「数学的にエレガント」より「計算機にとって嬉しい」を優先した工学的判断です。
5.4 3つの工夫の組み合わせ効果
💡 結論: 差分更新で大きな第1層の再計算を省き、 量子化で残りの層を整数で計算し、 ClippedReLUでSIMD演算に乗せる。 この3つが噛み合うことで、NNUE は CPU 1コアで 1秒に200万〜500万局面 を評価できるようになりました。
💭 鋭い疑問:NNUEで「序盤の定跡」はどう評価される?
HalfKP は「玉中心の駒関係」を表現する仕組み — 一見、序盤の定跡(例えば矢倉囲い、振り飛車、相掛かりなど)の知識がどこに入っているのか分からない。実は NNUE は 定跡を直接知っているわけではない。
✅ NNUE は 「単なる局面評価関数」。序盤・中盤・終盤の区別はなく、どんな局面でも「評価値」を1つ返す。
✅ 序盤の定跡は NNUE の外側で「定跡データベース(book)」として別管理される。
✅ 加えて、学習データに序盤局面を大量に含める ことで、NNUE自体もある程度の序盤理解を獲得する。
序盤を支える3つの仕組み
将棋AI(例えば「やねうら王 + 水匠NNUE」)の指し手選択は、実は 「定跡 → 探索+NNUE評価」 の2段階で動いています:
仕組み① 定跡データベース(book)— 序盤を「カンニング」する
やねうら王には 定跡ファイル(拡張子 .db や .book)を読み込む機能があります。
これは 「この局面ならこの手が最善」 という対応表で、強豪AI同士の対局結果から自動生成されます。
定跡ファイルの構造(イメージ)
// やねうら王の定跡ファイル形式(簡略化)
局面: 平手の初期局面(先手番)
指し手: ▲7六歩 評価値+25 勝率53% 探索深さ40 出現頻度65%
指し手: ▲2六歩 評価値+22 勝率52% 探索深さ40 出現頻度30%
指し手: ▲5六歩 評価値+15 勝率51% 探索深さ35 出現頻度5%
局面: ▲7六歩△8四歩▲2六歩△8五歩...
指し手: ▲7七角 評価値+30 勝率54%
指し手: ▲8八銀 評価値+18 勝率51%
...
定跡データベースの生成方法:
- 初期局面から 深さ40〜60手 ほどの深い探索を、各分岐について実行
- 結果として「この局面ではこの手が良い」というデータを蓄積
- 1つの定跡ファイルに 数十万〜数百万局面 のデータ
- 水匠の作者・たややん氏が公開している定跡ファイルが業界標準
実戦時の動き:序盤の20〜40手程度は定跡を引いて即指し、定跡が切れたら α-β + NNUE に切り替わる。 つまり 「NNUEは序盤を考えていない」のが現実 — 序盤は完全に定跡任せ。
仕組み② NNUE自体も序盤局面で学習している
ただし、定跡から外れた局面(相手が変な手を指した、定跡データにない局面)では NNUE が考える必要があります。 このために 学習データに序盤局面も大量に含めて、ある程度の序盤理解を獲得しています:
- 自己対局の 序盤の数十手 も学習データに含まれる
- 「玉の囲い形」「飛車・角の働き」「駒組みの効率」などを HalfKP 経由で間接的に学習
- ただし 定跡データベースより精度は劣る(深く読むのと「評価関数で1回見る」のは違う)
仕組み③ 💡 dlshogi の棋譜を「知識蒸留」で取り込む(2024〜2025の革命)
ここがご質問の 「NNUE系でも dlshogi で機械同士が戦った棋譜を学習データにすればいいのでは?」 に対する答え:
✅ まさにそれが現在行われています! 『将棋AIのゆくえ』で大森氏(氷彗開発者)が語った、2024〜2025年の最大のブレイクスルー: dlshogi の棋譜・推論結果を NNUE の教師データとして使う「知識蒸留」。
知識蒸留の3パターン
| 方式 | 何を教師にするか | NNUEが学ぶこと |
|---|---|---|
| ① 棋譜蒸留 | dlshogi 同士の自己対局棋譜 | 「dlshogi が好む指し手の流れ」 |
| ② 推論結果蒸留 | dlshogi の評価値・方策分布 | 「dlshogi の局面評価そのもの」 |
| ③ ハイブリッド | 従来の自己対局 + dlshogi 推論 | 両方の良いとこ取り(実用的) |
知識蒸留がもたらした効果
『将棋AIのゆくえ』で大森氏が語った具体的な数字:
- 2024年(電竜戦)の水匠:知識蒸留 なし モデル
- 2025年(WCSC35)の水匠10:知識蒸留 あり モデル
- レーティング差 175点以上(勝率約75%以上の差)
- 「相当強くなりましたね」と大森氏
山岡氏(dlshogi開発者)も「自分のモデルがNNUE系の底上げに使われている」ことに前向き:
山岡「DL系の技術が NNUE系を底上げしてるっていうのは、ネガティブな印象は特になくて(笑)。ブレークスルーが NNUE系でも起こってるっていうのは、技術的な興味というところでは面白いと思いますし」
※ 『将棋AIのゆくえ』松本博文(2026, マイナビ出版)第2章より要約引用
なぜ知識蒸留が効くのか
通常の自己対局で生成される教師データには、α-β探索の限界(読みが浅い、横並びの手を見落とす) が含まれてしまいます。 一方 dlshogi は MCTS + 深いCNN で局面の評価を出すので、α-β とは違う角度から「正解」を提示できる:
- α-β探索のNNUE:「読みが深いところでは強いが、深さの限界で見落としがある」
- MCTS+CNNのdlshogi:「読みは少ないが、局面の本質的な評価が違う角度」
- 両者を組み合わせると:互いの盲点を補完 → 相乗効果でレベルアップ
これは 第5章 で学んだ「知識蒸留(distillation)」 の典型的な活用例。元々は「大きいモデル → 小さいモデル」の文脈でしたが、ここでは 「異なるアーキテクチャ間の蒸留」 という新しい使い方になっています。
📊 序盤評価の3つの仕組みまとめ
| 方法 | 適用範囲 | 強み | 弱み |
|---|---|---|---|
| ① 定跡データベース | 序盤 20〜40手 | 事前計算で超高速・超精度 | 定跡外の局面では使えない |
| ② NNUE自身の学習 | 序盤〜終盤すべて | 汎用性 | 序盤は dlshogi より弱い傾向 |
| ③ dlshogi 知識蒸留 | NNUE全体の底上げ | 異なる角度の知識を獲得 | dlshogi の計算リソースが必要(学習時のみ) |
💡 1行まとめ: NNUE自体は序盤の定跡を直接知らない。だから現代の将棋AIは 「定跡データベース(カンニング)+ NNUE評価関数(応用)+ dlshogi知識蒸留(さらなる底上げ)」 の3段階で序盤を攻略している。 ご指摘の 「dlshogi の棋譜を NNUE学習に使う」 は、まさに2024〜2025年に実現したブレイクスルー。
6. 学習方法 — 教師あり学習でネットを作る
NNUE の評価関数ファイル(.nnue または各エンジンの nn.bin)は、教師あり学習で作られます。
第4章「学習の仕組み」 で学んだ Adam・MSE・誤差逆伝播法が、ここで全部使われます。
6.1 教師データの作り方
- 初期評価関数(既存の強いNNUE、または古典評価関数)を用意する
- それを使って 自己対局(または定跡からのランダム開始局面を多数)を行う
- 各局面で 探索評価値(α-β探索で深く読んだ評価値)を計算
- その対局の 最終勝敗(先手勝ち=1、後手勝ち=0、引き分け=0.5)も記録
- =1サンプルが 「局面、探索評価値、勝敗」の3つ組
こうして 数億〜数十億局面 のデータセットを作ります。これを 「教師局面」 と呼びます。
6.2 損失関数 — MSEと交差エントロピーの混合
NNUE の予測したい量は 「評価値」。教師として使えるのは:
- 探索評価値:強いエンジンが深く読んだ評価値(連続量)
- 勝敗:その局面から実際にどちらが勝ったか(離散)
この2つを混ぜた損失関数を使います:
$$\mathcal{L} = \lambda \cdot \mathrm{MSE}(\hat{v}, v_{\mathrm{search}}) + (1 - \lambda) \cdot \mathrm{CE}(\hat{v}, v_{\mathrm{game}})$$
ここで λ は混合比(典型的に 0.5〜0.7)。
MSE=探索評価値との二乗誤差、CE=勝敗との交差エントロピー。
なぜ混ぜるのか:
- 探索評価値だけだと「元エンジンを超えられない」(既知の問題)
- 勝敗だけだと「ノイズが多い」(強い側が悪手を指して負けることもある)
- → 両方を混ぜると、お互いの欠点を補える
💭 鋭い疑問:AlphaZero は勝敗だけで学習してる。なんで NNUE は探索評価値も混ぜるの?
おっしゃる通り、AlphaZero / dlshogi は 「最終勝敗」だけ を教師にしています(厳密には Policy ヘッドは MCTS の訪問回数分布も使うが、Value ヘッドは勝敗のみ)。 なぜ NNUE は探索評価値(連続値)まで使うのか?答えは 「学習スタイルが根本的に違う」 から:
NNUE:教師あり蒸留(knowledge distillation)。
既に強い別エンジン(教師)の評価値を真似する設計。だから教師の連続値スコアを使える。
AlphaZero:純粋な自己対局強化学習(self-play RL)。
教師が存在しないので、勝敗という究極の真実だけを頼る。
📊 2つの損失関数を並べて見る
| NNUE(教師あり蒸留) | AlphaZero / dlshogi(自己対局RL) | |
|---|---|---|
| 損失関数 | $\mathcal{L} = \lambda \cdot \mathrm{MSE}(\hat{v}, v_{\mathrm{search}}) + (1-\lambda) \cdot \mathrm{CE}(\hat{v}, v_{\mathrm{game}})$ | $\mathcal{L} = (z - \hat{v})^2 - \pi^\top \log \hat{p} + c\|\theta\|^2$ |
| Value(評価値)の教師 |
2種類: ① 探索評価値 $v_{\text{search}}$(連続) ② 最終勝敗 $v_{\text{game}}$(離散) |
1種類のみ: 最終勝敗 $z \in \{+1, 0, -1\}$ |
| Policy(指し手)の教師 | なし(NNUEはPolicyヘッドを持たない) |
MCTS の訪問回数分布 $\pi$ (自分自身の探索結果が教師) |
| 教師の正体 |
外部の強いエンジン (水匠、Háo、Stockfishなど) |
自分自身(過去バージョン) 無人の自己対局 |
| 外部データ依存 | 強い教師エンジンが必要 | 不要(ルールだけ) |
| 到達可能な強さの上限 | 原理的には教師の強さに収束(蒸留の限界) | 原理的に無限(自己改善の積み上げ) |
なぜ NNUE は「勝敗だけ」では足りないのか
重要なのは、NNUE の世界では「探索評価値が手に入る」という大前提があること。具体例で考えると:
1局の自己対局(200手)から学習する状況を想像
- 結果:先手勝ち(z = +1)
- 勝敗だけで学習すると:200局面 すべてに「+1」のラベルが付く
→ 中盤の すでに後手有利になっていた局面 にも「+1」が付いてしまう
→ 「強い側が悪手で負けかけたが、相手の悪手で逆転」みたいなケースをノイズとして学習してしまう - 探索評価値も使うと:各局面に「+500(やや先手有利)」「-300(やや後手有利)」のような連続的な真実値を割り当てられる
→ ノイズが激減、収束が速い
なぜ AlphaZero は「勝敗だけ」で済むのか
一見、AlphaZero も同じ問題があるはず。なぜ最終勝敗だけで強くなれるのか?答えは MCTS の訪問回数分布:
- AlphaZero は MCTS(モンテカルロ木探索)で各手を「賢く読む」ので、訪問回数の分布 $\pi$ が既に「その局面で良い手の確率」を表している
- つまり Value ヘッドの教師は「最終勝敗」だけでも、Policy ヘッドの教師として「MCTS の方策分布」がある → 各局面に局所的な情報が注入される
- 結果として、最終勝敗の「ノイズ」を Policy 学習で補える設計になっている
NNUE は Policy ヘッドを持たない(評価値のスカラー1つだけ出力)。だから Value の教師に「探索評価値」を追加することで、AlphaZero の Policy ヘッドが果たす役割を補っているとも言えます。
2025年の派生:ハイブリッド学習
最近の研究では 「自己対局+外部教師の混合」 や 「最初は教師あり、後半は強化学習」 のハイブリッド学習も試みられています。 NNUE の損失関数も、Stockfish 由来の "Lambda mixing" 以外に、WDL(Win-Draw-Loss) や knowledge distillation の改良版などが研究されており、両派の長所を取り込む方向に進化中。
💡 1行まとめ: AlphaZeroは「教師がいないから勝敗しか使えない」、NNUEは「強い教師がいるからその評価値も使える」。 損失関数の違いは設計思想ではなく 「使える教師信号の違い」の帰結。
6.3 オプティマイザと学習設定
| 項目 | 典型値 |
|---|---|
| オプティマイザ | Adam(または Ranger、RAdam) |
| 学習率 | 初期 8.75e-4 → cosine annealing |
| バッチサイズ | 16,384 局面 |
| エポック数 | 400 epoch(モデル次第で増減) |
| 必要GPU | RTX 3090 / 4090 等を1〜数枚 |
| 学習時間 | 数日〜2週間 |
| 使用フレームワーク | nnue-pytorch(PyTorchベース) |
つまり 「学習はGPU、運用はCPU」 という非対称な設計。 dlshogi が学習も運用もGPUなのと対照的です。
🧠 補足:nnue-pytorch のリポジトリ(クリックで展開)
NNUE 学習の標準フレームワークは glinscott/nnue-pytorch(Gary Linscott 氏らによる Stockfish 向け)と、
将棋向けの tanuki-/nnue-pytorch 派生があります。
主な構成:
model.py:HalfKP_256x2-32-32 のPyTorch実装features.py:特徴量抽出train.py:PyTorch Lightning ベースの学習ループserialize.py:学習済みモデルを.nnueバイナリに変換(量子化込み)
第4章「誤差逆伝播法」 で見た理論が、このコードで実際に動いています。
6.4 量子化の難しさ
学習はGPUで float32のまま行いますが、最終的に int8/int16 に変換して保存します。この変換が一筋縄ではいきません。
- 量子化感応学習(Quantization-Aware Training, QAT):学習中から「実行時は量子化されることを意識した」勾配計算をする
- 重みクリッピング:W₁ の重みが int16 範囲(±32,767)を超えないようにペナルティを課す
- キャリブレーション:学習後、検証データで活性化の分布を測り、最適なスケーリング係数を決める
これらを怠ると「学習時は強いが、量子化後に大幅に弱くなる」事故が起きます。NNUE 開発者の経験知が詰まっている部分です。
7. NNUE エコシステム — 代表的な評価関数
NNUE は「探索部と評価関数を分離して、評価関数ファイルだけ差し替えられる」という設計のおかげで、巨大なエコシステムが生まれました。
7.1 主要な NNUE評価関数(将棋)
| 名前 | 作者 | 特徴 | ファイル形式 |
|---|---|---|---|
| 水匠(Suisho) | たややん氏 | 最も人気。商用利用可。プロ棋士・解説者にも採用 | nn.bin |
| Háo(ハオ) | illqha チーム | 高棋力・安定。第33回世界コンピュータ将棋選手権上位 | nn.bin |
| tanuki- | tanuki-チーム | 研究色が強い。学習データ・手法を公開 | nn.bin |
| GCT | GCTチーム | 2020年代前半に活躍 | nn.bin |
| illqha | illqha | Háoの系統。独自学習を積極展開 | nn.bin |
これらはすべて 同じ NNUEアーキテクチャ(HalfKP_256x2-32-32 or 派生)を使いますが、学習データと学習方法が違うため、得手不得手や棋風が微妙に異なります。
7.2 ファイル形式と互換性
NNUE評価関数の実体は 1個のバイナリファイルです:
- 将棋系:
nn.bin(やねうら王が読む)または独自拡張子 - チェス系:
.nnue(Stockfish が読む) - サイズ:通常 20〜70 MB(量子化済み)
- 中身:マジックナンバー + アーキテクチャ情報 + 各層の重み(量子化)
ファイルを差し替えれば、エンジン本体(やねうら王)はそのままで評価関数だけ切り替わる。これが NNUE エコシステムの強さです。
7.3 探索部との関係
NNUE評価関数は単体では動きません。α-β 探索部と組み合わせて初めてエンジンになります。
- 将棋:やねうら王 が事実上の標準探索部
- チェス:Stockfish が探索部
つまり実運用は「やねうら王 + 水匠」「やねうら王 + Háo」のような 「探索部 × 評価関数」のペアです。 探索部と評価関数の 分業 が成立しているのが、NNUE エコシステムの特徴。
📚 補足:定期的なベンチマーク — floodgate と電竜戦(クリックで展開)
新しい NNUE評価関数の棋力は、主に2つの場で測定されます:
- floodgate:山下宏氏らが運営する将棋AIのオンライン対戦サーバ。24時間 NNUE 同士が戦い、レーティングが算出される
- 世界コンピュータ将棋選手権(WCSC):年1回の選手権大会
- 電竜戦(DENRYU-SEN):もう1つの年次大会
新しい評価関数が公開されると、開発者たちは floodgate で評価し合い、レーティングで比較します。 R30〜R50 程度の差で「明確に強くなった」と判断され、新世代として広まる、という生態系です。
8. NNUE が変えたもの
8.1 「機械学習で将棋AI」のハードルが激減
NNUE 以前、強い将棋AIを作るには:
- 古典評価関数の KPP/KPPT を手作業で設計し
- Bonanzaメソッドで 数千万のパラメータを最適化し
- そこに 独自の特徴量チューニングを積み上げる
という長い暗黒の調整作業が必要でした。NNUE 以降は:
- nnue-pytorch に教師局面を投入すれば、誰でも強い評価関数を学習できる
- 水匠 や Háo のような完成品をベースに、ファインチューニングだけで改良できる
- 個人開発者が 世界コンピュータ将棋選手権で優勝することすら可能になった
8.2 ノートPCでもプロ棋士超え
NNUE は CPU 1コアで秒間数百万局面。普通のノートPC(4〜8コアCPU)で:
- 秒間 2,000万〜4,000万局面 の探索が可能
- レーティングは R4,500 前後(人間のトッププロは R3,300 前後)
- つまり 「ノートPC + やねうら王 + 水匠」で人間のプロを大幅に上回る
これは AlphaZero 系(GPU必須・電力消費大)と決定的に違う点で、「AI が日常デバイスに降りてきた」瞬間でした。
8.3 分業エコシステムの成立
NNUE のおかげで、コンピュータ将棋界に 明確な分業構造が生まれました:
- 探索部の作者(やねうらお氏など):探索アルゴリズム、α-β、SEE、LMR、Null move pruning などを磨く
- 評価関数の作者(たややん氏、illqha など):ネット構造、学習データ、教師生成手法を磨く
- ユーザー(プロ棋士、研究者、解説者):完成品を組み合わせて活用
「強くするには全部自分で作る」だった時代から、「各人が得意な層を担当して連携する」時代になった。 これは CEDEC2024 講演 でも繰り返し強調されている重要な構造変化です。
8.4 日本発の技術がチェス世界へ
前述のとおり、2020年9月の Stockfish 12 で NNUE が公式採用され、以後チェス界の 世界標準に。 その後、Lichess の評価関数、Leela Chess Zero との対決、Kaggle の Chess Engines コンペなど、あらゆるチェスAI関連の場で NNUE が当たり前に使われています。
💡 歴史的意義: NNUE は 「日本発のAI技術が世界標準になった」稀有な事例。 これは将棋AI開発者コミュニティの数十年間の蓄積、なかでも那須悠氏の発明、Hisayori Noda 氏らの Stockfish 移植努力、そしてオープンソース文化の賜物です。
9. 関連リンク
9.1 サイト内の関連ページ
- 第3章「NNの基礎」 — NNUEの土台になる MLP・全結合層・活性化関数
- 第4章「学習の仕組み」 — Adam・MSE・誤差逆伝播法。NNUE 学習の基礎
- 第4章 §4「誤差逆伝播法」 — nnue-pytorch の内部で実際に動いている仕組み
- 第7章「将棋AIで全部確認」 — NNUE と dlshogi の比較を含む全体像
- 第7章 §2「NNUEの中身」 — このページのダイジェスト版
- 第8章「CEDEC2024で深堀り」 — やねうらお氏・たややん氏の講演で、NNUE エコシステムが語られる
9.2 詳細ページ(このサイト)
- dlshogi 詳細 — NNUE の 対抗馬。深いCNN + MCTS + GPU の別パラダイム
- やねうら王 詳細 — NNUE と組み合わせて動く 探索部のデファクト
9.3 外部リンク
- 那須悠氏 NNUE 原典論文(PDF) — WCSC29 アピール文書
- やねうら王 GitHub — 探索部のリファレンス実装
- nnue-pytorch GitHub — Stockfish向け NNUE 学習フレームワーク
- Chess Programming Wiki: Stockfish NNUE — チェス側の標準解説
まとめ
- NNUE = Efficiently Updatable NN。「差分更新できるように設計された浅いMLP評価関数」
- 2018年に 那須 悠氏が将棋向けに発表、2020年に Stockfishに輸出され世界標準に
- アーキテクチャは HalfKP_256x2-32-32(41,024 → 512 → 32 → 32 → 1)、約2,000万パラメータ
- 入力は HalfKP特徴=「玉の位置 × 他の駒1つ」のペアを全列挙
- 3つの工夫:差分更新・量子化・ClippedReLU。これらで CPU 1コア秒間200万〜500万局面を達成
- 学習は 教師あり(探索評価値 + 勝敗)、Adam + MSE/CE混合損失、GPU で数日
- 水匠・Háo・tanuki- などの 評価関数エコシステムと、やねうら王の 探索部の分業構造を生んだ
- 機械学習で将棋AI を作るハードルを劇的に下げ、ノートPCでプロ棋士超えを可能にした
もっと深く知るには
NNUE の対抗馬である深いCNN型については → dlshogi 詳細、 探索部のことは → やねうら王 詳細 をご覧ください。 将棋AI全体の歴史と進化は → 第8章 CEDEC2024で深堀り で開発者本人の言葉で確認できます。