Initial commit as Claude developed it
Some checks failed
CI/CD Pipeline / Code Linting (push) Failing after 22s
CI/CD Pipeline / Build and Test (Debug, clang, ubuntu-latest) (push) Failing after 5m44s
CI/CD Pipeline / Build and Test (Debug, gcc, ubuntu-latest) (push) Failing after 5m33s
CI/CD Pipeline / Build and Test (Release, clang, ubuntu-20.04) (push) Failing after 6m12s
CI/CD Pipeline / Build and Test (Release, clang, ubuntu-latest) (push) Failing after 5m13s
CI/CD Pipeline / Build and Test (Release, gcc, ubuntu-20.04) (push) Failing after 5m30s
CI/CD Pipeline / Build and Test (Release, gcc, ubuntu-latest) (push) Failing after 5m33s
CI/CD Pipeline / Docker Build Test (push) Failing after 13s
CI/CD Pipeline / Performance Benchmarks (push) Has been skipped
CI/CD Pipeline / Build Documentation (push) Successful in 31s
CI/CD Pipeline / Create Release Package (push) Has been skipped
Some checks failed
CI/CD Pipeline / Code Linting (push) Failing after 22s
CI/CD Pipeline / Build and Test (Debug, clang, ubuntu-latest) (push) Failing after 5m44s
CI/CD Pipeline / Build and Test (Debug, gcc, ubuntu-latest) (push) Failing after 5m33s
CI/CD Pipeline / Build and Test (Release, clang, ubuntu-20.04) (push) Failing after 6m12s
CI/CD Pipeline / Build and Test (Release, clang, ubuntu-latest) (push) Failing after 5m13s
CI/CD Pipeline / Build and Test (Release, gcc, ubuntu-20.04) (push) Failing after 5m30s
CI/CD Pipeline / Build and Test (Release, gcc, ubuntu-latest) (push) Failing after 5m33s
CI/CD Pipeline / Docker Build Test (push) Failing after 13s
CI/CD Pipeline / Performance Benchmarks (push) Has been skipped
CI/CD Pipeline / Build Documentation (push) Successful in 31s
CI/CD Pipeline / Create Release Package (push) Has been skipped
This commit is contained in:
195
include/svm_classifier/data_converter.hpp
Normal file
195
include/svm_classifier/data_converter.hpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <torch/torch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Forward declarations for libsvm and liblinear structures
|
||||
struct svm_node;
|
||||
struct svm_problem;
|
||||
struct feature_node;
|
||||
struct problem;
|
||||
|
||||
namespace svm_classifier {
|
||||
|
||||
/**
|
||||
* @brief Data converter between libtorch tensors and SVM library formats
|
||||
*
|
||||
* This class handles the conversion between PyTorch tensors and the data structures
|
||||
* required by libsvm and liblinear libraries. It manages memory allocation and
|
||||
* provides efficient conversion methods.
|
||||
*/
|
||||
class DataConverter {
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
DataConverter();
|
||||
|
||||
/**
|
||||
* @brief Destructor - cleans up allocated memory
|
||||
*/
|
||||
~DataConverter();
|
||||
|
||||
/**
|
||||
* @brief Convert PyTorch tensors to libsvm format
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,) - optional for prediction
|
||||
* @return Pointer to svm_problem structure
|
||||
*/
|
||||
std::unique_ptr<svm_problem> to_svm_problem(const torch::Tensor& X,
|
||||
const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Convert PyTorch tensors to liblinear format
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,) - optional for prediction
|
||||
* @return Pointer to problem structure
|
||||
*/
|
||||
std::unique_ptr<problem> to_linear_problem(const torch::Tensor& X,
|
||||
const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Convert single sample to libsvm format
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Pointer to svm_node array
|
||||
*/
|
||||
svm_node* to_svm_node(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert single sample to liblinear format
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Pointer to feature_node array
|
||||
*/
|
||||
feature_node* to_feature_node(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert predictions back to PyTorch tensor
|
||||
* @param predictions Vector of predictions
|
||||
* @return PyTorch tensor with predictions
|
||||
*/
|
||||
torch::Tensor from_predictions(const std::vector<double>& predictions);
|
||||
|
||||
/**
|
||||
* @brief Convert probabilities back to PyTorch tensor
|
||||
* @param probabilities 2D vector of class probabilities
|
||||
* @return PyTorch tensor with probabilities of shape (n_samples, n_classes)
|
||||
*/
|
||||
torch::Tensor from_probabilities(const std::vector<std::vector<double>>& probabilities);
|
||||
|
||||
/**
|
||||
* @brief Convert decision values back to PyTorch tensor
|
||||
* @param decision_values 2D vector of decision function values
|
||||
* @return PyTorch tensor with decision values
|
||||
*/
|
||||
torch::Tensor from_decision_values(const std::vector<std::vector<double>>& decision_values);
|
||||
|
||||
/**
|
||||
* @brief Validate input tensors
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor (optional)
|
||||
* @throws std::invalid_argument if tensors are invalid
|
||||
*/
|
||||
void validate_tensors(const torch::Tensor& X, const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Get number of features from last conversion
|
||||
* @return Number of features
|
||||
*/
|
||||
int get_n_features() const { return n_features_; }
|
||||
|
||||
/**
|
||||
* @brief Get number of samples from last conversion
|
||||
* @return Number of samples
|
||||
*/
|
||||
int get_n_samples() const { return n_samples_; }
|
||||
|
||||
/**
|
||||
* @brief Clean up all allocated memory
|
||||
*/
|
||||
void cleanup();
|
||||
|
||||
/**
|
||||
* @brief Set sparse threshold (features with absolute value below this are ignored)
|
||||
* @param threshold Sparse threshold (default: 1e-8)
|
||||
*/
|
||||
void set_sparse_threshold(double threshold) { sparse_threshold_ = threshold; }
|
||||
|
||||
/**
|
||||
* @brief Get sparse threshold
|
||||
* @return Current sparse threshold
|
||||
*/
|
||||
double get_sparse_threshold() const { return sparse_threshold_; }
|
||||
|
||||
private:
|
||||
int n_features_; ///< Number of features
|
||||
int n_samples_; ///< Number of samples
|
||||
double sparse_threshold_; ///< Threshold for sparse features
|
||||
|
||||
// Memory management for libsvm structures
|
||||
std::vector<std::vector<svm_node>> svm_nodes_storage_;
|
||||
std::vector<svm_node*> svm_x_space_;
|
||||
std::vector<double> svm_y_space_;
|
||||
|
||||
// Memory management for liblinear structures
|
||||
std::vector<std::vector<feature_node>> linear_nodes_storage_;
|
||||
std::vector<feature_node*> linear_x_space_;
|
||||
std::vector<double> linear_y_space_;
|
||||
|
||||
// Single sample storage (for prediction)
|
||||
std::vector<svm_node> single_svm_nodes_;
|
||||
std::vector<feature_node> single_linear_nodes_;
|
||||
|
||||
/**
|
||||
* @brief Convert tensor data to libsvm nodes for multiple samples
|
||||
* @param X Feature tensor
|
||||
* @return Vector of svm_node vectors
|
||||
*/
|
||||
std::vector<std::vector<svm_node>> tensor_to_svm_nodes(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Convert tensor data to liblinear nodes for multiple samples
|
||||
* @param X Feature tensor
|
||||
* @return Vector of feature_node vectors
|
||||
*/
|
||||
std::vector<std::vector<feature_node>> tensor_to_linear_nodes(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Convert single tensor sample to svm_node vector
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Vector of svm_node structures
|
||||
*/
|
||||
std::vector<svm_node> sample_to_svm_nodes(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert single tensor sample to feature_node vector
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Vector of feature_node structures
|
||||
*/
|
||||
std::vector<feature_node> sample_to_linear_nodes(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Extract labels from target tensor
|
||||
* @param y Target tensor
|
||||
* @return Vector of double labels
|
||||
*/
|
||||
std::vector<double> extract_labels(const torch::Tensor& y);
|
||||
|
||||
/**
|
||||
* @brief Check if tensor is on CPU and convert if necessary
|
||||
* @param tensor Input tensor
|
||||
* @return Tensor guaranteed to be on CPU
|
||||
*/
|
||||
torch::Tensor ensure_cpu_tensor(const torch::Tensor& tensor);
|
||||
|
||||
/**
|
||||
* @brief Validate tensor dimensions and data type
|
||||
* @param tensor Tensor to validate
|
||||
* @param expected_dims Expected number of dimensions
|
||||
* @param name Tensor name for error messages
|
||||
*/
|
||||
void validate_tensor_properties(const torch::Tensor& tensor, int expected_dims, const std::string& name);
|
||||
};
|
||||
|
||||
} // namespace svm_classifier
|
195
include/svm_classifier/kernel_parameters.hpp
Normal file
195
include/svm_classifier/kernel_parameters.hpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <torch/torch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Forward declarations for libsvm and liblinear structures
|
||||
struct svm_node;
|
||||
struct svm_problem;
|
||||
struct feature_node;
|
||||
struct problem;
|
||||
|
||||
namespace svm_classifier {
|
||||
|
||||
/**
|
||||
* @brief Data converter between libtorch tensors and SVM library formats
|
||||
*
|
||||
* This class handles the conversion between PyTorch tensors and the data structures
|
||||
* required by libsvm and liblinear libraries. It manages memory allocation and
|
||||
* provides efficient conversion methods.
|
||||
*/
|
||||
class DataConverter {
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
DataConverter();
|
||||
|
||||
/**
|
||||
* @brief Destructor - cleans up allocated memory
|
||||
*/
|
||||
~DataConverter();
|
||||
|
||||
/**
|
||||
* @brief Convert PyTorch tensors to libsvm format
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,) - optional for prediction
|
||||
* @return Pointer to svm_problem structure
|
||||
*/
|
||||
std::unique_ptr<svm_problem> to_svm_problem(const torch::Tensor& X,
|
||||
const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Convert PyTorch tensors to liblinear format
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,) - optional for prediction
|
||||
* @return Pointer to problem structure
|
||||
*/
|
||||
std::unique_ptr<problem> to_linear_problem(const torch::Tensor& X,
|
||||
const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Convert single sample to libsvm format
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Pointer to svm_node array
|
||||
*/
|
||||
svm_node* to_svm_node(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert single sample to liblinear format
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Pointer to feature_node array
|
||||
*/
|
||||
feature_node* to_feature_node(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert predictions back to PyTorch tensor
|
||||
* @param predictions Vector of predictions
|
||||
* @return PyTorch tensor with predictions
|
||||
*/
|
||||
torch::Tensor from_predictions(const std::vector<double>& predictions);
|
||||
|
||||
/**
|
||||
* @brief Convert probabilities back to PyTorch tensor
|
||||
* @param probabilities 2D vector of class probabilities
|
||||
* @return PyTorch tensor with probabilities of shape (n_samples, n_classes)
|
||||
*/
|
||||
torch::Tensor from_probabilities(const std::vector<std::vector<double>>& probabilities);
|
||||
|
||||
/**
|
||||
* @brief Convert decision values back to PyTorch tensor
|
||||
* @param decision_values 2D vector of decision function values
|
||||
* @return PyTorch tensor with decision values
|
||||
*/
|
||||
torch::Tensor from_decision_values(const std::vector<std::vector<double>>& decision_values);
|
||||
|
||||
/**
|
||||
* @brief Validate input tensors
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor (optional)
|
||||
* @throws std::invalid_argument if tensors are invalid
|
||||
*/
|
||||
void validate_tensors(const torch::Tensor& X, const torch::Tensor& y = torch::Tensor());
|
||||
|
||||
/**
|
||||
* @brief Get number of features from last conversion
|
||||
* @return Number of features
|
||||
*/
|
||||
int get_n_features() const { return n_features_; }
|
||||
|
||||
/**
|
||||
* @brief Get number of samples from last conversion
|
||||
* @return Number of samples
|
||||
*/
|
||||
int get_n_samples() const { return n_samples_; }
|
||||
|
||||
/**
|
||||
* @brief Clean up all allocated memory
|
||||
*/
|
||||
void cleanup();
|
||||
|
||||
/**
|
||||
* @brief Set sparse threshold (features with absolute value below this are ignored)
|
||||
* @param threshold Sparse threshold (default: 1e-8)
|
||||
*/
|
||||
void set_sparse_threshold(double threshold) { sparse_threshold_ = threshold; }
|
||||
|
||||
/**
|
||||
* @brief Get sparse threshold
|
||||
* @return Current sparse threshold
|
||||
*/
|
||||
double get_sparse_threshold() const { return sparse_threshold_; }
|
||||
|
||||
private:
|
||||
int n_features_; ///< Number of features
|
||||
int n_samples_; ///< Number of samples
|
||||
double sparse_threshold_; ///< Threshold for sparse features
|
||||
|
||||
// Memory management for libsvm structures
|
||||
std::vector<std::vector<svm_node>> svm_nodes_storage_;
|
||||
std::vector<svm_node*> svm_x_space_;
|
||||
std::vector<double> svm_y_space_;
|
||||
|
||||
// Memory management for liblinear structures
|
||||
std::vector<std::vector<feature_node>> linear_nodes_storage_;
|
||||
std::vector<feature_node*> linear_x_space_;
|
||||
std::vector<double> linear_y_space_;
|
||||
|
||||
// Single sample storage (for prediction)
|
||||
std::vector<svm_node> single_svm_nodes_;
|
||||
std::vector<feature_node> single_linear_nodes_;
|
||||
|
||||
/**
|
||||
* @brief Convert tensor data to libsvm nodes for multiple samples
|
||||
* @param X Feature tensor
|
||||
* @return Vector of svm_node vectors
|
||||
*/
|
||||
std::vector<std::vector<svm_node>> tensor_to_svm_nodes(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Convert tensor data to liblinear nodes for multiple samples
|
||||
* @param X Feature tensor
|
||||
* @return Vector of feature_node vectors
|
||||
*/
|
||||
std::vector<std::vector<feature_node>> tensor_to_linear_nodes(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Convert single tensor sample to svm_node vector
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Vector of svm_node structures
|
||||
*/
|
||||
std::vector<svm_node> sample_to_svm_nodes(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Convert single tensor sample to feature_node vector
|
||||
* @param sample Feature tensor of shape (n_features,)
|
||||
* @return Vector of feature_node structures
|
||||
*/
|
||||
std::vector<feature_node> sample_to_linear_nodes(const torch::Tensor& sample);
|
||||
|
||||
/**
|
||||
* @brief Extract labels from target tensor
|
||||
* @param y Target tensor
|
||||
* @return Vector of double labels
|
||||
*/
|
||||
std::vector<double> extract_labels(const torch::Tensor& y);
|
||||
|
||||
/**
|
||||
* @brief Check if tensor is on CPU and convert if necessary
|
||||
* @param tensor Input tensor
|
||||
* @return Tensor guaranteed to be on CPU
|
||||
*/
|
||||
torch::Tensor ensure_cpu_tensor(const torch::Tensor& tensor);
|
||||
|
||||
/**
|
||||
* @brief Validate tensor dimensions and data type
|
||||
* @param tensor Tensor to validate
|
||||
* @param expected_dims Expected number of dimensions
|
||||
* @param name Tensor name for error messages
|
||||
*/
|
||||
void validate_tensor_properties(const torch::Tensor& tensor, int expected_dims, const std::string& name);
|
||||
};
|
||||
|
||||
} // namespace svm_classifier
|
264
include/svm_classifier/multiclass_strategy.hpp
Normal file
264
include/svm_classifier/multiclass_strategy.hpp
Normal file
@@ -0,0 +1,264 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include "kernel_parameters.hpp"
|
||||
#include "data_converter.hpp"
|
||||
#include <torch/torch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
// Forward declarations
|
||||
struct svm_model;
|
||||
struct model;
|
||||
|
||||
namespace svm_classifier {
|
||||
|
||||
/**
|
||||
* @brief Abstract base class for multiclass classification strategies
|
||||
*/
|
||||
class MulticlassStrategyBase {
|
||||
public:
|
||||
/**
|
||||
* @brief Virtual destructor
|
||||
*/
|
||||
virtual ~MulticlassStrategyBase() = default;
|
||||
|
||||
/**
|
||||
* @brief Train the multiclass classifier
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,)
|
||||
* @param params Kernel parameters
|
||||
* @param converter Data converter instance
|
||||
* @return Training metrics
|
||||
*/
|
||||
virtual TrainingMetrics fit(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
const KernelParameters& params,
|
||||
DataConverter& converter) = 0;
|
||||
|
||||
/**
|
||||
* @brief Predict class labels
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param converter Data converter instance
|
||||
* @return Predicted class labels
|
||||
*/
|
||||
virtual std::vector<int> predict(const torch::Tensor& X,
|
||||
DataConverter& converter) = 0;
|
||||
|
||||
/**
|
||||
* @brief Predict class probabilities
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param converter Data converter instance
|
||||
* @return Class probabilities for each sample
|
||||
*/
|
||||
virtual std::vector<std::vector<double>> predict_proba(const torch::Tensor& X,
|
||||
DataConverter& converter) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get decision function values
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param converter Data converter instance
|
||||
* @return Decision function values
|
||||
*/
|
||||
virtual std::vector<std::vector<double>> decision_function(const torch::Tensor& X,
|
||||
DataConverter& converter) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get unique class labels
|
||||
* @return Vector of unique class labels
|
||||
*/
|
||||
virtual std::vector<int> get_classes() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the model supports probability prediction
|
||||
* @return True if probabilities are supported
|
||||
*/
|
||||
virtual bool supports_probability() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get number of classes
|
||||
* @return Number of classes
|
||||
*/
|
||||
virtual int get_n_classes() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get strategy type
|
||||
* @return Multiclass strategy type
|
||||
*/
|
||||
virtual MulticlassStrategy get_strategy_type() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<int> classes_; ///< Unique class labels
|
||||
bool is_trained_ = false; ///< Whether the model is trained
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief One-vs-Rest (OvR) multiclass strategy
|
||||
*/
|
||||
class OneVsRestStrategy : public MulticlassStrategyBase {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
OneVsRestStrategy();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~OneVsRestStrategy() override;
|
||||
|
||||
TrainingMetrics fit(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
const KernelParameters& params,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<int> predict(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<std::vector<double>> predict_proba(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<std::vector<double>> decision_function(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<int> get_classes() const override { return classes_; }
|
||||
|
||||
bool supports_probability() const override;
|
||||
|
||||
int get_n_classes() const override { return static_cast<int>(classes_.size()); }
|
||||
|
||||
MulticlassStrategy get_strategy_type() const override { return MulticlassStrategy::ONE_VS_REST; }
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<svm_model>> svm_models_; ///< SVM models (one per class)
|
||||
std::vector<std::unique_ptr<model>> linear_models_; ///< Linear models (one per class)
|
||||
KernelParameters params_; ///< Stored parameters
|
||||
SVMLibrary library_type_; ///< Which library is being used
|
||||
|
||||
/**
|
||||
* @brief Create binary labels for one-vs-rest
|
||||
* @param y Original labels
|
||||
* @param positive_class Positive class label
|
||||
* @return Binary labels (+1 for positive class, -1 for others)
|
||||
*/
|
||||
torch::Tensor create_binary_labels(const torch::Tensor& y, int positive_class);
|
||||
|
||||
/**
|
||||
* @brief Train a single binary classifier
|
||||
* @param X Feature tensor
|
||||
* @param y_binary Binary labels
|
||||
* @param params Kernel parameters
|
||||
* @param converter Data converter
|
||||
* @param class_idx Index of the class being trained
|
||||
* @return Training time for this classifier
|
||||
*/
|
||||
double train_binary_classifier(const torch::Tensor& X,
|
||||
const torch::Tensor& y_binary,
|
||||
const KernelParameters& params,
|
||||
DataConverter& converter,
|
||||
int class_idx);
|
||||
|
||||
/**
|
||||
* @brief Clean up all models
|
||||
*/
|
||||
void cleanup_models();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief One-vs-One (OvO) multiclass strategy
|
||||
*/
|
||||
class OneVsOneStrategy : public MulticlassStrategyBase {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
OneVsOneStrategy();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~OneVsOneStrategy() override;
|
||||
|
||||
TrainingMetrics fit(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
const KernelParameters& params,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<int> predict(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<std::vector<double>> predict_proba(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<std::vector<double>> decision_function(const torch::Tensor& X,
|
||||
DataConverter& converter) override;
|
||||
|
||||
std::vector<int> get_classes() const override { return classes_; }
|
||||
|
||||
bool supports_probability() const override;
|
||||
|
||||
int get_n_classes() const override { return static_cast<int>(classes_.size()); }
|
||||
|
||||
MulticlassStrategy get_strategy_type() const override { return MulticlassStrategy::ONE_VS_ONE; }
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<svm_model>> svm_models_; ///< SVM models (one per pair)
|
||||
std::vector<std::unique_ptr<model>> linear_models_; ///< Linear models (one per pair)
|
||||
std::vector<std::pair<int, int>> class_pairs_; ///< Class pairs for each model
|
||||
KernelParameters params_; ///< Stored parameters
|
||||
SVMLibrary library_type_; ///< Which library is being used
|
||||
|
||||
/**
|
||||
* @brief Extract samples for a specific class pair
|
||||
* @param X Feature tensor
|
||||
* @param y Label tensor
|
||||
* @param class1 First class
|
||||
* @param class2 Second class
|
||||
* @return Pair of (filtered_X, filtered_y)
|
||||
*/
|
||||
std::pair<torch::Tensor, torch::Tensor> extract_binary_data(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
int class1,
|
||||
int class2);
|
||||
|
||||
/**
|
||||
* @brief Train a single pairwise classifier
|
||||
* @param X Feature tensor
|
||||
* @param y Labels
|
||||
* @param class1 First class
|
||||
* @param class2 Second class
|
||||
* @param params Kernel parameters
|
||||
* @param converter Data converter
|
||||
* @param model_idx Index of the model being trained
|
||||
* @return Training time for this classifier
|
||||
*/
|
||||
double train_pairwise_classifier(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
int class1,
|
||||
int class2,
|
||||
const KernelParameters& params,
|
||||
DataConverter& converter,
|
||||
int model_idx);
|
||||
|
||||
/**
|
||||
* @brief Voting mechanism for OvO predictions
|
||||
* @param decisions Matrix of pairwise decisions
|
||||
* @return Predicted class for each sample
|
||||
*/
|
||||
std::vector<int> vote_predictions(const std::vector<std::vector<double>>& decisions);
|
||||
|
||||
/**
|
||||
* @brief Clean up all models
|
||||
*/
|
||||
void cleanup_models();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory function to create multiclass strategy
|
||||
* @param strategy Strategy type
|
||||
* @return Unique pointer to multiclass strategy
|
||||
*/
|
||||
std::unique_ptr<MulticlassStrategyBase> create_multiclass_strategy(MulticlassStrategy strategy);
|
||||
|
||||
} // namespace svm_classifier
|
297
include/svm_classifier/svm_classifier.hpp
Normal file
297
include/svm_classifier/svm_classifier.hpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include "kernel_parameters.hpp"
|
||||
#include "data_converter.hpp"
|
||||
#include "multiclass_strategy.hpp"
|
||||
#include <torch/torch.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace svm_classifier {
|
||||
|
||||
/**
|
||||
* @brief Support Vector Machine Classifier with scikit-learn compatible API
|
||||
*
|
||||
* This class provides a unified interface for SVM classification using both
|
||||
* liblinear (for linear kernels) and libsvm (for non-linear kernels).
|
||||
* It supports multiclass classification through One-vs-Rest and One-vs-One strategies.
|
||||
*/
|
||||
class SVMClassifier {
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor with default parameters
|
||||
*/
|
||||
SVMClassifier();
|
||||
|
||||
/**
|
||||
* @brief Constructor with JSON parameters
|
||||
* @param config JSON configuration object
|
||||
*/
|
||||
explicit SVMClassifier(const nlohmann::json& config);
|
||||
|
||||
/**
|
||||
* @brief Constructor with explicit parameters
|
||||
* @param kernel Kernel type
|
||||
* @param C Regularization parameter
|
||||
* @param multiclass_strategy Multiclass strategy
|
||||
*/
|
||||
SVMClassifier(KernelType kernel,
|
||||
double C = 1.0,
|
||||
MulticlassStrategy multiclass_strategy = MulticlassStrategy::ONE_VS_REST);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~SVMClassifier();
|
||||
|
||||
/**
|
||||
* @brief Copy constructor (deleted - models are not copyable)
|
||||
*/
|
||||
SVMClassifier(const SVMClassifier&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment (deleted - models are not copyable)
|
||||
*/
|
||||
SVMClassifier& operator=(const SVMClassifier&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
SVMClassifier(SVMClassifier&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Move assignment
|
||||
*/
|
||||
SVMClassifier& operator=(SVMClassifier&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Train the SVM classifier
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y Target tensor of shape (n_samples,) with class labels
|
||||
* @return Training metrics
|
||||
* @throws std::invalid_argument if input data is invalid
|
||||
* @throws std::runtime_error if training fails
|
||||
*/
|
||||
TrainingMetrics fit(const torch::Tensor& X, const torch::Tensor& y);
|
||||
|
||||
/**
|
||||
* @brief Predict class labels for samples
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @return Tensor of predicted class labels
|
||||
* @throws std::runtime_error if model is not fitted
|
||||
*/
|
||||
torch::Tensor predict(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Predict class probabilities for samples
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @return Tensor of shape (n_samples, n_classes) with class probabilities
|
||||
* @throws std::runtime_error if model is not fitted or doesn't support probabilities
|
||||
*/
|
||||
torch::Tensor predict_proba(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Get decision function values
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @return Tensor with decision function values
|
||||
* @throws std::runtime_error if model is not fitted
|
||||
*/
|
||||
torch::Tensor decision_function(const torch::Tensor& X);
|
||||
|
||||
/**
|
||||
* @brief Calculate accuracy score on test data
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y_true True labels tensor of shape (n_samples,)
|
||||
* @return Accuracy score (fraction of correctly predicted samples)
|
||||
* @throws std::runtime_error if model is not fitted
|
||||
*/
|
||||
double score(const torch::Tensor& X, const torch::Tensor& y_true);
|
||||
|
||||
/**
|
||||
* @brief Calculate detailed evaluation metrics
|
||||
* @param X Feature tensor of shape (n_samples, n_features)
|
||||
* @param y_true True labels tensor of shape (n_samples,)
|
||||
* @return Evaluation metrics including precision, recall, F1-score
|
||||
*/
|
||||
EvaluationMetrics evaluate(const torch::Tensor& X, const torch::Tensor& y_true);
|
||||
|
||||
/**
|
||||
* @brief Set parameters from JSON configuration
|
||||
* @param config JSON configuration object
|
||||
* @throws std::invalid_argument if parameters are invalid
|
||||
*/
|
||||
void set_parameters(const nlohmann::json& config);
|
||||
|
||||
/**
|
||||
* @brief Get current parameters as JSON
|
||||
* @return JSON object with current parameters
|
||||
*/
|
||||
nlohmann::json get_parameters() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the model is fitted/trained
|
||||
* @return True if model is fitted
|
||||
*/
|
||||
bool is_fitted() const { return is_fitted_; }
|
||||
|
||||
/**
|
||||
* @brief Get the number of classes
|
||||
* @return Number of classes (0 if not fitted)
|
||||
*/
|
||||
int get_n_classes() const;
|
||||
|
||||
/**
|
||||
* @brief Get unique class labels
|
||||
* @return Vector of unique class labels
|
||||
*/
|
||||
std::vector<int> get_classes() const;
|
||||
|
||||
/**
|
||||
* @brief Get the number of features
|
||||
* @return Number of features (0 if not fitted)
|
||||
*/
|
||||
int get_n_features() const { return n_features_; }
|
||||
|
||||
/**
|
||||
* @brief Get training metrics from last fit
|
||||
* @return Training metrics
|
||||
*/
|
||||
TrainingMetrics get_training_metrics() const { return training_metrics_; }
|
||||
|
||||
/**
|
||||
* @brief Check if the current model supports probability prediction
|
||||
* @return True if probabilities are supported
|
||||
*/
|
||||
bool supports_probability() const;
|
||||
|
||||
/**
|
||||
* @brief Save model to file
|
||||
* @param filename Path to save the model
|
||||
* @throws std::runtime_error if saving fails
|
||||
*/
|
||||
void save_model(const std::string& filename) const;
|
||||
|
||||
/**
|
||||
* @brief Load model from file
|
||||
* @param filename Path to load the model from
|
||||
* @throws std::runtime_error if loading fails
|
||||
*/
|
||||
void load_model(const std::string& filename);
|
||||
|
||||
/**
|
||||
* @brief Get kernel type
|
||||
* @return Current kernel type
|
||||
*/
|
||||
KernelType get_kernel_type() const { return params_.get_kernel_type(); }
|
||||
|
||||
/**
|
||||
* @brief Get multiclass strategy
|
||||
* @return Current multiclass strategy
|
||||
*/
|
||||
MulticlassStrategy get_multiclass_strategy() const { return params_.get_multiclass_strategy(); }
|
||||
|
||||
/**
|
||||
* @brief Get SVM library being used
|
||||
* @return SVM library type
|
||||
*/
|
||||
SVMLibrary get_svm_library() const { return get_svm_library(params_.get_kernel_type()); }
|
||||
|
||||
/**
|
||||
* @brief Perform cross-validation
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor
|
||||
* @param cv Number of folds (default: 5)
|
||||
* @return Cross-validation scores for each fold
|
||||
*/
|
||||
std::vector<double> cross_validate(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
int cv = 5);
|
||||
|
||||
/**
|
||||
* @brief Find optimal hyperparameters using grid search
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor
|
||||
* @param param_grid JSON object with parameter grid
|
||||
* @param cv Number of cross-validation folds
|
||||
* @return JSON object with best parameters and score
|
||||
*/
|
||||
nlohmann::json grid_search(const torch::Tensor& X,
|
||||
const torch::Tensor& y,
|
||||
const nlohmann::json& param_grid,
|
||||
int cv = 5);
|
||||
|
||||
/**
|
||||
* @brief Get feature importance (for linear kernels only)
|
||||
* @return Tensor with feature weights/importance
|
||||
* @throws std::runtime_error if not supported for current kernel
|
||||
*/
|
||||
torch::Tensor get_feature_importance() const;
|
||||
|
||||
/**
|
||||
* @brief Reset the classifier (clear trained model)
|
||||
*/
|
||||
void reset();
|
||||
|
||||
private:
|
||||
KernelParameters params_; ///< Model parameters
|
||||
std::unique_ptr<MulticlassStrategyBase> multiclass_strategy_; ///< Multiclass strategy
|
||||
std::unique_ptr<DataConverter> data_converter_; ///< Data converter
|
||||
|
||||
bool is_fitted_; ///< Whether model is fitted
|
||||
int n_features_; ///< Number of features
|
||||
TrainingMetrics training_metrics_; ///< Last training metrics
|
||||
|
||||
/**
|
||||
* @brief Validate input data
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor (optional)
|
||||
* @param check_fitted Whether to check if model is fitted
|
||||
*/
|
||||
void validate_input(const torch::Tensor& X,
|
||||
const torch::Tensor& y = torch::Tensor(),
|
||||
bool check_fitted = false);
|
||||
|
||||
/**
|
||||
* @brief Initialize multiclass strategy based on current parameters
|
||||
*/
|
||||
void initialize_multiclass_strategy();
|
||||
|
||||
/**
|
||||
* @brief Calculate confusion matrix
|
||||
* @param y_true True labels
|
||||
* @param y_pred Predicted labels
|
||||
* @return Confusion matrix
|
||||
*/
|
||||
std::vector<std::vector<int>> calculate_confusion_matrix(const std::vector<int>& y_true,
|
||||
const std::vector<int>& y_pred);
|
||||
|
||||
/**
|
||||
* @brief Calculate precision, recall, and F1-score from confusion matrix
|
||||
* @param confusion_matrix Confusion matrix
|
||||
* @return Tuple of (precision, recall, f1_score)
|
||||
*/
|
||||
std::tuple<double, double, double> calculate_metrics_from_confusion_matrix(
|
||||
const std::vector<std::vector<int>>& confusion_matrix);
|
||||
|
||||
/**
|
||||
* @brief Split data for cross-validation
|
||||
* @param X Feature tensor
|
||||
* @param y Target tensor
|
||||
* @param fold Current fold
|
||||
* @param n_folds Total number of folds
|
||||
* @return Tuple of (X_train, y_train, X_val, y_val)
|
||||
*/
|
||||
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor>
|
||||
split_for_cv(const torch::Tensor& X, const torch::Tensor& y, int fold, int n_folds);
|
||||
|
||||
/**
|
||||
* @brief Generate parameter combinations for grid search
|
||||
* @param param_grid JSON parameter grid
|
||||
* @return Vector of parameter combinations
|
||||
*/
|
||||
std::vector<nlohmann::json> generate_param_combinations(const nlohmann::json& param_grid);
|
||||
};
|
||||
|
||||
} // namespace svm_classifier
|
138
include/svm_classifier/types.hpp
Normal file
138
include/svm_classifier/types.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace svm_classifier {
|
||||
|
||||
/**
|
||||
* @brief Supported kernel types
|
||||
*/
|
||||
enum class KernelType {
|
||||
LINEAR, ///< Linear kernel: <x, y>
|
||||
RBF, ///< Radial Basis Function: exp(-gamma * ||x - y||^2)
|
||||
POLYNOMIAL, ///< Polynomial: (gamma * <x, y> + coef0)^degree
|
||||
SIGMOID ///< Sigmoid: tanh(gamma * <x, y> + coef0)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Multiclass classification strategies
|
||||
*/
|
||||
enum class MulticlassStrategy {
|
||||
ONE_VS_REST, ///< One-vs-Rest (OvR) strategy
|
||||
ONE_VS_ONE ///< One-vs-One (OvO) strategy
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SVM library type selection
|
||||
*/
|
||||
enum class SVMLibrary {
|
||||
LIBLINEAR, ///< Use liblinear (for linear kernels)
|
||||
LIBSVM ///< Use libsvm (for non-linear kernels)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Training result status
|
||||
*/
|
||||
enum class TrainingStatus {
|
||||
SUCCESS,
|
||||
INVALID_PARAMETERS,
|
||||
INSUFFICIENT_DATA,
|
||||
MEMORY_ERROR,
|
||||
CONVERGENCE_ERROR
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Prediction result structure
|
||||
*/
|
||||
struct PredictionResult {
|
||||
std::vector<int> predictions; ///< Predicted class labels
|
||||
std::vector<std::vector<double>> probabilities; ///< Class probabilities (if available)
|
||||
std::vector<std::vector<double>> decision_values; ///< Decision function values
|
||||
bool has_probabilities = false; ///< Whether probabilities are available
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Training metrics structure
|
||||
*/
|
||||
struct TrainingMetrics {
|
||||
double training_time = 0.0; ///< Training time in seconds
|
||||
int support_vectors = 0; ///< Number of support vectors
|
||||
int iterations = 0; ///< Number of iterations
|
||||
double objective_value = 0.0; ///< Final objective value
|
||||
TrainingStatus status = TrainingStatus::SUCCESS;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Model evaluation metrics
|
||||
*/
|
||||
struct EvaluationMetrics {
|
||||
double accuracy = 0.0; ///< Classification accuracy
|
||||
double precision = 0.0; ///< Macro-averaged precision
|
||||
double recall = 0.0; ///< Macro-averaged recall
|
||||
double f1_score = 0.0; ///< Macro-averaged F1-score
|
||||
std::vector<std::vector<int>> confusion_matrix; ///< Confusion matrix
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert kernel type to string
|
||||
*/
|
||||
inline std::string kernel_type_to_string(KernelType kernel)
|
||||
{
|
||||
switch (kernel) {
|
||||
case KernelType::LINEAR: return "linear";
|
||||
case KernelType::RBF: return "rbf";
|
||||
case KernelType::POLYNOMIAL: return "polynomial";
|
||||
case KernelType::SIGMOID: return "sigmoid";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert string to kernel type
|
||||
*/
|
||||
inline KernelType string_to_kernel_type(const std::string& kernel_str)
|
||||
{
|
||||
if (kernel_str == "linear") return KernelType::LINEAR;
|
||||
if (kernel_str == "rbf") return KernelType::RBF;
|
||||
if (kernel_str == "polynomial" || kernel_str == "poly") return KernelType::POLYNOMIAL;
|
||||
if (kernel_str == "sigmoid") return KernelType::SIGMOID;
|
||||
throw std::invalid_argument("Unknown kernel type: " + kernel_str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert multiclass strategy to string
|
||||
*/
|
||||
inline std::string multiclass_strategy_to_string(MulticlassStrategy strategy)
|
||||
{
|
||||
switch (strategy) {
|
||||
case MulticlassStrategy::ONE_VS_REST: return "ovr";
|
||||
case MulticlassStrategy::ONE_VS_ONE: return "ovo";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert string to multiclass strategy
|
||||
*/
|
||||
inline MulticlassStrategy string_to_multiclass_strategy(const std::string& strategy_str)
|
||||
{
|
||||
if (strategy_str == "ovr" || strategy_str == "one_vs_rest") {
|
||||
return MulticlassStrategy::ONE_VS_REST;
|
||||
}
|
||||
if (strategy_str == "ovo" || strategy_str == "one_vs_one") {
|
||||
return MulticlassStrategy::ONE_VS_ONE;
|
||||
}
|
||||
throw std::invalid_argument("Unknown multiclass strategy: " + strategy_str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine which SVM library to use based on kernel type
|
||||
*/
|
||||
inline SVMLibrary get_svm_library(KernelType kernel)
|
||||
{
|
||||
return (kernel == KernelType::LINEAR) ? SVMLibrary::LIBLINEAR : SVMLibrary::LIBSVM;
|
||||
}
|
||||
|
||||
} // namespace svm_classifier
|
Reference in New Issue
Block a user