見出し画像

ロジスティック回帰でも回帰がしたい!【python】

調査・分析:RMR(Rosso Machinelearning Reportage)
特技:外の景色からロジスティック曲線を見つけること

こんにちは。株式会社Rosso、AI部です。

突然ですが、こういう散布図を見るとロジスティック回帰回帰したくなるというのが人情というものです。

どうしてもロジスティック回帰したくなる散布図

しかし、sklearn.linear_model.LogisticRegressionで回帰させようとするとエラーになってしまう。

from sklearn.linear_model import LogisticRegression

lg_reg = LogisticRegression(penalty = 'l2',fit_intercept=True)
lg_reg.fit(x,y)

"""
ValueError: Unknown label type: 'continuous'
"""

【失敗】ロジスティック回帰で分類タスクを学習させてから回帰させる

では、ロジスティック回帰(分類)でいいからとりあえず学習させて、predict_probaでロジスティック回帰の形を取得できるか?とやってみるとうまくいかない。

from sklearn.linear_model import LogisticRegression

x = df["x"].values.reshape(-1,1)
#無理やり0,1にわける
y_class = df["y"].map(lambda x:1 if x>df["y"].median() else 0).values.reshape(-1,1)

#分類でいいからさせてみる
skmodel = LogisticRegression(fit_intercept=True)
skmodel.fit(x,y_class)
predict = skmodel.predict_proba(x_line)

plt.figure(figsize=(10,6))
plt.plot(df["x"],predict[:,1],label="ロジスティック回帰(確率)")
plt.scatter(x,y,label="連続値")
plt.scatter(x,y_class,label="疑似クラス")
plt.legend(fontsize=14)

う~ん。もっといい感じにフィットしてほしいですね。

ロジスティック回帰って名前なんだから勝手に回帰してくれるのかと思っていたら、
sklearnだと分類のみ。回帰してくれない。

しかし、ロジスティック回帰で回帰する方法を調べたところ、いろいろ見つかったので紹介します。

  1.  ロジット化してから線形回帰して戻す

  2.  scipy.optimize

  3.  statsmodels

  4.  ニューラルネットワーク


方法その1:ロジット化してから線形回帰して戻す

ロジット化してから線形回帰させ、
ロジスティック回帰して(0,1)に戻すといい感じに回帰してくれる。

$${logit(y) = \log{\frac{y}{1-y}}}$$

from sklearn.linear_model import LinearRegression

#ロジット化。 (0,1)範囲の値を(-∞,∞)に変換
y_logit = np.log(y/(1-y))

#ロジット化した値に対してフィットさせる
li_reg_logit = LinearRegression()
li_reg_logit.fit(x,y_logit)

x_line = np.linspace(0,1,50).reshape(-1,1)
y_pred_logit = li_reg_logit.predict(x_line)

#ロジスティック回帰で(0,1)の範囲に変換
y_pred = 1/(1+np.exp(-y_pred_logit))

plt.figure(figsize=(10,6))
plt.scatter(x,y,label="実データ")
plt.scatter(x_line,y_pred,label="ロジット化して線形回帰")
plt.title("LinearRegressionでロジスティック回帰",fontsize=14)
plt.legend(fontsize=14)

https://stackoverflow.com/questions/65268985/python-implementation-of-logistic-regression-as-regression-not-classification


方法その2:scipy.optimize.minimizeを使う

個人的にscipyのminimizeは直感的で他の用途にも使いやすいと思っているのでオススメ。

from scipy.optimize import minimize
from sklearn.metrics import mean_squared_error

def func(X,a,b,c,d):
    y = c/(1+np.exp(-(a*X+b))) + d
    return y

def mse(param):
    a = param[0]
    b = param[1]
    c = param[2]
    d = param[3]
    y = func(df["x"],a,b,c,d)
    return mean_squared_error(df["val_normed"],y)


mi_res = minimize(mse,[1,1,1,1])
y_mi = func(x_line,*mi_res.x)

plt.figure(figsize=(10,6))
plt.scatter(x,y,label="実データ")
plt.scatter(x_line,y_mi,label="minimize")
plt.title("scipy.optimize.minimizeによる回帰",fontsize=14)
plt.legend(fontsize=14)

方法その3:statsmodels.formula.apiを使う

smf.glmで線形予測子をsm.families.Binomial()を指定すると可能。デバッグが難しい気がする。

import statsmodels.api as sm
import statsmodels.formula.api as smf

logistic = smf.glm(formula = "y ~ x + 1",
                   data = df,
                   family = sm.families.Binomial()).fit()

df_line = pd.DataFrame(x_line,columns=["x"])
sm_pred = logistic.predict(df_line)

plt.figure(figsize=(10,6))
plt.scatter(x,y,label="実データ")
plt.scatter(df_line,sm_pred,label="statsmodelsによる回帰")
plt.title("statsmodelsによるロジスティック回帰",fontsize=14)
plt.legend(fontsize=14)

方法その4:ニューラルネットワークで殴る

ニューラルネットワークの出力層をSigmoid層にして、
もうなんでもいいからとにかくロジスティック曲線の形に無理やりフィットさせる。
他の方法に比べたらコードが大げさ過ぎるかもしれない。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

#GPU使用の確認
device = "cuda:0" if torch.cuda.is_available() else "cpu"
criterion = nn.MSELoss()

X_tensor = torch.tensor(df["x"].values.reshape(-1,1)).float().to(device)
Y_tensor = torch.tensor(df["y"].values.reshape(-1,1)).float().to(device)

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.seq = nn.Sequential(
            nn.Linear(1,20),
            nn.ReLU(),
            nn.Linear(20,20),
            nn.ReLU(),
            nn.Linear(20,1),
            nn.Sigmoid()
        )
        self.seq.to(device)
    def forward(self,x):
        return self.seq(x)

torch_model = Model()

#学習率気にしたくないからAdam
optimize = optim.Adam(torch_model.parameters())

for _ in range(1000):
    output = torch_model(X_tensor)
    loss = criterion(output,Y_tensor)
    
    optimize.zero_grad()
    
    loss.backward()
    optimize.step()

with torch.no_grad():
    Y_pred = torch_model.forward(torch.tensor(x_line).float().to(device))

y_pred = Y_pred.detach().cpu().numpy().flatten()

plt.figure(figsize=(10,6))
plt.scatter(x,y,label="実データ")
plt.scatter(x_line,y_pred,label="ニューラルネットワーク")
plt.title("ニューラルネットワークによる回帰",fontsize=14)
plt.legend(fontsize=14)

補足:ロジスティック回帰で回帰するのは合法?

機械学習ではロジスティック回帰はあくまで分類のためのものとされているが、
本来はロジスティック方程式として生物の個体数の変動を予測するために扱われていた。

$${\frac{dN}{dt} = rN(1-\frac{N}{K})}$$

N:個体数 t:時間 r:内的自然増加率, K:環境収容力
それが後に確率の世界で対数オッズ比を扱う

$${log\frac{p}{1-p}=f(x)}$$

これをを変形し、

$${p = \frac{1}{1+exp(-f(x))}}$$

と、確率を表す状態で扱われるようになった。

その後、マーケティング分析や機械学習界隈では

$${\frac{1}{1+exp(-f(x))}=C}$$

とおいて、
C以上ならラベル1、C未満ならラベル0。
という分類タスクで用いられるのがロジスティック回帰とされるようになった。

今ではロジスティック回帰=分類タスク用というイメージとなっているが、
確率が目的変数の回帰タスクとして扱われることもある。

確率を目的変数とする標準ロジスティック関数による回帰、つまり本記事の方法その1の場合、導出された各係数は、パラメータが1変化した場合の対数オッズ比を表すという重要な性質がある。

本記事の方法その4のようにニューラルネットワークで無理やりフィットさせるような場合は、そういった性質を利用できないので要注意。

まとめ

  • sklearnのLogisticRegressionは分類専用

  • LogisticRegressionで回帰するには4つの方法がある

  • ロジスティック回帰で回帰するのは合法!!!

ロジスティック回帰で回帰しようとしたら意外と方法がわからなかったため調べてみたところ、たくさんの方法があり、勉強になりました。
我々AI部でも「ロジスティック回帰で回帰するのは違法」と思っている社員がいるくらいでしたが、ロジスティック回帰はそれぞれの分野で独自の呼び名や進化を遂げているのも興味深いですね。