multiclass_metric.hpp 10.9 KB
Newer Older
1
2
3
4
/*!
 * Copyright (c) 2016 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_METRIC_MULTICLASS_METRIC_HPP_
#define LIGHTGBM_METRIC_MULTICLASS_METRIC_HPP_

8
9
10
#include <LightGBM/metric.h>
#include <LightGBM/utils/log.h>

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

namespace LightGBM {
/*!
* \brief Metric for multiclass task.
* Use static class "PointWiseLossCalculator" to calculate loss point-wise
*/
template<typename PointWiseLossCalculator>
class MulticlassMetric: public Metric {
Nikita Titov's avatar
Nikita Titov committed
23
 public:
24
  explicit MulticlassMetric(const Config& config) :config_(config) {
Guolin Ke's avatar
Guolin Ke committed
25
    num_class_ = config.num_class;
26
27
28
29
30
  }

  virtual ~MulticlassMetric() {
  }

Guolin Ke's avatar
Guolin Ke committed
31
  void Init(const Metadata& metadata, data_size_t num_data) override {
Belinda Trotta's avatar
Belinda Trotta committed
32
    name_.emplace_back(PointWiseLossCalculator::Name(config_));
33
34
35
36
37
38
    num_data_ = num_data;
    // get label
    label_ = metadata.label();
    // get weights
    weights_ = metadata.weights();
    if (weights_ == nullptr) {
39
      sum_weights_ = static_cast<double>(num_data_);
40
41
42
43
44
45
46
    } else {
      sum_weights_ = 0.0f;
      for (data_size_t i = 0; i < num_data_; ++i) {
        sum_weights_ += weights_[i];
      }
    }
  }
47

Guolin Ke's avatar
Guolin Ke committed
48
  const std::vector<std::string>& GetName() const override {
49
    return name_;
50
51
  }

52
  double factor_to_bigger_better() const override {
53
    return -1.0f;
54
  }
55

Guolin Ke's avatar
Guolin Ke committed
56
  std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
57
    double sum_loss = 0.0;
Guolin Ke's avatar
Guolin Ke committed
58
59
60
    int num_tree_per_iteration = num_class_;
    int num_pred_per_row = num_class_;
    if (objective != nullptr) {
Guolin Ke's avatar
Guolin Ke committed
61
      num_tree_per_iteration = objective->NumModelPerIteration();
Guolin Ke's avatar
Guolin Ke committed
62
63
      num_pred_per_row = objective->NumPredictOneRow();
    }
64
65
66
67
    if (objective != nullptr) {
      if (weights_ == nullptr) {
        #pragma omp parallel for schedule(static) reduction(+:sum_loss)
        for (data_size_t i = 0; i < num_data_; ++i) {
Guolin Ke's avatar
Guolin Ke committed
68
          std::vector<double> raw_score(num_tree_per_iteration);
69
70
          for (int k = 0; k < num_tree_per_iteration; ++k) {
            size_t idx = static_cast<size_t>(num_data_) * k + i;
Guolin Ke's avatar
Guolin Ke committed
71
            raw_score[k] = static_cast<double>(score[idx]);
72
          }
Guolin Ke's avatar
Guolin Ke committed
73
74
          std::vector<double> rec(num_pred_per_row);
          objective->ConvertOutput(raw_score.data(), rec.data());
75
          // add loss
Guolin Ke's avatar
Guolin Ke committed
76
          sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_);
77
78
79
80
        }
      } else {
        #pragma omp parallel for schedule(static) reduction(+:sum_loss)
        for (data_size_t i = 0; i < num_data_; ++i) {
Guolin Ke's avatar
Guolin Ke committed
81
          std::vector<double> raw_score(num_tree_per_iteration);
82
83
          for (int k = 0; k < num_tree_per_iteration; ++k) {
            size_t idx = static_cast<size_t>(num_data_) * k + i;
Guolin Ke's avatar
Guolin Ke committed
84
            raw_score[k] = static_cast<double>(score[idx]);
85
          }
Guolin Ke's avatar
Guolin Ke committed
86
87
          std::vector<double> rec(num_pred_per_row);
          objective->ConvertOutput(raw_score.data(), rec.data());
88
          // add loss
Guolin Ke's avatar
Guolin Ke committed
89
          sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_) * weights_[i];
90
91
92
        }
      }
    } else {
93
94
95
96
97
98
99
100
101
      if (weights_ == nullptr) {
        #pragma omp parallel for schedule(static) reduction(+:sum_loss)
        for (data_size_t i = 0; i < num_data_; ++i) {
          std::vector<double> rec(num_tree_per_iteration);
          for (int k = 0; k < num_tree_per_iteration; ++k) {
            size_t idx = static_cast<size_t>(num_data_) * k + i;
            rec[k] = static_cast<double>(score[idx]);
          }
          // add loss
Guolin Ke's avatar
Guolin Ke committed
102
          sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_);
103
104
105
106
107
108
109
110
111
112
        }
      } else {
        #pragma omp parallel for schedule(static) reduction(+:sum_loss)
        for (data_size_t i = 0; i < num_data_; ++i) {
          std::vector<double> rec(num_tree_per_iteration);
          for (int k = 0; k < num_tree_per_iteration; ++k) {
            size_t idx = static_cast<size_t>(num_data_) * k + i;
            rec[k] = static_cast<double>(score[idx]);
          }
          // add loss
Guolin Ke's avatar
Guolin Ke committed
113
          sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_) * weights_[i];
114
115
116
        }
      }
    }
117
118
    double loss = sum_loss / sum_weights_;
    return std::vector<double>(1, loss);
119
120
  }

Nikita Titov's avatar
Nikita Titov committed
121
 private:
122
123
124
  /*! \brief Number of data */
  data_size_t num_data_;
  /*! \brief Pointer of label */
125
  const label_t* label_;
126
  /*! \brief Pointer of weighs */
127
  const label_t* weights_;
128
  /*! \brief Sum weights */
129
  double sum_weights_;
130
  /*! \brief Name of this test set */
131
  std::vector<std::string> name_;
Guolin Ke's avatar
Guolin Ke committed
132
  int num_class_;
Belinda Trotta's avatar
Belinda Trotta committed
133
134
  /*! \brief config parameters*/
  Config config_;
135
136
};

Belinda Trotta's avatar
Belinda Trotta committed
137
/*! \brief top-k error for multiclass task; if k=1 (default) this is the usual multi-error */
138
class MultiErrorMetric: public MulticlassMetric<MultiErrorMetric> {
Nikita Titov's avatar
Nikita Titov committed
139
 public:
Guolin Ke's avatar
Guolin Ke committed
140
  explicit MultiErrorMetric(const Config& config) :MulticlassMetric<MultiErrorMetric>(config) {}
141

Guolin Ke's avatar
Guolin Ke committed
142
  inline static double LossOnPoint(label_t label, std::vector<double>* score, const Config& config) {
143
    size_t k = static_cast<size_t>(label);
Guolin Ke's avatar
Guolin Ke committed
144
    auto& ref_score = *score;
Belinda Trotta's avatar
Belinda Trotta committed
145
    int num_larger = 0;
Guolin Ke's avatar
Guolin Ke committed
146
    for (size_t i = 0; i < score->size(); ++i) {
Guolin Ke's avatar
Guolin Ke committed
147
      if (ref_score[i] >= ref_score[k]) ++num_larger;
Belinda Trotta's avatar
Belinda Trotta committed
148
      if (num_larger > config.multi_error_top_k) return 1.0f;
149
    }
Guolin Ke's avatar
Guolin Ke committed
150
    return 0.0f;
151
152
  }

Belinda Trotta's avatar
Belinda Trotta committed
153
  inline static const std::string Name(const Config& config) {
154
155
156
157
158
    if (config.multi_error_top_k == 1) {
      return "multi_error";
    } else {
      return "multi_error@" + std::to_string(config.multi_error_top_k);
    }
159
160
161
162
  }
};

/*! \brief Logloss for multiclass task */
Guolin Ke's avatar
Guolin Ke committed
163
class MultiSoftmaxLoglossMetric: public MulticlassMetric<MultiSoftmaxLoglossMetric> {
Nikita Titov's avatar
Nikita Titov committed
164
 public:
Guolin Ke's avatar
Guolin Ke committed
165
  explicit MultiSoftmaxLoglossMetric(const Config& config) :MulticlassMetric<MultiSoftmaxLoglossMetric>(config) {}
166

Guolin Ke's avatar
Guolin Ke committed
167
  inline static double LossOnPoint(label_t label, std::vector<double>* score, const Config&) {
168
    size_t k = static_cast<size_t>(label);
Guolin Ke's avatar
Guolin Ke committed
169
170
171
    auto& ref_score = *score;
    if (ref_score[k] > kEpsilon) {
      return static_cast<double>(-std::log(ref_score[k]));
172
173
174
175
    } else {
      return -std::log(kEpsilon);
    }
  }
176

Belinda Trotta's avatar
Belinda Trotta committed
177
  inline static const std::string Name(const Config&) {
178
    return "multi_logloss";
179
180
181
  }
};

Belinda Trotta's avatar
Belinda Trotta committed
182
183
/*! \brief Auc-mu for multiclass task*/
class AucMuMetric : public Metric {
184
 public:
Belinda Trotta's avatar
Belinda Trotta committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
  explicit AucMuMetric(const Config& config) : config_(config) {
    num_class_ = config.num_class;
    class_weights_ = config.auc_mu_weights_matrix;
  }

  virtual ~AucMuMetric() {}

  const std::vector<std::string>& GetName() const override { return name_; }

  double factor_to_bigger_better() const override { return 1.0f; }

  void Init(const Metadata& metadata, data_size_t num_data) override {
    name_.emplace_back("auc_mu");

    num_data_ = num_data;
    label_ = metadata.label();

    // sort the data indices by true class
    sorted_data_idx_ = std::vector<data_size_t>(num_data_, 0);
    for (data_size_t i = 0; i < num_data_; ++i) {
      sorted_data_idx_[i] = i;
    }
    Common::ParallelSort(sorted_data_idx_.begin(), sorted_data_idx_.end(),
      [this](data_size_t a, data_size_t b) { return label_[a] < label_[b]; });
  }

  std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
    // the notation follows that used in the paper introducing the auc-mu metric:
    // http://proceedings.mlr.press/v97/kleiman19a/kleiman19a.pdf

    // get size of each class
    auto class_sizes = std::vector<data_size_t>(num_class_, 0);
    for (data_size_t i = 0; i < num_data_; ++i) {
      data_size_t curr_label = static_cast<data_size_t>(label_[i]);
      ++class_sizes[curr_label];
    }

    auto S = std::vector<std::vector<double>>(num_class_, std::vector<double>(num_class_, 0));
    int i_start = 0;
    for (int i = 0; i < num_class_; ++i) {
      int j_start = i_start + class_sizes[i];
      for (int j = i + 1; j < num_class_; ++j) {
        std::vector<double> curr_v;
        for (int k = 0; k < num_class_; ++k) {
          curr_v.emplace_back(class_weights_[i][k] - class_weights_[j][k]);
        }
        double t1 = curr_v[i] - curr_v[j];
        // extract the data indices belonging to class i or j
        std::vector<data_size_t> class_i_j_indices;
        class_i_j_indices.assign(sorted_data_idx_.begin() + i_start, sorted_data_idx_.begin() + i_start + class_sizes[i]);
        class_i_j_indices.insert(class_i_j_indices.end(),
          sorted_data_idx_.begin() + j_start, sorted_data_idx_.begin() + j_start + class_sizes[j]);
        // sort according to distance from separating hyperplane
        std::vector<std::pair<data_size_t, double>> dist;
        for (data_size_t k = 0; static_cast<size_t>(k) < class_i_j_indices.size(); ++k) {
          data_size_t a = class_i_j_indices[k];
          double v_a = 0;
          for (int m = 0; m < num_class_; ++m) {
            v_a += curr_v[m] * score[num_data_ * m + a];
          }
          dist.push_back(std::pair<data_size_t, double>(a, t1 * v_a));
        }
        Common::ParallelSort(dist.begin(), dist.end(),
          [this](std::pair<data_size_t, double> a, std::pair<data_size_t, double> b) {
          // if scores are equal, put j class first
          if (std::fabs(a.second - b.second) < kEpsilon) {
            return label_[a.first] > label_[b.first];
252
          } else if (a.second < b.second) {
Belinda Trotta's avatar
Belinda Trotta committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
            return true;
          } else {
            return false;
          }
        });
        // calculate auc
        double num_j = 0;
        double last_j_dist = 0;
        double num_current_j = 0;
        for (size_t k = 0; k < dist.size(); ++k) {
          data_size_t a = dist[k].first;
          double curr_dist = dist[k].second;
          if (label_[a] == i) {
            if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
              S[i][j] += num_j - 0.5 * num_current_j;  // members of class j with same distance as a contribute 0.5
            } else {
              S[i][j] += num_j;
            }
          } else {
            ++num_j;
            if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
              ++num_current_j;
            } else {
              last_j_dist = dist[k].second;
              num_current_j = 1;
            }
          }
        }
        j_start += class_sizes[j];
      }
      i_start += class_sizes[i];
    }

    double ans = 0;
    for (int i = 0; i < num_class_; ++i) {
      for (int j = i + 1; j < num_class_; ++j) {
        ans += S[i][j] / (class_sizes[i] * class_sizes[j]);
      }
    }
    ans = 2 * ans / (num_class_ * (num_class_ - 1));
    return std::vector<double>(1, ans);
  }

296
 private:
Belinda Trotta's avatar
Belinda Trotta committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
  /*! \brief Number of data*/
  data_size_t num_data_;
  /*! \brief Pointer to label*/
  const label_t* label_;
  /*! \brief Name of this metric*/
  std::vector<std::string> name_;
  /*! \brief Number of classes*/
  int num_class_;
  /*! \brief class_weights*/
  std::vector<std::vector<double>> class_weights_;
  /*! \brief config parameters*/
  Config config_;
  /*! \brief index to data, sorted by true class*/
  std::vector<data_size_t> sorted_data_idx_;
};

313
314
}  // namespace LightGBM
#endif   // LightGBM_METRIC_MULTICLASS_METRIC_HPP_