A Day in the Life

加重アンサンブルの最適な重み付けを制約付き線形最小二乗法で求める

先日、Kaggle参加したコンペU.S. Patent Phrase to Phrase Matchinでは、最終局面ではアンサンブル用のモデルをpretrain済みの多様なモデルと複数の最適と思われるパラメータで20個ぐらい作成し、どのモデルをどれぐらいの配合で混ぜると良いのか、というのを色々試した。最終提出したものは、6つのアンサンブル物と9アンサンブル物である。

そうなると、そのアンサンブルの最適な結合重みを人間がモデルの性能とお気持ちを考え決める…、というのはあまり現実的でなく(もちろんドメインエキスパートならそちらのほうが良い結果になることもあろう)、機械が自動で算出してほしい。重みの配合比率を変えて最良のMSEスコアを求めることは、ただの線形結合なので、最適値は一意に算出できるはずと考えてみた。

簡単なデータで考える

このようなデータがあるとする。yは正解の値、Xは各種アンサンブルモデルが予想した値とする。

import numpy as np
y = np.array([0.5, 0.75, 0.25, 1.0, 0.5])
X = np.array([
    [0.52, 0.9, 0.41, 0.99, 0.51],
    [0.52, 0.7, 0.41, 0.99, 0.51],
    [0.48, 0.73, 0.12, 0.97, 0.47],
    [0.45, 0.35, 0.25, 0.9, 0.49],
])

まず単純に各々のX行の MSE を見てみよう。

np.square(X - y).mean(axis=1)
=> array([0.00974, 0.00574, 0.0039 , 0.03452])

続いて純粋に平均で出すとMSEはこちら。単体スコアMSEより悪くなってしまっている。

np.square(X - y).mean(axis=0).mean(axis=0)
=> 0.013475

最小二乗法

最適な係数を求める方法の一つに、最小二乗法(線形回帰)がある。求めてみよう。

from sklearn.linear_model import LinearRegression
reg = LinearRegression().fit(X.T, y)
reg.coef_
=> array([ 0.43575566, -0.05397578,  0.46076883,  0.21063718])

負の係数が出てしまったのはさておき、このパラメータを重みの係数として掛け合わせると最小の値が求められると。

X.T @ reg.coef_
=>array([0.51448131, 0.76448131, 0.26448131, 1.01448131, 0.51448131])

確かにスコアに良い感じの値が求められた。のでMSEを求める。良い数値が出た。

np.square(X.T @ reg.coef_ - y).mean(axis=0)
=> 0.00020970822203200185

ただ、そもそもアンサンブルするモデルに負の係数を掛けていいのだっけ?と疑問がわく。係数は正であってほしい。

制約付き最小二乗法で解く

では係数が正になるような制約を入れた最小二乗問題として考える。制約付きの最小二乗法のソルバ実装があれば簡単そうだ。restricted least square method scipyでググると scipy にズバリのものがあった。scipy.optimize.nnlsは非負条件の最小二乗法のソルバなのでこれを使う。

weights, rnorm = scipy.optimize.nnls(X.T, y)
weights
=> array([0.29260857, 0.08404164, 0.52487508, 0.12761238])

正の係数が求められた。これをアンサンブル結合時の重みとして求めてみる。

X.T @ weights
=> array([0.50522372, 0.75      , 0.24931469, 0.99686367, 0.50131296])
np.square(X.T @ weights - y).mean(axis=0)
=> 7.863453999510499e-06

MSEの値は最小値かつ正の係数で求めらた。ので、このような単純な重み分配でアンサンブルの結果を線形結合する場合、scipy.optimize.nnls で解くのが楽で良さそうだ。


これらの重みで算出したアンサンブル結果はCVとLB(publicもprivateも)は相関となり、人間が重みを決めずにささっと最適比率の重みを出せたのは、Kaggleコンペ最終局面の時間がないかつ脳が疲れてきているときには良かったように思える。この例では単純な線形結合の重み付けの場合で、他にもアンサンブルの重み付けは様々な方法があるので、用途用途によって「最適な重み付け」の実装方法は変わってくるであろう。

なお、コンペ終了後に知ったのだけど、この手法はstackingと呼ばれるものの線形問題の解決手法のようだ。他にも自分はLightGBM(GBDT)でコンテキストごとに分けたアンサンブル最適化もやってみたのだけどCV最適化はされるがLBスコア的にはうまく行かず、この話によるとNNは過剰適合しやすいのでGBDTは向いてないとのこだった。なるほど。


線形代数を知っている人は当然のごとく係数を求められると思うが、自分の場合は先日ベクトル・行列からはじめる最適化数学を読んで、初めて線形性とはどのようなものかを(少々)理解することが出来た。この本を読んでいなければ「制約付き(非線形でない)最小二乗法」といったキーワードも思いつかなかったと思う。基礎は大事だなぁ。

記事の一覧 >