From ee0c93137472981554ad30a766a1542cf155bf7b Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Fri, 22 May 2015 01:45:14 +0800 Subject: [PATCH 1/8] MatCaffe3 : a powerful matlab interface for caffe Added matcaffe3, a powerful matlab interface. To test it, run 'make mattest' --- Makefile | 22 +- matlab/+caffe/+test/test_net.m | 74 +++++ matlab/+caffe/+test/test_solver.m | 43 +++ matlab/+caffe/Blob.m | 71 ++++ matlab/+caffe/Layer.m | 32 ++ matlab/+caffe/Net.m | 133 ++++++++ matlab/+caffe/Solver.m | 56 ++++ matlab/+caffe/get_net.m | 37 +++ matlab/+caffe/get_solver.m | 11 + matlab/+caffe/io.m | 22 ++ matlab/+caffe/private/CHECK.m | 7 + matlab/+caffe/private/CHECK_FILE_EXIST.m | 7 + matlab/+caffe/private/caffe_.cpp | 546 +++++++++++++++++++++++++++++++ matlab/+caffe/private/is_valid_handle.m | 28 ++ matlab/+caffe/reset.m | 8 + matlab/+caffe/run_tests.m | 12 + matlab/+caffe/set_device.m | 11 + matlab/+caffe/set_mode_cpu.m | 7 + matlab/+caffe/set_mode_gpu.m | 7 + matlab/CMakeLists.txt | 8 + 20 files changed, 1141 insertions(+), 1 deletion(-) create mode 100644 matlab/+caffe/+test/test_net.m create mode 100644 matlab/+caffe/+test/test_solver.m create mode 100644 matlab/+caffe/Blob.m create mode 100644 matlab/+caffe/Layer.m create mode 100644 matlab/+caffe/Net.m create mode 100644 matlab/+caffe/Solver.m create mode 100644 matlab/+caffe/get_net.m create mode 100644 matlab/+caffe/get_solver.m create mode 100644 matlab/+caffe/io.m create mode 100644 matlab/+caffe/private/CHECK.m create mode 100644 matlab/+caffe/private/CHECK_FILE_EXIST.m create mode 100644 matlab/+caffe/private/caffe_.cpp create mode 100644 matlab/+caffe/private/is_valid_handle.m create mode 100644 matlab/+caffe/reset.m create mode 100644 matlab/+caffe/run_tests.m create mode 100644 matlab/+caffe/set_device.m create mode 100644 matlab/+caffe/set_mode_cpu.m create mode 100644 matlab/+caffe/set_mode_gpu.m diff --git a/Makefile b/Makefile index d2e5e5720ed..74b167763c3 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ NONGEN_CXX_SRCS := $(shell find \ include/$(PROJECT) \ python/$(PROJECT) \ matlab/$(PROJECT) \ + matlab/+$(PROJECT)/private \ examples \ tools \ -name "*.cpp" -or -name "*.hpp" -or -name "*.cu" -or -name "*.cuh") @@ -81,10 +82,13 @@ PY$(PROJECT)_SO := python/$(PROJECT)/_$(PROJECT).so PY$(PROJECT)_HXX := include/$(PROJECT)/python_layer.hpp # MAT$(PROJECT)_SRC is the matlab wrapper for $(PROJECT) MAT$(PROJECT)_SRC := matlab/$(PROJECT)/mat$(PROJECT).cpp +# MAT$(PROJECT)_PKG_SRC is the mex entrance point of matlab package for $(PROJECT) +MAT$(PROJECT)_PKG_SRC := matlab/+$(PROJECT)/private/$(PROJECT)_.cpp ifneq ($(MATLAB_DIR),) MAT_SO_EXT := $(shell $(MATLAB_DIR)/bin/mexext) endif MAT$(PROJECT)_SO := matlab/$(PROJECT)/$(PROJECT).$(MAT_SO_EXT) +MAT$(PROJECT)_PKG_SO := matlab/+$(PROJECT)/private/$(PROJECT)_.$(MAT_SO_EXT) ############################## # Derive generated files @@ -447,7 +451,7 @@ $(PY$(PROJECT)_SO): $(PY$(PROJECT)_SRC) $(PY$(PROJECT)_HXX) | $(DYNAMIC_NAME) mat$(PROJECT): mat -mat: $(MAT$(PROJECT)_SO) +mat: $(MAT$(PROJECT)_SO) $(MAT$(PROJECT)_PKG_SO) $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME) @ if [ -z "$(MATLAB_DIR)" ]; then \ @@ -460,6 +464,18 @@ $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME) CXX="$(CXX)" \ CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \ CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@ + +$(MAT$(PROJECT)_PKG_SO): $(MAT$(PROJECT)_PKG_SRC) $(STATIC_NAME) + @ if [ -z "$(MATLAB_DIR)" ]; then \ + echo "MATLAB_DIR must be specified in $(CONFIG_FILE)" \ + "to build mat$(PROJECT)."; \ + exit 1; \ + fi + @ echo MEX $< + $(Q)$(MATLAB_DIR)/bin/mex $(MAT$(PROJECT)_PKG_SRC) \ + CXX="$(CXX)" \ + CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \ + CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@ runtest: $(TEST_ALL_BIN) $(TOOL_BUILD_DIR)/caffe @@ -467,6 +483,9 @@ runtest: $(TEST_ALL_BIN) pytest: py cd python; python -m unittest discover -s caffe/test + +mattest: mat + cd matlab; $(MATLAB_DIR)/bin/matlab -nodisplay -r 'caffe.run_tests(), exit()' warn: $(EMPTY_WARN_REPORT) @@ -582,6 +601,7 @@ clean: @- $(RM) -rf $(DISTRIBUTE_DIR) @- $(RM) $(PY$(PROJECT)_SO) @- $(RM) $(MAT$(PROJECT)_SO) + @- $(RM) $(MAT$(PROJECT)_PKG_SO) supercleanfiles: $(eval SUPERCLEAN_FILES := $(strip \ diff --git a/matlab/+caffe/+test/test_net.m b/matlab/+caffe/+test/test_net.m new file mode 100644 index 00000000000..4958d50f9e3 --- /dev/null +++ b/matlab/+caffe/+test/test_net.m @@ -0,0 +1,74 @@ +classdef test_net < matlab.unittest.TestCase + + properties + num_output + model_file + net + end + + methods (Static) + function model_file = simple_net_file(num_output) + model_file = tempname(); + fid = fopen(model_file, 'w'); + fprintf(fid, [ ... + 'name: "testnet" force_backward: true\n' ... + 'layer { type: "DummyData" name: "data" top: "data" top: "label"\n' ... + 'dummy_data_param { num: 5 channels: 2 height: 3 width: 4\n' ... + ' num: 5 channels: 1 height: 1 width: 1\n' ... + ' data_filler { type: "gaussian" std: 1 }\n' ... + ' data_filler { type: "constant" } } }\n' ... + 'layer { type: "Convolution" name: "conv" bottom: "data" top: "conv"\n' ... + ' convolution_param { num_output: 11 kernel_size: 2 pad: 3\n' ... + ' weight_filler { type: "gaussian" std: 1 }\n' ... + ' bias_filler { type: "constant" value: 2 } }\n' ... + ' param { decay_mult: 1 } param { decay_mult: 0 }\n' ... + ' }\n' ... + 'layer { type: "InnerProduct" name: "ip" bottom: "conv" top: "ip"\n' ... + ' inner_product_param { num_output: ' num2str(num_output) ... + ' weight_filler { type: "gaussian" std: 2.5 }\n' ... + ' bias_filler { type: "constant" value: -3 } } }\n' ... + 'layer { type: "SoftmaxWithLoss" name: "loss" bottom: "ip" bottom: "label"\n' ... + ' top: "loss" }' ]); + fclose(fid); + end + end + methods + function self = test_net() + self.num_output = 13; + self.model_file = caffe.test.test_net.simple_net_file(self.num_output); + self.net = caffe.Net(self.model_file, 'train'); + % also make sure get_solver runs + caffe.get_net(self.model_file, 'train'); + + % fill in valid labels + self.net.blobs('label').set_data(randi( ... + self.num_output - 1, self.net.blobs('label').shape)); + + delete(self.model_file); + end + end + methods (Test) + function test_forward_backward(self) + self.net.forward_prefilled(); + self.net.backward_prefilled(); + end + function test_inputs_outputs(self) + self.verifyEqual(self.net.inputs, cell(0, 1)) + self.verifyEqual(self.net.outputs, {'loss'}); + end + function test_save_and_read(self) + weights_file = tempname(); + self.net.save(weights_file); + model_file2 = caffe.test.test_net.simple_net_file(self.num_output); + net2 = caffe.Net(model_file2, weights_file, 'train'); + delete(model_file2); + delete(weights_file); + for l = 1:length(self.net.layer_vec) + for i = 1:length(self.net.layer_vec(l).params) + self.verifyEqual(self.net.layer_vec(l).params(i).get_data(), ... + net2.layer_vec(l).params(i).get_data()); + end + end + end + end +end \ No newline at end of file diff --git a/matlab/+caffe/+test/test_solver.m b/matlab/+caffe/+test/test_solver.m new file mode 100644 index 00000000000..9774ec1d2fd --- /dev/null +++ b/matlab/+caffe/+test/test_solver.m @@ -0,0 +1,43 @@ +classdef test_solver < matlab.unittest.TestCase + + properties + num_output + solver + end + + methods + function self = test_solver() + self.num_output = 13; + model_file = caffe.test.test_net.simple_net_file(self.num_output); + solver_file = tempname(); + + fid = fopen(solver_file, 'w'); + fprintf(fid, [ ... + 'net: "' model_file '"\n' ... + 'test_iter: 10 test_interval: 10 base_lr: 0.01 momentum: 0.9\n' ... + 'weight_decay: 0.0005 lr_policy: "inv" gamma: 0.0001 power: 0.75\n' ... + 'display: 100 max_iter: 100 snapshot_after_train: false\n' ]); + fclose(fid); + + self.solver = caffe.Solver(solver_file); + % also make sure get_solver runs + caffe.get_solver(solver_file); + caffe.set_mode_cpu(); + % fill in valid labels + self.solver.net.blobs('label').set_data(randi( ... + self.num_output - 1, self.solver.net.blobs('label').shape)); + self.solver.test_nets(1).blobs('label').set_data(randi( ... + self.num_output - 1, self.solver.test_nets(1).blobs('label').shape)); + + delete(solver_file); + delete(model_file); + end + end + methods (Test) + function test_solve(self) + self.verifyEqual(self.solver.iter(), 0) + self.solver.solve() + self.verifyEqual(self.solver.iter(), 100) + end + end +end \ No newline at end of file diff --git a/matlab/+caffe/Blob.m b/matlab/+caffe/Blob.m new file mode 100644 index 00000000000..a6326d1a36c --- /dev/null +++ b/matlab/+caffe/Blob.m @@ -0,0 +1,71 @@ +classdef Blob < handle + % Wrapper class of caffe::Blob in matlab + + properties (Access = private) + hBlob_self + end + + methods + function self = Blob(hBlob_blob) + CHECK(is_valid_handle(hBlob_blob), 'invalid input handle'); + + % setup self handle and attributes + self.hBlob_self = hBlob_blob; + end + function shape = shape(self) + shape = caffe_('blob_get_shape', self.hBlob_self); + end + function reshape(self, shape) + shape = self.check_and_preprocess_shape(shape); + caffe_('blob_reshape', self.hBlob_self, shape); + end + function data = get_data(self) + data = caffe_('blob_get_data', self.hBlob_self); + end + function set_data(self, data) + data = self.check_and_preprocess_data(data); + caffe_('blob_set_data', self.hBlob_self, data); + end + function diff = get_diff(self) + diff = caffe_('blob_get_diff', self.hBlob_self); + end + function set_diff(self, diff) + diff = self.check_and_preprocess_data(diff); + caffe_('blob_set_diff', self.hBlob_self, diff); + end + end + + methods (Access = private) + function shape = check_and_preprocess_shape(~, shape) + CHECK(isempty(shape) || isnumeric(shape) && isrow(shape), ... + 'shape must be a integer row vector'); + shape = double(shape); + end + function data = check_and_preprocess_data(self, data) + CHECK(isnumeric(data), 'data or diff must be numeric types'); + self.check_data_size_matches(data) + data = single(data); + end + function check_data_size_matches(self, data) + % check whether size of data matches shape of this blob + % note: matlab arrays always have at least 2 dimensions. To compare + % shape between size of data and shape of this blob, extend shape of + % this blob to have at least 2 dimensions + data_size = size(data); + self_shape_extended = self.shape; + if isempty(self_shape_extended) + % target blob is a scalar (0 dim) + self_shape_extended = [1, 1]; + elseif isscalar(self_shape_extended) + % target blob is a vector (1 dim) + self_shape_extended = [self_shape_extended, 1]; + end + is_matched = (length(self_shape_extended) == length(data_size)) ... + && all(self_shape_extended == data_size); + CHECK(is_matched, ... + sprintf('%s, data size: [ %s], blob shape: [ %s]', ... + 'data size does not match blob shape', ... + sprintf('%d ', data_size), sprintf('%d ', self_shape_extended))); + end + end +end \ No newline at end of file diff --git a/matlab/+caffe/Layer.m b/matlab/+caffe/Layer.m new file mode 100644 index 00000000000..7587ed7ddf7 --- /dev/null +++ b/matlab/+caffe/Layer.m @@ -0,0 +1,32 @@ +classdef Layer < handle + % Wrapper class of caffe::Layer in matlab + + properties (Access = private) + hLayer_self + attributes + % attributes fields: + % hBlob_blobs + end + properties (SetAccess = private) + params + end + + methods + function self = Layer(hLayer_layer) + CHECK(is_valid_handle(hLayer_layer), 'invalid input handle'); + + % setup self handle and attributes + self.hLayer_self = hLayer_layer; + self.attributes = caffe_('layer_get_attr', self.hLayer_self); + + % setup weights + self.params = caffe.Blob.empty(); + for n = 1:length(self.attributes.hBlob_blobs) + self.params(n) = caffe.Blob(self.attributes.hBlob_blobs(n)); + end + end + function layer_type = type(self) + layer_type = caffe_('layer_get_type', self.hLayer_self); + end + end +end diff --git a/matlab/+caffe/Net.m b/matlab/+caffe/Net.m new file mode 100644 index 00000000000..5319634dc8b --- /dev/null +++ b/matlab/+caffe/Net.m @@ -0,0 +1,133 @@ +classdef Net < handle + % Wrapper class of caffe::Net in matlab + + properties (Access = private) + hNet_self + attributes + % attribute fields + % hLayer_layers + % hBlob_blobs + % input_blob_indices + % output_blob_indices + % layer_names + % blob_names + end + properties (SetAccess = private) + layer_vec + blob_vec + inputs + outputs + name2layer_index + name2blob_index + layer_names + blob_names + end + + methods + function self = Net(varargin) + % decide whether to construct a net from model_file or handle + if ~(nargin == 1 && isstruct(varargin{1})) + % construct a net from model_file + self = caffe.get_net(varargin{:}); + return + end + % construct a net from handle + hNet_net = varargin{1}; + CHECK(is_valid_handle(hNet_net), 'invalid input handle'); + + % setup self handle and attributes + self.hNet_self = hNet_net; + self.attributes = caffe_('net_get_attr', self.hNet_self); + + % setup layer_vec + self.layer_vec = caffe.Layer.empty(); + for n = 1:length(self.attributes.hLayer_layers) + self.layer_vec(n) = caffe.Layer(self.attributes.hLayer_layers(n)); + end + + % setup blob_vec + self.blob_vec = caffe.Blob.empty(); + for n = 1:length(self.attributes.hBlob_blobs); + self.blob_vec(n) = caffe.Blob(self.attributes.hBlob_blobs(n)); + end + + % setup input and output blob and their names + % note: add 1 to indices as matlab is 1-indexed while C++ is 0-indexed + self.inputs = ... + self.attributes.blob_names(self.attributes.input_blob_indices + 1); + self.outputs = ... + self.attributes.blob_names(self.attributes.output_blob_indices + 1); + + % create map objects to map from name to layers and blobs + self.name2layer_index = containers.Map(self.attributes.layer_names, ... + 1:length(self.attributes.layer_names)); + self.name2blob_index = containers.Map(self.attributes.blob_names, ... + 1:length(self.attributes.blob_names)); + + % expose layer_names and blob_names for public access + self.layer_names = self.attributes.layer_names; + self.blob_names = self.attributes.blob_names; + end + function layer = layers(self, layer_name) + CHECK(ischar(layer_name), 'layer_name must be a string'); + layer = self.layer_vec(self.name2layer_index(layer_name)); + end + function blob = blobs(self, blob_name) + CHECK(ischar(blob_name), 'blob_name must be a string'); + blob = self.blob_vec(self.name2blob_index(blob_name)); + end + function blob = params(self, layer_name, blob_index) + CHECK(ischar(layer_name), 'layer_name must be a string'); + CHECK(isscalar(blob_index), 'blob_index must be a scalar'); + blob = self.layer_vec(self.name2layer_index(layer_name)).params(blob_index); + end + function forward_prefilled(self) + caffe_('net_forward', self.hNet_self); + end + function backward_prefilled(self) + caffe_('net_backward', self.hNet_self); + end + function res = forward(self, input_data) + CHECK(iscell(input_data), 'input_data must be a cell array'); + CHECK(length(input_data) == length(self.inputs), ... + 'input data cell length must match input blob number'); + % copy data to input_blobs + for n = 1:length(self.inputs) + self.blobs(self.inputs{n}).set_data(input_data{n}); + end + self.forward_prefilled(); + % retrieve data from output_blobs + res = cell(length(self.outputs), 1); + for n = 1:length(self.outputs) + res{n} = self.blobs(self.outputs{n}).get_data(); + end + end + function res = backward(self, output_diff) + CHECK(iscell(output_diff), 'output_diff must be a cell array'); + CHECK(length(output_diff) == length(self.outputs), ... + 'output diff cell length must match output blob number'); + % copy diff to output_blobs + for n = 1:length(self.outputs) + self.blobs(self.outputs{n}).set_diff(output_diff{n}); + end + self.backward_prefilled(); + % retrieve diff from input_blobs + res = cell(length(self.inputs), 1); + for n = 1:length(self.inputs) + res{n} = self.blobs(self.inputs{n}).get_diff(); + end + end + function copy_from(self, weights_file) + CHECK(ischar(weights_file), 'weights_file must be a string'); + CHECK_FILE_EXIST(weights_file); + caffe_('net_copy_from', self.hNet_self, weights_file); + end + function reshape(self) + caffe_('net_reshape', self.hNet_self); + end + function save(self, weights_file) + CHECK(ischar(weights_file), 'weights_file must be a string'); + caffe_('net_save', self.hNet_self, weights_file); + end + end +end diff --git a/matlab/+caffe/Solver.m b/matlab/+caffe/Solver.m new file mode 100644 index 00000000000..80fa5394739 --- /dev/null +++ b/matlab/+caffe/Solver.m @@ -0,0 +1,56 @@ +classdef Solver < handle + % Wrapper class of caffe::SGDSolver in matlab + + properties (Access = private) + hSolver_self + attributes + % attribute fields + % hNet_net + % hNet_test_nets + end + properties (SetAccess = private) + net + test_nets + end + + methods + function self = Solver(varargin) + % decide whether to construct a solver from solver_file or handle + if ~(nargin == 1 && isstruct(varargin{1})) + % construct a solver from solver_file + self = caffe.get_solver(varargin{:}); + return + end + % construct a solver from handle + hSolver_solver = varargin{1}; + CHECK(is_valid_handle(hSolver_solver), 'invalid input handle'); + + % setup self handle and attributes + self.hSolver_self = hSolver_solver; + self.attributes = caffe_('solver_get_attr', self.hSolver_self); + + % setup net and test_nets + self.net = caffe.Net(self.attributes.hNet_net); + self.test_nets = caffe.Net.empty(); + for n = 1:length(self.attributes.hNet_test_nets) + self.test_nets(n) = caffe.Net(self.attributes.hNet_test_nets(n)); + end + end + function iter = iter(self) + iter = caffe_('solver_get_iter', self.hSolver_self); + end + function restore(self, snapshot_filename) + CHECK(ischar(snapshot_filename), 'snapshot_filename must be a string'); + CHECK_FILE_EXIST(snapshot_filename) + caffe_('solver_restore', self.hSolver_self, snapshot_filename); + end + function solve(self) + caffe_('solver_solve', self.hSolver_self); + end + function step(self, iters) + CHECK(isscalar(iters) && iters > 0, 'iters must be positive integer'); + iters = double(iters); + caffe_('solver_step', self.hSolver_self, iters); + end + end +end diff --git a/matlab/+caffe/get_net.m b/matlab/+caffe/get_net.m new file mode 100644 index 00000000000..04872b263c8 --- /dev/null +++ b/matlab/+caffe/get_net.m @@ -0,0 +1,37 @@ +function net = get_net(varargin) +% net = get_net(model_file, phase_name) or +% net = get_net(model_file, weights_file, phase_name) +% Construct a net from model_file, and load weights from weights_file +% phase_name can only be 'train' or 'test' + +CHECK(nargin == 2 || nargin == 3, ['usage: ' ... + 'net = get_net(model_file, phase_name) or ', ... + 'net = get_net(model_file, weights_file, phase_name)']); +if nargin == 3 + model_file = varargin{1}; + weights_file = varargin{2}; + phase_name = varargin{3}; +elseif nargin == 2 + model_file = varargin{1}; + phase_name = varargin{2}; +end + +CHECK(ischar(model_file), 'model_file must be a string'); +CHECK(ischar(phase_name), 'phase_name must be a string'); +CHECK_FILE_EXIST(model_file); +CHECK(strcmp(phase_name, 'train') || strcmp(phase_name, 'test'), ... + sprintf('phase_name can only be %strain%s or %stest%s', ... + char(39), char(39), char(39), char(39))); + +hNet = caffe_('get_net', model_file, phase_name); +net = caffe.Net(hNet); + +% load weights from weights_file +if nargin == 3 + CHECK(ischar(weights_file), 'weights_file must be a string'); + CHECK_FILE_EXIST(weights_file); + net.copy_from(weights_file); +end + +end + diff --git a/matlab/+caffe/get_solver.m b/matlab/+caffe/get_solver.m new file mode 100644 index 00000000000..855709c725e --- /dev/null +++ b/matlab/+caffe/get_solver.m @@ -0,0 +1,11 @@ +function solver = get_solver(solver_file) +% solver = get_solver(solver_file) +% Construct a Solver object from solver_file + +CHECK(ischar(solver_file), 'solver_file must be a string'); +CHECK_FILE_EXIST(solver_file); + +pSolver = caffe_('get_solver', solver_file); +solver = caffe.Solver(pSolver); + +end \ No newline at end of file diff --git a/matlab/+caffe/io.m b/matlab/+caffe/io.m new file mode 100644 index 00000000000..95d58909abd --- /dev/null +++ b/matlab/+caffe/io.m @@ -0,0 +1,22 @@ +classdef io + % a class for input and output functions + + methods (Static) + function im_data = load_image(im_file) + CHECK(ischar(im_file), 'im_file must be a string'); + CHECK_FILE_EXIST(im_file); + % load an image from disk into Caffe-supported data format + % switch channels from RGB to BGR, make width the fastest dimension, and + % convert to single + im = imread(im_file); + im_data = im(:, :, [3, 2, 1]); + im_data = permute(im_data, [2 1 3]); + im_data = single(im_data); + end + function mean_data = read_mean(mean_proto_file) + CHECK(ischar(mean_proto_file), 'im_file must be a string'); + CHECK_FILE_EXIST(mean_proto_file); + mean_data = caffe_('read_mean', mean_proto_file); + end + end +end \ No newline at end of file diff --git a/matlab/+caffe/private/CHECK.m b/matlab/+caffe/private/CHECK.m new file mode 100644 index 00000000000..1cf21032b36 --- /dev/null +++ b/matlab/+caffe/private/CHECK.m @@ -0,0 +1,7 @@ +function CHECK(expr, error_msg) + +if ~expr + error(error_msg); +end + +end \ No newline at end of file diff --git a/matlab/+caffe/private/CHECK_FILE_EXIST.m b/matlab/+caffe/private/CHECK_FILE_EXIST.m new file mode 100644 index 00000000000..1db464c8258 --- /dev/null +++ b/matlab/+caffe/private/CHECK_FILE_EXIST.m @@ -0,0 +1,7 @@ +function CHECK_FILE_EXIST(filename) + +if exist(filename, 'file') == 0 + error('%s does not exist', filename); +end + +end \ No newline at end of file diff --git a/matlab/+caffe/private/caffe_.cpp b/matlab/+caffe/private/caffe_.cpp new file mode 100644 index 00000000000..96a1920ac39 --- /dev/null +++ b/matlab/+caffe/private/caffe_.cpp @@ -0,0 +1,546 @@ +// +// caffe_.cpp provides wrappers of the caffe::Solver class, caffe::Net class, +// caffe::Layer class and caffe::Blob class and some caffe::Caffe functions, +// so that one could easily use Caffe from matlab. +// Note that for matlab, we will simply use float as the data type. + +// Internally, data is stored with dimensions reversed from Caffe's: +// e.g., if the Caffe blob axes are (num, channels, height, width), +// the matcaffe data is stored as (width, height, channels, num) +// where width is the fastest dimension. + +#include +#include +#include + +#include "mex.h" + +#include "caffe/caffe.hpp" + +#define MEX_ARGS int nlhs, mxArray **plhs, int nrhs, const mxArray **prhs + +using namespace caffe; // NOLINT(build/namespaces) + +// Do CHECK and throw a Mex error if check failsf +inline void mxCHECK(bool expr, const std::string &msg) { + if (!expr) { + LOG(ERROR) << msg; + mexErrMsgTxt(msg.c_str()); + } +} +inline void mxERROR(const std::string &msg) { mxCHECK(false, msg); } + +// Check if a file exists and can be opened +void mxCHECK_FILE_EXIST(const char* file) { + std::ifstream f(file); + if (!f.good()) { + f.close(); + mxERROR("Could not open file " + string(file)); + } + f.close(); +} + +// The pointers to caffe::Solver and caffe::Net instances +static vector > > solvers_; +static vector > > nets_; +static double init_key = static_cast(caffe_rng_rand()); + +/** ----------------------------------------------------------------- + ** data conversion functions + **/ +// Enum indicates which blob memory to use +enum WhichMemory { DATA, DIFF }; + +// Copy matlab array to Blob data or diff +static void mx_mat_to_blob(const mxArray* mx_mat, Blob* blob, + WhichMemory data_or_diff) { + mxCHECK(blob->count() == mxGetNumberOfElements(mx_mat), + "number of elements in target blob doesn't match that in input mxArray"); + const float* mat_mem_ptr = reinterpret_cast(mxGetData(mx_mat)); + float* blob_mem_ptr = NULL; + switch (Caffe::mode()) { + case Caffe::CPU: + blob_mem_ptr = (data_or_diff == DATA ? + blob->mutable_cpu_data() : blob->mutable_cpu_diff()); + break; + case Caffe::GPU: + blob_mem_ptr = (data_or_diff == DATA ? + blob->mutable_gpu_data() : blob->mutable_gpu_diff()); + break; + default: + mxERROR("Unknown Caffe mode"); + } + caffe_copy(blob->count(), mat_mem_ptr, blob_mem_ptr); +} + +// Copy Blob data or diff to matlab array +static mxArray* blob_to_mx_mat(const Blob* blob, + WhichMemory data_or_diff) { + const int num_axes = blob->num_axes(); + vector dims(num_axes); + for (int blob_axis = 0, mat_axis = num_axes - 1; blob_axis < num_axes; + ++blob_axis, --mat_axis) { + dims[mat_axis] = static_cast(blob->shape(blob_axis)); + } + // matlab array needs to have at least one dimension, convert scalar to 1-dim + if (num_axes == 0) { + dims.push_back(1); + } + mxArray* mx_mat = + mxCreateNumericArray(dims.size(), dims.data(), mxSINGLE_CLASS, mxREAL); + float* mat_mem_ptr = reinterpret_cast(mxGetData(mx_mat)); + const float* blob_mem_ptr = NULL; + switch (Caffe::mode()) { + case Caffe::CPU: + blob_mem_ptr = (data_or_diff == DATA ? blob->cpu_data() : blob->cpu_diff()); + break; + case Caffe::GPU: + blob_mem_ptr = (data_or_diff == DATA ? blob->gpu_data() : blob->gpu_diff()); + break; + default: + mxERROR("Unknown Caffe mode"); + } + caffe_copy(blob->count(), blob_mem_ptr, mat_mem_ptr); + return mx_mat; +} + +// convert vector to matlab vector +static mxArray* int_vec_to_mx_vec(const vector& int_vec) { + mxArray* mx_vec = mxCreateDoubleMatrix(int_vec.size(), 1, mxREAL); + double* vec_mem_ptr = mxGetPr(mx_vec); + for (int i = 0; i < int_vec.size(); i++) { + vec_mem_ptr[i] = int_vec[i]; + } + return mx_vec; +} + +// convert vector to matlab string cell vector +static mxArray* str_vec_to_mx_strcell(const vector& str_vec) { + mxArray* mx_strcell = mxCreateCellMatrix(str_vec.size(), 1); + for (int i = 0; i < str_vec.size(); i++) { + mxSetCell(mx_strcell, i, mxCreateString(str_vec[i].c_str())); + } + return mx_strcell; +} + +/** ----------------------------------------------------------------- + ** handle and pointer conversion functions + ** a handle is a struct array with the following fields + ** (uint64) ptr : the pointer to the C++ object + ** (double) init_key : caffe initialization key + **/ +// Convert a handle in matlab to a pointer in C++. Check if init_key matches +template +static T* handle_to_ptr(const mxArray* mx_handle) { + mxArray* mx_ptr = mxGetField(mx_handle, 0, "ptr"); + mxArray* mx_init_key = mxGetField(mx_handle, 0, "init_key"); + mxCHECK(mxIsUint64(mx_ptr), "pointer type must be uint64"); + mxCHECK(mxGetScalar(mx_init_key) == init_key, "incorrect handle init_key"); + return reinterpret_cast(*reinterpret_cast(mxGetData(mx_ptr))); +} + +// Create an empty handle struct array +template +static mxArray* create_handles(int ptr_num) { + const int handle_field_num = 2; + const char* handle_fields[handle_field_num] = { "ptr", "init_key" }; + return mxCreateStructMatrix(ptr_num, 1, handle_field_num, handle_fields); +} + +// Set up each handle in a handle struct array +template +static void setup_handle(const T* ptr, int index, mxArray* mx_handles) { + mxArray* mx_ptr = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL); + *reinterpret_cast(mxGetData(mx_ptr)) = + reinterpret_cast(ptr); + mxSetField(mx_handles, index, "ptr", mx_ptr); + mxSetField(mx_handles, index, "init_key", mxCreateDoubleScalar(init_key)); +} + +// Convert a pointer in C++ to a handle in matlab +template +static mxArray* ptr_to_handle(const T* ptr) { + mxArray* mx_handle = create_handles(1); + setup_handle(ptr, 0, mx_handle); + return mx_handle; +} + +// Convert a vector of shared_ptr in C++ to handle struct array in matlab +template +static mxArray* ptr_vec_to_handles(const vector >& ptr_vec) { + mxArray* mx_handle = create_handles(ptr_vec.size()); + for (int i = 0; i < ptr_vec.size(); i++) { + setup_handle(ptr_vec[i].get(), i, mx_handle); + } + return mx_handle; +} + +/** ----------------------------------------------------------------- + ** matlab command functions: caffe_(api_command, arg1, arg2, ...) + **/ +// Usage: caffe_('get_solver', solver_file); +static void get_solver(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsChar(prhs[0]), + "Usage: caffe_('get_solver', solver_file)"); + const char* solver_file = mxArrayToString(prhs[0]); + mxCHECK_FILE_EXIST(solver_file); + shared_ptr > solver; + solver.reset(new caffe::SGDSolver(solver_file)); + solvers_.push_back(solver); + plhs[0] = ptr_to_handle >(solver.get()); +} + +// Usage: caffe_('solver_get_attr', hSolver) +static void solver_get_attr(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('solver_get_attr', hSolver)"); + Solver* solver = handle_to_ptr >(prhs[0]); + const int solver_attr_num = 2; + const char* solver_attrs[solver_attr_num] = { "hNet_net", "hNet_test_nets" }; + mxArray* mx_solver_attr = mxCreateStructMatrix(1, 1, solver_attr_num, + solver_attrs); + mxSetField(mx_solver_attr, 0, "hNet_net", + ptr_to_handle >(solver->net().get())); + mxSetField(mx_solver_attr, 0, "hNet_test_nets", + ptr_vec_to_handles >(solver->test_nets())); + plhs[0] = mx_solver_attr; +} + +// Usage: caffe_('solver_get_iter', hSolver) +static void solver_get_iter(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('solver_get_iter', hSolver)"); + Solver* solver = handle_to_ptr >(prhs[0]); + plhs[0] = mxCreateDoubleScalar(solver->iter()); +} + +// Usage: caffe_('solver_restore', hSolver, snapshot_file) +static void solver_restore(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), + "Usage: caffe_('solver_restore', hSolver, snapshot_file)"); + Solver* solver = handle_to_ptr >(prhs[0]); + const char* snapshot_file = mxArrayToString(prhs[1]); + mxCHECK_FILE_EXIST(snapshot_file); + solver->Restore(snapshot_file); +} + +// Usage: caffe_('solver_solve', hSolver) +static void solver_solve(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('solver_solve', hSolver)"); + Solver* solver = handle_to_ptr >(prhs[0]); + solver->Solve(); +} + +// Usage: caffe_('solver_solve', hSolver, iters) +static void solver_step(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsDouble(prhs[1]), + "Usage: caffe_('solver_solve', hSolver, iters)"); + Solver* solver = handle_to_ptr >(prhs[0]); + int iters = mxGetScalar(prhs[1]); + solver->Step(iters); +} + +// Usage: caffe_('get_net', model_file, phase_name) +static void get_net(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsChar(prhs[0]) && mxIsChar(prhs[1]), + "Usage: caffe_('get_net', model_file, phase_name)"); + const char* model_file = mxArrayToString(prhs[0]); + mxCHECK_FILE_EXIST(model_file); + const char* phase_name = mxArrayToString(prhs[1]); + Phase phase; + if (strcmp(phase_name, "train") == 0) { + phase = TRAIN; + } else if (strcmp(phase_name, "test") == 0) { + phase = TEST; + } else { + mxERROR("Unknown phase."); + } + shared_ptr > net; + net.reset(new caffe::Net(model_file, phase)); + nets_.push_back(net); + plhs[0] = ptr_to_handle >(net.get()); +} + +// Usage: caffe_('net_get_attr', hNet) +static void net_get_attr(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('net_get_attr', hNet)"); + Net* net = handle_to_ptr >(prhs[0]); + const int net_attr_num = 6; + const char* net_attrs[net_attr_num] = { "hLayer_layers", "hBlob_blobs", + "input_blob_indices", "output_blob_indices", "layer_names", "blob_names"}; + mxArray* mx_net_attr = mxCreateStructMatrix(1, 1, net_attr_num, + net_attrs); + mxSetField(mx_net_attr, 0, "hLayer_layers", + ptr_vec_to_handles >(net->layers())); + mxSetField(mx_net_attr, 0, "hBlob_blobs", + ptr_vec_to_handles >(net->blobs())); + mxSetField(mx_net_attr, 0, "input_blob_indices", + int_vec_to_mx_vec(net->input_blob_indices())); + mxSetField(mx_net_attr, 0, "output_blob_indices", + int_vec_to_mx_vec(net->output_blob_indices())); + mxSetField(mx_net_attr, 0, "layer_names", + str_vec_to_mx_strcell(net->layer_names())); + mxSetField(mx_net_attr, 0, "blob_names", + str_vec_to_mx_strcell(net->blob_names())); + plhs[0] = mx_net_attr; +} + +// Usage: caffe_('net_forward', hNet) +static void net_forward(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('net_forward', hNet)"); + Net* net = handle_to_ptr >(prhs[0]); + net->ForwardPrefilled(); +} + +// Usage: caffe_('net_backward', hNet) +static void net_backward(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('net_backward', hNet)"); + Net* net = handle_to_ptr >(prhs[0]); + net->Backward(); +} + +// Usage: caffe_('net_copy_from', hNet, weights_file) +static void net_copy_from(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), + "Usage: caffe_('net_copy_from', hNet, weights_file)"); + Net* net = handle_to_ptr >(prhs[0]); + const char* weights_file = mxArrayToString(prhs[1]); + mxCHECK_FILE_EXIST(weights_file); + net->CopyTrainedLayersFrom(weights_file); +} + +// Usage: caffe_('net_reshape', hNet) +static void net_reshape(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('net_reshape', hNet)"); + Net* net = handle_to_ptr >(prhs[0]); + net->Reshape(); +} + +// Usage: caffe_('net_save', hNet, save_file) +static void net_save(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), + "Usage: caffe_('net_save', hNet, save_file)"); + Net* net = handle_to_ptr >(prhs[0]); + const char* weights_file = mxArrayToString(prhs[1]); + NetParameter net_param; + net->ToProto(&net_param, false); + WriteProtoToBinaryFile(net_param, weights_file); +} + +// Usage: caffe_('layer_get_attr', hLayer) +static void layer_get_attr(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('layer_get_attr', hLayer)"); + Layer* layer = handle_to_ptr >(prhs[0]); + const int layer_attr_num = 1; + const char* layer_attrs[layer_attr_num] = { "hBlob_blobs" }; + mxArray* mx_layer_attr = mxCreateStructMatrix(1, 1, layer_attr_num, + layer_attrs); + mxSetField(mx_layer_attr, 0, "hBlob_blobs", + ptr_vec_to_handles >(layer->blobs())); + plhs[0] = mx_layer_attr; +} + +// Usage: caffe_('layer_get_attr', hLayer) +static void layer_get_type(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('layer_get_attr', hLayer)"); + Layer* layer = handle_to_ptr >(prhs[0]); + plhs[0] = mxCreateString(layer->type()); +} + +// Usage: caffe_('blob_get_shape', hBlob) +static void blob_get_shape(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('blob_get_shape', hBlob)"); + Blob* blob = handle_to_ptr >(prhs[0]); + const int num_axes = blob->num_axes(); + mxArray* mx_shape = mxCreateDoubleMatrix(1, num_axes, mxREAL); + double* shape_mem_mtr = mxGetPr(mx_shape); + for (int blob_axis = 0, mat_axis = num_axes - 1; blob_axis < num_axes; + ++blob_axis, --mat_axis) { + shape_mem_mtr[mat_axis] = static_cast(blob->shape(blob_axis)); + } + plhs[0] = mx_shape; +} + +// Usage: caffe_('blob_reshape', hBlob, new_shape) +static void blob_reshape(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsDouble(prhs[1]), + "Usage: caffe_('blob_reshape', hBlob, new_shape)"); + Blob* blob = handle_to_ptr >(prhs[0]); + const mxArray* mx_shape = prhs[1]; + double* shape_mem_mtr = mxGetPr(mx_shape); + const int num_axes = mxGetNumberOfElements(mx_shape); + vector blob_shape(num_axes); + for (int blob_axis = 0, mat_axis = num_axes - 1; blob_axis < num_axes; + ++blob_axis, --mat_axis) { + blob_shape[blob_axis] = static_cast(shape_mem_mtr[mat_axis]); + } + blob->Reshape(blob_shape); +} + +// Usage: caffe_('blob_get_data', hBlob) +static void blob_get_data(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('blob_get_data', hBlob)"); + Blob* blob = handle_to_ptr >(prhs[0]); + plhs[0] = blob_to_mx_mat(blob, DATA); +} + +// Usage: caffe_('blob_set_data', hBlob, new_data) +static void blob_set_data(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsSingle(prhs[1]), + "Usage: caffe_('blob_set_data', hBlob, new_data)"); + Blob* blob = handle_to_ptr >(prhs[0]); + mx_mat_to_blob(prhs[1], blob, DATA); +} + +// Usage: caffe_('blob_get_diff', hBlob) +static void blob_get_diff(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), + "Usage: caffe_('blob_get_diff', hBlob)"); + Blob* blob = handle_to_ptr >(prhs[0]); + plhs[0] = blob_to_mx_mat(blob, DIFF); +} + +// Usage: caffe_('blob_set_diff', hBlob, new_diff) +static void blob_set_diff(MEX_ARGS) { + mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsSingle(prhs[1]), + "Usage: caffe_('blob_set_diff', hBlob, new_diff)"); + Blob* blob = handle_to_ptr >(prhs[0]); + mx_mat_to_blob(prhs[1], blob, DIFF); +} + +// Usage: caffe_('set_mode_cpu') +static void set_mode_cpu(MEX_ARGS) { + mxCHECK(nrhs == 0, "Usage: caffe_('set_mode_cpu')"); + Caffe::set_mode(Caffe::CPU); +} + +// Usage: caffe_('set_mode_gpu') +static void set_mode_gpu(MEX_ARGS) { + mxCHECK(nrhs == 0, "Usage: caffe_('set_mode_gpu')"); + Caffe::set_mode(Caffe::GPU); +} + +// Usage: caffe_('set_device', device_id) +static void set_device(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsDouble(prhs[0]), + "Usage: caffe_('set_device', device_id)"); + int device_id = static_cast(mxGetScalar(prhs[0])); + Caffe::SetDevice(device_id); +} + +// Usage: caffe_('get_init_key') +static void get_init_key(MEX_ARGS) { + mxCHECK(nrhs == 0, "Usage: caffe_('get_init_key')"); + plhs[0] = mxCreateDoubleScalar(init_key); +} + +// Usage: caffe_('reset') +static void reset(MEX_ARGS) { + mxCHECK(nrhs == 0, "Usage: caffe_('reset')"); + mexPrintf("cleared %d solvers and %d stand-alone nets\n", solvers_.size(), + nets_.size()); + solvers_.clear(); + nets_.clear(); + init_key = static_cast(caffe_rng_rand()); +} + +// Usage: caffe_('read_mean', mean_proto_file) +static void read_mean(MEX_ARGS) { + mxCHECK(nrhs == 1 && mxIsChar(prhs[0]), + "Usage: caffe_('read_mean', mean_proto_file)"); + const char* mean_proto_file = mxArrayToString(prhs[0]); + Blob data_mean; + mexPrintf("Loading mean file from: %s\n", mean_proto_file); + BlobProto blob_proto; + bool result = ReadProtoFromBinaryFile(mean_proto_file, &blob_proto); + mxCHECK(result, "Couldn't read the file"); + data_mean.FromProto(blob_proto); + mwSize dims[4] = {data_mean.width(), data_mean.height(), + data_mean.channels(), data_mean.num() }; + mxArray* mx_blob = mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); + float* data_ptr = reinterpret_cast(mxGetData(mx_blob)); + caffe_copy(data_mean.count(), data_mean.cpu_data(), data_ptr); + mexPrintf("Remember that Caffe saves in [width, height, channels]" + " format and channels are also BGR!\n"); + plhs[0] = mx_blob; +} + +/** ----------------------------------------------------------------- + ** Available commands. + **/ +struct handler_registry { + string cmd; + void (*func)(MEX_ARGS); +}; + +static handler_registry handlers[] = { + // Public API functions + { "get_solver", get_solver }, + { "solver_get_attr", solver_get_attr }, + { "solver_get_iter", solver_get_iter }, + { "solver_restore", solver_restore }, + { "solver_solve", solver_solve }, + { "solver_step", solver_step }, + { "get_net", get_net }, + { "net_get_attr", net_get_attr }, + { "net_forward", net_forward }, + { "net_backward", net_backward }, + { "net_copy_from", net_copy_from }, + { "net_reshape", net_reshape }, + { "net_save", net_save }, + { "layer_get_attr", layer_get_attr }, + { "layer_get_type", layer_get_type }, + { "blob_get_shape", blob_get_shape }, + { "blob_reshape", blob_reshape }, + { "blob_get_data", blob_get_data }, + { "blob_set_data", blob_set_data }, + { "blob_get_diff", blob_get_diff }, + { "blob_set_diff", blob_set_diff }, + { "set_mode_cpu", set_mode_cpu }, + { "set_mode_gpu", set_mode_gpu }, + { "set_device", set_device }, + { "get_init_key", get_init_key }, + { "reset", reset }, + { "read_mean", read_mean }, + // The end. + { "END", NULL }, +}; + +/** ----------------------------------------------------------------- + ** matlab entry point: caffe_(api_command, arg1, arg2, ...) + **/ +void mexFunction(MEX_ARGS) { + mexLock(); // Avoid clearing the mex file. + if (nrhs == 0) { + mxERROR("No API command given"); + return; + } + + { // Handle input command + char* cmd = mxArrayToString(prhs[0]); + bool dispatched = false; + // Dispatch to cmd handler + for (int i = 0; handlers[i].func != NULL; i++) { + if (handlers[i].cmd.compare(cmd) == 0) { + handlers[i].func(nlhs, plhs, nrhs-1, prhs+1); + dispatched = true; + break; + } + } + if (!dispatched) { + ostringstream error_msg; + error_msg << "Unknown command '" << cmd << "'"; + mxERROR(error_msg.str().c_str()); + } + mxFree(cmd); + } +} diff --git a/matlab/+caffe/private/is_valid_handle.m b/matlab/+caffe/private/is_valid_handle.m new file mode 100644 index 00000000000..f03469c4f86 --- /dev/null +++ b/matlab/+caffe/private/is_valid_handle.m @@ -0,0 +1,28 @@ +function valid = is_valid_handle(hObj) +% valid = is_valid_handle(hObj) or is_valid_handle('get_new_init_key') +% Check if a handle is valid (has the right data type and init_key matches) +% Use is_valid_handle('get_new_init_key') to get new init_key from C++; + +% a handle is a struct array with the following fields +% (uint64) ptr : the pointer to the C++ object +% (double) init_key : caffe initialization key + +persistent init_key; +if isempty(init_key) + init_key = caffe_('get_init_key'); +end + +% is_valid_handle('get_new_init_key') to get new init_key from C++; +if ischar(hObj) && strcmp(hObj, 'get_new_init_key') + init_key = caffe_('get_init_key'); + valid = true; + return +else + % check whether data types are correct and init_key matches + valid = isstruct(hObj) ... + && isscalar(hObj.ptr) && isa(hObj.ptr, 'uint64') ... + && isscalar(hObj.init_key) && isa(hObj.init_key, 'double') ... + && hObj.init_key == init_key; +end + +end \ No newline at end of file diff --git a/matlab/+caffe/reset.m b/matlab/+caffe/reset.m new file mode 100644 index 00000000000..2df812b3f1d --- /dev/null +++ b/matlab/+caffe/reset.m @@ -0,0 +1,8 @@ +function reset() +% reset() +% reset Caffe to initial status + +caffe_('reset'); +is_valid_handle('get_new_init_key'); + +end \ No newline at end of file diff --git a/matlab/+caffe/run_tests.m b/matlab/+caffe/run_tests.m new file mode 100644 index 00000000000..20834d497c2 --- /dev/null +++ b/matlab/+caffe/run_tests.m @@ -0,0 +1,12 @@ +function results = run_tests() +% results = run_tests() +% run all tests in this caffe matlab wrapper package + +caffe.reset(); +results = [... + run(caffe.test.test_net) ... + run(caffe.test.test_solver) + ]; +caffe.reset(); + +end \ No newline at end of file diff --git a/matlab/+caffe/set_device.m b/matlab/+caffe/set_device.m new file mode 100644 index 00000000000..43f00bddee6 --- /dev/null +++ b/matlab/+caffe/set_device.m @@ -0,0 +1,11 @@ +function set_device(device_id) +% set_device(device_id) +% set Caffe's GPU device ID + +CHECK(isscalar(device_id) && device_id >= 0, ... + 'device_id must be non-negative integer'); +device_id = double(device_id); + +caffe_('set_device', device_id); + +end \ No newline at end of file diff --git a/matlab/+caffe/set_mode_cpu.m b/matlab/+caffe/set_mode_cpu.m new file mode 100644 index 00000000000..8c0576ccbe4 --- /dev/null +++ b/matlab/+caffe/set_mode_cpu.m @@ -0,0 +1,7 @@ +function set_mode_cpu() +% set_mode_cpu() +% set Caffe to CPU mode + +caffe_('set_mode_cpu'); + +end \ No newline at end of file diff --git a/matlab/+caffe/set_mode_gpu.m b/matlab/+caffe/set_mode_gpu.m new file mode 100644 index 00000000000..ecd13b59bba --- /dev/null +++ b/matlab/+caffe/set_mode_gpu.m @@ -0,0 +1,7 @@ +function set_mode_gpu() +% set_mode_gpu() +% set Caffe to GPU mode + +caffe_('set_mode_gpu'); + +end \ No newline at end of file diff --git a/matlab/CMakeLists.txt b/matlab/CMakeLists.txt index 791a4e70f43..63182abcc44 100644 --- a/matlab/CMakeLists.txt +++ b/matlab/CMakeLists.txt @@ -32,7 +32,9 @@ endfunction() # global settings file(GLOB Matlab_srcs caffe/matcaffe.cpp) +file(GLOB Matlab_pkg_srcs +caffe/private/caffe_.cpp) set(Matlab_caffe_mex ${PROJECT_SOURCE_DIR}/matlab/caffe/caffe.mex) +set(Matlab_caffe_pkg_mex ${PROJECT_SOURCE_DIR}/matlab/+caffe/private/caffe_.mex) caffe_get_current_cflags(cflags) caffe_parse_linker_libs(Caffe_LINKER_LIBS folders libflags macos_frameworks) @@ -50,9 +52,15 @@ if(build_using MATCHES "Matlab") ARGS -output ${Matlab_caffe_mex} ${Matlab_srcs} ${cflags} ${link_folders} ${libflags} DEPENDS caffe COMMENT "Building Matlab interface: ${Matlab_caffe_mex}" VERBATIM) add_custom_target(matlab ALL DEPENDS ${Matlab_caffe_mex} SOURCES ${Matlab_srcs}) + caffe_fetch_and_set_proper_mexext(Matlab_caffe_pkg_mex) + add_custom_command(OUTPUT ${Matlab_caffe_pkg_mex} COMMAND ${Matlab_mex} + ARGS -output ${Matlab_caffe_pkg_mex} ${Matlab_pkg_srcs} ${cflags} ${link_folders} ${libflags} + DEPENDS caffe COMMENT "Building Matlab interface: ${Matlab_caffe_pkg_mex}" VERBATIM) + add_custom_target(matlab ALL DEPENDS ${Matlab_caffe_pkg_mex} SOURCES ${Matlab_pkg_srcs}) elseif(build_using MATCHES "Octave") + # Note: Matlab Caffe package cannot be used in Octave, so we don't build it if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(libflags -Wl,-force_load,$ ${libflags}) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") From a53df9892c49758bfee1de3455054dec9fc6dbdb Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Wed, 27 May 2015 12:37:20 +0800 Subject: [PATCH 2/8] Fix matlab tailing dimension 1 issue for shape match Matlab cannot have tailing dimension 1 for ndim > 2, so you cannot create 20 x 10 x 1 x 1 array in matlab as it becomes 20 x 10. Extend matlab arrays to have tailing dimension 1 during shape match. --- matlab/+caffe/+test/test_net.m | 2 +- matlab/+caffe/+test/test_solver.m | 2 +- matlab/+caffe/Blob.m | 15 ++++++++++----- matlab/+caffe/get_net.m | 1 - matlab/+caffe/get_solver.m | 2 +- matlab/+caffe/io.m | 2 +- matlab/+caffe/private/CHECK.m | 2 +- matlab/+caffe/private/CHECK_FILE_EXIST.m | 2 +- matlab/+caffe/private/is_valid_handle.m | 2 +- matlab/+caffe/reset.m | 2 +- matlab/+caffe/run_tests.m | 2 +- matlab/+caffe/set_device.m | 2 +- matlab/+caffe/set_mode_cpu.m | 2 +- matlab/+caffe/set_mode_gpu.m | 2 +- 14 files changed, 22 insertions(+), 18 deletions(-) diff --git a/matlab/+caffe/+test/test_net.m b/matlab/+caffe/+test/test_net.m index 4958d50f9e3..5d9ba000209 100644 --- a/matlab/+caffe/+test/test_net.m +++ b/matlab/+caffe/+test/test_net.m @@ -71,4 +71,4 @@ function test_save_and_read(self) end end end -end \ No newline at end of file +end diff --git a/matlab/+caffe/+test/test_solver.m b/matlab/+caffe/+test/test_solver.m index 9774ec1d2fd..682dad48a3b 100644 --- a/matlab/+caffe/+test/test_solver.m +++ b/matlab/+caffe/+test/test_solver.m @@ -40,4 +40,4 @@ function test_solve(self) self.verifyEqual(self.solver.iter(), 100) end end -end \ No newline at end of file +end diff --git a/matlab/+caffe/Blob.m b/matlab/+caffe/Blob.m index a6326d1a36c..f9b64096adc 100644 --- a/matlab/+caffe/Blob.m +++ b/matlab/+caffe/Blob.m @@ -51,7 +51,6 @@ function check_data_size_matches(self, data) % note: matlab arrays always have at least 2 dimensions. To compare % shape between size of data and shape of this blob, extend shape of % this blob to have at least 2 dimensions - data_size = size(data); self_shape_extended = self.shape; if isempty(self_shape_extended) % target blob is a scalar (0 dim) @@ -60,12 +59,18 @@ function check_data_size_matches(self, data) % target blob is a vector (1 dim) self_shape_extended = [self_shape_extended, 1]; end - is_matched = (length(self_shape_extended) == length(data_size)) ... - && all(self_shape_extended == data_size); + % also, matlab cannot have tailing dimension 1 for ndim > 2, so you + % cannot create 20 x 10 x 1 x 1 array in matlab as it becomes 20 x 10 + % extend matlab arrays to have tailing dimension 1 during shape match + data_size_extended = ... + [size(data), ones(1, length(self_shape_extended) - ndims(data))]; + is_matched = ... + (length(self_shape_extended) == length(data_size_extended)) ... + && all(self_shape_extended == data_size_extended); CHECK(is_matched, ... sprintf('%s, data size: [ %s], blob shape: [ %s]', ... 'data size does not match blob shape', ... - sprintf('%d ', data_size), sprintf('%d ', self_shape_extended))); + sprintf('%d ', data_size_extended), sprintf('%d ', self_shape_extended))); end end -end \ No newline at end of file +end diff --git a/matlab/+caffe/get_net.m b/matlab/+caffe/get_net.m index 04872b263c8..d60979d04b5 100644 --- a/matlab/+caffe/get_net.m +++ b/matlab/+caffe/get_net.m @@ -34,4 +34,3 @@ end end - diff --git a/matlab/+caffe/get_solver.m b/matlab/+caffe/get_solver.m index 855709c725e..30366d851e4 100644 --- a/matlab/+caffe/get_solver.m +++ b/matlab/+caffe/get_solver.m @@ -8,4 +8,4 @@ pSolver = caffe_('get_solver', solver_file); solver = caffe.Solver(pSolver); -end \ No newline at end of file +end diff --git a/matlab/+caffe/io.m b/matlab/+caffe/io.m index 95d58909abd..7fad9686ac1 100644 --- a/matlab/+caffe/io.m +++ b/matlab/+caffe/io.m @@ -19,4 +19,4 @@ mean_data = caffe_('read_mean', mean_proto_file); end end -end \ No newline at end of file +end diff --git a/matlab/+caffe/private/CHECK.m b/matlab/+caffe/private/CHECK.m index 1cf21032b36..21706549cfa 100644 --- a/matlab/+caffe/private/CHECK.m +++ b/matlab/+caffe/private/CHECK.m @@ -4,4 +4,4 @@ function CHECK(expr, error_msg) error(error_msg); end -end \ No newline at end of file +end diff --git a/matlab/+caffe/private/CHECK_FILE_EXIST.m b/matlab/+caffe/private/CHECK_FILE_EXIST.m index 1db464c8258..8c80fb8094f 100644 --- a/matlab/+caffe/private/CHECK_FILE_EXIST.m +++ b/matlab/+caffe/private/CHECK_FILE_EXIST.m @@ -4,4 +4,4 @@ function CHECK_FILE_EXIST(filename) error('%s does not exist', filename); end -end \ No newline at end of file +end diff --git a/matlab/+caffe/private/is_valid_handle.m b/matlab/+caffe/private/is_valid_handle.m index f03469c4f86..77abf21dd11 100644 --- a/matlab/+caffe/private/is_valid_handle.m +++ b/matlab/+caffe/private/is_valid_handle.m @@ -25,4 +25,4 @@ && hObj.init_key == init_key; end -end \ No newline at end of file +end diff --git a/matlab/+caffe/reset.m b/matlab/+caffe/reset.m index 2df812b3f1d..c1cfea41a76 100644 --- a/matlab/+caffe/reset.m +++ b/matlab/+caffe/reset.m @@ -5,4 +5,4 @@ function reset() caffe_('reset'); is_valid_handle('get_new_init_key'); -end \ No newline at end of file +end diff --git a/matlab/+caffe/run_tests.m b/matlab/+caffe/run_tests.m index 20834d497c2..fb1089c2d96 100644 --- a/matlab/+caffe/run_tests.m +++ b/matlab/+caffe/run_tests.m @@ -9,4 +9,4 @@ ]; caffe.reset(); -end \ No newline at end of file +end diff --git a/matlab/+caffe/set_device.m b/matlab/+caffe/set_device.m index 43f00bddee6..f94068cbe98 100644 --- a/matlab/+caffe/set_device.m +++ b/matlab/+caffe/set_device.m @@ -8,4 +8,4 @@ function set_device(device_id) caffe_('set_device', device_id); -end \ No newline at end of file +end diff --git a/matlab/+caffe/set_mode_cpu.m b/matlab/+caffe/set_mode_cpu.m index 8c0576ccbe4..a87e0e2852b 100644 --- a/matlab/+caffe/set_mode_cpu.m +++ b/matlab/+caffe/set_mode_cpu.m @@ -4,4 +4,4 @@ function set_mode_cpu() caffe_('set_mode_cpu'); -end \ No newline at end of file +end diff --git a/matlab/+caffe/set_mode_gpu.m b/matlab/+caffe/set_mode_gpu.m index ecd13b59bba..78e5f6773a1 100644 --- a/matlab/+caffe/set_mode_gpu.m +++ b/matlab/+caffe/set_mode_gpu.m @@ -4,4 +4,4 @@ function set_mode_gpu() caffe_('set_mode_gpu'); -end \ No newline at end of file +end From 9735f4b3b257379ac4e6c9d310aab32bfb198661 Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Thu, 28 May 2015 13:40:26 +0800 Subject: [PATCH 3/8] Aesthetic changes on code style and some minor fix --- matlab/+caffe/Blob.m | 22 ++--- matlab/+caffe/Layer.m | 2 +- matlab/+caffe/Net.m | 10 +-- matlab/+caffe/Solver.m | 2 +- matlab/+caffe/get_net.m | 3 +- matlab/+caffe/get_solver.m | 1 - matlab/+caffe/io.m | 15 ++-- matlab/+caffe/private/caffe_.cpp | 154 ++++++++++++++++---------------- matlab/+caffe/private/is_valid_handle.m | 1 - matlab/+caffe/run_tests.m | 8 +- 10 files changed, 113 insertions(+), 105 deletions(-) diff --git a/matlab/+caffe/Blob.m b/matlab/+caffe/Blob.m index f9b64096adc..e39f7ee3f20 100644 --- a/matlab/+caffe/Blob.m +++ b/matlab/+caffe/Blob.m @@ -7,9 +7,9 @@ methods function self = Blob(hBlob_blob) - CHECK(is_valid_handle(hBlob_blob), 'invalid input handle'); + CHECK(is_valid_handle(hBlob_blob), 'invalid Blob handle'); - % setup self handle and attributes + % setup self handle self.hBlob_self = hBlob_blob; end function shape = shape(self) @@ -37,14 +37,16 @@ function set_diff(self, diff) methods (Access = private) function shape = check_and_preprocess_shape(~, shape) - CHECK(isempty(shape) || isnumeric(shape) && isrow(shape), ... + CHECK(isempty(shape) || (isnumeric(shape) && isrow(shape)), ... 'shape must be a integer row vector'); shape = double(shape); end function data = check_and_preprocess_data(self, data) CHECK(isnumeric(data), 'data or diff must be numeric types'); - self.check_data_size_matches(data) - data = single(data); + self.check_data_size_matches(data); + if ~isa(data, 'single') + data = single(data); + end end function check_data_size_matches(self, data) % check whether size of data matches shape of this blob @@ -59,17 +61,17 @@ function check_data_size_matches(self, data) % target blob is a vector (1 dim) self_shape_extended = [self_shape_extended, 1]; end - % also, matlab cannot have tailing dimension 1 for ndim > 2, so you + % Also, matlab cannot have tailing dimension 1 for ndim > 2, so you % cannot create 20 x 10 x 1 x 1 array in matlab as it becomes 20 x 10 - % extend matlab arrays to have tailing dimension 1 during shape match + % Extend matlab arrays to have tailing dimension 1 during shape match data_size_extended = ... [size(data), ones(1, length(self_shape_extended) - ndims(data))]; is_matched = ... - (length(self_shape_extended) == length(data_size_extended)) ... + (length(self_shape_extended) == length(data_size_extended)) ... && all(self_shape_extended == data_size_extended); CHECK(is_matched, ... - sprintf('%s, data size: [ %s], blob shape: [ %s]', ... - 'data size does not match blob shape', ... + sprintf('%s, input data/diff size: [ %s] vs target blob shape: [ %s]', ... + 'input data/diff size does not match target blob shape', ... sprintf('%d ', data_size_extended), sprintf('%d ', self_shape_extended))); end end diff --git a/matlab/+caffe/Layer.m b/matlab/+caffe/Layer.m index 7587ed7ddf7..4c2023101a5 100644 --- a/matlab/+caffe/Layer.m +++ b/matlab/+caffe/Layer.m @@ -13,7 +13,7 @@ methods function self = Layer(hLayer_layer) - CHECK(is_valid_handle(hLayer_layer), 'invalid input handle'); + CHECK(is_valid_handle(hLayer_layer), 'invalid Layer handle'); % setup self handle and attributes self.hLayer_self = hLayer_layer; diff --git a/matlab/+caffe/Net.m b/matlab/+caffe/Net.m index 5319634dc8b..a6761060dfb 100644 --- a/matlab/+caffe/Net.m +++ b/matlab/+caffe/Net.m @@ -33,7 +33,7 @@ end % construct a net from handle hNet_net = varargin{1}; - CHECK(is_valid_handle(hNet_net), 'invalid input handle'); + CHECK(is_valid_handle(hNet_net), 'invalid Net handle'); % setup self handle and attributes self.hNet_self = hNet_net; @@ -64,7 +64,7 @@ self.name2blob_index = containers.Map(self.attributes.blob_names, ... 1:length(self.attributes.blob_names)); - % expose layer_names and blob_names for public access + % expose layer_names and blob_names for public read access self.layer_names = self.attributes.layer_names; self.blob_names = self.attributes.blob_names; end @@ -91,12 +91,12 @@ function backward_prefilled(self) CHECK(iscell(input_data), 'input_data must be a cell array'); CHECK(length(input_data) == length(self.inputs), ... 'input data cell length must match input blob number'); - % copy data to input_blobs + % copy data to input blobs for n = 1:length(self.inputs) self.blobs(self.inputs{n}).set_data(input_data{n}); end self.forward_prefilled(); - % retrieve data from output_blobs + % retrieve data from output blobs res = cell(length(self.outputs), 1); for n = 1:length(self.outputs) res{n} = self.blobs(self.outputs{n}).get_data(); @@ -106,7 +106,7 @@ function backward_prefilled(self) CHECK(iscell(output_diff), 'output_diff must be a cell array'); CHECK(length(output_diff) == length(self.outputs), ... 'output diff cell length must match output blob number'); - % copy diff to output_blobs + % copy diff to output blobs for n = 1:length(self.outputs) self.blobs(self.outputs{n}).set_diff(output_diff{n}); end diff --git a/matlab/+caffe/Solver.m b/matlab/+caffe/Solver.m index 80fa5394739..daaa8022b91 100644 --- a/matlab/+caffe/Solver.m +++ b/matlab/+caffe/Solver.m @@ -23,7 +23,7 @@ end % construct a solver from handle hSolver_solver = varargin{1}; - CHECK(is_valid_handle(hSolver_solver), 'invalid input handle'); + CHECK(is_valid_handle(hSolver_solver), 'invalid Solver handle'); % setup self handle and attributes self.hSolver_self = hSolver_solver; diff --git a/matlab/+caffe/get_net.m b/matlab/+caffe/get_net.m index d60979d04b5..4b5683eb82e 100644 --- a/matlab/+caffe/get_net.m +++ b/matlab/+caffe/get_net.m @@ -5,7 +5,7 @@ % phase_name can only be 'train' or 'test' CHECK(nargin == 2 || nargin == 3, ['usage: ' ... - 'net = get_net(model_file, phase_name) or ', ... + 'net = get_net(model_file, phase_name) or ' ... 'net = get_net(model_file, weights_file, phase_name)']); if nargin == 3 model_file = varargin{1}; @@ -23,6 +23,7 @@ sprintf('phase_name can only be %strain%s or %stest%s', ... char(39), char(39), char(39), char(39))); +% construct caffe net from model_file hNet = caffe_('get_net', model_file, phase_name); net = caffe.Net(hNet); diff --git a/matlab/+caffe/get_solver.m b/matlab/+caffe/get_solver.m index 30366d851e4..74d576eb31b 100644 --- a/matlab/+caffe/get_solver.m +++ b/matlab/+caffe/get_solver.m @@ -4,7 +4,6 @@ CHECK(ischar(solver_file), 'solver_file must be a string'); CHECK_FILE_EXIST(solver_file); - pSolver = caffe_('get_solver', solver_file); solver = caffe.Solver(pSolver); diff --git a/matlab/+caffe/io.m b/matlab/+caffe/io.m index 7fad9686ac1..7a30bfb5772 100644 --- a/matlab/+caffe/io.m +++ b/matlab/+caffe/io.m @@ -3,17 +3,20 @@ methods (Static) function im_data = load_image(im_file) + % im_data = load_image(im_file) + % load an image from disk into Caffe-supported data format + % switch channels from RGB to BGR, make width the fastest dimension + % and convert to single CHECK(ischar(im_file), 'im_file must be a string'); CHECK_FILE_EXIST(im_file); - % load an image from disk into Caffe-supported data format - % switch channels from RGB to BGR, make width the fastest dimension, and - % convert to single - im = imread(im_file); - im_data = im(:, :, [3, 2, 1]); - im_data = permute(im_data, [2 1 3]); + im_data = imread(im_file); + im_data = im_data(:, :, [3, 2, 1]); + im_data = permute(im_data, [2, 1, 3]); im_data = single(im_data); end function mean_data = read_mean(mean_proto_file) + % mean_data = read_mean(mean_proto_file) + % read image mean data from binaryproto file CHECK(ischar(mean_proto_file), 'im_file must be a string'); CHECK_FILE_EXIST(mean_proto_file); mean_data = caffe_('read_mean', mean_proto_file); diff --git a/matlab/+caffe/private/caffe_.cpp b/matlab/+caffe/private/caffe_.cpp index 96a1920ac39..4e0ebc1c00a 100644 --- a/matlab/+caffe/private/caffe_.cpp +++ b/matlab/+caffe/private/caffe_.cpp @@ -21,21 +21,22 @@ using namespace caffe; // NOLINT(build/namespaces) -// Do CHECK and throw a Mex error if check failsf -inline void mxCHECK(bool expr, const std::string &msg) { +// Do CHECK and throw a Mex error if check fails +inline void mxCHECK(bool expr, const char* msg) { if (!expr) { - LOG(ERROR) << msg; - mexErrMsgTxt(msg.c_str()); + mexErrMsgTxt(msg); } } -inline void mxERROR(const std::string &msg) { mxCHECK(false, msg); } +inline void mxERROR(const char* msg) { mexErrMsgTxt(msg); } // Check if a file exists and can be opened void mxCHECK_FILE_EXIST(const char* file) { std::ifstream f(file); if (!f.good()) { f.close(); - mxERROR("Could not open file " + string(file)); + std::string msg("Could not open file "); + msg += file; + mxERROR(msg.c_str()); } f.close(); } @@ -43,6 +44,7 @@ void mxCHECK_FILE_EXIST(const char* file) { // The pointers to caffe::Solver and caffe::Net instances static vector > > solvers_; static vector > > nets_; +// init_key is generated at the beginning and everytime you call reset static double init_key = static_cast(caffe_rng_rand()); /** ----------------------------------------------------------------- @@ -104,17 +106,17 @@ static mxArray* blob_to_mx_mat(const Blob* blob, return mx_mat; } -// convert vector to matlab vector +// Convert vector to matlab row vector static mxArray* int_vec_to_mx_vec(const vector& int_vec) { mxArray* mx_vec = mxCreateDoubleMatrix(int_vec.size(), 1, mxREAL); double* vec_mem_ptr = mxGetPr(mx_vec); for (int i = 0; i < int_vec.size(); i++) { - vec_mem_ptr[i] = int_vec[i]; + vec_mem_ptr[i] = static_cast(int_vec[i]); } return mx_vec; } -// convert vector to matlab string cell vector +// Convert vector to matlab cell vector of strings static mxArray* str_vec_to_mx_strcell(const vector& str_vec) { mxArray* mx_strcell = mxCreateCellMatrix(str_vec.size(), 1); for (int i = 0; i < str_vec.size(); i++) { @@ -135,44 +137,46 @@ static T* handle_to_ptr(const mxArray* mx_handle) { mxArray* mx_ptr = mxGetField(mx_handle, 0, "ptr"); mxArray* mx_init_key = mxGetField(mx_handle, 0, "init_key"); mxCHECK(mxIsUint64(mx_ptr), "pointer type must be uint64"); - mxCHECK(mxGetScalar(mx_init_key) == init_key, "incorrect handle init_key"); + mxCHECK(mxGetScalar(mx_init_key) == init_key, + "Could not convert handle to pointer due to invalid init_key. " + "The object might have been cleared."); return reinterpret_cast(*reinterpret_cast(mxGetData(mx_ptr))); } -// Create an empty handle struct array +// Create a handle struct vector, without setting up each handle in it template -static mxArray* create_handles(int ptr_num) { +static mxArray* create_handle_vec(int ptr_num) { const int handle_field_num = 2; const char* handle_fields[handle_field_num] = { "ptr", "init_key" }; return mxCreateStructMatrix(ptr_num, 1, handle_field_num, handle_fields); } -// Set up each handle in a handle struct array +// Set up a handle in a handle struct vector by its index template -static void setup_handle(const T* ptr, int index, mxArray* mx_handles) { +static void setup_handle(const T* ptr, int index, mxArray* mx_handle_vec) { mxArray* mx_ptr = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL); *reinterpret_cast(mxGetData(mx_ptr)) = reinterpret_cast(ptr); - mxSetField(mx_handles, index, "ptr", mx_ptr); - mxSetField(mx_handles, index, "init_key", mxCreateDoubleScalar(init_key)); + mxSetField(mx_handle_vec, index, "ptr", mx_ptr); + mxSetField(mx_handle_vec, index, "init_key", mxCreateDoubleScalar(init_key)); } // Convert a pointer in C++ to a handle in matlab template static mxArray* ptr_to_handle(const T* ptr) { - mxArray* mx_handle = create_handles(1); + mxArray* mx_handle = create_handle_vec(1); setup_handle(ptr, 0, mx_handle); return mx_handle; } -// Convert a vector of shared_ptr in C++ to handle struct array in matlab +// Convert a vector of shared_ptr in C++ to handle struct vector template -static mxArray* ptr_vec_to_handles(const vector >& ptr_vec) { - mxArray* mx_handle = create_handles(ptr_vec.size()); +static mxArray* ptr_vec_to_handle_vec(const vector >& ptr_vec) { + mxArray* mx_handle_vec = create_handle_vec(ptr_vec.size()); for (int i = 0; i < ptr_vec.size(); i++) { - setup_handle(ptr_vec[i].get(), i, mx_handle); + setup_handle(ptr_vec[i].get(), i, mx_handle_vec); } - return mx_handle; + return mx_handle_vec; } /** ----------------------------------------------------------------- @@ -182,12 +186,12 @@ static mxArray* ptr_vec_to_handles(const vector >& ptr_vec) { static void get_solver(MEX_ARGS) { mxCHECK(nrhs == 1 && mxIsChar(prhs[0]), "Usage: caffe_('get_solver', solver_file)"); - const char* solver_file = mxArrayToString(prhs[0]); + char* solver_file = mxArrayToString(prhs[0]); mxCHECK_FILE_EXIST(solver_file); - shared_ptr > solver; - solver.reset(new caffe::SGDSolver(solver_file)); + shared_ptr > solver(new caffe::SGDSolver(solver_file)); solvers_.push_back(solver); plhs[0] = ptr_to_handle >(solver.get()); + mxFree(solver_file); } // Usage: caffe_('solver_get_attr', hSolver) @@ -202,7 +206,7 @@ static void solver_get_attr(MEX_ARGS) { mxSetField(mx_solver_attr, 0, "hNet_net", ptr_to_handle >(solver->net().get())); mxSetField(mx_solver_attr, 0, "hNet_test_nets", - ptr_vec_to_handles >(solver->test_nets())); + ptr_vec_to_handle_vec >(solver->test_nets())); plhs[0] = mx_solver_attr; } @@ -219,9 +223,10 @@ static void solver_restore(MEX_ARGS) { mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), "Usage: caffe_('solver_restore', hSolver, snapshot_file)"); Solver* solver = handle_to_ptr >(prhs[0]); - const char* snapshot_file = mxArrayToString(prhs[1]); + char* snapshot_file = mxArrayToString(prhs[1]); mxCHECK_FILE_EXIST(snapshot_file); solver->Restore(snapshot_file); + mxFree(snapshot_file); } // Usage: caffe_('solver_solve', hSolver) @@ -232,10 +237,10 @@ static void solver_solve(MEX_ARGS) { solver->Solve(); } -// Usage: caffe_('solver_solve', hSolver, iters) +// Usage: caffe_('solver_step', hSolver, iters) static void solver_step(MEX_ARGS) { mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsDouble(prhs[1]), - "Usage: caffe_('solver_solve', hSolver, iters)"); + "Usage: caffe_('solver_step', hSolver, iters)"); Solver* solver = handle_to_ptr >(prhs[0]); int iters = mxGetScalar(prhs[1]); solver->Step(iters); @@ -245,21 +250,22 @@ static void solver_step(MEX_ARGS) { static void get_net(MEX_ARGS) { mxCHECK(nrhs == 2 && mxIsChar(prhs[0]) && mxIsChar(prhs[1]), "Usage: caffe_('get_net', model_file, phase_name)"); - const char* model_file = mxArrayToString(prhs[0]); + char* model_file = mxArrayToString(prhs[0]); + char* phase_name = mxArrayToString(prhs[1]); mxCHECK_FILE_EXIST(model_file); - const char* phase_name = mxArrayToString(prhs[1]); Phase phase; if (strcmp(phase_name, "train") == 0) { phase = TRAIN; } else if (strcmp(phase_name, "test") == 0) { phase = TEST; } else { - mxERROR("Unknown phase."); + mxERROR("Unknown phase"); } - shared_ptr > net; - net.reset(new caffe::Net(model_file, phase)); + shared_ptr > net(new caffe::Net(model_file, phase)); nets_.push_back(net); plhs[0] = ptr_to_handle >(net.get()); + mxFree(model_file); + mxFree(phase_name); } // Usage: caffe_('net_get_attr', hNet) @@ -273,15 +279,15 @@ static void net_get_attr(MEX_ARGS) { mxArray* mx_net_attr = mxCreateStructMatrix(1, 1, net_attr_num, net_attrs); mxSetField(mx_net_attr, 0, "hLayer_layers", - ptr_vec_to_handles >(net->layers())); + ptr_vec_to_handle_vec >(net->layers())); mxSetField(mx_net_attr, 0, "hBlob_blobs", - ptr_vec_to_handles >(net->blobs())); + ptr_vec_to_handle_vec >(net->blobs())); mxSetField(mx_net_attr, 0, "input_blob_indices", int_vec_to_mx_vec(net->input_blob_indices())); mxSetField(mx_net_attr, 0, "output_blob_indices", int_vec_to_mx_vec(net->output_blob_indices())); mxSetField(mx_net_attr, 0, "layer_names", - str_vec_to_mx_strcell(net->layer_names())); + str_vec_to_mx_strcell(net->layer_names())); mxSetField(mx_net_attr, 0, "blob_names", str_vec_to_mx_strcell(net->blob_names())); plhs[0] = mx_net_attr; @@ -308,9 +314,10 @@ static void net_copy_from(MEX_ARGS) { mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), "Usage: caffe_('net_copy_from', hNet, weights_file)"); Net* net = handle_to_ptr >(prhs[0]); - const char* weights_file = mxArrayToString(prhs[1]); + char* weights_file = mxArrayToString(prhs[1]); mxCHECK_FILE_EXIST(weights_file); net->CopyTrainedLayersFrom(weights_file); + mxFree(weights_file); } // Usage: caffe_('net_reshape', hNet) @@ -326,10 +333,11 @@ static void net_save(MEX_ARGS) { mxCHECK(nrhs == 2 && mxIsStruct(prhs[0]) && mxIsChar(prhs[1]), "Usage: caffe_('net_save', hNet, save_file)"); Net* net = handle_to_ptr >(prhs[0]); - const char* weights_file = mxArrayToString(prhs[1]); + char* weights_file = mxArrayToString(prhs[1]); NetParameter net_param; net->ToProto(&net_param, false); WriteProtoToBinaryFile(net_param, weights_file); + mxFree(weights_file); } // Usage: caffe_('layer_get_attr', hLayer) @@ -342,14 +350,14 @@ static void layer_get_attr(MEX_ARGS) { mxArray* mx_layer_attr = mxCreateStructMatrix(1, 1, layer_attr_num, layer_attrs); mxSetField(mx_layer_attr, 0, "hBlob_blobs", - ptr_vec_to_handles >(layer->blobs())); + ptr_vec_to_handle_vec >(layer->blobs())); plhs[0] = mx_layer_attr; } -// Usage: caffe_('layer_get_attr', hLayer) +// Usage: caffe_('layer_get_type', hLayer) static void layer_get_type(MEX_ARGS) { mxCHECK(nrhs == 1 && mxIsStruct(prhs[0]), - "Usage: caffe_('layer_get_attr', hLayer)"); + "Usage: caffe_('layer_get_type', hLayer)"); Layer* layer = handle_to_ptr >(prhs[0]); plhs[0] = mxCreateString(layer->type()); } @@ -446,10 +454,12 @@ static void get_init_key(MEX_ARGS) { // Usage: caffe_('reset') static void reset(MEX_ARGS) { mxCHECK(nrhs == 0, "Usage: caffe_('reset')"); - mexPrintf("cleared %d solvers and %d stand-alone nets\n", solvers_.size(), - nets_.size()); + // Clear solvers and stand-alone nets + mexPrintf("Cleared %d solvers and %d stand-alone nets\n", + solvers_.size(), nets_.size()); solvers_.clear(); nets_.clear(); + // Generate new init_key, so that handles created before becomes invalid init_key = static_cast(caffe_rng_rand()); } @@ -457,21 +467,15 @@ static void reset(MEX_ARGS) { static void read_mean(MEX_ARGS) { mxCHECK(nrhs == 1 && mxIsChar(prhs[0]), "Usage: caffe_('read_mean', mean_proto_file)"); - const char* mean_proto_file = mxArrayToString(prhs[0]); + char* mean_proto_file = mxArrayToString(prhs[0]); + mxCHECK_FILE_EXIST(mean_proto_file); Blob data_mean; - mexPrintf("Loading mean file from: %s\n", mean_proto_file); BlobProto blob_proto; bool result = ReadProtoFromBinaryFile(mean_proto_file, &blob_proto); - mxCHECK(result, "Couldn't read the file"); + mxCHECK(result, "Could not read your mean file"); data_mean.FromProto(blob_proto); - mwSize dims[4] = {data_mean.width(), data_mean.height(), - data_mean.channels(), data_mean.num() }; - mxArray* mx_blob = mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); - float* data_ptr = reinterpret_cast(mxGetData(mx_blob)); - caffe_copy(data_mean.count(), data_mean.cpu_data(), data_ptr); - mexPrintf("Remember that Caffe saves in [width, height, channels]" - " format and channels are also BGR!\n"); - plhs[0] = mx_blob; + plhs[0] = blob_to_mx_mat(&data_mean, DATA); + mxFree(mean_proto_file); } /** ----------------------------------------------------------------- @@ -516,31 +520,27 @@ static handler_registry handlers[] = { }; /** ----------------------------------------------------------------- - ** matlab entry point: caffe_(api_command, arg1, arg2, ...) + ** matlab entry point. **/ +// Usage: caffe_(api_command, arg1, arg2, ...) void mexFunction(MEX_ARGS) { mexLock(); // Avoid clearing the mex file. - if (nrhs == 0) { - mxERROR("No API command given"); - return; - } - - { // Handle input command - char* cmd = mxArrayToString(prhs[0]); - bool dispatched = false; - // Dispatch to cmd handler - for (int i = 0; handlers[i].func != NULL; i++) { - if (handlers[i].cmd.compare(cmd) == 0) { - handlers[i].func(nlhs, plhs, nrhs-1, prhs+1); - dispatched = true; - break; - } - } - if (!dispatched) { - ostringstream error_msg; - error_msg << "Unknown command '" << cmd << "'"; - mxERROR(error_msg.str().c_str()); + mxCHECK(nrhs > 0, "Usage: caffe_(api_command, arg1, arg2, ...)"); + // Handle input command + char* cmd = mxArrayToString(prhs[0]); + bool dispatched = false; + // Dispatch to cmd handler + for (int i = 0; handlers[i].func != NULL; i++) { + if (handlers[i].cmd.compare(cmd) == 0) { + handlers[i].func(nlhs, plhs, nrhs-1, prhs+1); + dispatched = true; + break; } - mxFree(cmd); } + if (!dispatched) { + ostringstream error_msg; + error_msg << "Unknown command '" << cmd << "'"; + mxERROR(error_msg.str().c_str()); + } + mxFree(cmd); } diff --git a/matlab/+caffe/private/is_valid_handle.m b/matlab/+caffe/private/is_valid_handle.m index 77abf21dd11..a0648ecdf61 100644 --- a/matlab/+caffe/private/is_valid_handle.m +++ b/matlab/+caffe/private/is_valid_handle.m @@ -15,7 +15,6 @@ % is_valid_handle('get_new_init_key') to get new init_key from C++; if ischar(hObj) && strcmp(hObj, 'get_new_init_key') init_key = caffe_('get_init_key'); - valid = true; return else % check whether data types are correct and init_key matches diff --git a/matlab/+caffe/run_tests.m b/matlab/+caffe/run_tests.m index fb1089c2d96..afdd8f3309d 100644 --- a/matlab/+caffe/run_tests.m +++ b/matlab/+caffe/run_tests.m @@ -2,11 +2,15 @@ % results = run_tests() % run all tests in this caffe matlab wrapper package +% reset caffe before testing caffe.reset(); + +% put all test cases here results = [... run(caffe.test.test_net) ... - run(caffe.test.test_solver) - ]; + run(caffe.test.test_solver) ]; + +% reset caffe after testing caffe.reset(); end From 6af8efc0bf51d475077be146e5ab7b7899ad705d Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Thu, 28 May 2015 14:52:47 +0800 Subject: [PATCH 4/8] Add MatCaffe docs to docs/tutorial/interfaces.md --- docs/tutorial/interfaces.md | 209 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/interfaces.md b/docs/tutorial/interfaces.md index 17430b35c57..f824c7cc54b 100644 --- a/docs/tutorial/interfaces.md +++ b/docs/tutorial/interfaces.md @@ -67,10 +67,211 @@ Compile pycaffe by `make pycaffe`. The module dir caffe/python/caffe should be i ## MATLAB -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. +The MATLAB interface -- matcaffe -- is a `caffe` package in caffe/matlab in which you can integrate Caffe in your Matlab code. -A MATLAB demo is in caffe/matlab/caffe/matcaffe_demo.m +In MatCaffe, you can -Note that MATLAB matrices and memory are in column-major layout counter to Caffe's row-major layout! Double-check your work accordingly. +* Creating multiple Nets in Matlab +* Do forward and backward computation +* Access any layer within a network, and any parameter blob in a layer +* Get and set data or diff to any blob within a network, not restricting to input blobs or output blobs +* Save a network's parameters to file, and load parameters from file +* Reshape a blob and reshape a network +* Edit network parameter and do network surgery +* Create multiple Solvers in Matlab for training +* Resume training from solver snapshots +* Access train net and test nets in a solver +* Run for a certain number of iterations and give back control to Matlab +* Intermingle arbitrary Matlab code to with gradient steps -Compile matcaffe by `make matcaffe`. +A MATLAB demo is in caffe/matlab/matcaffe_demo.m + +### Build MatCaffe + +Build MatCaffe with `make all matcaffe`. After that, you may test it using `make mattest`. + +Common issue: if you run into error messages `libstdc++.so.6:version 'GLIBCXX_3.4.15' not found` during `make mattest`, then it means that your Matlab's runtime libraries does not match your compile-time libraries. You may need to do the following before you start matlab: + + export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64 + export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 + +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` in Matlab to see its runtime libraries, and preload your compile-time libraries by exporting them to your `LD_PRELOAD` environment variable. + +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. + + addpath ./matlab + +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. + +### Use MatCaffe + +MatCaffe is very similar to PyCaffe in usage. + +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. + + model = './models/bvlc_reference_caffenet/deploy.prototxt'; + weights = './models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'; + +#### Set mode and device + +**Mode and device should always be set BEFORE you create a net or a solver.** + +Use CPU: + + caffe.set_mode_cpu(); + +Use GPU and specify its gpu_id: + + caffe.set_mode_gpu(); + caffe.set_device(gpu_id); + +#### Create a network and access its layers and blobs + +Create a network: + + net = caffe.Net(model, weights, 'test'); % create net and load weights + +Or + + net = caffe.Net(model, 'test'); % create net but not load weights + net.copy_from(weights); % load weights + +which creates `net` object as + + Net with properties: + + layer_vec: [1x23 caffe.Layer] + blob_vec: [1x15 caffe.Blob] + inputs: {'data'} + outputs: {'prob'} + name2layer_index: [23x1 containers.Map] + name2blob_index: [15x1 containers.Map] + layer_names: {23x1 cell} + blob_names: {15x1 cell} + +The two `containers.Map` objects are useful to find the index of a layer or a blob by its name. + +You have access to every blob in this network. To fill blob 'data' with all ones: + + net.blobs('data').set_data(ones(net.blobs('data').shape)); + +To multiply all values in blob 'data' by 10: + + net.blobs('data').set_data(net.blobs('data').get_data() * 10); + +**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. + +You also have access to every layer, so you can do network surgery. For example, to multiply conv1 parameters by 10: + + net.params('conv1', 1).set_data(net.params('conv1', 1).get_data() * 10); % set weights + net.params('conv1', 2).set_data(net.params('conv1', 2).get_data() * 10); % set bias + +Alternatively, you can use + + net.layers('conv1').params(1).set_data(net.layers('conv1').params(1).get_data() * 10); + net.layers('conv1').params(2).set_data(net.layers('conv1').params(2).get_data() * 10); + +To save the network you just modified: + + net.save('my_net.caffemodel'); + +To get a layer's type (string): + + layer_type = net.layers('conv1').type; + +#### Forward and backward + +Forward pass can be done using `net.forward` or `net.forward_prefilled`. After creating some data for input blobs like `data = rand(net.blobs('data').shape);` you can run + + res = net.forward({data}); + prob = res{1}; + +Or + + net.blobs('data').set_data(data); + net.forward_prefilled(); + prob = net.blobs('prob').get_data(); + +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 + + res = net.backward({prob_diff}); + data_diff = res{1}; + +Or + + net.blobs('prob').set_diff(prob_diff); + net.backward_prefilled(); + data_diff = net.blobs('data').get_diff(); + +**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.** + +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: + + pool5_feat = net.blobs('pool5').get_data(); + +#### Reshape + +Assume you want to run 1 image at a time instead of 10: + + net.blobs('data').reshape([227 227 3 1]); % reshape blob 'data' + net.reshape(); + +Then the whole network is reshaped, and now `net.blobs('prob').shape` should be `[1000 1]`; + +#### Training + +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: + + solver = caffe.Solver('./models/bvlc_reference_caffenet/solver.prototxt'); + +which creates `solver` object as + + Solver with properties: + + net: [1x1 caffe.Net] + test_nets: [1x1 caffe.Net] + +To train: + + solver.solve(); + +Or train for only 1000 iterations (so that you can do something to its net before training more iterations) + + solver.step(1000); + +To get iteration number: + + iter = solver.iter(); + +To get its network: + + train_net = solver.net; + test_net = solver.test_nets(1); + +To resume from a snapshot "your_snapshot.solverstate": + + solver.restore('your_snapshot.solverstate'); + +#### Input and output + +`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`): + + mean_data = caffe.io.read_mean('./data/ilsvrc12/imagenet_mean.binaryproto'); + +To read Caffe's example image and resize to `[width, height]` and suppose we want `width = 256; height = 256;` + + im_data = caffe.io.load_image('./examples/images/cat.jpg'); + im_data = imresize(im_data, [width, height]); % resize using Matlab's imresize + +**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 + + im_data = imread('./examples/images/cat.jpg'); % read image + im_data = im_data(:, :, [3, 2, 1]); % convert from RGB to BGR + im_data = permute(im_data, [2, 1, 3]); % permute width and height + im_data = single(im_data); % convert to single precision + +We do not provide extra functions for data output as Matlab itself is already quite powerful in output. + +#### Clear nets and solvers + +Call `caffe.reset()` to clear all solvers and stand-alone nets you have created. From d3d7d07be981ab046da99e1ad5d84217bf0ec4c3 Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Thu, 28 May 2015 16:24:30 +0800 Subject: [PATCH 5/8] Clean up old matcaffe wrapper and rename caffe.reset to caffe.reset_all Remove old matlab wrapper but keep the classification demo and hdf5 demo Change 'caffe.reset()' to 'caffe.reset_all()' to avoid potential name conflict. Otherwise, Matlab R2015a complains: Warning: Function reset has the same name as a MATLAB builtin. We suggest you rename the function to avoid a potential name conflict. --- Makefile | 25 +- docs/tutorial/interfaces.md | 8 +- .../imagenet}/ilsvrc_2012_mean.mat | Bin matlab/+caffe/reset.m | 8 - matlab/+caffe/reset_all.m | 8 + matlab/+caffe/run_tests.m | 4 +- matlab/CMakeLists.txt | 12 +- matlab/caffe/matcaffe.cpp | 421 --------------------- matlab/caffe/matcaffe_batch.m | 75 ---- matlab/caffe/matcaffe_demo_vgg.m | 96 ----- matlab/caffe/matcaffe_demo_vgg_mean_pix.m | 102 ----- matlab/caffe/matcaffe_init.m | 41 -- matlab/caffe/prepare_batch.m | 41 -- matlab/caffe/print_cell.m | 42 -- matlab/caffe/read_cell.m | 21 - .../matcaffe_demo.m => classification_demo.m} | 64 +++- matlab/{caffe => }/hdf5creation/.gitignore | 0 matlab/{caffe => }/hdf5creation/demo.m | 0 matlab/{caffe => }/hdf5creation/store2hdf5.m | 0 19 files changed, 63 insertions(+), 905 deletions(-) rename matlab/{caffe => +caffe/imagenet}/ilsvrc_2012_mean.mat (100%) delete mode 100644 matlab/+caffe/reset.m create mode 100644 matlab/+caffe/reset_all.m delete mode 100644 matlab/caffe/matcaffe.cpp delete mode 100644 matlab/caffe/matcaffe_batch.m delete mode 100644 matlab/caffe/matcaffe_demo_vgg.m delete mode 100644 matlab/caffe/matcaffe_demo_vgg_mean_pix.m delete mode 100644 matlab/caffe/matcaffe_init.m delete mode 100644 matlab/caffe/prepare_batch.m delete mode 100644 matlab/caffe/print_cell.m delete mode 100644 matlab/caffe/read_cell.m rename matlab/{caffe/matcaffe_demo.m => classification_demo.m} (56%) rename matlab/{caffe => }/hdf5creation/.gitignore (100%) rename matlab/{caffe => }/hdf5creation/demo.m (100%) rename matlab/{caffe => }/hdf5creation/store2hdf5.m (100%) diff --git a/Makefile b/Makefile index 74b167763c3..3748b52ca47 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,6 @@ NONGEN_CXX_SRCS := $(shell find \ src/$(PROJECT) \ include/$(PROJECT) \ python/$(PROJECT) \ - matlab/$(PROJECT) \ matlab/+$(PROJECT)/private \ examples \ tools \ @@ -80,15 +79,12 @@ NONEMPTY_LINT_REPORT := $(BUILD_DIR)/$(LINT_EXT) PY$(PROJECT)_SRC := python/$(PROJECT)/_$(PROJECT).cpp PY$(PROJECT)_SO := python/$(PROJECT)/_$(PROJECT).so PY$(PROJECT)_HXX := include/$(PROJECT)/python_layer.hpp -# MAT$(PROJECT)_SRC is the matlab wrapper for $(PROJECT) -MAT$(PROJECT)_SRC := matlab/$(PROJECT)/mat$(PROJECT).cpp -# MAT$(PROJECT)_PKG_SRC is the mex entrance point of matlab package for $(PROJECT) -MAT$(PROJECT)_PKG_SRC := matlab/+$(PROJECT)/private/$(PROJECT)_.cpp +# MAT$(PROJECT)_SRC is the mex entrance point of matlab package for $(PROJECT) +MAT$(PROJECT)_SRC := matlab/+$(PROJECT)/private/$(PROJECT)_.cpp ifneq ($(MATLAB_DIR),) MAT_SO_EXT := $(shell $(MATLAB_DIR)/bin/mexext) endif -MAT$(PROJECT)_SO := matlab/$(PROJECT)/$(PROJECT).$(MAT_SO_EXT) -MAT$(PROJECT)_PKG_SO := matlab/+$(PROJECT)/private/$(PROJECT)_.$(MAT_SO_EXT) +MAT$(PROJECT)_SO := matlab/+$(PROJECT)/private/$(PROJECT)_.$(MAT_SO_EXT) ############################## # Derive generated files @@ -451,7 +447,7 @@ $(PY$(PROJECT)_SO): $(PY$(PROJECT)_SRC) $(PY$(PROJECT)_HXX) | $(DYNAMIC_NAME) mat$(PROJECT): mat -mat: $(MAT$(PROJECT)_SO) $(MAT$(PROJECT)_PKG_SO) +mat: $(MAT$(PROJECT)_SO) $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME) @ if [ -z "$(MATLAB_DIR)" ]; then \ @@ -464,18 +460,6 @@ $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME) CXX="$(CXX)" \ CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \ CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@ - -$(MAT$(PROJECT)_PKG_SO): $(MAT$(PROJECT)_PKG_SRC) $(STATIC_NAME) - @ if [ -z "$(MATLAB_DIR)" ]; then \ - echo "MATLAB_DIR must be specified in $(CONFIG_FILE)" \ - "to build mat$(PROJECT)."; \ - exit 1; \ - fi - @ echo MEX $< - $(Q)$(MATLAB_DIR)/bin/mex $(MAT$(PROJECT)_PKG_SRC) \ - CXX="$(CXX)" \ - CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \ - CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@ runtest: $(TEST_ALL_BIN) $(TOOL_BUILD_DIR)/caffe @@ -601,7 +585,6 @@ clean: @- $(RM) -rf $(DISTRIBUTE_DIR) @- $(RM) $(PY$(PROJECT)_SO) @- $(RM) $(MAT$(PROJECT)_SO) - @- $(RM) $(MAT$(PROJECT)_PKG_SO) supercleanfiles: $(eval SUPERCLEAN_FILES := $(strip \ diff --git a/docs/tutorial/interfaces.md b/docs/tutorial/interfaces.md index f824c7cc54b..a57e17339b0 100644 --- a/docs/tutorial/interfaces.md +++ b/docs/tutorial/interfaces.md @@ -67,7 +67,7 @@ Compile pycaffe by `make pycaffe`. The module dir caffe/python/caffe should be i ## MATLAB -The MATLAB interface -- matcaffe -- is a `caffe` package in caffe/matlab in which you can integrate Caffe in your Matlab code. +The MATLAB interface -- matcaffe -- is the `caffe` package in caffe/matlab in which you can integrate Caffe in your Matlab code. In MatCaffe, you can @@ -84,7 +84,7 @@ In MatCaffe, you can * Run for a certain number of iterations and give back control to Matlab * Intermingle arbitrary Matlab code to with gradient steps -A MATLAB demo is in caffe/matlab/matcaffe_demo.m +An ILSVRC image classification demo is in caffe/matlab/classification_demo.m ### Build MatCaffe @@ -181,7 +181,7 @@ To get a layer's type (string): #### Forward and backward -Forward pass can be done using `net.forward` or `net.forward_prefilled`. After creating some data for input blobs like `data = rand(net.blobs('data').shape);` you can run +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 res = net.forward({data}); prob = res{1}; @@ -274,4 +274,4 @@ We do not provide extra functions for data output as Matlab itself is already qu #### Clear nets and solvers -Call `caffe.reset()` to clear all solvers and stand-alone nets you have created. +Call `caffe.reset_all()` to clear all solvers and stand-alone nets you have created. diff --git a/matlab/caffe/ilsvrc_2012_mean.mat b/matlab/+caffe/imagenet/ilsvrc_2012_mean.mat similarity index 100% rename from matlab/caffe/ilsvrc_2012_mean.mat rename to matlab/+caffe/imagenet/ilsvrc_2012_mean.mat diff --git a/matlab/+caffe/reset.m b/matlab/+caffe/reset.m deleted file mode 100644 index c1cfea41a76..00000000000 --- a/matlab/+caffe/reset.m +++ /dev/null @@ -1,8 +0,0 @@ -function reset() -% reset() -% reset Caffe to initial status - -caffe_('reset'); -is_valid_handle('get_new_init_key'); - -end diff --git a/matlab/+caffe/reset_all.m b/matlab/+caffe/reset_all.m new file mode 100644 index 00000000000..a8b33dee8d5 --- /dev/null +++ b/matlab/+caffe/reset_all.m @@ -0,0 +1,8 @@ +function reset_all() +% reset_all() +% clear all solvers and stand-alone nets and reset Caffe to initial status + +caffe_('reset'); +is_valid_handle('get_new_init_key'); + +end diff --git a/matlab/+caffe/run_tests.m b/matlab/+caffe/run_tests.m index afdd8f3309d..8773c9f638b 100644 --- a/matlab/+caffe/run_tests.m +++ b/matlab/+caffe/run_tests.m @@ -3,7 +3,7 @@ % run all tests in this caffe matlab wrapper package % reset caffe before testing -caffe.reset(); +caffe.reset_all(); % put all test cases here results = [... @@ -11,6 +11,6 @@ run(caffe.test.test_solver) ]; % reset caffe after testing -caffe.reset(); +caffe.reset_all(); end diff --git a/matlab/CMakeLists.txt b/matlab/CMakeLists.txt index 63182abcc44..4b0d549f07f 100644 --- a/matlab/CMakeLists.txt +++ b/matlab/CMakeLists.txt @@ -31,10 +31,8 @@ function(caffe_fetch_and_set_proper_mexext mexfile_variable) endfunction() # global settings -file(GLOB Matlab_srcs caffe/matcaffe.cpp) -file(GLOB Matlab_pkg_srcs +caffe/private/caffe_.cpp) -set(Matlab_caffe_mex ${PROJECT_SOURCE_DIR}/matlab/caffe/caffe.mex) -set(Matlab_caffe_pkg_mex ${PROJECT_SOURCE_DIR}/matlab/+caffe/private/caffe_.mex) +file(GLOB Matlab_srcs +caffe/private/caffe_.cpp) +set(Matlab_caffe_mex ${PROJECT_SOURCE_DIR}/matlab/+caffe/private/caffe_.mex) caffe_get_current_cflags(cflags) caffe_parse_linker_libs(Caffe_LINKER_LIBS folders libflags macos_frameworks) @@ -52,15 +50,9 @@ if(build_using MATCHES "Matlab") ARGS -output ${Matlab_caffe_mex} ${Matlab_srcs} ${cflags} ${link_folders} ${libflags} DEPENDS caffe COMMENT "Building Matlab interface: ${Matlab_caffe_mex}" VERBATIM) add_custom_target(matlab ALL DEPENDS ${Matlab_caffe_mex} SOURCES ${Matlab_srcs}) - caffe_fetch_and_set_proper_mexext(Matlab_caffe_pkg_mex) - add_custom_command(OUTPUT ${Matlab_caffe_pkg_mex} COMMAND ${Matlab_mex} - ARGS -output ${Matlab_caffe_pkg_mex} ${Matlab_pkg_srcs} ${cflags} ${link_folders} ${libflags} - DEPENDS caffe COMMENT "Building Matlab interface: ${Matlab_caffe_pkg_mex}" VERBATIM) - add_custom_target(matlab ALL DEPENDS ${Matlab_caffe_pkg_mex} SOURCES ${Matlab_pkg_srcs}) elseif(build_using MATCHES "Octave") - # Note: Matlab Caffe package cannot be used in Octave, so we don't build it if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(libflags -Wl,-force_load,$ ${libflags}) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") diff --git a/matlab/caffe/matcaffe.cpp b/matlab/caffe/matcaffe.cpp deleted file mode 100644 index da37d920b20..00000000000 --- a/matlab/caffe/matcaffe.cpp +++ /dev/null @@ -1,421 +0,0 @@ -// -// matcaffe.cpp provides a wrapper of the caffe::Net class as well as some -// caffe::Caffe functions so that one could easily call it from matlab. -// Note that for matlab, we will simply use float as the data type. - -#include -#include -#include - -#include "mex.h" - -#include "caffe/caffe.hpp" - -#define MEX_ARGS int nlhs, mxArray **plhs, int nrhs, const mxArray **prhs - -// Log and throw a Mex error -inline void mex_error(const std::string &msg) { - LOG(ERROR) << msg; - mexErrMsgTxt(msg.c_str()); -} - -using namespace caffe; // NOLINT(build/namespaces) - -// The pointer to the internal caffe::Net instance -static shared_ptr > net_; -static int init_key = -2; - -// Five things to be aware of: -// caffe uses row-major order -// matlab uses column-major order -// caffe uses BGR color channel order -// matlab uses RGB color channel order -// images need to have the data mean subtracted -// -// Data coming in from matlab needs to be in the order -// [width, height, channels, images] -// where width is the fastest dimension. -// Here is the rough matlab for putting image data into the correct -// format: -// % convert from uint8 to single -// im = single(im); -// % reshape to a fixed size (e.g., 227x227) -// im = imresize(im, [IMAGE_DIM IMAGE_DIM], 'bilinear'); -// % permute from RGB to BGR and subtract the data mean (already in BGR) -// im = im(:,:,[3 2 1]) - data_mean; -// % flip width and height to make width the fastest dimension -// im = permute(im, [2 1 3]); -// -// If you have multiple images, cat them with cat(4, ...) -// -// The actual forward function. It takes in a cell array of 4-D arrays as -// input and outputs a cell array. - -static mxArray* do_forward(const mxArray* const bottom) { - const vector*>& input_blobs = net_->input_blobs(); - if (static_cast(mxGetDimensions(bottom)[0]) != - input_blobs.size()) { - mex_error("Invalid input size"); - } - for (unsigned int i = 0; i < input_blobs.size(); ++i) { - const mxArray* const elem = mxGetCell(bottom, i); - if (!mxIsSingle(elem)) { - mex_error("MatCaffe require single-precision float point data"); - } - if (mxGetNumberOfElements(elem) != input_blobs[i]->count()) { - std::string error_msg; - error_msg += "MatCaffe input size does not match the input size "; - error_msg += "of the network"; - mex_error(error_msg); - } - - const float* const data_ptr = - reinterpret_cast(mxGetPr(elem)); - switch (Caffe::mode()) { - case Caffe::CPU: - caffe_copy(input_blobs[i]->count(), data_ptr, - input_blobs[i]->mutable_cpu_data()); - break; - case Caffe::GPU: - caffe_copy(input_blobs[i]->count(), data_ptr, - input_blobs[i]->mutable_gpu_data()); - break; - default: - mex_error("Unknown Caffe mode"); - } // switch (Caffe::mode()) - } - const vector*>& output_blobs = net_->ForwardPrefilled(); - mxArray* mx_out = mxCreateCellMatrix(output_blobs.size(), 1); - for (unsigned int i = 0; i < output_blobs.size(); ++i) { - // internally data is stored as (width, height, channels, num) - // where width is the fastest dimension - mwSize dims[4] = {output_blobs[i]->width(), output_blobs[i]->height(), - output_blobs[i]->channels(), output_blobs[i]->num()}; - mxArray* mx_blob = mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); - mxSetCell(mx_out, i, mx_blob); - float* data_ptr = reinterpret_cast(mxGetPr(mx_blob)); - switch (Caffe::mode()) { - case Caffe::CPU: - caffe_copy(output_blobs[i]->count(), output_blobs[i]->cpu_data(), - data_ptr); - break; - case Caffe::GPU: - caffe_copy(output_blobs[i]->count(), output_blobs[i]->gpu_data(), - data_ptr); - break; - default: - mex_error("Unknown Caffe mode"); - } // switch (Caffe::mode()) - } - - return mx_out; -} - -static mxArray* do_backward(const mxArray* const top_diff) { - const vector*>& output_blobs = net_->output_blobs(); - const vector*>& input_blobs = net_->input_blobs(); - if (static_cast(mxGetDimensions(top_diff)[0]) != - output_blobs.size()) { - mex_error("Invalid input size"); - } - // First, copy the output diff - for (unsigned int i = 0; i < output_blobs.size(); ++i) { - const mxArray* const elem = mxGetCell(top_diff, i); - const float* const data_ptr = - reinterpret_cast(mxGetPr(elem)); - switch (Caffe::mode()) { - case Caffe::CPU: - caffe_copy(output_blobs[i]->count(), data_ptr, - output_blobs[i]->mutable_cpu_diff()); - break; - case Caffe::GPU: - caffe_copy(output_blobs[i]->count(), data_ptr, - output_blobs[i]->mutable_gpu_diff()); - break; - default: - mex_error("Unknown Caffe mode"); - } // switch (Caffe::mode()) - } - // LOG(INFO) << "Start"; - net_->Backward(); - // LOG(INFO) << "End"; - mxArray* mx_out = mxCreateCellMatrix(input_blobs.size(), 1); - for (unsigned int i = 0; i < input_blobs.size(); ++i) { - // internally data is stored as (width, height, channels, num) - // where width is the fastest dimension - mwSize dims[4] = {input_blobs[i]->width(), input_blobs[i]->height(), - input_blobs[i]->channels(), input_blobs[i]->num()}; - mxArray* mx_blob = mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); - mxSetCell(mx_out, i, mx_blob); - float* data_ptr = reinterpret_cast(mxGetPr(mx_blob)); - switch (Caffe::mode()) { - case Caffe::CPU: - caffe_copy(input_blobs[i]->count(), input_blobs[i]->cpu_diff(), data_ptr); - break; - case Caffe::GPU: - caffe_copy(input_blobs[i]->count(), input_blobs[i]->gpu_diff(), data_ptr); - break; - default: - mex_error("Unknown Caffe mode"); - } // switch (Caffe::mode()) - } - - return mx_out; -} - -static mxArray* do_get_weights() { - const vector > >& layers = net_->layers(); - const vector& layer_names = net_->layer_names(); - - // Step 1: count the number of layers with weights - int num_layers = 0; - { - string prev_layer_name = ""; - for (unsigned int i = 0; i < layers.size(); ++i) { - vector > >& layer_blobs = layers[i]->blobs(); - if (layer_blobs.size() == 0) { - continue; - } - if (layer_names[i] != prev_layer_name) { - prev_layer_name = layer_names[i]; - num_layers++; - } - } - } - - // Step 2: prepare output array of structures - mxArray* mx_layers; - { - const mwSize dims[2] = {num_layers, 1}; - const char* fnames[2] = {"weights", "layer_names"}; - mx_layers = mxCreateStructArray(2, dims, 2, fnames); - } - - // Step 3: copy weights into output - { - string prev_layer_name = ""; - int mx_layer_index = 0; - for (unsigned int i = 0; i < layers.size(); ++i) { - vector > >& layer_blobs = layers[i]->blobs(); - if (layer_blobs.size() == 0) { - continue; - } - - mxArray* mx_layer_cells = NULL; - if (layer_names[i] != prev_layer_name) { - prev_layer_name = layer_names[i]; - const mwSize dims[2] = {static_cast(layer_blobs.size()), 1}; - mx_layer_cells = mxCreateCellArray(2, dims); - mxSetField(mx_layers, mx_layer_index, "weights", mx_layer_cells); - mxSetField(mx_layers, mx_layer_index, "layer_names", - mxCreateString(layer_names[i].c_str())); - mx_layer_index++; - } - - for (unsigned int j = 0; j < layer_blobs.size(); ++j) { - // internally data is stored as (width, height, channels, num) - // where width is the fastest dimension - mwSize dims[4] = {layer_blobs[j]->width(), layer_blobs[j]->height(), - layer_blobs[j]->channels(), layer_blobs[j]->num()}; - - mxArray* mx_weights = - mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); - mxSetCell(mx_layer_cells, j, mx_weights); - float* weights_ptr = reinterpret_cast(mxGetPr(mx_weights)); - - switch (Caffe::mode()) { - case Caffe::CPU: - caffe_copy(layer_blobs[j]->count(), layer_blobs[j]->cpu_data(), - weights_ptr); - break; - case Caffe::GPU: - caffe_copy(layer_blobs[j]->count(), layer_blobs[j]->gpu_data(), - weights_ptr); - break; - default: - mex_error("Unknown Caffe mode"); - } - } - } - } - - return mx_layers; -} - -static void get_weights(MEX_ARGS) { - plhs[0] = do_get_weights(); -} - -static void set_mode_cpu(MEX_ARGS) { - Caffe::set_mode(Caffe::CPU); -} - -static void set_mode_gpu(MEX_ARGS) { - Caffe::set_mode(Caffe::GPU); -} - -static void set_device(MEX_ARGS) { - if (nrhs != 1) { - ostringstream error_msg; - error_msg << "Expected 1 argument, got " << nrhs; - mex_error(error_msg.str()); - } - - int device_id = static_cast(mxGetScalar(prhs[0])); - Caffe::SetDevice(device_id); -} - -static void get_init_key(MEX_ARGS) { - plhs[0] = mxCreateDoubleScalar(init_key); -} - -static void init(MEX_ARGS) { - if (nrhs != 3) { - ostringstream error_msg; - error_msg << "Expected 3 arguments, got " << nrhs; - mex_error(error_msg.str()); - } - - char* param_file = mxArrayToString(prhs[0]); - char* model_file = mxArrayToString(prhs[1]); - char* phase_name = mxArrayToString(prhs[2]); - - Phase phase; - if (strcmp(phase_name, "train") == 0) { - phase = TRAIN; - } else if (strcmp(phase_name, "test") == 0) { - phase = TEST; - } else { - mex_error("Unknown phase."); - } - - net_.reset(new Net(string(param_file), phase)); - net_->CopyTrainedLayersFrom(string(model_file)); - - mxFree(param_file); - mxFree(model_file); - mxFree(phase_name); - - init_key = random(); // NOLINT(caffe/random_fn) - - if (nlhs == 1) { - plhs[0] = mxCreateDoubleScalar(init_key); - } -} - -static void reset(MEX_ARGS) { - if (net_) { - net_.reset(); - init_key = -2; - LOG(INFO) << "Network reset, call init before use it again"; - } -} - -static void forward(MEX_ARGS) { - if (nrhs != 1) { - ostringstream error_msg; - error_msg << "Expected 1 argument, got " << nrhs; - mex_error(error_msg.str()); - } - - plhs[0] = do_forward(prhs[0]); -} - -static void backward(MEX_ARGS) { - if (nrhs != 1) { - ostringstream error_msg; - error_msg << "Expected 1 argument, got " << nrhs; - mex_error(error_msg.str()); - } - - plhs[0] = do_backward(prhs[0]); -} - -static void is_initialized(MEX_ARGS) { - if (!net_) { - plhs[0] = mxCreateDoubleScalar(0); - } else { - plhs[0] = mxCreateDoubleScalar(1); - } -} - -static void read_mean(MEX_ARGS) { - if (nrhs != 1) { - mexErrMsgTxt("Usage: caffe('read_mean', 'path_to_binary_mean_file'"); - return; - } - const string& mean_file = mxArrayToString(prhs[0]); - Blob data_mean; - LOG(INFO) << "Loading mean file from: " << mean_file; - BlobProto blob_proto; - bool result = ReadProtoFromBinaryFile(mean_file.c_str(), &blob_proto); - if (!result) { - mexErrMsgTxt("Couldn't read the file"); - return; - } - data_mean.FromProto(blob_proto); - mwSize dims[4] = {data_mean.width(), data_mean.height(), - data_mean.channels(), data_mean.num() }; - mxArray* mx_blob = mxCreateNumericArray(4, dims, mxSINGLE_CLASS, mxREAL); - float* data_ptr = reinterpret_cast(mxGetPr(mx_blob)); - caffe_copy(data_mean.count(), data_mean.cpu_data(), data_ptr); - mexWarnMsgTxt("Remember that Caffe saves in [width, height, channels]" - " format and channels are also BGR!"); - plhs[0] = mx_blob; -} - -/** ----------------------------------------------------------------- - ** Available commands. - **/ -struct handler_registry { - string cmd; - void (*func)(MEX_ARGS); -}; - -static handler_registry handlers[] = { - // Public API functions - { "forward", forward }, - { "backward", backward }, - { "init", init }, - { "is_initialized", is_initialized }, - { "set_mode_cpu", set_mode_cpu }, - { "set_mode_gpu", set_mode_gpu }, - { "set_device", set_device }, - { "get_weights", get_weights }, - { "get_init_key", get_init_key }, - { "reset", reset }, - { "read_mean", read_mean }, - // The end. - { "END", NULL }, -}; - - -/** ----------------------------------------------------------------- - ** matlab entry point: caffe(api_command, arg1, arg2, ...) - **/ -void mexFunction(MEX_ARGS) { - mexLock(); // Avoid clearing the mex file. - if (nrhs == 0) { - mex_error("No API command given"); - return; - } - - { // Handle input command - char *cmd = mxArrayToString(prhs[0]); - bool dispatched = false; - // Dispatch to cmd handler - for (int i = 0; handlers[i].func != NULL; i++) { - if (handlers[i].cmd.compare(cmd) == 0) { - handlers[i].func(nlhs, plhs, nrhs-1, prhs+1); - dispatched = true; - break; - } - } - if (!dispatched) { - ostringstream error_msg; - error_msg << "Unknown command '" << cmd << "'"; - mex_error(error_msg.str()); - } - mxFree(cmd); - } -} diff --git a/matlab/caffe/matcaffe_batch.m b/matlab/caffe/matcaffe_batch.m deleted file mode 100644 index f6d1aa83b84..00000000000 --- a/matlab/caffe/matcaffe_batch.m +++ /dev/null @@ -1,75 +0,0 @@ -function [scores,list_im] = matcaffe_batch(list_im, use_gpu) -% scores = matcaffe_batch(list_im, use_gpu) -% -% Demo of the matlab wrapper using the ILSVRC network. -% -% input -% list_im list of images files -% use_gpu 1 to use the GPU, 0 to use the CPU -% -% output -% scores 1000 x num_images ILSVRC output vector -% -% You may need to do the following before you start matlab: -% $ export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64 -% $ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 -% Or the equivalent based on where things are installed on your system -% -% Usage: -% scores = matcaffe_batch({'peppers.png','onion.png'}); -% scores = matcaffe_batch('list_images.txt', 1); -if nargin < 1 - % For test purposes - list_im = {'peppers.png','onions.png'}; -end -if ischar(list_im) - %Assume it is a file contaning the list of images - filename = list_im; - list_im = read_cell(filename); -end -% Adjust the batch size and dim to match with models/bvlc_reference_caffenet/deploy.prototxt -batch_size = 10; -dim = 1000; -disp(list_im) -if mod(length(list_im),batch_size) - warning(['Assuming batches of ' num2str(batch_size) ' images rest will be filled with zeros']) -end - -% init caffe network (spews logging info) -if exist('use_gpu', 'var') - matcaffe_init(use_gpu); -else - matcaffe_init(); -end - -d = load('ilsvrc_2012_mean'); -IMAGE_MEAN = d.image_mean; - -% prepare input - -num_images = length(list_im); -scores = zeros(dim,num_images,'single'); -num_batches = ceil(length(list_im)/batch_size) -initic=tic; -for bb = 1 : num_batches - batchtic = tic; - range = 1+batch_size*(bb-1):min(num_images,batch_size * bb); - tic - input_data = prepare_batch(list_im(range),IMAGE_MEAN,batch_size); - toc, tic - fprintf('Batch %d out of %d %.2f%% Complete ETA %.2f seconds\n',... - bb,num_batches,bb/num_batches*100,toc(initic)/bb*(num_batches-bb)); - output_data = caffe('forward', {input_data}); - toc - output_data = squeeze(output_data{1}); - scores(:,range) = output_data(:,mod(range-1,batch_size)+1); - toc(batchtic) -end -toc(initic); - -if exist('filename', 'var') - save([filename '.probs.mat'],'list_im','scores','-v7.3'); -end - - - diff --git a/matlab/caffe/matcaffe_demo_vgg.m b/matlab/caffe/matcaffe_demo_vgg.m deleted file mode 100644 index 4e5a98eb5f4..00000000000 --- a/matlab/caffe/matcaffe_demo_vgg.m +++ /dev/null @@ -1,96 +0,0 @@ -function scores = matcaffe_demo_vgg(im, use_gpu, model_def_file, model_file, mean_file) -% scores = matcaffe_demo_vgg(im, use_gpu, model_def_file, model_file, mean_file) -% -% Demo of the matlab wrapper using the networks described in the BMVC-2014 paper "Return of the Devil in the Details: Delving Deep into Convolutional Nets" -% -% INPUT -% im - color image as uint8 HxWx3 -% use_gpu - 1 to use the GPU, 0 to use the CPU -% model_def_file - network configuration (.prototxt file) -% model_file - network weights (.caffemodel file) -% mean_file - mean BGR image as uint8 HxWx3 (.mat file) -% -% OUTPUT -% scores 1000-dimensional ILSVRC score vector -% -% EXAMPLE USAGE -% model_def_file = 'zoo/VGG_CNN_F_deploy.prototxt'; -% model_file = 'zoo/VGG_CNN_F.caffemodel'; -% mean_file = 'zoo/VGG_mean.mat'; -% use_gpu = true; -% im = imread('../../examples/images/cat.jpg'); -% scores = matcaffe_demo_vgg(im, use_gpu, model_def_file, model_file, mean_file); -% -% NOTES -% the image crops are prepared as described in the paper (the aspect ratio is preserved) -% -% PREREQUISITES -% You may need to do the following before you start matlab: -% $ export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64 -% $ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 -% Or the equivalent based on where things are installed on your system - -% init caffe network (spews logging info) -matcaffe_init(use_gpu, model_def_file, model_file); - -% prepare oversampled input -% input_data is Height x Width x Channel x Num -tic; -input_data = {prepare_image(im, mean_file)}; -toc; - -% do forward pass to get scores -% scores are now Width x Height x Channels x Num -tic; -scores = caffe('forward', input_data); -toc; - -scores = scores{1}; -% size(scores) -scores = squeeze(scores); -% scores = mean(scores,2); - -% [~,maxlabel] = max(scores); - -% ------------------------------------------------------------------------ -function images = prepare_image(im, mean_file) -% ------------------------------------------------------------------------ -IMAGE_DIM = 256; -CROPPED_DIM = 224; - -d = load(mean_file); -IMAGE_MEAN = d.image_mean; - -% resize to fixed input size -im = single(im); - -if size(im, 1) < size(im, 2) - im = imresize(im, [IMAGE_DIM NaN]); -else - im = imresize(im, [NaN IMAGE_DIM]); -end - -% RGB -> BGR -im = im(:, :, [3 2 1]); - -% oversample (4 corners, center, and their x-axis flips) -images = zeros(CROPPED_DIM, CROPPED_DIM, 3, 10, 'single'); - -indices_y = [0 size(im,1)-CROPPED_DIM] + 1; -indices_x = [0 size(im,2)-CROPPED_DIM] + 1; -center_y = floor(indices_y(2) / 2)+1; -center_x = floor(indices_x(2) / 2)+1; - -curr = 1; -for i = indices_y - for j = indices_x - images(:, :, :, curr) = ... - permute(im(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :)-IMAGE_MEAN, [2 1 3]); - images(:, :, :, curr+5) = images(end:-1:1, :, :, curr); - curr = curr + 1; - end -end -images(:,:,:,5) = ... - permute(im(center_y:center_y+CROPPED_DIM-1,center_x:center_x+CROPPED_DIM-1,:)-IMAGE_MEAN, ... - [2 1 3]); -images(:,:,:,10) = images(end:-1:1, :, :, curr); diff --git a/matlab/caffe/matcaffe_demo_vgg_mean_pix.m b/matlab/caffe/matcaffe_demo_vgg_mean_pix.m deleted file mode 100644 index 5f7898a7029..00000000000 --- a/matlab/caffe/matcaffe_demo_vgg_mean_pix.m +++ /dev/null @@ -1,102 +0,0 @@ -function scores = matcaffe_demo_vgg_mean_pix(im, use_gpu, model_def_file, model_file) -% scores = matcaffe_demo_vgg(im, use_gpu, model_def_file, model_file) -% -% Demo of the matlab wrapper based on the networks used for the "VGG" entry -% in the ILSVRC-2014 competition and described in the tech. report -% "Very Deep Convolutional Networks for Large-Scale Image Recognition" -% http://arxiv.org/abs/1409.1556/ -% -% INPUT -% im - color image as uint8 HxWx3 -% use_gpu - 1 to use the GPU, 0 to use the CPU -% model_def_file - network configuration (.prototxt file) -% model_file - network weights (.caffemodel file) -% -% OUTPUT -% scores 1000-dimensional ILSVRC score vector -% -% EXAMPLE USAGE -% model_def_file = 'zoo/deploy.prototxt'; -% model_file = 'zoo/model.caffemodel'; -% use_gpu = true; -% im = imread('../../examples/images/cat.jpg'); -% scores = matcaffe_demo_vgg(im, use_gpu, model_def_file, model_file); -% -% NOTES -% mean pixel subtraction is used instead of the mean image subtraction -% -% PREREQUISITES -% You may need to do the following before you start matlab: -% $ export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64 -% $ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 -% Or the equivalent based on where things are installed on your system - -% init caffe network (spews logging info) -matcaffe_init(use_gpu, model_def_file, model_file); - -% mean BGR pixel -mean_pix = [103.939, 116.779, 123.68]; - -% prepare oversampled input -% input_data is Height x Width x Channel x Num -tic; -input_data = {prepare_image(im, mean_pix)}; -toc; - -% do forward pass to get scores -% scores are now Width x Height x Channels x Num -tic; -scores = caffe('forward', input_data); -toc; - -scores = scores{1}; -% size(scores) -scores = squeeze(scores); -% scores = mean(scores,2); - -% [~,maxlabel] = max(scores); - -% ------------------------------------------------------------------------ -function images = prepare_image(im, mean_pix) -% ------------------------------------------------------------------------ -IMAGE_DIM = 256; -CROPPED_DIM = 224; - -% resize to fixed input size -im = single(im); - -if size(im, 1) < size(im, 2) - im = imresize(im, [IMAGE_DIM NaN]); -else - im = imresize(im, [NaN IMAGE_DIM]); -end - -% RGB -> BGR -im = im(:, :, [3 2 1]); - -% oversample (4 corners, center, and their x-axis flips) -images = zeros(CROPPED_DIM, CROPPED_DIM, 3, 10, 'single'); - -indices_y = [0 size(im,1)-CROPPED_DIM] + 1; -indices_x = [0 size(im,2)-CROPPED_DIM] + 1; -center_y = floor(indices_y(2) / 2)+1; -center_x = floor(indices_x(2) / 2)+1; - -curr = 1; -for i = indices_y - for j = indices_x - images(:, :, :, curr) = ... - permute(im(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :), [2 1 3]); - images(:, :, :, curr+5) = images(end:-1:1, :, :, curr); - curr = curr + 1; - end -end -images(:,:,:,5) = ... - permute(im(center_y:center_y+CROPPED_DIM-1,center_x:center_x+CROPPED_DIM-1,:), ... - [2 1 3]); -images(:,:,:,10) = images(end:-1:1, :, :, curr); - -% mean BGR pixel subtraction -for c = 1:3 - images(:, :, c, :) = images(:, :, c, :) - mean_pix(c); -end diff --git a/matlab/caffe/matcaffe_init.m b/matlab/caffe/matcaffe_init.m deleted file mode 100644 index 5d0a0a70bde..00000000000 --- a/matlab/caffe/matcaffe_init.m +++ /dev/null @@ -1,41 +0,0 @@ -function matcaffe_init(use_gpu, model_def_file, model_file) -% matcaffe_init(model_def_file, model_file, use_gpu) -% Initilize matcaffe wrapper - -if nargin < 1 - % By default use CPU - use_gpu = 0; -end -if nargin < 2 || isempty(model_def_file) - % By default use imagenet_deploy - model_def_file = '../../models/bvlc_reference_caffenet/deploy.prototxt'; -end -if nargin < 3 || isempty(model_file) - % By default use caffe reference model - model_file = '../../models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'; -end - - -if caffe('is_initialized') == 0 - if exist(model_file, 'file') == 0 - % NOTE: you'll have to get the pre-trained ILSVRC network - error('You need a network model file'); - end - if ~exist(model_def_file,'file') - % NOTE: you'll have to get network definition - error('You need the network prototxt definition'); - end - % load network in TEST phase - caffe('init', model_def_file, model_file, 'test') -end -fprintf('Done with init\n'); - -% set to use GPU or CPU -if use_gpu - fprintf('Using GPU Mode\n'); - caffe('set_mode_gpu'); -else - fprintf('Using CPU Mode\n'); - caffe('set_mode_cpu'); -end -fprintf('Done with set_mode\n'); diff --git a/matlab/caffe/prepare_batch.m b/matlab/caffe/prepare_batch.m deleted file mode 100644 index 345c8eb5f0b..00000000000 --- a/matlab/caffe/prepare_batch.m +++ /dev/null @@ -1,41 +0,0 @@ -% ------------------------------------------------------------------------ -function images = prepare_batch(image_files,IMAGE_MEAN,batch_size) -% ------------------------------------------------------------------------ -if nargin < 2 - d = load('ilsvrc_2012_mean'); - IMAGE_MEAN = d.image_mean; -end -num_images = length(image_files); -if nargin < 3 - batch_size = num_images; -end - -IMAGE_DIM = 256; -CROPPED_DIM = 227; -indices = [0 IMAGE_DIM-CROPPED_DIM] + 1; -center = floor(indices(2) / 2)+1; - -num_images = length(image_files); -images = zeros(CROPPED_DIM,CROPPED_DIM,3,batch_size,'single'); - -parfor i=1:num_images - % read file - fprintf('%c Preparing %s\n',13,image_files{i}); - try - im = imread(image_files{i}); - % resize to fixed input size - im = single(im); - im = imresize(im, [IMAGE_DIM IMAGE_DIM], 'bilinear'); - % Transform GRAY to RGB - if size(im,3) == 1 - im = cat(3,im,im,im); - end - % permute from RGB to BGR (IMAGE_MEAN is already BGR) - im = im(:,:,[3 2 1]) - IMAGE_MEAN; - % Crop the center of the image - images(:,:,:,i) = permute(im(center:center+CROPPED_DIM-1,... - center:center+CROPPED_DIM-1,:),[2 1 3]); - catch - warning('Problems with file',image_files{i}); - end -end \ No newline at end of file diff --git a/matlab/caffe/print_cell.m b/matlab/caffe/print_cell.m deleted file mode 100644 index 864340d4be9..00000000000 --- a/matlab/caffe/print_cell.m +++ /dev/null @@ -1,42 +0,0 @@ -function res=print_cell(input,file,linesep,cellsep) -assert(iscell(input),'The input should be a cell') -if nargin < 4 - cellsep = '\t'; -end -if nargin < 3 - linesep = '\n'; -end -if exist('file','var') && ~isempty(file) - %% - fid = fopen(file,'w'); - for l=1:length(input) - if iscell(input{l}) - for i=1:length(input{l}) - fprintf(fid,['%s' cellsep],input{l}{i}); - end - fprintf(fid,linesep); - else - if size(input,2) > 1 - for i=1:size(input,2) - fprintf(fid,'%s ',input{l,i}); - end - fprintf(fid,linesep); - else - fprintf(fid,['%s' linesep],input{l}); - end - end - end - fclose(fid); -else - res = ''; - for l=1:length(input) - if iscell(input{l}) - for i=1:length(input{l}) - res = [res sprintf([cellsep{1} '%s' cellsep{2}],input{l}{i})]; - end - res = [res sprintf(linesep)]; - else - res = [res sprintf(['%s' linesep],input{l}(:))]; - end - end -end \ No newline at end of file diff --git a/matlab/caffe/read_cell.m b/matlab/caffe/read_cell.m deleted file mode 100644 index 19831167106..00000000000 --- a/matlab/caffe/read_cell.m +++ /dev/null @@ -1,21 +0,0 @@ -function res=read_cell(filename,linesep,cellsep) -if nargin < 2, linesep='\n'; end -if nargin < 3, cellsep = '\t'; end -if exist(filename,'file') - fid = fopen(filename); -else - % Assume that filename is either a file ide or a string - fid = filename; -end - -fileLines = textscan(fid,'%s','delimiter',linesep,'BufSize',100000); - -fileLines = fileLines{1}; - -if regexp(fileLines{1},cellsep,'once') - fileLines = regexprep(fileLines,['^' cellsep '|' cellsep '$'],''); - res = regexp(fileLines,cellsep,'split'); - res = cell2matcell(res); -else - res = fileLines; -end diff --git a/matlab/caffe/matcaffe_demo.m b/matlab/classification_demo.m similarity index 56% rename from matlab/caffe/matcaffe_demo.m rename to matlab/classification_demo.m index a931f910cbf..43a7bf62fdf 100644 --- a/matlab/caffe/matcaffe_demo.m +++ b/matlab/classification_demo.m @@ -1,7 +1,16 @@ -function [scores, maxlabel] = matcaffe_demo(im, use_gpu) -% scores = matcaffe_demo(im, use_gpu) +function [scores, maxlabel] = classification_demo(im, use_gpu) +% scores = classification_demo(im, use_gpu) % -% Demo of the matlab wrapper using the ILSVRC network. +% Image classification demo using BVLC CaffeNet. +% +% IMPORTANT: before you run this demo, you should download BVLC CaffeNet +% from Model Zoo (http://caffe.berkeleyvision.org/model_zoo.html) +% +% **************************************************************************** +% For detailed documentation and usage on Caffe's Matlab interface, please +% refer to Caffe Interface Tutorial at +% http://caffe.berkeleyvision.org/tutorial/interfaces.html#matlab +% **************************************************************************** % % input % im color image as uint8 HxWx3 @@ -16,8 +25,8 @@ % Or the equivalent based on where things are installed on your system % % Usage: -% im = imread('../../examples/images/cat.jpg'); -% scores = matcaffe_demo(im, 1); +% im = imread('../examples/images/cat.jpg'); +% scores = classification_demo(im, 1); % [score, class] = max(scores); % Five things to be aware of: % caffe uses row-major order @@ -26,7 +35,7 @@ % matlab uses RGB color channel order % images need to have the data mean subtracted -% Data coming in from matlab needs to be in the order +% Data coming in from matlab needs to be in the order % [width, height, channels, images] % where width is the fastest dimension. % Here is the rough matlab for putting image data into the correct @@ -42,20 +51,27 @@ % If you have multiple images, cat them with cat(4, ...) -% The actual forward function. It takes in a cell array of 4-D arrays as -% input and outputs a cell array. - - -% init caffe network (spews logging info) -if exist('use_gpu', 'var') - matcaffe_init(use_gpu); +% Set caffe mode +if exist('use_gpu', 'var') && use_gpu + caffe.set_mode_gpu(); + gpu_id = 0; % we will use the first gpu in this demo + caffe.set_device(gpu_id); else - matcaffe_init(); + caffe.set_mode_cpu(); end +% Initialize the network using BVLC CaffeNet for image classification +% Weights (parameter) file needs to be downloaded from Model Zoo. +model_dir = '../models/bvlc_reference_caffenet/'; +net_model = [model_dir 'deploy.prototxt']; +net_weights = [model_dir 'bvlc_reference_caffenet.caffemodel']; +phase = 'test'; +net = caffe.Net(net_model, net_weights, phase); + if nargin < 1 - % For demo purposes we will use the peppers image - im = imread('peppers.png'); + % For demo purposes we will use the cat image + fprintf('using ../examples/images/cat.jpg as input image\n'); + im = imread('../examples/images/cat.jpg'); end % prepare oversampled input @@ -67,7 +83,10 @@ % do forward pass to get scores % scores are now Width x Height x Channels x Num tic; -scores = caffe('forward', input_data); +% The net forward function. It takes in a cell array of N-D arrays +% (where N == 4 here) containing data of input blob(s) and outputs a cell +% array containing data from output blob(s) +scores = net.forward(input_data); toc; scores = scores{1}; @@ -77,10 +96,13 @@ [~,maxlabel] = max(scores); +% call caffe.reset_all() to reset caffe +caffe.reset_all(); + % ------------------------------------------------------------------------ function images = prepare_image(im) % ------------------------------------------------------------------------ -d = load('ilsvrc_2012_mean'); +d = load('+caffe/imagenet/ilsvrc_2012_mean.mat'); IMAGE_MEAN = d.image_mean; IMAGE_DIM = 256; CROPPED_DIM = 227; @@ -98,13 +120,13 @@ for i = indices for j = indices images(:, :, :, curr) = ... - permute(im(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :), [2 1 3]); + permute(im(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :), [2 1 3]); images(:, :, :, curr+5) = images(end:-1:1, :, :, curr); curr = curr + 1; end end center = floor(indices(2) / 2)+1; images(:,:,:,5) = ... - permute(im(center:center+CROPPED_DIM-1,center:center+CROPPED_DIM-1,:), ... - [2 1 3]); + permute(im(center:center+CROPPED_DIM-1,center:center+CROPPED_DIM-1,:), ... + [2 1 3]); images(:,:,:,10) = images(end:-1:1, :, :, curr); diff --git a/matlab/caffe/hdf5creation/.gitignore b/matlab/hdf5creation/.gitignore similarity index 100% rename from matlab/caffe/hdf5creation/.gitignore rename to matlab/hdf5creation/.gitignore diff --git a/matlab/caffe/hdf5creation/demo.m b/matlab/hdf5creation/demo.m similarity index 100% rename from matlab/caffe/hdf5creation/demo.m rename to matlab/hdf5creation/demo.m diff --git a/matlab/caffe/hdf5creation/store2hdf5.m b/matlab/hdf5creation/store2hdf5.m similarity index 100% rename from matlab/caffe/hdf5creation/store2hdf5.m rename to matlab/hdf5creation/store2hdf5.m From 0f13feef342fc59aa113bafb45041b35ff97b649 Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Thu, 28 May 2015 18:33:54 +0800 Subject: [PATCH 6/8] Move demo to demo/ and check weights file existence Move all Matlab demo to caffe/matlab/demo. Since we want the user to add caffe/matlab to Matlab search PATH, we don't want to mess it up with too many files Check if CaffeNet is already downloaded in classification demo. --- docs/tutorial/interfaces.md | 10 ++++++---- matlab/{ => demo}/classification_demo.m | 27 ++++++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) rename matlab/{ => demo}/classification_demo.m (84%) diff --git a/docs/tutorial/interfaces.md b/docs/tutorial/interfaces.md index a57e17339b0..a59a410d964 100644 --- a/docs/tutorial/interfaces.md +++ b/docs/tutorial/interfaces.md @@ -84,18 +84,18 @@ In MatCaffe, you can * Run for a certain number of iterations and give back control to Matlab * Intermingle arbitrary Matlab code to with gradient steps -An ILSVRC image classification demo is in caffe/matlab/classification_demo.m +An ILSVRC image classification demo is in caffe/matlab/demo/classification_demo.m ### Build MatCaffe Build MatCaffe with `make all matcaffe`. After that, you may test it using `make mattest`. -Common issue: if you run into error messages `libstdc++.so.6:version 'GLIBCXX_3.4.15' not found` during `make mattest`, then it means that your Matlab's runtime libraries does not match your compile-time libraries. You may need to do the following before you start matlab: +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: export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda/lib64 export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 -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` in Matlab to see its runtime libraries, and preload your compile-time libraries by exporting them to your `LD_PRELOAD` environment variable. +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. 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. @@ -270,7 +270,9 @@ To read Caffe's example image and resize to `[width, height]` and suppose we wan im_data = permute(im_data, [2, 1, 3]); % permute width and height im_data = single(im_data); % convert to single precision -We do not provide extra functions for data output as Matlab itself is already quite powerful in output. +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. + +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. #### Clear nets and solvers diff --git a/matlab/classification_demo.m b/matlab/demo/classification_demo.m similarity index 84% rename from matlab/classification_demo.m rename to matlab/demo/classification_demo.m index 43a7bf62fdf..453582476be 100644 --- a/matlab/classification_demo.m +++ b/matlab/demo/classification_demo.m @@ -1,5 +1,5 @@ function [scores, maxlabel] = classification_demo(im, use_gpu) -% scores = classification_demo(im, use_gpu) +% [scores, maxlabel] = classification_demo(im, use_gpu) % % Image classification demo using BVLC CaffeNet. % @@ -18,6 +18,7 @@ % % output % scores 1000-dimensional ILSVRC score vector +% maxlabel the label of the highest score % % You may need to do the following before you start matlab: % $ export LD_LIBRARY_PATH=/opt/intel/mkl/lib/intel64:/usr/local/cuda-5.5/lib64 @@ -25,7 +26,7 @@ % Or the equivalent based on where things are installed on your system % % Usage: -% im = imread('../examples/images/cat.jpg'); +% im = imread('../../examples/images/cat.jpg'); % scores = classification_demo(im, 1); % [score, class] = max(scores); % Five things to be aware of: @@ -51,6 +52,13 @@ % If you have multiple images, cat them with cat(4, ...) +% Add caffe/matlab to you Matlab search PATH to use matcaffe +if exist('../+caffe', 'dir') + addpath('..'); +else + error('Please run this demo from caffe/matlab/demo'); +end + % Set caffe mode if exist('use_gpu', 'var') && use_gpu caffe.set_mode_gpu(); @@ -62,16 +70,21 @@ % Initialize the network using BVLC CaffeNet for image classification % Weights (parameter) file needs to be downloaded from Model Zoo. -model_dir = '../models/bvlc_reference_caffenet/'; +model_dir = '../../models/bvlc_reference_caffenet/'; net_model = [model_dir 'deploy.prototxt']; net_weights = [model_dir 'bvlc_reference_caffenet.caffemodel']; -phase = 'test'; +phase = 'test'; % run with phase test (so that dropout isn't applied) +if ~exist(net_weights, 'file') + error('Please download CaffeNet from Model Zoo before you run this demo'); +end + +% Initialize a network net = caffe.Net(net_model, net_weights, phase); if nargin < 1 % For demo purposes we will use the cat image - fprintf('using ../examples/images/cat.jpg as input image\n'); - im = imread('../examples/images/cat.jpg'); + fprintf('using caffe/examples/images/cat.jpg as input image\n'); + im = imread('../../examples/images/cat.jpg'); end % prepare oversampled input @@ -102,7 +115,7 @@ % ------------------------------------------------------------------------ function images = prepare_image(im) % ------------------------------------------------------------------------ -d = load('+caffe/imagenet/ilsvrc_2012_mean.mat'); +d = load('../+caffe/imagenet/ilsvrc_2012_mean.mat'); IMAGE_MEAN = d.image_mean; IMAGE_DIM = 256; CROPPED_DIM = 227; From 18adbb8d1a1be91598aa23bad6550eed954e32a9 Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Fri, 29 May 2015 00:23:06 +0800 Subject: [PATCH 7/8] Fix automatic header file dependency for MatCaffe Automatic header file dependency was introduced in #1472, but not correctly applied to matcaffe. Fix it by moving ./caffe_.d to build/matlab/+caffe/private/caffe_.d and add it to DEPS --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3748b52ca47..e4e66dfd138 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,7 @@ GTEST_OBJ := $(addprefix $(BUILD_DIR)/, ${GTEST_SRC:.cpp=.o}) EXAMPLE_OBJS := $(addprefix $(BUILD_DIR)/, ${EXAMPLE_SRCS:.cpp=.o}) # Output files for automatic dependency generation DEPS := ${CXX_OBJS:.o=.d} ${CU_OBJS:.o=.d} ${TEST_CXX_OBJS:.o=.d} \ - ${TEST_CU_OBJS:.o=.d} + ${TEST_CU_OBJS:.o=.d} $(BUILD_DIR)/${MAT$(PROJECT)_SO:.$(MAT_SO_EXT)=.d} # tool, example, and test bins TOOL_BINS := ${TOOL_OBJS:.o=.bin} EXAMPLE_BINS := ${EXAMPLE_OBJS:.o=.bin} @@ -460,6 +460,9 @@ $(MAT$(PROJECT)_SO): $(MAT$(PROJECT)_SRC) $(STATIC_NAME) CXX="$(CXX)" \ CXXFLAGS="\$$CXXFLAGS $(MATLAB_CXXFLAGS)" \ CXXLIBS="\$$CXXLIBS $(STATIC_LINK_COMMAND) $(LDFLAGS)" -output $@ + @ if [ -f "$(PROJECT)_.d" ]; then \ + mv -f $(PROJECT)_.d $(BUILD_DIR)/${MAT$(PROJECT)_SO:.$(MAT_SO_EXT)=.d}; \ + fi runtest: $(TEST_ALL_BIN) $(TOOL_BUILD_DIR)/caffe From d07e5f796907a2bc048bdab3cdb4ace05fa60d7a Mon Sep 17 00:00:00 2001 From: Ronghang Hu Date: Fri, 29 May 2015 07:50:23 +0800 Subject: [PATCH 8/8] More tests for Blob, Layer, copy_from and step, fix some typos More testes are added into test_net.m and test_solver.m --- docs/tutorial/interfaces.md | 4 ++-- matlab/+caffe/+test/test_net.m | 24 +++++++++++++++++++++++- matlab/+caffe/+test/test_solver.m | 2 ++ matlab/+caffe/Net.m | 2 +- matlab/+caffe/Solver.m | 2 +- matlab/+caffe/io.m | 2 +- matlab/+caffe/run_tests.m | 3 +++ 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/interfaces.md b/docs/tutorial/interfaces.md index a59a410d964..12963318485 100644 --- a/docs/tutorial/interfaces.md +++ b/docs/tutorial/interfaces.md @@ -82,9 +82,9 @@ In MatCaffe, you can * Resume training from solver snapshots * Access train net and test nets in a solver * Run for a certain number of iterations and give back control to Matlab -* Intermingle arbitrary Matlab code to with gradient steps +* Intermingle arbitrary Matlab code with gradient steps -An ILSVRC image classification demo is in caffe/matlab/demo/classification_demo.m +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). ### Build MatCaffe diff --git a/matlab/+caffe/+test/test_net.m b/matlab/+caffe/+test/test_net.m index 5d9ba000209..3dabe84d111 100644 --- a/matlab/+caffe/+test/test_net.m +++ b/matlab/+caffe/+test/test_net.m @@ -48,6 +48,24 @@ end end methods (Test) + function self = test_blob(self) + self.net.blobs('data').set_data(10 * ones(self.net.blobs('data').shape)); + self.verifyEqual(self.net.blobs('data').get_data(), ... + 10 * ones(self.net.blobs('data').shape, 'single')); + self.net.blobs('data').set_diff(-2 * ones(self.net.blobs('data').shape)); + self.verifyEqual(self.net.blobs('data').get_diff(), ... + -2 * ones(self.net.blobs('data').shape, 'single')); + original_shape = self.net.blobs('data').shape; + self.net.blobs('data').reshape([6 5 4 3 2 1]); + self.verifyEqual(self.net.blobs('data').shape, [6 5 4 3 2 1]); + self.net.blobs('data').reshape(original_shape); + self.net.reshape(); + end + function self = test_layer(self) + self.verifyEqual(self.net.params('conv', 1).shape, [2 2 2 11]); + self.verifyEqual(self.net.layers('conv').params(2).shape, 11); + self.verifyEqual(self.net.layers('conv').type(), 'Convolution'); + end function test_forward_backward(self) self.net.forward_prefilled(); self.net.backward_prefilled(); @@ -60,13 +78,17 @@ function test_save_and_read(self) weights_file = tempname(); self.net.save(weights_file); model_file2 = caffe.test.test_net.simple_net_file(self.num_output); - net2 = caffe.Net(model_file2, weights_file, 'train'); + net2 = caffe.Net(model_file2, 'train'); + net2.copy_from(weights_file); + net3 = caffe.Net(model_file2, weights_file, 'train'); delete(model_file2); delete(weights_file); for l = 1:length(self.net.layer_vec) for i = 1:length(self.net.layer_vec(l).params) self.verifyEqual(self.net.layer_vec(l).params(i).get_data(), ... net2.layer_vec(l).params(i).get_data()); + self.verifyEqual(self.net.layer_vec(l).params(i).get_data(), ... + net3.layer_vec(l).params(i).get_data()); end end end diff --git a/matlab/+caffe/+test/test_solver.m b/matlab/+caffe/+test/test_solver.m index 682dad48a3b..739258b0e85 100644 --- a/matlab/+caffe/+test/test_solver.m +++ b/matlab/+caffe/+test/test_solver.m @@ -36,6 +36,8 @@ methods (Test) function test_solve(self) self.verifyEqual(self.solver.iter(), 0) + self.solver.step(30); + self.verifyEqual(self.solver.iter(), 30) self.solver.solve() self.verifyEqual(self.solver.iter(), 100) end diff --git a/matlab/+caffe/Net.m b/matlab/+caffe/Net.m index a6761060dfb..e6295bba1a4 100644 --- a/matlab/+caffe/Net.m +++ b/matlab/+caffe/Net.m @@ -111,7 +111,7 @@ function backward_prefilled(self) self.blobs(self.outputs{n}).set_diff(output_diff{n}); end self.backward_prefilled(); - % retrieve diff from input_blobs + % retrieve diff from input blobs res = cell(length(self.inputs), 1); for n = 1:length(self.inputs) res{n} = self.blobs(self.inputs{n}).get_diff(); diff --git a/matlab/+caffe/Solver.m b/matlab/+caffe/Solver.m index daaa8022b91..f8bdc4e22b2 100644 --- a/matlab/+caffe/Solver.m +++ b/matlab/+caffe/Solver.m @@ -41,7 +41,7 @@ end function restore(self, snapshot_filename) CHECK(ischar(snapshot_filename), 'snapshot_filename must be a string'); - CHECK_FILE_EXIST(snapshot_filename) + CHECK_FILE_EXIST(snapshot_filename); caffe_('solver_restore', self.hSolver_self, snapshot_filename); end function solve(self) diff --git a/matlab/+caffe/io.m b/matlab/+caffe/io.m index 7a30bfb5772..c9e07aee9e8 100644 --- a/matlab/+caffe/io.m +++ b/matlab/+caffe/io.m @@ -17,7 +17,7 @@ function mean_data = read_mean(mean_proto_file) % mean_data = read_mean(mean_proto_file) % read image mean data from binaryproto file - CHECK(ischar(mean_proto_file), 'im_file must be a string'); + CHECK(ischar(mean_proto_file), 'mean_proto_file must be a string'); CHECK_FILE_EXIST(mean_proto_file); mean_data = caffe_('read_mean', mean_proto_file); end diff --git a/matlab/+caffe/run_tests.m b/matlab/+caffe/run_tests.m index 8773c9f638b..93896855ac2 100644 --- a/matlab/+caffe/run_tests.m +++ b/matlab/+caffe/run_tests.m @@ -2,6 +2,9 @@ % results = run_tests() % run all tests in this caffe matlab wrapper package +% use CPU for testing +caffe.set_mode_cpu(); + % reset caffe before testing caffe.reset_all();