Deep Learning
Build and train statically typed neural networks with klyn.math.nn. Start with a small sequential model, then explore activations, FP16 precision and CPU/GPU execution before saving your model in the next chapter.
Build, Train and Evaluate
A Sequential<T> composes typed layers in order. This small classification example uses four rows of two features and integer class labels. The class is 1 when the first feature is 1, and 0 otherwise.
import klyn.math
import klyn.math.nn
import klyn.math.nn.layers
features = NDArray<Float>([
[0f, 0f], [0f, 1f], [1f, 0f], [1f, 1f]
])
labels = NDArray<Int>([0, 0, 1, 1])
model = Sequential<Float>([
Input<Float>((2,)),
Dense<Float>(8, seed=17l, activation="relu"),
Dense<Float>(2, seed=23l, activation="softmax")
])
model.compile(optimizer="adam")
model.to(Device.cpu)
history = model.fit(features, labels, epochs=200l, batchSize=4l, verbose=0)
result = model.evaluate(features, labels)
print(f"loss={result.loss:.4f}, accuracy={result.accuracy:.4f}")
predictedClasses = model.predict(features).argMax(1)
print(predictedClasses)
The first tensor dimension is the batch: each feature row is one observation. Input<Float>((2,)) declares the per-observation shape and builds the network immediately. The final two values are class probabilities. compile() defaults to sparse categorical cross-entropy, so labels contain class indices rather than one-hot vectors. argMax(1) selects one class index per row.
For multidimensional arrays and their arithmetic, see Math and Scientific Computing. Use separate training and validation data for a real experiment; fit(..., validationData=(xTest, yTest)) records validation metrics after each epoch.
Load Datasets as NDArrays
Dataset loaders live in klyn.math.datasets and return four independent,
statically typed arrays in the order xTrain, yTrain, xTest, yTest.
Missing data is downloaded once over HTTPS, verified, and cached outside your
project. Downloads display progress on an interactive error console; cached loads
are silent. Downloading uses the curl executable; decompression is performed by Klyn.
Pass download=false to require already available local data.
import klyn.math.datasets
xTrain, yTrain, xTest, yTest = MNIST.load()
# Images: NDArray<Float>, shape [samples, 28, 28], normalized to [0, 1].
# Labels: NDArray<Int>, shape [samples], containing classes 0 through 9.
xTrain, yTrain, xTest, yTest = CaliforniaHousing.load()
# Features: NDArray<Float>, shape [samples, 8].
# Targets: NDArray<Float>, shape [samples], containing continuous values.
CaliforniaHousing contains 20,640 observations. The default seeded shuffle
(seed=42l, testSplit=0.2) produces 16,512 training and
4,128 test observations. CaliforniaHousing.featureNames is an immutable
list identifying the columns: median income, house age, average rooms, average
bedrooms, population, average occupancy, latitude and longitude.
Average counts are computed per household; the target is the median house value
in units of 100,000 US dollars. Features are not automatically standardized:
fit any preprocessing statistics on training data only, then apply them to both splits.
import klyn.io
import klyn.math.datasets
xTrain, yTrain, xTest, yTest = CaliforniaHousing.load<Double>(
folder=Path("datasets/housing"),
testSplit=0.25,
seed=123l
)
print(CaliforniaHousing.featureNames)
print(xTrain.shape, yTrain.shape, xTest.shape, yTest.shape)
All four CaliforniaHousing tensors use the selected precision: Float
by default, or Float16 / Double. FP16 storage is genuinely
16-bit; values overflowing that precision are rejected. shuffle=false
preserves source row order. The local shuffle does not alter application-wide
randomness and is reproducible within Klyn, not an identical permutation to that
of other libraries. Optional trainLimit and testLimit
select prefixes of the established partitions without changing their membership.
The default cache is ~/datasets/mnist for MNIST and
~/datasets/california_housing/cal_housing.data for CaliforniaHousing.
A supplied local CaliforniaHousing subset may contain 2 to 20,640 records in the
original, headerless nine-column format, one record per physical line.
Loading uses bounded buffers, a row permutation, and the four output tensors;
no additional full-sized feature tensor is retained.
MNIST uses NDArray<Int> class indices with sparse categorical
cross-entropy. CaliforniaHousing uses continuous targets in the model's floating
precision and mean squared error. Regression never interprets house values as
class indices or reports a classification accuracy.
Regression with CaliforniaHousing
import klyn.math.datasets
import klyn.math.nn
import klyn.math.nn.layers
import klyn.math.preprocessing
xTrain, yTrain, xTest, yTest = CaliforniaHousing.load()
scaler = StandardScaler<Float>()
scaler.fitTransform(xTrain, inPlace=true)
scaler.transform(xTest, inPlace=true)
model = Sequential<Float>([
Input<Float>((Long(xTrain.shape[1]),)),
Dense<Float>(64, activation="sigmoid"),
Dense<Float>(32, activation="sigmoid"),
Dense<Float>(16, activation="sigmoid"),
Dense<Float>(1)
])
model.compile(optimizer="adam", loss="mean_squared_error")
model.summary()
history = model.fit(xTrain, yTrain, epochs=20,
validationData=(xTest, yTest))
predictions = model.predict(xTest)
print(history.validationLoss[-1])
"mse" and "mean_squared_error" are equivalent.
The typed objective is MeanSquaredError<T> in
klyn.math.nn.losses. It averages squared residuals over every
observation and output coordinate and computes the corresponding mean-reduced
derivative. CPU and GPU paths fuse these operations without temporary residual
and squared-residual tensors. Float16 keeps its 16-bit tensor storage.
Regression targets use the model's precision and match its output shape. A
[samples] vector is also accepted for a single linear output
[samples, 1]. A final sigmoid or Softmax is not added implicitly.
The default metrics list is empty for MSE; asking for
metrics=["accuracy"] raises ValueException.
Classification still defaults to accuracy.
Progress shows loss and, when supplied, val_loss.
History provides cached immutable epochNumbers,
trainingLoss and validationLoss series.
Evaluation.hasAccuracy is false; reading regression accuracy or
accuracy history raises ValueException rather than inventing a metric.
The complete samples/math/nn/CaliforniaDNN.kn example standardizes
both feature splits with training-only statistics, trains for 20 epochs and compares
observed and predicted house values using two
coolwarm scatter charts.
Marker area is population divided by 100. Both charts share fixed color limits
from the observed targets and draw the highest-value housing last. Prediction
outliers saturate the palette without rescaling the observed chart or changing
the values used to calculate loss. A commented checkpoint line saves the model;
retain the preprocessing statistics separately for future inputs.
Neural Network Activations
Sigmoid<T> maps each value into [0, 1], while
Tanh<T> maps into [-1, 1]. Both preserve the batch shape,
support Float16, Float and Double, and run on CPU or a supported NVIDIA GPU.
Their derivatives reuse saved outputs during training, without materializing a Jacobian.
import klyn.math
import klyn.math.nn
import klyn.math.nn.layers
import klyn.math.nn.activations
inputs = NDArray<Float>([[-1f, 0f, 1f]])
probabilities = Sigmoid<Float>().forward(inputs)
centered = Tanh<Float>().forward(inputs)
assert probabilities[0, 1] == 0.5f
assert centered[0, 1] == 0f
model = Sequential<Float>([
Input<Float>((3,)),
Dense<Float>(16, activation="sigmoid"),
Dense<Float>(8, activation="tanh"),
Dense<Float>(2, activation="softmax")
])
model.compile()
These activations can also follow a convolution, or be configured directly with
Conv2D(..., activation="sigmoid") or activation="tanh",
including transposed convolutions. NaN propagates; infinities saturate to the
activation's endpoints. The classification loss still requires a Softmax output,
or linear logits when configured with fromLogits=true.
FP16 Storage and Training
Use Sequential<Float16> with Float16 layers and
MNIST.load<Float16>() to keep images, parameters and activations
in FP16. Matrix products accumulate in FP32 before rounding their output.
Adam keeps separate FP32 master weights and moments so small updates are not lost;
training uses dynamic gradient scaling. These optimizer buffers are additional
memory, not FP16 parameter storage. summary() reports parameter bytes
(weights and biases), excluding optimizer state, activations and object metadata.
FP16 reduces tensor storage, but is not a promise of faster arithmetic on every CPU.
On compatible x86 CPUs, matrix products and Float16 Adam updates use AVX2 and hardware FP16 conversions. Other CPUs retain a portable implementation. Parameters remain stored in two bytes on either path; no full-size FP32 copy of the tensors is needed for matrix multiplication.
klyn samples/math/nn/MnistDNN-FP16.kn
CPU and GPU Execution
Neural-network models default to Device.auto. Use
model.to(Device.cpu) to require CPU execution,
model.to(Device.gpu) to select one GPU, or
model.to(Device.gpu[0]) to select a specific GPU.
Device.list() lists detected devices and
model.device reports the resolved device. NVIDIA execution
on Linux and Windows requires only a compatible GPU and its installed
driver, not CUDA Toolkit. Weights and optimizer state survive device
changes. Unsupported explicit selections raise an exception;
only automatic selection may choose CPU instead. NPU selectors exist,
but currently report that execution support is coming in a future release.
With verbose=1 or verbose=2, fit()
prints the training device before the first epoch. Training metrics
aggregate predictions made while parameters change between batches;
validation and evaluation use the final, fixed parameters with dropout
disabled. Their accuracy can therefore exceed the training accuracy,
especially during the first epochs. Compare the two dataset splits
with evaluate() when you need metrics for the same model state.
Complete Samples
The MNIST examples demonstrate larger dense and convolutional networks. Training may download the dataset on its first invocation; choose a workload and device that fit your machine.
klyn samples/math/nn/MnistCNN.kn
The dense versions are samples/math/nn/MnistDNN.kn and samples/math/nn/MnistDNN-FP16.kn. Find these files alongside the convolutional example.
Next Step
Continue with Model Checkpoints.