xgboost/regrank/xgboost_regrank_eval.h
2014-05-02 00:16:12 +08:00

329 lines
14 KiB
C++

#ifndef XGBOOST_REGRANK_EVAL_H
#define XGBOOST_REGRANK_EVAL_H
/*!
* \file xgboost_regrank_eval.h
* \brief evaluation metrics for regression and classification and rank
* \author Kailong Chen: chenkl198812@gmail.com, Tianqi Chen: tianqi.tchen@gmail.com
*/
#include <cmath>
#include <vector>
#include <algorithm>
#include "../utils/xgboost_utils.h"
#include "../utils/xgboost_omp.h"
#include "../utils/xgboost_random.h"
#include "xgboost_regrank_data.h"
#include <functional>
#include <tuple>
namespace xgboost{
namespace regrank{
/*! \brief evaluator that evaluates the loss metrics */
struct IEvaluator{
/*!
* \brief evaluate a specific metric
* \param preds prediction
* \param info information, including label etc.
*/
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const = 0;
/*! \return name of metric */
virtual const char *Name(void) const = 0;
/*! \brief virtual destructor */
virtual ~IEvaluator(void){}
};
inline static bool CmpFirst(const std::pair<float, unsigned> &a, const std::pair<float, unsigned> &b){
return a.first > b.first;
}
/*! \brief RMSE */
struct EvalRMSE : public IEvaluator{
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const {
const unsigned ndata = static_cast<unsigned>(preds.size());
float sum = 0.0, wsum = 0.0;
#pragma omp parallel for reduction(+:sum,wsum) schedule( static )
for (unsigned i = 0; i < ndata; ++i){
const float wt = info.GetWeight(i);
const float diff = info.labels[i] - preds[i];
sum += diff*diff * wt;
wsum += wt;
}
return sqrtf(sum / wsum);
}
virtual const char *Name(void) const{
return "rmse";
}
};
/*! \brief Error */
struct EvalLogLoss : public IEvaluator{
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const {
const unsigned ndata = static_cast<unsigned>(preds.size());
float sum = 0.0f, wsum = 0.0f;
#pragma omp parallel for reduction(+:sum,wsum) schedule( static )
for (unsigned i = 0; i < ndata; ++i){
const float y = info.labels[i];
const float py = preds[i];
const float wt = info.GetWeight(i);
sum -= wt * (y * std::log(py) + (1.0f - y)*std::log(1 - py));
wsum += wt;
}
return sum / wsum;
}
virtual const char *Name(void) const{
return "negllik";
}
};
/*! \brief Error */
struct EvalError : public IEvaluator{
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const {
const unsigned ndata = static_cast<unsigned>(preds.size());
float sum = 0.0f, wsum = 0.0f;
#pragma omp parallel for reduction(+:sum,wsum) schedule( static )
for (unsigned i = 0; i < ndata; ++i){
const float wt = info.GetWeight(i);
if (preds[i] > 0.5f){
if (info.labels[i] < 0.5f) sum += wt;
}
else{
if (info.labels[i] >= 0.5f) sum += wt;
}
wsum += wt;
}
return sum / wsum;
}
virtual const char *Name(void) const{
return "error";
}
};
/*! \brief Area under curve, for both classification and rank */
struct EvalAuc : public IEvaluator{
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const {
std::vector<unsigned> tgptr(2, 0); tgptr[1] = preds.size();
const std::vector<unsigned> &gptr = info.group_ptr.size() == 0 ? tgptr : info.group_ptr;
utils::Assert(gptr.back() == preds.size(), "EvalAuc: group structure must match number of prediction");
const unsigned ngroup = static_cast<unsigned>(gptr.size() - 1);
double sum_auc = 0.0f;
#pragma omp parallel reduction(+:sum_auc)
{
// each thread takes a local rec
std::vector< std::pair<float, unsigned> > rec;
#pragma omp for schedule(static)
for (unsigned k = 0; k < ngroup; ++k){
rec.clear();
for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j){
rec.push_back(std::make_pair(preds[j], j));
}
std::sort(rec.begin(), rec.end(), CmpFirst);
// calculate AUC
double sum_pospair = 0.0;
double sum_npos = 0.0, sum_nneg = 0.0, buf_pos = 0.0, buf_neg = 0.0;
for (size_t j = 0; j < rec.size(); ++j){
const float wt = info.GetWeight(rec[j].second);
const float ctr = info.labels[rec[j].second];
// keep bucketing predictions in same bucket
if (j != 0 && rec[j].first != rec[j - 1].first){
sum_pospair += buf_neg * (sum_npos + buf_pos *0.5);
sum_npos += buf_pos; sum_nneg += buf_neg;
buf_neg = buf_pos = 0.0f;
}
buf_pos += ctr * wt; buf_neg += (1.0f - ctr) * wt;
}
sum_pospair += buf_neg * (sum_npos + buf_pos *0.5);
sum_npos += buf_pos; sum_nneg += buf_neg;
//
utils::Assert(sum_npos > 0.0 && sum_nneg > 0.0, "the dataset only contains pos or neg samples");
// this is the AUC
sum_auc += sum_pospair / (sum_npos*sum_nneg);
}
}
// return average AUC over list
return static_cast<float>(sum_auc) / ngroup;
}
virtual const char *Name(void) const{
return "auc";
}
};
/*! \brief Precison at N, for both classification and rank */
struct EvalPrecision : public IEvaluator{
unsigned topn_;
std::string name_;
EvalPrecision(const char *name){
name_ = name;
utils::Assert(sscanf(name, "pre@%u", &topn_));
}
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const {
const std::vector<unsigned> &gptr = info.group_ptr;
utils::Assert(gptr.size() != 0 && gptr.back() == preds.size(), "EvalAuc: group structure must match number of prediction");
const unsigned ngroup = static_cast<unsigned>(gptr.size() - 1);
double sum_pre = 0.0f;
#pragma omp parallel reduction(+:sum_pre)
{
// each thread takes a local rec
std::vector< std::pair<float, unsigned> > rec;
#pragma omp for schedule(static)
for (unsigned k = 0; k < ngroup; ++k){
rec.clear();
for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j){
rec.push_back(std::make_pair(preds[j], (int)info.labels[j]));
}
std::sort(rec.begin(), rec.end(), CmpFirst);
// calculate Preicsion
unsigned nhit = 0;
for (size_t j = 0; j < rec.size() && j < topn_; ++j){
nhit += rec[j].second;
}
sum_pre += ((float)nhit) / topn_;
}
}
return static_cast<float>(sum_pre) / ngroup;
}
virtual const char *Name(void) const{
return name_.c_str();
}
};
/*! \brief Normalized DCG */
class EvalNDCG : public IEvaluator {
public:
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const{
if (info.group_ptr.size() <= 1) return 0;
float acc = 0;
std::vector< std::pair<float, float> > pairs_sort;
for (int i = 0; i < info.group_ptr.size() - 1; i++){
for (int j = info.group_ptr[i]; j < info.group_ptr[i + 1]; j++){
pairs_sort.push_back(std::make_pair(preds[j], info.labels[j]));
}
acc += NDCG(pairs_sort);
}
return acc / (info.group_ptr.size() - 1);
}
static float DCG(const std::vector<float> &labels){
float ans = 0.0;
for (int i = 0; i < labels.size(); i++){
ans += (pow(2, labels[i]) - 1) / log(i + 2);
}
return ans;
}
virtual const char *Name(void) const {
return "NDCG";
}
private:
/*\brief Obtain NDCG given the list of labels and predictions
* \param pairs_sort the first field is prediction and the second is label
*/
float NDCG(std::vector< std::pair<float, float> > pairs_sort) const{
std::sort(pairs_sort.begin(), pairs_sort.end(), [](std::pair<float, float> a, std::pair<float, float> b){
return std::get<0>(a) > std::get<0>(b);
});
float dcg = DCG(pairs_sort);
std::sort(pairs_sort.begin(), pairs_sort.end(), [](std::pair<float, float> a, std::pair<float, float> b){
return std::get<1>(a) > std::get<1>(b);
});
float IDCG = DCG(pairs_sort);
if (IDCG == 0) return 0;
return dcg / IDCG;
}
float DCG(std::vector< std::pair<float, float> > pairs_sort) const{
std::vector<float> labels;
for (int i = 1; i < pairs_sort.size(); i++){
labels.push_back(std::get<1>(pairs_sort[i]));
}
return DCG(labels);
}
};
/*! \brief Mean Average Precision */
class EvalMAP : public IEvaluator {
public:
virtual float Eval(const std::vector<float> &preds,
const DMatrix::Info &info) const{
if (info.group_ptr.size() <= 1) return 0;
float acc = 0;
std::vector<std::pair<float,float>> pairs_sort;
for (int i = 0; i < info.group_ptr.size() - 1; i++){
for (int j = info.group_ptr[i]; j < info.group_ptr[i + 1]; j++){
pairs_sort.push_back(std::make_pair(preds[j], info.labels[j]));
}
acc += average_precision(pairs_sort);
}
return acc / (info.group_ptr.size() - 1);
}
virtual const char *Name(void) const {
return "MAP";
}
private:
/*\brief Obtain average precision given the list of labels and predictions
* \param pairs_sort the first field is prediction and the second is label
*/
float average_precision(std::vector< std::pair<float,float> > pairs_sort) const{
std::sort(pairs_sort.begin(), pairs_sort.end(), [](std::pair<float, float> a, std::pair<float, float> b){
return std::get<0>(a) > std::get<0>(b);
});
float hits = 0;
float average_precision = 0;
for (int j = 0; j < pairs_sort.size(); j++){
if (std::get<1>(pairs_sort[j]) == 1){
hits++;
average_precision += hits / (j + 1);
}
}
if (hits != 0) average_precision /= hits;
return average_precision;
}
};
};
namespace regrank{
/*! \brief a set of evaluators */
struct EvalSet{
public:
inline void AddEval(const char *name){
for (size_t i = 0; i < evals_.size(); ++i){
if (!strcmp(name, evals_[i]->Name())) return;
}
if (!strcmp(name, "rmse")) evals_.push_back(new EvalRMSE());
if (!strcmp(name, "error")) evals_.push_back(new EvalError());
if (!strcmp(name, "logloss")) evals_.push_back(new EvalLogLoss());
if (!strcmp(name, "auc")) evals_.push_back(new EvalAuc());
if (!strncmp(name, "pre@", 4)) evals_.push_back(new EvalPrecision(name));
}
~EvalSet(){
for (size_t i = 0; i < evals_.size(); ++i){
delete evals_[i];
}
}
inline void Eval(FILE *fo, const char *evname,
const std::vector<float> &preds,
const DMatrix::Info &info) const{
for (size_t i = 0; i < evals_.size(); ++i){
float res = evals_[i]->Eval(preds, info);
fprintf(fo, "\t%s-%s:%f", evname, evals_[i]->Name(), res);
}
}
private:
std::vector<const IEvaluator*> evals_;
};
};
};
#endif