From abd34616396f730ea55e3332d7a2fb87c483b521 Mon Sep 17 00:00:00 2001 From: akrizhevsky Date: Mon, 3 Oct 2011 21:45:49 +0000 Subject: [PATCH] Rewrote shownet so that it extends the model class now, making it easy to run the model from that script. Added ability to show predictions made by the net. --- convdata.py | 7 +- convnet.py | 16 +-- include/common/matrix.h | 1 + include/pyconvnet.cuh | 5 +- include/worker.cuh | 10 ++ options.py | 2 +- shownet.py | 309 ++++++++++++++++++++++++---------------- src/pyconvnet.cu | 60 ++++---- src/worker.cu | 35 +++++ 9 files changed, 274 insertions(+), 171 deletions(-) diff --git a/convdata.py b/convdata.py index 47847bc..9cf19e4 100644 --- a/convdata.py +++ b/convdata.py @@ -30,13 +30,13 @@ class CIFARDataProvider(LabeledMemoryDataProvider): def __init__(self, data_dir, batch_range, init_epoch=1, init_batchnum=None, dp_params={}, test=False): LabeledMemoryDataProvider.__init__(self, data_dir, batch_range, init_epoch, init_batchnum, dp_params, test) - data_mean = self.batch_meta['data_mean'] - + self.data_mean = self.batch_meta['data_mean'] + self.num_colors = 3 # Subtract the mean from the data and make sure that both data and # labels are in single-precision floating point. for d in self.data_dic: # This converts the data matrix to single precision and makes sure that it is C-ordered - d['data'] = n.require((d['data'] - data_mean), dtype=n.single, requirements='C') + d['data'] = n.require((d['data'] - self.data_mean), dtype=n.single, requirements='C') d['labels'] = n.require(d['labels'].reshape((1, d['data'].shape[1])), dtype=n.single) def get_next_batch(self): @@ -55,6 +55,7 @@ def __init__(self, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, self.multiview = dp_params['multiview_test'] and test self.num_views = 9 self.data_mult = self.num_views if self.multiview else 1 + self.num_colors = 3 for d in self.data_dic: d['data'] = n.require(d['data'], requirements='C') diff --git a/convnet.py b/convnet.py index d1871ed..c88ddf7 100644 --- a/convnet.py +++ b/convnet.py @@ -51,8 +51,8 @@ def init_model_state(self): else: ms['layers'] = LayerParser.parse_layers(self.layer_def, self.layer_params, self) - if self.op.get_value('multiview_test'): - logreg_name = self.op.get_value('logreg_name') + logreg_name = self.op.get_value('logreg_name') + if logreg_name: try: self.logreg_idx = [l['name'] for l in ms['layers']].index(logreg_name) if ms['layers'][self.logreg_idx]['type'] != 'cost.logreg': @@ -136,7 +136,7 @@ def get_options_parser(cls): op.add_option("layer-def", "layer_def", StringOptionParser, "Layer definition file", set_once=True) op.add_option("layer-params", "layer_params", StringOptionParser, "Layer parameter file") op.add_option("check-grads", "check_grads", BooleanOptionParser, "Check gradients and quit?", default=0, excuses=['data_path','save_path','train_batch_range','test_batch_range']) - op.add_option("multiview-test", "multiview_test", BooleanOptionParser, "Cropped DP: test on multiple patches?", default=0) + op.add_option("multiview-test", "multiview_test", BooleanOptionParser, "Cropped DP: test on multiple patches?", default=0, requires=['logreg_name']) op.add_option("crop-border", "crop_border", IntegerOptionParser, "Cropped DP: crop border size", default=4) op.add_option("logreg-name", "logreg_name", StringOptionParser, "Cropped DP: logreg layer name", default="") @@ -146,16 +146,16 @@ def get_options_parser(cls): op.options["num_epochs"].default = 50000 op.options['dp_type'].default = None + DataProvider.register_data_provider('cifar', 'CIFAR', CIFARDataProvider) + DataProvider.register_data_provider('dummy-cn-n', 'Dummy ConvNet', DummyConvNetDataProvider) + DataProvider.register_data_provider('cifar-cropped', 'Cropped CIFAR', CroppedCIFARDataProvider) + return op if __name__ == "__main__": #nr.seed(5) op = GPUModel.get_options_parser() - - DataProvider.register_data_provider('cifar', 'CIFAR', CIFARDataProvider) - DataProvider.register_data_provider('dummy-cn-n', 'Dummy ConvNet', DummyConvNetDataProvider) - DataProvider.register_data_provider('cifar-cropped', 'Cropped CIFAR', CroppedCIFARDataProvider) - + op, load_dic = IGPUModel.parse_options(op) model = GPUModel("ConvNet", op, load_dic) model.start() diff --git a/include/common/matrix.h b/include/common/matrix.h index 4d797bb..bb6b128 100644 --- a/include/common/matrix.h +++ b/include/common/matrix.h @@ -197,6 +197,7 @@ class Matrix { * Use transpose() if you want to get the transpose of this matrix. */ inline void setTrans(bool trans) { + assert(!isView()); _trans = trans ? CblasTrans : CblasNoTrans; } diff --git a/include/pyconvnet.cuh b/include/pyconvnet.cuh index 63afb2b..b66d494 100644 --- a/include/pyconvnet.cuh +++ b/include/pyconvnet.cuh @@ -30,9 +30,6 @@ #define _QUOTEME(x) #x #define QUOTEME(x) _QUOTEME(x) -#ifdef EXEC -int main(int argc, char** argv); -#else extern "C" void INITNAME(); PyObject* initModel(PyObject *self, PyObject *args); @@ -41,7 +38,7 @@ PyObject* finishBatch(PyObject *self, PyObject *args); PyObject* checkGradients(PyObject *self, PyObject *args); PyObject* syncWithHost(PyObject *self, PyObject *args); PyObject* startMultiviewTest(PyObject *self, PyObject *args); -#endif +PyObject* startLabeler(PyObject *self, PyObject *args); #endif /* PYCONVNET3_CUH */ diff --git a/include/worker.cuh b/include/worker.cuh index bbe5299..af6bb7a 100644 --- a/include/worker.cuh +++ b/include/worker.cuh @@ -88,5 +88,15 @@ public: void run(); }; +class LabelWorker : public Worker { +protected: + CPUData* _data; + Matrix* _preds; + int _logregIdx; +public: + LabelWorker(ConvNet& convNet, CPUData& data, Matrix& preds, int logregIdx); + void run(); +}; + #endif /* WORKER_CUH */ diff --git a/options.py b/options.py index 42a7d13..0bfbeef 100644 --- a/options.py +++ b/options.py @@ -150,7 +150,7 @@ def parse(self, eval_expr_defaults=False): # check requirements if o.prefixed_letter in dic: for o2 in self.get_options_list(sort_order=self.SORT_LETTER): - if o2.name in o.requires and o2.prefixed_letter not in dic and o2.default is None: + if o2.name in o.requires and o2.prefixed_letter not in dic: raise OptionMissingException("Option %s (%s) requires option %s (%s)" % (o.prefixed_letter, o.desc, o2.prefixed_letter, o2.desc)) if eval_expr_defaults: diff --git a/shownet.py b/shownet.py index b547caa..bd16f30 100644 --- a/shownet.py +++ b/shownet.py @@ -31,150 +31,213 @@ from gpumodel import IGPUModel import random as r import numpy.random as nr +from convnet import GPUModel +from options import * try: - from pylab import * + import pylab as pl except: print "This script requires the matplotlib python library (Ubuntu/Fedora package name python-matplotlib). Please install it." sys.exit(1) -FILTERS_PER_ROW = 16 -MAX_ROWS = 16 -MAX_FILTERS = FILTERS_PER_ROW * MAX_ROWS - class ShowNetError(Exception): pass -def draw_filters(filters, filter_start, fignum, _title, num_filters, combine_chans): - num_colors = filters.shape[0] - f_per_row = int(ceil(FILTERS_PER_ROW / float(1 if combine_chans else num_colors))) - filter_end = min(filter_start+MAX_FILTERS, num_filters) - filter_rows = int(ceil(float(filter_end - filter_start) / f_per_row)) +class ShowGPUModel(GPUModel): + def __init__(self, model_name, op, load_dic): + GPUModel.__init__(self, model_name, op, load_dic) + + def import_model(self): + if self.op.get_value('show_preds'): + GPUModel.import_model(self) + + def init_model_lib(self): + if self.op.get_value('show_preds'): + GPUModel.init_model_lib(self) + + def get_gpus(self): + if self.op.get_value('show_preds'): + GPUModel.get_gpus(self) + + def plot_cost(self): + if self.show_cost not in self.train_outputs[0]: + raise ShowNetError("Cost function with name '%s' not defined by given convnet." % self.show_cost) + train_errors = [o[self.show_cost][0] for o in self.train_outputs] + test_errors = [o[self.show_cost][0] for o in self.test_outputs] + + numbatches = len(self.train_batch_range) + test_errors = numpy.row_stack(test_errors) + test_errors = numpy.tile(test_errors, (1, self.testing_freq)) + test_errors = list(test_errors.flatten()) + test_errors += [test_errors[-1]] * (len(train_errors) - len(test_errors)) - filter_pixels = filters.shape[1] - filter_size = int(sqrt(filters.shape[1])) - fig = figure(fignum) - fig.text(.5, .95, '%s %dx%d filters %d-%d' % (_title, filter_size, filter_size, filter_start, filter_end-1), horizontalalignment='center') - num_filters = filter_end - filter_start - if not combine_chans: - bigpic = n.zeros((filter_size * filter_rows + filter_rows + 1, filter_size*num_colors * f_per_row + f_per_row + 1), dtype=n.single) - else: - bigpic = n.zeros((3, filter_size * filter_rows + filter_rows + 1, filter_size * f_per_row + f_per_row + 1), dtype=n.single) + numepochs = len(train_errors) / float(numbatches) + pl.figure(1) + x = range(0, len(train_errors)) + pl.plot(x, train_errors, 'k-', label='Training set') + pl.plot(x, test_errors, 'r-', label='Test set') + pl.legend() + ticklocs = range(numbatches, len(train_errors) - len(train_errors) % numbatches + 1, numbatches) + epoch_label_gran = int(ceil(numepochs / 20.)) # aim for about 20 labels + epoch_label_gran = int(ceil(float(epoch_label_gran) / 10) * 10) # but round to nearest 10 + ticklabels = map(lambda x: str((x[1] / numbatches)) if x[0] % epoch_label_gran == epoch_label_gran-1 else '', enumerate(ticklocs)) - for m in xrange(filter_start,filter_end ): - filter = filters[:,:,m] - y, x = (m - filter_start) / f_per_row, (m - filter_start) % f_per_row + pl.xticks(ticklocs, ticklabels) + pl.xlabel('Epoch') + pl.ylabel(self.show_cost) + pl.title(self.show_cost) + + def make_filter_fig(self, filters, filter_start, fignum, _title, num_filters, combine_chans): + FILTERS_PER_ROW = 16 + MAX_ROWS = 16 + MAX_FILTERS = FILTERS_PER_ROW * MAX_ROWS + num_colors = filters.shape[0] + f_per_row = int(ceil(FILTERS_PER_ROW / float(1 if combine_chans else num_colors))) + filter_end = min(filter_start+MAX_FILTERS, num_filters) + filter_rows = int(ceil(float(filter_end - filter_start) / f_per_row)) + + filter_pixels = filters.shape[1] + filter_size = int(sqrt(filters.shape[1])) + fig = pl.figure(fignum) + fig.text(.5, .95, '%s %dx%d filters %d-%d' % (_title, filter_size, filter_size, filter_start, filter_end-1), horizontalalignment='center') + num_filters = filter_end - filter_start if not combine_chans: - for c in xrange(num_colors): - filter_pic = filter[c,:].reshape((filter_size,filter_size)) - bigpic[1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size, - 1 + (1 + filter_size*num_colors) * x + filter_size*c:1 + (1 + filter_size*num_colors) * x + filter_size*(c+1)] = filter_pic + bigpic = n.zeros((filter_size * filter_rows + filter_rows + 1, filter_size*num_colors * f_per_row + f_per_row + 1), dtype=n.single) else: - filter_pic = filter.reshape((3, filter_size,filter_size)) - bigpic[:, - 1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size, - 1 + (1 + filter_size) * x:1 + (1 + filter_size) * x + filter_size] = filter_pic - - xticks([]) - yticks([]) - if not combine_chans: - imshow(bigpic, cmap=cm.gray, interpolation='nearest') - else: - bigpic = bigpic.swapaxes(0,2).swapaxes(0,1) - imshow(bigpic, interpolation='nearest') - -def print_usage(): - print "%s usage:" % sys.argv[0] - print "-f " - print "[-e ] -- plot given cost function value" - print "[-l ] -- draw filters in given layer" - print "[-c ] -- number of channels in given layer name (for fully-connected layers only)" - print "[-i ] -- input index for which to draw filters (for fully-connected layers only)" - print "[-o] -- don't combine channels into RGB when there are exactly 3 of them" - print "\nSee http://code.google.com/p/cuda-convnet/wiki/ViewingNet for more thorough documentation, with usage examples." - -if __name__ == "__main__": - if len(sys.argv) == 1: - print_usage() - sys.exit(0) - - try: - (options, args) = opt.getopt(sys.argv[1:], "f:l:e:c:i:o") - options = dict(options) + bigpic = n.zeros((3, filter_size * filter_rows + filter_rows + 1, filter_size * f_per_row + f_per_row + 1), dtype=n.single) - net_file = options["-f"] - err_name = options["-e"] if "-e" in options else None - layer_name = options["-l"] if "-l" in options else None + for m in xrange(filter_start,filter_end ): + filter = filters[:,:,m] + y, x = (m - filter_start) / f_per_row, (m - filter_start) % f_per_row + if not combine_chans: + for c in xrange(num_colors): + filter_pic = filter[c,:].reshape((filter_size,filter_size)) + bigpic[1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size, + 1 + (1 + filter_size*num_colors) * x + filter_size*c:1 + (1 + filter_size*num_colors) * x + filter_size*(c+1)] = filter_pic + else: + filter_pic = filter.reshape((3, filter_size,filter_size)) + bigpic[:, + 1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size, + 1 + (1 + filter_size) * x:1 + (1 + filter_size) * x + filter_size] = filter_pic + + pl.xticks([]) + pl.yticks([]) + if not combine_chans: + pl.imshow(bigpic, cmap=cm.gray, interpolation='nearest') + else: + bigpic = bigpic.swapaxes(0,2).swapaxes(0,1) + pl.imshow(bigpic, interpolation='nearest') + + def plot_filters(self): + filter_start = 0 # First filter to show + layer_names = [l['name'] for l in self.layers] + if self.show_filters not in layer_names: + raise ShowNetError("Layer with name '%s' not defined by given convnet." % self.show_filters) + layer = self.layers[layer_names.index(self.show_filters)] + filters = layer['weights'] + if layer['type'] == 'fc': # Fully-connected layer + input_idx = int(options["-i"]) + filters = filters[input_idx] + num_filters = layer['outputs'] + channels = int(options["-c"]) + elif layer['type'] in ('conv', 'local'): # Conv layer + num_filters = layer['filters'] + channels = layer['filterChannels'] + if layer['type'] == 'local': + filters = filters.reshape((layer['modules'], layer['filterPixels'] * channels, layer['filters'])) + filter_start = r.randint(0, layer['modules']-1)*layer['filters'] # pick out some random modules + filters = filters.swapaxes(0,1).reshape(channels * layer['filterPixels'], layer['filters'] * layer['modules']) + num_filters *= layer['modules'] - dic = IGPUModel.load_checkpoint(net_file) - - dic["op"].print_values() - dic.update(dic["model_state"]) - dic.update(dict((v.name, v.value) for v in dic["op"].options.itervalues())) - - # Plot error - if err_name: - if err_name not in dic['train_outputs'][0]: - raise ShowNetError("Cost function with name '%s' not defined by given convnet." % err_name) - train_errors = [o[err_name][0] for o in dic["train_outputs"]] - test_errors = [o[err_name][0] for o in dic["test_outputs"]] - testing_freq = dic["testing_freq"] - - numbatches = len(dic["train_batch_range"]) - test_errors = numpy.row_stack(test_errors) - test_errors = numpy.tile(test_errors, (1, testing_freq)) - test_errors = list(test_errors.flatten()) - test_errors += [test_errors[-1]] * (len(train_errors) - len(test_errors)) - - numepochs = len(train_errors) / float(numbatches) - figure(1) - x = range(0, len(train_errors)) - plot(x, train_errors, 'k-', label='Training set') - plot(x, test_errors, 'r-', label='Test set') - legend() - ticklocs = range(numbatches, len(train_errors) - len(train_errors) % numbatches + 1, numbatches) - epoch_label_gran = int(ceil(numepochs / 20.)) # aim for about 20 labels - epoch_label_gran = int(ceil(float(epoch_label_gran) / 10) * 10) # but round to nearest 10 - ticklabels = map(lambda x: str((x[1] / numbatches)) if x[0] % epoch_label_gran == epoch_label_gran-1 else '', enumerate(ticklocs)) + filters = filters.reshape(channels, filters.shape[0]/channels, filters.shape[1]) + combine_chans = not self.no_rgb and channels == 3 + + filters -= filters.min() + filters /= filters.max() + + self.make_filter_fig(filters, filter_start, 2, 'Layer %s' % self.show_filters, num_filters, combine_chans) - xticks(ticklocs, ticklabels) - xlabel('Epoch') - ylabel(err_name) - title(err_name) + def plot_predictions(self): + if self.test_data_provider.num_colors != 3: + raise ShowNetError("Can only show color images") + data = self.get_next_batch(train=False)[2] # get a test batch + num_classes = self.test_data_provider.get_num_classes() + NUM_ROWS = 2 + NUM_COLS = 4 + NUM_IMGS = NUM_ROWS * NUM_COLS + NUM_TOP_CLASSES = min(num_classes, 4) # show this many top labels - # Draw some filters - if layer_name: - filter_start = 0 # First filter to show - layer_names = [l['name'] for l in dic['layers']] - if layer_name not in layer_names: - raise ShowNetError("Layer with name '%s' not defined by given convnet." % layer_name) - layer = dic['layers'][layer_names.index(layer_name)] - filters = layer['weights'] - if layer['type'] == 'fc': # Fully-connected layer - input_idx = int(options["-i"]) - filters = filters[input_idx] - num_filters = layer['outputs'] - channels = int(options["-c"]) - elif layer['type'] in ('conv', 'local'): # Conv layer - num_filters = layer['filters'] - channels = layer['filterChannels'] - if layer['type'] == 'local': - filters = filters.reshape((layer['modules'], layer['filterPixels'] * channels, layer['filters'])) - filter_start = r.randint(0, layer['modules']-1)*layer['filters'] # pick out some random modules - filters = filters.swapaxes(0,1).reshape(channels * layer['filterPixels'], layer['filters'] * layer['modules']) - num_filters *= layer['modules'] + img_size = int(sqrt(self.test_data_provider.get_data_dims() / 3)) + label_names = self.test_data_provider.batch_meta['label_names'] + preds = n.zeros((NUM_IMGS, num_classes), dtype=n.single) + img_indices = nr.randint(0, data[0].shape[1], NUM_IMGS) + data[0] = n.require(data[0][:,img_indices], requirements='C') + data[1] = n.require(data[1][:,img_indices], requirements='C') + data += [preds] + self.libmodel.startLabeler(data, self.logreg_idx) + self.finish_batch() + fig = pl.figure(3) + fig.text(.4, .95, 'Random test case predictions') + if self.only_errors: + err_idx = n.where(preds.argmax(axis=1) != data[1][0,:])[0] # what the net got wrong + data[0], data[1], preds = data[0][:,err_idx], data[1][:,err_idx], preds[err_idx,:] + data[0] += self.test_data_provider.data_mean + data[0] /= 255.0 + #print [sum(x == i for x in data[1][0,:]) for i in range(16)] + for r in xrange(NUM_ROWS): + for c in xrange(NUM_COLS): + img_idx = r * NUM_COLS + c + pl.subplot(NUM_ROWS*2, NUM_COLS, r * 2 * NUM_COLS + c + 1) + pl.xticks([]) + pl.yticks([]) + img = data[0][:, img_idx].reshape(3, img_size, img_size).swapaxes(0,2).swapaxes(0,1) + pl.imshow(img, interpolation='nearest') + true_label = int(data[1][0,img_idx]) - filters = filters.reshape(channels, filters.shape[0]/channels, filters.shape[1]) - combine_chans = "-o" not in options and channels == 3 - - filters -= filters.min() - filters /= filters.max() + img_labels = sorted(zip(list(preds[img_idx,:]), label_names), key=lambda x: x[0], reverse=True)[:NUM_TOP_CLASSES] + img_labels.reverse() + pl.subplot(NUM_ROWS*2, NUM_COLS, (r * 2 + 1) * NUM_COLS + c + 1, aspect='equal') + #pl.pie([l[0] for l in img_labels], + # labels=[l[1] for l in img_labels]) + ylocs = n.array(range(NUM_TOP_CLASSES)) + 0.5 + height = 0.5 + width = max(ylocs) + pl.barh(ylocs, [l[0]*width for l in img_labels], height=height) + pl.title(label_names[true_label]) + pl.yticks(ylocs + height/2, [l[1] for l in img_labels]) + pl.xticks([width/2.0, width], ['50%', '100%']) + pl.ylim(0, ylocs[-1] + height*2) - draw_filters(filters, filter_start, 2, 'Layer %s' % layer_name, num_filters, combine_chans) + def start(self): + if self.show_cost: + self.plot_cost() + if self.show_filters: + self.plot_filters() + if self.show_preds: + self.plot_predictions() + pl.show() + sys.exit(0) + + @classmethod + def get_options_parser(cls): + op = GPUModel.get_options_parser() + op.add_option("show-cost", "show_cost", StringOptionParser, "Show specified objective function", default="") + op.add_option("show-filters", "show_filters", StringOptionParser, "Show learned filters in specified layer", default="") + op.add_option("filter-input-idx", "filter_input_idx", IntegerOptionParser, "Input index for layer given to --show-filters (fully-connected layers only)", default=0) + op.add_option("no-rgb", "no_rgb", BooleanOptionParser, "Don't combine filter channels into RGB in layer given to --show-filters", default=False) + op.add_option("channels", "channels", IntegerOptionParser, "Number of channels in layer given to --show-filters (fully-connected layers only)", default=0) + op.add_option("show-preds", "show_preds", BooleanOptionParser, "Show predictions made on test set", default=False, requires=['logreg_name']) + op.add_option("only-errors", "only_errors", BooleanOptionParser, "Show only mistaken predictions", default=False, requires=['show_preds']) + + return op - show() +if __name__ == "__main__": + try: + op = ShowGPUModel.get_options_parser() + op, load_dic = IGPUModel.parse_options(op) + model = ShowGPUModel("ConvNet", op, load_dic) + model.start() except (UnpickleError, ShowNetError, opt.GetoptError), e: print "----------------" print "Error:" print e - diff --git a/src/pyconvnet.cu b/src/pyconvnet.cu index 6451873..3cc702d 100644 --- a/src/pyconvnet.cu +++ b/src/pyconvnet.cu @@ -41,40 +41,17 @@ #include #include -#ifdef EXEC - -#include - -int main(int argc, char** argv) { - // This line just for compiling and examining profiler output. -// exit(0); bwdPass_16_trans<8,16><<<0, 0>>>(NULL, NULL, NULL,0, 0, 0, 0, 0, 0); - - int boardNum = get_board_lock(); - if (boardNum == GPU_LOCK_NO_BOARD) { - printf("No free GPU boards!\n"); - exit(EXIT_FAILURE); - } else if(boardNum == GPU_LOCK_NO_SCRIPT) { - printf("Running on default board.\n"); - } else { - printf("Running on board %d\n", boardNum); - } - init_tests(boardNum); - // Put tests here - - return 0; -} -#else - using namespace std; static ConvNet* model = NULL; static PyMethodDef _ConvNetMethods[] = { { "initModel", initModel, METH_VARARGS }, - { "startBatch", startBatch, METH_VARARGS }, - { "finishBatch", finishBatch, METH_VARARGS }, - { "checkGradients", checkGradients, METH_VARARGS }, - { "startMultiviewTest", startMultiviewTest, METH_VARARGS }, - { "syncWithHost", syncWithHost, METH_VARARGS }, - { NULL, NULL } + { "startBatch", startBatch, METH_VARARGS }, + { "finishBatch", finishBatch, METH_VARARGS }, + { "checkGradients", checkGradients, METH_VARARGS }, + { "startMultiviewTest", startMultiviewTest, METH_VARARGS }, + { "startLabeler", startLabeler, METH_VARARGS }, + { "syncWithHost", syncWithHost, METH_VARARGS }, + { NULL, NULL } }; void INITNAME() { @@ -142,6 +119,27 @@ PyObject* startMultiviewTest(PyObject *self, PyObject *args) { return Py_BuildValue("i", 0); } +/* + * Starts testing on the given batch (asynchronous -- returns immediately). + */ +PyObject* startLabeler(PyObject *self, PyObject *args) { + assert(model != NULL); + PyListObject* data; + int logregIdx; + if (!PyArg_ParseTuple(args, "O!i", + &PyList_Type, &data, + &logregIdx)) { + return NULL; + } + MatrixV& mvec = *getMatrixV((PyObject*)data); + Matrix& preds = *mvec.back(); + mvec.pop_back(); + + LabelWorker* wr = new LabelWorker(*model, *new CPUData(mvec), preds, logregIdx); + model->getWorkerQueue().enqueue(wr); + return Py_BuildValue("i", 0); +} + /* * Waits for the trainer to finish training on the batch given to startBatch. */ @@ -199,5 +197,3 @@ PyObject* syncWithHost(PyObject *self, PyObject *args) { return Py_BuildValue("i", 0); } -#endif - diff --git a/src/worker.cu b/src/worker.cu index beb580e..3c8c28e 100644 --- a/src/worker.cu +++ b/src/worker.cu @@ -161,4 +161,39 @@ void MultiviewTestWorker::run() { batchCost /= numCasesReal; _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost)); +} + +/* + * ==================== + * LabelWorker + * ==================== + */ +LabelWorker::LabelWorker(ConvNet& convNet, CPUData& data, Matrix& preds, int logregIdx) + : Worker(convNet), _data(&data), _preds(&preds), _logregIdx(logregIdx) { + assert(preds.getNumRows() == data.getNumCases()); + assert(!preds.isTrans()); +} + +void LabelWorker::run() { + _convNet->setData(*_data); + DataProvider& dp = _convNet->getDataProvider(); + Layer& softmaxLayer = *_convNet->getLayer(_logregIdx).getPrev()[1]; + + Cost& batchCost = *new Cost(); + for (int i = 0; i < dp.getNumMinibatches(); i++) { + _convNet->fprop(i, PASS_TEST); + _convNet->getCost(batchCost); + + Matrix& miniPreds = _preds->sliceRows(i * dp.getMinibatchSize(), + min(dp.getNumCases(), (i + 1) * dp.getMinibatchSize())); + NVMatrix softmaxActs_T; + softmaxLayer.getActs().transpose(softmaxActs_T); + softmaxActs_T.copyToHost(miniPreds); + delete &miniPreds; + } + cudaThreadSynchronize(); + + batchCost /= _data->getNumCases(); + delete _preds; + _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost)); } \ No newline at end of file