% THIS CODE IS RELEASED UNDER THE  GNU GENERAL PUBLIC LICENSE Version 3, 29
% June 2007: http://gplv3.fsf.org/
%
% IT IS AN ADAPTATION OF TATIANA TOMMASI'S CODE FOR THE PAPER:  "Safety in
% Numbers: Learning Categories from Few Examples with Multi Model Knowledge
% Transfer" T. Tommasi, F. Orabona, B. Caputo

classdef KTExperience < handle
    % USE THIS CLASS TO RUN THE KNOWLEDGE TRANSFER EXPERIENCE
    %
    % USAGE EXAMPLE:
    % dtsetFile='/path/to/Caltech256sub';
    %
    % prior_models_file='prior_models_10class';
    % nSplits=3;
    % exp=KTExperience(dtsetDir,prior_models_file,nSplits);
    %
    % [no_KT_acc, single_KT_acc, avg_KT_acc, multi_KT_acc] =exp.runExperiment();
    
    
    properties
        %EXPERIMENT PATHS
        datasetPath         % the path where the dataset is stored  (a directory containing another directory called images, where the images are stored)
        resultsPath         % by default is datasetPath/results
        
        nSplits             %the number of times the experiment will be repeated on random splits
        
        priorModelsFile     %property containing the name of the prior model in use
        classes             %property containing the names of the classes involved in the experiment, as loaded from the specified file
        priorModels         %property containing the prior models loaded from the specified file
        kernelGamma         %it contains the best gamma found by cross_validation
    end
    
    methods
        
        function newExperiment=KTExperience(dataset_path,prior_models_file,n_splits)
            % CONSTRUCTOR OF THE EXPERIMENT
            %
            % PARAMETERS
            % dataset_path is the path where the dataset is stored
            %
            % prior_models_file is the filename of a set of prior models that
            % are going to be used in the experiment
            %
            % n_splits is the number of times the algorithm will be tested
            % on random training/testing splits
            
            results_path=sprintf('%s/results',dataset_path);
            
            newExperiment.datasetPath=dataset_path;
            newExperiment.resultsPath=results_path;
            newExperiment.priorModelsFile=prior_models_file;
            newExperiment.nSplits=n_splits;
            
            
            %CREATE DIRS
            newExperiment.createDirs();
            
            tmp=load(sprintf('%s/data/%s.mat',newExperiment.datasetPath,newExperiment.priorModelsFile)); %load prior knowledge
            
            newExperiment.priorModels=tmp.prior_models;
            newExperiment.classes=tmp.names;
            newExperiment.kernelGamma=tmp.g_best;
        end
        
        function createDirs(obj)
            %CREATES THE DATASET DIRECTORIES FOR KERNELS AND RESULTS
            if ~exist(obj.resultsPath,'dir')
                mkdir(obj.resultsPath);
            end
        end
        
        function [no_KT_acc, single_KT_acc, avg_KT_acc, multi_KT_acc] = runExperiment(obj,n_tr_steps)
            % Main method to run the full KT experiment.
            % RETURNS the accuracy without knowledge transfer,and the
            % accuracies of the different knowledge transfer approaches
            % discussed in the paper
            %
            % PARAMETERS
            % n_tr_steps represent the maximum number of target training
            % samples which will be used to train the target model
            
            % it MUST be (step*n_tr_steps)<round(min(numrow(1:n))/3);
            tr_step = 1;
            if ~exist('n_tr_steps','var')
                n_tr_steps=6;
            end
            
            % Uses the best kernel gamma, obtained through cross validation
            hp.type='rbf';
            hp.gamma=obj.kernelGamma;
            
            % four features are combined using the average kernel
            feat={'SIFT', 'PHOG', 'LBP', 'RECOV'};
            n_classes=numel(obj.classes);
            n_feats=numel(feat);
            
            % loads the features
            [data,back,n_samp]=obj.loadData(obj.datasetPath,feat,obj.classes);
            
            iter=obj.nSplits;
            
            acc_single_KT=zeros(n_classes,n_tr_steps,iter);
            acc_avg_KT=zeros(n_classes,n_tr_steps,iter);
            acc_multi_KT=zeros(n_classes,n_tr_steps,iter);
            acc_no_KT=zeros(n_classes,n_tr_steps,iter);
            
            
            for z=1:iter % the whole set of experiments is repeated "iter" times
                fprintf('Random split training/test: %d \n',z);
                
                
                rp=cell(n_classes+1,1);
                rand('state', z);
                rp{n_classes+1}=randperm(n_samp(n_classes+1));
                for c=1:n_classes % each class-out experiment
                    fprintf('\t Class index: %d \n',c);
                    
                    rp{c}=randperm(n_samp(c));
                    
                    % prepares training and testing data for the current split
                    cue=obj.prepareTrTe(feat,data,back,c,rp,tr_step,n_tr_steps);
                    
                    % tests the old model on the new data
                    priors_dec_vals_te=obj.testPriorsOnTest(obj.priorModels,cue,c);
                    
                    for i=1:tr_step:n_tr_steps % relearns the model for each different number of training samples on the target class
                        
                        % Prepares the subset of the training data
                        tr_feats=cell(n_feats,1);
                        tr_labels=cell(n_feats,1);
                        for r=1:n_feats
                            tr_feats{r}=[cue{r}.trFeats(1:tr_step*i,:); cue{r}.trFeats(tr_step*n_tr_steps+1:end,:)];
                            tr_labels{r}=[cue{r}.trLabels(1:tr_step*i); cue{r}.trLabels(tr_step*n_tr_steps+1:end)];
                        end
                        
                        %TEST THE PRIOR MODELS ON THE NEW TRAINING DATA
                        priors_dec_vals_tr=obj.testPriorsOnTrain(obj.priorModels,tr_feats,c);
                        
                        % TRAINS / TESTS NO-TRANSFER
                        [no_KT_model,acc_no_KT(c,i,z)]=obj.noKtTrainTest(hp,cue,tr_feats,tr_labels);
                        
                        % TRAINS/TESTS Single-KT
                        modelSingleKT=obj.singleKtTrain(no_KT_model, priors_dec_vals_tr);
                        [~,~,acc_single_KT(c,i,z)]=obj.KtPredict(modelSingleKT,priors_dec_vals_te,cue,c);
                        
                        % TRAINS/TESTS Average-KT
                        modelAvgKT=obj.avgKtTrain(no_KT_model, priors_dec_vals_tr);
                        [~,~,acc_avg_KT(c,i,z)]=obj.KtPredict(modelAvgKT,priors_dec_vals_te,cue,c);
                        
                        % TRAINS/TESTS CVPR 2010 - Multi-KT
                        modelMultiKT(c,i,z)=obj.multiKtTrain(no_KT_model, priors_dec_vals_tr);
                        [~,~,acc_multi_KT(c,i,z)]=obj.KtPredict(modelMultiKT(c,i,z),priors_dec_vals_te,cue,c);
                    end
                end
            end
            
            %COMPUTES THE AVG RESULTS OVER THE DIFERENT SPLITS
            no_KT_acc=mean(mean(acc_no_KT,3));  %LS-SVM-W
            single_KT_acc=mean(mean(acc_single_KT,3));  %Single-KT
            avg_KT_acc=mean(mean(acc_avg_KT,3));  %Average-KT
            multi_KT_acc=mean(mean(acc_multi_KT,3));  %Multi-KT
            
            suffix=[ obj.priorModelsFile '_' num2str(n_tr_steps) 'steps' '_' num2str(obj.nSplits) 'splits'];
            
            %SAVES THE RESULTS
            save([obj.resultsPath '/accur_' suffix '.mat'], 'no_KT_acc', 'single_KT_acc','avg_KT_acc','multi_KT_acc');
            
            %PLOTS THE CURVES
            f=obj.plotResults(no_KT_acc,single_KT_acc,avg_KT_acc,multi_KT_acc,tr_step,n_tr_steps);
            saveas(f,[obj.resultsPath '/accur_' suffix],'pdf');
        end
    end
    
    
    methods (Static)
        
        function model = multiKtTrain(no_KT_model,priors_dec_vals_tr)
            % Method to train the CVPR 2010 multi Knowledge Transfer,
            % RETURNS the trained KT model, containing a field 'beta',
            % with the coefficients for the prior konwledges
            %
            % PARAMETERS
            %
            % no_KT_model is a pre-trained wkls standard model
            %
            % priors dec_vals_tr is a variable containing the decision
            % values of the prior models on the target training set
            
            nY=numel(no_KT_model.Y);
            n_models=numel(priors_dec_vals_tr);
            
            dd=diag(no_KT_model.H);
            
            alpha_prev=cell(n_models,1);
            loo_terms_prior=zeros(n_models,nY);
            for j=1:n_models
                if numel(priors_dec_vals_tr{j})==0
                    cur_model_idx=j;
                    continue;
                end
                %alpha_prev{j} is the vector obtained by multiplying H by the
                %decision values of the prior knowledge
                alpha_prev{j}=no_KT_model.H*[priors_dec_vals_tr{j};0];
                loo_terms_prior(j,:)=(alpha_prev{j}(1:end-1)./dd(1:end-1))';
            end
            
            loo_terms_no_KT=(no_KT_model.alpha./dd(1:end-1))';
            
            %computes the optimal prior knowledge combination coefficients
            %via projected gradient descent
            beta=KTExperience.projectedGradientDesc(no_KT_model,loo_terms_no_KT,loo_terms_prior,cur_model_idx);
            
            %copies the no_KT_model, except for the alpha and the b
            %parameters
            model=rmfield(rmfield(no_KT_model,'alpha'),'b');
            
            %combines the alphas of the prior models using the computed
            %coefficients, beta
            alpha_prev_comb=zeros(nY+1,1);
            for j=1:n_models
                if numel(priors_dec_vals_tr{j})==0, continue; end
                alpha_prev_comb=alpha_prev_comb + error('complete the code to continue.\n Suggestion: use beta and alpha_prev');
            end
            
            % we want the output model to contain the correct expressions
            % of alpha and b, obtained through adaptation.
            %  model.alpha=no_KT_model.alpha;   % + something ...
            %  model.b=no_KT_model.b;   % + something ...
            model.alpha=no_KT_model.alpha + error('complete the code to continue.\n Suggestion: use alpha_prev_comb');
            model.b=no_KT_model.b + error('complete the code to continue\n Suggestion: use alpha_prev_comb');
            model.beta=beta;
        end
        
        function model = avgKtTrain(no_KT_model,priors_dec_vals_tr)
            % Method to train the average Knowledge Transfer
            % RETURNS the trained KT model, containing a field 'beta',
            % with the coefficients for the prior konwledges
            %
            % PARAMETERS
            %
            % no_KT_model is a pre-trained wkls standard no_KT_model
            %
            % priors dec_vals_tr is a variable containing the decision
            % values of the prior models on the target training set
            
            nY=numel(no_KT_model.Y);
            n_models=numel(priors_dec_vals_tr);
            
            % assigns to the prior models a uniform weight beta
            beta=ones(n_models,1)/(n_models-1);
            
            alpha_prev_comb=zeros(nY+1,1);
            for m=1:n_models
                if numel(priors_dec_vals_tr{m})==0, continue; end
                %alpha_prev is the vector obtained by multiplying H by the
                %decision values of the prior knowledge
                alpha_prev=no_KT_model.H*[priors_dec_vals_tr{m};0];
                alpha_prev_comb=alpha_prev_comb + error('complete the code to continue.\n Suggestion: use beta and alpha_prev');
            end
            
            model=rmfield(rmfield(no_KT_model,'alpha'),'b');
            
            % we want the output model to contain the correct expressions
            % of alpha and b, obtained through adaptation.
            %  model.alpha=no_KT_model.alpha;   % + something ...
            %  model.b=no_KT_model.b;   % + something ...
            model.alpha=no_KT_model.alpha + error('complete the code to continue.\n Suggestion: use alpha_prev_comb');
            model.b=no_KT_model.b + error('complete the code to continue\n Suggestion: use alpha_prev_comb');
            model.beta=beta;
        end
        
        function model = singleKtTrain(no_KT_model,priors_dec_vals_tr)
            % Method to train the single Knowledge Transfer
            % RETURNS the trained KT model, containing a field 'beta',
            % with the coefficients for the prior konwledges
            %
            % PARAMETERS
            %
            % no_KT_model is a pre-trained wkls standard model
            %
            % priors dec_vals_tr is a variable containing the decision
            % values of the prior models on the target training set
            
            nY=numel(no_KT_model.Y);
            n_models=numel(priors_dec_vals_tr);
            
            dd=diag(no_KT_model.H);
            
            alphas=cell(n_models,1);
            bs=zeros(n_models,1);
            best_beta_id=zeros(n_models,1);
            best_obj=zeros(n_models,1);
            
            beta_steps=0:0.01:1;
            n_steps=numel(beta_steps);
            
            % selecting the best prior knowledge from where to transfer...
            for m=1:n_models
                if numel(priors_dec_vals_tr{m})==0, continue; end
                
                alpha_prev=no_KT_model.H*[priors_dec_vals_tr{m};0];
                
                alphas{m}=alpha_prev(1:end-1);
                bs(m)=alpha_prev(end);
                loo_terms=alphas{m}./dd(1:end-1);
                
                obj_v=zeros(n_steps,1);
                for j=1:n_steps
                    % below you find the original version of the loo error, correct it
                    % considering adaptation (see Eq. (4) in the paper).
                    % loo_err=no_KT_model.alpha./dd(1:end-1);
                    loo_err=no_KT_model.alpha./dd(1:end-1) + error('complete the code to continue.\n Suggestion: use  beta_steps(j) and loo_terms');
                    marg=zeros(1,nY);
                    for i=1:nY
                        marg(i)=(no_KT_model.Y(i)*loo_err(i)-1);
                    end
                    obj_v(j)=sum(no_KT_model.zed./(1+exp(-10*marg)));
                end
                [mx,ind]=min(obj_v);
                best_beta_id(m)=ind;
                best_obj(m)=mx;
            end
            
            pos=find(best_obj~=0);
            [~, tmpBestId]=min(best_obj(pos));
            best_model_id=pos(tmpBestId);
            
            beta_best_model=beta_steps(best_beta_id(best_model_id));
            
            %copies the no_KT_model, except for the alpha and the b
            %parameters
            model=rmfield(rmfield(no_KT_model,'alpha'),'b');
            
            %creates a column vector of coefficients with the only non-zero value
            %being the coefficient associated to the best prior knowledge
            beta=error('complete the code to continue');
            
            % we want the output model to contain the correct expressions of alpha and
            % b obtained through adaptation.
            % model.alpha=no_KT_model.alpha; % + something ...
            % model.b=no_KT_model.b; % + something ...
            model.alpha=no_KT_model.alpha + error('complete the code to continue.\n Suggestion: use beta_best_model and alphas{best_model_id}');
            model.b=no_KT_model.b + error('complete the code to continue.\n Suggestion: use beta_best_model and bs(best_model_id)');
            model.beta=beta;
        end
        
        function [predicted_labels,decision_values,accuracy]=KtPredict(model,priors_dec_vals_te,prepared_data,cur_cla)
            % Prediction method for KT models,
            % RETURNS the predicted_labels, the decision_values and the
            % accuracy
            %
            % PARAMETERS
            %
            % prepared_data contains the target testing data
            % model is a trained KT model
            % priors_dec_vals_te are the decision values of the prior
            % models on the current testing samples
            % cur_cla specifies which class is being currently used (so
            % that its prior model decision values are ignored
            
            n_classes=numel(priors_dec_vals_te);
            nTest=numel(prepared_data{1}.teLabels);
            
            Ktest=KTExperience.utilityKer(prepared_data, model);
            [~, decision_vals_basic]=KTExperience.wklsPredict(Ktest, model);
            
            
            decision_values=decision_vals_basic;
            for i=1:n_classes
                if i==cur_cla, continue; end
                % decision_vals_basic contains just one part of
                % the output margins for the adaptation problem. Complete the code
                % and calculate the recognition rate. Substitute 0 with the correct
                % missing terms.
                decision_values=decision_values+error('complete the code to continue.\n Suggestion: use model.beta and priors_dec_vals_te');
            end
            
            predicted_labels=sign(decision_values);
            accuracy=  sum(prepared_data{1}.teLabels==predicted_labels)/nTest;
        end
        
        function [model, loo_err, loo_pred, H] = wklsTrain(model)
            % Method to train a Weighted Kernel Least-Squares SVM model
            % RETURNS the trained model, containing:
            % model.alpha (trained alpha coefficients)
            % model.b     (the trained bias)
            % model.zed   (the computed sample weights)
            %
            % as well as the leave-one-out error, the leave-one-out
            % prediction and the solution of the linear system, H
            %
            %PARAMETERS
            %
            % model is a structure containing:
            % model.Y= training labels
            % model.C= SVM parameter C
            % model.K= training kernel
            
            n_tr = size(model.K,1);
            model.zed=KTExperience.getZeta(model);
            [x,H]=KTExperience.solveLinearSystem(model);
            model.alpha = x(1:end-1);
            model.b = x(end);
            
            % leave one out prediction
            dd=diag(H);
            loo_pred=model.Y'-model.alpha./dd(1:end-1);
            %leave one out training classification error
            loo_err=1-numel(find( model.Y'==sign(loo_pred) ))/n_tr;
        end
        
        function [predicted_labels,decision_values] = wklsPredict(Kte,model)
            % Prediction method for the Weighted Kernel Least-Squares SVM
            % models.
            %
            % PARAMETERS:
            % Kte= testing kernel
            % model.alpha=
            % model.b = b (bias term)
            
            % Substitute the following line with the correct margin calculation = Kte*alpha + b)
            % decision_values = zeros(1, size(Kte,1));
            decision_values=error('complete the code to continue.\n Suggestion: use something like Kte*alpha + b');
            predicted_labels=sign(decision_values);
        end
        
        function beta=projectedGradientDesc(model,loo_terms_no_KT,loo_terms_prior,cur_model_idx)
            % Projected Gradient Descent to estimante the coefficients of
            % the prior models, for the CVPR 2010 multi KT method
            % RETURNS the coefficients beta, representing the weights
            % associated to the prior knowledges
            %
            % PARAMETERS
            % model is a trained no-KT model
            % loo_terms_no_KT contains the vector of alpha_i/H_ii
            % loo_terms_prior contains the matrix of alpha_i(j)/H_ii
            % cur_model_idx is the index of the current model (to enforce
            % its coefficient to be zero in the solution)
            
            nY=numel(model.Y);
            n_models=size(loo_terms_prior,1);
            maxIter=10000;
            
            % Initializes a weight for each prior knowldge model
            beta=zeros(1,n_models);
            
            % projected sub-gradient descent
            for iter=1:(maxIter)
                
                %gd step
                gdStep=1/(sqrt(iter)*nY);
                
                w_loo_terms=beta*loo_terms_prior;
                
                % Calculate the loss in Eq (15) of the paper and its gradient respect
                % to alpha. Suggestion: use loo_terms_no_KT and w_loo_terms
                loss_vec=error('complete the code to continue\n Suggestion: use loo_terms_no_KT and w_loo_terms');
                grad=error('complete the code to continue\n Suggestion: use loo_terms_prior');
                
                % PERFORMS THE GRADIENT DESCENT STEP
                beta=beta-gdStep*grad;
                %ENFORCES THE COEFFICIENT OF THE CURRENT CLASSIFIER TO BE ZERO
                beta(cur_model_idx)=0;
                
                %PROJECTION IN THE POSITIVE ORTHANT, IN THE SPHERE WITH
                %NORM LESS THEN OR EQUAL TO 1
                beta(beta<0)=0;
                beta=beta*min(1,1/norm(beta));
            end
        end
        
        function [x,H]=solveLinearSystem(model)
            % Method to solve the linear system defined by the input model
            % RETURNS the solution x, of the system G * x = [y' 0]'
            % H is the inverse of the coefficients matrix G
            %
            % PARAMETERS
            %
            % model is a structure containing:
            % model.Y= training labels
            % model.C= SVM parameter C
            % model.K= training kernel
            
            W=diag(model.zed.^(-1));
            G=error('complete the code to continue.\nSuggestion: G is the matrix on the left side of eq. (9) in the paper\nSuggestion: use model.K and W');
            H=pinv(G);
            x=H*[model.Y';0];
        end
        
        function zed=getZeta(model)
            % Method to compute a row vector zed, where zed(i) is the weight associated to each training
            % sample
            % PARAMETERS
            %
            % model is a structure containing:
            % model.Y= training labels
            
            
            % Substitute the following line with the correct calculation for the zeta
            % vector. The value of each element of the zeta vector is indicated in Eq.
            % (7) in the paper. Suggestion: use model.Y>0
            zed=error('Complete the code to continue.\n Suggestion: use model.Y>0');
        end
        
        function [model,recognitionRate]=noKtTrainTest(hp,prepared_data,tr_feats,tr_labels)
            % Method that performs training and testing of a wlks model,
            % without any knowledge transfer. It returns the trained model
            % and the recognition rate on the test set.
            %
            % PARAMETERS
            %
            % hp is a structure containing the informations about the
            % kernel to be used in training:
            % hp.type defines the kernel family (i.e. 'rbf')
            % hp.gamma defines the kernel parameter
            %
            % prepared_data is a structure containing the test data
            %
            % tr_feats contains the training features
            %
            % tr_feats contains the training labels
            
            
            %%%%%%%%%%%%%%%%%%%%%  NO TRANSFER %%%%%%%%%%%%%%%%%%%%%%%%%%%%
            c_array=[0.5 1 5 10 50 100 200 500 800];
            
            nC=numel(c_array);
            nTest=numel(prepared_data{1}.teLabels);
            n_feats=numel(prepared_data);
            
            c_models=cell(nC,1);
            trained_model=cell(nC,1);
            c_rr=zeros(nC,1);
            
            %performs selection of the C value
            for c=1:nC
                c_models{c}=KTExperience.initModel(@compute_kernel,hp,c_array(c));
                
                nY=length(tr_labels{1}');
                Ktr=zeros(nY,nY,n_feats);
                for r=1:n_feats
                    if ~isequal(c_models{c}.ker,@compute_kernel)
                        error('wrong function call');
                    end
                    Ktr(:,:,r)=feval(@KTExperience.computeKernel, tr_feats{r}', 1:nY, 1:nY, c_models{c}.kerparam);
                    c_models{c}.XX{r}=tr_feats{r}';
                end
                
                Ktrain=mean(Ktr,3);
                c_models{c}.K=Ktrain;
                c_models{c}.S=1:nY;
                c_models{c}.Y=tr_labels{1}';
                
                clear Ktr Ktrain
                [trained_model{c},~,~,H]=KTExperience.wklsTrain(c_models{c});
                trained_model{c}.H=H;
                
                Ktest2=KTExperience.utilityKer(prepared_data, trained_model{c});
                pred=KTExperience.wklsPredict(Ktest2, trained_model{c});
                
                % substitute 0 with the correct recognition rate
                % calculation
                c_rr(c)=sum(prepared_data{1}.teLabels==pred)/nTest;
            end
            
            [recognitionRate,I]=max(c_rr);
            model=trained_model{I};
        end
        
        function priors_dec_vals=testPriorsOnTest(prior_models,prepared_data,cur_model_idx)
            % Method to test the prior models on the target test data,
            % returning the predicted decision values priors_dec_vals
            %
            % PARAMETERS
            %
            % prior_models is a cell containing the trained prior models
            %
            % prepared_data is a structure containing the target test data
            %
            % cur_model_idx is the index of the current model (which will
            % not be tested)
            
            n_classes=numel(prior_models);
            priors_dec_vals=cell(1,n_classes);
            
            for j=1:n_classes
                if j==cur_model_idx, continue; end
                
                Ktest=KTExperience.utilityKer(prepared_data,prior_models{j});
                [~, priors_dec_vals{j}]=KTExperience.wklsPredict(Ktest, prior_models{j});
                clear Ktest
            end
        end
        
        function priors_dec_vals=testPriorsOnTrain(prior_models,tr_feats,cur_model_idx)
            % Method to test the prior models on the target training  data,
            % returning the predicted decision values priors_dec_vals
            %
            % PARAMETERS
            %
            % prior_models is a cell containing the trained prior models
            %
            % tr_feats is a structure containing the target training
            % data
            %
            % cur_model_idx is the index of the current model (which will
            % not be tested)
            
            n_classes=numel(prior_models);
            n_feats=numel(tr_feats);
            
            priors_dec_vals=cell(1,n_classes);
            for i=1:n_classes
                if i==cur_model_idx, continue; end
                nt = size(tr_feats{1},1);
                nr = length(prior_models{i}.Y);
                Ktest=zeros(nt,nr,n_feats);
                for r=1:n_feats %num features
                    
                    xtrain=prior_models{i}.XX{r};
                    xtest=tr_feats{r}';
                    if ~isequal(prior_models{i}.ker,@compute_kernel)
                        error('wrong function call');
                    end
                    Ktest(:,:,r) = feval(@KTExperience.computeKernel,[xtrain , xtest],(1:nt)+nr,prior_models{i}.S,prior_models{i}.kerparam);
                end
                
                KtestAvg=mean(Ktest,3);
                [~, priors_dec_vals{i}]=KTExperience.wklsPredict(KtestAvg, prior_models{i});
            end
        end
        
        function [kernel] = utilityKer(prepared_data, model)
            % Utility method to compute the average kernel from the
            % features
            
            nt = size(prepared_data{1}.teFeats,1);
            nr = length(model.Y);
            nKerns=numel(prepared_data);
            K=zeros(nt,nr,nKerns);
            
            for r=1:nKerns %num features
                xtrain=model.XX{r};
                xtest=prepared_data{r}.teFeats';
                
                if ~isequal(model.ker,@compute_kernel)
                    error('wrong function call');
                end
                K(:,:,r) = feval(@KTExperience.computeKernel,[xtrain , xtest],(1:nt)+nr,model.S,model.kerparam);
            end
            kernel=mean(K,3);
        end
        
        function K = computeKernel(in1,in2,in3,in4)
            % Utility method to compute the rbf kernel
            
            if nargin==4
                X=in1;
                ind1=in2;
                ind2=in3;
                hp=in4;
                X1=X(:,ind1);
                X2=X(:,ind2);
            else
                X1=in1;
                X2=in2;
                hp=in3;
            end
            
            if size(X1,2)==0
                K = [];
                return;
            end
            
            switch hp.type
                case 'rbf'
                    normX = sum(X1.^2,1);
                    normY = sum(X2.^2,1);
                    K = exp(-hp.gamma*(repmat(normX' ,1,size(X2,2)) + ...
                        repmat(normY,size(X1,2),1) - ...
                        2*X1'*X2));
                otherwise
                    error('Unknown kernel');
            end
        end
        
        function model = initModel(ker,kerparam,C)
            % Utility method to initialize a model
            %
            model.K=[];
            model.X=[];
            model.Y=[];
            model.S=[];
            model.C=C;
            model.ker=ker;
            model.kerparam=kerparam;
            model.opt=[];
            model.XX=cell(4,1); %four features
        end
        
        function prepared_data=prepareTrTe(feat,data,back,cur_model_idx,rp,tr_step,n_tr_steps)
            % Utility method to prepare the target training/testing data
            % structures
            
            n_classes=size(data,1);
            n_feats=numel(feat);
            
            prepared_data=cell(n_feats,1);
            
            n_tr=tr_step*n_tr_steps;
            tr_ind=rp{cur_model_idx}(1:n_tr);
            tr_ind_bck=rp{n_classes+1}(1:n_tr);
            
            n_te=50;
            te_ind=rp{cur_model_idx}(n_tr+(1:n_te));
            te_ind_bck=rp{n_classes+1}(n_tr+(1:n_te));
            %%%%  prepare training and testing %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            for f=1:n_feats;
                %in train there are n_tr object + n_tr background images
                prepared_data{f}.trFeats=[data{cur_model_idx,f}.feature(tr_ind,:); back{f}.background(tr_ind_bck,:)];
                prepared_data{f}.trLabels=[ones(n_tr_steps,1); (-1)*ones(n_tr_steps,1)];
                
                %in test there are n_te object + n_te background images
                prepared_data{f}.teFeats=[data{cur_model_idx,f}.feature(te_ind,:);back{f}.background(te_ind_bck,:)];
                prepared_data{f}.teLabels=[ones(n_te,1); (-1)*ones(n_te,1)];
            end
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        end
        
        function [data,back,n_samp]=loadData(datasetPath,feat,names)
            % Utility method to load the data features
            %
            
            n_classes=numel(names);
            n_feats=numel(feat);
            
            data=cell(n_classes,n_feats);
            back=cell(1,n_feats);
            %%%% load data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            n_samp=zeros(n_classes,1);
            for f=1:n_feats
                for c=1:n_classes
                    file=[datasetPath '/data/' feat{f} '/' names{c} ];
                    data{c,f}=load(sprintf('%s.mat',file));
                    if f==1, n_samp(c)=size(data{c,f}.feature,1); end
                end
                
                file=[datasetPath '/data/' feat{f} '/' '257.clutter' ];
                back{f}=load(sprintf('%s.mat',file));
                if f==1,
                    n_samp(n_classes+1)=size(back{f}.background,1);
                end
                
            end
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        end
        
        function f=plotResults(no_KT_acc,single_KT_acc,avg_KT_acc,multi_KT_acc,tr_step,n_tr_steps)
            % Utility method to plot the curves obtained by running the
            % experiment
            
            f=figure();
            
            plot(no_KT_acc, '-k', 'LineWidth', 2 );
            hold on;
            plot(single_KT_acc, '-or', 'LineWidth', 2 );
            hold on;
            plot(avg_KT_acc, '-xm', 'LineWidth', 2);
            hold on;
            plot(multi_KT_acc, '-sc', 'LineWidth', 2);
            hold off;
            
            set(gca,'YGrid','on');
            set(gca,'XGrid','on');
            set(gca,'XTick',1:tr_step:n_tr_steps, 'FontSize', 16, 'fontweight','b')
            set(gca,'YTick',0.5:0.05:1, 'FontSize', 16, 'fontweight','b')
            xlabel('Number of Samples', 'FontSize', 16, 'fontweight','b')
            ylabel('Recognition Rate', 'FontSize', 16, 'fontweight','b')
            
            set(f, 'PaperPositionMode', 'auto');
            set(f, 'PaperUnits', 'inches');
            set(f, 'PaperSize', [6.5 5]);
            
            drawnow;
            
            h = legend('No Adapt', 'Single-KT', 'Average-KT','Multi-KT',4);
            set(h,'Interpreter','none', 'Location','best');
        end
        
        function f=plotClassDistance(beta,classes)
            % Function that turns the prior model weights in to a distance
            % matrix and uses it to plot the classes in a rescaled 2d plane
            %
            % PARAMETERS
            %
            % beta is a vector of multi-KT prior model weights, as learned
            % by a multi-KT algorithm
            % classes is a cell containing the name of the classes being
            % considered in the experiment (i.e. obj.classes)
            
            betamat=1-beta;
            betamat=betamat+betamat';
            for k=1:size(betamat,1)
                betamat(k,k)=0;
            end
            Y=cmdscale(betamat);
            
            f=figure();
            plot(Y(:,1),Y(:,2), 'o', 'MarkerSize',15, 'MarkerEdgeColor','b', 'linewidth', 2);
            text(Y(:,1)+0.1,Y(:,2), classes,'FontSize' , 14 ,  'FontWeight', 'bold');
            
            drawnow;
        end
    end
end
