PythonとScikit-learnによる多項式特徴量の生成:PolynomialFeaturesの基本

Python

PythonのScikit-learnライブラリを使って機械学習モデル、特に「線形回帰(Linear Regression)」を学んだとき、こんな悩みを持ったことはありませんか?

「手元のデータは明らかに曲線(非線形)を描いているのに、線形回帰だと直線しか引けなくて、まったく予測が当たらない…」

線形モデルはシンプルで解釈しやすい反面、その名の通り「線形(直線的)」な関係しか捉えられないという大きな限界があります。

しかし、ご安心ください。Scikit-learnには、この問題を解決し、線形モデルの表現力を劇的に向上させるための強力な前処理ツールが用意されています。それが、今回徹底解説するPolynomialFeatures(多項式特徴量)です。

この記事では、PolynomialFeaturesの基本的な使い方から、LinearRegressionと組み合わせて非線形データにフィットさせる「多項式回帰」の実装方法まで、初心者の方にも分かりやすく解説していきます。

本記事で解説するPolynomialFeaturesの公式ドキュメントはこちらです。

この記事で学べること:

  • PolynomialFeaturesの基本的な使い方(fit_transform
  • degree(次数)やinteraction_onlyなどの重要パラメータの意味
  • LinearRegressionと組み合わせて「多項式回帰」を実装する実践的な方法
  • Pipelineを使って、前処理とモデル構築をスマートに連携させるテクニック

1. はじめに:なぜ「多項式特徴量」が必要なのか?

まずは、PolynomialFeaturesの必要性について、もう少し深く掘り下げてみましょう。

線形モデルの限界

LinearRegression(線形回帰)は、入力された特徴量(説明変数 $x$)と予測したい値(目的変数 $y$)の関係を、以下のような「直線」の式で表そうとします。

$y = w_1 x_1 + w_0$

($w_1$は傾き、$w_0$は切片)

もしデータが2次元(特徴量が $x_1$ と $x_2$)なら、「平面」になります。

$y = w_1 x_1 + w_2 x_2 + w_0$

これでは、データが$y = x^2$のような放物線を描いている場合、うまく予測線を当てはめることができません。

解決策:特徴量を「変換」する

ここでPolynomialFeaturesの出番です。

PolynomialFeaturesは、元の特徴量 $x_1$ を使って、新しい特徴量 $x_1^2$, $x_1^3$ … を自動で作り出してくれます。

例えば、元の特徴量が $[x_1]$ だけだったものを、PolynomialFeaturesで $[x_1, x_1^2]$ という2つの特徴量に変換(拡張)します。

この新しい特徴量 $[x_1, x_1^2]$ を使って線形回帰を行うと、モデルは内部的に以下の式を学習することになります。

$y = w_1 (x_1) + w_2 (x_1^2) + w_0$

これは、見かけ上は線形回帰ですが、元の $x_1$ に対しては**2次の多項式(曲線)**の関係を学習していることになり、非線形のデータにもフィットできるようになるのです。

2. PolynomialFeaturesとは?

PolynomialFeaturesは、Scikit-learnのsklearn.preprocessingモジュールに含まれるクラスです。

その役割は、すでにご説明した通り、入力された特徴量(Feature)から、**多項式(Polynomial)の特徴量や、特徴量同士の積である交互作用項(Interaction terms)**を生成することです。

具体的な変換イメージ

もし入力データが $[x_1, x_2]$ という2つの特徴量を持っていた場合、PolynomialFeatures(デフォルトのdegree=2)は、以下のような特徴量セットに変換します。

変換前: $[x_1, x_2]$

変換後: $[1, x_1, x_2, x_1^2, x_1x_2, x_2^2]$

  • $1$: バイアス項(切片項)
  • $x_1, x_2$: 元の特徴量(次数1)
  • $x_1^2, x_2^2$: 2次の項
  • $x_1x_2$: 交互作用項($x_1$と$x_2$の積)

この変換された特徴量セットを線形回帰モデル(LinearRegressionなど)に入力することで、モデルは複雑な非線形関係を学習できます。

LinearRegressionは、線形モデルの基本であり、PolynomialFeaturesの最高のパートナーです。LinearRegressionについて詳しい記事はこちら

3. PolynomialFeaturesの基本的な使い方 (fit_transform)

それでは、実際のコードでPolynomialFeaturesの使い方を見ていきましょう。

Scikit-learnの他の前処理クラス(StandardScalerやSimpleImputerなど)と同様に、fit_transform()メソッドを使うのが基本です。

必要なライブラリのインポート

import numpy as np
from sklearn.preprocessing import PolynomialFeatures

1特徴量の場合 (degree=2)

最もシンプルな、特徴量が1つ(例: $x_1$)の場合です。

# サンプルデータ (1特徴量, 3サンプル)
# Scikit-learnの入力は (サンプル数, 特徴量数) の2次元配列である必要があります
X = np.array([[2],
              [3],
              [4]])

print("元のデータ (X):")
print(X)
print("元のデータのshape:", X.shape)

# PolynomialFeaturesのインスタンスを作成 (degree=2はデフォルト)
# degree=2 は、2次までの項(1, x, x^2)を生成することを意味します
poly = PolynomialFeatures(degree=2)

# fit_transformで変換を実行
X_poly = poly.fit_transform(X)

print("\n変換後のデータ (X_poly):")
print(X_poly)
print("変換後のshape:", X_poly.shape)

実行結果:

元のデータ (X):
[[2]
 [3]]
 [4]]
元のデータのshape: (3, 1)

変換後のデータ (X_poly):
[[ 1.  2.  4.]  <-- [1, 2, 2^2]
 [ 1.  3.  9.]  <-- [1, 3, 3^2]
 [ 1.  4. 16.]] <-- [1, 4, 4^2]
変換後のshape: (3, 3)

元の特徴量は1つでしたが、変換後は $[1, x_1, x_1^2]$ の3つの特徴量に増えていることがわかります。

2特徴量の場合 (degree=2)

次に、特徴量が2つ(例: $x_1, x_2$)の場合です。

# サンプルデータ (2特徴量, 2サンプル)
X_2d = np.array([[2, 3],   # サンプル1: x1=2, x2=3
                 [4, 5]])  # サンプル2: x1=4, x2=5

print("元のデータ (X_2d):")
print(X_2d)

# degree=2のインスタンス
poly_2d = PolynomialFeatures(degree=2)

# 変換
X_2d_poly = poly_2d.fit_transform(X_2d)

print("\n変換後のデータ (X_2d_poly):")
print(X_2d_poly)

実行結果:

元のデータ (X_2d):
[[2 3]
 [4 5]]

変換後のデータ (X_2d_poly):
[[ 1.  2.  3.  4.  6.  9.]   <-- [1, x1, x2, x1^2, x1*x2, x2^2] = [1, 2, 3, 2^2, 2*3, 3^2]
 [ 1.  4.  5. 16. 20. 25.]]  <-- [1, x1, x2, x1^2, x1*x2, x2^2] = [1, 4, 5, 4^2, 4*5, 5^2]

変換後の特徴量は、先ほど説明した $[1, x_1, x_2, x_1^2, x_1x_2, x_2^2]$ の順番で6つ生成されていることが確認できます。

4. 押さえておくべき重要パラメータ

PolynomialFeaturesの挙動は、主に3つのパラメータでコントロールします。

degree (次数)

これは最も重要なパラメータで、生成する多項式の最大次数を指定します。デフォルトは2です。

degree=3に設定すると、 $[x_1, x_2]$ の入力から、 $x_1^3$, $x_1^2x_2$, $x_1x_2^2$, $x_2^3$ といった3次の項までが生成されます。

# degree=3の場合
poly_deg3 = PolynomialFeatures(degree=3)
X_2d_poly3 = poly_deg3.fit_transform(X_2d)

print(f"degree=2 の特徴量数: {X_2d_poly.shape[1]}")
print(f"degree=3 の特徴量数: {X_2d_poly3.shape[1]}")

print("\ndegree=3 の変換結果 (サンプル1 [2, 3] のみ):")
print(X_2d_poly3[0])
# [1, x1, x2, x1^2, x1x2, x2^2, x1^3, x1^2x2, x1x2^2, x2^3]
# [1, 2, 3, 4,   6,   9,   8,    12,    18,    27]

実行結果:

degree=2 の特徴量数: 6
degree=3 の特徴量数: 10

degree=3 の変換結果 (サンプル1 [2, 3] のみ):
[ 1.  2.  3.  4.  6.  9.  8. 12. 18. 27.]

特徴量の数が6個から10個に増えました。

注意点:

degreeを大きくすればするほどモデルは複雑な非線形関係を捉えられますが、大きくしすぎると以下の問題が発生します。

  • 過学習(Overfitting): 学習データに過剰にフィットしすぎて、未知のデータに対する予測精度(汎化性能)が著しく低下します。
  • 計算コストの増大: 特徴量の数が爆発的に増加し(次元の呪い)、学習に必要な時間とメモリが膨大になります。

degreeは通常、2か3が使われることが多く、それ以上にする場合は過学習していないか慎重な検証が必要です。

interaction_only (交互作用項のみ)

このパラメータは、異なる特徴量同士の積(交互作用項)だけを生成するかどうかを決めます。デフォルトはFalseです。

  • interaction_only=False (デフォルト):$x_1^2$ や $x_2^2$ のような単一特徴量のべき乗と、$x_1x_2$ のような交互作用項の両方を生成します。
  • interaction_only=True:$x_1^2$ や $x_2^2$ のような項は生成せず、$x_1x_2$ のような交互作用項のみを生成します(次数degreeまでの)。
# degree=2, interaction_only=True の場合
poly_inter = PolynomialFeatures(degree=2, interaction_only=True)
X_2d_inter = poly_inter.fit_transform(X_2d)

print("interaction_only=True の変換結果:")
print(X_2d_inter)
# [1, x1, x2, x1*x2]

実行結果:

interaction_only=True の変換結果:
[[ 1.  2.  3.  6.]
 [ 1.  4.  5. 20.]]

degree=2(デフォルト)の時と比較して、$x_1^2$(4.0, 16.0)と $x_2^2$(9.0, 25.0)の列が消え、$[1, x_1, x_2, x_1x_2]$ の4特徴量だけになっていることがわかります。

include_bias (バイアス項/切片項)

これは、値が常に1である特徴量(バイアス項または切片項)を含めるかどうかを制御します。デフォルトはTrueです。

  • include_bias=True (デフォルト):変換後の特徴量の先頭に「1」の列が追加されます。
  • include_bias=False:「1」の列を追加しません。
# include_bias=False の場合
poly_nobias = PolynomialFeatures(degree=2, include_bias=False)
X_2d_nobias = poly_nobias.fit_transform(X_2d)

print("include_bias=False の変換結果:")
print(X_2d_nobias)
# [x1, x2, x1^2, x1*x2, x2^2]

実行結果:

include_bias=False の変換結果:
[[ 2.  3.  4.  6.  9.]
 [ 4.  5. 16. 20. 25.]]

先頭にあった1.の列がなくなっていることがわかります。

TIPS:

LinearRegressionなどのScikit-learnの線形モデルは、デフォルトでモデル自身が切片(バイアス項)を計算するようになっています (fit_intercept=True)。

そのため、PolynomialFeaturesとLinearRegressionを組み合わせて使う場合は、**PolynomialFeatures(include_bias=False)**に設定し、切片項が二重に計算されないようにすることが推奨されます。

5. 【実践】多項式回帰 (Polynomial Regression) の実装方法

お待たせしました。いよいよPolynomialFeaturesLinearRegressionを組み合わせて、非線形データにフィットする「多項式回帰」を実装してみましょう。

1. サンプルデータ(非線形)の準備

まずは、学習対象となる非線形のデータを作成します。

ここでは、$y = 0.5 x^2 + 1.0 x + 2.0$ という2次関数に、ランダムなノイズ(np.random.randn)を加えたデータを使います。

import matplotlib.pyplot as plt

# 乱数のシードを固定
np.random.seed(42)

# サンプルデータ数
m = 100
# X: -3から3までの値をランダムに100個生成し、ソートする
X = 6 * np.random.rand(m, 1) - 3
X_sorted = np.sort(X, axis=0) # プロット用にソートしたもの

# y: 2次関数 + ノイズ
y = 0.5 * X**2 + 1.0 * X + 2.0 + np.random.randn(m, 1)

# データをプロットして確認 (plt.show()は環境に応じて実行してください)
plt.scatter(X, y, alpha=0.6, label="Actual Data")
plt.xlabel("X")
plt.ylabel("y")
plt.title("Non-linear Sample Data")
plt.legend()
# plt.show()

(上記のコードを実行すると、データがきれいな放物線状に散布されていることが確認できるはずです。)

2. (比較) 通常の線形回帰を適用する

まず、比較のために、このデータにPolynomialFeaturesを使わず、通常のLinearRegressionだけを適用してみます。

from sklearn.linear_model import LinearRegression

# 線形回帰モデルのインスタンス化と学習
lin_reg = LinearRegression()
lin_reg.fit(X, y)

# 予測
y_pred_lin = lin_reg.predict(X_sorted)

# プロット
plt.scatter(X, y, alpha=0.6, label="Actual Data")
plt.plot(X_sorted, y_pred_lin, color="red", linewidth=2, label="Linear Regression")
plt.xlabel("X")
plt.ylabel("y")
plt.title("Linear Regression Fit")
plt.legend()
# plt.show()

(このコードを実行すると、赤い直線が引かれますが、データの曲線的なトレンドをまったく捉えられていないことがわかります。)

LinearRegressionは線形モデルの基本です。LinearRegressionについて詳しい記事はこちら

3. PolynomialFeaturesを使って多項式回帰を適用する

次に、本命の多項式回帰です。

PolynomialFeaturesを使ってXを2次の特徴量($x, x^2$)に変換してから、LinearRegressionに渡します。

# 1. PolynomialFeaturesで変換 (degree=2, 切片項はLinearRegressionに任せるため False)
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)

print(f"元のXのshape: {X.shape}")       # (100, 1)
print(f"変換後のX_polyのshape: {X_poly.shape}") # (100, 2)  <- [x, x^2]

# 2. 変換後の X_poly を使って LinearRegression を学習
poly_reg = LinearRegression()
poly_reg.fit(X_poly, y)

# 3. 予測
# 予測時も、予測したいデータ(X_sorted)を同じようにtransformする必要がある
X_sorted_poly = poly_features.transform(X_sorted)
y_pred_poly = poly_reg.predict(X_sorted_poly)

# プロット
plt.scatter(X, y, alpha=0.6, label="Actual Data")
plt.plot(X_sorted, y_pred_poly, color="red", linewidth=2, label="Polynomial Regression (d=2)")
plt.xlabel("X")
plt.ylabel("y")
plt.title("Polynomial Regression Fit")
plt.legend()
# plt.show()

(今度のコードでは、赤い線がデータの放物線に沿って、きれいにフィットしていることが確認できるはずです! これが多項式回帰の効果です。)

LinearRegressionだけでなく、正則化項(過学習を抑える機能)を持つRidge回帰、Lasso回帰、ElasticNet回帰とも全く同じ方法で組み合わせることができます。

degreeを大きくしすぎると過学習が起こりやすくなりますが、そのような場合にRidgeLassoは有効です。Ridgeについて詳しい記事はこちらLassoについて詳しい記事はこちらElasticNetについて詳しい記事はこちら

4. (推奨) Pipelineを使って処理をまとめる

PolynomialFeaturesを使った方法は強力ですが、「学習データ(X)と予測用データ(X_sorted)の両方に、fit_transformtransformを忘れずに適用しなければならない」という手間があり、ミスも起こりやすいです。

この一連の処理(「特徴量変換」→「モデル学習」)をまとめるために、Scikit-learnの**Pipeline**(またはmake_pipeline)を使うことが強く推奨されます。

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# Pipeline(パイプライン)の構築
# 処理のステップをリストで渡す
# (名前, 実行するインスタンス) のタプル

# make_pipelineを使うと、名前を自動で付けてくれるので更に簡単
from sklearn.pipeline import make_pipeline

# (1) PolynomialFeatures (d=2)
# (2) StandardScaler (スケールを揃える)
# (3) LinearRegression (モデル)
poly_pipeline = make_pipeline(
    PolynomialFeatures(degree=2, include_bias=False),
    StandardScaler(),
    LinearRegression()
)

# パイプライン全体を fit させる (元のXとyを渡すだけ)
# 内部で自動的に X -> X_poly -> X_scaled と変換され、LinearRegressionに渡される
poly_pipeline.fit(X, y)

# 予測 (元のX_sortedを渡すだけ)
# 内部で自動的に X_sorted -> X_sorted_poly -> X_sorted_scaled と変換される
y_pred_pipe = poly_pipeline.predict(X_sorted)

# プロット結果は先ほどと(ほぼ)同じになる
plt.scatter(X, y, alpha=0.6, label="Actual Data")
plt.plot(X_sorted, y_pred_pipe, color="red", linewidth=2, label="Pipeline (Poly + Scaler + LR)")
plt.xlabel("X")
plt.ylabel("y")
plt.title("Polynomial Regression with Pipeline")
plt.legend()
# plt.show()

なぜStandardScalerを追加したか?

PolynomialFeaturesで $x^2$ や $x^3$ を作ると、元の $x$ との数値のスケール(大きさ)が極端に変わってしまいます(例: $x=2$ なら $x^3=8$ だが、$x=100$ なら $x^3=1,000,000$)。

このように特徴量間のスケールが違いすぎると、線形モデル(特に正則化を使うRidgeやLasso、またはLogisticRegression)の学習が不安定になったり、収束が遅くなったりします。

StandardScalerは、すべての特徴量の平均を0、標準偏差を1に揃える(標準化)クラスです。PolynomialFeaturesの後(そしてモデル学習の前)にStandardScalerを挟むのは、非常に一般的なテクニックです。

(StandardScalerはデータ前処理の必須テクニックです。StandardScalerについて詳しい記事はこちら

(PolynomialFeaturesは回帰だけでなく、LogisticRegressionと組み合わせて非線形な分類境界を作るためにも使われます。LogisticRegressionについて詳しい記事はこちら

6. (補足) 生成された特徴量の名前を確認する方法

PolynomialFeaturesがどのような特徴量を生成したか、その名前(例: “x0^2”, “x0 x1″)を確認したい場合があります。これはデバッグやモデルの解釈に役立ちます。

get_feature_names_out() メソッドを使います。

# 2特徴量の場合の例
X_2d_df = pd.DataFrame(X_2d, columns=['Age', 'Income'])

poly_check = PolynomialFeatures(degree=2, include_bias=False)
poly_check.fit(X_2d_df)

# input_featuresに元のカラム名を渡す
feature_names = poly_check.get_feature_names_out(input_features=['Age', 'Income'])

print("生成された特徴量の名前:")
print(feature_names)

(※このコードを実行するにはpandasライブラリが必要です。import pandas as pdを先頭に追加してください。)

実行結果:

生成された特徴量の名前:
['Age' 'Income' 'Age^2' 'Age Income' 'Income^2']

このように、どの特徴量が生成されたかを名前で確認することができます。

7. まとめ:PolynomialFeaturesで線形モデルの可能性を広げよう

今回は、Scikit-learnのPolynomialFeaturesについて、その基本から多項式回帰という実践的な応用までを解説しました。

  • PolynomialFeaturesは線形モデルの救世主: 線形モデル(LinearRegressionなど)に非線形データ(曲線)をフィットさせるための強力な前処理ツールです。
  • degree(次数)の調整が鍵: degreeを大きくすると複雑なデータを表現できますが、過学習のリスクが伴います。通常は2か3から試すのが良いでしょう。
  • Pipelineでの活用がベスト: PolynomialFeatures, StandardScaler, LinearRegressionといった一連の処理はPipelineでまとめることで、コードがクリーンになり、ミスも防げます。

データが非線形だからといって、いきなり複雑なモデル(ニューラルネットワークなど)に飛びつく前に、まずはPolynomialFeaturesと線形モデルの組み合わせを試してみてください。多くの場合、これで十分な精度と解釈のしやすさを両立できるはずです。

コメント

タイトルとURLをコピーしました