静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念



scikit-learnで目的変数を対数変換したりするTransformedTargetRegressor

はじめに

 経済系の分析などで、目的変数を対数変換して分析するというケースがあります。scikit-learnはそのようなケースもサポートしています。

 どうやったらいいのかわからなくて、自分で変数を変換している人も中にはいるかと思いますが、モデル構築まではなんとかなっても、予測のことまで考えると不便になります。うまくPipelineなどで自動化できるといいのですが、普通のやり方では目的変数は処理してくれません。しかし、TransformedTargetRegressorなら大丈夫です。

目的変数の対数変換

 sklearn.compose.TransformedTargetRegressorを使います。ほとんど紹介を見かけないのですが、実際これでできます。

>>> import numpy as np
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.compose import TransformedTargetRegressor
>>> tt = TransformedTargetRegressor(regressor=LinearRegression(),
...                                 func=np.log, inverse_func=np.exp)
>>> X = np.arange(4).reshape(-1, 1)
>>> y = np.exp(2 * X).ravel()
>>> tt.fit(X, y) 
TransformedTargetRegressor(...)
>>> tt.score(X, y)
1.0
>>> tt.regressor_.coef_
array([2.])

sklearn.compose.TransformedTargetRegressor — scikit-learn 0.21.2 documentation

 なかなか特殊な感じですね。モデルを作るときにfuncとinverse_funcを渡すのがミソで、学習はfuncで変換された目的変数に対して行われます。予測のときはinverse_funcで逆変換されて出てくるので、余計な手間をすべて省くことができます。

やってみる

 ということで、対数変換した方がうまくいくようなデータを作って線形回帰してみます。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.compose import TransformedTargetRegressor

def main():
    np.random.seed(0)
    x = np.linspace(0, 5, 100)
    y = 20 + np.exp(x + 
                    np.random.normal(scale=0.5, size=x.shape))
    X = x.reshape(-1, 1)

    lr = LinearRegression()
    lr_logy = TransformedTargetRegressor(
        regressor=lr, func=np.log, inverse_func=np.exp)

    plt.scatter(x, y, c="b", alpha=0.1)
    for name, model in [("Linear", lr), 
                        ("Linear-logy", lr_logy)]:
        model.fit(X, y)
        pred = model.predict(X)
        plt.plot(x, pred, label=name)
    plt.legend()
    plt.savefig("result.png")

if __name__ == "__main__":
    main()
       

result.png
result.png

 非等分散だったりして条件が悪いのですが、線形回帰で素直にやるのと比べると良いフィッティングを見せています。

おまけ:説明変数を対数変換したいとき

 FunctionTransformerを使ってください。

sklearn.preprocessing.FunctionTransformer — scikit-learn 0.21.2 documentation

>>> import numpy as np
>>> from sklearn.preprocessing import FunctionTransformer
>>> transformer = FunctionTransformer(np.log1p, validate=True)
>>> X = np.array([[0, 1], [2, 3]])
>>> transformer.transform(X)
array([[0.        , 0.69314718],
       [1.09861229, 1.38629436]])

5.3. Preprocessing data — scikit-learn 0.21.2 documentation

 Pipelineで組み合わせれば、説明変数・目的変数ともに対数変換でloglogというのも簡単です。

まとめ

 対数変換した方がうまくいくようなデータのときは試してみましょう。もちろん対数以外の変換でもいけるので、目的変数を変換したいときはこれでいいと思います。