機械学習には「ガベージイン、ガベージアウト*」という格言があります。世界で最も洗練されたアルゴリズムを使用できますが、適切に準備されていないデータを入力すると、結果は劣悪なものになります。逆に、綿密に準備された優れたデータが与えられた単純なアルゴリズムは、生の乱雑なデータが与えられた複雑なアルゴリズムよりも優れたパフォーマンスを発揮する可能性があります。
特徴エンジニアリングは、生データを機械学習アルゴリズムが効果的に学習できる表現に変換する技術です。これはおそらく、応用機械学習において最も影響力のあるスキルであり、深い専門知識とデータ サイエンスが出会う場所です。
機械学習では、特徴 は、予測しようとしているものの測定可能なプロパティまたは属性です。特徴はモデルへの入力です。 ラベル (またはターゲット) が出力です。
住宅価格予測モデルの場合:
電子メールスパム分類子の場合:
多くの場合、どのアルゴリズムを選択するかよりも、機能の質、量、関連性の方が重要です。
現実世界のデータは乱雑です。機械学習アルゴリズムには、生データがほとんど満たすことができない特定の要件があります。
特徴量エンジニアリングは、生データとモデル対応入力の間のギャップを埋めるプロセスです。
フィーチャのスケールが大きく異なる場合、モデルが誤解される可能性があります。数千の値を持つフィーチャは、0 から 1 までの値を持つフィーチャよりも優先される可能性があります。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X_train)
# Before: age=[22, 65, 34], income=[18000, 95000, 42000]
# After: age=[0.0, 1.0, 0.27], income=[0.0, 1.0, 0.31]
最小-最大スケーリングよりも外れ値に対してより堅牢です。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
# Transforms each feature to have mean=0, standard deviation=1
# Feature value → (value - mean) / std_dev
経験則: デフォルトで標準化を使用します。特定の境界範囲内の値が必要な場合 (ニューラル ネットワーク入力など)、min-max を使用します。
「赤/緑/青」や「ロンドン/マンチェスター/バーミンガム」などのカテゴリには、自然な数値順序がありません。ほとんどのアルゴリズムにフィードする前に、それらをエンコードする必要があります。
カテゴリごとにバイナリ列を作成します。カテゴリに固有の順序がない場合に最適です。
import pandas as pd
# Original: colour = ['red', 'green', 'blue', 'red']
df = pd.DataFrame({'colour': ['red', 'green', 'blue', 'red']})
encoded = pd.get_dummies(df['colour'], prefix='colour')
print(encoded)
# colour_blue colour_green colour_red
# 0 0 0 1
# 1 0 1 0
# 2 1 0 0
# 3 0 0 1
ログイン ディスカッションに参加
各カテゴリを整数にマップします。カテゴリに自然な順序がある場合にのみ使用します。
from sklearn.preprocessing import LabelEncoder
# Works for: ['low', 'medium', 'high'] → [0, 1, 2]
# DANGER: Don't use for unordered categories like cities —
# the model will incorrectly assume London(0) < Paris(1) < Tokyo(2)
le = LabelEncoder()
df['education_level'] = le.fit_transform(df['education_level'])
# 'school' → 0, 'undergraduate' → 1, 'postgraduate' → 2
欠損データは、現実世界のデータセットではほぼ普遍的に存在します。いくつかのオプションがあります。
欠損値を計算された代替値で埋めます。
from sklearn.impute import SimpleImputer
import numpy as np
# Strategy options: 'mean', 'median', 'most_frequent', 'constant'
imputer = SimpleImputer(strategy='median')
X_imputed = imputer.fit_transform(X)
# For numerical data: median is robust to outliers
# For categorical data: use 'most_frequent'
データが欠落しているという事実自体が有益な場合もあります。
# Create a binary flag: 1 if income was missing, 0 if present
df['income_missing'] = df['income'].isna().astype(int)
# Then impute the original column
df['income'].fillna(df['income'].median(), inplace=True)
欠損値が約 70 ~ 80% を超える場合、列を削除します (有益なことはほとんどありません)。行を削除するのは、データが大量にあり、行がランダムに (体系的にではなく) 欠落している場合にのみです。
ここで特徴量エンジニアリングが芸術となります。ドメインの知識を使用して、生データには表示されないパターンを捕捉する機能を作成します。
df['purchase_datetime'] = pd.to_datetime(df['purchase_datetime'])
# Extract meaningful components
df['hour_of_day'] = df['purchase_datetime'].dt.hour
df['day_of_week'] = df['purchase_datetime'].dt.dayofweek
df['is_weekend'] = (df['day_of_week'] >= 5).astype(int)
df['month'] = df['purchase_datetime'].dt.month
df['is_holiday'] = df['purchase_datetime'].isin(uk_holidays).astype(int)
生のタイムスタンプはモデルに直接何も伝えません。これらの派生機能により、「午前 3 時に詐欺のピーク」や「週末の売上の急増」などのパターンが明らかになります。
# Square footage × number of bathrooms might be more predictive
# than either feature alone for house prices
df['size_per_bathroom'] = df['sqft'] / df['bathrooms']
# Ratio of income to loan amount — a classic credit risk feature
df['debt_to_income'] = df['loan_amount'] / df['annual_income']
from sklearn.feature_extraction.text import TfidfVectorizer
# Convert raw text to numerical feature matrix
vectorizer = TfidfVectorizer(max_features=1000, stop_words='english')
text_features = vectorizer.fit_transform(df['review_text'])
# Each word becomes a feature; its value reflects how important
# the word is in that document relative to the whole corpus
モデルを構築したら、どの機能が最も有用であるかを測定できます。これは、モデルを理解する上でも、どの機能を維持するか削除するかを決定する上でも役立ちます。
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# Get feature importances
importances = pd.Series(
model.feature_importances_,
index=feature_names
).sort_values(ascending=False)
print(importances.head(10))
# income_to_debt_ratio 0.187
# days_since_last_payment 0.143
# credit_utilisation 0.121
# ...
重要性がほぼゼロの特徴は、通常、パフォーマンスに影響を与えることなく削除できます。また、それらを削除すると、モデルが簡素化され、トレーニング時間が短縮され、一般化が向上します。
何百、何千もの特徴がある場合、モデルは苦戦する可能性があります。これは次元の呪いと呼ばれます。次元削減技術は、最も重要なパターンを維持しながら特徴を圧縮します。
from sklearn.decomposition import PCA
# Reduce 100 features to 10 components that capture 95% of variance
pca = PCA(n_components=10)
X_reduced = pca.fit_transform(X_scaled)
print(f"Variance explained: {pca.explained_variance_ratio_.sum():.2%}")
# Variance explained: 94.7%
PCA (主成分分析) が最も一般的なアプローチですが、他のアプローチには t-SNE (視覚化用) や UMAP (局所構造の保存用) などがあります。
50 の異なる都市名を含む「city」列を持つデータセットがあります。どのようなエンコード手法を使用する必要がありますか?