/* * Copyright (C) 2005-2019 Centre National d'Etudes Spatiales (CNES) * * This file is part of Orfeo Toolbox * * https://www.orfeo-toolbox.org/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef otbLibSVMMachineLearningModel_hxx #define otbLibSVMMachineLearningModel_hxx #include #include "otbLibSVMMachineLearningModel.h" #include "otbSVMCrossValidationCostFunction.h" #include "otbExhaustiveExponentialOptimizer.h" #include "otbMacro.h" #include "otbUtils.h" namespace otb { template LibSVMMachineLearningModel::LibSVMMachineLearningModel() { this->SetSVMType(C_SVC); this->SetKernelType(LINEAR); this->SetPolynomialKernelDegree(3); this->SetKernelGamma(1.); // 1/k this->SetKernelCoef0(1.); this->SetNu(0.5); this->SetC(1.0); this->SetEpsilon(1e-3); this->SetP(0.1); this->SetDoProbabilityEstimates(false); this->DoShrinking(true); this->SetCacheSize(40); // MB this->m_ParameterOptimization = false; this->m_IsRegressionSupported = true; this->SetCVFolders(5); this->m_InitialCrossValidationAccuracy = 0.; this->m_FinalCrossValidationAccuracy = 0.; this->m_CoarseOptimizationNumberOfSteps = 5; this->m_FineOptimizationNumberOfSteps = 5; this->m_ConfidenceMode = LibSVMMachineLearningModel::CM_INDEX; this->m_Parameters.nr_weight = 0; this->m_Parameters.weight_label = nullptr; this->m_Parameters.weight = nullptr; this->m_Model = nullptr; this->m_Problem.l = 0; this->m_Problem.y = nullptr; this->m_Problem.x = nullptr; #ifdef NDEBUG svm_set_print_string_function(&otb::Utils::PrintNothing); #endif } template LibSVMMachineLearningModel::~LibSVMMachineLearningModel() { this->DeleteModel(); this->DeleteProblem(); } /** Train the machine learning model */ template void LibSVMMachineLearningModel::Train() { this->DeleteProblem(); this->DeleteModel(); // Build problem this->BuildProblem(); // Check consistency this->ConsistencyCheck(); // Compute accuracy and eventually optimize parameters this->OptimizeParameters(); // train the model m_Model = svm_train(&m_Problem, &m_Parameters); this->m_ConfidenceIndex = this->HasProbabilities(); } template typename LibSVMMachineLearningModel::TargetSampleType LibSVMMachineLearningModel::DoPredict(const InputSampleType& input, ConfidenceValueType* quality, ProbaSampleType* proba) const { TargetSampleType target; target.Fill(0); // Get type and number of classes int svm_type = svm_get_svm_type(m_Model); // Allocate nodes (/TODO if performances problems are related to too // many allocations, a cache approach can be set) struct svm_node* x = new struct svm_node[input.Size() + 1]; // Fill the node for (unsigned int i = 0; i < input.Size(); i++) { x[i].index = i + 1; x[i].value = input[i]; } // terminate node x[input.Size()].index = -1; x[input.Size()].value = 0; if (proba != nullptr && !this->m_ProbaIndex) itkExceptionMacro("Probability per class not available for this classifier !"); if (quality != nullptr) { if (!this->m_ConfidenceIndex) { itkExceptionMacro("Confidence index not available for this classifier !"); } if (this->m_ConfidenceMode == CM_INDEX) { if (svm_type == C_SVC || svm_type == NU_SVC) { // Eventually allocate space for probabilities unsigned int nr_class = svm_get_nr_class(m_Model); double* prob_estimates = new double[nr_class]; // predict target[0] = static_cast(svm_predict_probability(m_Model, x, prob_estimates)); double maxProb = 0.0; double secProb = 0.0; for (unsigned int i = 0; i < nr_class; ++i) { if (maxProb < prob_estimates[i]) { secProb = maxProb; maxProb = prob_estimates[i]; } else if (secProb < prob_estimates[i]) { secProb = prob_estimates[i]; } } (*quality) = static_cast(maxProb - secProb); delete[] prob_estimates; } else { target[0] = static_cast(svm_predict(m_Model, x)); // Prob. model for test data: target value = predicted value + z // z: Laplace distribution e^(-|z|/sigma)/(2sigma) // sigma is output as confidence index (*quality) = svm_get_svr_probability(m_Model); } } else if (this->m_ConfidenceMode == CM_PROBA) { target[0] = static_cast(svm_predict_probability(m_Model, x, quality)); } else if (this->m_ConfidenceMode == CM_HYPER) { target[0] = static_cast(svm_predict_values(m_Model, x, quality)); } } else { // default case : if the model has probabilities, we call svm_predict_probabilities() // which gives different results than svm_predict() if (svm_check_probability_model(m_Model)) { unsigned int nr_class = svm_get_nr_class(m_Model); double* prob_estimates = new double[nr_class]; target[0] = static_cast(svm_predict_probability(m_Model, x, prob_estimates)); delete[] prob_estimates; } else { target[0] = static_cast(svm_predict(m_Model, x)); } } // Free allocated memory delete[] x; return target; } template void LibSVMMachineLearningModel::Save(const std::string& filename, const std::string& itkNotUsed(name)) { if (svm_save_model(filename.c_str(), m_Model) != 0) { itkExceptionMacro(<< "Problem while saving SVM model " << filename); } } template void LibSVMMachineLearningModel::Load(const std::string& filename, const std::string& itkNotUsed(name)) { this->DeleteModel(); m_Model = svm_load_model(filename.c_str()); if (m_Model == nullptr) { itkExceptionMacro(<< "Problem while loading SVM model " << filename); } m_Parameters = m_Model->param; this->m_ConfidenceIndex = this->HasProbabilities(); } template bool LibSVMMachineLearningModel::CanReadFile(const std::string& file) { // TODO: Rework. std::ifstream ifs; ifs.open(file); if (!ifs) { std::cerr << "Could not read file " << file << std::endl; return false; } // Read only the first line. std::string line; std::getline(ifs, line); // if (line.find(m_SVMModel->getName()) != std::string::npos) if (line.find("svm_type") != std::string::npos) { // std::cout<<"Reading a libSVM model"< bool LibSVMMachineLearningModel::CanWriteFile(const std::string& itkNotUsed(file)) { return false; } template void LibSVMMachineLearningModel::PrintSelf(std::ostream& os, itk::Indent indent) const { // Call superclass implementation Superclass::PrintSelf(os, indent); } template bool LibSVMMachineLearningModel::HasProbabilities(void) const { bool modelHasProba = static_cast(svm_check_probability_model(m_Model)); int type = svm_get_svm_type(m_Model); int cmMode = this->m_ConfidenceMode; bool ret = false; if (type == EPSILON_SVR || type == NU_SVR) { ret = (modelHasProba && cmMode == CM_INDEX); } else if (type == C_SVC || type == NU_SVC) { ret = (modelHasProba && (cmMode == CM_INDEX || cmMode == CM_PROBA)) || (cmMode == CM_HYPER); } return ret; } template void LibSVMMachineLearningModel::BuildProblem() { // Get number of samples typename InputListSampleType::Pointer samples = this->GetInputListSample(); typename TargetListSampleType::Pointer target = this->GetTargetListSample(); int probl = samples->Size(); if (probl < 1) { itkExceptionMacro(<< "No samples, can not build SVM problem."); } otbMsgDebugMacro(<< "Building problem ..."); // Get the size of the samples long int elements = samples->GetMeasurementVectorSize(); // Allocate the problem m_Problem.l = probl; m_Problem.y = new double[probl]; m_Problem.x = new struct svm_node*[probl]; for (int i = 0; i < probl; ++i) { m_Problem.x[i] = new struct svm_node[elements + 1]; } // Iterate on the samples typename InputListSampleType::ConstIterator sIt = samples->Begin(); typename TargetListSampleType::ConstIterator tIt = target->Begin(); int sampleIndex = 0; while (sIt != samples->End() && tIt != target->End()) { // Set the label m_Problem.y[sampleIndex] = tIt.GetMeasurementVector()[0]; const InputSampleType& sample = sIt.GetMeasurementVector(); for (int k = 0; k < elements; ++k) { m_Problem.x[sampleIndex][k].index = k + 1; m_Problem.x[sampleIndex][k].value = sample[k]; } // terminate node m_Problem.x[sampleIndex][elements].index = -1; m_Problem.x[sampleIndex][elements].value = 0; ++sampleIndex; ++sIt; ++tIt; } // Compute the kernel gamma from number of elements if necessary if (this->GetKernelGamma() == 0) { this->SetKernelGamma(1.0 / static_cast(elements)); } // allocate buffer for cross validation m_TmpTarget.resize(m_Problem.l); } template void LibSVMMachineLearningModel::ConsistencyCheck() { if (this->GetSVMType() == ONE_CLASS && this->GetDoProbabilityEstimates()) { otbMsgDebugMacro(<< "Disabling SVM probability estimates for ONE_CLASS SVM type."); this->SetDoProbabilityEstimates(false); } const char* error_msg = svm_check_parameter(&m_Problem, &m_Parameters); if (error_msg) { std::string err(error_msg); itkExceptionMacro("SVM parameter check failed : " << err); } } template void LibSVMMachineLearningModel::DeleteProblem() { if (m_Problem.y) { delete[] m_Problem.y; m_Problem.y = nullptr; } if (m_Problem.x) { for (int i = 0; i < m_Problem.l; ++i) { if (m_Problem.x[i]) { delete[] m_Problem.x[i]; } } delete[] m_Problem.x; m_Problem.x = nullptr; } m_Problem.l = 0; } template void LibSVMMachineLearningModel::DeleteModel(void) { if (m_Model) { svm_free_and_destroy_model(&m_Model); } m_Model = nullptr; } template unsigned int LibSVMMachineLearningModel::GetNumberOfKernelParameters() { unsigned int nb = 1; switch (this->GetKernelType()) { case LINEAR: // C nb = 1; break; case POLY: // C, gamma and coef0 nb = 3; break; case RBF: // C and gamma nb = 2; break; case SIGMOID: // C, gamma and coef0 nb = 3; break; default: // C nb = 1; break; } return nb; } template double LibSVMMachineLearningModel::CrossValidation(void) { double accuracy = 0.0; // Get the length of the problem unsigned int length = m_Problem.l; if (length == 0 || m_TmpTarget.size() < length) return accuracy; // Do cross validation svm_cross_validation(&m_Problem, &m_Parameters, m_CVFolders, &m_TmpTarget[0]); // Evaluate accuracy double total_correct = 0.; for (unsigned int i = 0; i < length; ++i) { if (m_TmpTarget[i] == m_Problem.y[i]) { ++total_correct; } } accuracy = total_correct / length; // return accuracy value return accuracy; } template void LibSVMMachineLearningModel::OptimizeParameters() { typedef SVMCrossValidationCostFunction> CrossValidationFunctionType; typename CrossValidationFunctionType::Pointer crossValidationFunction = CrossValidationFunctionType::New(); crossValidationFunction->SetModel(this); typename CrossValidationFunctionType::ParametersType initialParameters, coarseBestParameters, fineBestParameters; unsigned int nbParams = this->GetNumberOfKernelParameters(); initialParameters.SetSize(nbParams); initialParameters[0] = this->GetC(); if (nbParams > 1) initialParameters[1] = this->GetKernelGamma(); if (nbParams > 2) initialParameters[2] = this->GetKernelCoef0(); m_InitialCrossValidationAccuracy = crossValidationFunction->GetValue(initialParameters); m_FinalCrossValidationAccuracy = m_InitialCrossValidationAccuracy; otbMsgDebugMacro(<< "Initial accuracy : " << m_InitialCrossValidationAccuracy << ", Parameters Optimization" << m_ParameterOptimization); if (m_ParameterOptimization) { otbMsgDebugMacro(<< "Model parameters optimization"); typename ExhaustiveExponentialOptimizer::Pointer coarseOptimizer = ExhaustiveExponentialOptimizer::New(); typename ExhaustiveExponentialOptimizer::StepsType coarseNbSteps(initialParameters.Size()); coarseNbSteps.Fill(m_CoarseOptimizationNumberOfSteps); coarseOptimizer->SetNumberOfSteps(coarseNbSteps); coarseOptimizer->SetCostFunction(crossValidationFunction); coarseOptimizer->SetInitialPosition(initialParameters); coarseOptimizer->StartOptimization(); coarseBestParameters = coarseOptimizer->GetMaximumMetricValuePosition(); otbMsgDevMacro(<< "Coarse minimum accuracy: " << coarseOptimizer->GetMinimumMetricValue() << " " << coarseOptimizer->GetMinimumMetricValuePosition()); otbMsgDevMacro(<< "Coarse maximum accuracy: " << coarseOptimizer->GetMaximumMetricValue() << " " << coarseOptimizer->GetMaximumMetricValuePosition()); typename ExhaustiveExponentialOptimizer::Pointer fineOptimizer = ExhaustiveExponentialOptimizer::New(); typename ExhaustiveExponentialOptimizer::StepsType fineNbSteps(initialParameters.Size()); fineNbSteps.Fill(m_FineOptimizationNumberOfSteps); double stepLength = 1. / static_cast(m_FineOptimizationNumberOfSteps); fineOptimizer->SetNumberOfSteps(fineNbSteps); fineOptimizer->SetStepLength(stepLength); fineOptimizer->SetCostFunction(crossValidationFunction); fineOptimizer->SetInitialPosition(coarseBestParameters); fineOptimizer->StartOptimization(); otbMsgDevMacro(<< "Fine minimum accuracy: " << fineOptimizer->GetMinimumMetricValue() << " " << fineOptimizer->GetMinimumMetricValuePosition()); otbMsgDevMacro(<< "Fine maximum accuracy: " << fineOptimizer->GetMaximumMetricValue() << " " << fineOptimizer->GetMaximumMetricValuePosition()); fineBestParameters = fineOptimizer->GetMaximumMetricValuePosition(); m_FinalCrossValidationAccuracy = fineOptimizer->GetMaximumMetricValue(); this->SetC(fineBestParameters[0]); if (nbParams > 1) this->SetKernelGamma(fineBestParameters[1]); if (nbParams > 2) this->SetKernelCoef0(fineBestParameters[2]); } } } // end namespace otb #endif