Medial Code Documentation
Loading...
Searching...
No Matches
xentropy_metric.hpp
1#ifndef LIGHTGBM_METRIC_XENTROPY_METRIC_HPP_
2#define LIGHTGBM_METRIC_XENTROPY_METRIC_HPP_
3
4#include <LightGBM/metric.h>
5#include <LightGBM/meta.h>
6
7#include <LightGBM/utils/log.h>
8#include <LightGBM/utils/common.h>
9
10#include <algorithm>
11#include <vector>
12#include <sstream>
13
14/*
15 * Implements three related metrics:
16 *
17 * (1) standard cross-entropy that can be used for continuous labels in [0, 1]
18 * (2) "intensity-weighted" cross-entropy, also for continuous labels in [0, 1]
19 * (3) Kullback-Leibler divergence, also for continuous labels in [0, 1]
20 *
21 * (3) adds an offset term to (1); the entropy of the label
22 *
23 * See xentropy_objective.hpp for further details.
24 *
25 */
26
27namespace LightGBM {
28
29 // label should be in interval [0, 1];
30 // prob should be in interval (0, 1); prob is clipped if needed
31 inline static double XentLoss(label_t label, double prob) {
32 const double log_arg_epsilon = 1.0e-12;
33 double a = label;
34 if (prob > log_arg_epsilon) {
35 a *= std::log(prob);
36 } else {
37 a *= std::log(log_arg_epsilon);
38 }
39 double b = 1.0f - label;
40 if (1.0f - prob > log_arg_epsilon) {
41 b *= std::log(1.0f - prob);
42 } else {
43 b *= std::log(log_arg_epsilon);
44 }
45 return - (a + b);
46 }
47
48 // hhat >(=) 0 assumed; and weight > 0 required; but not checked here
49 inline static double XentLambdaLoss(label_t label, label_t weight, double hhat) {
50 return XentLoss(label, 1.0f - std::exp(-weight * hhat));
51 }
52
53 // Computes the (negative) entropy for label p; p should be in interval [0, 1];
54 // This is used to presum the KL-divergence offset term (to be _added_ to the cross-entropy loss).
55 // NOTE: x*log(x) = 0 for x=0,1; so only add when in (0, 1); avoid log(0)*0
56 inline static double YentLoss(double p) {
57 double hp = 0.0;
58 if (p > 0) hp += p * std::log(p);
59 double q = 1.0f - p;
60 if (q > 0) hp += q * std::log(q);
61 return hp;
62 }
63
64//
65// CrossEntropyMetric : "xentropy" : (optional) weights are used linearly
66//
67class CrossEntropyMetric : public Metric {
68public:
69 explicit CrossEntropyMetric(const Config&) {}
70 virtual ~CrossEntropyMetric() {}
71
72 void Init(const Metadata& metadata, data_size_t num_data) override {
73 name_.emplace_back("xentropy");
74 num_data_ = num_data;
75 label_ = metadata.label();
76 weights_ = metadata.weights();
77
78 CHECK_NOTNULL(label_);
79
80 // ensure that labels are in interval [0, 1], interval ends included
81 Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
82 Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
83
84 // check that weights are non-negative and sum is positive
85 if (weights_ == nullptr) {
86 sum_weights_ = static_cast<double>(num_data_);
87 } else {
88 label_t minw;
89 Common::ObtainMinMaxSum(weights_, num_data_, &minw, (label_t*)nullptr, &sum_weights_);
90 if (minw < 0.0f) {
91 Log::Fatal("[%s:%s]: (metric) weights not allowed to be negative", GetName()[0].c_str(), __func__);
92 }
93 }
94
95 // check weight sum (may fail to be zero)
96 if (sum_weights_ <= 0.0f) {
97 Log::Fatal("[%s:%s]: sum-of-weights = %f is non-positive", __func__, GetName()[0].c_str(), sum_weights_);
98 }
99 Log::Info("[%s:%s]: sum-of-weights = %f", GetName()[0].c_str(), __func__, sum_weights_);
100 }
101
102 std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
103 double sum_loss = 0.0f;
104 if (objective == nullptr) {
105 if (weights_ == nullptr) {
106 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
107 for (data_size_t i = 0; i < num_data_; ++i) {
108 sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability
109 }
110 } else {
111 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
112 for (data_size_t i = 0; i < num_data_; ++i) {
113 sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability
114 }
115 }
116 } else {
117 if (weights_ == nullptr) {
118 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
119 for (data_size_t i = 0; i < num_data_; ++i) {
120 double p = 0;
121 objective->ConvertOutput(&score[i], &p);
122 sum_loss += XentLoss(label_[i], p);
123 }
124 } else {
125 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
126 for (data_size_t i = 0; i < num_data_; ++i) {
127 double p = 0;
128 objective->ConvertOutput(&score[i], &p);
129 sum_loss += XentLoss(label_[i], p) * weights_[i];
130 }
131 }
132 }
133 double loss = sum_loss / sum_weights_;
134 return std::vector<double>(1, loss);
135 }
136
137 const std::vector<std::string>& GetName() const override {
138 return name_;
139 }
140
141 double factor_to_bigger_better() const override {
142 return -1.0f; // negative means smaller loss is better, positive means larger loss is better
143 }
144
145private:
147 data_size_t num_data_;
149 const label_t* label_;
151 const label_t* weights_;
153 double sum_weights_;
155 std::vector<std::string> name_;
156};
157
158//
159// CrossEntropyLambdaMetric : "xentlambda" : (optional) weights have a different meaning than for "xentropy"
160// ATTENTION: Supposed to be used when the objective also is "xentlambda"
161//
163public:
164 explicit CrossEntropyLambdaMetric(const Config&) {}
165 virtual ~CrossEntropyLambdaMetric() {}
166
167 void Init(const Metadata& metadata, data_size_t num_data) override {
168 name_.emplace_back("xentlambda");
169 num_data_ = num_data;
170 label_ = metadata.label();
171 weights_ = metadata.weights();
172
173 CHECK_NOTNULL(label_);
174 Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
175 Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
176
177 // check all weights are strictly positive; throw error if not
178 if (weights_ != nullptr) {
179 label_t minw;
180 Common::ObtainMinMaxSum(weights_, num_data_, &minw, (label_t*)nullptr, (label_t*)nullptr);
181 if (minw <= 0.0f) {
182 Log::Fatal("[%s:%s]: (metric) all weights must be positive", GetName()[0].c_str(), __func__);
183 }
184 }
185 }
186
187 std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
188 double sum_loss = 0.0f;
189 if (objective == nullptr) {
190 if (weights_ == nullptr) {
191 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
192 for (data_size_t i = 0; i < num_data_; ++i) {
193 double hhat = std::log(1.0f + std::exp(score[i])); // auto-convert
194 sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat);
195 }
196 } else {
197 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
198 for (data_size_t i = 0; i < num_data_; ++i) {
199 double hhat = std::log(1.0f + std::exp(score[i])); // auto-convert
200 sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat);
201 }
202 }
203 } else {
204 if (weights_ == nullptr) {
205 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
206 for (data_size_t i = 0; i < num_data_; ++i) {
207 double hhat = 0;
208 objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda"
209 sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat);
210 }
211 } else {
212 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
213 for (data_size_t i = 0; i < num_data_; ++i) {
214 double hhat = 0;
215 objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda"
216 sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat);
217 }
218 }
219 }
220 return std::vector<double>(1, sum_loss / static_cast<double>(num_data_));
221 }
222
223 const std::vector<std::string>& GetName() const override {
224 return name_;
225 }
226
227 double factor_to_bigger_better() const override {
228 return -1.0f;
229 }
230
231private:
233 data_size_t num_data_;
235 const label_t* label_;
237 const label_t* weights_;
239 std::vector<std::string> name_;
240};
241
242//
243// KullbackLeiblerDivergence : "kldiv" : (optional) weights are used linearly
244//
246public:
247 explicit KullbackLeiblerDivergence(const Config&) {}
248 virtual ~KullbackLeiblerDivergence() {}
249
250 void Init(const Metadata& metadata, data_size_t num_data) override {
251 name_.emplace_back("kldiv");
252 num_data_ = num_data;
253 label_ = metadata.label();
254 weights_ = metadata.weights();
255
256 CHECK_NOTNULL(label_);
257 Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
258 Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
259
260 if (weights_ == nullptr) {
261 sum_weights_ = static_cast<double>(num_data_);
262 } else {
263 label_t minw;
264 Common::ObtainMinMaxSum(weights_, num_data_, &minw, (label_t*)nullptr, &sum_weights_);
265 if (minw < 0.0f) {
266 Log::Fatal("[%s:%s]: (metric) at least one weight is negative", GetName()[0].c_str(), __func__);
267 }
268 }
269
270 // check weight sum
271 if (sum_weights_ <= 0.0f) {
272 Log::Fatal("[%s:%s]: sum-of-weights = %f is non-positive", GetName()[0].c_str(), __func__, sum_weights_);
273 }
274
275 Log::Info("[%s:%s]: sum-of-weights = %f", GetName()[0].c_str(), __func__, sum_weights_);
276
277 // evaluate offset term
278 presum_label_entropy_ = 0.0f;
279 if (weights_ == nullptr) {
280 // #pragma omp parallel for schedule(static)
281 for (data_size_t i = 0; i < num_data; ++i) {
282 presum_label_entropy_ += YentLoss(label_[i]);
283 }
284 } else {
285 // #pragma omp parallel for schedule(static)
286 for (data_size_t i = 0; i < num_data; ++i) {
287 presum_label_entropy_ += YentLoss(label_[i]) * weights_[i];
288 }
289 }
290 presum_label_entropy_ /= sum_weights_;
291
292 // communicate the value of the offset term to be added
293 Log::Info("%s offset term = %f", GetName()[0].c_str(), presum_label_entropy_);
294 }
295
296 std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
297 double sum_loss = 0.0f;
298 if (objective == nullptr) {
299 if (weights_ == nullptr) {
300 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
301 for (data_size_t i = 0; i < num_data_; ++i) {
302 sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability
303 }
304 } else {
305 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
306 for (data_size_t i = 0; i < num_data_; ++i) {
307 sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability
308 }
309 }
310 } else {
311 if (weights_ == nullptr) {
312 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
313 for (data_size_t i = 0; i < num_data_; ++i) {
314 double p = 0;
315 objective->ConvertOutput(&score[i], &p);
316 sum_loss += XentLoss(label_[i], p);
317 }
318 } else {
319 #pragma omp parallel for schedule(static) reduction(+:sum_loss)
320 for (data_size_t i = 0; i < num_data_; ++i) {
321 double p = 0;
322 objective->ConvertOutput(&score[i], &p);
323 sum_loss += XentLoss(label_[i], p) * weights_[i];
324 }
325 }
326 }
327 double loss = presum_label_entropy_ + sum_loss / sum_weights_;
328 return std::vector<double>(1, loss);
329 }
330
331 const std::vector<std::string>& GetName() const override {
332 return name_;
333 }
334
335 double factor_to_bigger_better() const override {
336 return -1.0f;
337 }
338
339private:
341 data_size_t num_data_;
343 const label_t* label_;
345 const label_t* weights_;
347 double sum_weights_;
349 double presum_label_entropy_;
351 std::vector<std::string> name_;
352};
353
354} // end namespace LightGBM
355
356#endif // end #ifndef LIGHTGBM_METRIC_XENTROPY_METRIC_HPP_
Definition xentropy_metric.hpp:162
void Init(const Metadata &metadata, data_size_t num_data) override
Initialize.
Definition xentropy_metric.hpp:167
std::vector< double > Eval(const double *score, const ObjectiveFunction *objective) const override
Calcaluting and printing metric result.
Definition xentropy_metric.hpp:187
Definition xentropy_metric.hpp:67
std::vector< double > Eval(const double *score, const ObjectiveFunction *objective) const override
Calcaluting and printing metric result.
Definition xentropy_metric.hpp:102
void Init(const Metadata &metadata, data_size_t num_data) override
Initialize.
Definition xentropy_metric.hpp:72
Definition xentropy_metric.hpp:245
std::vector< double > Eval(const double *score, const ObjectiveFunction *objective) const override
Calcaluting and printing metric result.
Definition xentropy_metric.hpp:296
void Init(const Metadata &metadata, data_size_t num_data) override
Initialize.
Definition xentropy_metric.hpp:250
This class is used to store some meta(non-feature) data for training data, e.g. labels,...
Definition dataset.h:36
const label_t * label() const
Get pointer of label.
Definition dataset.h:113
const label_t * weights() const
Get weights, if not exists, will return nullptr.
Definition dataset.h:146
The interface of metric. Metric is used to calculate metric result.
Definition metric.h:20
The interface of Objective Function.
Definition objective_function.h:13
desc and descl2 fields must be written in reStructuredText format
Definition application.h:10
float label_t
Type of metadata, include weight and label.
Definition meta.h:33
int32_t data_size_t
Type of data size, it is better to use signed type.
Definition meta.h:14
Definition config.h:27