Skip to content

Commit ae4a5b1

Browse files
committed
Merge pull request #2505 from ronghanghu/matcaffe3
MatCaffe: overhaul and improve the MATLAB interface
2 parents b12c171 + d07e5f7 commit ae4a5b1

34 files changed

+1425
-869
lines changed

Makefile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ NONGEN_CXX_SRCS := $(shell find \
6565
src/$(PROJECT) \
6666
include/$(PROJECT) \
6767
python/$(PROJECT) \
68-
matlab/$(PROJECT) \
68+
matlab/+$(PROJECT)/private \
6969
examples \
7070
tools \
7171
-name "*.cpp" -or -name "*.hpp" -or -name "*.cu" -or -name "*.cuh")
@@ -79,12 +79,12 @@ NONEMPTY_LINT_REPORT := $(BUILD_DIR)/$(LINT_EXT)
7979
PY$(PROJECT)_SRC := python/$(PROJECT)/_$(PROJECT).cpp
8080
PY$(PROJECT)_SO := python/$(PROJECT)/_$(PROJECT).so
8181
PY$(PROJECT)_HXX := include/$(PROJECT)/python_layer.hpp
82-
# MAT$(PROJECT)_SRC is the matlab wrapper for $(PROJECT)
83-
MAT$(PROJECT)_SRC := matlab/$(PROJECT)/mat$(PROJECT).cpp
82+
# MAT$(PROJECT)_SRC is the mex entrance point of matlab package for $(PROJECT)
83+
MAT$(PROJECT)_SRC := matlab/+$(PROJECT)/private/$(PROJECT)_.cpp
8484
ifneq ($(MATLAB_DIR),)
8585
MAT_SO_EXT := $(shell $(MATLAB_DIR)/bin/mexext)
8686
endif
87-
MAT$(PROJECT)_SO := matlab/$(PROJECT)/$(PROJECT).$(MAT_SO_EXT)
87+
MAT$(PROJECT)_SO := matlab/+$(PROJECT)/private/$(PROJECT)_.$(MAT_SO_EXT)
8888

8989
##############################
9090
# Derive generated files
@@ -118,7 +118,7 @@ GTEST_OBJ := $(addprefix $(BUILD_DIR)/, ${GTEST_SRC:.cpp=.o})
118118
EXAMPLE_OBJS := $(addprefix $(BUILD_DIR)/, ${EXAMPLE_SRCS:.cpp=.o})
119119
# Output files for automatic dependency generation
120120
DEPS := ${CXX_OBJS:.o=.d} ${CU_OBJS:.o=.d} ${TEST_CXX_OBJS:.o=.d} \
121-
${TEST_CU_OBJS:.o=.d}
121+
${TEST_CU_OBJS:.o=.d} $(BUILD_DIR)/${MAT$(PROJECT)_SO:.$(MAT_SO_EXT)=.d}
122122
# tool, example, and test bins
123123
TOOL_BINS := ${TOOL_OBJS:.o=.bin}
124124
EXAMPLE_BINS := ${EXAMPLE_OBJS:.o=.bin}
@@ -460,13 +460,19 @@ $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME)
460460
CXX="$(CXX)" \
461461
CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \
462462
CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@
463+
@ if [ -f "$(PROJECT)_.d" ]; then \
464+
mv -f $(PROJECT)_.d $(BUILD_DIR)/${MAT$(PROJECT)_SO:.$(MAT_SO_EXT)=.d}; \
465+
fi
463466

464467
runtest: $(TEST_ALL_BIN)
465468
$(TOOL_BUILD_DIR)/caffe
466469
$(TEST_ALL_BIN) $(TEST_GPUID) --gtest_shuffle $(TEST_FILTER)
467470

468471
pytest: py
469472
cd python; python -m unittest discover -s caffe/test
473+
474+
mattest: mat
475+
cd matlab; $(MATLAB_DIR)/bin/matlab -nodisplay -r 'caffe.run_tests(), exit()'
470476

471477
warn: $(EMPTY_WARN_REPORT)
472478

docs/tutorial/interfaces.md

Lines changed: 207 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,213 @@ Compile pycaffe by `make pycaffe`. The module dir caffe/python/caffe should be i
6767

6868
## MATLAB
6969

70-
The MATLAB interface -- matcaffe -- is the `caffe` mex and its helper m-files in caffe/matlab. Load models, do forward and backward, extract output and read-only model weights, and load the binaryproto format mean as a matrix.
70+
The MATLAB interface -- matcaffe -- is the `caffe` package in caffe/matlab in which you can integrate Caffe in your Matlab code.
7171

72-
A MATLAB demo is in caffe/matlab/caffe/matcaffe_demo.m
72+
In MatCaffe, you can
7373

74-
Note that MATLAB matrices and memory are in column-major layout counter to Caffe's row-major layout! Double-check your work accordingly.
74+
* Creating multiple Nets in Matlab
75+
* Do forward and backward computation
76+
* Access any layer within a network, and any parameter blob in a layer
77+
* Get and set data or diff to any blob within a network, not restricting to input blobs or output blobs
78+
* Save a network's parameters to file, and load parameters from file
79+
* Reshape a blob and reshape a network
80+
* Edit network parameter and do network surgery
81+
* Create multiple Solvers in Matlab for training
82+
* Resume training from solver snapshots
83+
* Access train net and test nets in a solver
84+
* Run for a certain number of iterations and give back control to Matlab
85+
* Intermingle arbitrary Matlab code with gradient steps
7586

76-
Compile matcaffe by `make matcaffe`.
87+
An ILSVRC image classification demo is in caffe/matlab/demo/classification_demo.m (you need to download BVLC CaffeNet from [Model Zoo](http://caffe.berkeleyvision.org/model_zoo.html) to run it).
88+
89+
### Build MatCaffe
90+
91+
Build MatCaffe with `make all matcaffe`. After that, you may test it using `make mattest`.
92+
93+
Common issue: if you run into error messages like `libstdc++.so.6:version 'GLIBCXX_3.4.15' not found` during `make mattest`, then it usually means that your Matlab's runtime libraries do not match your compile-time libraries. You may need to do the following before you start Matlab:
94+
95+
export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64
96+
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6
97+
98+
Or the equivalent based on where things are installed on your system, and do `make mattest` again to see if the issue is fixed. Note: this issue is sometimes more complicated since during its startup Matlab may overwrite your `LD_LIBRARY_PATH` environment variable. You can run `!ldd ./matlab/+caffe/private/caffe_.mexa64` (the mex extension may differ on your system) in Matlab to see its runtime libraries, and preload your compile-time libraries by exporting them to your `LD_PRELOAD` environment variable.
99+
100+
After successful building and testing, add this package to Matlab search PATH by starting `matlab` from caffe root folder and running the following commands in Matlab command window.
101+
102+
addpath ./matlab
103+
104+
You can save your Matlab search PATH by running `savepath` so that you don't have to run the command above again every time you use MatCaffe.
105+
106+
### Use MatCaffe
107+
108+
MatCaffe is very similar to PyCaffe in usage.
109+
110+
Examples below shows detailed usages and assumes you have downloaded BVLC CaffeNet from [Model Zoo](http://caffe.berkeleyvision.org/model_zoo.html) and started `matlab` from caffe root folder.
111+
112+
model = './models/bvlc_reference_caffenet/deploy.prototxt';
113+
weights = './models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel';
114+
115+
#### Set mode and device
116+
117+
**Mode and device should always be set BEFORE you create a net or a solver.**
118+
119+
Use CPU:
120+
121+
caffe.set_mode_cpu();
122+
123+
Use GPU and specify its gpu_id:
124+
125+
caffe.set_mode_gpu();
126+
caffe.set_device(gpu_id);
127+
128+
#### Create a network and access its layers and blobs
129+
130+
Create a network:
131+
132+
net = caffe.Net(model, weights, 'test'); % create net and load weights
133+
134+
Or
135+
136+
net = caffe.Net(model, 'test'); % create net but not load weights
137+
net.copy_from(weights); % load weights
138+
139+
which creates `net` object as
140+
141+
Net with properties:
142+
143+
layer_vec: [1x23 caffe.Layer]
144+
blob_vec: [1x15 caffe.Blob]
145+
inputs: {'data'}
146+
outputs: {'prob'}
147+
name2layer_index: [23x1 containers.Map]
148+
name2blob_index: [15x1 containers.Map]
149+
layer_names: {23x1 cell}
150+
blob_names: {15x1 cell}
151+
152+
The two `containers.Map` objects are useful to find the index of a layer or a blob by its name.
153+
154+
You have access to every blob in this network. To fill blob 'data' with all ones:
155+
156+
net.blobs('data').set_data(ones(net.blobs('data').shape));
157+
158+
To multiply all values in blob 'data' by 10:
159+
160+
net.blobs('data').set_data(net.blobs('data').get_data() * 10);
161+
162+
**Be aware that since Matlab is 1-indexed and column-major, the usual 4 blob dimensions in Matlab are `[width, height, channels, num]`, and `width` is the fastest dimension. Also be aware that images are in BGR channels.** Also, Caffe uses single-precision float data. If your data is not single, `set_data` will automatically convert it to single.
163+
164+
You also have access to every layer, so you can do network surgery. For example, to multiply conv1 parameters by 10:
165+
166+
net.params('conv1', 1).set_data(net.params('conv1', 1).get_data() * 10); % set weights
167+
net.params('conv1', 2).set_data(net.params('conv1', 2).get_data() * 10); % set bias
168+
169+
Alternatively, you can use
170+
171+
net.layers('conv1').params(1).set_data(net.layers('conv1').params(1).get_data() * 10);
172+
net.layers('conv1').params(2).set_data(net.layers('conv1').params(2).get_data() * 10);
173+
174+
To save the network you just modified:
175+
176+
net.save('my_net.caffemodel');
177+
178+
To get a layer's type (string):
179+
180+
layer_type = net.layers('conv1').type;
181+
182+
#### Forward and backward
183+
184+
Forward pass can be done using `net.forward` or `net.forward_prefilled`. Function `net.forward` takes in a cell array of N-D arrays containing data of input blob(s) and outputs a cell array containing data from output blob(s). Function `net.forward_prefilled` uses existing data in input blob(s) during forward pass, takes no input and produces no output. After creating some data for input blobs like `data = rand(net.blobs('data').shape);` you can run
185+
186+
res = net.forward({data});
187+
prob = res{1};
188+
189+
Or
190+
191+
net.blobs('data').set_data(data);
192+
net.forward_prefilled();
193+
prob = net.blobs('prob').get_data();
194+
195+
Backward is similar using `net.backward` or `net.backward_prefilled` and replacing `get_data` and `set_data` with `get_diff` and `set_diff`. After creating some gradients for output blobs like `prob_diff = rand(net.blobs('prob').shape);` you can run
196+
197+
res = net.backward({prob_diff});
198+
data_diff = res{1};
199+
200+
Or
201+
202+
net.blobs('prob').set_diff(prob_diff);
203+
net.backward_prefilled();
204+
data_diff = net.blobs('data').get_diff();
205+
206+
**However, the backward computation above doesn't get correct results, because Caffe decides that the network does not need backward computation. To get correct backward results, you need to set `'force_backward: true'` in your network prototxt.**
207+
208+
After performing forward or backward pass, you can also get the data or diff in internal blobs. For example, to extract pool5 features after forward pass:
209+
210+
pool5_feat = net.blobs('pool5').get_data();
211+
212+
#### Reshape
213+
214+
Assume you want to run 1 image at a time instead of 10:
215+
216+
net.blobs('data').reshape([227 227 3 1]); % reshape blob 'data'
217+
net.reshape();
218+
219+
Then the whole network is reshaped, and now `net.blobs('prob').shape` should be `[1000 1]`;
220+
221+
#### Training
222+
223+
Assume you have created training and validation lmdbs following our [ImageNET Tutorial](http://caffe.berkeleyvision.org/gathered/examples/imagenet.html), to create a solver and train on ILSVRC 2012 classification dataset:
224+
225+
solver = caffe.Solver('./models/bvlc_reference_caffenet/solver.prototxt');
226+
227+
which creates `solver` object as
228+
229+
Solver with properties:
230+
231+
net: [1x1 caffe.Net]
232+
test_nets: [1x1 caffe.Net]
233+
234+
To train:
235+
236+
solver.solve();
237+
238+
Or train for only 1000 iterations (so that you can do something to its net before training more iterations)
239+
240+
solver.step(1000);
241+
242+
To get iteration number:
243+
244+
iter = solver.iter();
245+
246+
To get its network:
247+
248+
train_net = solver.net;
249+
test_net = solver.test_nets(1);
250+
251+
To resume from a snapshot "your_snapshot.solverstate":
252+
253+
solver.restore('your_snapshot.solverstate');
254+
255+
#### Input and output
256+
257+
`caffe.io` class provides basic input functions `load_image` and `read_mean`. For example, to read ILSVRC 2012 mean file (assume you have downloaded imagenet example auxiliary files by running `./data/ilsvrc12/get_ilsvrc_aux.sh`):
258+
259+
mean_data = caffe.io.read_mean('./data/ilsvrc12/imagenet_mean.binaryproto');
260+
261+
To read Caffe's example image and resize to `[width, height]` and suppose we want `width = 256; height = 256;`
262+
263+
im_data = caffe.io.load_image('./examples/images/cat.jpg');
264+
im_data = imresize(im_data, [width, height]); % resize using Matlab's imresize
265+
266+
**Keep in mind that `width` is the fastest dimension and channels are BGR, which is different from the usual way that Matlab stores an image.** If you don't want to use `caffe.io.load_image` and prefer to load an image by yourself, you can do
267+
268+
im_data = imread('./examples/images/cat.jpg'); % read image
269+
im_data = im_data(:, :, [3, 2, 1]); % convert from RGB to BGR
270+
im_data = permute(im_data, [2, 1, 3]); % permute width and height
271+
im_data = single(im_data); % convert to single precision
272+
273+
Also, you may take a look at caffe/matlab/demo/classification_demo.m to see how to prepare input by taking crops from an image.
274+
275+
We show in caffe/matlab/hdf5creation how to read and write HDF5 data with Matlab. We do not provide extra functions for data output as Matlab itself is already quite powerful in output.
276+
277+
#### Clear nets and solvers
278+
279+
Call `caffe.reset_all()` to clear all solvers and stand-alone nets you have created.

matlab/+caffe/+test/test_net.m

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
classdef test_net < matlab.unittest.TestCase
2+
3+
properties
4+
num_output
5+
model_file
6+
net
7+
end
8+
9+
methods (Static)
10+
function model_file = simple_net_file(num_output)
11+
model_file = tempname();
12+
fid = fopen(model_file, 'w');
13+
fprintf(fid, [ ...
14+
'name: "testnet" force_backward: true\n' ...
15+
'layer { type: "DummyData" name: "data" top: "data" top: "label"\n' ...
16+
'dummy_data_param { num: 5 channels: 2 height: 3 width: 4\n' ...
17+
' num: 5 channels: 1 height: 1 width: 1\n' ...
18+
' data_filler { type: "gaussian" std: 1 }\n' ...
19+
' data_filler { type: "constant" } } }\n' ...
20+
'layer { type: "Convolution" name: "conv" bottom: "data" top: "conv"\n' ...
21+
' convolution_param { num_output: 11 kernel_size: 2 pad: 3\n' ...
22+
' weight_filler { type: "gaussian" std: 1 }\n' ...
23+
' bias_filler { type: "constant" value: 2 } }\n' ...
24+
' param { decay_mult: 1 } param { decay_mult: 0 }\n' ...
25+
' }\n' ...
26+
'layer { type: "InnerProduct" name: "ip" bottom: "conv" top: "ip"\n' ...
27+
' inner_product_param { num_output: ' num2str(num_output) ...
28+
' weight_filler { type: "gaussian" std: 2.5 }\n' ...
29+
' bias_filler { type: "constant" value: -3 } } }\n' ...
30+
'layer { type: "SoftmaxWithLoss" name: "loss" bottom: "ip" bottom: "label"\n' ...
31+
' top: "loss" }' ]);
32+
fclose(fid);
33+
end
34+
end
35+
methods
36+
function self = test_net()
37+
self.num_output = 13;
38+
self.model_file = caffe.test.test_net.simple_net_file(self.num_output);
39+
self.net = caffe.Net(self.model_file, 'train');
40+
% also make sure get_solver runs
41+
caffe.get_net(self.model_file, 'train');
42+
43+
% fill in valid labels
44+
self.net.blobs('label').set_data(randi( ...
45+
self.num_output - 1, self.net.blobs('label').shape));
46+
47+
delete(self.model_file);
48+
end
49+
end
50+
methods (Test)
51+
function self = test_blob(self)
52+
self.net.blobs('data').set_data(10 * ones(self.net.blobs('data').shape));
53+
self.verifyEqual(self.net.blobs('data').get_data(), ...
54+
10 * ones(self.net.blobs('data').shape, 'single'));
55+
self.net.blobs('data').set_diff(-2 * ones(self.net.blobs('data').shape));
56+
self.verifyEqual(self.net.blobs('data').get_diff(), ...
57+
-2 * ones(self.net.blobs('data').shape, 'single'));
58+
original_shape = self.net.blobs('data').shape;
59+
self.net.blobs('data').reshape([6 5 4 3 2 1]);
60+
self.verifyEqual(self.net.blobs('data').shape, [6 5 4 3 2 1]);
61+
self.net.blobs('data').reshape(original_shape);
62+
self.net.reshape();
63+
end
64+
function self = test_layer(self)
65+
self.verifyEqual(self.net.params('conv', 1).shape, [2 2 2 11]);
66+
self.verifyEqual(self.net.layers('conv').params(2).shape, 11);
67+
self.verifyEqual(self.net.layers('conv').type(), 'Convolution');
68+
end
69+
function test_forward_backward(self)
70+
self.net.forward_prefilled();
71+
self.net.backward_prefilled();
72+
end
73+
function test_inputs_outputs(self)
74+
self.verifyEqual(self.net.inputs, cell(0, 1))
75+
self.verifyEqual(self.net.outputs, {'loss'});
76+
end
77+
function test_save_and_read(self)
78+
weights_file = tempname();
79+
self.net.save(weights_file);
80+
model_file2 = caffe.test.test_net.simple_net_file(self.num_output);
81+
net2 = caffe.Net(model_file2, 'train');
82+
net2.copy_from(weights_file);
83+
net3 = caffe.Net(model_file2, weights_file, 'train');
84+
delete(model_file2);
85+
delete(weights_file);
86+
for l = 1:length(self.net.layer_vec)
87+
for i = 1:length(self.net.layer_vec(l).params)
88+
self.verifyEqual(self.net.layer_vec(l).params(i).get_data(), ...
89+
net2.layer_vec(l).params(i).get_data());
90+
self.verifyEqual(self.net.layer_vec(l).params(i).get_data(), ...
91+
net3.layer_vec(l).params(i).get_data());
92+
end
93+
end
94+
end
95+
end
96+
end

0 commit comments

Comments
 (0)