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:
22
examples/CMakeLists.txt
Normal file
22
examples/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
# Examples CMakeLists.txt
|
||||
|
||||
# Basic usage example
|
||||
add_executable(basic_usage basic_usage.cpp)
|
||||
target_link_libraries(basic_usage PRIVATE svm_classifier)
|
||||
target_include_directories(basic_usage PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_features(basic_usage PRIVATE cxx_std_17)
|
||||
|
||||
# Advanced usage examples (can be added later)
|
||||
# add_executable(multiclass_example multiclass_example.cpp)
|
||||
# target_link_libraries(multiclass_example PRIVATE svm_classifier)
|
||||
|
||||
# add_executable(hyperparameter_tuning hyperparameter_tuning.cpp)
|
||||
# target_link_libraries(hyperparameter_tuning PRIVATE svm_classifier)
|
||||
|
||||
# add_executable(cross_validation_example cross_validation_example.cpp)
|
||||
# target_link_libraries(cross_validation_example PRIVATE svm_classifier)
|
||||
|
||||
# Installation of examples (optional)
|
||||
install(TARGETS basic_usage
|
||||
RUNTIME DESTINATION bin/examples
|
||||
)
|
512
examples/advanced_usage.cpp
Normal file
512
examples/advanced_usage.cpp
Normal file
@@ -0,0 +1,512 @@
|
||||
#include <svm_classifier/svm_classifier.hpp>
|
||||
#include <torch/torch.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace svm_classifier;
|
||||
using json = nlohmann::json;
|
||||
|
||||
/**
|
||||
* @brief Generate a more realistic multi-class dataset with noise
|
||||
*/
|
||||
std::pair<torch::Tensor, torch::Tensor> generate_realistic_dataset(int n_samples,
|
||||
int n_features,
|
||||
int n_classes,
|
||||
double noise_factor = 0.1)
|
||||
{
|
||||
torch::manual_seed(42);
|
||||
|
||||
// Create class centers
|
||||
auto centers = torch::randn({ n_classes, n_features }) * 3.0;
|
||||
|
||||
std::vector<torch::Tensor> class_data;
|
||||
std::vector<torch::Tensor> class_labels;
|
||||
|
||||
int samples_per_class = n_samples / n_classes;
|
||||
|
||||
for (int c = 0; c < n_classes; ++c) {
|
||||
// Generate samples around each class center
|
||||
auto class_samples = torch::randn({ samples_per_class, n_features }) * noise_factor;
|
||||
class_samples += centers[c].unsqueeze(0).expand({ samples_per_class, n_features });
|
||||
|
||||
auto labels = torch::full({ samples_per_class }, c, torch::kInt32);
|
||||
|
||||
class_data.push_back(class_samples);
|
||||
class_labels.push_back(labels);
|
||||
}
|
||||
|
||||
// Concatenate all classes
|
||||
auto X = torch::cat(class_data, 0);
|
||||
auto y = torch::cat(class_labels, 0);
|
||||
|
||||
// Shuffle the data
|
||||
auto indices = torch::randperm(X.size(0));
|
||||
X = X.index_select(0, indices);
|
||||
y = y.index_select(0, indices);
|
||||
|
||||
return { X, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize features to [0, 1] range
|
||||
*/
|
||||
torch::Tensor normalize_features(const torch::Tensor& X)
|
||||
{
|
||||
auto min_vals = std::get<0>(torch::min(X, 0));
|
||||
auto max_vals = std::get<0>(torch::max(X, 0));
|
||||
auto range = max_vals - min_vals;
|
||||
|
||||
// Avoid division by zero
|
||||
range = torch::where(range == 0.0, torch::ones_like(range), range);
|
||||
|
||||
return (X - min_vals) / range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Standardize features (zero mean, unit variance)
|
||||
*/
|
||||
torch::Tensor standardize_features(const torch::Tensor& X)
|
||||
{
|
||||
auto mean = X.mean(0);
|
||||
auto std = X.std(0);
|
||||
|
||||
// Avoid division by zero
|
||||
std = torch::where(std == 0.0, torch::ones_like(std), std);
|
||||
|
||||
return (X - mean) / std;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print detailed evaluation metrics
|
||||
*/
|
||||
void print_evaluation_metrics(const EvaluationMetrics& metrics, const std::string& title)
|
||||
{
|
||||
std::cout << "\n=== " << title << " ===" << std::endl;
|
||||
std::cout << std::fixed << std::setprecision(4);
|
||||
std::cout << "Accuracy: " << metrics.accuracy * 100 << "%" << std::endl;
|
||||
std::cout << "Precision: " << metrics.precision * 100 << "%" << std::endl;
|
||||
std::cout << "Recall: " << metrics.recall * 100 << "%" << std::endl;
|
||||
std::cout << "F1-Score: " << metrics.f1_score * 100 << "%" << std::endl;
|
||||
|
||||
std::cout << "\nConfusion Matrix:" << std::endl;
|
||||
for (size_t i = 0; i < metrics.confusion_matrix.size(); ++i) {
|
||||
for (size_t j = 0; j < metrics.confusion_matrix[i].size(); ++j) {
|
||||
std::cout << std::setw(6) << metrics.confusion_matrix[i][j] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate comprehensive hyperparameter tuning
|
||||
*/
|
||||
void demonstrate_hyperparameter_tuning()
|
||||
{
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "COMPREHENSIVE HYPERPARAMETER TUNING EXAMPLE" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
// Generate dataset
|
||||
auto [X, y] = generate_realistic_dataset(1000, 20, 4, 0.3);
|
||||
|
||||
// Standardize features
|
||||
X = standardize_features(X);
|
||||
|
||||
std::cout << "Dataset: " << X.size(0) << " samples, " << X.size(1)
|
||||
<< " features, " << torch::unique(y).size(0) << " classes" << std::endl;
|
||||
|
||||
// Define comprehensive parameter grid
|
||||
json param_grids = {
|
||||
{
|
||||
"name", "Linear SVM Grid"
|
||||
},
|
||||
{
|
||||
"parameters", {
|
||||
{"kernel", {"linear"}},
|
||||
{"C", {0.01, 0.1, 1.0, 10.0, 100.0}},
|
||||
{"multiclass_strategy", {"ovr", "ovo"}}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SVMClassifier svm;
|
||||
|
||||
std::cout << "\n--- Linear SVM Hyperparameter Search ---" << std::endl;
|
||||
auto linear_grid = param_grids["parameters"];
|
||||
auto linear_results = svm.grid_search(X, y, linear_grid, 5);
|
||||
|
||||
std::cout << "Best Linear SVM parameters:" << std::endl;
|
||||
std::cout << linear_results["best_params"].dump(2) << std::endl;
|
||||
std::cout << "Best CV score: " << std::fixed << std::setprecision(4)
|
||||
<< linear_results["best_score"].get<double>() * 100 << "%" << std::endl;
|
||||
|
||||
// RBF parameter grid
|
||||
json rbf_grid = {
|
||||
{"kernel", {"rbf"}},
|
||||
{"C", {0.1, 1.0, 10.0}},
|
||||
{"gamma", {0.01, 0.1, 1.0, "auto"}},
|
||||
{"multiclass_strategy", {"ovr", "ovo"}}
|
||||
};
|
||||
|
||||
std::cout << "\n--- RBF SVM Hyperparameter Search ---" << std::endl;
|
||||
auto rbf_results = svm.grid_search(X, y, rbf_grid, 3);
|
||||
|
||||
std::cout << "Best RBF SVM parameters:" << std::endl;
|
||||
std::cout << rbf_results["best_params"].dump(2) << std::endl;
|
||||
std::cout << "Best CV score: " << std::fixed << std::setprecision(4)
|
||||
<< rbf_results["best_score"].get<double>() * 100 << "%" << std::endl;
|
||||
|
||||
// Polynomial parameter grid
|
||||
json poly_grid = {
|
||||
{"kernel", {"polynomial"}},
|
||||
{"C", {0.1, 1.0, 10.0}},
|
||||
{"degree", {2, 3, 4}},
|
||||
{"gamma", {0.01, 0.1, "auto"}},
|
||||
{"coef0", {0.0, 1.0}}
|
||||
};
|
||||
|
||||
std::cout << "\n--- Polynomial SVM Hyperparameter Search ---" << std::endl;
|
||||
auto poly_results = svm.grid_search(X, y, poly_grid, 3);
|
||||
|
||||
std::cout << "Best Polynomial SVM parameters:" << std::endl;
|
||||
std::cout << poly_results["best_params"].dump(2) << std::endl;
|
||||
std::cout << "Best CV score: " << std::fixed << std::setprecision(4)
|
||||
<< poly_results["best_score"].get<double>() * 100 << "%" << std::endl;
|
||||
|
||||
// Compare all models
|
||||
std::cout << "\n--- Model Comparison Summary ---" << std::endl;
|
||||
std::cout << std::setw(15) << "Model" << std::setw(12) << "CV Score" << std::setw(30) << "Best Parameters" << std::endl;
|
||||
std::cout << std::string(57, '-') << std::endl;
|
||||
|
||||
std::cout << std::setw(15) << "Linear"
|
||||
<< std::setw(12) << std::fixed << std::setprecision(4)
|
||||
<< linear_results["best_score"].get<double>() * 100 << "%"
|
||||
<< std::setw(30) << "C=" + std::to_string(linear_results["best_params"]["C"].get<double>()) << std::endl;
|
||||
|
||||
std::cout << std::setw(15) << "RBF"
|
||||
<< std::setw(12) << std::fixed << std::setprecision(4)
|
||||
<< rbf_results["best_score"].get<double>() * 100 << "%"
|
||||
<< std::setw(30) << "C=" + std::to_string(rbf_results["best_params"]["C"].get<double>()) << std::endl;
|
||||
|
||||
std::cout << std::setw(15) << "Polynomial"
|
||||
<< std::setw(12) << std::fixed << std::setprecision(4)
|
||||
<< poly_results["best_score"].get<double>() * 100 << "%"
|
||||
<< std::setw(30) << "deg=" + std::to_string(rbf_results["best_params"]["degree"].get<int>()) << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate model evaluation and validation
|
||||
*/
|
||||
void demonstrate_model_evaluation()
|
||||
{
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "MODEL EVALUATION AND VALIDATION EXAMPLE" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
// Generate larger dataset for proper train/test split
|
||||
auto [X_full, y_full] = generate_realistic_dataset(2000, 15, 5, 0.2);
|
||||
|
||||
// Normalize features
|
||||
X_full = normalize_features(X_full);
|
||||
|
||||
// Train/test split (80/20)
|
||||
int n_train = static_cast<int>(X_full.size(0) * 0.8);
|
||||
auto X_train = X_full.slice(0, 0, n_train);
|
||||
auto y_train = y_full.slice(0, 0, n_train);
|
||||
auto X_test = X_full.slice(0, n_train);
|
||||
auto y_test = y_full.slice(0, n_train);
|
||||
|
||||
std::cout << "Dataset split:" << std::endl;
|
||||
std::cout << " Training: " << X_train.size(0) << " samples" << std::endl;
|
||||
std::cout << " Testing: " << X_test.size(0) << " samples" << std::endl;
|
||||
std::cout << " Features: " << X_train.size(1) << std::endl;
|
||||
std::cout << " Classes: " << torch::unique(y_train).size(0) << std::endl;
|
||||
|
||||
// Configure different models for comparison
|
||||
std::vector<json> model_configs = {
|
||||
{{"kernel", "linear"}, {"C", 1.0}, {"multiclass_strategy", "ovr"}},
|
||||
{{"kernel", "linear"}, {"C", 1.0}, {"multiclass_strategy", "ovo"}},
|
||||
{{"kernel", "rbf"}, {"C", 10.0}, {"gamma", 0.1}, {"multiclass_strategy", "ovr"}},
|
||||
{{"kernel", "rbf"}, {"C", 10.0}, {"gamma", 0.1}, {"multiclass_strategy", "ovo"}},
|
||||
{{"kernel", "polynomial"}, {"degree", 3}, {"C", 1.0}}
|
||||
};
|
||||
|
||||
std::vector<std::string> model_names = {
|
||||
"Linear (OvR)",
|
||||
"Linear (OvO)",
|
||||
"RBF (OvR)",
|
||||
"RBF (OvO)",
|
||||
"Polynomial"
|
||||
};
|
||||
|
||||
std::cout << "\n--- Training and Evaluating Models ---" << std::endl;
|
||||
|
||||
for (size_t i = 0; i < model_configs.size(); ++i) {
|
||||
std::cout << "\n" << std::string(40, '-') << std::endl;
|
||||
std::cout << "Model: " << model_names[i] << std::endl;
|
||||
std::cout << "Config: " << model_configs[i].dump() << std::endl;
|
||||
|
||||
SVMClassifier svm(model_configs[i]);
|
||||
|
||||
// Train the model
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
auto training_metrics = svm.fit(X_train, y_train);
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto training_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
std::cout << "Training time: " << training_duration.count() << " ms" << std::endl;
|
||||
|
||||
// Evaluate on training set
|
||||
auto train_metrics = svm.evaluate(X_train, y_train);
|
||||
print_evaluation_metrics(train_metrics, "Training Set Performance");
|
||||
|
||||
// Evaluate on test set
|
||||
auto test_metrics = svm.evaluate(X_test, y_test);
|
||||
print_evaluation_metrics(test_metrics, "Test Set Performance");
|
||||
|
||||
// Cross-validation
|
||||
std::cout << "\n--- Cross-Validation Results ---" << std::endl;
|
||||
auto cv_scores = svm.cross_validate(X_train, y_train, 5);
|
||||
|
||||
double mean_cv = 0.0;
|
||||
for (double score : cv_scores) {
|
||||
mean_cv += score;
|
||||
}
|
||||
mean_cv /= cv_scores.size();
|
||||
|
||||
double std_cv = 0.0;
|
||||
for (double score : cv_scores) {
|
||||
std_cv += (score - mean_cv) * (score - mean_cv);
|
||||
}
|
||||
std_cv = std::sqrt(std_cv / cv_scores.size());
|
||||
|
||||
std::cout << "CV Scores: ";
|
||||
for (size_t j = 0; j < cv_scores.size(); ++j) {
|
||||
std::cout << std::fixed << std::setprecision(3) << cv_scores[j];
|
||||
if (j < cv_scores.size() - 1) std::cout << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "Mean CV: " << std::fixed << std::setprecision(4) << mean_cv * 100 << "% ± " << std_cv * 100 << "%" << std::endl;
|
||||
|
||||
// Prediction analysis
|
||||
auto predictions = svm.predict(X_test);
|
||||
std::cout << "\n--- Prediction Analysis ---" << std::endl;
|
||||
|
||||
// Count predictions per class
|
||||
auto unique_preds = torch::unique(predictions);
|
||||
std::cout << "Predicted classes: ";
|
||||
for (int j = 0; j < unique_preds.size(0); ++j) {
|
||||
std::cout << unique_preds[j].item<int>();
|
||||
if (j < unique_preds.size(0) - 1) std::cout << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Test probability prediction if supported
|
||||
if (svm.supports_probability()) {
|
||||
std::cout << "Probability prediction: Supported" << std::endl;
|
||||
auto probabilities = svm.predict_proba(X_test.slice(0, 0, 5)); // First 5 samples
|
||||
std::cout << "Sample probabilities (first 5 samples):" << std::endl;
|
||||
for (int j = 0; j < 5; ++j) {
|
||||
std::cout << " Sample " << j << ": ";
|
||||
for (int k = 0; k < probabilities.size(1); ++k) {
|
||||
std::cout << std::fixed << std::setprecision(3)
|
||||
<< probabilities[j][k].item<double>();
|
||||
if (k < probabilities.size(1) - 1) std::cout << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "Probability prediction: Not supported" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate feature preprocessing effects
|
||||
*/
|
||||
void demonstrate_preprocessing_effects()
|
||||
{
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "FEATURE PREPROCESSING EFFECTS EXAMPLE" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
// Generate dataset with different feature scales
|
||||
auto [X_base, y] = generate_realistic_dataset(800, 10, 3, 0.15);
|
||||
|
||||
// Create features with different scales
|
||||
auto X_unscaled = X_base.clone();
|
||||
X_unscaled.slice(1, 0, 3) *= 100.0; // Features 0-2: large scale
|
||||
X_unscaled.slice(1, 3, 6) *= 0.01; // Features 3-5: small scale
|
||||
// Features 6-9: original scale
|
||||
|
||||
std::cout << "Original dataset statistics:" << std::endl;
|
||||
std::cout << " Min values: " << std::get<0>(torch::min(X_unscaled, 0)) << std::endl;
|
||||
std::cout << " Max values: " << std::get<0>(torch::max(X_unscaled, 0)) << std::endl;
|
||||
std::cout << " Mean values: " << X_unscaled.mean(0) << std::endl;
|
||||
std::cout << " Std values: " << X_unscaled.std(0) << std::endl;
|
||||
|
||||
// Test different preprocessing approaches
|
||||
std::vector<std::pair<std::string, torch::Tensor>> preprocessing_methods = {
|
||||
{"No Preprocessing", X_unscaled},
|
||||
{"Normalization [0,1]", normalize_features(X_unscaled)},
|
||||
{"Standardization", standardize_features(X_unscaled)}
|
||||
};
|
||||
|
||||
json config = { {"kernel", "rbf"}, {"C", 1.0}, {"gamma", 0.1} };
|
||||
|
||||
std::cout << "\n--- Preprocessing Method Comparison ---" << std::endl;
|
||||
std::cout << std::setw(20) << "Method" << std::setw(15) << "CV Score" << std::setw(15) << "Training Time" << std::endl;
|
||||
std::cout << std::string(50, '-') << std::endl;
|
||||
|
||||
for (const auto& [method_name, X_processed] : preprocessing_methods) {
|
||||
SVMClassifier svm(config);
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
auto cv_scores = svm.cross_validate(X_processed, y, 5);
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
double mean_cv = std::accumulate(cv_scores.begin(), cv_scores.end(), 0.0) / cv_scores.size();
|
||||
|
||||
std::cout << std::setw(20) << method_name
|
||||
<< std::setw(15) << std::fixed << std::setprecision(4) << mean_cv * 100 << "%"
|
||||
<< std::setw(15) << duration.count() << " ms" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\nKey Insights:" << std::endl;
|
||||
std::cout << "- Normalization scales features to [0,1] range" << std::endl;
|
||||
std::cout << "- Standardization centers features at 0 with unit variance" << std::endl;
|
||||
std::cout << "- RBF kernels are particularly sensitive to feature scaling" << std::endl;
|
||||
std::cout << "- Preprocessing often improves performance significantly" << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate class imbalance handling
|
||||
*/
|
||||
void demonstrate_class_imbalance()
|
||||
{
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "CLASS IMBALANCE HANDLING EXAMPLE" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
// Create imbalanced dataset
|
||||
torch::manual_seed(42);
|
||||
|
||||
// Class 0: 500 samples (majority)
|
||||
auto X0 = torch::randn({ 500, 8 }) + torch::tensor({ 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
|
||||
auto y0 = torch::zeros({ 500 }, torch::kInt32);
|
||||
|
||||
// Class 1: 100 samples (minority)
|
||||
auto X1 = torch::randn({ 100, 8 }) + torch::tensor({ -1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 });
|
||||
auto y1 = torch::ones({ 100 }, torch::kInt32);
|
||||
|
||||
// Class 2: 50 samples (very minority)
|
||||
auto X2 = torch::randn({ 50, 8 }) + torch::tensor({ 0.0, 0.0, -1.0, -1.0, 1.0, 1.0, 0.0, 0.0 });
|
||||
auto y2 = torch::full({ 50 }, 2, torch::kInt32);
|
||||
|
||||
auto X = torch::cat({ X0, X1, X2 }, 0);
|
||||
auto y = torch::cat({ y0, y1, y2 }, 0);
|
||||
|
||||
// Shuffle
|
||||
auto indices = torch::randperm(X.size(0));
|
||||
X = X.index_select(0, indices);
|
||||
y = y.index_select(0, indices);
|
||||
|
||||
// Standardize features
|
||||
X = standardize_features(X);
|
||||
|
||||
std::cout << "Imbalanced dataset created:" << std::endl;
|
||||
std::cout << " Class 0: 500 samples (76.9%)" << std::endl;
|
||||
std::cout << " Class 1: 100 samples (15.4%)" << std::endl;
|
||||
std::cout << " Class 2: 50 samples (7.7%)" << std::endl;
|
||||
std::cout << " Total: 650 samples" << std::endl;
|
||||
|
||||
// Test different strategies
|
||||
std::vector<json> strategies = {
|
||||
{{"kernel", "linear"}, {"C", 1.0}, {"multiclass_strategy", "ovr"}},
|
||||
{{"kernel", "linear"}, {"C", 10.0}, {"multiclass_strategy", "ovr"}},
|
||||
{{"kernel", "rbf"}, {"C", 1.0}, {"gamma", 0.1}, {"multiclass_strategy", "ovr"}},
|
||||
{{"kernel", "rbf"}, {"C", 10.0}, {"gamma", 0.1}, {"multiclass_strategy", "ovo"}}
|
||||
};
|
||||
|
||||
std::vector<std::string> strategy_names = {
|
||||
"Linear (C=1.0, OvR)",
|
||||
"Linear (C=10.0, OvR)",
|
||||
"RBF (C=1.0, OvR)",
|
||||
"RBF (C=10.0, OvO)"
|
||||
};
|
||||
|
||||
std::cout << "\n--- Strategy Comparison for Imbalanced Data ---" << std::endl;
|
||||
|
||||
for (size_t i = 0; i < strategies.size(); ++i) {
|
||||
std::cout << "\n" << std::string(30, '-') << std::endl;
|
||||
std::cout << "Strategy: " << strategy_names[i] << std::endl;
|
||||
|
||||
SVMClassifier svm(strategies[i]);
|
||||
svm.fit(X, y);
|
||||
|
||||
auto metrics = svm.evaluate(X, y);
|
||||
print_evaluation_metrics(metrics, strategy_names[i] + " Performance");
|
||||
|
||||
// Per-class analysis
|
||||
std::cout << "\nPer-class analysis:" << std::endl;
|
||||
for (int class_idx = 0; class_idx < 3; ++class_idx) {
|
||||
int tp = metrics.confusion_matrix[class_idx][class_idx];
|
||||
int total = 0;
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
total += metrics.confusion_matrix[class_idx][j];
|
||||
}
|
||||
double class_recall = (total > 0) ? static_cast<double>(tp) / total : 0.0;
|
||||
std::cout << " Class " << class_idx << " recall: "
|
||||
<< std::fixed << std::setprecision(4) << class_recall * 100 << "%" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\nRecommendations for imbalanced data:" << std::endl;
|
||||
std::cout << "- Increase C parameter to give more weight to training errors" << std::endl;
|
||||
std::cout << "- Consider One-vs-One strategy for better minority class handling" << std::endl;
|
||||
std::cout << "- Use class-specific evaluation metrics (precision, recall per class)" << std::endl;
|
||||
std::cout << "- Consider resampling techniques in preprocessing" << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
std::cout << "Advanced SVM Classifier Usage Examples" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
// Set single-threaded mode for reproducible results
|
||||
torch::set_num_threads(1);
|
||||
|
||||
// Run comprehensive examples
|
||||
demonstrate_hyperparameter_tuning();
|
||||
demonstrate_model_evaluation();
|
||||
demonstrate_preprocessing_effects();
|
||||
demonstrate_class_imbalance();
|
||||
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "ALL ADVANCED EXAMPLES COMPLETED SUCCESSFULLY!" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
std::cout << "\nKey Takeaways:" << std::endl;
|
||||
std::cout << "1. Hyperparameter tuning is crucial for optimal performance" << std::endl;
|
||||
std::cout << "2. Feature preprocessing significantly affects RBF and polynomial kernels" << std::endl;
|
||||
std::cout << "3. Cross-validation provides robust performance estimates" << std::endl;
|
||||
std::cout << "4. Different kernels and strategies work better for different data types" << std::endl;
|
||||
std::cout << "5. Class imbalance requires special consideration in model selection" << std::endl;
|
||||
std::cout << "6. Linear kernels are fastest and work well for high-dimensional data" << std::endl;
|
||||
std::cout << "7. RBF kernels provide good general-purpose non-linear classification" << std::endl;
|
||||
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
272
examples/basic_usage.cpp
Normal file
272
examples/basic_usage.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#include <svm_classifier/svm_classifier.hpp>
|
||||
#include <torch/torch.h>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace svm_classifier;
|
||||
using json = nlohmann::json;
|
||||
|
||||
/**
|
||||
* @brief Generate synthetic 2D classification dataset
|
||||
* @param n_samples Number of samples to generate
|
||||
* @param n_classes Number of classes
|
||||
* @return Pair of (features, labels)
|
||||
*/
|
||||
std::pair<torch::Tensor, torch::Tensor> generate_classification_data(int n_samples, int n_classes = 3)
|
||||
{
|
||||
torch::manual_seed(42); // For reproducibility
|
||||
|
||||
// Generate random features
|
||||
auto X = torch::randn({ n_samples, 2 });
|
||||
|
||||
// Create clusters for different classes
|
||||
auto y = torch::zeros({ n_samples }, torch::kInt);
|
||||
|
||||
for (int i = 0; i < n_samples; ++i) {
|
||||
// Simple clustering based on position
|
||||
double x_val = X[i][0].item<double>();
|
||||
double y_val = X[i][1].item<double>();
|
||||
|
||||
if (x_val > 0.5 && y_val > 0.5) {
|
||||
y[i] = 0; // Class 0: top-right
|
||||
} else if (x_val <= 0.5 && y_val > 0.5) {
|
||||
y[i] = 1; // Class 1: top-left
|
||||
} else {
|
||||
y[i] = 2; // Class 2: bottom
|
||||
}
|
||||
}
|
||||
|
||||
// Add some noise to make it more interesting
|
||||
X += torch::randn_like(X) * 0.1;
|
||||
|
||||
return { X, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print tensor statistics
|
||||
*/
|
||||
void print_tensor_stats(const torch::Tensor& tensor, const std::string& name)
|
||||
{
|
||||
std::cout << name << " shape: [" << tensor.size(0) << ", " << tensor.size(1) << "]" << std::endl;
|
||||
std::cout << name << " min: " << tensor.min().item<double>() << std::endl;
|
||||
std::cout << name << " max: " << tensor.max().item<double>() << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate basic SVM usage
|
||||
*/
|
||||
void basic_svm_example()
|
||||
{
|
||||
std::cout << "=== Basic SVM Classification Example ===" << std::endl;
|
||||
|
||||
// Generate synthetic data
|
||||
auto [X, y] = generate_classification_data(200, 3);
|
||||
|
||||
// Split into train/test sets (80/20 split)
|
||||
int n_train = 160;
|
||||
auto X_train = X.slice(0, 0, n_train);
|
||||
auto y_train = y.slice(0, 0, n_train);
|
||||
auto X_test = X.slice(0, n_train);
|
||||
auto y_test = y.slice(0, n_train);
|
||||
|
||||
std::cout << "Dataset created:" << std::endl;
|
||||
print_tensor_stats(X_train, "X_train");
|
||||
std::cout << "Unique classes in y_train: ";
|
||||
auto unique_classes = torch::unique(y_train);
|
||||
for (int i = 0; i < unique_classes.size(0); ++i) {
|
||||
std::cout << unique_classes[i].item<int>() << " ";
|
||||
}
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
// Create SVM classifier with default parameters
|
||||
SVMClassifier svm;
|
||||
|
||||
// Train the model
|
||||
std::cout << "Training SVM with default parameters..." << std::endl;
|
||||
auto training_metrics = svm.fit(X_train, y_train);
|
||||
|
||||
std::cout << "Training completed:" << std::endl;
|
||||
std::cout << " Training time: " << training_metrics.training_time << " seconds" << std::endl;
|
||||
std::cout << " Support vectors: " << training_metrics.support_vectors << std::endl;
|
||||
std::cout << " Status: " << (training_metrics.status == TrainingStatus::SUCCESS ? "SUCCESS" : "FAILED") << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Make predictions
|
||||
std::cout << "Making predictions..." << std::endl;
|
||||
auto predictions = svm.predict(X_test);
|
||||
|
||||
// Calculate accuracy
|
||||
double accuracy = svm.score(X_test, y_test);
|
||||
std::cout << "Test accuracy: " << (accuracy * 100.0) << "%" << std::endl;
|
||||
|
||||
// Get detailed evaluation metrics
|
||||
auto eval_metrics = svm.evaluate(X_test, y_test);
|
||||
std::cout << "Detailed metrics:" << std::endl;
|
||||
std::cout << " Precision: " << (eval_metrics.precision * 100.0) << "%" << std::endl;
|
||||
std::cout << " Recall: " << (eval_metrics.recall * 100.0) << "%" << std::endl;
|
||||
std::cout << " F1-score: " << (eval_metrics.f1_score * 100.0) << "%" << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate different kernels
|
||||
*/
|
||||
void kernel_comparison_example()
|
||||
{
|
||||
std::cout << "=== Kernel Comparison Example ===" << std::endl;
|
||||
|
||||
// Generate more complex dataset
|
||||
auto [X, y] = generate_classification_data(300, 2);
|
||||
|
||||
// Split data
|
||||
int n_train = 240;
|
||||
auto X_train = X.slice(0, 0, n_train);
|
||||
auto y_train = y.slice(0, 0, n_train);
|
||||
auto X_test = X.slice(0, n_train);
|
||||
auto y_test = y.slice(0, n_train);
|
||||
|
||||
// Test different kernels
|
||||
std::vector<KernelType> kernels = {
|
||||
KernelType::LINEAR,
|
||||
KernelType::RBF,
|
||||
KernelType::POLYNOMIAL,
|
||||
KernelType::SIGMOID
|
||||
};
|
||||
|
||||
for (auto kernel : kernels) {
|
||||
std::cout << "Testing " << kernel_type_to_string(kernel) << " kernel:" << std::endl;
|
||||
|
||||
// Create classifier with specific kernel
|
||||
SVMClassifier svm(kernel, 1.0, MulticlassStrategy::ONE_VS_REST);
|
||||
|
||||
// Train and evaluate
|
||||
auto training_metrics = svm.fit(X_train, y_train);
|
||||
double accuracy = svm.score(X_test, y_test);
|
||||
|
||||
std::cout << " Training time: " << training_metrics.training_time << " seconds" << std::endl;
|
||||
std::cout << " Test accuracy: " << (accuracy * 100.0) << "%" << std::endl;
|
||||
std::cout << " Library used: " << (svm.get_svm_library() == SVMLibrary::LIBLINEAR ? "liblinear" : "libsvm") << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate JSON parameter configuration
|
||||
*/
|
||||
void json_configuration_example()
|
||||
{
|
||||
std::cout << "=== JSON Configuration Example ===" << std::endl;
|
||||
|
||||
// Create JSON configuration
|
||||
json config = {
|
||||
{"kernel", "rbf"},
|
||||
{"C", 10.0},
|
||||
{"gamma", 0.1},
|
||||
{"multiclass_strategy", "ovo"},
|
||||
{"probability", true},
|
||||
{"tolerance", 1e-4}
|
||||
};
|
||||
|
||||
std::cout << "Configuration JSON:" << std::endl;
|
||||
std::cout << config.dump(2) << std::endl << std::endl;
|
||||
|
||||
// Generate data
|
||||
auto [X, y] = generate_classification_data(200, 3);
|
||||
int n_train = 160;
|
||||
auto X_train = X.slice(0, 0, n_train);
|
||||
auto y_train = y.slice(0, 0, n_train);
|
||||
auto X_test = X.slice(0, n_train);
|
||||
auto y_test = y.slice(0, n_train);
|
||||
|
||||
// Create classifier from JSON
|
||||
SVMClassifier svm(config);
|
||||
|
||||
// Train the model
|
||||
auto training_metrics = svm.fit(X_train, y_train);
|
||||
|
||||
// Make predictions with probabilities
|
||||
auto predictions = svm.predict(X_test);
|
||||
|
||||
if (svm.supports_probability()) {
|
||||
auto probabilities = svm.predict_proba(X_test);
|
||||
std::cout << "Probability predictions shape: [" << probabilities.size(0) << ", " << probabilities.size(1) << "]" << std::endl;
|
||||
}
|
||||
|
||||
double accuracy = svm.score(X_test, y_test);
|
||||
std::cout << "Final accuracy: " << (accuracy * 100.0) << "%" << std::endl;
|
||||
|
||||
// Show current parameters
|
||||
auto current_params = svm.get_parameters();
|
||||
std::cout << "Current parameters:" << std::endl;
|
||||
std::cout << current_params.dump(2) << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Demonstrate cross-validation
|
||||
*/
|
||||
void cross_validation_example()
|
||||
{
|
||||
std::cout << "=== Cross-Validation Example ===" << std::endl;
|
||||
|
||||
// Generate dataset
|
||||
auto [X, y] = generate_classification_data(500, 3);
|
||||
|
||||
// Create SVM classifier
|
||||
SVMClassifier svm(KernelType::RBF, 1.0);
|
||||
|
||||
// Perform 5-fold cross-validation
|
||||
std::cout << "Performing 5-fold cross-validation..." << std::endl;
|
||||
auto cv_scores = svm.cross_validate(X, y, 5);
|
||||
|
||||
std::cout << "Cross-validation scores:" << std::endl;
|
||||
double mean_score = 0.0;
|
||||
for (size_t i = 0; i < cv_scores.size(); ++i) {
|
||||
std::cout << " Fold " << (i + 1) << ": " << (cv_scores[i] * 100.0) << "%" << std::endl;
|
||||
mean_score += cv_scores[i];
|
||||
}
|
||||
mean_score /= cv_scores.size();
|
||||
|
||||
std::cout << "Mean CV score: " << (mean_score * 100.0) << "%" << std::endl;
|
||||
|
||||
// Calculate standard deviation
|
||||
double std_dev = 0.0;
|
||||
for (auto score : cv_scores) {
|
||||
std_dev += (score - mean_score) * (score - mean_score);
|
||||
}
|
||||
std_dev = std::sqrt(std_dev / cv_scores.size());
|
||||
|
||||
std::cout << "Standard deviation: " << (std_dev * 100.0) << "%" << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
std::cout << "SVM Classifier Examples" << std::endl;
|
||||
std::cout << "======================" << std::endl << std::endl;
|
||||
|
||||
// Set PyTorch to single-threaded for reproducible results
|
||||
torch::set_num_threads(1);
|
||||
|
||||
// Run examples
|
||||
basic_svm_example();
|
||||
std::cout << std::string(50, '-') << std::endl;
|
||||
|
||||
kernel_comparison_example();
|
||||
std::cout << std::string(50, '-') << std::endl;
|
||||
|
||||
json_configuration_example();
|
||||
std::cout << std::string(50, '-') << std::endl;
|
||||
|
||||
cross_validation_example();
|
||||
|
||||
std::cout << std::endl << "All examples completed successfully!" << std::endl;
|
||||
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user