ブレンディングの重み決定手法
自然言語処理
Lastmod: 2023-10-09

ブレンディングとは複数のモデルで予測した結果を合わせることでより良い結果を作り出すことです。

(モデル1の結果) * 0.3 + (モデル2の結果) * 0.4 + (モデル3の結果) * 0.3

実際には上のようにして新しい予測結果を作ります。

ここで一番重要なのがこの重みの決め方です。この重みの決め方を実装します。

重みの決め方

ブレンディングでは検証用データを使って重みを決定することが多いです。いろいろ方法はありますが今回は一番実装が楽な全探索を使います。他にもoptunaを使う方法や線型計画法による方法もあります。

  1. 各モデルを生成する際にバリデーション用データとその予測を保存しておく(バリデーションデータは全てのモデルで同じになるようにする)
  2. 訓練データからバリデーションに使ったデータの正解を取得する
  3. 各バリデーションデータに適当な重みをかけ合わせて和を求め、正解データと比較する
  4. 最適な重みを全探索する。全ての重みを0.01ずつ足していき一番不正解数が少ない組み合わせを求める。この時重みの和が1になるようにする
  5. 提出データに算出した重みをかけ合わせて新しい提出データにする

多クラス分類を想定しているので正解データとの比較は不正解数の最小化を方針としています。

import sys

import pandas as pd
import numpy as np

model1 = pd.read_csv('validation1.csv')
model2 = pd.read_csv('validation2.csv')
model3 = pd.read_csv('validation3.csv')

ans = pd.read_csv('validation_answer.csv')

submit_model1 = pd.read_csv('submit1.csv', names=('id', 'label'))
submit_model2 = pd.read_csv('submit2.csv', names=('id', 'label'))
submit_model3 = pd.read_csv('submit3.csv', names=('id', 'label'))

correlation1 = model1.label.corr(model2.label)
correlation2 = model1.label.corr(model3.label)
correlation3 = model3.label.corr(model2.label)
print(correlation1, correlation2, correlation3)

step = 0.010
minimum = sys.maxsize
max_f1 = -1

result = []

for w1 in np.arange(0.1, 1.0, step):
    for w2 in np.arange(0.1, 1.0, step):
        w3 = 1 - w1 - w2 # 重みの和を1にする
        if w3 >= 0:
            pred = np.round(w1 * model1.label + w2 * model2.label + w3 * model3.label).astype('int')
            num_of_false = sum(ans['label'] != pred) # 不正解の数
            if num_of_false <= minimum:
                minimum = num_of_false
                result.append(w1)
                result.append(w2)
                result.append(w3)

print(sum(ans['label'] == np.round(min_w1 * model1.label + min_w2 * model2.label + min_w3 * model3.label).astype('int')))
print(sum(ans['label'] != np.round(min_w1 * model1.label + min_w2 * model2.label + min_w3 * model3.label).astype('int')))

labels = np.round(min_w1 * submit_model1.label + min_w2 * submit_model2.label + min_w3 * submit_model3.label).astype('int')
result_df = pd.DataFrame()
result_df['id'] = submit_model1.id
result_df['label'] = pd.Series(labels)
result_df.to_csv('blending_result.csv', index=False, header=False)

全探索すると計算量がO((ステップ数の逆数)^(モデル数))となって遅いのでモデル4つ程度が限界かと思います。この手法だとモデル5つになると1-2日かかる覚悟が必要です。

ブレンディングへの賛否

Kaggleにおいてしばしば「モデルに関する知識もなくKernelに載っている結果だけ拾ってきてblendingするのはいかがなものか?」という議論がなされることがあります。ブレンディングによって結果が良くなることもあるので理不尽を感じるとのこと。

those things are part of life

これに対して強い人はKernel上で「これもまた人生」って言ってます。

(おまけ) バリデーションデータの分割

バリデーションデータの作成は以下のようにStratifiedKFoldを使うと便利です。

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
train['fold_id'] = -1
for fold, (train_idx, valid_idx) in enumerate(kfold.split(train.index, train['label'])):
    train.loc[train.iloc[valid_idx].index, 'fold_id'] = fold
X_train = train.loc[train['fold_id']!=0]
X_valid = train.loc[train['fold_id']==0]

参考