StockBear — a from-scratch neural chess evaluator

StockBear is a 9,865-parameter feed-forward neural network built to approximate Stockfish position evaluations inside a handmade C++ chess game. It takes 64 integer-coded board squares and a turn indicator, then produces one scalar score. This release preserves the parameters archived in the project's stockbear.h as float32 Safetensors and supplies a small PyTorch inference implementation.

Status: educational, historical checkpoint; playing strength and evaluation accuracy are unmeasured. The surviving training code contains correctness defects described below. This conversion makes the saved network usable in modern tools; it does not establish that the original training succeeded.

Origin and inspiration

StockBear was created for BeanyZoldyck's Fall 2024 final project. The later writeup, Final Project: StockBear / Chess, is dated June 30, 2025; that is the report date, not the project's creation date. It describes building an interactive chess game in C++ with openFrameworks: board rendering, mouse-based selection, piece movement, checks and pins, special moves, promotion, and game-ending conditions. The next step was an opponent for solo play. As the report puts it, “friends were not included in the openframeworks library.”

Using Stockfish directly was outside the project's permitted scope, but learning to imitate its appraisals was the author's chosen approach. The network was implemented with arrays, explicit forward-pass loops, and handwritten gradient updates rather than a machine-learning framework. Conceptually, this is supervised regression on engine evaluations, resembling teacher–student imitation; it is not a fine-tune of Stockfish weights or a reproduction of Stockfish's architecture.

The name is a joke about Stockfish + regression = StockBear: when a stock “regresses,” the market is bearish. Despite the name, the model evaluates chess positions, not financial markets.

The design expands 65 input features to 100 hidden units, compresses them to 32, and produces a linear output. ReLU introduces nonlinearity in both hidden layers. The turn feature was intended to help the network distinguish whose opportunity to move a position represents. Integer board codes were reused from the game for implementation simplicity. In the report's retrospective, the author identifies this representation as a weakness and discusses one-hot encoding as an alternative.

Architecture

65 board/turn features → Linear(65,100) → ReLU
                      → Linear(100,32) → ReLU
                      → Linear(32,1)   → raw score
Layer Weight shape in this release Bias shape Parameters
fc1 100 × 65 100 6,600
fc2 32 × 100 32 3,232
fc3 1 × 32 1 33
Total 9,865

The tensor payload is 39,460 bytes (about 38.5 KiB), excluding file metadata. There is no attention, tokenizer, embedding layer, policy head, or recurrent state. CPU inference is sufficient for a network of this size.

Inputs

Input shape is [65] for one position or [batch, 65] for a batch, in float32. Squares are flattened rank by rank from a8 to h8, then a7 to h7, through h1. Values are passed directly; there is no normalization or one-hot encoding.

Piece Black White
Pawn 9 17
Knight 10 18
Bishop 11 19
Rook 12 20
Queen 13 21
King 14 22

Empty squares are 0. Feature 64 is intended to be 1 for White to move and 0 for Black. The original game's appraiseAI always supplies 0. The supplied encode_fen(..., legacy_game=True) reproduces that behavior; its default uses the actual FEN turn according to the intended representation. Neither mode recreates the undefined/uninitialized inputs in the historical training parser.

Castling rights, en-passant target, halfmove clock, fullmove number, and repetition history are not features. Positions that differ only in these fields are indistinguishable to the network. The FEN helper checks feature syntax, not whether the position is legally reachable.

Outputs and game integration

The output is an unbounded scalar, shape [1] or [batch, 1]. The report intends a Stockfish-like appraisal and illustrates Black selecting the lowest-scoring candidate. Exact label units, sign convention, and any mate-score transformation have not been confirmed, so the loader returns raw scores rather than labeling them centipawns, pawn advantages, or win probabilities.

In the original game, the surrounding chess code generates candidate moves, evaluates their resulting boards, and selects a low-scoring move for Black. The network itself does not generate legal moves or search a game tree. The report describes one-ply selection, not minimax or Monte Carlo tree search.

Usage

Download this model repository and run from its directory:

python3 -m pip install -r requirements.txt
import torch
from stockbear import StockBear, encode_fen

model = StockBear.from_pretrained(".")
fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
x = encode_fen(fen, legacy_game=True)
with torch.inference_mode():
    score = model(x).item()
print("Raw StockBear score:", score)

To obtain the files from the Hub:

from huggingface_hub import snapshot_download

folder = snapshot_download(repo_id="beanyzoldyck/stockbear")
print(folder)  # Run the example from this directory, or copy stockbear.py locally.

from_pretrained here accepts a local directory. This is a plain PyTorch module, not a Transformers AutoModel. Hosting the files on the Hub does not automatically provide a hosted inference endpoint or a chess widget.

Training data and procedure

The report states that the checkpoint was trained on 1,500 boards with Stockfish appraisal targets. The source repository's evals.h contains 15,000 FEN/score pairs. These figures describe different things: the visible training loop consumes the first 1,500 entries in 100 consecutive batches of 15.

Setting Evidence in the surviving source
Implementation Handwritten C++ in model.cpp
Initialization rand() scaled to approximately [-1, 1], including biases
Seed srand(time(0)); exact seed not recorded
Learning rate 0.005
Batch size 15
Updates 100; variable named EPOCHS, but no full-dataset epoch loop
Order Sequential; no visible shuffling
Printed error Mean signed residual, not MAE or MSE
Validation/test split Not documented
Hardware and duration Not documented

The author recalls downloading chess games from Lichess, but is unsure of the exact source. This is a recollection, not verified dataset provenance. The specific download, dataset license, conversion from games to FEN/score pairs, Stockfish version, analysis depth/time, position sampling, label perspective, and preprocessing remain unknown. The author does not remember whether the archived header was produced by the checked-in trainer or a corrected version. Its relationship to a particular training run has not been independently established. Training examples are linked in the source repository rather than bundled with this checkpoint.

Important historical implementation limitations

Inspection of model.cpp reveals problems beyond the representation tradeoff discussed in the report:

  • fenToVec never increments its output index. It repeatedly overwrites element zero and leaves other input elements uninitialized.
  • The output-bias update iterates over 32 hidden units while both output biases and output gradients have length one, causing out-of-bounds accesses.
  • Batch residuals are combined with activations from the final sample rather than accumulating each sample's weight gradients.
  • Hidden-layer-2 weights and biases are not updated; only 65 of 100 first-layer biases are updated. Hidden gradients also use already-updated output weights.

Consequently, the checked-in trainer is not a reliable recipe for reproducing or validating the checkpoint. The report's residual expression is not a complete nonnegative regression loss, and the printed signed error can cancel across samples. This release preserves the header's parameters without retraining.

The surrounding game has additional issues (for example, candidate-board clearing uses tempBoard[i][i] and the minimum-score helper stores its running minimum in an integer). Gameplay therefore cannot be treated as an isolated evaluation of the neural network.

Evaluation and intended use

The report supplies qualitative gameplay screenshots and concludes that the opponent started reasonably but played poorly. It contains no held-out regression metrics, engine match protocol, Elo estimate, or tactical benchmark. No such performance claims are made here.

Useful applications include studying a small dense network, comparing C++ and PyTorch inference, investigating historical ML implementation mistakes, and reproducing the archived evaluator. Numerical conversion verification measures fidelity to the original forward pass; it does not measure chess quality.

Conversion verification compared every parameter against floats dumped by a C++ compiler: all 9,865 matched exactly. On the starting position plus 100 positions sampled throughout evals.h, the maximum absolute difference between batched PyTorch inference and the original C++ evaluator was approximately 0.00024431 raw-score units. Both used the legacy constant-zero turn feature. Single-position inference also passed the comparison (atol=0.002, rtol=1e-5). Reproduce with python3 tools/verify_huggingface.py in the source repository (requires g++).

The integer encoding imposes artificial numerical relationships between piece types and colors. Missing game-state features, limited reported training exposure, unknown data coverage, and the training defects constrain interpretation of scores. This checkpoint is not established as a dependable chess-analysis tool.

Potential future work includes a corrected training implementation, categorical piece planes, full state features, documented teacher labels, game-disjoint train/test splits, and MAE/RMSE plus controlled engine matches. Those changes would produce a new experiment rather than merely repackage these weights.

Files and provenance

  • model.safetensors: six float32 tensors in PyTorch linear-layer layout.
  • stockbear.py: architecture, local checkpoint loader, and FEN feature helper.
  • config.json: architecture, feature conventions, and source-header SHA-256.
  • requirements.txt: Python dependencies.

The original C++ matrices use [input, output]; the converter transposes them to [output, input]. The output vector becomes [1, 32] and its scalar bias becomes [1]. Decimal constants are converted to float32, preserving the saved header precision; precision lost before the header was written cannot be recovered. Different accumulation orders can produce small floating-point output differences.

Source and reproducible export script: BeanyZoldyck/stockbear, tools/export_huggingface.py.

Primary reference: Final Project: StockBear / Chess, June 30, 2025, especially sections 2.3, 2.8, 3, 3.1, and the training-code appendix.

Author and license

Created by BeanyZoldyck (Hugging Face, GitHub).

The model weights, configuration, and model card are licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You may use, share, and adapt them, including commercially, with attribution, a license link, and an indication of changes. See LICENSE. Suggested credit:

StockBear by BeanyZoldyck, https://huggingface.co/beanyzoldyck/stockbear, licensed under CC BY 4.0. [Describe changes, if any.]

The Python inference code is licensed under MIT, which requires preserving the copyright and permission notice; see LICENSE-CODE. These grants cover this model package, not third-party datasets or artwork. The report attributes the game's piece artwork to chess.com; that artwork is not included in this model package.

Downloads last month
-
Safetensors
Model size
9.87k params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support