sklearn.preprocessing.PolynomialFeatures の仕様を調査する

こんにちは、company-rapperです

 

初ポストです

 

sklearn.preprocessing.PolynomialFeaturesは簡単に特徴量同士の積や自分自身の累乗を作り出すことができて便利ですが、

 

そもそもどの順番で何の積を出しているんだろう!?

 

と思ったことはありませんか!?

 

本家本元のドキュメントを読んでも・・・

scikit-learn.org

 

いまいちどの順番で何を出すかの仕様がわかりません。

 

ネットで調べても、はっきりと説明してくれているところが私が調べた限りでは見つからず、

 

それならば自分がやってみようと思い立った次第です。

 

調査のカラクリは単純で、ユニークな素数をnumpy行列の各要素に割り当てて、PolynomialFeaturesで変換後のnumpy行列を得て、その項目ごとに素因数分解をして、「何と何をかけたものであるか」を調べます。

 

コード(Jupyter notebook形式)はGithubにアップしています。

github.com

 

結論としては、

  • バイアス→係数1個→係数2個→・・・の順
  • 係数2個の中では、a×(a→b→c)→b×(b→c)→c×cの順
  • interaction_only = True (デフォルトはFalse)の時は同じ係数が2個以上登場しないもののみとなる

です。以降は、この結論に至るコードについて説明します!

 

 まずは、必要なライブラリをインポートします

import pandas as pd
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
import sympy

ここでは、素数を出力したり、素因数分解ができるメソッドを備えたsympyを使用します。

 

調査のために、a,b,cというカラム名を持ったpandasのDataFrameを作成します。

# 1番目からn番目までの素数をnp.arrayで返す関数
def primes(n=0):
    ret_primes = list()
    for i in np.arange(n):
        ret_primes.append(sympy.prime(i+1))
    return ret_primes

# コラム名リストからカラムに対してユニークな素数が割り付けられたデータフレームを返す関数
def generate_df_prime_from_column(columns_original = np.array(["a","b","c"])):
    data_original = np.array(primes(len(columns_original)))
    return pd.DataFrame(data=data_original[np.newaxis,:],columns=columns_original,index=["original"])

# テスト
display(generate_df_prime_from_column())

テストの出力は

  a b c
original 2 3 5

となります。データとして1行3列で要素として、ユニークな素数を与えています。

 

sklearn.preprocessing.PolynomialFeaturesにこのDataFrameを入力すると、ユニークな素数同士の掛け算の結果がnumpy配列(ndarray)形式で返ってきます。このndarray形式の配列の要素を一つずつ素因数分解すると、何の項目同士を掛けたかが分かるという仕組みです:

def investigate_PolynomialFeatures(poly=PolynomialFeatures(2),columns_from=["a","b","c"],power=False):
    df_from = generate_df_prime_from_column(columns_from)
    columns_from = df_from.columns
    data_from = df_from.values
    data_poly = poly.fit_transform(df_from)
    # columnをもう一度作り直す
    columns_poly = list()
    for i in np.arange(data_poly.shape[1]):
        if (data_poly[0][i] == 1):
            columns_poly.append("bias")
        else:
            prime_dict=sympy.factorint(data_poly[0][i])
            keys = list(prime_dict.keys())
            column_str = ""
            if power:
                # 累乗で書ける部分は累乗で書く(例:a^2)
                for j in np.arange(len(keys)):
                    column_str += columns_from[list(data_from[0]).index(keys[j])]
                    if prime_dict[keys[j]] > 1:
                        column_str += "^" + str(prime_dict[keys[j]])
                    if (j < len(keys)-1):
                        column_str += "×"
            else:
                # 単純に×で項目をつなげていく(例:a×a×b)
                for j in np.arange(len(keys)):
                    for k in np.arange(prime_dict[keys[j]]):
                        column_str += columns_from[list(data_from[0]).index(keys[j])]
                        if (j < len(keys)-1) | (k < prime_dict[keys[j]]-1):
                            column_str += "×"
            columns_poly.append(column_str)
    return pd.DataFrame(data=data_poly,columns=columns_poly,index=["poly"])

 

sympy.factorint()は整数を与えると、素因数分解した結果をdict形式で返してくれます。例えば8なら、{2: 3} (2の3乗)と返してくれます。これを基に素数に対応する元のDataFrameのカラム名を取得して何の掛け算で構成されているかを再構成し、カラム名を作成しています。

 

さて、実際に動かしてみると、以下のような結果になります

degree=2のとき
  bias a b c a×a a×b a×c b×b b×c c×c
poly 1.0 2.0 3.0 5.0 4.0 6.0 10.0 9.0 15.0 25.0
degree=2, interaction_only=Trueのとき
  bias a b c a×b a×c b×c
poly 1.0 2.0 3.0 5.0 6.0 10.0 15.0
degree=2, interaction_only=True, include_bias=Falseのとき
  a b c a×b a×c b×c
poly 2.0 3.0 5.0 6.0 10.0 15.0
degree=3, interaction_only=False, include_bias=Falseのとき
  a b c a^2 a×b a×c b^2 b×c c^2 a^3 a^2×b a^2×c a×b^2 a×b×c a×c^2 b^3 b^2×c b×c^2 c^3
poly 2.0 3.0 5.0 4.0 6.0 10.0 9.0 15.0 25.0 8.0 12.0 20.0 18.0 30.0 50.0 27.0 45.0 75.0 125.0
degree=3, interaction_only=True, include_bias=Falseのとき
  a b c a×b a×c b×c a×b×c
poly 2.0 3.0 5.0 6.0 10.0 15.0 30.0

 

これをまとめると、

  • バイアス→係数1個→係数2個→・・・の順
  • 係数2個の中では、a×(a→b→c)→b×(b→c)→c×cの順
  • interaction_only = True (デフォルトはFalse)の時は同じ係数が2個以上登場しないもののみとなる

ということが分かりました。ルールが明らかになれば、大したことじゃなかったんだなと思ってしまいます・・・

 

さて、このinvestigate_PolynomialFearures()ですが、単にsklearn.preprocessing.PolynomialFeaturesの挙動を調査するだけでなく、PolynomialFeatures適用後のcolumnが得られる優れものにもなるのです!

 

ということで、次回は調査目的ではなく、実用目的にフォーカスして少しコードを書き換えたもので、何か分析してみようと思います。

 

(次の投稿はいつになることやら・・・)

 

company-rapperでした!