multiclass_metric.hpp 12.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
  }
};

Andrew Ziem's avatar
Andrew Ziem committed
182
/*! \brief AUC mu for multiclass task*/
Belinda Trotta's avatar
Belinda Trotta committed
183
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
  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();

Belinda Trotta's avatar
Belinda Trotta committed
202
203
204
205
206
207
208
209
210
211
212
    // get weights
    weights_ = metadata.weights();
    if (weights_ == nullptr) {
      sum_weights_ = static_cast<double>(num_data_);
    } else {
      sum_weights_ = 0.0f;
      for (data_size_t i = 0; i < num_data_; ++i) {
        sum_weights_ += weights_[i];
      }
    }

Belinda Trotta's avatar
Belinda Trotta committed
213
214
215
216
217
218
219
220
221
    // 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]; });

    // get size of each class
Belinda Trotta's avatar
Belinda Trotta committed
222
    class_sizes_ = std::vector<data_size_t>(num_class_, 0);
Belinda Trotta's avatar
Belinda Trotta committed
223
224
    for (data_size_t i = 0; i < num_data_; ++i) {
      data_size_t curr_label = static_cast<data_size_t>(label_[i]);
Belinda Trotta's avatar
Belinda Trotta committed
225
      ++class_sizes_[curr_label];
Belinda Trotta's avatar
Belinda Trotta committed
226
227
    }

Belinda Trotta's avatar
Belinda Trotta committed
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    // get total weight of data in each class
    class_data_weights_ = std::vector<double>(num_class_, 0);
    if (weights_ != nullptr) {
      for (data_size_t i = 0; i < num_data_; ++i) {
        data_size_t curr_label = static_cast<data_size_t>(label_[i]);
        class_data_weights_[curr_label] += weights_[i];
      }
    }
  }

  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

Belinda Trotta's avatar
Belinda Trotta committed
242
243
244
    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) {
Belinda Trotta's avatar
Belinda Trotta committed
245
      int j_start = i_start + class_sizes_[i];
Belinda Trotta's avatar
Belinda Trotta committed
246
247
248
249
250
251
252
253
      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;
Belinda Trotta's avatar
Belinda Trotta committed
254
        class_i_j_indices.assign(sorted_data_idx_.begin() + i_start, sorted_data_idx_.begin() + i_start + class_sizes_[i]);
Belinda Trotta's avatar
Belinda Trotta committed
255
        class_i_j_indices.insert(class_i_j_indices.end(),
Belinda Trotta's avatar
Belinda Trotta committed
256
          sorted_data_idx_.begin() + j_start, sorted_data_idx_.begin() + j_start + class_sizes_[j]);
Belinda Trotta's avatar
Belinda Trotta committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
        // 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];
272
          } else if (a.second < b.second) {
Belinda Trotta's avatar
Belinda Trotta committed
273
274
275
276
277
            return true;
          } else {
            return false;
          }
        });
Andrew Ziem's avatar
Andrew Ziem committed
278
        // calculate AUC
Belinda Trotta's avatar
Belinda Trotta committed
279
280
281
        double num_j = 0;
        double last_j_dist = 0;
        double num_current_j = 0;
Belinda Trotta's avatar
Belinda Trotta committed
282
283
284
285
286
287
288
289
290
291
        if (weights_ == nullptr) {
          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;
              }
Belinda Trotta's avatar
Belinda Trotta committed
292
            } else {
Belinda Trotta's avatar
Belinda Trotta committed
293
294
295
296
297
298
299
              ++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;
              }
Belinda Trotta's avatar
Belinda Trotta committed
300
            }
Belinda Trotta's avatar
Belinda Trotta committed
301
302
303
304
305
306
307
308
309
310
311
312
          }
        } else {
          for (size_t k = 0; k < dist.size(); ++k) {
            data_size_t a = dist[k].first;
            double curr_dist = dist[k].second;
            double curr_weight = weights_[a];
            if (label_[a] == i) {
              if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
                S[i][j] += curr_weight * (num_j - 0.5 * num_current_j);  // members of class j with same distance as a contribute 0.5
              } else {
                S[i][j] += curr_weight * num_j;
              }
Belinda Trotta's avatar
Belinda Trotta committed
313
            } else {
Belinda Trotta's avatar
Belinda Trotta committed
314
315
316
317
318
319
320
              num_j += curr_weight;
              if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
                num_current_j += curr_weight;
              } else {
                last_j_dist = dist[k].second;
                num_current_j = curr_weight;
              }
Belinda Trotta's avatar
Belinda Trotta committed
321
322
323
            }
          }
        }
Belinda Trotta's avatar
Belinda Trotta committed
324
        j_start += class_sizes_[j];
Belinda Trotta's avatar
Belinda Trotta committed
325
      }
Belinda Trotta's avatar
Belinda Trotta committed
326
      i_start += class_sizes_[i];
Belinda Trotta's avatar
Belinda Trotta committed
327
328
329
330
    }
    double ans = 0;
    for (int i = 0; i < num_class_; ++i) {
      for (int j = i + 1; j < num_class_; ++j) {
Belinda Trotta's avatar
Belinda Trotta committed
331
332
333
334
335
        if (weights_ == nullptr) {
          ans += (S[i][j] / class_sizes_[i]) / class_sizes_[j];
        } else {
          ans += (S[i][j] / class_data_weights_[i]) / class_data_weights_[j];
        }
Belinda Trotta's avatar
Belinda Trotta committed
336
337
      }
    }
Belinda Trotta's avatar
Belinda Trotta committed
338
    ans = (2.0 * ans / num_class_) / (num_class_ - 1);
Guolin Ke's avatar
Guolin Ke committed
339
    return std::vector<double>(1, ans);
Belinda Trotta's avatar
Belinda Trotta committed
340
341
  }

342
 private:
Belinda Trotta's avatar
Belinda Trotta committed
343
344
345
346
347
348
349
350
  /*! \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_;
Belinda Trotta's avatar
Belinda Trotta committed
351
  /*! \brief Class auc-mu weights*/
Belinda Trotta's avatar
Belinda Trotta committed
352
  std::vector<std::vector<double>> class_weights_;
Belinda Trotta's avatar
Belinda Trotta committed
353
354
355
356
357
358
359
360
  /*! \brief Data weights */
  const label_t* weights_;
  /*! \brief Sum of data weights */
  double sum_weights_;
  /*! \brief Sum of data weights in each class*/
  std::vector<double> class_data_weights_;
  /*! \brief Number of data in each class*/
  std::vector<data_size_t> class_sizes_;
Belinda Trotta's avatar
Belinda Trotta committed
361
362
363
364
365
366
  /*! \brief config parameters*/
  Config config_;
  /*! \brief index to data, sorted by true class*/
  std::vector<data_size_t> sorted_data_idx_;
};

367
368
}  // namespace LightGBM
#endif   // LightGBM_METRIC_MULTICLASS_METRIC_HPP_