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

Improve python wrapper #311

Merged
merged 28 commits into from
May 20, 2014
Merged

Improve python wrapper #311

merged 28 commits into from
May 20, 2014

Conversation

shelhamer
Copy link
Member

This work-in-progress seeks to polish the python wrapper, document, and include more detailed examples.

  • standardize python mean to binaryproto mean 47ec9ac
  • input preprocessing for Caffe (mean subtraction, scaling, channel order) 8da2a32, 96cd02d
  • automagic input and output blobs (no matter the number) 56ca978
  • batch inputs to net 1b23680
  • friendly forward/backward to prepare blobs/diffs and unpack output 0e5a5cf, ac5e6fa
  • handle ndarrays throughout, instead of lists 31907b5
  • properly separate responsibilities between the pycaffe wrapper, the imagenet wrapper example (wrapper.py) and detection example (detector.py)
  • rewrite examples for completeness to show how to format inputs, compute the forward pass, and read off outputs and extracted features
  • raise exceptions instead of crashing the library. Done in part by 76c2554 thanks to @longjon–adopted for blob arg checking in 9d4324e
  • document, with feeling: docstrings written. TODO: an introductory guide and discussion of blob wrapping / synced memory.
  • fix handling of gpu memory seems like gpu blob access is alright for now (provided Forward or Backward are called, since they will trigger syncing) see Improve python wrapper #311 (comment)
  • save example notebooks on durian for timing reference

@longjon
Copy link
Contributor

longjon commented Apr 10, 2014

Let's also make sure everything that should has a docstring once the interface is stable.

@shelhamer
Copy link
Member Author

@longjon re: gpu memory, it seems it's not actually an issue. Although mutable_cpu_data() is always used, further calls to Caffe actions like Forward() will sync properly. I checked by zeroing out params in gpu mode–the change was seen in the forward pass.

I don't know what problem I had before. This seems fine, unless I'm tired and somehow missing the issue.

@longjon
Copy link
Contributor

longjon commented Apr 10, 2014

Yes, I can confirm that, e.g., this works in GPU mode:

net.params['fc8'][0].data[...] = 0
net.ForwardPrefilled()   # or your preferred forward call

and this fits my model of what SyncedMemory is doing. However, note that referential transparency is subtly violated:

p = net.params['fc8'][0].data
net.ForwardPrefilled()
p[...] = 0
net.ForwardPrefilled()   # uses unchanged parameters

which makes me a bit uncomfortable. However, getting blobs has the same issue. In fact, in this code data behaves like a value:

bl = net.blobs['fc8'].data
net.ForwardPrefilled()
# bl does not contain up-to-date fc8, and writing to it has no effect

but in this code like a reference:

bl = net.blobs['fc8'].data
net.ForwardPrefilled()
bl2 = net.blobs['fc8'].data
# bl now contains the same data as bl2, and writing to it works

I think it's fine and probably the best compromise to keep things like they are, but to maximize sanity one should consider data to be a reference that becomes invalid whenever forward or backward are called (and this is true even for params, which don't change on forward/backward).

@shelhamer shelhamer mentioned this pull request Apr 13, 2014
@sergeyk sergeyk added this to the 1.0 milestone Apr 22, 2014
@shelhamer shelhamer changed the title Python wrapper polish Python wrapper improvements Apr 27, 2014
@shelhamer shelhamer changed the title Python wrapper improvements [WIP] Python wrapper improvements May 1, 2014
ilsvrc_2012_mean.npy has dims K x H x W.

Code written for the old D x D x K mean needs to be rewritten!
Do forward pass by prefilled or packaging input + output blobs and
returning a {output blob name: output list} dict.
Preserve the non-batch dimensions of blob arrays, even for singletons.

The forward() and backward() helpers take lists of ndarrays instead of a
single ndarray per blob, and lists of ndarrays are likewise returned.

Note that for output the blob array could actually be returned as a
single ndarray instead of a list.
@shelhamer
Copy link
Member Author

@longjon Please review my proposed changes to the caffe.Net interface. @sergeyk Let me know what you think too.

Rewriting the rest of pycaffe (imagenet and detector wrappers + examples) according to the new interface will follow.

@@ -1,6 +1,6 @@
// Copyright 2014 BVLC and contributors.
// pycaffe provides a wrapper of the caffe::Net class as well as some
// caffe::Caffe functions so that one could easily call it from Python.
// caffe::Caffe functions so that one could easily call it from python.
Copy link
Contributor

Choose a reason for hiding this comment

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

Really? See python.org, Python wiki page, etc. Just sayin'.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, fair enough. Capital is fine and used throughout now.

@longjon
Copy link
Contributor

longjon commented May 15, 2014

Always the standard for Python is four-space indents (PEP8 or Google style guide).

# Input preprocessing
Net.mean = {} # image mean (ndarray, input dimensional or broadcastable)
Net.input_scale = {} # for a model that expects data = input * input_scale
Net.channel_swap = {} # for RGB -> BGR and the like
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the axis permutation also be part of this?

Copy link
Member Author

Choose a reason for hiding this comment

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

H x W x K is the scikit-image standard, so I'm comfortable leaving it out. It'd be easy to add on the model of the existing options if it turns out to be annoying.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, H x W x K is pretty much the standard for images, I am thinking of non-image data. But I agree we don't need to be eager to add things that aren't used.

@longjon
Copy link
Contributor

longjon commented May 15, 2014

Some time ago I suggested (which I am now recalling and still partial to) moving the blob copying in forward from C++ to Python. This would mean that Net.forward could be simplified to (just sketching here)

for k, v in kwargs.iteritems():
    self.blobs[k].data[...] = np.asarray(v)
net._forward_prefilled()
# now deal with output

where CaffeNet.Forward goes away and CaffeNet.ForwardPrefilled gets renamed CaffeNet._forward_prefilled and need not be called by the user.

The advantages of doing it this way:

  • replace a bunch of C++ shenanigans with a few lines of Python
  • noncontiguous or non-float32 data are only copied once, whereas now they are copied twice
  • the extra checks and C++ exceptions simply go away, the exception for wrongly-sized input is automatic

What do you think?

@longjon
Copy link
Contributor

longjon commented May 15, 2014

Final note for now, regarding the blobs= mechanism. I see four ways this could be accomplished:

  • how it is now, specifying the blobs you want in addition to the output blobs as an argument
  • return the output blobs if you don't ask for anything specifically, otherwise return exactly what you asked for
  • always return output blobs only, the user can dig in net.blobs for anything else she wants
  • provide a mechanism (to=?) to stop computation at some layer, and always return the last computed blobs

The fourth (and optionally the second?) saves time if the user wants some intermediate level features and not later ones. Of course, that option can be implemented in addition to one of the others.

And three related comments:

  • the returned blobs have the caveats described above re: GPU mode (if we keep this let's document it)
  • it might be nice to be scolded if passing in a blob that will be clobbered in computation
  • I can't decide if the convenience justifies the grating-ness of blobs= + **kwargs. At least one can still run nets with blobs named blobs by filling in net.blobs oneself.

@shelhamer
Copy link
Member Author

return the output blobs if you don't ask for anything specifically, otherwise return exactly what you asked for

This is equally reasonable, and one can ask for outputs + additional blobs by blobs=self.outputs + ['conv2', 'pool5'] or the like. For now I'm keeping it as it is, since the output blobs are always computed so they might as well be packaged in.

user can dig in net.blobs for anything else she wants

While true, it feels a bit awkward, and if we're going to have a convenience method let's make it convenient.

provide a mechanism (to=?) to stop computation at some layer, and always return the last computed blobs
[...]
saves time if the user wants some intermediate level features and not later ones

This is a good idea... for the future. For now the usual workaround of defining the decapitated net and running it is fine (ok, more like "barely tolerable").

the returned blobs have the caveats described above re: GPU mode (if we keep this let's document it)

I'm not sure how to fix it right now, so let's document it. We should make docs pages for the python wrapper and matlab wrapper in general with details like this.

it might be nice to be scolded if passing in a blob that will be clobbered in computation

Right now you're yelled at if you don't pass all the named input blobs. I'm happy with that (feel free to add further checks). Now it's going to yell whenever the args don't match the input blobs.

I can't decide if the convenience justifies the grating-ness of blobs= + **kwargs. At least one can still run nets with blobs named blobs by filling in net.blobs oneself.

I can relate–the collusion of named keywords + **kwargs feels like sin. But it's nice and I've made my peace.

@shelhamer
Copy link
Member Author

moving the blob copying in forward from C++ to Python

This is an excellent idea. I'll do this, barring unforeseen issues.

Take blob args and give blob returns as single ndarrays instead of lists
of arrays.

Assign the net blobs and diffs as needed on the python side, which
reduces copies and simplifies the C++ side of the wrapper.

Thanks @longjon for the suggestion.
...and refer to inputs as inputs and not images since general vectors
and matrices are perfectly fine.
Don't run scripts in the module dir to avoid import collisions between
io and caffe.io.
For compositionality and expectations.
@shelhamer
Copy link
Member Author

Ready to brew with python.

@shelhamer shelhamer changed the title [WIP] Python wrapper improvements Improve python wrapper May 20, 2014
shelhamer added a commit that referenced this pull request May 20, 2014
@shelhamer shelhamer merged commit f048bea into BVLC:dev May 20, 2014
@shelhamer shelhamer deleted the python-fixes branch May 20, 2014 19:20
@shelhamer shelhamer restored the python-fixes branch May 20, 2014 19:21
@shelhamer shelhamer deleted the python-fixes branch May 20, 2014 19:22
mitmul pushed a commit to mitmul/caffe that referenced this pull request Sep 30, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants