|
|
|
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License. */
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include "ModelConfig.pb.h"
|
|
|
|
#include "paddle/parameter/Argument.h"
|
|
|
|
#include "paddle/pserver/ParameterClient2.h"
|
|
|
|
#include "paddle/utils/ClassRegistrar.h"
|
|
|
|
#include "paddle/utils/Error.h"
|
|
|
|
|
|
|
|
namespace paddle {
|
|
|
|
|
|
|
|
class NeuralNetwork;
|
|
|
|
/**
|
|
|
|
* @def REGISTER_EVALUATOR
|
|
|
|
* @brief Macro for registering evaluator class
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define REGISTER_EVALUATOR(__type_name, __class_name) \
|
|
|
|
static InitFunction __reg_type_##__type_name([]() { \
|
|
|
|
Evaluator::registrar_.registerClass<__class_name>(#__type_name); \
|
|
|
|
})
|
|
|
|
/**
|
|
|
|
* @brief Base class for Evaluator
|
|
|
|
* Evaluating the performance of a model is very important.
|
|
|
|
* It indicates how successful the scores(predictions) of a datasets
|
|
|
|
* has been by a trained model.
|
|
|
|
*/
|
|
|
|
class Evaluator {
|
|
|
|
public:
|
|
|
|
static Evaluator* create(const EvaluatorConfig& config);
|
|
|
|
|
|
|
|
Evaluator() : numSamples_(0), totalScore_(0) {}
|
|
|
|
|
|
|
|
virtual ~Evaluator() {}
|
|
|
|
|
|
|
|
virtual void init(const EvaluatorConfig& config) { config_ = config; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief start to evaluate some data
|
|
|
|
*/
|
|
|
|
virtual void start() {
|
|
|
|
numSamples_ = 0;
|
|
|
|
totalScore_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Process a batch of data.
|
|
|
|
*/
|
|
|
|
virtual void eval(const NeuralNetwork& nn);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Process a batch of data.
|
|
|
|
* @return the score for the batch if it make sense to sum the score across
|
|
|
|
* batches.
|
|
|
|
* @note Otherwise evaluator should return 0 and override finish() and
|
|
|
|
* printStats() to do the right calculation.
|
|
|
|
*/
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments) = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Update the number of processed samples
|
|
|
|
*/
|
|
|
|
virtual void updateSamplesNum(const std::vector<Argument>& arguments) {
|
|
|
|
numSamples_ += arguments[0].getBatchSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// finish() should be called before distributeEval
|
|
|
|
virtual void distributeEval(ParameterClient2* client) {
|
|
|
|
LOG(FATAL) << "Not implemeted";
|
|
|
|
}
|
|
|
|
|
|
|
|
void mergeResultsOfAllClients(ParameterClient2* client) {
|
|
|
|
double data[2] = {totalScore_, numSamples_};
|
|
|
|
client->reduce(data, data, 2, FLAGS_trainer_id, 0);
|
|
|
|
totalScore_ = data[0];
|
|
|
|
numSamples_ = data[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief finish the evaluation.
|
|
|
|
*/
|
|
|
|
virtual void finish() {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief print the statistics of evaluate result
|
|
|
|
* @note finish() should be called before printStats
|
|
|
|
*/
|
|
|
|
virtual void printStats(std::ostream& os) const {
|
|
|
|
os << config_.name() << "="
|
|
|
|
<< (numSamples_ ? totalScore_ / numSamples_ : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
friend std::ostream& operator<<(std::ostream& os,
|
|
|
|
const Evaluator& evaluator) {
|
|
|
|
evaluator.printStats(os);
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
friend std::ostream&& operator<<(std::ostream&& os, // NOLINT
|
|
|
|
const Evaluator& evaluator) {
|
|
|
|
evaluator.printStats(os);
|
|
|
|
return std::move(os);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ClassRegistrar<Evaluator> registrar_;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief getNames will return all field names of current evaluator.
|
|
|
|
*
|
|
|
|
* The format of name is `evaluator_name.evaluator_fields`. If the evaluator
|
|
|
|
* has multiple field, the name could be `evaluator_name.field1`. For example
|
|
|
|
* the PrecisionRecallEvaluator contains `precision`, `recall` fields. The get
|
|
|
|
* names will return `precision_recall_evaluator.precision`,
|
|
|
|
* `precision_recall_evaluator.recal`, etc.
|
|
|
|
*
|
|
|
|
* Also, if current Evaluator is a combined evaluator. getNames will return
|
|
|
|
* all names of all evaluators inside the combined evaluator.
|
|
|
|
*
|
|
|
|
* @param names [out]: the field names of current evaluator.
|
|
|
|
* @note Never clear the names parameter inside getNames.
|
|
|
|
*/
|
|
|
|
virtual void getNames(std::vector<std::string>* names) {
|
|
|
|
names->push_back(config_.name());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief getValue will return the current evaluate value of one field.
|
|
|
|
*
|
|
|
|
* @param name: The field name of current evaluator.
|
|
|
|
* @param err [out]: The error state.
|
|
|
|
*
|
|
|
|
* @return The evaluate value(metric).
|
|
|
|
*/
|
|
|
|
virtual real getValue(const std::string& name, Error* err) const {
|
|
|
|
if (name != config_.name()) {
|
|
|
|
*err = Error("no such name of evaluator %s", name.c_str());
|
|
|
|
return .0f;
|
|
|
|
}
|
|
|
|
return this->getValueImpl();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief getType will return the evaluator type by field name.
|
|
|
|
*
|
|
|
|
* Evaluate Type is the current type of evaluator in string. Such as 'auc',
|
|
|
|
* 'precision_recall'. In combined evaluator, different name may get different
|
|
|
|
* evaluate type because it could be evaluated by different evaluator inside.
|
|
|
|
*
|
|
|
|
* @param name: The field name of current Evaluator.
|
|
|
|
* @param err: The error state. nullptr means don't care.
|
|
|
|
* @return the evaluator type string.
|
|
|
|
*/
|
|
|
|
virtual std::string getType(const std::string& name, Error* err) const {
|
|
|
|
if (name != config_.name()) {
|
|
|
|
*err = Error("no such name of evaluator %s", name.c_str());
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
return this->getTypeImpl();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/**
|
|
|
|
* @brief getValueImpl The simplest way to define getValue result. If this
|
|
|
|
* evaluator doesn't contain multiple fields, and do not throw any error, just
|
|
|
|
* implemented this method to get the evaluate result(metric).
|
|
|
|
* @return Evaluate result(metric).
|
|
|
|
*/
|
|
|
|
virtual real getValueImpl() const {
|
|
|
|
return numSamples_ != .0 ? totalScore_ / numSamples_ : .0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief getTypeImpl The simplest way to define getType result. If this
|
|
|
|
* evaluator doesn't combine many evaluators, the get type should only return
|
|
|
|
* itself type.
|
|
|
|
* @return Evaluator type.
|
|
|
|
*/
|
|
|
|
virtual std::string getTypeImpl() const { return "base"; }
|
|
|
|
|
|
|
|
protected:
|
|
|
|
EvaluatorConfig config_;
|
|
|
|
double numSamples_;
|
|
|
|
double totalScore_;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief The NotGetableEvaluator class is the base class of evaluator that
|
|
|
|
* cannot get value in runtime. The most NotGetableEvaluator is Printer
|
|
|
|
* Evaluator, which is only used to debug network configuration.
|
|
|
|
*/
|
|
|
|
class NotGetableEvaluator : public Evaluator {
|
|
|
|
// Evaluator interface
|
|
|
|
public:
|
|
|
|
void getNames(std::vector<std::string>* names) {}
|
|
|
|
|
|
|
|
real getValue(const std::string& name, Error* err) const {
|
|
|
|
*err = Error("Not implemented");
|
|
|
|
return .0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getType(const std::string& name, Error* err) const {
|
|
|
|
*err = Error("Not implemented");
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class DummyEvaluator : public Evaluator {
|
|
|
|
public:
|
|
|
|
DummyEvaluator() {}
|
|
|
|
virtual void init(const EvaluatorConfig&) {}
|
|
|
|
virtual void start() {}
|
|
|
|
virtual void eval(const NeuralNetwork&) {}
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments) {
|
|
|
|
(void)arguments;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
virtual void finish() {}
|
|
|
|
virtual void printStats(std::ostream&) const {}
|
|
|
|
|
|
|
|
// Evaluator interface
|
|
|
|
protected:
|
|
|
|
std::string getTypeImpl() const;
|
|
|
|
};
|
|
|
|
/**
|
|
|
|
* @brief evaluate AUC using colIdx-th column as prediction.
|
|
|
|
* The AUC(Area Under the Curve) is a common evaluation metric
|
|
|
|
* for binary classification problems. It computes the area under
|
|
|
|
* the receiver operating characteristic(ROC) curve.
|
|
|
|
*
|
|
|
|
* @note colIdx-th column
|
|
|
|
*
|
|
|
|
* - colIdx = 0: the 0-th column.
|
|
|
|
* - colIdx > 0: the colIdx-th column.
|
|
|
|
* - colIdx < 0: the last colIdx-th column.
|
|
|
|
*
|
|
|
|
* The config file api is auc_evaluator.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
class AucEvaluator : public Evaluator {
|
|
|
|
public:
|
|
|
|
AucEvaluator(int32_t colIdx)
|
|
|
|
: colIdx_(colIdx),
|
|
|
|
realColumnIdx_(0),
|
|
|
|
cpuOutput_(nullptr),
|
|
|
|
cpuLabel_(nullptr),
|
|
|
|
cpuWeight_(nullptr) {}
|
|
|
|
|
|
|
|
virtual void start();
|
|
|
|
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments);
|
|
|
|
|
|
|
|
virtual void printStats(std::ostream& os) const {
|
|
|
|
os << config_.name() << "=" << calcAuc();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void distributeEval(ParameterClient2* client);
|
|
|
|
|
|
|
|
private:
|
|
|
|
static const uint32_t kBinNum_ = (1 << 24) - 1;
|
|
|
|
static const int kNegativeLabel_ = 0;
|
|
|
|
double statPos_[kBinNum_ + 1];
|
|
|
|
double statNeg_[kBinNum_ + 1];
|
|
|
|
int32_t colIdx_;
|
|
|
|
uint32_t realColumnIdx_;
|
|
|
|
MatrixPtr cpuOutput_;
|
|
|
|
IVectorPtr cpuLabel_;
|
|
|
|
MatrixPtr cpuWeight_;
|
|
|
|
|
|
|
|
AucEvaluator() {}
|
|
|
|
|
|
|
|
inline static double trapezoidArea(double X1,
|
|
|
|
double X2,
|
|
|
|
double Y1,
|
|
|
|
double Y2) {
|
|
|
|
return (X1 > X2 ? (X1 - X2) : (X2 - X1)) * (Y1 + Y2) / 2.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
double calcAuc() const;
|
|
|
|
|
|
|
|
// Evaluator interface
|
|
|
|
protected:
|
|
|
|
real getValueImpl() const;
|
|
|
|
std::string getTypeImpl() const;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief RankAucEvaluator calculates the AUC of each list (i.e., titles
|
|
|
|
* under the same query), and averages them. Each list should be organized
|
|
|
|
* as a sequence. The inputs of this evaluator is [output, click, pv]. If pv
|
|
|
|
* is not provided, it will be set to 1. The types of click and pv are
|
|
|
|
* dense value.
|
|
|
|
*/
|
|
|
|
class RankAucEvaluator : public Evaluator {
|
|
|
|
public:
|
|
|
|
// evaluate ranking AUC
|
|
|
|
virtual void start();
|
|
|
|
|
|
|
|
virtual void updateSamplesNum(const std::vector<Argument>& arguments);
|
|
|
|
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments);
|
|
|
|
|
|
|
|
virtual void distributeEval(ParameterClient2* client) {
|
|
|
|
mergeResultsOfAllClients(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
MatrixPtr output_;
|
|
|
|
MatrixPtr click_;
|
|
|
|
MatrixPtr pv_;
|
|
|
|
std::vector<std::pair<real, int>> outputPair_;
|
|
|
|
|
|
|
|
double calcRankAuc(real* outputData,
|
|
|
|
real* clickData,
|
|
|
|
real* pvData,
|
|
|
|
size_t size);
|
|
|
|
|
|
|
|
// Evaluator interface
|
|
|
|
protected:
|
|
|
|
std::string getTypeImpl() const;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief precision, recall and f1 score Evaluator
|
|
|
|
* \f[
|
|
|
|
* precision = \frac{tp}{tp+tn} \\
|
|
|
|
* recall=\frac{tp}{tp+fn} \\
|
|
|
|
* f1=2*\frac{precsion*recall}{precision+recall}
|
|
|
|
* \f]
|
|
|
|
*
|
|
|
|
* The config file api is precision_recall_evaluator.
|
|
|
|
*/
|
|
|
|
class PrecisionRecallEvaluator : public Evaluator {
|
|
|
|
public:
|
|
|
|
// Evaluate precision, recall and F1 score
|
|
|
|
PrecisionRecallEvaluator()
|
|
|
|
: isMultiBinaryLabel_(false),
|
|
|
|
cpuOutput_(nullptr),
|
|
|
|
cpuLabel_(nullptr),
|
|
|
|
cpuWeight_(nullptr) {}
|
|
|
|
|
|
|
|
virtual void start();
|
|
|
|
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments);
|
|
|
|
|
|
|
|
virtual void printStats(std::ostream& os) const;
|
|
|
|
|
|
|
|
virtual void distributeEval(ParameterClient2* client);
|
|
|
|
|
|
|
|
void getNames(std::vector<std::string>* names);
|
|
|
|
|
|
|
|
real getValue(const std::string& name, Error* err) const;
|
|
|
|
|
|
|
|
std::string getType(const std::string& name, Error* err) const;
|
|
|
|
|
|
|
|
struct StatsInfo {
|
|
|
|
/// numbers of true positives
|
|
|
|
double TP;
|
|
|
|
/// numbers of true negatives
|
|
|
|
double TN;
|
|
|
|
/// numbers of false positives
|
|
|
|
double FP;
|
|
|
|
/// numbers of false negatives
|
|
|
|
double FN;
|
|
|
|
|
|
|
|
StatsInfo() : TP(0.0), TN(0.0), FP(0.0), FN(0.0) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool isMultiBinaryLabel_;
|
|
|
|
std::vector<StatsInfo> statsInfo_;
|
|
|
|
|
|
|
|
MatrixPtr cpuOutput_;
|
|
|
|
IVectorPtr cpuLabel_;
|
|
|
|
MatrixPtr cpuWeight_;
|
|
|
|
|
|
|
|
struct PrintStatsInfo {
|
|
|
|
double precision;
|
|
|
|
double recall;
|
|
|
|
double f1;
|
|
|
|
double macroAvgPrecision;
|
|
|
|
double macroAvgRecall;
|
|
|
|
double macroAvgF1Score;
|
|
|
|
double microAvgPrecision;
|
|
|
|
double microAvgRecall;
|
|
|
|
double microAvgF1Score;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool getStatsInfo(PrintStatsInfo* info) const;
|
|
|
|
|
|
|
|
void calcStatsInfo(const MatrixPtr& output,
|
|
|
|
const IVectorPtr& label,
|
|
|
|
const MatrixPtr& weight);
|
|
|
|
|
|
|
|
void calcStatsInfoMulti(const MatrixPtr& output,
|
|
|
|
const MatrixPtr& label,
|
|
|
|
const MatrixPtr& weight);
|
|
|
|
|
|
|
|
inline static double calcPrecision(double TP, double FP) {
|
|
|
|
if (TP > 0.0 || FP > 0.0) {
|
|
|
|
return TP / (TP + FP);
|
|
|
|
} else {
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static double calcRecall(double TP, double FN) {
|
|
|
|
if (TP > 0.0 || FN > 0.0) {
|
|
|
|
return TP / (TP + FN);
|
|
|
|
} else {
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static double calcF1Score(double precision, double recall) {
|
|
|
|
if (precision > 0.0 || recall > 0.0) {
|
|
|
|
return 2 * precision * recall / (precision + recall);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mutable std::unordered_map<std::string, real> values_;
|
|
|
|
|
|
|
|
void storeLocalValues() const;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @brief positive-negative pair rate Evaluator
|
|
|
|
*
|
|
|
|
* The config file api is pnpair_evaluator.
|
|
|
|
*/
|
|
|
|
class PnpairEvaluator : public Evaluator {
|
|
|
|
public:
|
|
|
|
PnpairEvaluator()
|
|
|
|
: cpuOutput_(nullptr),
|
|
|
|
cpuLabel_(nullptr),
|
|
|
|
cpuInfo_(nullptr),
|
|
|
|
cpuWeight_(nullptr) {}
|
|
|
|
|
|
|
|
virtual void start();
|
|
|
|
virtual real evalImp(std::vector<Argument>& arguments);
|
|
|
|
|
|
|
|
struct PredictionResult {
|
|
|
|
PredictionResult(real __out, int __label, int __queryid, real __weight)
|
|
|
|
: out(__out), label(__label), queryid(__queryid), weight(__weight) {}
|
|
|
|
real out;
|
|
|
|
int label;
|
|
|
|
int queryid;
|
|
|
|
real weight;
|
|
|
|
};
|
|
|
|
std::vector<PredictionResult> predictArray_;
|
|
|
|
void printPredictResults() {
|
|
|
|
std::ofstream fs(FLAGS_predict_file);
|
|
|
|
CHECK(fs) << "Fail to open " << FLAGS_predict_file;
|
|
|
|
for (auto& res : predictArray_) {
|
|
|
|
fs << res.out << " " << res.label << " " << res.queryid << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void stat(size_t start,
|
|
|
|
size_t end,
|
|
|
|
PredictionResult* answers,
|
|
|
|
double& pos,
|
|
|
|
double& neg,
|
|
|
|
double& spe);
|
|
|
|
void calc(std::vector<PredictionResult>& predictArray);
|
|
|
|
|
|
|
|
virtual void finish() { calc(predictArray_); }
|
|
|
|
|
|
|
|
virtual void printStats(std::ostream& os) const {
|
|
|
|
os << " pos/neg=" << this->getValueImpl();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void distributeEval(ParameterClient2* client) {
|
|
|
|
client->reduce(pairArray_, pairArray_, kPairArrayNum_, FLAGS_trainer_id, 0);
|
|
|
|
LOG(INFO) << " distribute eval calc total pos pair: " << pairArray_[0]
|
|
|
|
<< " calc total neg pair: " << pairArray_[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static const uint32_t kPairArrayNum_ = 2;
|
|
|
|
double pairArray_[kPairArrayNum_];
|
|
|
|
MatrixPtr cpuOutput_;
|
|
|
|
IVectorPtr cpuLabel_;
|
|
|
|
IVectorPtr cpuInfo_;
|
|
|
|
MatrixPtr cpuWeight_;
|
|
|
|
|
|
|
|
// Evaluator interface
|
|
|
|
protected:
|
|
|
|
real getValueImpl() const {
|
|
|
|
return pairArray_[0] / ((pairArray_[1] <= 0) ? 1.0 : pairArray_[1]);
|
|
|
|
}
|
|
|
|
std::string getTypeImpl() const;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace paddle
|