Training a Linear Assisted Player by Imitation¶

Author: Rob Hendriks
Last verified: 17 September 2026

This tutorial shows how to bootstrap the trainable linear (Lin) assisted players from a known-correct classical strategy.

Rather than train the complete game end-to-end, we first train the four learned components separately: Alice's measurement and combine layers, and Bob's measurement and combine layers. We then insert those weights into the full models and verify the result in a tournament.

The important representation rule is simple:

the game uses bits, while the trainable model uses logits.

A bit 0 or 1 is represented inside the model by the sign of a logit. In this tutorial we use hard logits -10 and +10. The gameplay adapters form the boundary between these two representations:

game bits → gameplay adapter → model logits → gameplay adapter → game bits

Inside the model — including between learned layers and the shared-resource (SR) layer — values remain logits.

Imports¶

We use the current linear assisted-player stack, the canonical dataset utilities, and the gameplay adapters used during tournament evaluation.

In [4]:
import numpy as np
import tensorflow as tf

from Q_Sea_Battle.game_layout import GameLayout
from Q_Sea_Battle.game_env import GameEnv
from Q_Sea_Battle.tournament import Tournament

from Q_Sea_Battle.lin_trainable_assisted_model_a import LinTrainableAssistedModelA
from Q_Sea_Battle.lin_trainable_assisted_model_b import LinTrainableAssistedModelB
from Q_Sea_Battle.trainable_assisted_players import TrainableAssistedPlayers
from Q_Sea_Battle.gameplay_adapters import GameplayModelAAdapter, GameplayModelBAdapter, hard_logit

from Q_Sea_Battle.lin_dataset_generation_utilities import generate_lin_dataset
from Q_Sea_Battle.lin_dataset_conversion_utilities import (
    convert_layer_measure_a,
    convert_layer_measure_b,
    convert_layer_combine_a,
    convert_layer_combine_b,
)

print("TensorFlow:", tf.__version__)
TensorFlow: 2.21.0

1. Choose a Small Test Problem¶

We use a 4×4 field and one communication bit. This is small enough to train quickly, while still exercising the complete linear assisted protocol.

p_rule=1 gives the ideal shared-resource correlation. With zero channel noise, a correctly trained and assembled model should therefore reach a tournament score very close to 1.0.

The measurement layers learn relatively simple local rules. The combine layers must learn parity-like functions and usually need substantially more training.

In [5]:
FIELD_SIZE = 4
COMMS_SIZE = 1

# shared resource (SR) correlation parameter used by your task
P_RULE = 1.0
BETA = 10.0  # magnitude used for hard logits throughout this tutorial

# Dataset / training sizes
DATASET_SIZE = 50_000
BATCH_SIZE = 256
EPOCHS_MEAS = 4 # we can use smaller number of epochs for measurement training, since it is an easier task
EPOCHS_COMB = 100  # parity-like combine layers usually need more training

# DIAL/DRU training settings
SR_MODE_BOOTSTRAP_EVAL = "stochastic"
SR_MODE_DIAL_TRAIN = "replay"
SR_MODE_DIAL_EVAL = "stochastic"

SEED = 123
tf.random.set_seed(SEED)
np.random.seed(SEED)

# Folders
data_dir = Path("notebooks/data")
models_dir = Path("notebooks/models")
data_dir.mkdir(parents=True, exist_ok=True)
models_dir.mkdir(parents=True, exist_ok=True)

n2 = FIELD_SIZE * FIELD_SIZE
print("n2:", n2, "m:", COMMS_SIZE)
n2: 16 m: 1

2. Generate the Classical Imitation Data¶

The canonical dataset is generated in ordinary bits. Before a tensor is presented to a trainable layer, however, we convert it to the representation used by the model.

For a hard-logit magnitude β = 10:

  • bit 0 becomes logit -10
  • bit 1 becomes logit +10

This distinction matters. A raw bit value 0 is not a valid logit representation of logical zero: when logits are interpreted by sign, both 0 and 1 would lie on the non-negative side of the decision boundary.

We therefore keep bits only in the canonical dataset and convert every model-facing input and imitation target to hard logits.

In [6]:
# Build layout for data generation (enemy_probability/channel_noise not used by these generators)
layout = GameLayout(field_size=FIELD_SIZE, comms_size=COMMS_SIZE)

# --- Generate canonical linear dataset in bits ---
ds_lin = generate_lin_dataset(n2=n2, m=COMMS_SIZE, num_games=DATASET_SIZE, seed=SEED, validate=True)

# --- Convert to layer-specific training views in hard logits ---
layer_data_meas_a = convert_layer_measure_a(ds_lin, rep_x="hard_logit", rep_y="hard_logit", beta=BETA)
layer_data_comb_a = convert_layer_combine_a(ds_lin, rep_outcome="hard_logit", rep_target="hard_logit", beta=BETA)
layer_data_meas_b = convert_layer_measure_b(ds_lin, rep_x="hard_logit", rep_y="hard_logit", beta=BETA)
layer_data_comb_b = convert_layer_combine_b(
    ds_lin,
    rep_outcome_b="hard_logit",
    rep_comm_in="hard_logit",
    rep_shoot="hard_logit",
    beta=BETA,
)

print("Datasets ready (bits canonical, logits training views).")
Datasets ready (bits canonical, logits training views).
In [7]:
def logits_bce_from_logit_targets(y_true_logits, y_pred_logits):
    """BCE with logits where targets are semantic logits and labels are derived by sign."""
    y_true_logits = tf.cast(y_true_logits, tf.float32)
    y_pred_logits = tf.cast(y_pred_logits, tf.float32)
    labels = tf.cast(y_true_logits >= 0.0, tf.float32)
    per_elem = tf.nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=y_pred_logits)
    return tf.reduce_mean(per_elem)


def sign_accuracy_from_logit_targets(y_true_logits, y_pred_logits):
    """Accuracy computed by sign agreement between target logits and predicted logits."""
    y_true_logits = tf.cast(y_true_logits, tf.float32)
    y_pred_logits = tf.cast(y_pred_logits, tf.float32)
    target_bits = tf.cast(y_true_logits >= 0.0, tf.float32)
    pred_bits = tf.cast(y_pred_logits >= 0.0, tf.float32)
    return tf.reduce_mean(tf.cast(tf.equal(target_bits, pred_bits), tf.float32))


def train_single_input_layer(layer, x_train, y_train, *, epochs: int, batch_size: int, learning_rate: float = 1e-3, verbose: int = 1):
    """Train a single-input Keras layer inside a tiny functional model."""
    input_tensor = tf.keras.Input(shape=x_train.shape[1:], dtype=tf.float32)
    output_tensor = layer(input_tensor)
    model = tf.keras.Model(inputs=input_tensor, outputs=output_tensor)
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=logits_bce_from_logit_targets,
        metrics=[sign_accuracy_from_logit_targets],
    )
    model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size, verbose=verbose)
    return model


def train_two_input_layer(layer, x_left, x_right, y_train, *, epochs: int, batch_size: int, learning_rate: float = 1e-3, verbose: int = 1):
    """Train a two-input Keras layer inside a tiny functional model."""
    left_tensor = tf.keras.Input(shape=x_left.shape[1:], dtype=tf.float32)
    right_tensor = tf.keras.Input(shape=x_right.shape[1:], dtype=tf.float32)
    output_tensor = layer(left_tensor, right_tensor)
    model = tf.keras.Model(inputs=[left_tensor, right_tensor], outputs=output_tensor)
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=logits_bce_from_logit_targets,
        metrics=[sign_accuracy_from_logit_targets],
    )
    model.fit([x_left, x_right], y_train, epochs=epochs, batch_size=batch_size, verbose=verbose)
    return model

3. Train the Four Learned Layers¶

We train the layers separately so that each part of the protocol can be checked independently:

  1. LinMeasurementLayerA
  2. LinCombineLayerA
  3. LinMeasurementLayerB
  4. LinCombineLayerB

The SR layer itself is not learned here; it implements the assisted correlation rule.

The training targets remain logits. Binary cross-entropy still needs 0/1 labels, so the loss converts the sign of each target logit to a label internally. Accuracy is also measured by sign agreement.

This keeps the representation contract intact: the network sees logits, while the loss only translates their sign at the final mathematical boundary.

In [8]:
# --- Train layers ---
from Q_Sea_Battle.lin_measurement_layer_a import LinMeasurementLayerA
from Q_Sea_Battle.lin_measurement_layer_b import LinMeasurementLayerB
from Q_Sea_Battle.lin_combine_layer_a import LinCombineLayerA
from Q_Sea_Battle.lin_combine_layer_b import LinCombineLayerB

# --- Build layers ---
n2 = FIELD_SIZE * FIELD_SIZE
model_a = LinTrainableAssistedModelA(
    field_size=FIELD_SIZE,
    comms_size=COMMS_SIZE,
    sr_mode=SR_MODE_BOOTSTRAP_EVAL,   # evaluation mode
    seed=SEED,
    p_rule=P_RULE,
)
meas_layer_a = model_a.measure_layer
comb_layer_a = model_a.combine_layer

model_b = LinTrainableAssistedModelB(
    field_size=FIELD_SIZE,
    comms_size=COMMS_SIZE,
    sr_mode=SR_MODE_BOOTSTRAP_EVAL,   # evaluation mode
    seed=SEED,
    p_rule=P_RULE,
)
meas_layer_b = model_b.measure_layer
comb_layer_b = model_b.combine_layer

x_meas_a, y_meas_a = layer_data_meas_a[0]
x_comb_a, y_comb_a = layer_data_comb_a[0]
x_meas_b, y_meas_b = layer_data_meas_b[0]
(x_comb_b_left, x_comb_b_right), y_comb_b = layer_data_comb_b[0]

_ = train_single_input_layer(meas_layer_a, x_meas_a, y_meas_a, epochs=EPOCHS_MEAS, batch_size=BATCH_SIZE)
_ = train_single_input_layer(comb_layer_a, x_comb_a, y_comb_a, epochs=EPOCHS_COMB, batch_size=BATCH_SIZE)

_ = train_single_input_layer(meas_layer_b, x_meas_b, y_meas_b, epochs=EPOCHS_MEAS, batch_size=BATCH_SIZE)
_ = train_two_input_layer(comb_layer_b, x_comb_b_left, x_comb_b_right, y_comb_b, epochs=EPOCHS_COMB, batch_size=BATCH_SIZE)

print("Standalone layers trained.")
WARNING:tensorflow:From c:\Users\nly99857\OneDrive - Philips\SW Projects\QSeaBattle\env_QSeaBattle\Lib\site-packages\keras\src\backend\tensorflow\core.py:233: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:TensorFlow GPU support is not available on native Windows for TensorFlow >= 2.11. Even if CUDA/cuDNN are installed, GPU will not be used. Please use WSL2 or the TensorFlow-DirectML plugin.
Epoch 1/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 2s 3ms/step - loss: 0.4460 - sign_accuracy_from_logit_targets: 0.8556
Epoch 2/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0280 - sign_accuracy_from_logit_targets: 0.9956
Epoch 3/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0105 - sign_accuracy_from_logit_targets: 0.9994
Epoch 4/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0055 - sign_accuracy_from_logit_targets: 0.9998
Epoch 1/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 1.0599 - sign_accuracy_from_logit_targets: 0.4993
Epoch 2/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.7414 - sign_accuracy_from_logit_targets: 0.5045
Epoch 3/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.7101 - sign_accuracy_from_logit_targets: 0.5337
Epoch 4/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.6692 - sign_accuracy_from_logit_targets: 0.5897
Epoch 5/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.6276 - sign_accuracy_from_logit_targets: 0.6495
Epoch 6/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.5922 - sign_accuracy_from_logit_targets: 0.6926
Epoch 7/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.5592 - sign_accuracy_from_logit_targets: 0.7269
Epoch 8/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.5279 - sign_accuracy_from_logit_targets: 0.7576
Epoch 9/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.4920 - sign_accuracy_from_logit_targets: 0.7841
Epoch 10/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.4622 - sign_accuracy_from_logit_targets: 0.8032
Epoch 11/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.4353 - sign_accuracy_from_logit_targets: 0.8193
Epoch 12/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.4056 - sign_accuracy_from_logit_targets: 0.8365
Epoch 13/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.3751 - sign_accuracy_from_logit_targets: 0.8523
Epoch 14/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.3502 - sign_accuracy_from_logit_targets: 0.8648
Epoch 15/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.3173 - sign_accuracy_from_logit_targets: 0.8842
Epoch 16/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.2830 - sign_accuracy_from_logit_targets: 0.9015
Epoch 17/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.2542 - sign_accuracy_from_logit_targets: 0.9163
Epoch 18/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.2269 - sign_accuracy_from_logit_targets: 0.9285
Epoch 19/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.2003 - sign_accuracy_from_logit_targets: 0.9411
Epoch 20/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1790 - sign_accuracy_from_logit_targets: 0.9507
Epoch 21/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1601 - sign_accuracy_from_logit_targets: 0.9580
Epoch 22/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1435 - sign_accuracy_from_logit_targets: 0.9641
Epoch 23/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1295 - sign_accuracy_from_logit_targets: 0.9689
Epoch 24/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1182 - sign_accuracy_from_logit_targets: 0.9724
Epoch 25/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1082 - sign_accuracy_from_logit_targets: 0.9758
Epoch 26/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0985 - sign_accuracy_from_logit_targets: 0.9790
Epoch 27/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0881 - sign_accuracy_from_logit_targets: 0.9822
Epoch 28/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0794 - sign_accuracy_from_logit_targets: 0.9845
Epoch 29/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0726 - sign_accuracy_from_logit_targets: 0.9859
Epoch 30/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0666 - sign_accuracy_from_logit_targets: 0.9869
Epoch 31/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0614 - sign_accuracy_from_logit_targets: 0.9883
Epoch 32/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0572 - sign_accuracy_from_logit_targets: 0.9892
Epoch 33/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0538 - sign_accuracy_from_logit_targets: 0.9898
Epoch 34/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0508 - sign_accuracy_from_logit_targets: 0.9905
Epoch 35/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0479 - sign_accuracy_from_logit_targets: 0.9911
Epoch 36/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0450 - sign_accuracy_from_logit_targets: 0.9918
Epoch 37/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0425 - sign_accuracy_from_logit_targets: 0.9922
Epoch 38/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0404 - sign_accuracy_from_logit_targets: 0.9926
Epoch 39/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0385 - sign_accuracy_from_logit_targets: 0.9932
Epoch 40/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0368 - sign_accuracy_from_logit_targets: 0.9934
Epoch 41/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0354 - sign_accuracy_from_logit_targets: 0.9935
Epoch 42/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0341 - sign_accuracy_from_logit_targets: 0.9935
Epoch 43/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0329 - sign_accuracy_from_logit_targets: 0.9938
Epoch 44/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 2s 8ms/step - loss: 0.0318 - sign_accuracy_from_logit_targets: 0.9939
Epoch 45/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0308 - sign_accuracy_from_logit_targets: 0.9940
Epoch 46/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0299 - sign_accuracy_from_logit_targets: 0.9942
Epoch 47/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0290 - sign_accuracy_from_logit_targets: 0.9944
Epoch 48/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0281 - sign_accuracy_from_logit_targets: 0.9944
Epoch 49/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0272 - sign_accuracy_from_logit_targets: 0.9946
Epoch 50/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0263 - sign_accuracy_from_logit_targets: 0.9947
Epoch 51/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0256 - sign_accuracy_from_logit_targets: 0.9950
Epoch 52/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0249 - sign_accuracy_from_logit_targets: 0.9951
Epoch 53/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0243 - sign_accuracy_from_logit_targets: 0.9952
Epoch 54/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0238 - sign_accuracy_from_logit_targets: 0.9952
Epoch 55/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0233 - sign_accuracy_from_logit_targets: 0.9953
Epoch 56/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0227 - sign_accuracy_from_logit_targets: 0.9955
Epoch 57/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0222 - sign_accuracy_from_logit_targets: 0.9956
Epoch 58/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0218 - sign_accuracy_from_logit_targets: 0.9957
Epoch 59/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0213 - sign_accuracy_from_logit_targets: 0.9957
Epoch 60/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0209 - sign_accuracy_from_logit_targets: 0.9957
Epoch 61/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0205 - sign_accuracy_from_logit_targets: 0.9957
Epoch 62/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0202 - sign_accuracy_from_logit_targets: 0.9957
Epoch 63/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0198 - sign_accuracy_from_logit_targets: 0.9958
Epoch 64/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0195 - sign_accuracy_from_logit_targets: 0.9960
Epoch 65/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0192 - sign_accuracy_from_logit_targets: 0.9960
Epoch 66/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0189 - sign_accuracy_from_logit_targets: 0.9960
Epoch 67/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0186 - sign_accuracy_from_logit_targets: 0.9961
Epoch 68/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0183 - sign_accuracy_from_logit_targets: 0.9961
Epoch 69/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0180 - sign_accuracy_from_logit_targets: 0.9961
Epoch 70/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0177 - sign_accuracy_from_logit_targets: 0.9961
Epoch 71/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0175 - sign_accuracy_from_logit_targets: 0.9962
Epoch 72/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0172 - sign_accuracy_from_logit_targets: 0.9962
Epoch 73/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0170 - sign_accuracy_from_logit_targets: 0.9963
Epoch 74/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0168 - sign_accuracy_from_logit_targets: 0.9963
Epoch 75/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0165 - sign_accuracy_from_logit_targets: 0.9964
Epoch 76/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0163 - sign_accuracy_from_logit_targets: 0.9964
Epoch 77/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0161 - sign_accuracy_from_logit_targets: 0.9964
Epoch 78/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0159 - sign_accuracy_from_logit_targets: 0.9964
Epoch 79/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0158 - sign_accuracy_from_logit_targets: 0.9964
Epoch 80/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0156 - sign_accuracy_from_logit_targets: 0.9964
Epoch 81/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0154 - sign_accuracy_from_logit_targets: 0.9965
Epoch 82/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0152 - sign_accuracy_from_logit_targets: 0.9965
Epoch 83/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0150 - sign_accuracy_from_logit_targets: 0.9966
Epoch 84/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0148 - sign_accuracy_from_logit_targets: 0.9966
Epoch 85/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0144 - sign_accuracy_from_logit_targets: 0.9967
Epoch 86/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0140 - sign_accuracy_from_logit_targets: 0.9968
Epoch 87/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0137 - sign_accuracy_from_logit_targets: 0.9970
Epoch 88/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0134 - sign_accuracy_from_logit_targets: 0.9970
Epoch 89/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0132 - sign_accuracy_from_logit_targets: 0.9971
Epoch 90/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0130 - sign_accuracy_from_logit_targets: 0.9971
Epoch 91/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0128 - sign_accuracy_from_logit_targets: 0.9972
Epoch 92/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0127 - sign_accuracy_from_logit_targets: 0.9972
Epoch 93/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0125 - sign_accuracy_from_logit_targets: 0.9972
Epoch 94/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0124 - sign_accuracy_from_logit_targets: 0.9972
Epoch 95/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0123 - sign_accuracy_from_logit_targets: 0.9972
Epoch 96/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0122 - sign_accuracy_from_logit_targets: 0.9973
Epoch 97/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0120 - sign_accuracy_from_logit_targets: 0.9973
Epoch 98/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0119 - sign_accuracy_from_logit_targets: 0.9973
Epoch 99/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0118 - sign_accuracy_from_logit_targets: 0.9974
Epoch 100/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0117 - sign_accuracy_from_logit_targets: 0.9974
Epoch 1/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.2549 - sign_accuracy_from_logit_targets: 0.9278
Epoch 2/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0114 - sign_accuracy_from_logit_targets: 1.0000
Epoch 3/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0042 - sign_accuracy_from_logit_targets: 1.0000
Epoch 4/4
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0022 - sign_accuracy_from_logit_targets: 1.0000
Epoch 1/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 3.1315 - sign_accuracy_from_logit_targets: 0.5027
Epoch 2/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 1.2644 - sign_accuracy_from_logit_targets: 0.5120
Epoch 3/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.9416 - sign_accuracy_from_logit_targets: 0.5358
Epoch 4/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.7768 - sign_accuracy_from_logit_targets: 0.5733
Epoch 5/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.6951 - sign_accuracy_from_logit_targets: 0.6054
Epoch 6/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.6534 - sign_accuracy_from_logit_targets: 0.6281
Epoch 7/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.6245 - sign_accuracy_from_logit_targets: 0.6515
Epoch 8/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.6018 - sign_accuracy_from_logit_targets: 0.6719
Epoch 9/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.5720 - sign_accuracy_from_logit_targets: 0.6969
Epoch 10/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.5412 - sign_accuracy_from_logit_targets: 0.7230
Epoch 11/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.5034 - sign_accuracy_from_logit_targets: 0.7524
Epoch 12/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.4724 - sign_accuracy_from_logit_targets: 0.7741
Epoch 13/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.4391 - sign_accuracy_from_logit_targets: 0.7950
Epoch 14/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.3947 - sign_accuracy_from_logit_targets: 0.8218
Epoch 15/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.3379 - sign_accuracy_from_logit_targets: 0.8525
Epoch 16/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.2804 - sign_accuracy_from_logit_targets: 0.8836
Epoch 17/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.2320 - sign_accuracy_from_logit_targets: 0.9068
Epoch 18/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1910 - sign_accuracy_from_logit_targets: 0.9256
Epoch 19/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.1599 - sign_accuracy_from_logit_targets: 0.9389
Epoch 20/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.1329 - sign_accuracy_from_logit_targets: 0.9505
Epoch 21/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.1114 - sign_accuracy_from_logit_targets: 0.9591
Epoch 22/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0960 - sign_accuracy_from_logit_targets: 0.9650
Epoch 23/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0825 - sign_accuracy_from_logit_targets: 0.9707
Epoch 24/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0715 - sign_accuracy_from_logit_targets: 0.9746
Epoch 25/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0624 - sign_accuracy_from_logit_targets: 0.9781
Epoch 26/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0552 - sign_accuracy_from_logit_targets: 0.9813
Epoch 27/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0490 - sign_accuracy_from_logit_targets: 0.9837
Epoch 28/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0436 - sign_accuracy_from_logit_targets: 0.9857
Epoch 29/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0392 - sign_accuracy_from_logit_targets: 0.9872
Epoch 30/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0360 - sign_accuracy_from_logit_targets: 0.9881
Epoch 31/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0333 - sign_accuracy_from_logit_targets: 0.9889
Epoch 32/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0310 - sign_accuracy_from_logit_targets: 0.9896
Epoch 33/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0288 - sign_accuracy_from_logit_targets: 0.9903
Epoch 34/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0271 - sign_accuracy_from_logit_targets: 0.9910
Epoch 35/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0251 - sign_accuracy_from_logit_targets: 0.9918
Epoch 36/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0235 - sign_accuracy_from_logit_targets: 0.9920
Epoch 37/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0221 - sign_accuracy_from_logit_targets: 0.9924
Epoch 38/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0212 - sign_accuracy_from_logit_targets: 0.9927
Epoch 39/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0204 - sign_accuracy_from_logit_targets: 0.9930
Epoch 40/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0199 - sign_accuracy_from_logit_targets: 0.9929
Epoch 41/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0194 - sign_accuracy_from_logit_targets: 0.9932
Epoch 42/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0191 - sign_accuracy_from_logit_targets: 0.9932
Epoch 43/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0190 - sign_accuracy_from_logit_targets: 0.9932
Epoch 44/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0186 - sign_accuracy_from_logit_targets: 0.9933
Epoch 45/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0183 - sign_accuracy_from_logit_targets: 0.9936
Epoch 46/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0183 - sign_accuracy_from_logit_targets: 0.9938
Epoch 47/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0181 - sign_accuracy_from_logit_targets: 0.9935
Epoch 48/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0185 - sign_accuracy_from_logit_targets: 0.9934
Epoch 49/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0184 - sign_accuracy_from_logit_targets: 0.9935
Epoch 50/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0185 - sign_accuracy_from_logit_targets: 0.9934
Epoch 51/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0187 - sign_accuracy_from_logit_targets: 0.9934
Epoch 52/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0188 - sign_accuracy_from_logit_targets: 0.9933
Epoch 53/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0179 - sign_accuracy_from_logit_targets: 0.9937
Epoch 54/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0169 - sign_accuracy_from_logit_targets: 0.9939
Epoch 55/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0175 - sign_accuracy_from_logit_targets: 0.9935
Epoch 56/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0169 - sign_accuracy_from_logit_targets: 0.9940
Epoch 57/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0154 - sign_accuracy_from_logit_targets: 0.9941
Epoch 58/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0159 - sign_accuracy_from_logit_targets: 0.9943
Epoch 59/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0154 - sign_accuracy_from_logit_targets: 0.9943
Epoch 60/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0173 - sign_accuracy_from_logit_targets: 0.9936
Epoch 61/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0154 - sign_accuracy_from_logit_targets: 0.9943
Epoch 62/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0148 - sign_accuracy_from_logit_targets: 0.9949
Epoch 63/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0149 - sign_accuracy_from_logit_targets: 0.9945
Epoch 64/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0152 - sign_accuracy_from_logit_targets: 0.9943
Epoch 65/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0150 - sign_accuracy_from_logit_targets: 0.9945
Epoch 66/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0144 - sign_accuracy_from_logit_targets: 0.9945
Epoch 67/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0135 - sign_accuracy_from_logit_targets: 0.9950
Epoch 68/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0117 - sign_accuracy_from_logit_targets: 0.9958
Epoch 69/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.0135 - sign_accuracy_from_logit_targets: 0.9953
Epoch 70/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0132 - sign_accuracy_from_logit_targets: 0.9952
Epoch 71/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0118 - sign_accuracy_from_logit_targets: 0.9961
Epoch 72/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0129 - sign_accuracy_from_logit_targets: 0.9953
Epoch 73/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0162 - sign_accuracy_from_logit_targets: 0.9940
Epoch 74/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0110 - sign_accuracy_from_logit_targets: 0.9960
Epoch 75/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0109 - sign_accuracy_from_logit_targets: 0.9962
Epoch 76/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0128 - sign_accuracy_from_logit_targets: 0.9954
Epoch 77/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0110 - sign_accuracy_from_logit_targets: 0.9962
Epoch 78/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0103 - sign_accuracy_from_logit_targets: 0.9963
Epoch 79/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0103 - sign_accuracy_from_logit_targets: 0.9963
Epoch 80/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0105 - sign_accuracy_from_logit_targets: 0.9963
Epoch 81/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0117 - sign_accuracy_from_logit_targets: 0.9958
Epoch 82/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0131 - sign_accuracy_from_logit_targets: 0.9953
Epoch 83/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0091 - sign_accuracy_from_logit_targets: 0.9969
Epoch 84/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0115 - sign_accuracy_from_logit_targets: 0.9956
Epoch 85/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0131 - sign_accuracy_from_logit_targets: 0.9952
Epoch 86/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0091 - sign_accuracy_from_logit_targets: 0.9971
Epoch 87/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0094 - sign_accuracy_from_logit_targets: 0.9967
Epoch 88/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0099 - sign_accuracy_from_logit_targets: 0.9962
Epoch 89/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0088 - sign_accuracy_from_logit_targets: 0.9969
Epoch 90/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0089 - sign_accuracy_from_logit_targets: 0.9969
Epoch 91/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0154 - sign_accuracy_from_logit_targets: 0.9941
Epoch 92/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0092 - sign_accuracy_from_logit_targets: 0.9969
Epoch 93/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0098 - sign_accuracy_from_logit_targets: 0.9967
Epoch 94/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0099 - sign_accuracy_from_logit_targets: 0.9963
Epoch 95/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0069 - sign_accuracy_from_logit_targets: 0.9977
Epoch 96/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0085 - sign_accuracy_from_logit_targets: 0.9973
Epoch 97/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0127 - sign_accuracy_from_logit_targets: 0.9953
Epoch 98/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0115 - sign_accuracy_from_logit_targets: 0.9958
Epoch 99/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0063 - sign_accuracy_from_logit_targets: 0.9979
Epoch 100/100
196/196 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 0.0077 - sign_accuracy_from_logit_targets: 0.9971
Standalone layers trained.

4. Assemble the Full Models¶

The four layers above were trained in isolation. We now create fresh evaluation models and copy each learned weight set into the corresponding layer.

This step is deliberately explicit: it verifies that the standalone layers and the complete models use the same interfaces and shapes.

The internal model continues to operate entirely on logits. The SR mechanism sits inside that logit-valued computation; we do not convert intermediate values back to bits.

In [9]:
# --- Install into fresh internal models for evaluation ---
# We explicitly copy each trained learned layer into the matching internal-model slot.

# Build source wrappers once so trained layers are materialized.
_ = model_a(tf.zeros((1, n2), tf.float32))
_dummy_gun = tf.zeros((1, n2), tf.float32)
_dummy_comm = tf.zeros((1, COMMS_SIZE), tf.float32)
_dummy_prev_meas_list = [tf.zeros((1, n2), tf.float32)]
_dummy_prev_out_list = [tf.zeros((1, n2), tf.float32)]
_ = model_b([_dummy_gun, _dummy_comm, _dummy_prev_meas_list, _dummy_prev_out_list])

# Create fresh internal wrappers for evaluation contract.
eval_model_a = LinTrainableAssistedModelA(
    field_size=FIELD_SIZE,
    comms_size=COMMS_SIZE,
    sr_mode=SR_MODE_BOOTSTRAP_EVAL,
    seed=SEED,
    p_rule=P_RULE,
)
eval_model_b = LinTrainableAssistedModelB(
    field_size=FIELD_SIZE,
    comms_size=COMMS_SIZE,
    sr_mode=SR_MODE_BOOTSTRAP_EVAL,
    seed=SEED,
    p_rule=P_RULE,
)

# Build destination wrappers before assigning layer weights.
_ = eval_model_a(tf.zeros((1, n2), tf.float32))
_ = eval_model_b([_dummy_gun, _dummy_comm, _dummy_prev_meas_list, _dummy_prev_out_list])

# Explicit weight transfer: trained layer -> matching internal-model layer.
eval_model_a.measure_layer.set_weights(meas_layer_a.get_weights())
eval_model_a.combine_layer.set_weights(comb_layer_a.get_weights())
eval_model_b.measure_layer.set_weights(meas_layer_b.get_weights())
eval_model_b.combine_layer.set_weights(comb_layer_b.get_weights())

print("Transferred trained layer weights into eval internal models.")
print("A: meas -> eval_model_a.measure_layer, comb -> eval_model_a.combine_layer")
print("B: meas -> eval_model_b.measure_layer, comb -> eval_model_b.combine_layer")
Transferred trained layer weights into eval internal models.
A: meas -> eval_model_a.measure_layer, comb -> eval_model_a.combine_layer
B: meas -> eval_model_b.measure_layer, comb -> eval_model_b.combine_layer

5. Check the Internal Representation¶

Before running a tournament, it is useful to inspect one example end-to-end.

The learned layers are free to produce logits of different magnitudes, but logical values are encoded by their sign. At the hardened interfaces we expect values close to ±β.

This small diagnostic is particularly useful after refactoring: if a tensor that should contain logits suddenly contains raw 0/1 bits, the model can look syntactically correct while behaving almost randomly.

In [10]:
def describe_tensor(name, x):
    t = tf.cast(tf.convert_to_tensor(x), tf.float32)
    arr = t.numpy()
    print(
        f"{name:24s} shape={arr.shape}, min={arr.min(): .3f}, max={arr.max(): .3f}, mean|x|={np.mean(np.abs(arr)): .3f}"
    )

# Single-sample gameplay bits from canonical dataset
field_bits = tf.convert_to_tensor(ds_lin["field_bits"][0:1, 0, :], dtype=tf.float32)
gun_bits = tf.convert_to_tensor(ds_lin["gun_bits"][0:1, 0, :], dtype=tf.float32)

# Build temporary adapters to probe the explicit gameplay boundary.
diag_model_a = GameplayModelAAdapter(internal_model_a=eval_model_a, beta=BETA, harden_between_levels=True)

# Adapter A boundary: bits -> logits (internal) -> bits for gameplay handoff
comm_bits, meas_bits_list, out_bits_list, _comm_logits_dbg = diag_model_a(
    field_bits,
    explore=False,
    return_comm_logits=True,
)

# Internal A tensors from explicit hard-logit input
field_logits_a = hard_logit(field_bits, BETA)
comm_logits_a, meas_logits_a_list, out_logits_a_list = eval_model_a.compute_with_internal(
    field_logits_a,
    training=False,
)

# Values passed A -> B across gameplay boundary are bits; adapter B remaps to logits.
comm_logits_for_b = hard_logit(comm_bits, BETA)
prev_meas_logits_for_b = hard_logit(meas_bits_list[0], BETA)
prev_out_logits_for_b = hard_logit(out_bits_list[0], BETA)
gun_logits_b = hard_logit(gun_bits, BETA)

shoot_logit_b, meas_logits_b_list, out_logits_b_list, _, _ = eval_model_b.compute_with_internal(
    gun_logits_b,
    comm_logits_for_b,
    [prev_meas_logits_for_b],
    [prev_out_logits_for_b],
    training=False,
)

print("Internal logit interface diagnostics (single sample):")
describe_tensor("field logits into A", field_logits_a)
describe_tensor("A measurement logits", meas_logits_a_list[0])
describe_tensor("A outcome logits", out_logits_a_list[0])
describe_tensor("A communication logits", comm_logits_a)
describe_tensor("gun logits into B", gun_logits_b)
describe_tensor("A->B prev meas logits", prev_meas_logits_for_b)
describe_tensor("A->B prev out logits", prev_out_logits_for_b)
describe_tensor("A->B comm logits", comm_logits_for_b)
describe_tensor("B measurement logits", meas_logits_b_list[0])
describe_tensor("B outcome logits", out_logits_b_list[0])
describe_tensor("shoot logit", shoot_logit_b)
Internal logit interface diagnostics (single sample):
field logits into A      shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
A measurement logits     shape=(1, 16), min=-10.688, max= 9.814, mean|x|= 7.637
A outcome logits         shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
A communication logits   shape=(1, 1), min= 15.024, max= 15.024, mean|x|= 15.024
gun logits into B        shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
A->B prev meas logits    shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
A->B prev out logits     shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
A->B comm logits         shape=(1, 1), min= 10.000, max= 10.000, mean|x|= 10.000
B measurement logits     shape=(1, 16), min=-12.103, max= 4.806, mean|x|= 8.673
B outcome logits         shape=(1, 16), min=-10.000, max= 10.000, mean|x|= 10.000
shoot logit              shape=(1, 1), min=-14.189, max=-14.189, mean|x|= 14.189

6. Run the Model as a Player¶

The final boundary is the gameplay interface.

LinTrainableAssistedModelA/B work with logits. GameplayModelAAdapter/BAdapter translate between that internal representation and the bit-valued interface expected by the player wrappers. TrainableAssistedPlayers can therefore participate in the same tournament machinery as the hand-written players without knowing anything about logits.

In short:

Tournament → Player wrapper (bits) → Gameplay adapter → Lin model (logits)

and the reverse path on the way back out.

With p_rule=1 and no channel noise, the tournament score should be close to 1.0. If the individual layer accuracies are already essentially perfect but the tournament is not, inspect the representation and assembly boundaries before simply adding more training.

In [11]:
# Route gameplay via adapters (bits at game boundary, logits internally).
# Keep SR in gameplay mode for both internals.
eval_model_a.sr_layer.set_sr_mode("stochastic")
eval_model_b.sr_layer.set_sr_mode("stochastic")

gameplay_model_a = GameplayModelAAdapter(internal_model_a=eval_model_a, beta=BETA, harden_between_levels=True)
gameplay_model_b = GameplayModelBAdapter(internal_model_b=eval_model_b, beta=BETA, harden_between_levels=True)

layout_eval = GameLayout(
    field_size=FIELD_SIZE,
    comms_size=COMMS_SIZE,
    enemy_probability=0.5,
    channel_noise=0.0,
    number_of_games_in_tournament=2_000,
)
env = GameEnv(layout_eval)
players = TrainableAssistedPlayers(layout_eval, model_a=gameplay_model_a, model_b=gameplay_model_b)

# Match historical stochastic gameplay behavior.
players.set_explore(True)

t = Tournament(env, players, layout_eval)
log = t.tournament()
mean_reward, std_err = log.outcome()
print(f"Bootstrap tournament over {layout_eval.number_of_games_in_tournament}: {mean_reward:.4f} ± {std_err:.4f}")
Bootstrap tournament over 2000: 0.9845 ± 0.0028

7. Save the Bootstrap Weights¶

The resulting weights provide a known-good initialization for later end-to-end or reinforcement-learning experiments. We save weights rather than full Keras model serialization so the files remain tied to the current model classes and interfaces.

In [12]:
model_a_path = models_dir / f"lin_model_a_bootstrap_f{FIELD_SIZE}_m{COMMS_SIZE}_r{P_RULE:.2f}.weights.h5"
model_b_path = models_dir / f"lin_model_b_bootstrap_f{FIELD_SIZE}_m{COMMS_SIZE}_r{P_RULE:.2f}.weights.h5"

# Save evaluated internal models (with explicitly transferred trained layer weights).
eval_model_a.save_weights(model_a_path)
eval_model_b.save_weights(model_b_path)

print("Saved weights:")
print(" -", model_a_path)
print(" -", model_b_path)
Saved weights:
 - notebooks\models\lin_model_a_bootstrap_f4_m1_r1.00.weights.h5
 - notebooks\models\lin_model_b_bootstrap_f4_m1_r1.00.weights.h5