xentropy_objective.hpp 9.25 KB
Newer Older
1
2
3
4
/*!
 * Copyright (c) 2017 Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE file in the project root for license information.
 */
5
6
7
#ifndef LIGHTGBM_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_
#define LIGHTGBM_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_

8
9
10
11
#include <LightGBM/meta.h>
#include <LightGBM/objective_function.h>
#include <LightGBM/utils/common.h>

12
13
#include <string>
#include <algorithm>
14
#include <cmath>
15
16
#include <cstring>
#include <vector>
17
18
19
20
21
22

/*
 * Implements gradients and hessians for the following point losses.
 * Target y is anything in interval [0, 1].
 *
 * (1) CrossEntropy; "xentropy";
Tony-Y's avatar
Tony-Y committed
23
 *
24
25
26
27
28
29
30
 * loss(y, p, w) = { -(1-y)*log(1-p)-y*log(p) }*w,
 * with probability p = 1/(1+exp(-f)), where f is being boosted
 *
 * ConvertToOutput: f -> p
 *
 * (2) CrossEntropyLambda; "xentlambda"
 *
Tony-Y's avatar
Tony-Y committed
31
 * loss(y, p, w) = -(1-y)*log(1-p)-y*log(p),
32
33
34
35
36
37
38
39
40
41
42
43
44
 * with p = 1-exp(-lambda*w), lambda = log(1+exp(f)), f being boosted, and w > 0
 *
 * ConvertToOutput: f -> lambda
 *
 * (1) and (2) are the same if w=1; but outputs still differ.
 *
 */

namespace LightGBM {
/*!
* \brief Objective function for cross-entropy (with optional linear weights)
*/
class CrossEntropy: public ObjectiveFunction {
Nikita Titov's avatar
Nikita Titov committed
45
 public:
Guolin Ke's avatar
Guolin Ke committed
46
  explicit CrossEntropy(const Config&) {
47
48
49
50
51
52
53
54
55
56
57
58
59
  }

  explicit CrossEntropy(const std::vector<std::string>&) {
  }

  ~CrossEntropy() {}

  void Init(const Metadata& metadata, data_size_t num_data) override {
    num_data_ = num_data;
    label_ = metadata.label();
    weights_ = metadata.weights();

    CHECK_NOTNULL(label_);
60
    Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName());
61
62
63
    Log::Info("[%s:%s]: (objective) labels passed interval [0, 1] check",  GetName(), __func__);

    if (weights_ != nullptr) {
64
      label_t minw;
65
      double sumw;
66
      Common::ObtainMinMaxSum(weights_, num_data_, &minw, static_cast<label_t*>(nullptr), &sumw);
67
      if (minw < 0.0f) {
68
        Log::Fatal("[%s]: at least one weight is negative", GetName());
69
70
      }
      if (sumw == 0.0f) {
71
        Log::Fatal("[%s]: sum of weights is zero", GetName());
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
      }
    }
  }

  void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
    if (weights_ == nullptr) {
      // compute pointwise gradients and hessians with implied unit weights
      #pragma omp parallel for schedule(static)
      for (data_size_t i = 0; i < num_data_; ++i) {
        const double z = 1.0f / (1.0f + std::exp(-score[i]));
        gradients[i] = static_cast<score_t>(z - label_[i]);
        hessians[i] = static_cast<score_t>(z * (1.0f - z));
      }
    } else {
      // compute pointwise gradients and hessians with given weights
      #pragma omp parallel for schedule(static)
      for (data_size_t i = 0; i < num_data_; ++i) {
        const double z = 1.0f / (1.0f + std::exp(-score[i]));
        gradients[i] = static_cast<score_t>((z - label_[i]) * weights_[i]);
        hessians[i] = static_cast<score_t>(z * (1.0f - z) * weights_[i]);
      }
    }
  }

  const char* GetName() const override {
Guolin Ke's avatar
Guolin Ke committed
97
    return "cross_entropy";
98
99
100
101
102
103
104
105
106
107
108
109
110
  }

  // convert score to a probability
  void ConvertOutput(const double* input, double* output) const override {
    output[0] = 1.0f / (1.0f + std::exp(-input[0]));
  }

  std::string ToString() const override {
    std::stringstream str_buf;
    str_buf << GetName();
    return str_buf.str();
  }

111
  // implement custom average to boost from (if enabled among options)
112
  double BoostFromScore(int) const override {
113
114
    double suml = 0.0f;
    double sumw = 0.0f;
115
    if (weights_ != nullptr) {
116
      #pragma omp parallel for schedule(static) reduction(+:suml, sumw)
117
118
119
120
121
122
      for (data_size_t i = 0; i < num_data_; ++i) {
        suml += label_[i] * weights_[i];
        sumw += weights_[i];
      }
    } else {
      sumw = static_cast<double>(num_data_);
123
      #pragma omp parallel for schedule(static) reduction(+:suml)
124
125
126
127
128
      for (data_size_t i = 0; i < num_data_; ++i) {
        suml += label_[i];
      }
    }
    double pavg = suml / sumw;
129
130
    pavg = std::min(pavg, 1.0 - kEpsilon);
    pavg = std::max<double>(pavg, kEpsilon);
131
    double initscore = std::log(pavg / (1.0f - pavg));
132
    Log::Info("[%s:%s]: pavg = %f -> initscore = %f",  GetName(), __func__, pavg, initscore);
133
    return initscore;
134
135
  }

Nikita Titov's avatar
Nikita Titov committed
136
 private:
137
138
139
  /*! \brief Number of data points */
  data_size_t num_data_;
  /*! \brief Pointer for label */
140
  const label_t* label_;
141
  /*! \brief Weights for data */
142
  const label_t* weights_;
143
144
145
146
147
148
};

/*!
* \brief Objective function for alternative parameterization of cross-entropy (see top of file for explanation)
*/
class CrossEntropyLambda: public ObjectiveFunction {
Nikita Titov's avatar
Nikita Titov committed
149
 public:
Guolin Ke's avatar
Guolin Ke committed
150
  explicit CrossEntropyLambda(const Config&) {
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    min_weight_ = max_weight_ = 0.0f;
  }

  explicit CrossEntropyLambda(const std::vector<std::string>&) {
  }

  ~CrossEntropyLambda() {}

  void Init(const Metadata& metadata, data_size_t num_data) override {
    num_data_ = num_data;
    label_ = metadata.label();
    weights_ = metadata.weights();

    CHECK_NOTNULL(label_);
165
    Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName());
166
167
168
    Log::Info("[%s:%s]: (objective) labels passed interval [0, 1] check",  GetName(), __func__);

    if (weights_ != nullptr) {
169
      Common::ObtainMinMaxSum(weights_, num_data_, &min_weight_, &max_weight_, static_cast<label_t*>(nullptr));
170
      if (min_weight_ <= 0.0f) {
171
        Log::Fatal("[%s]: at least one weight is non-positive", GetName());
172
173
174
175
      }

      // Issue an info statement about this ratio
      double weight_ratio = max_weight_ / min_weight_;
Tony-Y's avatar
Tony-Y committed
176
      Log::Info("[%s:%s]: min, max weights = %f, %f; ratio = %f",
177
178
179
180
                GetName(), __func__,
                min_weight_, max_weight_,
                weight_ratio);
    } else {
Tony-Y's avatar
Tony-Y committed
181
      // all weights are implied to be unity; no need to do anything
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    }
  }

  void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
    if (weights_ == nullptr) {
      // compute pointwise gradients and hessians with implied unit weights; exactly equivalent to CrossEntropy with unit weights
      #pragma omp parallel for schedule(static)
      for (data_size_t i = 0; i < num_data_; ++i) {
        const double z = 1.0f / (1.0f + std::exp(-score[i]));
        gradients[i] = static_cast<score_t>(z - label_[i]);
        hessians[i] = static_cast<score_t>(z * (1.0f - z));
      }
    } else {
      // compute pointwise gradients and hessians with given weights
      #pragma omp parallel for schedule(static)
      for (data_size_t i = 0; i < num_data_; ++i) {
        const double w = weights_[i];
        const double y = label_[i];
        const double epf = std::exp(score[i]);
        const double hhat = std::log(1.0f + epf);
        const double z = 1.0f - std::exp(-w*hhat);
203
        const double enf = 1.0f / epf;  // = std::exp(-score[i]);
204
205
206
207
208
209
210
211
212
213
214
215
        gradients[i] = static_cast<score_t>((1.0f - y / z) * w / (1.0f + enf));
        const double c = 1.0f / (1.0f - z);
        double d = 1.0f + epf;
        const double a = w * epf / (d * d);
        d = c - 1.0f;
        const double b = (c / (d * d) ) * (1.0f + w * epf - c);
        hessians[i] = static_cast<score_t>(a * (1.0f + y * b));
      }
    }
  }

  const char* GetName() const override {
Guolin Ke's avatar
Guolin Ke committed
216
    return "cross_entropy_lambda";
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
  }

  //
  // ATTENTION: the function output is the "normalized exponential parameter" lambda > 0, not the probability
  //
  // If this code would read: output[0] = 1.0f / (1.0f + std::exp(-input[0]));
  // The output would still not be the probability unless the weights are unity.
  //
  // Let z = 1 / (1 + exp(-f)), then prob(z) = 1-(1-z)^w, where w is the weight for the specific point.
  //

  void ConvertOutput(const double* input, double* output) const override {
    output[0] = std::log(1.0f + std::exp(input[0]));
  }

  std::string ToString() const override {
    std::stringstream str_buf;
    str_buf << GetName();
    return str_buf.str();
  }

238
  double BoostFromScore(int) const override {
239
    double suml = 0.0f;
240
    double sumw = 0.0f;
Laurae's avatar
Laurae committed
241
    if (weights_ != nullptr) {
242
      #pragma omp parallel for schedule(static) reduction(+:suml, sumw)
243
244
245
246
      for (data_size_t i = 0; i < num_data_; ++i) {
        suml += label_[i] * weights_[i];
        sumw += weights_[i];
      }
247
248
    } else {
      sumw = static_cast<double>(num_data_);
249
250
251
252
      #pragma omp parallel for schedule(static) reduction(+:suml)
      for (data_size_t i = 0; i < num_data_; ++i) {
        suml += label_[i];
      }
253
    }
254
255
    double havg = suml / sumw;
    double initscore = std::log(std::exp(havg) - 1.0f);
256
    Log::Info("[%s:%s]: havg = %f -> initscore = %f",  GetName(), __func__, havg, initscore);
257
    return initscore;
258
259
  }

260
 private:
261
262
263
  /*! \brief Number of data points */
  data_size_t num_data_;
  /*! \brief Pointer for label */
264
  const label_t* label_;
265
  /*! \brief Weights for data */
266
  const label_t* weights_;
267
  /*! \brief Minimum weight found during init */
268
  label_t min_weight_;
269
  /*! \brief Maximum weight found during init */
270
  label_t max_weight_;
271
272
273
274
275
};

}  // end namespace LightGBM

#endif   // end #ifndef LIGHTGBM_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_