/* * 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 otbAutoencoderModel_hxx #define otbAutoencoderModel_hxx #include "otbAutoencoderModel.h" #include "otbMacro.h" #include #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Woverloaded-virtual" #endif #include "otbSharkUtils.h" // include train function #include //~ #include //the error function performing the regularisation of the hidden neurons #include // the RProp optimization algorithm #include // squared loss used for regression #include //L2 regulariziation //~ #include //noise source to corrupt the inputs #include //A simple stopping criterion that stops after a fixed number of iterations #include //Stops when the algorithm seems to converge, Tracks the progress of the training error over a period of time #include #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif namespace otb { template AutoencoderModel::AutoencoderModel() { this->m_IsDoPredictBatchMultiThreaded = true; this->m_WriteLearningCurve = false; } template AutoencoderModel::~AutoencoderModel() { } template void AutoencoderModel::Train() { std::vector features; Shark::ListSampleToSharkVector(this->GetInputListSample(), features); shark::Data inputSamples = shark::createDataFromRange(features); shark::Data inputSamples_copy = inputSamples; std::ofstream ofs; if (this->m_WriteLearningCurve == true) { ofs.open(m_LearningCurveFileName); ofs << "learning curve" << std::endl; } // Initialization of the feed forward neural network m_Encoder = ModelType(); m_InLayers.clear(); size_t previousShape = shark::dataDimension(inputSamples); for (unsigned int i = 0; i < m_NumberOfHiddenNeurons.Size(); ++i) { m_InLayers.push_back(LayerType(previousShape, m_NumberOfHiddenNeurons[i])); previousShape = m_NumberOfHiddenNeurons[i]; m_Encoder.add(&(m_InLayers.back()), true); } for (unsigned int i = std::max(0, static_cast(m_NumberOfHiddenNeurons.Size() - 1)); i > 0; --i) { m_InLayers.push_back(LayerType(previousShape, m_NumberOfHiddenNeurons[i - 1])); previousShape = m_NumberOfHiddenNeurons[i - 1]; } m_OutLayer = OutLayerType(previousShape, shark::dataDimension(inputSamples)); // Training of the autoencoders pairwise, starting from the first and last layers for (unsigned int i = 0; i < m_NumberOfHiddenNeurons.Size(); ++i) { if (m_Epsilon > 0) { shark::TrainingProgress<> criterion(5, m_Epsilon); // Shark doesn't allow to train a layer using a sparsity term AND a noisy input. if (m_Noise[i] != 0) { TrainOneLayer(criterion, i, inputSamples, ofs); } else { TrainOneSparseLayer(criterion, i, inputSamples, ofs); } criterion.reset(); } else { shark::MaxIterations<> criterion(m_NumberOfIterations); // Shark doesn't allow to train a layer using a sparsity term AND a noisy input. if (m_Noise[i] != 0) { TrainOneLayer(criterion, i, inputSamples, ofs); otbMsgDevMacro(<< "m_Noise " << m_Noise[0]); } else { TrainOneSparseLayer(criterion, i, inputSamples, ofs); } criterion.reset(); } // encode the samples with the last encoder trained inputSamples = m_InLayers[i](inputSamples); } if (m_NumberOfIterationsFineTuning > 0) { shark::MaxIterations<> criterion(m_NumberOfIterationsFineTuning); TrainNetwork(criterion, inputSamples_copy, ofs); } this->SetDimension(m_NumberOfHiddenNeurons[m_NumberOfHiddenNeurons.Size() - 1]); } template template void AutoencoderModel::TrainOneLayer(shark::AbstractStoppingCriterion& criterion, unsigned int layer_index, shark::Data& samples, std::ostream& File) { typedef shark::AbstractModel BaseModelType; ModelType net; net.add(&(m_InLayers[layer_index]), true); net.add((layer_index ? (BaseModelType*)&(m_InLayers[m_NumberOfHiddenNeurons.Size() * 2 - 1 - layer_index]) : (BaseModelType*)&m_OutLayer), true); otbMsgDevMacro(<< "Noise " << m_Noise[layer_index]); std::size_t inputs = dataDimension(samples); initRandomUniform(net, -m_InitFactor * std::sqrt(1.0 / inputs), m_InitFactor * std::sqrt(1.0 / inputs)); //~ shark::ImpulseNoiseModel noise(inputs,m_Noise[layer_index],1.0); //set an input pixel with probability m_Noise to 0 //~ shark::ConcatenatedModel model = noise>> net; shark::LabeledData trainSet(samples, samples); // labels identical to inputs shark::SquaredLoss loss; //~ shark::ErrorFunction error(trainSet, &model, &loss); shark::ErrorFunction<> error(trainSet, &net, &loss); shark::TwoNormRegularizer<> regularizer(error.numberOfVariables()); error.setRegularizer(m_Regularization[layer_index], ®ularizer); shark::Adam<> optimizer; error.init(); optimizer.init(error); otbMsgDevMacro(<< "Error before training : " << optimizer.solution().value); if (this->m_WriteLearningCurve == true) { File << "end layer" << std::endl; } unsigned int i = 0; do { i++; optimizer.step(error); if (this->m_WriteLearningCurve == true) { File << optimizer.solution().value << std::endl; } otbMsgDevMacro(<< "Error after " << i << " iterations : " << optimizer.solution().value); } while (!criterion.stop(optimizer.solution())); net.setParameterVector(optimizer.solution().point); } template template void AutoencoderModel::TrainOneSparseLayer(shark::AbstractStoppingCriterion& criterion, unsigned int layer_index, shark::Data& samples, std::ostream& File) { typedef shark::AbstractModel BaseModelType; ModelType net; net.add(&(m_InLayers[layer_index]), true); net.add((layer_index ? (BaseModelType*)&(m_InLayers[m_NumberOfHiddenNeurons.Size() * 2 - 1 - layer_index]) : (BaseModelType*)&m_OutLayer), true); std::size_t inputs = dataDimension(samples); shark::initRandomUniform(net, -m_InitFactor * std::sqrt(1.0 / inputs), m_InitFactor * std::sqrt(1.0 / inputs)); // Idea : set the initials value for the output weights higher than the input weights shark::LabeledData trainSet(samples, samples); // labels identical to inputs shark::SquaredLoss loss; //~ shark::SparseAutoencoderError error(trainSet,&net, &loss, m_Rho[layer_index], m_Beta[layer_index]); // SparseAutoencoderError doesn't exist anymore, for now use a plain ErrorFunction shark::ErrorFunction<> error(trainSet, &net, &loss); shark::TwoNormRegularizer<> regularizer(error.numberOfVariables()); error.setRegularizer(m_Regularization[layer_index], ®ularizer); shark::Adam<> optimizer; error.init(); optimizer.init(error); otbMsgDevMacro(<< "Error before training : " << optimizer.solution().value); unsigned int i = 0; do { i++; optimizer.step(error); otbMsgDevMacro(<< "Error after " << i << " iterations : " << optimizer.solution().value); if (this->m_WriteLearningCurve == true) { File << optimizer.solution().value << std::endl; } } while (!criterion.stop(optimizer.solution())); if (this->m_WriteLearningCurve == true) { File << "end layer" << std::endl; } net.setParameterVector(optimizer.solution().point); } template template void AutoencoderModel::TrainNetwork(shark::AbstractStoppingCriterion& criterion, shark::Data& samples, std::ostream& File) { // create full network ModelType net; for (auto& layer : m_InLayers) { net.add(&layer, true); } net.add(&m_OutLayer, true); // labels identical to inputs shark::LabeledData trainSet(samples, samples); shark::SquaredLoss loss; shark::ErrorFunction<> error(trainSet, &net, &loss); shark::TwoNormRegularizer<> regularizer(error.numberOfVariables()); error.setRegularizer(m_Regularization[0], ®ularizer); shark::Adam<> optimizer; error.init(); optimizer.init(error); otbMsgDevMacro(<< "Error before training : " << optimizer.solution().value); unsigned int i = 0; while (!criterion.stop(optimizer.solution())) { i++; optimizer.step(error); otbMsgDevMacro(<< "Error after " << i << " iterations : " << optimizer.solution().value); if (this->m_WriteLearningCurve == true) { File << optimizer.solution().value << std::endl; } } } template bool AutoencoderModel::CanReadFile(const std::string& filename) { try { this->Load(filename); } catch (...) { return false; } return true; } template bool AutoencoderModel::CanWriteFile(const std::string& /*filename*/) { return true; } template void AutoencoderModel::Save(const std::string& filename, const std::string& /*name*/) { otbMsgDevMacro(<< "saving model ..."); std::ofstream ofs(filename); ofs << "Autoencoder" << std::endl; // the first line of the model file contains a key ofs << (m_InLayers.size() + 1) << std::endl; // second line is the number of encoders/decoders shark::TextOutArchive oa(ofs); for (const auto& layer : m_InLayers) { oa << layer; } oa << m_OutLayer; ofs.close(); } template void AutoencoderModel::Load(const std::string& filename, const std::string& /*name*/) { std::ifstream ifs(filename); char buffer[256]; // check first line ifs.getline(buffer, 256); std::string bufferStr(buffer); if (bufferStr != "Autoencoder") { itkExceptionMacro(<< "Error opening " << filename.c_str()); } // check second line ifs.getline(buffer, 256); int nbLevels = boost::lexical_cast(buffer); if (nbLevels < 2 || nbLevels % 2 == 1) { itkExceptionMacro(<< "Unexpected number of levels : " << buffer); } m_InLayers.clear(); m_Encoder = ModelType(); shark::TextInArchive ia(ifs); for (int i = 0; (i + 1) < nbLevels; i++) { LayerType layer; ia >> layer; m_InLayers.push_back(layer); } ia >> m_OutLayer; ifs.close(); for (int i = 0; i < nbLevels / 2; i++) { m_Encoder.add(&(m_InLayers[i]), true); } this->SetDimension(m_Encoder.outputShape()[0]); } template typename AutoencoderModel::TargetSampleType AutoencoderModel::DoPredict(const InputSampleType& value, ConfidenceValueType* /*quality*/, ProbaSampleType* /*proba*/) const { shark::RealVector samples(value.Size()); for (size_t i = 0; i < value.Size(); i++) { samples[i] = value[i]; } std::vector features; features.push_back(samples); shark::Data data = shark::createDataFromRange(features); // features layer for a network containing the encoder and decoder part data = m_Encoder(data); TargetSampleType target; target.SetSize(this->m_Dimension); for (unsigned int a = 0; a < this->m_Dimension; ++a) { target[a] = data.element(0)[a]; } return target; } template void AutoencoderModel::DoPredictBatch(const InputListSampleType* input, const unsigned int& startIndex, const unsigned int& size, TargetListSampleType* targets, ConfidenceListSampleType* /*quality*/, ProbaListSampleType* /*proba*/) const { std::vector features; Shark::ListSampleRangeToSharkVector(input, features, startIndex, size); shark::Data data = shark::createDataFromRange(features); TargetSampleType target; // features layer for a network containing the encoder and decoder part data = m_Encoder(data); unsigned int id = startIndex; target.SetSize(this->m_Dimension); for (const auto& p : data.elements()) { for (unsigned int a = 0; a < this->m_Dimension; ++a) { target[a] = p[a]; } targets->SetMeasurementVector(id, target); ++id; } } } // namespace otb #endif