From bafc81402a09f4fc89d219ba1d84f4168494167c Mon Sep 17 00:00:00 2001 From: FFMG Date: Fri, 12 Jun 2026 15:18:27 +0200 Subject: [PATCH 1/2] Created a python soltuion with example --- CHANGELOG.md | 3 + GEMINI.md | 83 +++++++++++++ README.md | 3 +- python/README.md | 76 ++++++++++++ python/bindings.cpp | 212 ++++++++++++++++++++++++++++++++ python/example.py | 92 ++++++++++++++ python/neuralnetwork_py.sln | 21 ++++ python/neuralnetwork_py.vcxproj | 112 +++++++++++++++++ 8 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 GEMINI.md create mode 100644 python/README.md create mode 100644 python/bindings.cpp create mode 100644 python/example.py create mode 100644 python/neuralnetwork_py.sln create mode 100644 python/neuralnetwork_py.vcxproj diff --git a/CHANGELOG.md b/CHANGELOG.md index 2361cda..8876895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to the `neural-network` library will be documented in this f - Created the `myoddweb::nn` namespace. - Wrapped all core neural network library classes, structures, and helper functions in the new `myoddweb::nn` namespace (including `NeuralNetwork`, `Layer`, `Neuron`, `activation`, `NeuralNetworkOptions`, etc.). - Added explicit documentation in the `README.md` explaining how to import and use the new namespace. +- Created a new `/python/` subdirectory containing a C++ binding toolchain (using `pybind11` and NuGet package restore) to compile the C++ library into a Python extension module (`neuralnetwork.pyd`). +- Added a Python test script `example.py` demonstrating how to train and use the neural network from Python. +- Added explicit documentation in `python/README.md` explaining how to build and call the Python module. ### Changed - Updated all stand-alone example header files in `src/neuralnetwork/examples/` to use the `myoddweb::nn` namespace. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..8957844 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,83 @@ +# Gemini Project Context: neural-network + +This document provides context and guidelines for interacting with the `neural-network` project. + +My name is Florent + +## Project Overview + +The `neural-network` project is a lightweight, dependency-free Feedforward and Recurrent Neural Network library implemented in modern C++. It is designed for both educational purposes and practical application in financial market analysis and trading. + +### Key Features +- **Neural Network Architecture:** Supports standard Feedforward (FF), Elman RNN, and Gated Recurrent Unit (GRU) layers. +- **Advanced Training:** Includes Adam, AdamW, Nadam, and NadamW optimizers, Backpropagation Through Time (BPTT), residual connections, gradient clipping, and dropout (dilution). +- **Learning Rate Strategies:** Implements warmup, exponential decay, periodic restarts with boosts, and adaptive learning rate scheduling. +- **Market Integration:** A dedicated `market` class handles technical indicators (RSI, Bollinger Bands, EMA, MACD, etc.) and data normalization for trading strategies. +- **Visualization:** A built-in `Chart` tool using the `olcPixelGameEngine` for real-time visualization of market data, predictions, and trade signals. +- **Persistence:** Supports saving and loading trained models via a custom serializer. +- **Zero Dependencies:** Built from scratch with minimal external dependencies (embedded `sqlite3`, `TinyJSON`, `olcPixelGameEngine`). + +### Technology Stack +- **Language:** C++17/C++20 +- **Platform:** Windows (Win32 API for screen metrics, MSVC for compilation) +- **Profiling:** Integrated with Tracy Profiler, disabled by default. + +--- + +## Building and Running + +### Prerequisites +- **Visual Studio 2022** (or compatible version) with C++ development workload. +- **Windows OS** (due to Win32 API usage in the charting tool). +- Try and support C++ 99 where possible, comment if not possible. + +### Build Instructions +1. Open `examples/neuralnetwork.sln` in Visual Studio. +2. Select the desired configuration (Debug/Release) and platform (x64). +3. Build the solution (`Ctrl+Shift+B`). + +### Running the Project +- The primary entry point is `examples/main.cpp`. + +### Examples +Multiple standalone examples are located in `examples/`, including: +- `xor.h`: Classic XOR problem. +- `threebitparity.h`: 3-bit parity check. +- `twomoon.h`: Two moons classification. +- `addingproblem.h`: RNN benchmark for long-term dependencies. + +- Examples are commented out by default, the user will enable the example(s) needed where and when needed. + +--- + +## Development Conventions + +### Coding Style +- Use 2 spaces, never tab. +- **Naming:** + - Classes: `PascalCase` (e.g., `NeuralNetwork`). + - Methods/Functions: `snake_case` (e.g., `train`, `think`). + - Private Members: Prefixed with an underscore (e.g., `_learning_rate`). +- **Headers:** Uses `#pragma once` for include guards. +- **Error Handling:** Uses `Logger` for logging and standard exceptions for critical failures. + +### Architecture Patterns +- **Options Pattern:** `NeuralNetworkOptions` uses a builder-like pattern for configuration. +- **RAII:** Strict adherence to RAII for memory and resource management (though some raw pointers are used in legacy parts). +- **Thread Safety:** Uses `std::shared_mutex` for concurrent access to market data and network states. + +### Logging and Profiling +- Use `Logger::info`, `Logger::debug`, `Logger::error`, etc., for all output. +- Every new function my start with MYODDWEB_PROFILE_FUNCTION("Name-Of-Class") +- If you see a function that does not have MYODDWEB_PROFILE_FUNCTION("Name-Of-Class") as a first line, please flag it to me. +- **important** The MACRO MYODDWEB_PROFILE_FUNCTION has no impact on performance, when I ask you to review my code for performance, ignore MYODDWEB_PROFILE_FUNCTION as it has no impact. + +--- + +- When looking at the Layer Class, remember to look at all the derived classes. +- Do not worry about Git commits, I will deal will them. + +## Key Files +- `include/neuralnetwork/neuralnetwork.h`: The main interface for the NN library. +- `examples/config.json`: Main configuration for the examples runner. +- `examples/strategy.json`: Trading strategy definitions. diff --git a/README.md b/README.md index 8facedd..c0f4a8b 100644 --- a/README.md +++ b/README.md @@ -271,9 +271,10 @@ For more information on AVX2, see the [Intel Intrinsics Guide](https://www.intel ## Repository Layout -* `\include\neuralnetwork\`: The stand-alone core neural network library (including `/layers/`, `/helpers/`, and `/common/` subdirectories). +* `\include\neuralnetwork\`: The stand-alone core C++ neural network library (including `/layers/`, `/helpers/`, and `/common/` subdirectories). * `\examples\`: Standalone example implementations, runner (`main.cpp`), and the main Visual Studio solution (`neuralnetwork.sln`). * `\tests\`: Comprehensive unit test suite. +* `\python\`: Pybind11-based Visual Studio 2022 solution (`neuralnetwork_py.sln`) and Python usage examples. ## Building and Running diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..efdfeec --- /dev/null +++ b/python/README.md @@ -0,0 +1,76 @@ +# Python Bindings for neural-network + +This subdirectory contains the Visual Studio 2022 C++ project and code necessary to build and run native Python bindings for the `myoddweb::nn` neural network library using `pybind11`. + +--- + +## Folder Layout + +* `bindings.cpp`: C++ source file defining the `pybind11` wrapper layer, mapping C++ types, enums, and classes into Python. +* `neuralnetwork_py.vcxproj`: Visual Studio C++ project file configured to build a dynamic library output with a `.pyd` file extension. +* `neuralnetwork_py.vcxproj.filters`: Project filters mapping for Solution Explorer organization. +* `neuralnetwork_py.sln`: Main Visual Studio solution. +* `example.py`: Python script illustrating options configuration, model instantiation, callback monitoring, training, inference, and serialization. + +--- + +## Build Prerequisites + +1. **Visual Studio 2022** with the **Desktop development with C++** workload installed. +2. **Python 3.x** (64-bit version recommended, matching the target build platform). +3. **pybind11**: Install the header-only package via `pip`: + ```bash + pip install pybind11 + ``` +4. **Environment Setup**: + To allow Visual Studio to locate Python headers (`python.h`) and libraries (`python3x.lib`), you should configure the `PYTHON_HOME` environment variable on your computer: + * Set `PYTHON_HOME` to your Python installation folder (e.g. `C:\Users\\AppData\Local\Programs\Python\Python312` or `C:\Program Files\Python312`). + * *Alternatively*, if you have Visual Studio's **Python development** workload installed, MSBuild will automatically search for the default Python paths. + +--- + +## Building the Module + +1. Open the solution file [neuralnetwork_py.sln](file:///H:/projects/github/neural-network2/python/neuralnetwork_py.sln) in Visual Studio 2022. +2. Set the solution build configuration to: + * **Configuration:** `Release` + * **Platform:** `x64` +3. Build the solution (`Ctrl+Shift+B` or right-click the project -> **Build**). +4. This compiles the library and generates `neuralnetwork.pyd` inside the `python/x64/Release/` folder. + +--- + +## Running the Python Example + +Once the `.pyd` module is built, you can run the example script: + +```bash +python example.py +``` + +This script trains a neural network on the XOR classification task and validates predictions. + +--- + +## API Usage Reference + +The Python bindings expose the C++ API in a clean, Pythonic wrapper inside the `neuralnetwork` module: + +### Enums +* `nn.ActivationMethod`: `Linear`, `Sigmoid`, `Tanh`, `Relu`, `LeakyRelu`, `PRelu`, `Selu`, `Swish`, `Mish`, `Gelu`, `Elu`, `Softmax`. +* `nn.OptimiserType`: `SGD`, `Momentum`, `Nesterov`, `RMSProp`, `Adam`, `AdamW`, `AdaGrad`, `AdaDelta`, `Nadam`, `NadamW`, `AMSGrad`, `LAMB`, `Lion`, `None`. +* `nn.ErrorCalculationType`: `None`, `HuberLoss`, `HuberDirectionLoss`, `MAE`, `MSE`, `RMSE`, `BCELoss`, `CrossEntropy`, etc. + +### Core Classes & Properties +* `nn.Activation(method, alpha, temperature=1.0)`: Represents the activation configuration. +* `nn.NeuralNetworkOptions.create(topology)`: Returns a builder options object. Exposes builder methods such as: + * `.with_number_of_epoch(500)` + * `.with_learning_rate(0.15)` + * `.with_batch_size(4)` + * `.with_progress_callback(callback_function)` (allows native Python functions to serve as callbacks during training). +* `nn.NeuralNetwork(options)`: Core model executor. Methods: + * `.train(inputs, outputs)`: Input arguments are standard Python nested lists of floats (e.g. `[[0, 0, 1], [1, 0, 1]]`). + * `.think(inputs)`: Returns predictions as Python lists. +* `nn.NeuralNetworkSerializer`: Exposes static methods: + * `nn.NeuralNetworkSerializer.save(net, filepath)` + * `nn.NeuralNetworkSerializer.load(filepath)` (returns a deserialized network instance). diff --git a/python/bindings.cpp b/python/bindings.cpp new file mode 100644 index 0000000..3aac6c4 --- /dev/null +++ b/python/bindings.cpp @@ -0,0 +1,212 @@ +#include +#include +#include + +#include "neuralnetwork.h" +#include "neuralnetworkoptions.h" +#include "common/activation.h" +#include "common/optimiser.h" +#include "layers/layer.h" +#include "layers/layerdetails.h" +#include "layers/outputlayerdetails.h" +#include "layers/multioutputlayerdetails.h" +#include "helpers/errorcalculation.h" +#include "helpers/neuralnetworkserializer.h" + +namespace py = pybind11; +using namespace myoddweb::nn; + +PYBIND11_MODULE(neuralnetwork, m) { + m.doc() = "Python bindings for the myoddweb::nn Neural Network Library"; + + // 1. Enums + py::enum_(m, "ActivationMethod") + .value("Linear", activation::method::linear) + .value("Sigmoid", activation::method::sigmoid) + .value("Tanh", activation::method::tanh) + .value("Relu", activation::method::relu) + .value("LeakyRelu", activation::method::leakyRelu) + .value("PRelu", activation::method::PRelu) + .value("Selu", activation::method::selu) + .value("Swish", activation::method::swish) + .value("Mish", activation::method::mish) + .value("Gelu", activation::method::gelu) + .value("Elu", activation::method::elu) + .value("Softmax", activation::method::softmax) + .export_values(); + + py::enum_(m, "OptimiserType") + .value("SGD", OptimiserType::SGD) + .value("Momentum", OptimiserType::Momentum) + .value("Nesterov", OptimiserType::Nesterov) + .value("RMSProp", OptimiserType::RMSProp) + .value("Adam", OptimiserType::Adam) + .value("AdamW", OptimiserType::AdamW) + .value("AdaGrad", OptimiserType::AdaGrad) + .value("AdaDelta", OptimiserType::AdaDelta) + .value("Nadam", OptimiserType::Nadam) + .value("NadamW", OptimiserType::NadamW) + .value("AMSGrad", OptimiserType::AMSGrad) + .value("LAMB", OptimiserType::LAMB) + .value("Lion", OptimiserType::Lion) + .value("None", OptimiserType::None) + .export_values(); + + py::enum_(m, "ErrorCalculationType") + .value("None", ErrorCalculation::type::none) + .value("HuberLoss", ErrorCalculation::type::huber_loss) + .value("HuberDirectionLoss", ErrorCalculation::type::huber_direction_loss) + .value("MAE", ErrorCalculation::type::mae) + .value("MSE", ErrorCalculation::type::mse) + .value("RMSE", ErrorCalculation::type::rmse) + .value("NRMSE", ErrorCalculation::type::nrmse) + .value("MAPE", ErrorCalculation::type::mape) + .value("SMAPE", ErrorCalculation::type::smape) + .value("WAPE", ErrorCalculation::type::wape) + .value("DirectionalAccuracy", ErrorCalculation::type::directional_accuracy) + .value("BCELoss", ErrorCalculation::type::bce_loss) + .value("CrossEntropy", ErrorCalculation::type::cross_entropy) + .value("LogCosh", ErrorCalculation::type::log_cosh) + .value("DirectionalConfidenceScore", ErrorCalculation::type::directional_confidence_score) + .value("PredictionCoverage", ErrorCalculation::type::prediction_coverage) + .export_values(); + + py::enum_(m, "LayerArchitecture") + .value("None", Layer::Architecture::None) + .value("FF", Layer::Architecture::FF) + .value("Elman", Layer::Architecture::Elman) + .value("Gru", Layer::Architecture::Gru) + .value("Lstm", Layer::Architecture::Lstm) + .value("MultiOutput", Layer::Architecture::MultiOutput) + .export_values(); + + py::enum_(m, "LayerRole") + .value("Input", Layer::Role::Input) + .value("Hidden", Layer::Role::Hidden) + .value("Output", Layer::Role::Output) + .value("MultiOutput", Layer::Role::MultiOutput) + .export_values(); + + // 2. Structs / Helper Classes + py::class_(m, "Activation") + .def(py::init()) + .def(py::init(), py::arg("method"), py::arg("alpha"), py::arg("temperature") = 1.0) + .def("activate", py::overload_cast(&activation::activate, py::const_)) + .def("activate_derivative", &activation::activate_derivative) + .def("method_to_string", py::overload_cast<>(&activation::method_to_string, py::const_)) + .def_property_readonly("method", &activation::get_method) + .def_property_readonly("alpha", &activation::get_alpha) + .def_property("inference_temperature", &activation::get_inference_temperature, &activation::set_inference_temperature); + + py::class_(m, "EvaluationConfig") + .def(py::init<>()) + .def(py::init(), + py::arg("neutral_tolerance"), + py::arg("confidence_threshold"), + py::arg("huber_delta"), + py::arg("direction_lambda"), + py::arg("use_direction_penalty"), + py::arg("cross_entropy_lambda"), + py::arg("epsilon") = 1e-12) + .def_property_readonly("neutral_tolerance", &EvaluationConfig::neutral_tolerance) + .def_property_readonly("confidence_threshold", &EvaluationConfig::confidence_threshold) + .def_property_readonly("huber_delta", &EvaluationConfig::huber_delta) + .def_property_readonly("direction_lambda", &EvaluationConfig::direction_lambda) + .def_property_readonly("use_direction_penalty", &EvaluationConfig::use_direction_penalty) + .def_property_readonly("cross_entropy_lambda", &EvaluationConfig::cross_entropy_lambda) + .def_property_readonly("epsilon", &EvaluationConfig::epsilon); + + py::class_(m, "LayerDetails") + .def(py::init()) + .def_property_readonly("architecture", &LayerDetails::get_layer_architecture) + .def_property_readonly("size", &LayerDetails::get_size) + .def_property_readonly("activation", &LayerDetails::get_activation) + .def_property_readonly("dropout", &LayerDetails::get_dropout) + .def_property_readonly("weight_decay", &LayerDetails::get_weight_decay) + .def_property_readonly("optimiser_type", &LayerDetails::get_optimiser_type) + .def_property_readonly("momentum", &LayerDetails::get_momentum); + + py::class_(m, "OutputLayerDetails") + .def(py::init()) + .def_property_readonly("size", &OutputLayerDetails::get_size) + .def_property_readonly("activation", &OutputLayerDetails::get_activation) + .def_property_readonly("output_error_calculation_type", &OutputLayerDetails::get_output_error_calculation_type) + .def_property_readonly("error_evaluation_config", &OutputLayerDetails::get_error_evaluation_config) + .def_property_readonly("weight_decay", &OutputLayerDetails::get_weight_decay) + .def_property_readonly("optimiser_type", &OutputLayerDetails::get_optimiser_type) + .def_property_readonly("momentum", &OutputLayerDetails::get_momentum); + + py::class_(m, "MultiOutputLayerDetails") + .def(py::init&, const OutputLayerDetails&>()) + .def_property_readonly("hidden_layers", &MultiOutputLayerDetails::get_hidden_layers) + .def_property_readonly("output_details", &MultiOutputLayerDetails::get_output_details); + + py::class_(m, "NeuralNetworkHelperMetrics") + .def_property_readonly("error", &NeuralNetworkHelperMetrics::error) + .def_property_readonly("error_type", &NeuralNetworkHelperMetrics::error_type); + + py::class_>(m, "NeuralNetworkHelper") + .def_property_readonly("learning_rate", &NeuralNetworkHelper::learning_rate) + .def_property_readonly("number_of_epoch", &NeuralNetworkHelper::number_of_epoch) + .def_property_readonly("epoch", &NeuralNetworkHelper::epoch) + .def_property_readonly("percent_complete", &NeuralNetworkHelper::percent_complete) + .def_property_readonly("sample_size", &NeuralNetworkHelper::sample_size) + .def("calculate_forecast_metric", &NeuralNetworkHelper::calculate_forecast_metric) + .def("calculate_forecast_metrics", &NeuralNetworkHelper::calculate_forecast_metrics); + + // 3. NeuralNetworkOptions + py::class_(m, "NeuralNetworkOptions") + .def_static("create", py::overload_cast&>(&NeuralNetworkOptions::create)) + .def("with_has_bias", &NeuralNetworkOptions::with_has_bias) + .def("with_output_layer_details", py::overload_cast&>(&NeuralNetworkOptions::with_output_layer_details)) + .def("with_output_layer_details", py::overload_cast(&NeuralNetworkOptions::with_output_layer_details)) + .def("with_output_layer_details", py::overload_cast&>(&NeuralNetworkOptions::with_output_layer_details)) + .def("with_output_layer_details", py::overload_cast(&NeuralNetworkOptions::with_output_layer_details)) + .def("with_number_of_epoch", &NeuralNetworkOptions::with_number_of_epoch) + .def("with_batch_size", &NeuralNetworkOptions::with_batch_size) + .def("with_data_is_unique", &NeuralNetworkOptions::with_data_is_unique) + .def("with_progress_callback", [](NeuralNetworkOptions& self, const std::function& callback) { + return self.with_progress_callback(callback); + }) + .def("with_number_of_threads", &NeuralNetworkOptions::with_number_of_threads) + .def("with_learning_rate", &NeuralNetworkOptions::with_learning_rate) + .def("with_learning_rate_decay_rate", &NeuralNetworkOptions::with_learning_rate_decay_rate) + .def("with_learning_rate_warmup", &NeuralNetworkOptions::with_learning_rate_warmup) + .def("with_learning_rate_boost_rate", &NeuralNetworkOptions::with_learning_rate_boost_rate) + .def("with_adaptive_learning_rates", &NeuralNetworkOptions::with_adaptive_learning_rates) + .def("with_hidden_layers", &NeuralNetworkOptions::with_hidden_layers) + .def("with_residual_layer_jump", &NeuralNetworkOptions::with_residual_layer_jump) + .def("with_clip_threshold", &NeuralNetworkOptions::with_clip_threshold) + .def("with_shuffle_training_data", &NeuralNetworkOptions::with_shuffle_training_data) + .def("with_shuffle_bptt_batches", &NeuralNetworkOptions::with_shuffle_bptt_batches) + .def("with_enable_bptt", &NeuralNetworkOptions::with_enable_bptt) + .def("with_bptt_max_ticks", &NeuralNetworkOptions::with_bptt_max_ticks) + .def("with_update_training_monitor_percent", &NeuralNetworkOptions::with_update_training_monitor_percent) + .def("with_final_error_calculation_types", &NeuralNetworkOptions::with_final_error_calculation_types); + + // 4. NeuralNetwork + py::class_(m, "NeuralNetwork") + .def(py::init()) + .def(py::init&, const activation::method&, const activation::method&>()) + .def("train", &NeuralNetwork::train) + .def("think", py::overload_cast>&>(&NeuralNetwork::think, py::const_)) + .def("think", py::overload_cast&>(&NeuralNetwork::think, py::const_)) + .def("get_topology", &NeuralNetwork::get_topology) + .def("calculate_forecast_metric", &NeuralNetwork::calculate_forecast_metric) + .def("calculate_forecast_metrics", &NeuralNetwork::calculate_forecast_metrics, py::arg("error_types"), py::arg("final_check") = false) + .def("calculate_forecast_metric_all_layers", &NeuralNetwork::calculate_forecast_metric_all_layers) + .def("calculate_forecast_metrics_all_layers", &NeuralNetwork::calculate_forecast_metrics_all_layers, py::arg("error_types"), py::arg("final_check") = false) + .def("get_learning_rate", &NeuralNetwork::get_learning_rate) + .def("get_temperature", py::overload_cast<>(&NeuralNetwork::get_temperature, py::const_)) + .def("get_temperature", py::overload_cast(&NeuralNetwork::get_temperature, py::const_)) + .def("get_inference_temperature", &NeuralNetwork::get_inference_temperature) + .def("get_percent_complete", &NeuralNetwork::get_percent_complete) + .def("has_training_data", &NeuralNetwork::has_training_data) + .def("options", py::overload_cast<>(&NeuralNetwork::options)) + .def("options", py::overload_cast<>(&NeuralNetwork::options, py::const_)); + + // 5. NeuralNetworkSerializer + py::class_>(m, "NeuralNetworkSerializer") + .def_static("load", &NeuralNetworkSerializer::load, py::return_value_policy::take_ownership) + .def_static("save", &NeuralNetworkSerializer::save); +} diff --git a/python/example.py b/python/example.py new file mode 100644 index 0000000..747c770 --- /dev/null +++ b/python/example.py @@ -0,0 +1,92 @@ +import os +import sys + +# Add the directory containing the compiled .pyd module to the import search path +# (Assuming the module is built in x64/Release or placed in this folder) +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "x64", "Release")) + +try: + import neuralnetwork as nn + print("Successfully imported neuralnetwork library!") +except ImportError as e: + print(f"Error: Could not import neuralnetwork extension module: {e}") + print("Please make sure you have built the solution in Release/x64 first.") + sys.exit(1) + +def run_xor_example(): + print("\n--- Running XOR Example ---") + + # 1. Configure the network topology and options + topology = [3, 2, 1] + + # Expose individual output layer settings + out_activation = nn.Activation(nn.ActivationMethod.Sigmoid, 1.0) + out_layer = nn.OutputLayerDetails( + topology[-1], + out_activation, + nn.ErrorCalculationType.MSE, + nn.EvaluationConfig(), + 0.0, + nn.OptimiserType.SGD, + 0.99 + ) + + # Create the builder options + options = nn.NeuralNetworkOptions.create(topology) \ + .with_batch_size(4) \ + .with_output_layer_details(out_layer) \ + .with_learning_rate(0.15) \ + .with_number_of_epoch(500) + + # Optional progress callback (defined in Python!) + def on_progress(helper): + if helper.epoch % 50 == 0: + print(f"Epoch: {helper.epoch:3d} | Complete: {helper.percent_complete * 100:5.1f}%") + return True # Return False to stop training early + + options = options.with_progress_callback(on_progress) + + # 2. Instantiate the network + net = nn.NeuralNetwork(options) + + # 3. Define the training dataset (XOR problem with bias/3rd input) + training_inputs = [ + [0.0, 0.0, 1.0], + [1.0, 1.0, 1.0], + [1.0, 0.0, 1.0], + [0.0, 1.0, 1.0] + ] + + training_outputs = [ + [0.0], + [0.0], + [1.0], + [1.0] + ] + + # 4. Train the model + print("Training model...") + net.train(training_inputs, training_outputs) + + # 5. Evaluate the model (Inference) + print("\nEvaluating predictions:") + for inputs, expected in zip(training_inputs, training_outputs): + outputs = net.think(inputs) + print(f"Input: {inputs[:2]} | Expected: {expected[0]} | Predicted: {outputs[0]:.4f}") + + # 6. Serialise and deserialise model + model_path = "python_xor_model.json" + print(f"\nSaving model to: {model_path}...") + nn.NeuralNetworkSerializer.save(net, model_path) + + print(f"Loading model from: {model_path}...") + loaded_net = nn.NeuralNetworkSerializer.load(model_path) + + # Test loaded model + test_input = [1.0, 0.0, 1.0] + loaded_output = loaded_net.think(test_input) + print(f"Loaded model prediction on {test_input[:2]}: {loaded_output[0]:.4f}") + +if __name__ == '__main__': + run_xor_example() diff --git a/python/neuralnetwork_py.sln b/python/neuralnetwork_py.sln new file mode 100644 index 0000000..5066536 --- /dev/null +++ b/python/neuralnetwork_py.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35312.102 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "neuralnetwork_py", "neuralnetwork_py.vcxproj", "{A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x64.ActiveCfg = Debug|x64 + {A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x64.Build.0 = Debug|x64 + {A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x64.ActiveCfg = Release|x64 + {A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/python/neuralnetwork_py.vcxproj b/python/neuralnetwork_py.vcxproj new file mode 100644 index 0000000..344af1b --- /dev/null +++ b/python/neuralnetwork_py.vcxproj @@ -0,0 +1,112 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {A9B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D} + neuralnetwork + 10.0 + neuralnetwork_py + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + .pyd + neuralnetwork + + + .pyd + neuralnetwork + + + + Level3 + true + VALIDATE_DATA=1;_DEBUG;_WINDOWS;_USRDLL;MYODDWEB_PROFILE=0;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + ..\include;..\include\neuralnetwork;..\include\neuralnetwork\libraries;$(PYTHON_HOME)\include;$(PythonDir)include;$(PYTHON_HOME)\Lib\site-packages\pybind11\include;$(PythonDir)Lib\site-packages\pybind11\include;$(LOCALAPPDATA)\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\pybind11\include;%(AdditionalIncludeDirectories) + AdvancedVectorExtensions2 + + + Windows + true + $(PYTHON_HOME)\libs;$(PythonDir)libs;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;_USRDLL;VALIDATE_DATA=0;MYODDWEB_PROFILE=0;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + ..\include;..\include\neuralnetwork;..\include\neuralnetwork\libraries;$(PYTHON_HOME)\include;$(PythonDir)include;$(PYTHON_HOME)\Lib\site-packages\pybind11\include;$(PythonDir)Lib\site-packages\pybind11\include;$(LOCALAPPDATA)\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\pybind11\include;%(AdditionalIncludeDirectories) + AdvancedVectorExtensions2 + + + Windows + true + true + true + $(PYTHON_HOME)\libs;$(PythonDir)libs;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + From 869311cc6ca59f6499f3eb5aaf715628dbe7d7d3 Mon Sep 17 00:00:00 2001 From: FFMG Date: Fri, 12 Jun 2026 15:29:12 +0200 Subject: [PATCH 2/2] Added workflow for python --- .github/workflows/python.yml | 36 ++++++++++++++++++++++++++++++++++++ python/README.md | 1 + python/import_check.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 .github/workflows/python.yml create mode 100644 python/import_check.py diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..e7dfb90 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,36 @@ +name: Python Binding CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-verify-python: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install pybind11 dependency + run: | + python -m pip install --upgrade pip + pip install pybind11 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Build Python Binding Module + run: msbuild python/neuralnetwork_py.sln /p:Configuration=Release /p:Platform=x64 + env: + PYTHON_HOME: ${{ env.pythonLocation }} + + - name: Verify Module Import and Functionality + run: python python/import_check.py diff --git a/python/README.md b/python/README.md index efdfeec..68be5c3 100644 --- a/python/README.md +++ b/python/README.md @@ -11,6 +11,7 @@ This subdirectory contains the Visual Studio 2022 C++ project and code necessary * `neuralnetwork_py.vcxproj.filters`: Project filters mapping for Solution Explorer organization. * `neuralnetwork_py.sln`: Main Visual Studio solution. * `example.py`: Python script illustrating options configuration, model instantiation, callback monitoring, training, inference, and serialization. +* `import_check.py`: A lightweight validation script that verifies the binary module loads, initializes enums, and starts up correctly (used in CI). --- diff --git a/python/import_check.py b/python/import_check.py new file mode 100644 index 0000000..bb6490d --- /dev/null +++ b/python/import_check.py @@ -0,0 +1,32 @@ +import os +import sys + +# Add the directory containing the compiled .pyd module to the import search path +# The module is built in x64/Release by MSBuild in python/x64/Release/ +module_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "x64", "Release") +sys.path.append(module_dir) + +try: + import neuralnetwork as nn + print("Successfully imported neuralnetwork library!") + print(f"Docstring: {nn.__doc__}") + + # Check activation enum and class instantiation + print("Verifying ActivationMethod...") + sigmoid_method = nn.ActivationMethod.Sigmoid + print(f"Sigmoid enum value: {sigmoid_method}") + + print("Instantiating Activation class...") + act = nn.Activation(sigmoid_method, 1.0) + print(f"Activation instantiation success: method={act.method}, alpha={act.alpha}") + + # Verify Options + print("Verifying NeuralNetworkOptions creation...") + options = nn.NeuralNetworkOptions.create([3, 2, 1]) + print("NeuralNetworkOptions created successfully!") + + print("All checks passed successfully!") + sys.exit(0) +except Exception as e: + print(f"Verification failed: {e}", file=sys.stderr) + sys.exit(1)