Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In the rank objective, lambdas and hessians need to factor sigmoid_ into the computation. #2322

Merged
merged 4 commits into from
Aug 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 4 additions & 15 deletions src/objective/rank_objective.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,6 @@ class LambdarankNDCG: public ObjectiveFunction {
}
std::stable_sort(sorted_idx.begin(), sorted_idx.end(),
[score](data_size_t a, data_size_t b) { return score[a] > score[b]; });
// get best and worst score
const double best_score = score[sorted_idx[0]];
data_size_t worst_idx = cnt - 1;
if (worst_idx > 0 && score[sorted_idx[worst_idx]] == kMinScore) {
worst_idx -= 1;
}
const double wrost_score = score[sorted_idx[worst_idx]];
// start accmulate lambdas by pairs
for (data_size_t i = 0; i < cnt; ++i) {
const data_size_t high = sorted_idx[i];
Expand Down Expand Up @@ -141,16 +134,12 @@ class LambdarankNDCG: public ObjectiveFunction {
const double paired_discount = fabs(high_discount - low_discount);
// get delta NDCG
double delta_pair_NDCG = dcg_gap * paired_discount * inverse_max_dcg;
// regular the delta_pair_NDCG by score distance
if (high_label != low_label && best_score != wrost_score) {
delta_pair_NDCG /= (0.01f + fabs(delta_score));
}
// calculate lambda for this pair
double p_lambda = GetSigmoid(delta_score);
double p_hessian = p_lambda * (2.0f - p_lambda);
double p_hessian = p_lambda * (1.0f - p_lambda);
// update
p_lambda *= -delta_pair_NDCG;
p_hessian *= 2 * delta_pair_NDCG;
p_lambda *= -sigmoid_ * delta_pair_NDCG;
p_hessian *= sigmoid_ * sigmoid_ * delta_pair_NDCG;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking about eliminating sigmoid_ here. That is, no sigmoid_ in p_lambda, and only sigmoid_ in p_hessian. What is your opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the LambdaMART (implicit) cost function has the following form:

log(1 + exp(-sigmoid_ * (s_i - s_j)))

The first derivative of the above has the sigmoid_ term -- so mathematically p_lambda in the code must have the sigmoid_ term in its computation. The second derivative will have sigmoid_^2 as proposed in the code change.

For more details, please see Section 7 of this paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.180.634&rep=rep1&type=pdf

high_sum_lambda += p_lambda;
high_sum_hessian += p_hessian;
lambdas[low] -= static_cast<score_t>(p_lambda);
Expand Down Expand Up @@ -193,7 +182,7 @@ class LambdarankNDCG: public ObjectiveFunction {
// cache
for (size_t i = 0; i < _sigmoid_bins; ++i) {
const double score = i / sigmoid_table_idx_factor_ + min_sigmoid_input_;
sigmoid_table_[i] = 2.0f / (1.0f + std::exp(2.0f * score * sigmoid_));
sigmoid_table_[i] = 1.0f / (1.0f + std::exp(score * sigmoid_));
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/python_package_test/test_sklearn.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def test_lambdarank(self):
eval_group=[q_test], eval_at=[1, 3], early_stopping_rounds=5, verbose=False,
callbacks=[lgb.reset_parameter(learning_rate=lambda x: 0.95 ** x * 0.1)])
self.assertLessEqual(gbm.best_iteration_, 12)
self.assertGreater(gbm.best_score_['valid_0']['ndcg@1'], 0.65)
self.assertGreater(gbm.best_score_['valid_0']['ndcg@3'], 0.65)
self.assertGreater(gbm.best_score_['valid_0']['ndcg@1'], 0.6173)
self.assertGreater(gbm.best_score_['valid_0']['ndcg@3'], 0.6479)

def test_regression_with_custom_objective(self):
def objective_ls(y_true, y_pred):
Expand Down