L'une des fonctionnalités de LightGBM, une bibliothèque populaire de kaggle, est qu'elle s'optimise bien en donnant le gradient de la fonction objectif que vous souhaitez minimiser. Choisir correctement cette fonction d'objectif est l'une des astuces pour faire un bon modèle, et par défaut plusieurs objectifs est implémenté. Cependant, LightGBM vous permet de passer une fonction pour calculer le dégradé en python. Dans cet article, je voudrais présenter le type d'implémentation effectué en créant un objectif équivalent avec python tout en faisant référence à l'implémentation officielle de LightGBM.
Dans cet article, on suppose que lgb.Dataset est passé à lgb.train pour l'entraînement à la régression / classification à deux classes. Lors de la création d'un objectif de classification multi-classes, [l'article de M. Tawara (effectuer une régression multi-tâches (ha ???) de force avec LightGBM)](https://tawara.hatenablog.com/entry/2020/05/ 14/120016) Je pense que c'est facile à comprendre si vous lisez. De plus, je ne sais pas pourquoi je veux un diplôme et un hess, alors veuillez vous référer à d'autres documents à ce sujet.
Commencera le sujet principal. Puisque la partie principale de lightGBM est implémentée en C ++ pour accélérer, la partie Objective est également écrite en C ++. Lisez le code (https://github.com/microsoft/LightGBM/blob/master/src/objective/regression_objective.hpp) pour le comportement lorsque objective = "l2". La partie qui calcule le dégradé est implémentée dans GetGradients ().
cpp:github.com/microsoft/LightGBM/blob/master/src/objective/regression_objective.hpp
void GetGradients(const double* score, score_t* gradients,
score_t* hessians) const override {
if (weights_ == nullptr) {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
gradients[i] = static_cast<score_t>(score[i] - label_[i]);
hessians[i] = 1.0f;
}
} else {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
gradients[i] = static_cast<score_t>((score[i] - label_[i]) * weights_[i]);
hessians[i] = static_cast<score_t>(weights_[i]);
}
}
}
Ce n'est pas pratique car ce sera lent, mais si vous reproduisez cela avec python, ce sera comme ça.
def l2_loss(pred, data):
true = data.get_label()
grad = pred - true
hess = np.ones(len(grad))
return grad, hess
Cet objectif minimise la perte de poisson. Quand je lis la métrique sur la perte de poisson, c'est comme suit.
cpp:github.com/microsoft/LightGBM/blob/master/src/metric/regression_metric.hpp
class PoissonMetric: public RegressionMetric<PoissonMetric> {
public:
explicit PoissonMetric(const Config& config) :RegressionMetric<PoissonMetric>(config) {
}
inline static double LossOnPoint(label_t label, double score, const Config&) {
const double eps = 1e-10f;
if (score < eps) {
score = eps;
}
return score - label * std::log(score);
}
inline static const char* Name() {
return "poisson";
}
};
Et quand je lis objectif, il a la mise en œuvre suivante.
cpp:github.com/microsoft/LightGBM/blob/master/src/objective/regression_objective.hpp
void GetGradients(const double* score, score_t* gradients,
score_t* hessians) const override {
if (weights_ == nullptr) {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
gradients[i] = static_cast<score_t>(std::exp(score[i]) - label_[i]);
hessians[i] = static_cast<score_t>(std::exp(score[i] + max_delta_step_));
}
} else {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
gradients[i] = static_cast<score_t>((std::exp(score[i]) - label_[i]) * weights_[i]);
hessians[i] = static_cast<score_t>(std::exp(score[i] + max_delta_step_) * weights_[i]);
}
}
}
... as-tu remarqué? En fait, le score de cet objectif n'est pas la valeur prédite telle quelle, mais la valeur de la partie exposante x de e lorsqu'elle est exprimée par score = e ^ x. Essayez d'entrer la formule dans Wolfram Alpha Tu comprendras. Par conséquent, lors de la création d'un objectif de type poisson (d'autres comme gamma et tweedie) avec objectif, la métrique doit également être calculée avec la valeur prédite = e ^ (pred).
def poisson_metric(pred, data):
true = data.get_label()
loss = np.exp(pred) - true*pred
return "poisson", np.mean(loss), False
def poisson_object(pred, data):
poisson_max_delta_step = 0.7
true = data.get_label()
grad = np.exp(pred) - true
hess = exp(pred + poisson_max_delta_step)
return grad, hess
J'aimerais voir l'objectif au moment du classement de Niclas en poussant mal. La métrique au moment du binaire est la suivante.
cpp:github.com/microsoft/LightGBM/blob/master/src/metric/binary_metric.hpp
class BinaryLoglossMetric: public BinaryMetric<BinaryLoglossMetric> {
public:
explicit BinaryLoglossMetric(const Config& config) :BinaryMetric<BinaryLoglossMetric>(config) {}
inline static double LossOnPoint(label_t label, double prob) {
if (label <= 0) {
if (1.0f - prob > kEpsilon) {
return -std::log(1.0f - prob);
}
} else {
if (prob > kEpsilon) {
return -std::log(prob);
}
}
return -std::log(kEpsilon);
}
inline static const char* Name() {
return "binary_logloss";
}
};
Notez que l'objectif est sigmoid = 1, label_val = [-1, 1], label_weights = [1, 1] lorsque is_unbalance = False.
cpp:github.com/microsoft/LightGBM/blob/master/src/objective/binary_objective.hpp
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
if (!need_train_) {
return;
}
if (weights_ == nullptr) {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
// get label and label weights
const int is_pos = is_pos_(label_[i]);
const int label = label_val_[is_pos];
const double label_weight = label_weights_[is_pos];
// calculate gradients and hessians
const double response = -label * sigmoid_ / (1.0f + std::exp(label * sigmoid_ * score[i]));
const double abs_response = fabs(response);
gradients[i] = static_cast<score_t>(response * label_weight);
hessians[i] = static_cast<score_t>(abs_response * (sigmoid_ - abs_response) * label_weight);
}
} else {
#pragma omp parallel for schedule(static)
for (data_size_t i = 0; i < num_data_; ++i) {
// get label and label weights
const int is_pos = is_pos_(label_[i]);
const int label = label_val_[is_pos];
const double label_weight = label_weights_[is_pos];
// calculate gradients and hessians
const double response = -label * sigmoid_ / (1.0f + std::exp(label * sigmoid_ * score[i]));
const double abs_response = fabs(response);
gradients[i] = static_cast<score_t>(response * label_weight * weights_[i]);
hessians[i] = static_cast<score_t>(abs_response * (sigmoid_ - abs_response) * label_weight * weights_[i]);
}
}
}
Comme dans le cas de poisson, le score est prédit valeur = sigmoïde (score), donc le gradient est comme ça. Vérification avec Wolfram Alpha comme avant [when label = 0](https://ja.wolframalpha.com/input/?i=d%2Fdx+log%281+-+%281%2F%281+%2B + e% 5E% 28-x% 29% 29% 29% 29), quand étiquette = 1 % 2F% 281 +% 2B + e% 5E% 28-x% 29% 29% 29% 29), donc si vous écrivez objectif en python, ce sera comme suit.
def binary_metric(pred, data):
true = data.get_label()
loss = -(true * np.log(1/(1+np.exp(-pred))) + (1 - true) * np.log(1 - 1/(1+np.exp(-pred))))
return "binary", np.mean(loss), False
def binary_objective(pred, data):
true = data.get_label()
label = 2*true - 1
response = -label / (1 + np.exp(label * pred))
abs_response = np.abs(response)
grad = response
hess = abs_response * (1 - abs_response)
return grad, hess
Cette fois, j'ai reproduit l'implémentation officielle de lightGBM avec python. Comprendre les bases introduites cette fois facilitera la création de votre propre objectif personnalisé. Je voudrais vous présenter l'objectif que j'ai mis en œuvre dans le concours dans un autre article, alors n'hésitez pas à me contacter.
Recommended Posts