pairwise ranking implemented

This commit is contained in:
kalenhaha 2014-04-05 00:14:55 +08:00
parent 0b1e584d73
commit c62dea8325
3 changed files with 115 additions and 114 deletions

View File

@ -94,20 +94,20 @@ public:
inline void InitTrainer( void ) { inline void InitTrainer( void ) {
base_gbm.InitTrainer(); base_gbm.InitTrainer();
if( mparam.loss_type == PAIRWISE) { if( mparam.loss_type == PAIRWISE) {
evaluator_.AddEval( "pairwise" ); evaluator_.AddEval( "PAIR" );
} else if( mparam.loss_type == MAP) { } else if( mparam.loss_type == MAP) {
evaluator_.AddEval( "MAP" ); evaluator_.AddEval( "MAP" );
} else { } else {
evaluator_.AddEval( "NDCG" ); evaluator_.AddEval( "NDCG" );
} }
evaluator_.Init(); evaluator_.Init();
sampler.AssignSampler(mparam.sampler_type);
} }
/*! /*!
* \brief initialize the current data storage for model, if the model is used first time, call this function * \brief initialize the current data storage for model, if the model is used first time, call this function
*/ */
inline void InitModel( void ) { inline void InitModel( void ) {
base_gbm.InitModel(); base_gbm.InitModel();
mparam.AdjustBase();
} }
/*! /*!
* \brief load model from stream * \brief load model from stream
@ -134,6 +134,7 @@ public:
inline void DumpPath( FILE *fo, const RMatrix &data ) { inline void DumpPath( FILE *fo, const RMatrix &data ) {
base_gbm.DumpPath( fo, data.data ); base_gbm.DumpPath( fo, data.data );
} }
/*! /*!
* \brief save model to stream * \brief save model to stream
* \param fo output stream * \param fo output stream
@ -142,6 +143,7 @@ public:
base_gbm.SaveModel( fo ); base_gbm.SaveModel( fo );
fo.Write( &mparam, sizeof(ModelParam) ); fo.Write( &mparam, sizeof(ModelParam) );
} }
/*! /*!
* \brief update the model for one iteration * \brief update the model for one iteration
* \param iteration iteration number * \param iteration iteration number
@ -177,7 +179,7 @@ public:
const unsigned ndata = static_cast<unsigned>( data.Size() ); const unsigned ndata = static_cast<unsigned>( data.Size() );
#pragma omp parallel for schedule( static ) #pragma omp parallel for schedule( static )
for( unsigned j = 0; j < ndata; ++ j ) { for( unsigned j = 0; j < ndata; ++ j ) {
preds[j] = mparam.base_score + base_gbm.Predict( data.data, j, -1 ); preds[j] = base_gbm.Predict( data.data, j, -1 );
} }
} }
@ -219,7 +221,7 @@ private:
const unsigned ndata = static_cast<unsigned>( data.Size() ); const unsigned ndata = static_cast<unsigned>( data.Size() );
#pragma omp parallel for schedule( static ) #pragma omp parallel for schedule( static )
for( unsigned j = 0; j < ndata; ++ j ) { for( unsigned j = 0; j < ndata; ++ j ) {
preds[j] = mparam.base_score + base_gbm.InteractPredict( data.data, j, buffer_offset + j ); preds[j] = base_gbm.InteractPredict( data.data, j, buffer_offset + j );
} }
} }
/*! \brief repredict trial */ /*! \brief repredict trial */
@ -238,7 +240,7 @@ private:
const unsigned ndata = static_cast<unsigned>( data.Size() ); const unsigned ndata = static_cast<unsigned>( data.Size() );
#pragma omp parallel for schedule( static ) #pragma omp parallel for schedule( static )
for( unsigned j = 0; j < ndata; ++ j ) { for( unsigned j = 0; j < ndata; ++ j ) {
preds[j] = mparam.base_score + base_gbm.Predict( data.data, j, buffer_offset + j ); preds[j] = base_gbm.Predict( data.data, j, buffer_offset + j );
} }
} }
@ -254,13 +256,13 @@ private:
bool j_better; bool j_better;
float pred_diff,pred_diff_exp,first_order_gradient,second_order_gradient; float pred_diff,pred_diff_exp,first_order_gradient,second_order_gradient;
for(int i = 0; i < group_index.size() - 1; i++){ for(int i = 0; i < group_index.size() - 1; i++){
xgboost::rank::sample::PairSamplerSet sampler;
xgboost::rank::sample::Pairs pairs = sampler.GenPairs(preds,labels,group_index[i],group_index[i+1]); sample::Pairs pairs = sampler.GenPairs(preds,labels,group_index[i],group_index[i+1]);
for(int j = group_index[i]; j < group_index[i+1]; j++){ for(int j = group_index[i]; j < group_index[i + 1]; j++){
std::vector<int> pair_instance = pairs.GetPairs(j); std::vector<int> pair_instance = pairs.GetPairs(j);
for(int k = 0; k < pair_instance.size(); k++){
j_better = labels[j] > labels[pair_instance[k]]; j_better = labels[j] > labels[pair_instance[k]];
if(j_better){ if(j_better){
for(int k = 0; k < pair_instance.size(); k++){
pred_diff = preds[preds[j] - pair_instance[k]]; pred_diff = preds[preds[j] - pair_instance[k]];
pred_diff_exp = j_better? expf(-pred_diff):expf(pred_diff); pred_diff_exp = j_better? expf(-pred_diff):expf(pred_diff);
first_order_gradient = mparam.FirstOrderGradient(pred_diff_exp); first_order_gradient = mparam.FirstOrderGradient(pred_diff_exp);
@ -285,17 +287,16 @@ private:
/*! \brief training parameter for regression */ /*! \brief training parameter for regression */
struct ModelParam { struct ModelParam {
/* \brief global bias */
float base_score;
/* \brief type of loss function */ /* \brief type of loss function */
int loss_type; int loss_type;
/* \brief number of features */ /* \brief number of features */
int num_feature; int num_feature;
/*! \brief reserved field */ /*! \brief reserved field */
int reserved[ 16 ]; int reserved[ 16 ];
/*! \brief sampler type */
int sampler_type;
/*! \brief constructor */ /*! \brief constructor */
ModelParam( void ) { ModelParam( void ) {
base_score = 0.5f;
loss_type = 0; loss_type = 0;
num_feature = 0; num_feature = 0;
memset( reserved, 0, sizeof( reserved ) ); memset( reserved, 0, sizeof( reserved ) );
@ -306,36 +307,11 @@ private:
* \param val value of the parameter * \param val value of the parameter
*/ */
inline void SetParam( const char *name, const char *val ) { inline void SetParam( const char *name, const char *val ) {
if( !strcmp("base_score", name ) ) base_score = (float)atof( val );
if( !strcmp("loss_type", name ) ) loss_type = atoi( val ); if( !strcmp("loss_type", name ) ) loss_type = atoi( val );
if( !strcmp("bst:num_feature", name ) ) num_feature = atoi( val ); if( !strcmp("bst:num_feature", name ) ) num_feature = atoi( val );
} if( !strcmp("rank:sampler",name)) sampler = atoi( val );
/*!
* \brief adjust base_score
*/
inline void AdjustBase( void ) {
if( loss_type == 1 || loss_type == 2 ) {
utils::Assert( base_score > 0.0f && base_score < 1.0f, "sigmoid range constrain" );
base_score = - logf( 1.0f / base_score - 1.0f );
}
} }
/*!
* \brief transform the linear sum to prediction
* \param x linear sum of boosting ensemble
* \return transformed prediction
*/
inline float PredTransform( float x ) {
switch( loss_type ) {
case PAIRWISE:
case MAP:
case NDCG:
return 1.0f/(1.0f + expf(-x));
default:
utils::Error("unknown loss_type");
return 0.0f;
}
}
/*! /*!
* \brief calculate first order gradient of pairwise loss function(f(x) = ln(1+exp(-x)), * \brief calculate first order gradient of pairwise loss function(f(x) = ln(1+exp(-x)),
@ -358,57 +334,11 @@ private:
inline float SecondOrderGradient( float pred_diff_exp ) const { inline float SecondOrderGradient( float pred_diff_exp ) const {
return pred_diff_exp/pow(1 + pred_diff_exp,2); return pred_diff_exp/pow(1 + pred_diff_exp,2);
} }
/*!
* \brief calculating the loss, given the predictions, labels and the loss type
* \param preds the given predictions
* \param labels the given labels
* \return the specified loss
*/
inline float Loss(const std::vector<float> &preds, const std::vector<float> &labels) const {
switch( loss_type ) {
case kLinearSquare:
return SquareLoss(preds,labels);
case kLogisticNeglik:
case kLogisticClassify:
return NegLoglikelihoodLoss(preds,labels);
default:
utils::Error("unknown loss_type");
return 0.0f;
}
}
/*!
* \brief calculating the square loss, given the predictions and labels
* \param preds the given predictions
* \param labels the given labels
* \return the summation of square loss
*/
inline float SquareLoss(const std::vector<float> &preds, const std::vector<float> &labels) const {
float ans = 0.0;
for(size_t i = 0; i < preds.size(); i++) {
float dif = preds[i] - labels[i];
ans += dif * dif;
}
return ans;
}
/*!
* \brief calculating the square loss, given the predictions and labels
* \param preds the given predictions
* \param labels the given labels
* \return the summation of square loss
*/
inline float NegLoglikelihoodLoss(const std::vector<float> &preds, const std::vector<float> &labels) const {
float ans = 0.0;
for(size_t i = 0; i < preds.size(); i++)
ans -= labels[i] * logf(preds[i]) + ( 1 - labels[i] ) * logf(1 - preds[i]);
return ans;
}
}; };
private: private:
int silent; int silent;
RankEvalSet evaluator_; RankEvalSet evaluator_;
sample::PairSamplerWrapper sampler;
booster::GBMBase base_gbm; booster::GBMBase base_gbm;
ModelParam mparam; ModelParam mparam;
const RMatrix *train_; const RMatrix *train_;

View File

@ -46,6 +46,14 @@ bool PairValueComparer(const Pair &a, const Pair &b){
return a.value_ < b.value_; return a.value_ < b.value_;
} }
struct EvalPair : public IRankEvaluator{
virtual float Eval( const std::vector<float> &preds,
const std::vector<float> &labels,
const std::vector<int> &group_index ) const {
return 0;
}
};
/*! \brief Mean Average Precision */ /*! \brief Mean Average Precision */
struct EvalMAP : public IRankEvaluator { struct EvalMAP : public IRankEvaluator {
virtual float Eval( const std::vector<float> &preds, virtual float Eval( const std::vector<float> &preds,
@ -130,6 +138,7 @@ namespace rank {
struct RankEvalSet { struct RankEvalSet {
public: public:
inline void AddEval( const char *name ) { inline void AddEval( const char *name ) {
if( !strcmp( name, "PAIR" )) evals_.push_back( &pair_);
if( !strcmp( name, "MAP") ) evals_.push_back( &map_ ); if( !strcmp( name, "MAP") ) evals_.push_back( &map_ );
if( !strcmp( name, "NDCG") ) evals_.push_back( &ndcg_ ); if( !strcmp( name, "NDCG") ) evals_.push_back( &ndcg_ );
} }
@ -144,12 +153,13 @@ public:
const std::vector<float> &labels, const std::vector<float> &labels,
const std::vector<int> &group_index ) const { const std::vector<int> &group_index ) const {
for( size_t i = 0; i < evals_.size(); ++ i ) { for( size_t i = 0; i < evals_.size(); ++ i ) {
float res = evals_[i]->Eval( preds, labels ); float res = evals_[i]->Eval( preds, labels,group_index );
fprintf( fo, "\t%s-%s:%f", evname, evals_[i]->Name(), res ); fprintf( fo, "\t%s-%s:%f", evname, evals_[i]->Name(), res );
} }
} }
private: private:
EvalPair pair_;
EvalMAP map_; EvalMAP map_;
EvalNDCG ndcg_; EvalNDCG ndcg_;
std::vector<const IRankEvaluator*> evals_; std::vector<const IRankEvaluator*> evals_;

View File

@ -1,21 +1,47 @@
#ifndef _XGBOOST_SAMPLE_H_ #ifndef _XGBOOST_SAMPLE_H_
#define _XGBOOST_SAMPLE_H_ #define _XGBOOST_SAMPLE_H_
#include<vector>
#include"../utils/xgboost_utils.h" #include"../utils/xgboost_utils.h"
namespace xgboost { namespace xgboost {
namespace rank { namespace rank {
namespace sample { namespace sample {
/*
* \brief the data structure to maintain the sample pairs
*/
struct Pairs { struct Pairs {
/*
* \brief constructor given the start and end offset of the sampling group
* in overall instances
* \param start the begin index of the group
* \param end the end index of the group
*/
Pairs(int start,int end):start_(start),end_(end_){
for(int i = start; i < end; i++){
vector<int> v;
pairs_.push_back(v);
}
}
/* /*
* \brief retrieve the related pair information of an data instances * \brief retrieve the related pair information of an data instances
* \param index, the index of retrieved instance * \param index, the index of retrieved instance
* \return the index of instances paired * \return the index of instances paired
*/ */
std::vector<int> GetPairs(int index) { std::vector<int> GetPairs(int index) {
utils::assert(index >= start_ && index < end_, "The query index out of sampling bound"); utils::assert(index >= start_ && index < end_,"The query index out of sampling bound");
return pairs_[index-start_];
}
/*
* \brief add in a sampled pair
* \param index the index of the instance to sample a friend
* \param paired_index the index of the instance sampled as a friend
*/
void push(int index,int paired_index){
pairs_[index - start_].push_back(paired_index);
} }
std::vector<std::vector<int>> pairs_; std::vector<std::vector<int>> pairs_;
@ -23,6 +49,9 @@ namespace xgboost {
int end_; int end_;
}; };
/*
* \brief the interface of pair sampler
*/
struct IPairSampler { struct IPairSampler {
/* /*
* \brief Generate sample pairs given the predcions, labels, the start and the end index * \brief Generate sample pairs given the predcions, labels, the start and the end index
@ -36,29 +65,61 @@ namespace xgboost {
virtual Pairs GenPairs(const std::vector<float> &preds, virtual Pairs GenPairs(const std::vector<float> &preds,
const std::vector<float> &labels, const std::vector<float> &labels,
int start,int end) = 0; int start,int end) = 0;
}; };
/*! \brief a set of evaluators */ enum{
struct PairSamplerSet{ BINARY_LINEAR_SAMPLER
public: };
inline void AssignSampler( const char *name ){
if( !strcmp( name, "rmse") ) evals_.push_back( &rmse_ );
if( !strcmp( name, "error") ) evals_.push_back( &error_ );
if( !strcmp( name, "logloss") ) evals_.push_back( &logloss_ );
}
/*! \brief A simple pair sampler when the rank relevence scale is binary
* for each positive instance, we will pick a negative
* instance and add in a pair. When using binary linear sampler,
* we should guarantee the labels are 0 or 1
*/
struct BinaryLinearSampler:public IPairSampler{
virtual Pairs GenPairs(const std::vector<float> &preds,
const std::vector<float> &labels,
int start,int end) {
Pairs pairs(start,end);
int pointer = 0, last_pointer = 0,index = start, interval = end - start;
for(int i = start; i < end; i++){
if(labels[i] == 1){
while(true){
index = (++pointer) % interval + start;
if(labels[index] == 0) break;
if(pointer - last_pointer > interval) return pairs;
}
pairs.push(i,index);
pairs.push(index,i);
last_pointer = pointer;
}
}
return pairs;
}
};
/*! \brief Pair Sampler Wrapper*/
struct PairSamplerWrapper{
public:
inline void AssignSampler( int sampler_index ){
switch(sampler_index){
case BINARY_LINEAR_SAMPLER:sampler_ = &binary_linear_sampler;break;
default:utils::Error("Cannot find the specified sampler");
}
}
Pairs GenPairs(const std::vector<float> &preds, Pairs GenPairs(const std::vector<float> &preds,
const std::vector<float> &labels, const std::vector<float> &labels,
int start,int end){ int start,int end){
return sampler_.GenPairs(preds,labels,start,end);
} }
private: private:
EvalRMSE rmse_; BinaryLinearSampler binary_linear_sampler;
EvalError error_; IPairSampler *sampler_;
EvalLogLoss logloss_;
std::vector<const IEvaluator*> evals_;
}; };
} }
} }