%CNORMC Classifier normalisation for ML posterior probabilities
% 
%   W = CNORMC(W,A)
% 
% INPUT
% 	W  Classifier mapping
%		A  Labeled dataset
%
% OUTPUT
%		W  Scaled classifier mapping
%
% DESCRIPTION
% The mapping W is scaled such that the likelihood of the posterior
% probabilities of the samples in A, estimated by A*W*SIGM, are maximized.
% This is particularly suitable for two-class discriminants. To obtain
% consistent classifiers in PRTools, it is necessary to call CNORMC in the
% construction of all classifiers that output distances instead of densities
% or posterior probability estimates.
%
% If A has soft labels or target labels, W is returned without change.
% 
% SEE ALSO
% MAPPINGS, DATASETS, CLASSC

% Copyright: R.P.W. Duin, duin@ph.tn.tudelft.nl
% Faculty of Applied Sciences, Delft University of Technology
% P.O. Box 5046, 2600 GA Delft, The Netherlands

% $Id: cnormc.m,v 1.5 2008/07/03 09:14:02 duin Exp $

function w = cnormc (w,a)

		
	% Parameters
	reg     = 1e-7; 		% Regularization, important for non-overlapping classes.
	epsilon = 1e-6;			% Stop when change in likelihood falls below this.

	% Check arguments.
	if (~ismapping(w))
		error('first argument should be a mapping');
	end
	if (islabtype(a,'targets'))
		prwarning(4,'Dataset has target labels, not taking any action.');
		return;
	end
	if (islabtype(a,'soft'))
		prwarning(4,'Dataset has soft labels, scaling optimization is not yet implemented.');
		w = setscale(w,0.01); % just a trial
		w = setout_conv(w,1);
		return;
	end

	[m,k,c] = getsize(a); nlab = getnlab(a); p = getprior(a,0);
	mapped = double(a*w);						% Calculate mapping outputs.
	if (c == 2) & (size(mapped,2) == 1)		
		mapped = [mapped -mapped];				% Old (perhaps unnecessary) transformation 
														% for classifiers that give distances.
	end
	mapped = mapped(m*(nlab-1)+[1:m]');	% Select output corresponding to label
																			% (uses 1D matrix addressing).

	% Initialise variables.
	scale = 1e-10; likelihood = -inf; likelihood_new = -realmax;

	% Iteratively update scale to maximize likelihood of outputs.
	while (abs(likelihood_new - likelihood) > epsilon)
		
		% Class A is, for each object, the class the object is classified as;
		% class B stands for all other classes.
		pax = sigm(mapped*scale,1);  			% Estimate of P(class A|x).
		pbx = 1 - pax; 							% Estimate of P(class B|x).
		likelihood = likelihood_new; 
		likelihood_new = mean(p(nlab)'.*log(pax + realmin)) - reg*log(scale+1);

		% Gradient step to maximize likelihood.
		scale = scale + ((p(nlab)'.*pbx)' * mapped - reg*m/(scale+1)) / ...
								    ((p(nlab)'.*mapped.*pax)'*(mapped.*pbx) - ...
											m*reg/((scale+1)*(scale+1)) + realmin);
	end

	% Dirty trick to avoid 0 and inf.
	scale = min(max(scale,0.1/(std(mapped)+1e-16)),1e16);

	% Adjust mapping: set scale and set output conversion to SIGM(1).
	w = setscale(w,scale);
	w = setout_conv(w,1);

return;

