/*========================================================================= * * 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 itkQuasiNewtonOptimizerv4_hxx #define itkQuasiNewtonOptimizerv4_hxx #include "itkQuasiNewtonOptimizerv4EstimateNewtonStepThreader.h" namespace itk { template QuasiNewtonOptimizerv4Template::QuasiNewtonOptimizerv4Template() : m_PreviousValue(0.0) , m_BestValue(0.0) , m_BestPosition(0) { this->m_LearningRate = NumericTraits::OneValue(); // m_MaximumNewtonStepSizeInPhysicalUnits is used for automatic learning // rate estimation. it may be initialized either by calling // SetMaximumNewtonStepSizeInPhysicalUnits manually or by using m_ScalesEstimator // automatically. and the former has higher priority than the latter. this->m_MaximumNewtonStepSizeInPhysicalUnits = TInternalComputationValueType{}; /** Threader for Quasi-Newton method */ using OptimizerType = QuasiNewtonOptimizerv4EstimateNewtonStepThreaderTemplate; using OptimizerPointer = typename OptimizerType::Pointer; OptimizerPointer estimateNewtonStepThreader = OptimizerType::New(); this->m_EstimateNewtonStepThreader = estimateNewtonStepThreader; } template void QuasiNewtonOptimizerv4Template::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); } template void QuasiNewtonOptimizerv4Template::StartOptimization(bool doOnlyInitialization) { itkDebugMacro("StartOptimization"); const SizeValueType numPara = this->m_Metric->GetNumberOfParameters(); const SizeValueType numLocalPara = this->m_Metric->GetNumberOfLocalParameters(); const SizeValueType numLocals = this->m_Metric->GetNumberOfParameters() / numLocalPara; /* Set up the data for Quasi-Newton method */ this->m_NewtonStep.SetSize(numPara); this->m_NewtonStepValidFlags.resize(numLocals); this->m_HessianArray.resize(numLocals); for (SizeValueType loc = 0; loc < numLocals; ++loc) { this->m_HessianArray[loc].SetSize(numLocalPara, numLocalPara); } if (this->m_ScalesEstimator.IsNotNull()) { if (this->m_MaximumNewtonStepSizeInPhysicalUnits <= NumericTraits::epsilon()) { // Newton step size might be bigger than one voxel spacing. // empirically, we set it to 1~5 voxel spacings. this->m_MaximumNewtonStepSizeInPhysicalUnits = 3.0 * this->m_ScalesEstimator->EstimateMaximumStepSize(); } } /* Must call the superclass version for basic validation, setup, * and to start the optimization loop. */ Superclass::StartOptimization(doOnlyInitialization); } template void QuasiNewtonOptimizerv4Template::AdvanceOneStep() { itkDebugMacro("AdvanceOneStep"); const SizeValueType numPara = this->m_Metric->GetNumberOfParameters(); this->m_CurrentPosition = this->m_Metric->GetParameters(); if (this->GetCurrentIteration() == 0) { // initialize some information this->m_PreviousValue = this->GetCurrentMetricValue(); this->m_PreviousPosition = this->GetCurrentPosition(); this->m_PreviousGradient = this->GetGradient(); this->m_BestValue = this->m_CurrentMetricValue; this->m_BestPosition = this->m_CurrentPosition; this->m_BestIteration = this->GetCurrentIteration(); } else if (m_BestValue > this->m_CurrentMetricValue) { // store the best value and related information this->m_BestValue = this->m_CurrentMetricValue; this->m_BestPosition = this->m_CurrentPosition; this->m_BestIteration = this->GetCurrentIteration(); } if (this->GetCurrentIteration() - m_BestIteration > m_MaximumIterationsWithoutProgress) { ParametersType backStep; backStep = m_BestPosition - this->m_Metric->GetParameters(); this->m_Metric->UpdateTransformParameters(backStep); this->m_CurrentPosition = this->m_BestPosition; this->m_CurrentMetricValue = this->m_BestValue; this->m_StopCondition = StopConditionObjectToObjectOptimizerEnum::STEP_TOO_SMALL; this->m_StopConditionDescription << "Optimization stops after " << this->GetCurrentIteration() << " iterations since" << " there is no progress in the last " << m_MaximumIterationsWithoutProgress << " steps." << std::endl << " The best value is from Iteration " << m_BestIteration << '.'; this->StopOptimization(); return; } if (this->GetCurrentIteration() > 0) { ParametersType lastStep(numPara); lastStep = this->m_CurrentPosition - this->m_PreviousPosition; if (lastStep.squared_magnitude() < NumericTraits::epsilon()) { this->m_StopCondition = StopConditionObjectToObjectOptimizerEnum::STEP_TOO_SMALL; this->m_StopConditionDescription << "Optimization stops after " << this->GetCurrentIteration() << " iterations since" << " the last step is almost zero."; this->StopOptimization(); return; } } this->EstimateNewtonStep(); /** Save for the next iteration */ this->m_PreviousValue = this->GetCurrentMetricValue(); this->m_PreviousPosition = this->GetCurrentPosition(); this->m_PreviousGradient = this->GetGradient(); this->CombineGradientNewtonStep(); this->ModifyCombinedNewtonStep(); try { /* Pass gradient to transform and let it do its own updating */ this->m_Metric->UpdateTransformParameters(this->m_NewtonStep); } 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 void QuasiNewtonOptimizerv4Template::CombineGradientNewtonStep() { const SizeValueType numLocalPara = this->m_Metric->GetNumberOfLocalParameters(); const SizeValueType numLocals = this->m_Metric->GetNumberOfParameters() / numLocalPara; bool validNewtonStepExists = false; for (SizeValueType loc = 0; loc < numLocals; ++loc) { if (this->m_NewtonStepValidFlags[loc]) { validNewtonStepExists = true; break; } } TInternalComputationValueType ratio = NumericTraits::OneValue(); if (validNewtonStepExists) { TInternalComputationValueType gradStepScale = this->m_ScalesEstimator->EstimateStepScale(this->m_Gradient); TInternalComputationValueType newtonStepScale = this->m_ScalesEstimator->EstimateStepScale(this->m_NewtonStep); if (gradStepScale > NumericTraits::epsilon()) { ratio = newtonStepScale / gradStepScale; } } for (SizeValueType loc = 0; loc < numLocals; ++loc) { if (!this->m_NewtonStepValidFlags[loc]) { // Using the Gradient step const SizeValueType offset = loc * numLocalPara; for (SizeValueType p = offset; p < (offset + numLocalPara); ++p) { this->m_NewtonStep[p] = this->m_Gradient[p] * ratio; } } } } template void QuasiNewtonOptimizerv4Template::ModifyCombinedNewtonStep() { // If m_ScalesEstimator is not set, we will not change the Newton step. if (this->m_ScalesEstimator.IsNull()) { return; } TInternalComputationValueType stepScale = this->m_ScalesEstimator->EstimateStepScale(this->m_NewtonStep); if (stepScale <= NumericTraits::epsilon()) { this->m_LearningRate = NumericTraits::OneValue(); } else { this->m_LearningRate = this->m_MaximumNewtonStepSizeInPhysicalUnits / stepScale; if (this->m_LearningRate > NumericTraits::OneValue()) { // learning rate is at most 1 for a newton step this->m_LearningRate = NumericTraits::OneValue(); } } if (itk::Math::abs(this->m_LearningRate - NumericTraits::OneValue()) > 0.01) { this->m_NewtonStep *= this->m_LearningRate; } } template void QuasiNewtonOptimizerv4Template::ResetNewtonStep(IndexValueType loc) { const SizeValueType numLocalPara = this->m_Metric->GetNumberOfLocalParameters(); // Initialize Hessian to identity matrix m_HessianArray[loc].Fill(TInternalComputationValueType{}); for (unsigned int i = 0; i < numLocalPara; ++i) { m_HessianArray[loc][i][i] = NumericTraits::OneValue(); // identity matrix } IndexValueType offset = loc * numLocalPara; for (SizeValueType p = 0; p < numLocalPara; ++p) { // Set to zero for invalid Newton steps. // They must be defined since they will be used during step scale estimation. this->m_NewtonStep[offset + p] = TInternalComputationValueType{}; } } template void QuasiNewtonOptimizerv4Template::EstimateNewtonStep() { if (this->m_Gradient.GetSize() == 0) { return; } IndexRangeType fullrange; fullrange[0] = 0; fullrange[1] = this->m_Gradient.GetSize() - 1; // range is inclusive /* Perform the modification either with or without threading */ if (this->m_Gradient.GetSize() > 10000) { /* This ends up calling EstimateNewtonStepOverSubRange from each thread */ this->m_EstimateNewtonStepThreader->Execute(this, fullrange); } else { this->EstimateNewtonStepOverSubRange(fullrange); } } template void QuasiNewtonOptimizerv4Template::EstimateNewtonStepOverSubRange( const IndexRangeType & subrange) { const SizeValueType numLocalPara = this->m_Metric->GetNumberOfLocalParameters(); IndexValueType low = subrange[0] / numLocalPara; IndexValueType high = subrange[1] / numLocalPara; // let us denote the i-th thread's sub range by subrange_i // we assume subrange_i[1] + 1 = subrange_(i+1)[0] . // if the subrange_i doesn't start with the multiple of numLocalPara, // we assign this starting block of local parameters to thread_i // if the subrange_i doesn't end with the multiple of numLocalPara, // we assign this ending block of local parameters to thread_(i+1) . if ((subrange[1] + 1) % numLocalPara != 0) { --high; } for (IndexValueType loc = low; loc <= high; ++loc) { if (this->GetCurrentIteration() == 0) { this->m_NewtonStepValidFlags[loc] = false; } else { this->m_NewtonStepValidFlags[loc] = this->ComputeHessianAndStepWithBFGS(loc); } if (!this->m_NewtonStepValidFlags[loc]) { this->ResetNewtonStep(loc); } } // end for loc } template bool QuasiNewtonOptimizerv4Template::ComputeHessianAndStepWithBFGS(IndexValueType loc) { if (this->GetCurrentIteration() == 0) { return false; } const SizeValueType numLocalPara = this->m_Metric->GetNumberOfLocalParameters(); IndexValueType offset = loc * numLocalPara; ParametersType dx(numLocalPara); // delta of position x: x_k+1 - x_k DerivativeType dg(numLocalPara); // delta of gradient: g_k+1 - g_k DerivativeType edg(numLocalPara); // estimated delta of gradient: hessian_k * dx for (SizeValueType p = 0; p < numLocalPara; ++p) { dx[p] = this->m_CurrentPosition[offset + p] - this->m_PreviousPosition[offset + p]; // gradient is already negated dg[p] = this->m_PreviousGradient[offset + p] - this->m_Gradient[offset + p]; } edg = this->m_HessianArray[loc] * dx; TInternalComputationValueType dot_dg_dx = inner_product(dg, dx); TInternalComputationValueType dot_edg_dx = inner_product(edg, dx); if (itk::Math::abs(dot_dg_dx) <= NumericTraits::epsilon() || itk::Math::abs(dot_edg_dx) <= NumericTraits::epsilon()) { return false; } vnl_matrix plus = outer_product(dg, dg) / dot_dg_dx; vnl_matrix minus = outer_product(edg, edg) / dot_edg_dx; vnl_matrix newHessian = this->m_HessianArray[loc] + plus - minus; this->m_HessianArray[loc] = newHessian; for (SizeValueType p = 0; p < numLocalPara; ++p) { if (newHessian[p][p] < 0) { return false; } } TInternalComputationValueType threshold = NumericTraits::epsilon(); if (itk::Math::abs(vnl_determinant(newHessian)) <= threshold) { return false; } else { DerivativeType gradient(numLocalPara); for (SizeValueType p = 0; p < numLocalPara; ++p) { gradient[p] = this->m_Gradient[offset + p]; } const vnl_matrix hessianInverse{ vnl_matrix_inverse(newHessian).as_matrix() }; // gradient is already negated const DerivativeType newtonStep{ hessianInverse * gradient }; for (SizeValueType p = 0; p < numLocalPara; ++p) { this->m_NewtonStep[offset + p] = newtonStep[p]; } } return true; } } // end namespace itk #endif