-
Notifications
You must be signed in to change notification settings - Fork 352
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
Custom pickler #615
Custom pickler #615
Conversation
if obj.name: | ||
if obj.name == 'pkl': | ||
ValueError("can't pickle shared variable with name `pkl`") | ||
if hasattr(obj, 'tag'): |
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.
Don't shared variables always have the tag
attribute? It could be an empty dictionary though if it's not annotated.
Thinking about it, this class should probably inherit from PersistentNdarrayID
. Right now, when super
gets called, doesn't the parent class (PersistentSharedVariableID
) simply override the name you've just created?
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.
Oh.. Funny, it was working because I made a wrong super call.
I suppose, we won't need I'd like to save Do you agree? |
We'd only need I would change |
@bartvm , the problem was completely different and I solved it now. But now I propose to remove |
It is also possible to use a construction in a script like
|
So The If this limitation has always existed, I'd argue that we just need to document it clearly. I imagine that something like this should work fine, right? # my_model.py
def some_helper_function():
pass # train_my_model.py
from my_model import some_helper_function
main_loop = MainLoop(...) # With Checkpoint, depends on some_helper_function
main_loop.run() Because when unpickling, it can load the function from |
Yes, it always had this limitation. Yes, this is going to work, but we cannot provide a general script which is able to continue any kind of experiment. So I think, the best we can provide is a function # define or import objects from global namespace like you defined it before
if __name__ == '__main__':
if continue:
continue_training(path)
# Construct main loop with Checkpoint extension |
Right, we could just make it a function instead of a script, but we should make it clear that using it within a script isn't necessarily the only way to use it. Technically speaking the user could have just created their model in the interpreter, without any sort of script anywhere. They should still be able to fire up a new interpreter then and call Anyway, that sounds like a reasonable solution to me. The one thing that I think would be very nice--but I have no idea how difficult this is--is being able to give a warning whenever the main loop depends on functions defined in the
Looking at |
How can I test, that arrays were not pickled in a regular way? Is there a better way then to check |
You could load the zip file using I don't know of any documentation on pickle's internals that is any better... It's pretty badly documented I'm afraid. You can look at custom picklers like |
Yes, but I'd like to check that it was not saved twice. If something goes wrong in Seems, there is no |
I just opened up |
if isinstance(obj, SharedVariable): | ||
if obj.name: | ||
if obj.name == 'pkl': | ||
ValueError("can't pickle shared variable with name `pkl`") |
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.
Forgot the raise
statement here.
I think this is starting to look pretty good, and it'd be great if some other people could have a look, because it's a pretty big change (@rizar, @dwf). It replaces the The advantage is that it effectively does what |
Maybe I need to rebase it because I made a lot of stupid commits changing things back and forth. |
Yeah, a rebase would be nice eventually. Also, I made a comment about a missing |
Oh, and we'll need a bunch of tests I guess. |
The main test I'd like to add is to make sure that #480 is fixed for both brick parameters as well as normal shared variables. |
… pickler helper instead
Import operator
Removed unused imports
For my log it decreases pickling time from 5.3s to 1.4s while dumping without a persistent id with |
Conflicts: tests/test_dump.py tests/test_examples.py
How can I create a pull request to two repositories simultaneously? Tests fail for |
The way I've done it is by making a pull request to |
Great, so two things left to do then:
def dump(obj, file_handler, protocol=DEFAULT_PROTOCOL,
persistent_id=PersistentParameterID, use_cpickle=False):
with closing(zipfile.ZipFile(file_handler, 'w', zipfile.ZIP_DEFLATED,
allowZip64=True)) as zip_file:
def func(f):
if use_cpickle:
p = cPickle.Pickler(f, protocol=protocol)
else:
p = PicklerWithWarning(f, protocol=protocol)
p.persistent_id = persistent_id(zip_file)
p.dump(obj)
pkl_utils.zipadd(func, zip_file, 'pkl') |
On my log in Python3 the slowdown from switching from |
Oops, my bad on the formatting errors, but at least tests are passing again. We need a bunch of tests now, because I realised that the previous PR was actually not storing a lot of things in the ZIP file (and the initial one was storing too many things...). @rizar You might want to re-run your benchmarks, and test the support for |
Needs to be rebased on top of #680 when that is merged |
#680 was merged, so could you rebase @dmitriy-serdyuk? Earlier the doctests were failing |
@@ -61,14 +61,14 @@ def main(save_to, num_batches, continue_=False): | |||
step_rule=Scale(learning_rate=0.001)), | |||
get_data_stream(range(100)), | |||
model=Model(cost), | |||
extensions=([LoadFromDump(save_to)] if continue_ else []) + | |||
extensions=([Load(save_to)] if continue_ else []) + |
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.
Oops, missed this line, needs to be deleted
fca03dc
to
9219c78
Compare
name = '{}.{}'.format( | ||
BRICK_DELIMITER.join([brick.name for brick in | ||
get_brick(obj).get_unique_path()]), | ||
[obj.name] |
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.
@dmitriy-serdyuk The current error is my bad, this should be obj.name
instead of [obj.name]
So there's only one thing left to do that I can think of, which is changing |
Update Load extension, small fixes
Via @bartvm in the comments on #685 (adding here for posterity):
[This PR] combines the previous
Checkpoint
extension (which pickled the main loop) and theDump
extension (which collected the values of the parameters and saved them to an NPZ file). It does so by making use of pickle's "persistent ID" support (which we already merged into Theano a while ago, Theano/Theano#2802).In practice, it means that during pickling, each shared variable gets intercepted by the
PersistentSharedVariableID
class and is assigned a name (if its owned by a brick, the path to the brick is used, otherwise the name of the shared variable is used). The value of the shared variable is saved as an NPY file to a ZIP file, together with the pickled main loop (which contains string references to the names of the parameters). The final result is a single ZIP file with a pickled main loop and a series of NumPy arrays containing the values of the all the shared variables contained within the main loop.Advantages over the old method:
Checkpoint
andDump
. This single method allows you to resume training seamlessly, while also allowing you to inspect and share parameter values across platforms; no unpickling needed.Dump
does.