%
% This code is part of the supplementary material to the CVPR 2013 paper
% "From N to N+1: Multiclass Transfer Incremental Learning",
% Ilja Kuzborskij, Francesco Orabona and Barbara Caputo
%
% Copyright (C) 2012-2013, Ilja Kuzborskij
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
% Contact the author: ilja.kuzborskij [at] gmail.com
%

classdef MKTL < GenericClassifier
    methods
        function obj = MKTL(parameters)
            obj@GenericClassifier(parameters);
        end

        function train_model(obj, hyperparameters, data_obj)
            [junk, prior_margins] = obj.parameters.priors.predict(data_obj.train_x);
            K = obj.compute_mean_kernel_combinations(hyperparameters, data_obj.train_x);

            obj.model.class_map = unique(data_obj.train_y);
            train_y = arrayfun(@(y) find(obj.model.class_map==y), data_obj.train_y);

            model_init.n_cla     = numel(unique(train_y));
            model_init.step      = 100*numel(train_y);
            model_init.T         = 300;

            model_init.C = hyperparameters.c_val;
            model_init.p = hyperparameters.p_val;

            % Reusing Tatiana's mktl_train
            obj.model.mktl_model = mktl_train(K, prior_margins', train_y, model_init);
            obj.model.SVs = data_obj.train_x;
            obj.model.hyperparameters = hyperparameters;
        end

        function [pred, margins] = predict(obj, x)
            Ktest = obj.compute_mean_kernel_combinations(obj.model.hyperparameters, obj.model.SVs, x);
            [junk, prior_margins] = obj.parameters.priors.predict(x);
            prior_margins = prior_margins';
            Ktest = Ktest';

            n_test = size(Ktest, 1);

            out_p = zeros(obj.model.mktl_model.n_cla, n_test);
            for i=1:n_test
                out_p(:, i) = sum(obj.model.mktl_model.beta_p .* repmat(prior_margins(:, i)', ...
                                  obj.model.mktl_model.n_cla, 1) .* obj.model.mktl_model.weights_p, 2);
            end

            if isa(x, 'single')
                out_k = full(obj.model.mktl_model.beta_k) * kbeta_single(Ktest, obj.model.mktl_model.weights_k)';
            else
                out_k = full(obj.model.mktl_model.beta_k) * kbeta_double(Ktest, obj.model.mktl_model.weights_k)';
            end

            out  = out_p + out_k;
            [junk, pred]=max(out,[],1);
            margins = out';
            pred = obj.model.class_map(pred);
        end
    end
end
