%
% 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 PmtSvm < GenericClassifier

    properties
    end

    methods

        function obj = PmtSvm(parameters)
            obj@GenericClassifier(parameters);
        end

        function cue_index = get_cue_index(obj)
            if isfield(obj.parameters, 'cue_index')
                cue_index = obj.parameters.cue_index;
            else
                cue_index = 1;
            end
            disp(sprintf('PmtSvm: using cue with index %d.', cue_index));
        end

        function weights = get_priors(obj, train_x, train_y, c_val)
        % Priors will be trained on the fly since apart from PMT-SVM none of methods rely
        % on this form of priors.
        %
        % Here using excerpts of code from:
        % Y. Aytar and A. Zisserman,
        % Tabula Rasa: Model Transfer for Object Category Detection, ICCV'11

            % Train source classifier
            % Given a zero vector as the source, A-SVM converges to the classical SVM
            % formulation. Thus we use A-SVM for training an SVM classifier.
            disp('Training source classifier.');

            %train_y(train_y~=obj.parameters.class_vs_rest_id) = -1;
            %train_y(train_y==obj.parameters.class_vs_rest_id) = 1;

            source = MulticlassRLS(struct('with_bias', false));
            source.train(struct('c_val', c_val, 'kernel_type', 'linear'), {train_x}, train_y);
            wsource = mean(train_x'*source.model.alpha, 2); % averaging hyperplanes
            weights = wsource/norm(wsource); % l2 normalization

            % ova_labels = obj.coding_ova(train_y);
            % ws = [];
            % for i=1:size(ova_labels, 2)
            %     ws_zero = zeros(1, size(train_x, 2));
            %     source.svm = A_SVM(ova_labels(:,i),train_x,c_val,ws_zero);
            %     ws = [ws source.svm.w];
            % end
            % ws = mean(ws, 2);
            % ws = ws/norm(ws);
            % weights = ws;
        end

        function [pred, margins] = predict(obj, test_x)
            cue_ix = obj.get_cue_index;
            margins = test_x{cue_ix} * obj.model.pmt_svm_model.w + obj.model.pmt_svm_model.b;
            margins = margins';
            pred=sign(margins);
        end

        function train_model(obj, hyperparameters, data_obj)
        % Relying on code provided with publication:
        % Y. Aytar and A. Zisserman,
        % Tabula Rasa: Model Transfer for Object Category Detection, ICCV'11
            cue_ix = obj.get_cue_index;
            wpriors = obj.get_priors(obj.parameters.prior_train_x{cue_ix}, obj.parameters.prior_train_y, ...
                                     hyperparameters.c_val);


            % Cannot use kernel averaging, PMT_SVM is not a kernel method
            disp('Training PMT-SVM...');
            tic;
            obj.model.pmt_svm_model = PMT_SVM(data_obj.train_y, data_obj.train_x{cue_ix}, ...
                                              hyperparameters.c_val, wpriors, hyperparameters.pmt_gamma);
            toc
        end
    end
end
