NAAZ1605's picture
Prepare ISL command recognition project
e2d95ed
Raw History Blame Contribute Delete
4.9 kB
"""Model I/O: construction, shapes, parameter counts, saving/loading, determinism."""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pytest
import config
from training import models as model_zoo
def test_builders_return_expected_output_shape():
# the builders take the input width and class count explicitly; build_model()
# supplies the project defaults (225 features, 50 classes)
assert model_zoo.build_gru(config.MODEL_INPUT_DIM, config.NUM_CLASSES) is not None
for name in config.AVAILABLE_MODELS:
model = model_zoo.build_model(name)
assert tuple(model.input_shape) == (None, None, config.MODEL_INPUT_DIM), name
assert tuple(model.output_shape) == (None, config.NUM_CLASSES), name
def test_parameter_counts_are_within_budget():
for name in config.AVAILABLE_MODELS:
model = model_zoo.build_model(name)
params = model_zoo.parameter_count(model)
assert 0 < params < config.MAX_PARAM_BUDGET, name
def test_models_are_comparable_in_size():
"""The proposed model must not win by being an order of magnitude bigger."""
counts = {n: model_zoo.parameter_count(model_zoo.build_model(n)) for n in config.AVAILABLE_MODELS}
assert max(counts.values()) / min(counts.values()) < 3.0, counts
def test_gru_mha_contains_attention_and_pooling():
model = model_zoo.build_model("gru_mha")
layers = [type(l).__name__ for l in model.layers]
assert any("MultiHeadAttention" in n for n in layers)
assert model_zoo.parameter_count(model) > model_zoo.parameter_count(model_zoo.build_model("gru"))
def test_output_is_a_probability_distribution():
model = model_zoo.build_model("gru")
x = np.random.default_rng(0).normal(size=(2, config.SEQUENCE_LENGTH, config.MODEL_INPUT_DIM)).astype(np.float32)
probs = model.predict(x, verbose=0)
assert probs.shape == (2, config.NUM_CLASSES)
assert np.allclose(probs.sum(axis=1), 1.0, atol=1e-4)
assert probs.min() >= 0.0
def test_seeding_makes_initialisation_reproducible():
from training.train_common import set_seeds
set_seeds(123)
a = model_zoo.build_model("gru")
w1 = [w.copy() for w in a.get_weights()]
set_seeds(123)
b = model_zoo.build_model("gru")
w2 = [w.copy() for w in b.get_weights()]
assert all(np.allclose(x, y) for x, y in zip(w1, w2))
def test_save_and_load_roundtrip(tmp_path):
import tensorflow as tf
model = model_zoo.build_model("lstm")
path = tmp_path / "tiny.keras"
model.save(path)
reloaded = tf.keras.models.load_model(str(path))
x = np.random.default_rng(1).normal(size=(1, config.SEQUENCE_LENGTH, config.MODEL_INPUT_DIM)).astype(np.float32)
assert np.allclose(model.predict(x, verbose=0), reloaded.predict(x, verbose=0), atol=1e-6)
def test_models_accept_variable_sequence_length():
"""Input is (None, None, D): live buffers of any length can be scored."""
model = model_zoo.build_model("gru")
for t in (10, config.SEQUENCE_LENGTH, 45):
x = np.zeros((1, t, config.MODEL_INPUT_DIM), dtype=np.float32)
assert model.predict(x, verbose=0).shape == (1, config.NUM_CLASSES)
def test_unknown_model_name_raises():
with pytest.raises(KeyError):
model_zoo.build_model("transformer_xl")
# --------------------------------------------------------------------------
# trained artefacts
# --------------------------------------------------------------------------
def test_trained_models_load_and_predict(trained_model):
name, model = trained_model
x = np.zeros((1, config.SEQUENCE_LENGTH, config.MODEL_INPUT_DIM), dtype=np.float32)
probs = model.predict(x, verbose=0)[0]
assert probs.shape == (config.NUM_CLASSES,)
assert abs(float(probs.sum()) - 1.0) < 1e-4
def test_all_trained_models_agree_on_input_shape(all_trained_models):
for name, model in all_trained_models.items():
assert model.input_shape[-1] == config.MODEL_INPUT_DIM, name
assert model.output_shape[-1] == config.NUM_CLASSES, name
def test_training_history_recorded_metrics():
import json
for name in config.AVAILABLE_MODELS:
p = Path(config.MODEL_PATHS[name]) / "training_history.json"
if not p.exists():
continue
h = json.loads(p.read_text())
assert h["parameters"] > 0
assert 0.0 <= h["best_val_accuracy"] <= 1.0
assert len(h["history"]["loss"]) == h["epochs_run"]
assert h["config"]["seed"] == config.RANDOM_SEED
def test_models_select_one_architecture_each():
"""Different architectures must actually differ in layer composition."""
def kinds(name):
return [type(l).__name__ for l in model_zoo.build_model(name).layers]
assert kinds("lstm") != kinds("gru")
assert "MultiHeadAttention" in kinds("gru_mha")
assert "LSTM" in kinds("lstm") and "GRU" in kinds("gru")