// Note: For small probabilities, product may end up zero due to underflow error. Can circumvent // by taking logs. @Override protected double[] score0(double[] data, double[] preds) { double[] nums = new double[_output._levels.length]; // log(p(x,y)) for all levels of y assert preds.length == (_output._levels.length + 1); // Note: First column of preds is predicted response class // Compute joint probability of predictors for every response class for (int rlevel = 0; rlevel < _output._levels.length; rlevel++) { // Take logs to avoid overflow: p(x,y) = p(x|y)*p(y) -> log(p(x,y)) = log(p(x|y)) + log(p(y)) nums[rlevel] = Math.log(_output._apriori_raw[rlevel]); for (int col = 0; col < _output._ncats; col++) { if (Double.isNaN(data[col])) continue; // Skip predictor in joint x_1,...,x_m if NA int plevel = (int) data[col]; double prob = plevel < _output._pcond_raw.length ? _output._pcond_raw[col][rlevel][plevel] : _parms._laplace / ((double) _output._rescnt[rlevel] + _parms._laplace * _output ._domains[col] .length); // Laplace smoothing if predictor level unobserved in // training set nums[rlevel] += Math.log( prob <= _parms._eps_prob ? _parms._min_prob : prob); // log(p(x|y)) = \sum_{j = 1}^m p(x_j|y) } // For numeric predictors, assume Gaussian distribution with sample mean and variance from // model for (int col = _output._ncats; col < data.length; col++) { if (Double.isNaN(data[col])) continue; // Skip predictor in joint x_1,...,x_m if NA double x = data[col]; double mean = Double.isNaN(_output._pcond_raw[col][rlevel][0]) ? 0 : _output._pcond_raw[col][rlevel][0]; double stddev = Double.isNaN(_output._pcond_raw[col][rlevel][1]) ? 1.0 : (_output._pcond_raw[col][rlevel][1] <= _parms._eps_sdev ? _parms._min_sdev : _output._pcond_raw[col][rlevel][1]); // double prob = Math.exp(new NormalDistribution(mean, stddev).density(data[col])); // // slower double prob = Math.exp(-((x - mean) * (x - mean)) / (2. * stddev * stddev)) / (stddev * Math.sqrt(2. * Math.PI)); // faster nums[rlevel] += Math.log(prob <= _parms._eps_prob ? _parms._min_prob : prob); } } // Numerically unstable: // p(x,y) = exp(log(p(x,y))), p(x) = \Sum_{r = levels of y} exp(log(p(x,y = r))) -> p(y|x) = // p(x,y)/p(x) // Instead, we rewrite using a more stable form: // p(y|x) = p(x,y)/p(x) = exp(log(p(x,y))) / (\Sum_{r = levels of y} exp(log(p(x,y = r))) // = 1 / ( exp(-log(p(x,y))) * \Sum_{r = levels of y} exp(log(p(x,y = r))) ) // = 1 / ( \Sum_{r = levels of y} exp( log(p(x,y = r)) - log(p(x,y)) )) for (int i = 0; i < nums.length; i++) { double sum = 0; for (int j = 0; j < nums.length; j++) sum += Math.exp(nums[j] - nums[i]); preds[i + 1] = 1 / sum; } // Select class with highest conditional probability preds[0] = GenModel.getPrediction(preds, _output._priorClassDist, data, defaultThreshold()); return preds; }