頑張らないために頑張る

ゆるく頑張ります

LogisticRegressionのsolverパラメータはデフォルト値が変わってた

Posted at — Dec 15, 2019

はじめに

scikit-learnライブラリのロジスティック回帰(LogisticRegression)を使っていたときに気づいた事象です。

まあまあこの界隈ではありがちですが、「過去に動作していたコードがライブラリ(やパッケージ)のアップデートで動作しなくなる」パターンのお話です。

具体的な現象

下記のようなコードでエラーが発生します。具体的にはLogisticRegression()実行時にL1正規化を行うと「L1正規化はサポートしてないぜ!」っていうエラーになります。

lr_l1 = LogisticRegression(C=C, penalty='l1').fit(X_train, y_train)

エラーの内容はこんな感じ。

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/devp/hoge.py in 
      1 for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
----> 2     lr_l1 = LogisticRegression(C=C, penalty='l1').fit(X_train, y_train)
      3     print('Training accuracy of l1 logreg with C={:.f3}: {:.2f}'.format(C, lr_l1.score(X_train, y_train)))
      4     print('Test accuracy of l1 logreg with C={:.f3}: {:.2f}'.format(C, lr_l1.score(X_test, y_test)))
      5     plt.plot(lr_l1.coef_.T, marker, label='C={:.3f}'.format(C))

/usr/local/lib/python3.7/site-packages/sklearn/linear_model/_logistic.py in fit(self, X, y, sample_weight)
   1484         The SAGA solver supports both float64 and float32 bit arrays.
   1485         """
-> 1486         solver = _check_solver(self.solver, self.penalty, self.dual)
   1487 
   1488         if not isinstance(self.C, numbers.Number) or self.C < 0:

/usr/local/lib/python3.7/site-packages/sklearn/linear_model/_logistic.py in _check_solver(solver, penalty, dual)
    443     if solver not in ['liblinear', 'saga'] and penalty not in ('l2', 'none'):
    444         raise ValueError("Solver %s supports only 'l2' or 'none' penalties, "
--> 445                          "got %s penalty." % (solver, penalty))
    446     if solver != 'liblinear' and dual:
    447         raise ValueError("Solver %s supports only "

ValueError: Solver lbfgs supports only 'l2' or 'none' penalties, got l1 penalty.

solverlbfgsl2noneしかサポートしてないよ、っていうエラーですね。lbfgsなんてコードのどこにも書いてないぞ、ってところなんですが、これはsolverのデフォルト値です。

結論から

下記のように記述するのが正解です。

lr_l1 = LogisticRegression(C=C, penalty='l1', solver='liblinear').fit(X_train, y_train)

solverliblinearを指定するとエラーを解消できます。

原因はなによ

ここに書いてあるのですが、LogisticRegressionsolverのデフォルト値がアップデートにより変更されたせいです。

Changed in version 0.22: The default solver changed from ‘liblinear’ to ‘lbfgs’ in 0.22.

これにより本来solverのデフォルト値をliblinearだと想定して、solverの設定を記述していないソースコードが軒並み影響を受けて、実行結果が異なったりエラーを吐いたりしているわけです。

scikit-learnのバージョン0.22が公開されたのは2019年12月3日なんですが、それより前に書かれた本だとかブログなんかのソースでsolverliblinearを想定しているものは注意が必要です。

備考

そもそも「Pythonではじめる機械学習」って言う本のソースコードを、最近改めて写経しているときに気づいたんですよね。テキストに書いてあるコードをそのまま記述して実行しているにもかかわらず、実行結果が異なったり実行そのものがエラーになるケースが、このLogisticRegressionを実行しているときに頻発しました。

実行結果が異なるのは、なんかトレーニングデータでの訓練時にrandom_stateの値設定が間違っているかなー、とかプログラムのミス(というか写経ミス)を疑っていたんですが、エラーが出力された時点で「これライブラリ側のせいなんじゃ?」と考えるに至りました。調査してみたらやっぱりライブラリ側の原因じゃん、という話で。

こういう「ライブラリ側の原因で動作が変わる、あるいは動作しなくなる」ケースって、scikit-learnに限った話では全然なくて、この界隈あるあるなんですよね。自分のコーディングミスだと思って調べていた時間が若干もったいなかったなぁ、という感じ。

よく利用するライブラリのバージョンアップは、欠かさずキャッチアップしておきたいものです。

comments powered by Disqus