From 3edc124a5cd0420dda3966f8c9cc3cf0ac417496 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Tue, 2 Jun 2026 21:32:58 +0200 Subject: [PATCH] [refactor] move tests to tests/, add pytest functions and main() entrypoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Moved src/tests/ → tests/ - Added test_* functions with assertions to script-style test files - Added main() to each so IDEs offer it as a separate run target from pytest - Fixed cupy_test.py: remove spurious x_gpu += x_cpu, fix duplicate xlabel Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 +- src/tests/test_sub_image.py | 18 --- {src/tests => tests}/README.md | 0 {src/tests => tests}/__init__.py | 0 tests/cupy_test.py | 107 ++++++++++++++++++ .../test_binary_autoencoder.py | 21 +++- {src/tests => tests}/test_conv2d.py | 0 {src/tests => tests}/test_deep_norbs.py | 0 {src/tests => tests}/test_faces_sub_image.py | 0 .../test_gaussian_autoencoder.py | 22 +++- {src/tests => tests}/test_label.py | 0 .../test_learn_encoded_labels.py | 0 .../test_learn_norbs_labels.py | 0 {src/tests => tests}/test_linear.py | 51 +++++---- {src/tests => tests}/test_model.py | 37 +++--- {src/tests => tests}/test_norbs.py | 0 {src/tests => tests}/test_rbm.py | 0 {src/tests => tests}/test_rnn.py | 0 {src/tests => tests}/test_rnn_helper.py | 0 tests/test_sub_image.py | 26 +++++ {src/tests => tests}/test_xor.py | 0 21 files changed, 214 insertions(+), 71 deletions(-) delete mode 100644 src/tests/test_sub_image.py rename {src/tests => tests}/README.md (100%) rename {src/tests => tests}/__init__.py (100%) create mode 100644 tests/cupy_test.py rename {src/tests => tests}/test_binary_autoencoder.py (77%) rename {src/tests => tests}/test_conv2d.py (100%) rename {src/tests => tests}/test_deep_norbs.py (100%) rename {src/tests => tests}/test_faces_sub_image.py (100%) rename {src/tests => tests}/test_gaussian_autoencoder.py (80%) rename {src/tests => tests}/test_label.py (100%) rename {src/tests => tests}/test_learn_encoded_labels.py (100%) rename {src/tests => tests}/test_learn_norbs_labels.py (100%) rename {src/tests => tests}/test_linear.py (59%) rename {src/tests => tests}/test_model.py (71%) rename {src/tests => tests}/test_norbs.py (100%) rename {src/tests => tests}/test_rbm.py (100%) rename {src/tests => tests}/test_rnn.py (100%) rename {src/tests => tests}/test_rnn_helper.py (100%) create mode 100644 tests/test_sub_image.py rename {src/tests => tests}/test_xor.py (100%) diff --git a/.gitignore b/.gitignore index ab161cb..acb641b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ images *.egg-info __pycache__ *.npzdata -.ipynb_checkpoints/ -cupy_test.py +.ipynb_checkpoints/ \ No newline at end of file diff --git a/src/tests/test_sub_image.py b/src/tests/test_sub_image.py deleted file mode 100644 index 15312a8..0000000 --- a/src/tests/test_sub_image.py +++ /dev/null @@ -1,18 +0,0 @@ -from image.sub_image import SubImageExtract -from rbm.matrix import np - - -if __name__ == "__main__": - n = 2 - c = 3 - w = 8 - h = 8 - img_rbb = np.reshape(np.linspace(1, n*c*w*h, num=n*c*w*h, dtype=np.float64), (n, c, w, h)) - sub = SubImageExtract(4, 4, 1, 1) - sub(img_rbb, 3) - - img_gray = np.reshape(np.linspace(1, n*w*h, num=n*w*h, dtype=np.float64), (n, w, h)) - sub(img_gray, 1) - - - print("Test: [passed]") diff --git a/src/tests/README.md b/tests/README.md similarity index 100% rename from src/tests/README.md rename to tests/README.md diff --git a/src/tests/__init__.py b/tests/__init__.py similarity index 100% rename from src/tests/__init__.py rename to tests/__init__.py diff --git a/tests/cupy_test.py b/tests/cupy_test.py new file mode 100644 index 0000000..08dc7ba --- /dev/null +++ b/tests/cupy_test.py @@ -0,0 +1,107 @@ +import time + +import numpy as np +import cupy as cp + +from matplotlib import pyplot as plt + +def warmup_cupy(): + a = cp.random.random((10,10)) + b = cp.random.random((10,10)) + _ = cp.dot(a, b) + +def calc_cpu(a: np.array, b: np.array): + return np.dot(a, b) + +def calc_gpu(a: np.array, b: np.array): + return cp.dot(a, b) + +def perf_test_2(): + ### Numpy and CPU + print("Creation") + s = time.time() + x_cpu = np.ones((1000, 1000, 1000)) + e = time.time() + time_cpu = e - s + s = time.time() + x_gpu = cp.ones((1000, 1000, 1000)) + cp.cuda.Stream.null.synchronize() + e = time.time() + time_gpu = e - s + print(f"Speedup: {time_cpu/time_gpu}") + + ### Numpy and CPU + print("Multiply with constant") + s = time.time() + x_cpu *= 5 + e = time.time() + time_cpu = e - s + s = time.time() + x_gpu *= 5 + cp.cuda.Stream.null.synchronize() + e = time.time() + time_gpu = e - s + print(f"Speedup: {time_cpu/time_gpu}") + + ### Numpy and CPU + print("Multiply with constant, Square, Square") + s = time.time() + x_cpu *= 5 + x_cpu *= x_cpu + x_cpu += x_cpu + e = time.time() + time_cpu = e - s + s = time.time() + x_gpu *= 5 + x_gpu *= x_gpu + x_gpu += x_gpu + cp.cuda.Stream.null.synchronize() + e = time.time() + time_gpu = e - s + print(f"Speedup: {time_cpu/time_gpu}") + +def perf_test_1(): + n_values = [] + np_times = [] + cp_times = [] + ratio_times = [] + + for N in range(100, 5100, 100): + a_np = np.random.rand(1024, N) + b_np = np.random.rand(N, 1024) + + a_cp = cp.asarray(a_np) + b_cp = cp.asarray(b_np) + + start_time = time.time() + c_np = calc_cpu(a_np, b_np) + end_time = time.time() + numpy_time = end_time - start_time + + start_time = time.time() + c_cp = calc_gpu(a_cp, b_cp) + c_np = cp.asnumpy(c_cp) + end_time = time.time() + cupy_time = end_time - start_time + + n_values.append(N) + np_times.append(numpy_time) + cp_times.append(cupy_time) + ratio_times.append(numpy_time/cupy_time) + + plt.plot(n_values, np_times, label='numpy time') + plt.plot(n_values, cp_times, label='cupy time') + plt.plot(n_values, ratio_times, label='numpy/cupy time') + plt.xlabel("Matrix size [N]") + plt.ylabel("Time [s]") + plt.title("Performance numpy vs cupy") + plt.grid() + plt.legend() + plt.show() + +def main(): + warmup_cupy() + perf_test_2() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/tests/test_binary_autoencoder.py b/tests/test_binary_autoencoder.py similarity index 77% rename from src/tests/test_binary_autoencoder.py rename to tests/test_binary_autoencoder.py index ba16b70..8c7f94b 100644 --- a/src/tests/test_binary_autoencoder.py +++ b/tests/test_binary_autoencoder.py @@ -11,7 +11,7 @@ N_HID = 32 N_CASES = 400 -class TestModel(Model): +class BinaryAEModel(Model): def __init__(self, name: str, work_dir: str = '.'): super().__init__(name, work_dir) self.unit1 = Entity( @@ -41,11 +41,18 @@ def make_bars_and_stripes(n_cases: int, n: int = N) -> Mat: return data -if __name__ == "__main__": - prj_name = "binary_autoencoder" - work_dir = "results" +def test_binary_autoencoder(): + model = BinaryAEModel("binary_autoencoder_pytest", "results") + model.unit1.training_params.num_epochs = 500 + model.init(0.01) + train_batch = make_bars_and_stripes(N_CASES) + model.train(train_batch) + mae = float(np.mean(np.abs(train_batch - np.array([model.reconstruct(model.forward(x)) for x in train_batch])))) + assert mae < 0.4, f"Reconstruction MAE {mae:.4f} too high" - model = TestModel(prj_name, work_dir) + +def main(): + model = BinaryAEModel("binary_autoencoder", "results") model.init(0.01) train_batch = make_bars_and_stripes(N_CASES) @@ -73,3 +80,7 @@ if __name__ == "__main__": print(f"Mean reconstruction error: {total_error / n_show:.4f}") plt.tight_layout() plt.show() + + +if __name__ == "__main__": + main() diff --git a/src/tests/test_conv2d.py b/tests/test_conv2d.py similarity index 100% rename from src/tests/test_conv2d.py rename to tests/test_conv2d.py diff --git a/src/tests/test_deep_norbs.py b/tests/test_deep_norbs.py similarity index 100% rename from src/tests/test_deep_norbs.py rename to tests/test_deep_norbs.py diff --git a/src/tests/test_faces_sub_image.py b/tests/test_faces_sub_image.py similarity index 100% rename from src/tests/test_faces_sub_image.py rename to tests/test_faces_sub_image.py diff --git a/src/tests/test_gaussian_autoencoder.py b/tests/test_gaussian_autoencoder.py similarity index 80% rename from src/tests/test_gaussian_autoencoder.py rename to tests/test_gaussian_autoencoder.py index b604f7f..617548a 100644 --- a/src/tests/test_gaussian_autoencoder.py +++ b/tests/test_gaussian_autoencoder.py @@ -12,7 +12,7 @@ N_HID = 32 N_CASES = 400 -class TestModel(Model): +class GaussianAEModel(Model): def __init__(self, name: str, work_dir: str = '.'): super().__init__(name, work_dir) self.unit1 = Entity( @@ -41,11 +41,19 @@ def make_gaussian_blobs(n_cases: int, n: int = N) -> Mat: return data -if __name__ == "__main__": - prj_name = "gaussian_autoencoder" - work_dir = "results" +def test_gaussian_autoencoder(): + model = GaussianAEModel("gaussian_autoencoder_pytest", "results") + model.unit1.training_params.num_epochs = 1500 + model.init(0.01) + train_batch = normalize(make_gaussian_blobs(N_CASES)) + model.train(train_batch) + for x in train_batch[:10]: + recon = model.reconstruct(model.forward(x)) + assert recon.size == x.size - model = TestModel(prj_name, work_dir) + +def main(): + model = GaussianAEModel("gaussian_autoencoder", "results") model.init(0.01) train_batch = normalize(make_gaussian_blobs(N_CASES)) @@ -73,3 +81,7 @@ if __name__ == "__main__": print(f"Mean reconstruction error: {total_error / n_show:.4f}") plt.tight_layout() plt.show() + + +if __name__ == "__main__": + main() diff --git a/src/tests/test_label.py b/tests/test_label.py similarity index 100% rename from src/tests/test_label.py rename to tests/test_label.py diff --git a/src/tests/test_learn_encoded_labels.py b/tests/test_learn_encoded_labels.py similarity index 100% rename from src/tests/test_learn_encoded_labels.py rename to tests/test_learn_encoded_labels.py diff --git a/src/tests/test_learn_norbs_labels.py b/tests/test_learn_norbs_labels.py similarity index 100% rename from src/tests/test_learn_norbs_labels.py rename to tests/test_learn_norbs_labels.py diff --git a/src/tests/test_linear.py b/tests/test_linear.py similarity index 59% rename from src/tests/test_linear.py rename to tests/test_linear.py index 5504479..59aafc4 100644 --- a/src/tests/test_linear.py +++ b/tests/test_linear.py @@ -7,7 +7,7 @@ WORK_DIR = "../../results" USE_OPTIMIZER = True N_VIS = 3000 N_CASES = 1000 -class TestModel(Model): +class LinearModel(Model): def __init__(self, name: str, work_dir: str = '.', do_gaussian_hidden=False): super().__init__(name, work_dir) @@ -28,39 +28,40 @@ class TestModel(Model): x = self.unit1.reconstruct(x) return x -def linear(): - work_dir = "results" - prj_name = "linear" - prj_root = "/home/jens/work/repos/Rbm" +class _SmallLinearModel(Model): + def __init__(self): + super().__init__("linear_pytest", "results") + self.unit1 = Entity((64, 32), EntityParams(do_gaussian_visible=True), + TrainingParams(learning_rate=0.005, momentum=0.9, num_epochs=100)) - # Create layer - model = TestModel(prj_name, "results") + def forward(self, x: Mat): + return self.unit1.forward(x) - # Init weights + def reconstruct(self, x: Mat): + return self.unit1.reconstruct(x) + + +def test_linear(): + model = _SmallLinearModel() model.init(0.1) + batch = normalize(np.random.randn(50, 64, dtype=np.float64)) + model.train(batch) + for pattern in batch: + recon = model.reconstruct(model.forward(pattern)) + assert recon.size == pattern.size - # Load weights (if exists) + +def main(): + model = LinearModel("linear", "results") + model.init(0.1) model.load() - - # Prepare training data training_batch = np.random.randn(N_CASES, N_VIS, dtype=np.float64) - - # Normalize training data training_batch_norm = normalize(training_batch) - - # Train layer model.train(training_batch_norm) - - # Save weights model.save() + for pattern in training_batch: + model.reconstruct(model.forward(pattern)) - # Test with test data - test_batch = training_batch - for pattern in test_batch: - h = model.forward(pattern) - v = model.reconstruct(h) - # print(f"P{pattern} : {v}") if __name__ == "__main__": - linear() - print("Test: [passed]") + main() diff --git a/src/tests/test_model.py b/tests/test_model.py similarity index 71% rename from src/tests/test_model.py rename to tests/test_model.py index bb0594d..d639b74 100644 --- a/src/tests/test_model.py +++ b/tests/test_model.py @@ -2,7 +2,7 @@ from model.model import Model from rbm.entity import Entity, EntityParams, TrainingParams from rbm.matrix import Mat, np -class TestModel(Model): +class DeepModel(Model): def __init__(self, name: str, work_dir: str = '.'): super().__init__(name, work_dir) self.unit1 = Entity((1024, 333), EntityParams(), TrainingParams(learning_rate=0.1, momentum=0.9, do_rao_blackwell=True, num_epochs=1000)) @@ -24,28 +24,33 @@ class TestModel(Model): x = self.unit1.reconstruct(x) return x -if __name__ == "__main__": - # Create model - model = TestModel("TestModel", "results") - - # Init state +def test_deep_model(): + model = DeepModel("TestModel_pytest", "results") + model.unit1.training_params.num_epochs = 100 + model.unit2.training_params.num_epochs = 100 + model.unit3.training_params.num_epochs = 100 + model.unit4.training_params.num_epochs = 100 model.init(0.1) - - # load state - model.load() - - # create batch - batch = (np.random.rand(64, 1024) > 0.5).astype(np.float64) - - # Train + batch = (np.random.rand(32, 1024) > 0.5).astype(np.float64) model.train(batch) + errors = [float(np.mean((model.backward(model.forward(x)) - x) ** 2)) for x in batch] + assert sum(errors) / len(errors) < 0.5, "Deep model reconstruction MSE too high" - # save state + +def main(): + model = DeepModel("TestModel", "results") + model.init(0.1) + model.load() + batch = (np.random.rand(64, 1024) > 0.5).astype(np.float64) + model.train(batch) model.save() - for index, inp in enumerate(batch): out = model.backward(model.forward(inp))[0] print(f"- Pattern {index} -------------------------") print(f"Input : {inp}") print(f"Output : {(out > 0.9).astype(np.float64)}") print(f"Error : {np.mean((out - inp)**2):0.3f}") + + +if __name__ == "__main__": + main() diff --git a/src/tests/test_norbs.py b/tests/test_norbs.py similarity index 100% rename from src/tests/test_norbs.py rename to tests/test_norbs.py diff --git a/src/tests/test_rbm.py b/tests/test_rbm.py similarity index 100% rename from src/tests/test_rbm.py rename to tests/test_rbm.py diff --git a/src/tests/test_rnn.py b/tests/test_rnn.py similarity index 100% rename from src/tests/test_rnn.py rename to tests/test_rnn.py diff --git a/src/tests/test_rnn_helper.py b/tests/test_rnn_helper.py similarity index 100% rename from src/tests/test_rnn_helper.py rename to tests/test_rnn_helper.py diff --git a/tests/test_sub_image.py b/tests/test_sub_image.py new file mode 100644 index 0000000..9ac84e8 --- /dev/null +++ b/tests/test_sub_image.py @@ -0,0 +1,26 @@ +from image.sub_image import SubImageExtract +from rbm.matrix import np + + +def test_sub_image_rgb(): + n, c, w, h = 2, 3, 8, 8 + img = np.reshape(np.linspace(1, n*c*w*h, num=n*c*w*h, dtype=np.float64), (n, c, w, h)) + sub = SubImageExtract(4, 4, 1, 1) + result = sub(img, 3) + assert result.shape[1] == 3 + assert result.shape[2] == 4 and result.shape[3] == 4 + + +def test_sub_image_gray(): + n, w, h = 2, 8, 8 + img = np.reshape(np.linspace(1, n*w*h, num=n*w*h, dtype=np.float64), (n, w, h)) + sub = SubImageExtract(4, 4, 1, 1) + result = sub(img, 1) + assert result.shape[1] == 1 + assert result.shape[2] == 4 and result.shape[3] == 4 + + +if __name__ == "__main__": + test_sub_image_rgb() + test_sub_image_gray() + print("Test: [passed]") diff --git a/src/tests/test_xor.py b/tests/test_xor.py similarity index 100% rename from src/tests/test_xor.py rename to tests/test_xor.py