-
Notifications
You must be signed in to change notification settings - Fork 228
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
[WIP] [RFC!] Refactoring... #266
Conversation
I'm not as familiar with the codebase as others, but from my quick review I like what I see. 👍 |
More removed lines than added? Ship it! But seriously. I also think that something like Parameters would be good, either manually or just use the package. The |
@KristofferC I also sneaked in the transition from macros to functions in |
alpha::T | ||
mayterminate::Bool | ||
f_calls::Int64 | ||
g_calls::Int64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Things like f_calls
etc seem generic enough to not be in each solver specific state.
or alternatively one could create a little macro that adds these common fields to the current type
type BFGSState{T}
bfgs_specific_variable::Int64
@add_generic_fields()
end
(I think)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! At least all the fields for multivariateoptimizationoptions Will be the same.
700c597
to
8e53c71
Compare
Current coverage is 86.75% (diff: 91.16%)@@ master #266 diff @@
==========================================
Files 31 32 +1
Lines 2348 2144 -204
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
- Hits 2019 1860 -159
+ Misses 329 284 -45
Partials 0 0
|
Added Tests are running longer now, mostly because we recently added more tests I think. One of the |
mayterminate::Bool | ||
f_calls::Int64 | ||
g_calls::Int64 | ||
lsr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the type of this not known? only asking since you went through the trouble of using leaf types everywhere else a little further down it is initialized with LineSearchResults(T)
EDIT: actually, no, I am wrong, Array{T}
won't be a leaf type I don't think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may have forgotten some annotations in the process :) thanks for reading the code so thoroughly.
ac3f5dc
to
e496672
Compare
fails because nelder mead is slow, maybe I should drop that test again. |
The fields that are in all states are now added with a macro. This is what you meant right @Evizero ? What do you think @johnmyleswhite (and others). Is it a few lines fewer at the cost of a decrease in readability? |
That was what I meant, yes. If it is worth the reduction of code repetition I can't say, but it seems worth considering. |
With the general refactoring + the macros (the |
clear!(lsr) | ||
push!(lsr, zero(T), f_x, dphi0) | ||
type AcceleratedGradientDescentState{T} | ||
@add_generic_fields() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I suggest we use composition rather than macros? In that approach, you'd create a new type called GenericOptimizationState
and place that type inside of each of these types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do that. If I had to try to defend it, it would probably be that it adds a layer of dots (state.generic.x
) but I guess this can be handled with an "unpack"-like functionality as in Parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any comments for my comment @johnmyleswhite ? At least I think we should do this with the line search part, so
method.linesearch!(d, state.x, state.s, state.x_ls, state.g_ls, state.lsr,
state.alpha, state.mayterminate)
becomes
method.linesearch!(d, state.x, state.s, state.linesearch_buffers)
or whatever name the field might get.
Still really excited about this! One thing I'd like to try is to build on top of this to optimize problems with constraints. I'm not sure if this will end up being a dead end, but I decided to start experiment by doing projected gradient descent to solve a non-negative least squares problem (a terrible way to solve it, but a useful toy problem nonetheless). using Optim
const A = rand(10,10)
const b = rand(10)
f(x) = 0.5*sumabs2(A*x - b)
d = DifferentiableFunction(f)
x0 = randn(10)
# project onto non-negative orthant
project!(x) = map!(z-> z<0 ? 0.0 : z, x)
gd = GradientDescent()
o = OptimizationOptions()
s = Optim.initial_state(gd, o, d, x0)
for iter = 1:iterations
Optim.update!(d, s, gd)
project!(s.x)
end Right now I get this error:
I'll keep playing around with this, but am curious to hear feedback on whether this will mesh well with the intended approach here. (I also want to mention #254 so that earlier discussion is linked to this PR) |
Great to see a simple example of how this could be useful outside of Optim. I actually found a place in NelderMead where I hadn't added the I'll see if I can further investigate it later. |
Changing the inner loop like this seems to work (though there is a lot of unnecessary overhead): s = Optim.initial_state(gd, o, d, x0)
for iter = 1:iterations
Optim.update!(d, s, gd)
s = Optim.initial_state(gd, o, d, project!(s.x))
end Looks like the problem is that |
I think this could be made to work if we want to make |
Again, I think your example really shows why this refactoring is worth it, but we need to think about how to do this the best way. We could move the gradient calls to the top. What is the issue with that? Well, then we have a superfluous gradient calculation in the first iteration, as we have already calculated the gradient to do the pre-iteration convergence check. If we don't do it, we revive the "linesearch error if initial point is a stationary point". Instead, we could remove the initial convergence check, and then turn the line search error into a line search warning. We could then add a flag, such that when the warning is thrown, a flag is changed to This would then require some form of #263 to be implemented. I think that might work. |
0972402
to
c310490
Compare
This now works (as far as I can tell) using Optim
const A = rand(10,10)
const b = rand(10)
f(x) = 0.5*sumabs2(A*x - b)
d = DifferentiableFunction(f)
x0 = randn(10)
# project onto non-negative orthant
project!(x) = map!(z-> z<0 ? 0.0 : z, x)
gd = GradientDescent()
o = OptimizationOptions()
s = Optim.initial_state(gd, o, d, x0)
iterations = 100
for iter = 1:iterations
Optim.update_state!(d, s, gd)
project!(s.x)
Optim.update_g!(d, s, gd)
end |
att: @ahwillia (btw, if I edit a post and add a mention, are people then pinged?) |
I believe most of the work in the PR is done by now. The only problem right now is the appearance of |
👏 nice. I will play around with this later this week. Let me know if there is anything specific you'd like feedback on. |
f14bb37
to
278b68f
Compare
Alright, I'll go ahead and merge this, but this doesn't have to be the final say. We can test it out, and tweak it before the next tag. Thanks for all the comments! |
I know this is closed now, but @johnmyleswhite we were not the first to be surprised/confused by the wording in the docs JuliaLang/julia#9551 (comment) |
A first attempt, proof of concept, work in progress, and whatever other word you might want to use for temporary or unfinished.
I didn't want to go through the entire package without any comments, so I would love to hear everyone's input.
There are some weird things in there, and specifically a few new types are simply invented for dispatch reasons. They will go again.
Right now, I'm seeing
state.
here,state.
there,state.
s everywhere! Should we use something like the macros in Parameters such that we can omit thestate.
s?I'll keep on working on it, but criticism is very welcome.
TODO
method_name
in favour of a default field in the types)Refactored
etc)state.
s,state.
s everywhere!