91
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LightGBMのweightの使い方、weightは一体何を行っているのか

Last updated at Posted at 2020-05-10

簡潔に

  • LightGBMのパラメータであるweightの実装方法と、何を行っているのかを説明した。
  • ざっくり言うと、各行に重みを与え、重みが大きい行が学習時に重視されるようになる。
  • 詳しく言うと、weightは学習時のgradiant,hessian,lossの変化に影響を与え、weightが大きい行が木の分岐点決定において重視されるようになる。
  • H2O.aiのサイトでは、weightが2だった場合は、該当の行を複製(2行に増やす)ことと同じです。と説明されている。

初めに

Kaggleで大人気のLightGBMにはweightというパラメータが存在します。上位ソリューションでは、このパラメータを使った解法が比較的多いと感じていますが、weightを日本語で解説している記事があまり多くなかったため、説明記事を自分で作成しました。

weightの使い方

使い方を先に記載します。コードの下記部分に追加を行います。
コードは公式Documentより引用してます。

公式の例
w = np.random.rand(500, )
train_data = lgb.Dataset(data, label=label, weight=w)#ここ

上記の例ではnumpyの1-D arrayでweightを掛けていますが、
pandasのseriesかlistでも問題ありません。その説明は、github上のここに記載があります。

公式の説明
 weight : list, numpy 1-D array, pandas Series or None
            Weight to be set for each data point.

個人的には、dataframeにweightというcolumnを追加し、
下記のようにした方が分かりやすいと思います。

自分の使い方
lgb_train = lgb.Dataset(
    X_train.drop("weight", axis=1), #学習には用いないためweightを落とす
    y_train, 
    weight=X_train["weight"] #ここ
)

lgb_eval = lgb.Dataset(
    X_valid.drop("weight", axis=1), #同様にweightを落とす
    y_valid, 
    reference=lgb_train, 
    weight=X_valid["weight"] #ここ
)

たまにlgb_evalにはweightを記載しない例を見ますが、下記公式の例にてvalidにも記載しているため、lgb_evalにもweightは必要かと思います。

公式の例
lgb_train = lgb.Dataset(X_train, y_train,
                        weight=W_train, free_raw_data=False)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train,
                       weight=W_test, free_raw_data=False)

weight とは

まずは、公式の説明を探してみましょう。
ここでweightの説明があります。

image.png

まとめると

  • LightGBMは重み付け学習が可能だよ
  • 追加ファイルを使う場合は、重みファイルをdata fileと同じフォルダに置いてね。(今回こちらの使い方は触れません。)
  • パラメータweight_columnで指定することもできるよ。(この使い方も触れません。)

という感じでした。

weightの使い方は説明してくれていますが、そもそもweightが何をしているのかは触れてないですね・・・🤔(公式documentのどこかで説明あるんですかね?ご存知の方いたら教えてください。)

というわけで、ここから先は自身で調べたweightの意味合いを記載していきます。

weightが行っていること

ざっくりした理解

ざっくりな説明をすると、weightが大きい行をより重視し、weightが小さい行はあまり重視しなくなります。

もし下記のようなweightの掛け方をした場合、2行目(性別が女性)がより重視されます。何かしらの理由で女性のデータが重要な場合に使えますね。

Sex Height weight
Male 180 0.3
Female 160 0.7
Male 175 0.3

しっかりした理解

「重視とは、具体的に何をしているのだろうか・・・🤔」となると思うので、ここからはgithub上でのLigthGBMのコードや数式を用いて説明を行います。(kaggler-jaの私の質問に対する回答をベースにしています。ご回答ありがとうございます。)

まずweightを使っているコードは下記部分となります。

basic.py_L1486~
def set_weight(self, weight):
        """Set weight of each instance.
        Parameters
        ----------
        weight : list, numpy 1-D array, pandas Series or None
            Weight to be set for each data point.
        Returns
        -------
        self : Dataset
            Dataset with set weight.
        """
        if weight is not None and np.all(weight == 1):
            weight = None
        self.weight = weight
        if self.handle is not None and weight is not None:
            weight = list_to_1d_numpy(weight, name='weight')
            self.set_field('weight', weight)
            self.weight = self.get_field('weight')  # original values can be modified at cpp side
        return self

self.weight = self.get_field('weight') # original values can be modified at cpp side
とあるため、weightの計算はcppファイルにて行われているようです。

このcppファイルですがobjective_function.cppが(多分)該当し、このファイルは同じフォルダにおいてあるhppファイルを参照しにいきます。そのため、次はweightを使用しているhppファイルの一つであるregression_objective.hppを見にいきましょう。

本ファイル内には、下記のような行が存在します。

regression_objective.hpp_L372~
else {
      #pragma omp parallel for schedule(static)
      for (data_size_t i = 0; i < num_data_; ++i) {
        const double diff = score[i] - label_[i];
        if (std::abs(diff) <= alpha_) {
          gradients[i] = static_cast<score_t>(diff * weights_[i]);
        } else {
          gradients[i] = static_cast<score_t>(Common::Sign(diff) * weights_[i] * alpha_);
        }
        hessians[i] = static_cast<score_t>(weights_[i]);
      }
    }

gradients[i] = static_cast<score_t>(diff * weights_[i]);
この行ではgradientにweightをかけています。

hessians[i] = static_cast<score_t>(weights_[i]);
この行ではhessianにweightをかけています。

つまり、LightGBMの学習時に用いるgradientとhessianにweightが掛けられることが分かりました。

「gradientとhessianにweightがかかる・・・つまりどういうことだ・・・🤔」と私はなったゆえ、追加で説明していきます。
(ここからはあまり自信がありません。間違いありましたら指摘お願いします。)

まず、LightGBMは各特徴量の分岐点を決定する際、XGBoostと同様の方法でlossの計算を行っていると思われます。木を構築する際、分岐後のlossが最も小さくなるような分岐点を選びます。

JackさんによるXGBoostの解説スライドの式をお借りすると、下記のような式でlossの計算を行っています。

$ΔL$は分岐前後のlossの変化であり,
$g_i$がgradient, $h_i$がhessianです。
また$Σ$はデータ各行の総和であることを意味します。
($λ$は正則化項のパラメータです。今回は関係ありません。)

つまり、gradientとhessianにweightをかけると、

  • weightが大きいデータに対しては、gradientとhessianが同様に大きくなる。分子のgradientは二乗されているため、$ΔL$は負の方向に大きくなる。(=分岐後のlossがより小さくなる。)
  • LightGBMは、分岐後のlossがより小さくなる方へデータの分岐点を決定する。
  • よって、weightが大きいデータのlossが下がる方へ、分岐点が決定されやすくなる。

上記より、
weightが大きい行がlossの変化に影響を与え、木の分岐点決定においてより重視されるようになることが分かりました。

おまけ 

機械学習プラットフォームであるH2O.aiのサイトの説明にはweights_columnの説明が下記のようになっています。

For example, a weight of 2 is identical to duplicating a row.

意味としては
weightが2だった場合は、該当の行を複製(2行に増やす)ことと同じです。
という感じですね。

確かにgradientとhessianの考え方を当てはめると、上記のように理解して問題なさそうです。直感的で分かりやすいですね。

最後に

疑問点、おかしい部分などあれば遠慮なく教えてください。

91
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
91
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?