🔎 詳細

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 は:

第7章 §2「NNUEの中身」第3章「NNの基礎」 を先に読んでいる人は、 このページではさらに 「なぜそれがCPUで動くのか」「歴史的にどう生まれたのか」「学習はどうやるのか」 という詳細を見ていきます。

1

歴史

2018年、那須 悠氏が将棋向けに発表。 Bonanzaメソッドの後継として 「NN化しつつCPU動作」を実現する制約から生まれた。

2

仕組み

HalfKP_256x2-32-32 という浅いMLP。差分更新・量子化・ClippedReLUの3つの工夫で CPU爆速

3

影響

将棋AIの主流を一新し、チェスのStockfishにも輸出されて世界標準に。 日本発の技術が世界を変えた稀有な例。

1. 歴史 — 那須 悠(Yu Nasu)氏の発明

1.1 2018年、ある電竜戦向け論文から

NNUE は 2018年、将棋AI開発者の 那須 悠(Yu Nasu)氏によって発表されました。 正確には「Efficiently Updatable Neural-Network based Evaluation Functions for Computer Shogi」というタイトルの論文が世界コンピュータ将棋選手権の場で配布されたのが最初のお披露目です。

当時、将棋AI界はちょうど 転換点 にありました:

この 「ニューラルネット化したい、でも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の1局面処理フロー:局面 → 特徴ベクトル → 浅いMLP → 評価値 ① 局面(例:中盤の一局面) 先手▲ vs 後手▽ (各駒に「位置・種類・持ち主」がある) 特徴抽出 ② HalfKP特徴ベクトル(約41,024次元) 41,024ビット中、現局面に該当する数十個だけ「1」(残りは全部0) 「玉の位置 × 他の駒の組み合わせ」を全部ビットにする: ▲玉=53 × ▲金=64 → 特徴 #1247 = 1 ▲玉=53 × ▲銀=43 → 特徴 #2891 = 1 ▲玉=53 × ▽歩=24 → 特徴 #15042 = 1 ⋯(玉以外の全駒について同様、約40個) 埋め込み (W₁) ③ 浅いMLP(4層)を通す 入力 41,024 次元 (疎) 512 (256×2) 先手用+後手用 32 32 1 各層の役割: • 41024→512:「この局面の意味」を抽出 • 512→32→32:抽象化して圧縮 • 32→1:最終的に 1つの数字 に集約 活性化関数はすべて ClippedReLU (整数演算で高速化) ④ 評価値(スカラー) +120 「先手やや有利」

ポイント:NNUEは たった4層の浅いMLP。「深いCNN」ではなく 「巨大なスパース入力 + 浅い密接続」がCPU向きの設計です。 続く §3〜§5 で、各ステップの中身を順番にほぐしていきます。

2.1 💭「巨大なスパース入力」とは?

スパース(sparse)」 = 「中身がほとんどゼロ」 という意味の数学・コンピュータ用語。日本語だと 「疎(そ)」 とも訳されます。

NNUEの入力ベクトル(41,024次元)は 巨大だけど、実際に1になっているのは数十個だけ。残りの 40,000個以上は全部0 — これが「スパース」。

スパース(疎) vs 密(dense)の違い 🟦 密ベクトル(dense):全部の値に意味がある 例:画像のピクセル値(全マスに 0〜255 の数字が入る) [0.3, 0.7, 0.5, 0.9, 0.2, 0.8, 0.1, 0.6, 0.4, 0.9, ⋯] ← 全部 0以外の値 ⬜ スパース(疎):ほとんどが0、たまに1がある 例:NNUEのHalfKP特徴(41,024中、現局面に該当する数十個だけ「1」) [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, ⋯, 0, 1, 0, 0] ← ほぼ全部 0

なぜスパースになるのか

41,024個の特徴ビットは「玉の位置 × 他の駒の(位置×種類×持ち主)」の 全ての組み合わせ を用意したもの。 でも実際の1局面で実現するのは 盤上にある駒の数(〜40個) だけ。

スパースだと何が嬉しいのか — 計算の節約

普通に「$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 構造図と名前の対応

HalfKP_256x2-32-32 — 名前と構造の対応 名前「HalfKP_256x2-32-32」の各部分の意味: HalfKP → 入力特徴(玉と他の駒のペア、片側ぶん) 256x2 → 256次元の埋め込みを 2つ(先手玉視点+後手玉視点)= 512次元の中間層1 -32-32 → 以降の中間層が 32次元、32次元 ①入力(HalfKP × 2) 先手玉視点 41,024次元 後手玉視点 41,024次元 スパース2値 W₁ 埋め込み ②中間層1 (=「256x2」) 256 256 連結 →512 ClippedReLU W₂ ③中間層2 (=「-32」) 32 ClippedReLU W₃ ④中間層3 (=「-32」) 32 ClippedReLU W₄ ⑤出力 +120 評価値 📐 各層の数値の詳細 W₁ (埋め込み層): 41,024 × 256 ≈ 1,050万パラメータ(先手用 + 後手用で2倍) W₂ (連結後→32): 512 × 32 = 16,384 パラメータ W₃ (32→32): 32 × 32 = 1,024 パラメータ W₄ (32→1): 32 × 1 = 32 パラメータ 合計:約 2,100万パラメータ(大部分は W₁) 後段の MLP(W₂以降)は驚くほど小さい   ほとんどの「賢さ」は W₁ の埋め込みに集約されている。   これが 「ほぼ線形評価関数」 とも呼ばれる所以。

3.2 別表示の構造図(線形ビュー)

同じアーキテクチャを、入力→出力の 1直線で描き直したのが下の図。差分更新対象(★)が入力層と第1層の間にあることもわかります。

NNUE のアーキテクチャ — HalfKP_256x2-32-32(5層・約2,500万パラメータ) 入力層 HalfKP特徴 41,024 次元 スパース2値 (数十個だけ1) ★差分更新対象 第1層 (Accumulator) 256 × 2 512 次元 先手用 256 + 後手用 256 ClippedReLU 第2層 32 次元 ClippedReLU 第3層 32 次元 ClippedReLU 出力層 評価値 (1次元) 各層間の重み(W)と総パラメータ数: W₁: 41,024 × 256 ≈ 1,050万 (※先手用) + 同じく後手用 → 約 2,100万 W₂: 512 × 32 ≈ 16,384 W₃: 32 × 32 ≈ 1,024 W₄: 32 × 1 = 32 合計 ≒ 2,100万パラメータ(≒ 50MB の .nnue ファイル)

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" の意味:

「256x2」の正体:先手玉視点と後手玉視点の2セット ①「先手玉=58」を中心とした HalfKP(41,024次元) →「先手玉 vs 自分の駒たち、相手の駒たち」の関係を全部エンコード [0,0,1,0,...,1,0,0] (スパース) W₁ 埋め込み 256次元 先手玉視点の埋め込み ②「後手玉=22」を中心とした HalfKP(41,024次元) →「後手玉 vs 自分の駒たち、相手の駒たち」の関係を全部エンコード [0,1,0,0,...,0,0,1] (スパース) W₁' 埋め込み 256次元 後手玉視点の埋め込み ③ 連結 (concat) 512次元(256 + 256) これが中間層1の入力 → 以降のMLPで両玉の関係も学習される

なぜ「自分玉 × 自分玉」じゃダメで「2セット」なのか

将棋の評価は 両側の玉の安全度の差 で決まります。「自分の玉だけ安全」では足りない — 「相手玉も詰みに近いほど自分有利」を測る必要がある。 HalfKP を 2セット用意することで:

つまり 「両玉の関係」は中間層で暗黙的に学習 される設計。直接「玉×玉」というペアの特徴は作らないけれど、両視点を持ち寄ることで実質的に網羅できているわけです。 = 「玉ごとに別個の表現」を学習している。先手と後手で守備の形が違うのは将棋として自然な対称性で、これがアーキテクチャに直接組み込まれているわけです。

📚 補足:派生アーキテクチャ — 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で一度に処理
SSE2128 bit16要素8要素
AVX2256 bit32要素16要素
AVX-512512 bit64要素32要素

つまり 「次元数 = 計算速度の整数倍」 という制約が、まず候補を絞り込む。 「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の設計思想

🧪 制約③:実験で見つけた「最適なバランス」

那須 悠氏の元論文(2018年)と、その後の Stockfish の fishtest(大規模A/Bテスト基盤)で 何百もの組合せ を試した結果:

つまり 「数千回の対戦テストで、勝率が最も高くなる組合せ」として 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つの単語の頭文字を取ったものですが、それぞれが具体的な意味を持っています:

HalfKP の頭文字を1つずつ解剖する Half =半分(片側) 「先手玉視点」 「後手玉視点」 どちらか片方を作る → 2セット作って連結("256×2") King =玉(王) 玉の位置を中心に 他の駒との関係を エンコードする → 玉が「軸」 Piece =駒(玉以外の1枚) 歩・香・桂・銀・金・ 角・飛・成駒の14種 =「他のもう1枚の駒」 → ※「Prince」ではない!

📖 「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」 を別途用意します:

「持ち駒として何枚持っているか」を 枚数ごとに1ビット立てる形で表現されます:

持ち駒は「枚数」ごとに index 化される 例:先手の持ち駒が「歩 3枚、銀 1枚」のとき ▲歩 (max 18枚): 1 2 3 4 5 18 ↑ 「3枚目」のビットが 1 になる(=現在 3枚持っている状態) ▲銀 (max 4枚): 1 2 3 4 ↑ 「1枚目」のビットが 1 先手の持ち駒index総数: 歩(18) + 香(4) + 桂(4) + 銀(4) + 金(4) + 角(2) + 飛(2) = 38 index 後手用も同じ 38 index → 持ち駒だけで合計 76 index

📐 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は以下のような戦略を学べます:

持ち駒も「玉中心の関係性」として符号化されるので、攻めにも守りにも、駒の取り合いにも反応できる評価関数になります。 dlshogi も同様に持ち駒チャンネルを入力に持っています。

💡 1行まとめ: 持ち駒は HalfKP の「他の駒の位置」に 「持ち駒index」として埋め込まれる。 盤上駒と持ち駒が同じ枠組みで扱われ、「玉×手駒のペア」として攻め・守りの両面が表現される。

4.1 数えてみる

HalfKP = Half-King-Piece — 玉の位置 × 他の駒の(位置×種類×持ち主) 41,024次元の正体(先手用Accumulator): 玉の位置 81 (9×9マス) × 他の駒の (位置 × 種類 × 持ち主) 81 × 14 × 2 ≈ 506 (実際は玉自身を除く調整で約506) = 特徴次元 41,024 具体例:先手玉=58、相手の銀=43 という関係を表す1ビットがある → 特徴ベクトルの「玉=58 ×(位置43, 銀, 後手)」というインデックスのビットが 1 → 玉が 58→68 に動くと、このビット群は一斉に別の場所へ切り替わる(差分更新が効く) つまり 「玉を中心にした駒の相対関係」 をすべて符号化している =美濃囲い・矢倉・銀冠など「囲いの形」が、玉周辺のビットパターンとして自然に出てくる

4.1.5 各パラメータを1つずつ図解する

81 × 81 × 14 × 2 ≈ 41,024」 の各数字が何を意味しているか、4つのパラメータすべてを図で解きほぐします。

🔵 パラメータ① 「81」 = 玉が置かれうるマスの数

将棋盤は 9筋 × 9段 = 81マス。玉はそのうちの 1マス に必ずいます(取られたら詰み)。 「玉の位置」は 81通り考えられる、ということ:

玉の位置:9×9 = 81通り 918171615141312111 928272625242322212 938373635343332313 948474645444342414 958575655545352515 968676665646362616 978777675747372717 988878685848382818 998979695949392919 📋 81マスの番号 右上=1一、左下=9九 のように、 「筋(縦の列)」と「段(横の行)」の2桁数字で表現 🔴 例:玉が「58」にいる 5筋8段目 = 左下寄りの位置 → 81通りの中の1つの状態 ⇒ 「玉の位置」というパラメータが   取りうる値 = 81通り

🟢 パラメータ② 「81」 = 他の駒が置かれうるマスの数

これは①と同じ 9×9 = 81マス。「玉以外のもう1個の駒」が、どこにいるかを表します:

玉=58 だけが盤に置かれた状態を想像してください。他の駒(例えば自分の金、相手の銀など)は、残りの80マスのどれかに置かれる可能性がある。 厳密には「玉の位置と被らない」という制約がありますが、HalfKP では シンプルさのために81通り全部を用意し、玉と重なるindexは使われないだけで存在はする設計。

🟡 パラメータ③ 「14」 = 駒の種類

将棋の駒は 全14種類。8種類の通常駒 + 6種類の成駒:

14種類の駒(8つの通常駒 + 6つの成駒) 通常駒(8種) 歩兵 香車 桂馬 銀将 金将 角行 飛車 玉将 成駒(6種)— 敵陣で「成る」と変化した駒 と金(成歩) 成香 杏(成香) 成桂 圭(成桂) 成銀 全(成銀) 竜馬(成角) 龍王(成飛) ⚠️ HalfKPの「14」と実装上の補足: • 「他の駒」のリストとして 14 種類を数える • ただし 玉⑧は「相手の玉」も含めるか除外するかが実装で違う(標準 HalfKP では除外する派もある) • 持ち駒(hand)には成駒は存在しない(成った駒は手駒に戻ると元に戻るルール)

🟣 パラメータ④ 「2」 = 持ち主(先手 / 後手)

将棋には 2人のプレイヤーがいて、それぞれが駒を持っています。同じ「銀」でも、「先手の銀」と「後手の銀」は別物 として扱う必要があります:

駒の「持ち主」 = 2通り ▲先手(こちらの駒) 向きが上(自陣 → 敵陣) ▽後手(相手の駒) 向きが下(自陣 → 敵陣、反対側) ⇒ 「位置」「種類」が同じでも、持ち主が違うと別の特徴ビット として扱う(味方か敵かは決定的に重要だから)

🧮 4つを掛け合わせる — 「組合せの全数え」

ここまでの4パラメータを すべて掛け合わせる と、「玉と他駒のペア」の全パターン数になります:

4パラメータをすべて掛け合わせる ①玉の位置 81 9×9マス × ②他駒の位置 81 9×9マス × ③駒の種類 14 歩〜龍 × ④持ち主 2 先手 / 後手 = 理論上の組合せ数 81 × 81 × 14 × 2 = 183,708 ↓ 玉自身を除く、持ち駒の特殊扱いなどの調整で減る 実用上の HalfKP 次元数:約 41,024

🔍 「約506」と「約41,024」の正体

元の式 「81 × 506 ≈ 41,024」 の「506」は、上の 「② × ③ × ④」 をまとめた数です:

$$\underbrace{81}_{\text{他駒の位置}} \times \underbrace{14}_{\text{駒種}} \times \underbrace{2}_{\text{持ち主}} = 2{,}268$$

…のはずですが、実装では 以下の調整で約 506 に圧縮されます:

細かい引き算と足し算の結果、「玉=固定」での他駒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ビットに対応。組合せ総数は:

$$81 \times (81 \times 12 \times 2) = 81 \times 506 \approx 41{,}024 \text{ 通り}$$

これが「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:持ち主を「外側」に出す(片側ぶんで内側を組み立てる)

$$81 \times \underbrace{(81 \times 14 + 38)}_{\text{盤上+持ち駒、片側ぶん}} \times \underbrace{2}_{\text{持ち主}} = 81 \times 1{,}172 \times 2 = \mathbf{189{,}864}$$

方式B:持ち主を「内側」に含める(両側ぶんで内側を組み立てる)

$$81 \times \underbrace{(81 \times 14 \times 2 + 76)}_{\text{盤上(両側)+持ち駒(両側)}} = 81 \times 2{,}344 = \mathbf{189{,}864}$$

どちらも同じ 189,864。「持ち主は内側か外側、どちらか一回だけ掛ける」が鉄則。

📐 ズレの正体

$196{,}020 - 189{,}864 = 6{,}156 = 81 \times 76$ — これがちょうど 持ち駒の持ち主ぶんの二重カウント

🤔 「189,864 vs 実装値 41,024」のさらなるギャップは?

正しく計算しても 189,864 と実装値 41,024 はまだ 約 4.6倍 違う。これは 実装上の調整 によるもの:

これら全てを反映した 標準HalfKP(Stockfish系)の実装値は約 506 × 81 ≈ 41,024。 つまり 「素直な掛け算 189,864」→「実装で約4.6倍コンパクト化 → 41,024」という流れ。

💡 1行まとめ: 持ち主は 「内側か外側、どちらか一回だけ」 掛ける。 正しく組むと 81 × 2,344 = 189,864通り。さらに実装上の重複削除で 約 41,024 に圧縮される。

🔗 持ち駒のさらに詳しい説明は §4.0.2「持ち駒は HalfKP でどう表現されているか?」 を参照。

🎯 局面 → 特徴ビット への翻訳例

例えば現在の局面が以下のようだったとします:

「玉が 58 にいる」ことを軸に、他の駒1個ずつとペアを作って、対応する特徴ビットを 1 にしていきます:

// HalfKP 特徴の翻訳(先手側、玉が58のとき)
特徴ビット[ 玉=58 × (位置68, 金, 先手) ] = 1   ← 自分の金との関係
特徴ビット[ 玉=58 × (位置43, 銀, 後手) ] = 1   ← 相手の銀との関係(脅威)
特徴ビット[ 玉=58 × (位置24, 歩, 後手) ] = 1   ← 相手の歩との関係
... (盤上の駒全部について同様、30〜40個のビットが1になる)

それ以外の40,000個以上のビットは全部 0
→ 「スパースな2値ベクトル」になる

🔄 玉が動くと、すべての特徴ビットが切り替わる

ここが重要な性質:玉の位置が1マスでも動くと、その玉に関するすべての HalfKP 特徴ビットが切り替わる

これが「玉中心の局面表現」の意味。玉の位置によって、まったく違う特徴になるのが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」のような 「玉以外の駒同士のペア」 は入力ビットに存在しない。

ではどうやって 美濃囲い・矢倉などの「複数駒で構成される囲い」 を認識しているか?答えは 「中間層のニューラルネットが暗黙的に合成して学習している」

  1. 入力時点:「玉=58 × 銀=77」と「玉=58 × 金=68」が 別々のビット として両方1になる
  2. W₁ 埋め込み:それぞれが256次元のベクトルに変換される(ベクトル足し算で「合成」される)
  3. ClippedReLU + W₂, W₃ の非線形変換:「銀@77 と 金@68 が 両方 立っている時の特別な意味(=美濃囲い)」を組み合わせとして学習
  4. 結果:囲いの完成パターンを認識し、そのぶん評価値を上げる

つまり 「玉×単一駒」を片っ端から並べておけば、その組み合わせ(=複数駒の関係)は 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を選んだのか
2025年の最先端:HalfKAv2_hm

実は2025年現在、最強ソフトは HalfKP のさらに改良版 HalfKAv2_hm を使っています:

つまり 「もっと豊かな関係性を直接表現するほうが強い」 という方向に進化しつつあります。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 メソッド時代から続く「人間が考え抜いた特徴設計」の発展形です。

つまり 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上で動く速さ」

つまりNNUEは 「人間が頑張って特徴を設計するかわりに、何百万局面でも秒間で評価できる」 というトレードオフを選んだ設計。dlshogi はその逆で「特徴抽出はGPUに任せて、1局面の深い読みで勝負」を選んだ。

💡 1行まとめ: NNUEのHalfKP特徴は 「人間が設計した将棋AI界の伝統的特徴」。 自動で決まるのは特徴量を組み合わせる重みのほう。
「特徴を自動学習する」深層学習の流儀は dlshogi 側の話で、NNUEはあえてその逆を行くことで CPU高速評価を実現している。

5. 3つの工夫 — なぜCPUで秒間数百万局面が動くか

NNUE が CPU でほぼ古典評価関数並みに高速なのは、3つの工夫の合わせ技です。

5.1 工夫① 差分更新(Efficient Update)

最大の発明がこれ。「1手指したとき、入力層から第1層への計算を、変化分だけにする」

差分更新 — 前局面のAccumulator + 変化ビットの加減算 前の局面(指す前) 玉=58, 金=64, 銀=43, 歩=... (活性ビット: 数十個) 第1層 Accumulator [0.2, -0.5, 1.1, ..., 0.8] 512次元のベクトル(計算済み) ▲5八金 (1手進める) 後の局面(指した後) 玉=58, 金=58, 銀=43, 歩=... (金64 → 金58 だけ変化) 第1層 Accumulator [0.3, -0.4, 1.0, ..., 0.9] 差分だけ更新! 差分更新の計算(疑似コード): new_acc = old_acc new_acc -= W[「玉=58 × 金=64」の特徴インデックス] // 消えるビット new_acc += W[「玉=58 × 金=58」の特徴インデックス] // 新しいビット

効果: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行まとめ: 差分更新=「前局面の計算結果を覚えておいて、変化したぶんだけ加減算する」。 将棋の「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 で十分
Accumulatorint16差分加算の累積誤差を抑える
活性化(ClippedReLU後)uint8 (0〜127)SIMDベクトル演算に最適

効果

5.3 工夫③ ClippedReLU

活性化関数として、普通のReLUではなく ClippedReLU を使います。

ClippedReLU(x) = clamp(x, 0, 127)
              = min(max(x, 0), 127)

つまり「0未満なら0、127超なら127、それ以外はそのまま」。出力が必ず 0〜127 の整数範囲 に収まるので:

ふつうのReLUとClippedReLUの違い(図で比較)

ReLU と ClippedReLU の形の違い 通常のReLU(上限なし) x y y = x(無限に伸びる) ClippedReLU(上限127) x y 127 127 127で頭打ち 127で打ち切ることで、出力が int8 の範囲(0〜127) に収まる → 量子化(int8)と完璧に相性が良い
なぜ「127」?— int8 の都合
📚 補足:通常の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段階で動いています:

将棋AIの指し手選択の流れ 現在の局面 が入力される ① 定跡データベースに この局面はある? (book ファイル) YES 📖 定跡から指し手を選択 → NNUE は使われない 序盤は事前検討済みの定跡で快適 NO(定跡切れ) ② α-β探索 + NNUE評価 数百万局面を読む 最も良い手を選択 中盤〜終盤の主役

仕組み① 定跡データベース(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%
  ...

定跡データベースの生成方法

実戦時の動き:序盤の20〜40手程度は定跡を引いて即指し、定跡が切れたら α-β + NNUE に切り替わる。 つまり 「NNUEは序盤を考えていない」のが現実 — 序盤は完全に定跡任せ。

仕組み② NNUE自体も序盤局面で学習している

ただし、定跡から外れた局面(相手が変な手を指した、定跡データにない局面)では NNUE が考える必要があります。 このために 学習データに序盤局面も大量に含めて、ある程度の序盤理解を獲得しています:

仕組み③ 💡 dlshogi の棋譜を「知識蒸留」で取り込む(2024〜2025の革命)

ここがご質問の 「NNUE系でも dlshogi で機械同士が戦った棋譜を学習データにすればいいのでは?」 に対する答え:

まさにそれが現在行われています! 『将棋AIのゆくえ』で大森氏(氷彗開発者)が語った、2024〜2025年の最大のブレイクスルー: dlshogi の棋譜・推論結果を NNUE の教師データとして使う「知識蒸留」

知識蒸留の3パターン

方式何を教師にするかNNUEが学ぶこと
① 棋譜蒸留 dlshogi 同士の自己対局棋譜 「dlshogi が好む指し手の流れ」
② 推論結果蒸留 dlshogi の評価値・方策分布 「dlshogi の局面評価そのもの」
③ ハイブリッド 従来の自己対局 + dlshogi 推論 両方の良いとこ取り(実用的)

知識蒸留がもたらした効果

『将棋AIのゆくえ』で大森氏が語った具体的な数字:

山岡氏(dlshogi開発者)も「自分のモデルがNNUE系の底上げに使われている」ことに前向き:

山岡「DL系の技術が NNUE系を底上げしてるっていうのは、ネガティブな印象は特になくて(笑)。ブレークスルーが NNUE系でも起こってるっていうのは、技術的な興味というところでは面白いと思いますし」

『将棋AIのゆくえ』松本博文(2026, マイナビ出版)第2章より要約引用

なぜ知識蒸留が効くのか

通常の自己対局で生成される教師データには、α-β探索の限界(読みが浅い、横並びの手を見落とす) が含まれてしまいます。 一方 dlshogi は MCTS + 深いCNN で局面の評価を出すので、α-β とは違う角度から「正解」を提示できる:

これは 第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 教師データの作り方

  1. 初期評価関数(既存の強いNNUE、または古典評価関数)を用意する
  2. それを使って 自己対局(または定跡からのランダム開始局面を多数)を行う
  3. 各局面で 探索評価値(α-β探索で深く読んだ評価値)を計算
  4. その対局の 最終勝敗(先手勝ち=1、後手勝ち=0、引き分け=0.5)も記録
  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手)から学習する状況を想像

なぜ AlphaZero は「勝敗だけ」で済むのか

一見、AlphaZero も同じ問題があるはず。なぜ最終勝敗だけで強くなれるのか?答えは MCTS の訪問回数分布

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(モデル次第で増減)
必要GPURTX 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 に変換して保存します。この変換が一筋縄ではいきません。

これらを怠ると「学習時は強いが、量子化後に大幅に弱くなる」事故が起きます。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個のバイナリファイルです:

ファイルを差し替えれば、エンジン本体(やねうら王)はそのままで評価関数だけ切り替わる。これが NNUE エコシステムの強さです。

7.3 探索部との関係

NNUE評価関数は単体では動きません。α-β 探索部と組み合わせて初めてエンジンになります。

つまり実運用は「やねうら王 + 水匠」「やねうら王 + 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を作るには:

という長い暗黒の調整作業が必要でした。NNUE 以降は:

8.2 ノートPCでもプロ棋士超え

NNUE は CPU 1コアで秒間数百万局面。普通のノートPC(4〜8コアCPU)で:

これは AlphaZero 系(GPU必須・電力消費大)と決定的に違う点で、「AI が日常デバイスに降りてきた」瞬間でした。

8.3 分業エコシステムの成立

NNUE のおかげで、コンピュータ将棋界に 明確な分業構造が生まれました:

「強くするには全部自分で作る」だった時代から、「各人が得意な層を担当して連携する」時代になった。 これは CEDEC2024 講演 でも繰り返し強調されている重要な構造変化です。

8.4 日本発の技術がチェス世界へ

前述のとおり、2020年9月の Stockfish 12 で NNUE が公式採用され、以後チェス界の 世界標準に。 その後、Lichess の評価関数、Leela Chess Zero との対決、Kaggle の Chess Engines コンペなど、あらゆるチェスAI関連の場で NNUE が当たり前に使われています。

💡 歴史的意義: NNUE は 「日本発のAI技術が世界標準になった」稀有な事例。 これは将棋AI開発者コミュニティの数十年間の蓄積、なかでも那須悠氏の発明、Hisayori Noda 氏らの Stockfish 移植努力、そしてオープンソース文化の賜物です。

9.1 サイト内の関連ページ

9.2 詳細ページ(このサイト)

9.3 外部リンク

まとめ

  1. NNUE = Efficiently Updatable NN。「差分更新できるように設計された浅いMLP評価関数」
  2. 2018年に 那須 悠氏が将棋向けに発表、2020年に Stockfishに輸出され世界標準に
  3. アーキテクチャは HalfKP_256x2-32-32(41,024 → 512 → 32 → 32 → 1)、約2,000万パラメータ
  4. 入力は HalfKP特徴=「玉の位置 × 他の駒1つ」のペアを全列挙
  5. 3つの工夫:差分更新・量子化・ClippedReLU。これらで CPU 1コア秒間200万〜500万局面を達成
  6. 学習は 教師あり(探索評価値 + 勝敗)、Adam + MSE/CE混合損失、GPU で数日
  7. 水匠・Háo・tanuki- などの 評価関数エコシステムと、やねうら王の 探索部の分業構造を生んだ
  8. 機械学習で将棋AI を作るハードルを劇的に下げ、ノートPCでプロ棋士超えを可能にした

もっと深く知るには

NNUE の対抗馬である深いCNN型については → dlshogi 詳細、 探索部のことは → やねうら王 詳細 をご覧ください。 将棋AI全体の歴史と進化は → 第8章 CEDEC2024で深堀り で開発者本人の言葉で確認できます。