#ifndef NISPS_IML_IMPL_HPP #define NISPS_IML_IMPL_HPP #include #include namespace nisps { template IML::IML(size_t n_inputs, size_t n_outputs, std::vector hidden_layers, size_t max_iterations, Float learning_rate, Float convergence_threshold) : n_inputs_(n_inputs) , n_outputs_(n_outputs) , max_iterations_(max_iterations) , learning_rate_(learning_rate) , convergence_threshold_(convergence_threshold) { // Build layer sizes: input + hidden + output const size_t kBias = 1; std::vector layer_sizes; layer_sizes.push_back(n_inputs + kBias); for (size_t h : hidden_layers) { layer_sizes.push_back(h); } layer_sizes.push_back(n_outputs); // Activation functions: RELU for hidden, SIGMOID for output std::vector activations; for (size_t i = 0; i < hidden_layers.size(); ++i) { activations.push_back(RELU); } activations.push_back(SIGMOID); dataset_ = std::make_unique(); mlp_ = std::make_unique>( layer_sizes, activations, loss::LOSS_MSE, false, // use_constant_weight_init 0.0f // constant_weight_init ); input_state_.resize(n_inputs, static_cast(0.5)); output_state_.resize(n_outputs, static_cast(0)); } template void IML::set_input(size_t index, Float value) { if (index >= n_inputs_) return; if (value < 0) value = 0; if (value > 1) value = 1; input_state_[index] = value; input_updated_ = true; } template void IML::set_inputs(const Float* values, size_t count) { for (size_t i = 0; i < count && i < n_inputs_; ++i) { set_input(i, values[i]); } } template const Float* IML::get_outputs() const { return output_state_.data(); } template void IML::set_output(size_t index, Float value) { if (index >= n_outputs_) return; if (value < 0) value = 0; if (value > 1) value = 1; output_state_[index] = value; } template void IML::set_outputs(const Float* values, size_t count) { for (size_t i = 0; i < count && i < n_outputs_; ++i) { set_output(i, values[i]); } } template void IML::process() { if (!perform_inference_ || !input_updated_) return; // Add bias term std::vector input_with_bias = input_state_; input_with_bias.push_back(static_cast(1.0)); // Run inference std::vector output(n_outputs_); mlp_->GetOutput(input_with_bias, &output); output_state_ = output; input_updated_ = false; } template void IML::set_mode(Mode mode) { if (mode == Mode::Inference && mode_ == Mode::Training) { train(); } mode_ = mode; } template void IML::save_example() { // First call: stop inference, user will position output if (perform_inference_) { perform_inference_ = false; log("Move to desired output position..."); return; } // Second call: store the example dataset_->Add(input_state_, output_state_); perform_inference_ = true; // Run inference with new example std::vector input_with_bias = input_state_; input_with_bias.push_back(static_cast(1.0)); std::vector output(n_outputs_); mlp_->GetOutput(input_with_bias, &output); output_state_ = output; log("Example saved."); } template void IML::add_example(const Float* inputs, size_t n_in, const Float* outputs, size_t n_out) { std::vector in_vec(inputs, inputs + std::min(n_in, n_inputs_)); in_vec.resize(n_inputs_, static_cast(0)); std::vector out_vec(outputs, outputs + std::min(n_out, n_outputs_)); out_vec.resize(n_outputs_, static_cast(0)); dataset_->Add(in_vec, out_vec); } template void IML::clear_dataset() { if (mode_ == Mode::Training) { dataset_->Clear(); log("Dataset cleared."); } } template void IML::randomise_weights() { if (mode_ == Mode::Training) { stored_weights_ = mlp_->GetWeights(); mlp_->DrawWeights(); weights_randomised_ = true; // Run inference to show effect std::vector input_with_bias = input_state_; input_with_bias.push_back(static_cast(1.0)); std::vector output(n_outputs_); mlp_->GetOutput(input_with_bias, &output); output_state_ = output; log("Weights randomised."); } } template void IML::randomise_weights(Float spread) { if (mode_ == Mode::Training) { stored_weights_ = mlp_->GetWeights(); mlp_->DrawWeightsSpread(spread); weights_randomised_ = true; // Run inference to show effect std::vector input_with_bias = input_state_; input_with_bias.push_back(static_cast(1.0)); std::vector output(n_outputs_); mlp_->GetOutput(input_with_bias, &output); output_state_ = output; log("Weights randomised (spread)."); } } template void IML::move_weights(Float speed, Float spread) { mlp_->MoveWeightsSpread(speed, spread); // Run inference to show effect of perturbation input_updated_ = true; process(); } template void IML::train() { // Restore weights if they were randomised if (weights_randomised_) { mlp_->SetWeights(stored_weights_); weights_randomised_ = false; } auto features = dataset_->GetFeatures(true); // with bias auto& labels = dataset_->GetLabels(); if (features.empty() || labels.empty()) { log("Empty dataset, skipping training."); return; } typename MLP::training_pair_t training_data(features, labels); log("Training..."); Float loss = mlp_->Train( training_data, learning_rate_, static_cast(max_iterations_), convergence_threshold_, false // output_log ); // Run inference after training std::vector input_with_bias = input_state_; input_with_bias.push_back(static_cast(1.0)); std::vector output(n_outputs_); mlp_->GetOutput(input_with_bias, &output); output_state_ = output; log("Training complete."); } // ── Serialization accessors ─────────────────────────────────────── template typename MLP::mlp_weights IML::get_weights() const { return mlp_->GetWeights(); } template void IML::set_weights(typename MLP::mlp_weights& weights) { mlp_->SetWeights(weights); } template size_t IML::get_example_count() const { Dataset::DatasetVector* feats; Dataset::DatasetVector* labels; const_cast(dataset_.get())->Fetch(feats, labels); return feats ? feats->size() : 0; } template size_t IML::get_max_examples() const { return Dataset::kMax_examples; } template std::vector> IML::get_example_features() const { auto feats = const_cast(dataset_.get())->GetFeatures(false); std::vector> result; result.reserve(feats.size()); for (auto& f : feats) { result.emplace_back(f.begin(), f.end()); } return result; } template std::vector> IML::get_example_labels() const { auto& labels = const_cast(dataset_.get())->GetLabels(); std::vector> result; result.reserve(labels.size()); for (auto& l : labels) { result.emplace_back(l.begin(), l.end()); } return result; } template void IML::load_examples(const std::vector>& features, const std::vector>& labels) { dataset_->Clear(); size_t count = std::min(features.size(), labels.size()); for (size_t i = 0; i < count; i++) { std::vector feat(features[i].begin(), features[i].end()); std::vector label(labels[i].begin(), labels[i].end()); dataset_->Add(feat, label); } } template Float IML::nearest_example_distance(const Float* input, size_t n_in) const { auto feats = const_cast(dataset_.get())->GetFeatures(false); if (feats.empty()) return static_cast(-1); Float minDist = std::numeric_limits::max(); size_t dims = std::min(n_in, n_inputs_); for (auto& f : feats) { Float dist = 0; for (size_t d = 0; d < dims && d < f.size(); d++) { Float diff = static_cast(f[d]) - input[d]; dist += diff * diff; } dist = std::sqrt(dist); if (dist < minDist) minDist = dist; } return minDist; } } // namespace nisps #endif // NISPS_IML_IMPL_HPP