Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jimmy adding iteration limit convergence criterion #1862

Open
wants to merge 15 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions developer_tools/XSDSchemas/Optimizers.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<xsd:element name="gradient" type="xsd:float" minOccurs="0"/>
<xsd:element name="objective" type="xsd:float" minOccurs="0"/>
<xsd:element name="stepSize" type="xsd:float" minOccurs="0"/>
<xsd:element name="iterationLimit" type="xsd:integer" minOccurs="0"/>
<xsd:element name="terminateFollowers" type="terminateFollowersType" minOccurs="0"/>
</xsd:all>
</xsd:complexType>
Expand Down
17 changes: 16 additions & 1 deletion ravenframework/Optimizers/GradientDescent.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class GradientDescent(RavenSampled):
# TODO change in input space?
'objective': r"""provides the maximum relative change in the objective function for convergence.""",
'stepSize': r"""provides the maximum size in relative step size for convergence.""",
'iterationLimit': r"""provides the maximum number of iterations at which the optimizer will terminate regardless of the status of any convergence criteria.
Note that for Gradient Descent this counts only the optimization sweeps not the gradient evaluations.
It is important to differentiate this from sampler'sinit <limit> node as the latter counts every model evaluation,
hence, if n variables we will have 2n gradient evaluation if using centeral difference, number of iteration will be <limit>/(2n). If is less than the <iterationlimit> the GA will honor this lower limit.
I.e., the termination (given all other covergence criteria are not met) will occur at min{<iterationLimit>, ceil(<limit>/gradient evaluations per variable/\# of variables) +1}
For instance: if the \# of Variables is 2, and we perform 2 evaluations per variable for gradients, and if <limit> = 20, and <iterationLimit> is 10, the termination will occur at min{ceiling(20/(2*2))+1,10} = 6."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some light editing, I suggest:

provides the maximum number of iterations at which the optimizer will terminate regardless of the status of any 
convergence criteria. Note that for Gradient Descent each new optimal point with its nearby gradient evaluation 
points all count as a single iteration. It is important to differentiate \xmlNode{iterationLimit} from the sampler's 
\xmlNode{limit} node as the latter counts every model evaluation, not just successive steps in the optimization 
algorithm. Hence, if $n$ variables are being optimized, the optimizer perform $n+1$ samples (in 
\texttt{FiniteDifference}) per iteration. If for example we have 4 variables, and the \xmlNode{limit} is set to 100, 
then at most 20 optimization iterations can be performed, since there are 5 model evaluations per optimization 
iteration. If we have 4 variables and instead the \xmlNode{iterationLimit} is set to 100, then at most 100 
optimization iterations can be performed, which results in 500 model evaluations. The optimizer will terminate on 
the first limit it observes. \default{None}.

Is this default correct, or is there a default iteration limit?

}

##########################
Expand Down Expand Up @@ -149,7 +155,6 @@ def getInputSpecification(cls):
descr=r"""provides the number of consecutive times a functional constraint boundary can be explored
for an acceptable sampling point before aborting search. Only apples if using a
\xmlNode{Constraint}. \default{500}"""))

for name, descr in cls.convergenceOptions.items():
conv.addSub(InputData.parameterInputFactory(name, contentType=InputTypes.FloatType, descr=descr))
terminate = InputData.parameterInputFactory('terminateFollowers', contentType=InputTypes.BoolType,
Expand Down Expand Up @@ -265,6 +270,8 @@ def handleInput(self, paramInput):
elif sub.getName() == 'terminateFollowers':
self._terminateFollowers = sub.value
self._followerProximity = sub.parameterValues.get('proximity', 1e-2)
elif sub.getName() == 'iterationLimit':
self._convergenceCriteria['iterationLimit'] = sub.value
elif sub.getName() == 'constraintExplorationLimit':
self._functionalConstraintExplorationLimit = sub.value
else:
Expand Down Expand Up @@ -619,6 +626,7 @@ def _checkAcceptability(self, traj, opt, optVal, info):
# if this is an opt point rerun, accept it without checking.
elif self._acceptRerun[traj]:
acceptable = 'rerun'
self.incrementIteration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this line do anything? Did you mean self.incrementIteration()?

self._acceptRerun[traj] = False
self._stepRecommendations[traj] = 'shrink' # FIXME how much do we really want this?
# check if same point
Expand Down Expand Up @@ -790,6 +798,13 @@ def _checkConvGradient(self, traj):

return converged

def _checkConvIterationLimit(self,traj):
converged = self.getIteration(traj) >= self._convergenceCriteria['iterationLimit']
self.raiseADebug(self.convFormat.format(name='iterationLimit',
conv=str(converged),
got=int(self.getIteration(traj)),
req=int(self._convergenceCriteria['iterationLimit'])))
return converged
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline after return please

def _checkConvStepSize(self, traj):
"""
Checks the step size for convergence
Expand Down
4 changes: 3 additions & 1 deletion ravenframework/Optimizers/RavenSampled.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def localGenerateInput(self, model, inp):
self.inputInfo['batchMode'] = False
for _ in range(self.batch):
inputInfo = {'SampledVarsPb':{}, 'batchMode':self.inputInfo['batchMode']} # ,'prefix': str(self.batchId)+'_'+str(i)
if self.counter == self.limit + 1:
if self.getIteration(all(self._activeTraj)) == self.limit + 1:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with what all does here. Doesn't that do a binary check on each entry of self._activeTraj? Did you want:

if all(self.getIteration(self._activeTraj) == self.limit + 1)

perhaps?

break
# get point from stack
point, info = self._submissionQueue.popleft()
Expand Down Expand Up @@ -560,6 +560,8 @@ def _resolveNewOptPoint(self, traj, rlz, optVal, info):
# nothing else to do but wait for the grad points to be collected
elif acceptable == 'rejected':
self._rejectOptPoint(traj, info, old)
if self.printTag == 'GradientDescent':
self.__stepCounter[traj] -= 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this concerns me; are we not counting rejected steps as iterations now? This seems like it will create indexing problems and logistical mismatches. Help me understand why I'm wrong about that.

elif acceptable == 'rerun':
# update the most recently obtained opt value for the rerun point
# NOTE we do this because if we got "lucky" in an opt point evaluation, we can get stuck
Expand Down
112 changes: 112 additions & 0 deletions tests/framework/Optimizers/GradientDescent/converge_iterationLimit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?xml version="1.0" ?>
<Simulation verbosity="debug">
<TestInfo>
<name>framework/Optimizers.ConvergeObjective</name>
<author>talbpaul</author>
<created>2020-01-29</created>
<classesTested>Optimizer</classesTested>
<description>
Extending on the Basic test, this test converges on change in objective function instead of
number of samples.
</description>
<analytic>
This test uses Beale's function, which is documented in the analytic tests documentation under
the Optimizer functions section.
</analytic>
</TestInfo>

<RunInfo>
<WorkingDir>ConvergeIterationLimit</WorkingDir>
<Sequence>optimize, print</Sequence>
</RunInfo>

<Steps>
<MultiRun name="optimize">
<Input class="DataObjects" type="PointSet">placeholder</Input>
<Model class="Models" type="ExternalModel">beale</Model>
<Optimizer class="Optimizers" type="SPSA">opter</Optimizer>
<SolutionExport class="DataObjects" type="PointSet">opt_export</SolutionExport>
<Output class="DataObjects" type="PointSet">optOut</Output>
</MultiRun>
<IOStep name="print">
<Input class="DataObjects" type="PointSet">opt_export</Input>
<Output class="OutStreams" type="Print">opt_export</Output>
</IOStep>
</Steps>

<Distributions>
<Uniform name='beale_dist'>
<lowerBound>-4.5</lowerBound>
<upperBound>4.5</upperBound>
</Uniform>
</Distributions>

<Optimizers>
<GradientDescent name="opter">
<objective>ans</objective>
<variable name="x">
<distribution>beale_dist</distribution>
<initial>-2</initial>
</variable>
<variable name="y">
<distribution>beale_dist</distribution>
<initial>-2</initial>
</variable>
<TargetEvaluation class="DataObjects" type="PointSet">optOut</TargetEvaluation>
<samplerInit>
<limit>100</limit>
<initialSeed>42</initialSeed>
<writeSteps>every</writeSteps>
</samplerInit>
<gradient>
<FiniteDifference/>
</gradient>
<stepSize>
<GradientHistory>
<growthFactor>1.25</growthFactor>
<shrinkFactor>1.5</shrinkFactor>
</GradientHistory>
</stepSize>
<acceptance>
<Strict/>
</acceptance>
<convergence>
<iterationLimit>10</iterationLimit>
<gradient>1e-4</gradient>
<objective>1e-4</objective>
<stepSize>1e-12</stepSize>
</convergence>
</GradientDescent>
</Optimizers>

<Models>
<ExternalModel ModuleToLoad="../../../framework/AnalyticModels/optimizing/beale" name="beale" subType="">
<variables>x,y,ans</variables>
</ExternalModel>
</Models>

<DataObjects>
<PointSet name="placeholder"/>
<PointSet name="optOut">
<Input>x,y</Input>
<Output>ans</Output>
</PointSet>
<PointSet name="opt_export">
<Input>trajID</Input>
<Output>x,y,ans,stepSize,iteration,accepted,conv_iterationLimit,conv_gradient,conv_samePoint,conv_objective</Output>
</PointSet>
</DataObjects>

<OutStreams>
<Print name="optOut">
<type>csv</type>
<source>optOut</source>
</Print>
<Print name="opt_export">
<type>csv</type>
<source>opt_export</source>
<clusterLabel>trajID</clusterLabel>
</Print>
</OutStreams>

</Simulation>
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,38 @@ x,y,ans,stepSize,iteration,accepted,conv_gradient
2.1431640625,0.5,1.15860859692,0.1220703125,6.0,accepted,0.0
3.51645507813,0.5,0.420926728435,0.152587890625,7.0,accepted,0.0
1.79984130859,0.5,2.27310108344,0.190734863281,8.0,rejected,0.0
3.51645507813,0.5,0.420926728435,0.190734863281,9.0,rerun,0.0
2.37204589844,0.5,0.622296276884,0.127156575521,10.0,rejected,0.0
3.51645507813,0.5,0.420926728435,0.127156575521,11.0,rerun,0.0
2.753515625,0.5,0.0958782696724,0.0847710503472,12.0,accepted,0.0
3.70718994141,0.5,0.789248108373,0.105963812934,13.0,rejected,0.0
2.753515625,0.5,0.0958782696724,0.105963812934,14.0,rerun,0.0
3.3892985026,0.5,0.239170089642,0.070642541956,15.0,rejected,0.0
2.753515625,0.5,0.0958782696724,0.070642541956,16.0,rerun,0.0
3.17737087674,0.5,0.049648487802,0.0470950279707,17.0,accepted,0.0
2.64755181207,0.5,0.196034253796,0.0588687849633,18.0,rejected,0.0
3.17737087674,0.5,0.049648487802,0.0588687849633,19.0,rerun,0.0
2.82415816696,0.5,0.0487961777355,0.0392458566422,20.0,accepted,0.0
3.26567405418,0.5,0.111388328275,0.0490573208028,21.0,rejected,0.0
2.82415816696,0.5,0.0487961777355,0.0490573208028,22.0,rerun,0.0
3.11850209177,0.5,0.0221612081439,0.0327048805352,23.0,accepted,0.0
2.75057218575,0.5,0.0981818388529,0.040881100669,24.0,rejected,0.0
3.11850209177,0.5,0.0221612081439,0.040881100669,25.0,rerun,0.0
2.87321548776,0.5,0.0253672744839,0.0272540671127,26.0,rejected,0.0
3.11850209177,0.5,0.0221612081439,0.0272540671127,27.0,rerun,0.0
2.9549776891,0.5,0.00319887275602,0.0181693780751,28.0,accepted,0.0
3.15938319244,0.5,0.0400891125832,0.0227117225939,29.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0227117225939,30.0,rerun,0.0
3.09124802466,0.5,0.0131397875381,0.0151411483959,31.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0151411483959,32.0,rerun,0.0
3.04582457947,0.5,0.00331389219477,0.0100940989306,33.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0100940989306,34.0,rerun,0.0
3.01554228268,0.5,0.000381215900671,0.00672939928708,35.0,accepted,0.0
2.9398365407,0.5,0.0057122472706,0.00841174910885,36.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00841174910885,37.0,rerun,0.0
2.96507178803,0.5,0.00192528092425,0.00560783273923,38.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00560783273923,39.0,rerun,0.0
2.98189528625,0.5,0.000517278854294,0.00373855515949,40.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00373855515949,41.0,rerun,0.0
2.99311095172,0.5,7.4896212526e-05,0.00249237010632,42.0,accepted,0.0
2.99311095172,0.5,7.4896212526e-05,0.00249237010632,42.0,final,0.0
3.51645507813,0.5,0.420926728435,0.190734863281,8.0,rerun,0.0
2.37204589844,0.5,0.622296276884,0.127156575521,9.0,rejected,0.0
3.51645507813,0.5,0.420926728435,0.127156575521,9.0,rerun,0.0
2.753515625,0.5,0.0958782696724,0.0847710503472,10.0,accepted,0.0
3.70718994141,0.5,0.789248108373,0.105963812934,11.0,rejected,0.0
2.753515625,0.5,0.0958782696724,0.105963812934,11.0,rerun,0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the duplicate step numbers here, I'm struggling to resolve that, unless we want to consider a reject and subsequent rerun as part of the same step, in which case the user input example model run count is only accurate if there are no rejected points.

3.3892985026,0.5,0.239170089642,0.070642541956,12.0,rejected,0.0
2.753515625,0.5,0.0958782696724,0.070642541956,12.0,rerun,0.0
3.17737087674,0.5,0.049648487802,0.0470950279707,13.0,accepted,0.0
2.64755181207,0.5,0.196034253796,0.0588687849633,14.0,rejected,0.0
3.17737087674,0.5,0.049648487802,0.0588687849633,14.0,rerun,0.0
2.82415816696,0.5,0.0487961777355,0.0392458566422,15.0,accepted,0.0
3.26567405418,0.5,0.111388328275,0.0490573208028,16.0,rejected,0.0
2.82415816696,0.5,0.0487961777355,0.0490573208028,16.0,rerun,0.0
3.11850209177,0.5,0.0221612081439,0.0327048805352,17.0,accepted,0.0
2.75057218575,0.5,0.0981818388529,0.040881100669,18.0,rejected,0.0
3.11850209177,0.5,0.0221612081439,0.040881100669,18.0,rerun,0.0
2.87321548776,0.5,0.0253672744839,0.0272540671127,19.0,rejected,0.0
3.11850209177,0.5,0.0221612081439,0.0272540671127,19.0,rerun,0.0
2.9549776891,0.5,0.00319887275602,0.0181693780751,20.0,accepted,0.0
3.15938319244,0.5,0.0400891125832,0.0227117225939,21.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0227117225939,21.0,rerun,0.0
3.09124802466,0.5,0.0131397875381,0.0151411483959,22.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0151411483959,22.0,rerun,0.0
3.04582457947,0.5,0.00331389219477,0.0100940989306,23.0,rejected,0.0
2.9549776891,0.5,0.00319887275602,0.0100940989306,23.0,rerun,0.0
3.01554228268,0.5,0.000381215900671,0.00672939928708,24.0,accepted,0.0
2.9398365407,0.5,0.0057122472706,0.00841174910885,25.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00841174910885,25.0,rerun,0.0
2.96507178803,0.5,0.00192528092425,0.00560783273923,26.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00560783273923,26.0,rerun,0.0
2.98189528625,0.5,0.000517278854294,0.00373855515949,27.0,rejected,0.0
3.01554228268,0.5,0.000381215900671,0.00373855515949,27.0,rerun,0.0
2.99311095172,0.5,7.4896212526e-05,0.00249237010632,28.0,accepted,0.0
2.99311095172,0.5,7.4896212526e-05,0.00249237010632,28.0,final,0.0
Loading