Medial Code Documentation
Loading...
Searching...
No Matches
rank_objective.hpp
1#ifndef LIGHTGBM_OBJECTIVE_RANK_OBJECTIVE_HPP_
2#define LIGHTGBM_OBJECTIVE_RANK_OBJECTIVE_HPP_
3
4#include <LightGBM/objective_function.h>
5#include <LightGBM/metric.h>
6
7#include <cstdio>
8#include <cstring>
9#include <cmath>
10
11#include <vector>
12#include <algorithm>
13#include <limits>
14
15namespace LightGBM {
20public:
21 explicit LambdarankNDCG(const Config& config) {
22 sigmoid_ = static_cast<double>(config.sigmoid);
23 label_gain_ = config.label_gain;
24 // initialize DCG calculator
25 DCGCalculator::DefaultLabelGain(&label_gain_);
26 DCGCalculator::Init(label_gain_);
27 // will optimize NDCG@optimize_pos_at_
28 optimize_pos_at_ = config.max_position;
29 sigmoid_table_.clear();
30 inverse_max_dcgs_.clear();
31 if (sigmoid_ <= 0.0) {
32 Log::Fatal("Sigmoid param %f should be greater than zero", sigmoid_);
33 }
34 }
35
36 explicit LambdarankNDCG(const std::vector<std::string>&) {
37 }
38
40 }
41 void Init(const Metadata& metadata, data_size_t num_data) override {
42 num_data_ = num_data;
43 // get label
44 label_ = metadata.label();
45 DCGCalculator::CheckLabel(label_, num_data_);
46 // get weights
47 weights_ = metadata.weights();
48 // get boundries
49 query_boundaries_ = metadata.query_boundaries();
50 if (query_boundaries_ == nullptr) {
51 Log::Fatal("Lambdarank tasks require query information");
52 }
53 num_queries_ = metadata.num_queries();
54 // cache inverse max DCG, avoid computation many times
55 inverse_max_dcgs_.resize(num_queries_);
56#pragma omp parallel for schedule(static)
57 for (data_size_t i = 0; i < num_queries_; ++i) {
58 inverse_max_dcgs_[i] = DCGCalculator::CalMaxDCGAtK(optimize_pos_at_,
59 label_ + query_boundaries_[i],
60 query_boundaries_[i + 1] - query_boundaries_[i]);
61
62 if (inverse_max_dcgs_[i] > 0.0) {
63 inverse_max_dcgs_[i] = 1.0f / inverse_max_dcgs_[i];
64 }
65 }
66 // construct sigmoid table to speed up sigmoid transform
67 ConstructSigmoidTable();
68 }
69
70 void GetGradients(const double* score, score_t* gradients,
71 score_t* hessians) const override {
72 #pragma omp parallel for schedule(guided)
73 for (data_size_t i = 0; i < num_queries_; ++i) {
74 GetGradientsForOneQuery(score, gradients, hessians, i);
75 }
76 }
77
78 inline void GetGradientsForOneQuery(const double* score,
79 score_t* lambdas, score_t* hessians, data_size_t query_id) const {
80 // get doc boundary for current query
81 const data_size_t start = query_boundaries_[query_id];
82 const data_size_t cnt =
83 query_boundaries_[query_id + 1] - query_boundaries_[query_id];
84 // get max DCG on current query
85 const double inverse_max_dcg = inverse_max_dcgs_[query_id];
86 // add pointers with offset
87 const label_t* label = label_ + start;
88 score += start;
89 lambdas += start;
90 hessians += start;
91 // initialize with zero
92 for (data_size_t i = 0; i < cnt; ++i) {
93 lambdas[i] = 0.0f;
94 hessians[i] = 0.0f;
95 }
96 // get sorted indices for scores
97 std::vector<data_size_t> sorted_idx;
98 for (data_size_t i = 0; i < cnt; ++i) {
99 sorted_idx.emplace_back(i);
100 }
101 std::stable_sort(sorted_idx.begin(), sorted_idx.end(),
102 [score](data_size_t a, data_size_t b) { return score[a] > score[b]; });
103 // get best and worst score
104 const double best_score = score[sorted_idx[0]];
105 data_size_t worst_idx = cnt - 1;
106 if (worst_idx > 0 && score[sorted_idx[worst_idx]] == kMinScore) {
107 worst_idx -= 1;
108 }
109 const double wrost_score = score[sorted_idx[worst_idx]];
110 // start accmulate lambdas by pairs
111 for (data_size_t i = 0; i < cnt; ++i) {
112 const data_size_t high = sorted_idx[i];
113 const int high_label = static_cast<int>(label[high]);
114 const double high_score = score[high];
115 if (high_score == kMinScore) { continue; }
116 const double high_label_gain = label_gain_[high_label];
117 const double high_discount = DCGCalculator::GetDiscount(i);
118 double high_sum_lambda = 0.0;
119 double high_sum_hessian = 0.0;
120 for (data_size_t j = 0; j < cnt; ++j) {
121 // skip same data
122 if (i == j) { continue; }
123
124 const data_size_t low = sorted_idx[j];
125 const int low_label = static_cast<int>(label[low]);
126 const double low_score = score[low];
127 // only consider pair with different label
128 if (high_label <= low_label || low_score == kMinScore) { continue; }
129
130 const double delta_score = high_score - low_score;
131
132 const double low_label_gain = label_gain_[low_label];
133 const double low_discount = DCGCalculator::GetDiscount(j);
134 // get dcg gap
135 const double dcg_gap = high_label_gain - low_label_gain;
136 // get discount of this pair
137 const double paired_discount = fabs(high_discount - low_discount);
138 // get delta NDCG
139 double delta_pair_NDCG = dcg_gap * paired_discount * inverse_max_dcg;
140 // regular the delta_pair_NDCG by score distance
141 if (high_label != low_label && best_score != wrost_score) {
142 delta_pair_NDCG /= (0.01f + fabs(delta_score));
143 }
144 // calculate lambda for this pair
145 double p_lambda = GetSigmoid(delta_score);
146 double p_hessian = p_lambda * (2.0f - p_lambda);
147 // update
148 p_lambda *= -delta_pair_NDCG;
149 p_hessian *= 2 * delta_pair_NDCG;
150 high_sum_lambda += p_lambda;
151 high_sum_hessian += p_hessian;
152 lambdas[low] -= static_cast<score_t>(p_lambda);
153 hessians[low] += static_cast<score_t>(p_hessian);
154 }
155 // update
156 lambdas[high] += static_cast<score_t>(high_sum_lambda);
157 hessians[high] += static_cast<score_t>(high_sum_hessian);
158 }
159 // if need weights
160 if (weights_ != nullptr) {
161 for (data_size_t i = 0; i < cnt; ++i) {
162 lambdas[i] = static_cast<score_t>(lambdas[i] * weights_[start + i]);
163 hessians[i] = static_cast<score_t>(hessians[i] * weights_[start + i]);
164 }
165 }
166 }
167
168
169 inline double GetSigmoid(double score) const {
170 if (score <= min_sigmoid_input_) {
171 // too small, use lower bound
172 return sigmoid_table_[0];
173 } else if (score >= max_sigmoid_input_) {
174 // too big, use upper bound
175 return sigmoid_table_[_sigmoid_bins - 1];
176 } else {
177 return sigmoid_table_[static_cast<size_t>((score - min_sigmoid_input_) * sigmoid_table_idx_factor_)];
178 }
179 }
180
181 void ConstructSigmoidTable() {
182 // get boundary
183 min_sigmoid_input_ = min_sigmoid_input_ / sigmoid_ / 2;
184 max_sigmoid_input_ = -min_sigmoid_input_;
185 sigmoid_table_.resize(_sigmoid_bins);
186 // get score to bin factor
187 sigmoid_table_idx_factor_ =
188 _sigmoid_bins / (max_sigmoid_input_ - min_sigmoid_input_);
189 // cache
190 for (size_t i = 0; i < _sigmoid_bins; ++i) {
191 const double score = i / sigmoid_table_idx_factor_ + min_sigmoid_input_;
192 sigmoid_table_[i] = 2.0f / (1.0f + std::exp(2.0f * score * sigmoid_));
193 }
194 }
195
196 const char* GetName() const override {
197 return "lambdarank";
198 }
199
200 std::string ToString() const override {
201 std::stringstream str_buf;
202 str_buf << GetName();
203 return str_buf.str();
204 }
205
206 bool NeedAccuratePrediction() const override { return false; }
207
208private:
210 std::vector<double> label_gain_;
212 std::vector<double> inverse_max_dcgs_;
214 double sigmoid_;
216 int optimize_pos_at_;
218 data_size_t num_queries_;
220 data_size_t num_data_;
222 const label_t* label_;
224 const label_t* weights_;
226 const data_size_t* query_boundaries_;
228 std::vector<double> sigmoid_table_;
230 size_t _sigmoid_bins = 1024 * 1024;
232 double min_sigmoid_input_ = -50;
234 double max_sigmoid_input_ = 50;
236 double sigmoid_table_idx_factor_;
237};
238
239} // namespace LightGBM
240#endif // LightGBM_OBJECTIVE_RANK_OBJECTIVE_HPP_
static void CheckLabel(const label_t *label, data_size_t num_data)
Check the label range for NDCG and lambdarank.
Definition dcg_calculator.cpp:152
static void Init(const std::vector< double > &label_gain)
Initial logic.
Definition dcg_calculator.cpp:40
static double GetDiscount(data_size_t k)
Get discount score of position k.
Definition metric.h:124
static double CalMaxDCGAtK(data_size_t k, const label_t *label, data_size_t num_data)
Calculate the Max DCG score at position k.
Definition dcg_calculator.cpp:51
Objective function for Lambdrank with NDCG.
Definition rank_objective.hpp:19
void Init(const Metadata &metadata, data_size_t num_data) override
Initialize.
Definition rank_objective.hpp:41
void GetGradients(const double *score, score_t *gradients, score_t *hessians) const override
calculating first order derivative of loss function
Definition rank_objective.hpp:70
bool NeedAccuratePrediction() const override
The prediction should be accurate or not. True will disable early stopping for prediction.
Definition rank_objective.hpp:206
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 data_size_t * query_boundaries() const
Get data boundaries on queries, if not exists, will return nullptr we assume data will order by query...
Definition dataset.h:161
const label_t * weights() const
Get weights, if not exists, will return nullptr.
Definition dataset.h:146
data_size_t num_queries() const
Get Number of queries.
Definition dataset.h:173
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 score_t
Type of score, and gradients.
Definition meta.h:26
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