/*========================================================================= * * Copyright NumFOCUS * * 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 * * https://www.apache.org/licenses/LICENSE-2.0.txt * * 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 itkRegularStepGradientDescentOptimizerv4_hxx #define itkRegularStepGradientDescentOptimizerv4_hxx namespace itk { template RegularStepGradientDescentOptimizerv4::RegularStepGradientDescentOptimizerv4() : m_RelaxationFactor(0.5) , m_MinimumStepLength(1e-4) , // Initialize parameter for the convergence checker m_GradientMagnitudeTolerance(1e-4) , m_CurrentLearningRateRelaxation(0) { this->m_DoEstimateLearningRateAtEachIteration = false; this->m_DoEstimateLearningRateOnce = false; } template void RegularStepGradientDescentOptimizerv4::StartOptimization(bool doOnlyInitialization) { this->m_UseConvergenceMonitoring = false; // Call the grandparent version for basic validation and setup GradientDescentOptimizerBasev4Template::StartOptimization(doOnlyInitialization); if (this->m_ReturnBestParametersAndValue) { this->m_BestParameters = this->GetCurrentPosition(); this->m_CurrentBestValue = NumericTraits::max(); } const SizeValueType spaceDimension = this->m_Metric->GetNumberOfParameters(); this->m_Gradient = DerivativeType(spaceDimension); this->m_PreviousGradient = DerivativeType(spaceDimension); this->m_Gradient.Fill(0.0f); this->m_PreviousGradient.Fill(0.0f); // Reset the iterations and learning rate scale when the optimizer is started again. this->m_CurrentLearningRateRelaxation = 1.0; this->m_CurrentIteration = 0; // validity check for the value of GradientMagnitudeTolerance if (m_GradientMagnitudeTolerance < 0.0) { itkExceptionMacro("Gradient magnitude tolerance must be " "greater or equal 0.0. Current value is " << m_GradientMagnitudeTolerance); } if (!doOnlyInitialization) { this->ResumeOptimization(); } } template void RegularStepGradientDescentOptimizerv4::AdvanceOneStep() { itkDebugMacro("AdvanceOneStep"); // Make sure the scales have been set properly if (this->m_Scales.size() != this->m_Gradient.Size()) { itkExceptionMacro("The size of Scales is " << this->m_Scales.size() << ", but the NumberOfParameters for the CostFunction is " << this->m_Gradient.Size() << '.'); } if (this->m_RelaxationFactor < 0.0) { itkExceptionMacro("Relaxation factor must be positive. Current value is " << this->m_RelaxationFactor); } if (this->m_RelaxationFactor >= 1.0) { itkExceptionMacro("Relaxation factor must be less than 1.0. Current value is " << this->m_RelaxationFactor); } // // Begin threaded gradient modification. // Scale gradient and previous gradient by scales. // The m_Gradient and m_PreviousGradient variables are modified in-place. // this->ModifyGradientByScales(); CompensatedSummationType compensatedSummation; for (SizeValueType dim = 0; dim < this->m_Gradient.Size(); ++dim) { const double weighted = this->m_Gradient[dim]; compensatedSummation += weighted * weighted; } const double gradientMagnitude = std::sqrt(compensatedSummation.GetSum()); if (gradientMagnitude < this->m_GradientMagnitudeTolerance) { this->m_StopCondition = StopConditionObjectToObjectOptimizerEnum::GRADIENT_MAGNITUDE_TOLEARANCE; this->m_StopConditionDescription << "Gradient magnitude tolerance met after " << this->m_CurrentIteration << " iterations. Gradient magnitude (" << gradientMagnitude << ") is less than gradient magnitude tolerance (" << this->m_GradientMagnitudeTolerance << ")."; this->StopOptimization(); return; } compensatedSummation.ResetToZero(); for (SizeValueType i = 0; i < this->m_Gradient.Size(); ++i) { const double weight1 = this->m_Gradient[i]; const double weight2 = this->m_PreviousGradient[i]; compensatedSummation += weight1 * weight2; } const double scalarProduct = compensatedSummation.GetSum(); // Check for direction changes if (scalarProduct < 0) { this->m_CurrentLearningRateRelaxation *= this->m_RelaxationFactor; } const double stepLength = this->m_CurrentLearningRateRelaxation * this->m_LearningRate; if (stepLength < this->m_MinimumStepLength) { this->m_StopCondition = StopConditionObjectToObjectOptimizerEnum::STEP_TOO_SMALL; this->m_StopConditionDescription << "Step too small after " << this->m_CurrentIteration << " iterations. Current step (" << stepLength << ") is less than minimum step (" << this->m_MinimumStepLength << ")."; this->StopOptimization(); return; } this->EstimateLearningRate(); this->ModifyGradientByLearningRate(); const double factor = NumericTraits::OneValue() / gradientMagnitude; try { // Pass gradient to transform and let it do its own updating this->m_Metric->UpdateTransformParameters(this->m_Gradient, factor); } catch (const ExceptionObject &) { this->m_StopCondition = StopConditionObjectToObjectOptimizerEnum::UPDATE_PARAMETERS_ERROR; this->m_StopConditionDescription << "UpdateTransformParameters error"; this->StopOptimization(); // Pass exception to caller throw; } this->InvokeEvent(IterationEvent()); } template double RegularStepGradientDescentOptimizerv4::GetCurrentStepLength() const { return (this->m_CurrentLearningRateRelaxation * this->m_LearningRate); } template void RegularStepGradientDescentOptimizerv4::ModifyGradientByScalesOverSubRange( const IndexRangeType & subrange) { const ScalesType & scales = this->GetScales(); const ScalesType & weights = this->GetWeights(); ScalesType factor(scales.Size()); if (this->GetWeightsAreIdentity()) { for (SizeValueType i = 0; i < factor.Size(); ++i) { factor[i] = NumericTraits::OneValue() / scales[i]; } } else { for (SizeValueType i = 0; i < factor.Size(); ++i) { factor[i] = weights[i] / scales[i]; } } // Loop over the range. It is inclusive. for (IndexValueType j = subrange[0]; j <= subrange[1]; ++j) { // Scale is checked during StartOptimization for values <= // machine epsilon. // Take the modulo of the index to handle gradients from transforms // with local support. The gradient array stores the gradient of local // parameters at each local index with linear packing. IndexValueType index = j % scales.Size(); this->m_Gradient[j] = this->m_Gradient[j] * factor[index]; this->m_PreviousGradient[j] = this->m_PreviousGradient[j] * factor[index]; } } template void RegularStepGradientDescentOptimizerv4::ModifyGradientByLearningRateOverSubRange( const IndexRangeType & subrange) { // Loop over the range. It is inclusive. for (IndexValueType j = subrange[0]; j <= subrange[1]; ++j) { this->m_Gradient[j] = this->m_Gradient[j] * this->m_CurrentLearningRateRelaxation * this->m_LearningRate; } } template void RegularStepGradientDescentOptimizerv4::EstimateLearningRate() { if (this->m_ScalesEstimator.IsNull()) { return; } if (this->m_DoEstimateLearningRateAtEachIteration || (this->m_DoEstimateLearningRateOnce && this->m_CurrentIteration == 0)) { TInternalComputationValueType stepScale = this->m_ScalesEstimator->EstimateStepScale(this->m_Gradient); if (stepScale <= NumericTraits::epsilon()) { this->m_LearningRate = NumericTraits::OneValue(); } else { this->m_LearningRate = this->m_MaximumStepSizeInPhysicalUnits / stepScale; } CompensatedSummationType compensatedSummation; for (SizeValueType dim = 0; dim < this->m_Gradient.Size(); ++dim) { const double weighted = this->m_Gradient[dim]; compensatedSummation += weighted * weighted; } const double gradientMagnitude = std::sqrt(compensatedSummation.GetSum()); // // Specialized to keep the step size regularized this additional // scale is needed to make the learning rate independent on the // gradient magnitude. // this->m_LearningRate *= gradientMagnitude; } } template void RegularStepGradientDescentOptimizerv4::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); os << indent << "Relaxation factor: " << this->m_RelaxationFactor << std::endl; os << indent << "Minimum step length: " << this->m_MinimumStepLength << std::endl; os << indent << "Gradient magnitude tolerance: " << this->m_GradientMagnitudeTolerance << std::endl; os << indent << "Current learning rate relaxation: " << this->m_CurrentLearningRateRelaxation << std::endl; } } // namespace itk #endif