Eine der Funktionen von LightGBM, einer beliebten Bibliothek in Kaggle, ist die gute Optimierung, indem der Gradient der Zielfunktion angegeben wird, den Sie minimieren möchten. Die richtige Auswahl dieser Zielfunktion ist einer der Tricks, um ein gutes Modell zu erstellen, und standardmäßig ist viele Ziele implementiert. Mit LightGBM können Sie jedoch eine Funktion übergeben, um den Gradienten in Python zu berechnen. In diesem Artikel möchte ich vorstellen, welche Art der Implementierung durchgeführt wird, indem ein gleichwertiges Ziel mit Python erstellt wird, während auf die offizielle Implementierung von LightGBM verwiesen wird.
In diesem Artikel wird davon ausgegangen, dass lgb.Dataset für das Training in Regression / Zwei-Klassen-Klassifizierung an lgb.train übergeben wird. Bei der Erstellung eines Ziels für die Klassifizierung mehrerer Klassen [Mr. Tawaras Artikel (zwangsweise Multi-Task-Regression (ha ???) mit LightGBM durchführen)](https://tawara.hatenablog.com/entry/2020/05/ 14/120016) Ich denke, es ist leicht zu verstehen, wenn Sie herumlesen. Ich bin mir auch nicht sicher, warum ich grad und hess will, also verweise bitte auf andere Materialien dazu.
Beginnt das Hauptthema. Da der Kernteil von lightGBM zur Beschleunigung in C ++ implementiert ist, ist der Zielteil auch in C ++ geschrieben. Lesen Sie den Code (https://github.com/microsoft/LightGBM/blob/master/src/objective/regression_objective.hpp) für das Verhalten bei Objective = "l2". Der Teil, der den Gradienten berechnet, ist in GetGradients () implementiert.
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]);
}
}
}
Es ist nicht praktisch, weil es langsam sein wird, aber wenn Sie dies mit Python reproduzieren, wird es so sein.
def l2_loss(pred, data):
true = data.get_label()
grad = pred - true
hess = np.ones(len(grad))
return grad, hess
Dieses Ziel minimiert den Poissonverlust. Wenn ich die Metrik über Poisson-Verlust lese, ist sie wie folgt.
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";
}
};
Und wenn ich objektiv lese, hat es die folgende Implementierung.
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]);
}
}
}
... Hast du bemerkt? Tatsächlich ist die Punktzahl dieses Ziels nicht der vorhergesagte Wert, wie er ist, sondern der Wert des Exponententeils x von e, wenn er durch Punktzahl = e ^ x dargestellt wird, ist enthalten. Versuchen Sie, die Formel in Wolfram Alpha einzugeben Du wirst verstehen. Daher muss bei der Erstellung eines Ziels vom Poisson-Typ (andere wie Gamma und Tweedie) mit Ziel auch die Metrik mit dem vorhergesagten Wert = e ^ (pred) berechnet werden.
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
Ich würde das Ziel zum Zeitpunkt der Niclas-Klassifizierung gerne sehen, indem ich es schlecht treibe. Die Metrik zum Zeitpunkt der Binärdatei ist wie folgt.
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";
}
};
Beachten Sie, dass das Ziel sigmoid = 1, label_val = [-1, 1], label_weights = [1, 1] ist, wenn 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]);
}
}
}
Wie im Fall von Poisson wird die Punktzahl als Wert = Sigmoid (Punktzahl) vorhergesagt, so dass der Gradient wie folgt ist. Überprüfen mit Wolfram Alpha wie zuvor [wenn label = 0](https://ja.wolframalpha.com/input/?i=d%2Fdx+log%281+-+%281%2F%281+%2B + e% 5E% 28-x% 29% 29% 29% 29), wenn label = 1 % 2F% 281 +% 2B + e% 5E% 28-x% 29% 29% 29% 29) Wenn Sie also ein Ziel in Python schreiben, ist dies wie folgt.
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
Dieses Mal habe ich die offizielle Implementierung von lightGBM mit Python reproduziert. Wenn Sie die diesmal eingeführten Grundlagen verstehen, können Sie leichter Ihr eigenes benutzerdefiniertes Ziel erstellen. Ich möchte das Ziel, das ich im Wettbewerb umgesetzt habe, in einem anderen Artikel vorstellen. Bitte zögern Sie nicht, mich zu kontaktieren.
Recommended Posts