第3章:Pythonで体験する機械学習

実践的な予測モデル構築 - 環境構築から実プロジェクトまで

📖 読了時間: 30-40分 📊 難易度: 中級 💻 コード例: 35個 📝 演習問題: 5問

この章では、Pythonを使って実際に機械学習モデルを構築します。環境構築から始めて、回帰・分類問題の実装、モデル比較、ハイパーパラメータチューニング、そして実プロジェクトまで、35個の実行可能なコード例で学びます。

学習目標

3.1 環境構築:3つの選択肢

機械学習を実践するには、まずPython環境を構築する必要があります。状況に応じて3つの選択肢から選べます。

3.1.1 Option 1: Anaconda(初心者推奨)

特徴:

インストール手順:

# コード例1: Anaconda環境構築

# 1. Anacondaをダウンロード
# 公式サイト: https://www.anaconda.com/download
# Python 3.11以上を選択

# 2. インストール後、Anaconda Promptを起動

# 3. 仮想環境を作成(ML専用環境)
conda create -n ml_env python=3.11

# 4. 環境を有効化
conda activate ml_env

# 5. 必要なライブラリをインストール
conda install numpy pandas matplotlib seaborn scikit-learn jupyter

# 6. 動作確認
python --version
# 期待される出力: Python 3.11.x

# 7. Jupyter Notebookを起動
jupyter notebook

成功の確認: コマンドプロンプトで (ml_env) というプレフィックスが表示されれば、環境が正しくアクティブ化されています。

Anacondaの利点と欠点:

利点欠点
NumPy、SciPyなどが最初から含まれるファイルサイズが大きい(3GB以上)
依存関係の問題が少ないインストールに時間がかかる
Anaconda Navigatorで視覚的に管理可能ディスク容量を消費

3.1.2 Option 2: venv(Python標準)

特徴:

Tips: requirements.txtを作成しておくと、環境の再現が簡単になります。

# requirements.txt
numpy>=1.24.0
pandas>=2.0.0
matplotlib>=3.7.0
seaborn>=0.12.0
scikit-learn>=1.3.0
jupyter>=1.0.0

インストール: pip install -r requirements.txt

3.1.3 Option 3: Google Colab(インストール不要)

特徴:

3.1.4 環境選択ガイド

状況推奨オプション理由
初めてのPython環境Anaconda環境構築が簡単、トラブルが少ない
既にPython環境があるvenv軽量、プロジェクトごとに独立
今すぐ試したいGoogle Colabインストール不要、即座に開始可能
GPU計算が必要Google Colab無料でGPUアクセス可能

3.2 データの準備と可視化

機械学習の最初のステップは、データを正しく読み込み、理解することです。

3.2.1 データの読み込みと基本操作

# コード例4: Irisデータセットの読み込み

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris

# Irisデータセット(アヤメの品種分類)を読み込み
iris = load_iris()

# DataFrameに変換
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target

# データの先頭5行を表示
print("データの先頭5行:")
print(df.head())

# 期待される出力:
#    sepal length (cm)  sepal width (cm)  ...  petal width (cm)  target
# 0                5.1               3.5  ...               0.2       0
# 1                4.9               3.0  ...               0.2       0
# 2                4.7               3.2  ...               0.2       0
# 3                4.6               3.1  ...               0.2       0
# 4                5.0               3.6  ...               0.2       0

# データの形状を確認
print(f"\nデータの形状: {df.shape}")
# 出力: データの形状: (150, 5)
# 150サンプル、5列(特徴量4つ + ターゲット1つ)


# コード例5: 基本統計量の確認

# 基本統計量を表示
print("基本統計量:")
print(df.describe())

# 期待される出力:
#        sepal length (cm)  sepal width (cm)  ...  petal width (cm)     target
# count         150.000000        150.000000  ...        150.000000  150.000000
# mean            5.843333          3.057333  ...          1.199333    1.000000
# std             0.828066          0.435866  ...          0.762238    0.819232
# min             4.300000          2.000000  ...          0.100000    0.000000
# 25%             5.100000          2.800000  ...          0.300000    0.000000
# 50%             5.800000          3.000000  ...          1.300000    1.000000
# 75%             6.400000          3.300000  ...          1.800000    2.000000
# max             7.900000          4.400000  ...          2.500000    2.000000

# データ型を確認
print("\nデータ型:")
print(df.dtypes)

# 欠損値を確認
print("\n欠損値の数:")
print(df.isnull().sum())
# 期待される出力: すべて0(Irisデータセットには欠損値がない)

3.2.2 データの可視化

# コード例6: ヒストグラム(分布の可視化)

import matplotlib.pyplot as plt

# 各特徴量のヒストグラムを作成
df.hist(figsize=(12, 8), bins=20, edgecolor='black')
plt.suptitle('Iris Dataset - Feature Distributions', fontsize=16)
plt.tight_layout()
plt.show()

# 解釈:
# - sepal length: 5-6cmあたりにピーク
# - petal length: 二峰性(品種による違いが大きい)


# コード例7: 散布図マトリックス(特徴量間の関係)

import seaborn as sns

# 散布図マトリックス(pairplot)
sns.pairplot(df, hue='target', palette='Set1', markers=['o', 's', 'D'])
plt.suptitle('Iris Dataset - Pairplot by Species', y=1.02)
plt.show()

# 解釈:
# - petal length vs petal width: 品種がきれいに分離
# - sepal length vs sepal width: 一部重なりあり
# ⇒ petal系の特徴量が分類に有効

3.3 回帰問題:住宅価格予測

回帰問題では、連続値(価格、温度など)を予測します。カリフォルニア住宅価格データで線形回帰を実装します。

3.3.1 データの準備

# コード例8: データ準備(カリフォルニア住宅価格)

from sklearn.datasets import fetch_california_housing

# カリフォルニア住宅価格データセットを読み込み
housing = fetch_california_housing()
X = housing.data  # 特徴量(8次元)
y = housing.target  # ターゲット(住宅価格、単位: $100,000)

# データの確認
print("特徴量の形状:", X.shape)  # (20640, 8)
print("ターゲットの形状:", y.shape)  # (20640,)
print("\n特徴量名:")
print(housing.feature_names)
# ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms',
#  'Population', 'AveOccup', 'Latitude', 'Longitude']

print("\n最初の3サンプル:")
print(X[:3])
print("対応する価格:", y[:3])
# 価格: [4.526 3.585 3.521] (単位: $100,000)

3.3.2 データ分割

# コード例9: 訓練データとテストデータに分割

from sklearn.model_selection import train_test_split

# データを訓練80%、テスト20%に分割
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print("訓練データ:", X_train.shape)  # (16512, 8)
print("テストデータ:", X_test.shape)  # (4128, 8)

# random_state=42: 再現性のため固定
# test_size=0.2: 一般的な分割比率

3.3.3 特徴量のスケーリング

# コード例10: 特徴量のスケーリング(標準化)

from sklearn.preprocessing import StandardScaler

# StandardScaler: 平均0、標準偏差1に変換
scaler = StandardScaler()

# 訓練データでfit(平均・標準偏差を計算)
X_train_scaled = scaler.fit_transform(X_train)

# テストデータは訓練データの統計量でtransform
X_test_scaled = scaler.transform(X_test)

print("スケーリング前(最初の1サンプル):")
print(X_train[0])
print("\nスケーリング後:")
print(X_train_scaled[0])

# 重要: テストデータでfitしない(データリークを防ぐ)

3.3.4 線形回帰モデルの訓練

# コード例11: 線形回帰モデルの訓練

from sklearn.linear_model import LinearRegression

# モデルのインスタンス化
model = LinearRegression()

# 訓練データでモデルを学習
model.fit(X_train_scaled, y_train)

# 学習したパラメータを確認
print("切片(bias):", model.intercept_)
print("\n係数(weights):")
for feature, coef in zip(housing.feature_names, model.coef_):
    print(f"  {feature:12s}: {coef:7.4f}")

# 期待される出力例:
# MedInc      :  0.8296  (所得が高いほど価格が上がる)
# Latitude    : -0.8231  (緯度が高いほど価格が下がる)

3.3.5 予測と評価

# コード例12: 予測の実行

# テストデータで予測
y_pred = model.predict(X_test_scaled)

# 最初の5件の予測を確認
print("実際の価格 vs 予測価格(最初の5件):")
for i in range(5):
    print(f"実際: {y_test[i]:.3f}, 予測: {y_pred[i]:.3f}")

# 期待される出力例:
# 実際: 4.526, 予測: 4.321
# 実際: 3.585, 予測: 3.712
# 実際: 3.521, 予測: 3.498
# 実際: 3.413, 予測: 3.289
# 実際: 3.422, 予測: 3.501


# コード例13: モデルの評価

from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error

# 平均二乗誤差(MSE)
mse = mean_squared_error(y_test, y_pred)

# 平均二乗平方根誤差(RMSE)
rmse = np.sqrt(mse)

# 平均絶対誤差(MAE)
mae = mean_absolute_error(y_test, y_pred)

# 決定係数(R²)
r2 = r2_score(y_test, y_pred)

print("モデル評価指標:")
print(f"  RMSE: {rmse:.3f}")  # 小さいほど良い
print(f"  MAE:  {mae:.3f}")   # 小さいほど良い
print(f"  R²:   {r2:.3f}")    # 1に近いほど良い(最大1.0)

# 期待される出力:
# RMSE: 0.729(約$72,900の誤差)
# MAE:  0.526(約$52,600の誤差)
# R²:   0.576(57.6%の分散を説明)

3.3.6 予測結果の可視化

# コード例14: 予測vs実測のプロット

plt.figure(figsize=(10, 6))

# 散布図
plt.scatter(y_test, y_pred, alpha=0.5, edgecolor='black')

# 理想的な予測線(y=x)
plt.plot([y_test.min(), y_test.max()],
         [y_test.min(), y_test.max()],
         'r--', lw=2, label='Perfect Prediction')

plt.xlabel('Actual Price ($100,000)', fontsize=12)
plt.ylabel('Predicted Price ($100,000)', fontsize=12)
plt.title('Linear Regression: Predictions vs Actual', fontsize=14)
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

# 解釈:
# - 点が赤線に近いほど予測精度が高い
# - 低価格帯(0-2): 予測精度が高い
# - 高価格帯(4以上): 予測が過小評価傾向

3.4 分類問題:Iris品種分類

分類問題では、カテゴリ(品種、良/不良など)を予測します。複数のモデルを比較します。

3.4.1 ロジスティック回帰

# コード例15: ロジスティック回帰による分類

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

# データ準備
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# ロジスティック回帰モデル
lr_model = LogisticRegression(max_iter=200, random_state=42)
lr_model.fit(X_train, y_train)

# 予測
lr_pred = lr_model.predict(X_test)

# 確率も取得可能
lr_proba = lr_model.predict_proba(X_test)

print("最初の3サンプルの予測:")
for i in range(3):
    print(f"実際: {y_test[i]}, 予測: {lr_pred[i]}, 確率: {lr_proba[i]}")

# 期待される出力例:
# 実際: 1, 予測: 1, 確率: [0.00 0.79 0.21]
# 実際: 0, 予測: 0, 確率: [0.97 0.03 0.00]
# 実際: 2, 予測: 2, 確率: [0.00 0.01 0.99]

3.4.2 精度評価

# コード例16: 分類精度の評価

from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# 精度(Accuracy)
accuracy = accuracy_score(y_test, lr_pred)
print(f"Accuracy: {accuracy:.3f}")  # 期待: 1.000(100%)

# 詳細な分類レポート
print("\n分類レポート:")
print(classification_report(y_test, lr_pred, target_names=iris.target_names))

# 期待される出力:
#               precision    recall  f1-score   support
#     setosa       1.00      1.00      1.00        10
# versicolor       1.00      1.00      1.00         9
#  virginica       1.00      1.00      1.00        11
#   accuracy                           1.00        30

# precision: 正解と予測した中で実際に正解だった割合
# recall: 実際の正解の中で正しく予測できた割合
# f1-score: precisionとrecallの調和平均

3.4.3 混同行列の可視化

# コード例17: 混同行列(Confusion Matrix)

import seaborn as sns

# 混同行列を計算
cm = confusion_matrix(y_test, lr_pred)

# ヒートマップで可視化
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=iris.target_names,
            yticklabels=iris.target_names)
plt.title('Confusion Matrix - Logistic Regression', fontsize=14)
plt.ylabel('True Label', fontsize=12)
plt.xlabel('Predicted Label', fontsize=12)
plt.tight_layout()
plt.show()

# 解釈:
# - 対角線: 正しく分類された数
# - 対角線以外: 誤分類
# - Irisデータは単純なので誤分類がほぼゼロ

3.4.4 決定木

# コード例18: 決定木による分類

from sklearn.tree import DecisionTreeClassifier

# 決定木モデル(深さ3に制限)
dt_model = DecisionTreeClassifier(max_depth=3, random_state=42)
dt_model.fit(X_train, y_train)

# 予測
dt_pred = dt_model.predict(X_test)

# 精度
dt_accuracy = accuracy_score(y_test, dt_pred)
print(f"Decision Tree Accuracy: {dt_accuracy:.3f}")
# 期待: 1.000

# 決定木の利点: 解釈性が高い
# 特徴量の重要度を確認
print("\n特徴量の重要度:")
for feature, importance in zip(iris.feature_names, dt_model.feature_importances_):
    print(f"  {feature:20s}: {importance:.3f}")

3.4.5 ランダムフォレスト

# コード例19: ランダムフォレスト(アンサンブル学習)

from sklearn.ensemble import RandomForestClassifier

# ランダムフォレスト(100本の決定木)
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# 予測
rf_pred = rf_model.predict(X_test)

# 精度
rf_accuracy = accuracy_score(y_test, rf_pred)
print(f"Random Forest Accuracy: {rf_accuracy:.3f}")
# 期待: 1.000

# ランダムフォレストの利点:
# - 決定木より過学習しにくい
# - 高い予測精度
# - 特徴量の重要度が安定

3.4.6 特徴量の重要度可視化

# コード例20: 特徴量の重要度(Feature Importance)

# ランダムフォレストの特徴量重要度
importances = rf_model.feature_importances_
feature_names = iris.feature_names

# 降順にソート
indices = np.argsort(importances)[::-1]

# 棒グラフで可視化
plt.figure(figsize=(10, 6))
plt.barh(range(len(importances)), importances[indices], color='skyblue', edgecolor='black')
plt.yticks(range(len(importances)), [feature_names[i] for i in indices])
plt.xlabel('Importance', fontsize=12)
plt.title('Feature Importance (Random Forest)', fontsize=14)
plt.tight_layout()
plt.show()

# 解釈:
# - petal width (cm): 最も重要(0.45)
# - petal length (cm): 2番目に重要(0.42)
# ⇒ 花びらの特徴が品種分類に最も有効

3.4.7 サポートベクターマシン(SVM)

# コード例21: SVM(Support Vector Machine)

from sklearn.svm import SVC

# SVMモデル(RBFカーネル)
svm_model = SVC(kernel='rbf', random_state=42)
svm_model.fit(X_train, y_train)

# 予測
svm_pred = svm_model.predict(X_test)

# 精度
svm_accuracy = accuracy_score(y_test, svm_pred)
print(f"SVM Accuracy: {svm_accuracy:.3f}")
# 期待: 1.000

# SVMの利点:
# - 高次元データに強い
# - カーネルトリックで非線形分離可能
# 欠点:
# - 大規模データでは遅い
# - 確率予測が標準でない

3.5 モデル比較と選択

複数のモデルを公平に比較し、最適なモデルを選択します。

3.5.1 クロスバリデーション

# コード例22: クロスバリデーションによるモデル比較

from sklearn.model_selection import cross_val_score

# 比較するモデル
models = {
    'Logistic Regression': LogisticRegression(max_iter=200),
    'Decision Tree': DecisionTreeClassifier(max_depth=3),
    'Random Forest': RandomForestClassifier(n_estimators=100),
    'SVM': SVC(kernel='rbf')
}

print("5-Fold Cross-Validation Results:")
print("-" * 50)

results = []
for name, model in models.items():
    # 5分割クロスバリデーション
    scores = cross_val_score(model, X_train, y_train, cv=5)

    results.append({
        'Model': name,
        'Mean': scores.mean(),
        'Std': scores.std()
    })

    print(f"{name:20s}: {scores.mean():.3f} (+/- {scores.std():.3f})")

# 期待される出力:
# Logistic Regression : 0.967 (+/- 0.033)
# Decision Tree       : 0.958 (+/- 0.042)
# Random Forest       : 0.967 (+/- 0.025)
# SVM                 : 0.975 (+/- 0.025)

3.5.2 学習曲線

# コード例23: 学習曲線(Learning Curve)

from sklearn.model_selection import learning_curve

# ランダムフォレストの学習曲線
train_sizes, train_scores, val_scores = learning_curve(
    RandomForestClassifier(n_estimators=100, random_state=42),
    X_train, y_train, cv=5, n_jobs=-1,
    train_sizes=np.linspace(0.1, 1.0, 10)
)

# 平均と標準偏差を計算
train_mean = train_scores.mean(axis=1)
train_std = train_scores.std(axis=1)
val_mean = val_scores.mean(axis=1)
val_std = val_scores.std(axis=1)

# プロット
plt.figure(figsize=(10, 6))
plt.plot(train_sizes, train_mean, label='Training score', color='blue', marker='o')
plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std,
                 alpha=0.15, color='blue')
plt.plot(train_sizes, val_mean, label='Validation score', color='red', marker='s')
plt.fill_between(train_sizes, val_mean - val_std, val_mean + val_std,
                 alpha=0.15, color='red')

plt.xlabel('Training Size', fontsize=12)
plt.ylabel('Score', fontsize=12)
plt.title('Learning Curve - Random Forest', fontsize=14)
plt.legend(loc='best')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

# 解釈:
# - 訓練スコアと検証スコアが近い ⇒ 良好な汎化性能
# - 検証スコアが収束 ⇒ データを追加しても改善は限定的

3.5.3 モデル性能比較表

# コード例24: モデル性能の包括的比較

import time

# 比較結果を格納
results = []

for name, model in models.items():
    # 訓練時間を計測
    start_time = time.time()
    model.fit(X_train, y_train)
    train_time = time.time() - start_time

    # 予測時間を計測
    start_time = time.time()
    pred = model.predict(X_test)
    predict_time = time.time() - start_time

    # 精度
    acc = accuracy_score(y_test, pred)

    results.append({
        'Model': name,
        'Accuracy': acc,
        'Train Time (s)': train_time,
        'Predict Time (s)': predict_time
    })

# DataFrameに変換して表示
results_df = pd.DataFrame(results)
results_df = results_df.sort_values('Accuracy', ascending=False)

print("\nモデル性能比較:")
print(results_df.to_string(index=False))

# 期待される出力例:
#                 Model  Accuracy  Train Time (s)  Predict Time (s)
#                   SVM     1.000           0.002             0.001
#     Random Forest     1.000           0.158             0.012
# Logistic Regression     1.000           0.005             0.001
#         Decision Tree     1.000           0.002             0.001

3.6 ハイパーパラメータチューニング

モデルの性能を最大化するため、ハイパーパラメータ(学習前に設定するパラメータ)を調整します。

# コード例25: Grid Search(全探索)

from sklearn.model_selection import GridSearchCV

# ランダムフォレストのハイパーパラメータ候補
param_grid = {
    'n_estimators': [50, 100, 200],          # 木の数
    'max_depth': [3, 5, 10, None],           # 木の深さ
    'min_samples_split': [2, 5, 10]          # 分割に必要な最小サンプル数
}

# Grid Search(5分割クロスバリデーション)
grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid, cv=5, n_jobs=-1, verbose=1
)

# 実行(3×4×3=36通りの組み合わせを試す)
grid_search.fit(X_train, y_train)

# 最良のパラメータ
print("最良のパラメータ:")
print(grid_search.best_params_)
# 期待: {'max_depth': 5, 'min_samples_split': 2, 'n_estimators': 100}

# 最良のスコア
print(f"\n最良のCV Score: {grid_search.best_score_:.3f}")
# 期待: 0.967

3.6.2 最良モデルの評価

# コード例26: チューニング後のモデル評価

# 最良のモデルを取得
best_model = grid_search.best_estimator_

# テストデータで評価
best_pred = best_model.predict(X_test)
best_accuracy = accuracy_score(y_test, best_pred)

print(f"Test Accuracy (tuned model): {best_accuracy:.3f}")
# 期待: 1.000

# チューニング前との比較
print(f"Test Accuracy (default):     {rf_accuracy:.3f}")
# 期待: 1.000

# Irisデータは単純なので差が出にくいが、
# 複雑なデータセットではチューニングで5-10%改善することも
# コード例27: Random Search(効率的サンプリング)

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

# パラメータの分布を定義
param_dist = {
    'n_estimators': randint(50, 300),        # 50-300の範囲でランダム
    'max_depth': randint(3, 20),             # 3-20の範囲でランダム
    'min_samples_split': randint(2, 20)      # 2-20の範囲でランダム
}

# Random Search(20回のランダムサンプリング)
random_search = RandomizedSearchCV(
    RandomForestClassifier(random_state=42),
    param_dist, n_iter=20, cv=5, random_state=42, n_jobs=-1
)

random_search.fit(X_train, y_train)

print("Random Search 最良のパラメータ:")
print(random_search.best_params_)

print(f"\n最良のCV Score: {random_search.best_score_:.3f}")

# Random Searchの利点:
# - Grid Searchより高速(20回 vs 36回)
# - 広い探索空間をカバー可能
# - 連続値パラメータにも対応

3.6.4 ハイパーパラメータ効果の可視化

# コード例28: ハイパーパラメータ効果のヒートマップ

# Grid Searchの結果をDataFrameに変換
results_df = pd.DataFrame(grid_search.cv_results_)

# n_estimatorsとmax_depthの効果を可視化
pivot = results_df.pivot_table(
    values='mean_test_score',
    index='param_max_depth',
    columns='param_n_estimators'
)

# ヒートマップ
plt.figure(figsize=(10, 6))
sns.heatmap(pivot, annot=True, fmt='.3f', cmap='YlGnBu', cbar_kws={'label': 'CV Score'})
plt.title('Grid Search Results: max_depth vs n_estimators', fontsize=14)
plt.xlabel('n_estimators', fontsize=12)
plt.ylabel('max_depth', fontsize=12)
plt.tight_layout()
plt.show()

# 解釈:
# - max_depth=5, n_estimators=100: 最高スコア
# - max_depth=None(制限なし): 過学習のリスク

3.7 特徴量エンジニアリング

生の特徴量を変換・拡張することで、モデルの性能を向上させます。

3.7.1 多項式特徴量

# コード例29: 多項式特徴量(Polynomial Features)

from sklearn.preprocessing import PolynomialFeatures

# サンプルデータ
X_simple = np.array([[1, 2], [3, 4], [5, 6]])
print("元の特徴量:")
print(X_simple)
print("形状:", X_simple.shape)  # (3, 2)

# 2次の多項式特徴量を生成
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_simple)

print("\n多項式特徴量:")
print(X_poly)
print("形状:", X_poly.shape)  # (3, 5)

# 特徴量名を確認
print("\n特徴量名:")
print(poly.get_feature_names_out(['x1', 'x2']))
# ['x1', 'x2', 'x1^2', 'x1*x2', 'x2^2']

# 解釈:
# - 元: [1, 2] → 拡張: [1, 2, 1, 2, 4]
# - x1^2, x1*x2, x2^2 などの相互作用項を追加

3.7.2 特徴量選択

# コード例30: 特徴量選択(Feature Selection)

from sklearn.feature_selection import SelectKBest, f_classif

# Irisデータで最も重要な2つの特徴量を選択
selector = SelectKBest(f_classif, k=2)
X_selected = selector.fit_transform(iris.data, iris.target)

# 選択された特徴量を確認
selected_features = np.array(iris.feature_names)[selector.get_support()]
print("選択された特徴量:")
print(selected_features)
# 期待: ['petal length (cm)', 'petal width (cm)']

# F値(ANOVA)スコアを確認
print("\n各特徴量のF値:")
for feature, score in zip(iris.feature_names, selector.scores_):
    print(f"  {feature:20s}: {score:7.2f}")

# 解釈:
# - petal length: 1179.03(最も重要)
# - petal width: 960.01(2番目に重要)
# - sepal系は相対的に重要度が低い

3.7.3 主成分分析(PCA)

# コード例31: PCA(次元削減)

from sklearn.decomposition import PCA

# Irisデータ(4次元)を2次元に削減
pca = PCA(n_components=2)
X_pca = pca.fit_transform(iris.data)

print("元の次元:", iris.data.shape)  # (150, 4)
print("PCA後の次元:", X_pca.shape)   # (150, 2)

# 説明された分散の割合
print("\n各主成分の寄与率:")
print(pca.explained_variance_ratio_)
# 期待: [0.92, 0.05](第1主成分で92%を説明)

# 累積寄与率
print(f"累積寄与率: {pca.explained_variance_ratio_.sum():.3f}")
# 期待: 0.977(97.7%の情報を保持)

# 可視化
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=iris.target,
                      cmap='viridis', edgecolor='black', s=50)
plt.xlabel('First Principal Component', fontsize=12)
plt.ylabel('Second Principal Component', fontsize=12)
plt.title('PCA of Iris Dataset', fontsize=14)
plt.colorbar(scatter, label='Species')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

# 解釈:
# - 4次元を2次元に削減しても97.7%の情報を保持
# - 品種がきれいに分離される

3.7.4 モデルベースの特徴量重要度

# コード例32: モデルベースの特徴量重要度分析

# ランダムフォレストで特徴量重要度を計算
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(iris.data, iris.target)

# 特徴量重要度をDataFrameに整理
feature_importance_df = pd.DataFrame({
    'feature': iris.feature_names,
    'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)

print("特徴量重要度ランキング:")
print(feature_importance_df)

# 期待される出力:
#               feature  importance
# 2  petal length (cm)       0.445
# 3   petal width (cm)       0.425
# 0  sepal length (cm)       0.089
# 1   sepal width (cm)       0.041

# 可視化
plt.figure(figsize=(10, 6))
plt.barh(feature_importance_df['feature'], feature_importance_df['importance'],
         color='coral', edgecolor='black')
plt.xlabel('Importance', fontsize=12)
plt.title('Feature Importance (Random Forest)', fontsize=14)
plt.tight_layout()
plt.show()

3.8 トラブルシューティング

機械学習の実装でよくあるエラーと解決策をまとめます。

3.8.1 よくあるエラーと解決策

エラー原因解決策
ModuleNotFoundErrorライブラリ未インストールpip install [library_name]
ValueError: shape mismatchデータの次元が合わないX.shapey.shapeを確認
ConvergenceWarning最適化が収束しないmax_iterを増やす、またはスケーリング
低精度(<0.5)データ品質、モデル選択データ確認、特徴量追加、モデル変更
過学習(train>test)モデルが複雑すぎる正則化、データ追加、CV使用
MemoryErrorデータサイズ過大バッチ処理、データサンプリング
訓練が遅いデータ量、モデル複雑度n_jobs=-1、GPU使用、モデル簡略化

3.8.2 デバッグチェックリスト

5ステップデバッグ手順:

  1. データ確認 : df.head(), df.info(), df.describe()
  2. 欠損値チェック : df.isnull().sum()
  3. データ型確認 : df.dtypes(数値型になっているか)
  4. 形状確認 : X.shape, y.shape(次元が一致するか)
  5. 簡単なモデルで試す : まずLogisticRegressionで動作確認

3.9 プロジェクトチャレンジ:Titanic生存予測

ここまで学んだ技術を使って、実際のデータサイエンスプロジェクトに挑戦します。

3.9.1 プロジェクト概要

プロジェクト目標:

3.9.2 データ読み込みとEDA

# コード例33: Titanicデータの探索的データ分析(EDA)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# データ読み込み(実際にはKaggleからダウンロード)
# ここではサンプルデータを作成
# 実際のプロジェクトでは以下のURLからダウンロード:
# https://www.kaggle.com/c/titanic/data

# サンプルデータ作成(デモ用)
np.random.seed(42)
df = pd.DataFrame({
    'PassengerId': range(1, 892),
    'Survived': np.random.choice([0, 1], 891),
    'Pclass': np.random.choice([1, 2, 3], 891),
    'Sex': np.random.choice(['male', 'female'], 891),
    'Age': np.random.normal(30, 15, 891).clip(0, 80),
    'SibSp': np.random.choice([0, 1, 2, 3], 891),
    'Parch': np.random.choice([0, 1, 2], 891),
    'Fare': np.random.exponential(30, 891),
    'Embarked': np.random.choice(['S', 'C', 'Q'], 891)
})

# 欠損値をランダムに追加
df.loc[np.random.choice(df.index, 177, replace=False), 'Age'] = np.nan
df.loc[np.random.choice(df.index, 2, replace=False), 'Embarked'] = np.nan

print("データの先頭5行:")
print(df.head())

print("\nデータの基本情報:")
print(df.info())

print("\n基本統計量:")
print(df.describe())

print("\n欠損値の確認:")
print(df.isnull().sum())

# 期待される出力:
# Age: 177件の欠損値
# Embarked: 2件の欠損値

3.9.3 データ前処理

# コード例34: データ前処理と特徴量エンジニアリング

from sklearn.preprocessing import LabelEncoder

# データのコピーを作成
df_processed = df.copy()

# 1. 欠損値の処理
# Age: 中央値で補完
df_processed['Age'].fillna(df_processed['Age'].median(), inplace=True)

# Embarked: 最頻値で補完
df_processed['Embarked'].fillna(df_processed['Embarked'].mode()[0], inplace=True)

# 2. 特徴量エンジニアリング
# 家族サイズ = SibSp(兄弟・配偶者) + Parch(親・子供) + 1(本人)
df_processed['FamilySize'] = df_processed['SibSp'] + df_processed['Parch'] + 1

# 一人旅かどうか
df_processed['IsAlone'] = (df_processed['FamilySize'] == 1).astype(int)

# 年齢層(カテゴリ化)
df_processed['AgeGroup'] = pd.cut(df_processed['Age'],
                                   bins=[0, 12, 18, 60, 100],
                                   labels=['Child', 'Teen', 'Adult', 'Senior'])

# 3. カテゴリ変数のエンコーディング
# Sex: LabelEncoder(male=1, female=0)
le = LabelEncoder()
df_processed['Sex'] = le.fit_transform(df_processed['Sex'])

# Embarked: One-Hot Encoding
df_processed = pd.get_dummies(df_processed, columns=['Embarked'], prefix='Embarked')

# AgeGroup: One-Hot Encoding
df_processed = pd.get_dummies(df_processed, columns=['AgeGroup'], prefix='Age')

# 4. 不要な列を削除
df_processed.drop(['PassengerId', 'SibSp', 'Parch'], axis=1, inplace=True)

print("前処理後のデータ:")
print(df_processed.head())
print("\n形状:", df_processed.shape)
print("\n欠損値:", df_processed.isnull().sum().sum())  # 期待: 0

3.9.4 モデル訓練と評価

# コード例35: 複数モデルの訓練と比較

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# 特徴量とターゲットを分離
X = df_processed.drop('Survived', axis=1)
y = df_processed['Survived']

# データ分割(訓練80%, テスト20%)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 複数モデルを定義
models = {
    'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
    'Decision Tree': DecisionTreeClassifier(max_depth=5, random_state=42),
    'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
    'SVM': SVC(kernel='rbf', random_state=42)
}

print("モデル比較結果:")
print("-" * 70)

results = []
for name, model in models.items():
    # 訓練
    model.fit(X_train, y_train)

    # 予測
    y_pred = model.predict(X_test)

    # 評価
    accuracy = accuracy_score(y_test, y_pred)

    # クロスバリデーション
    cv_scores = cross_val_score(model, X_train, y_train, cv=5)

    results.append({
        'Model': name,
        'Test Accuracy': accuracy,
        'CV Mean': cv_scores.mean(),
        'CV Std': cv_scores.std()
    })

    print(f"{name:20s}: Test={accuracy:.3f}, CV={cv_scores.mean():.3f} (+/-{cv_scores.std():.3f})")

# 結果をDataFrameに変換
results_df = pd.DataFrame(results).sort_values('Test Accuracy', ascending=False)

print("\n最終結果(精度順):")
print(results_df.to_string(index=False))

# 最良モデルの詳細レポート
best_model_name = results_df.iloc[0]['Model']
best_model = models[best_model_name]
best_pred = best_model.predict(X_test)

print(f"\n{best_model_name} の詳細:")
print(classification_report(y_test, best_pred))

# 混同行列
cm = confusion_matrix(y_test, best_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title(f'Confusion Matrix - {best_model_name}')
plt.ylabel('True Label')
plt.xlabel('Predicted Label')
plt.tight_layout()
plt.show()

# プロジェクト成功判定
if results_df.iloc[0]['Test Accuracy'] >= 0.80:
    print("\n🎉 プロジェクト成功!目標精度80%を達成しました!")
else:
    print(f"\n目標精度80%まであと{0.80 - results_df.iloc[0]['Test Accuracy']:.1%}です。")
    print("改善のヒント: 特徴量追加、ハイパーパラメータチューニング、アンサンブル学習")

3.9.5 プロジェクト拡張アイデア

さらなる挑戦:

  1. 特徴量の追加 : 名前からタイトル(Mr., Mrs.等)を抽出
  2. ハイパーパラメータチューニング : GridSearchCVで最適化
  3. アンサンブル学習 : VotingClassifierで複数モデルを組み合わせ
  4. 特徴量重要度分析 : どの特徴量が生存に影響したか分析
  5. Kaggleサブミット : 実際のKaggleコンペに提出
  6. 深層学習 : ニューラルネットワーク(Keras/PyTorch)で実装

本章のまとめ

この章では、Pythonを使った機械学習の実践的な流れを学びました。

習得したスキル

重要なポイント

機械学習プロジェクトの5ステップ:

  1. データ理解 : EDA、可視化、統計量確認
  2. 前処理 : 欠損値処理、スケーリング、エンコーディング
  3. モデル選択 : 複数モデルを試して比較
  4. 評価・改善 : CV、チューニング、特徴量エンジニアリング
  5. デプロイ準備 : 最良モデルの保存、ドキュメント作成

次のステップ

第4章では、機械学習の実世界への応用を学びます:

演習問題

問題1(難易度:Easy) - データ読み込みと基本統計

問題: Irisデータセットを読み込み、各特徴量の平均値、中央値、標準偏差を計算してください。

ヒント: df.describe()を使うと一度に確認できます。

解答例

from sklearn.datasets import load_iris
import pandas as pd

# データ読み込み
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)

# 基本統計量
print(df.describe())

# 個別に計算する場合
print("\n平均値:")
print(df.mean())

print("\n中央値:")
print(df.median())

print("\n標準偏差:")
print(df.std())

問題2(難易度:Easy) - 訓練データとテストデータの分割

問題: カリフォルニア住宅データを訓練70%、テスト30%に分割してください。分割後のデータ数を確認してください。

ヒント: train_test_splittest_sizeパラメータを変更します。

解答例

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split

# データ読み込み
housing = fetch_california_housing()
X, y = housing.data, housing.target

# 訓練70%, テスト30%に分割
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

print("元のデータ数:", len(X))           # 20640
print("訓練データ数:", len(X_train))     # 14448
print("テストデータ数:", len(X_test))   # 6192
print("分割比率:", len(X_train)/len(X))  # 0.70

問題3(難易度:Medium) - モデルの精度比較

問題: Irisデータセットで、ロジスティック回帰、決定木、ランダムフォレストの3つのモデルを訓練し、テスト精度を比較してください。最も精度の高いモデルを特定してください。

ヒント: accuracy_scoreを使って各モデルの精度を計算します。

解答例

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# データ準備
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# モデル定義
models = {
    'Logistic Regression': LogisticRegression(max_iter=200),
    'Decision Tree': DecisionTreeClassifier(max_depth=3),
    'Random Forest': RandomForestClassifier(n_estimators=100)
}

# 訓練と評価
results = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, pred)
    results[name] = accuracy
    print(f"{name}: {accuracy:.3f}")

# 最良モデル
best_model = max(results, key=results.get)
print(f"\n最良モデル: {best_model} ({results[best_model]:.3f})")

問題4(難易度:Medium) - ハイパーパラメータチューニング

問題: ランダムフォレストでn_estimators=[50, 100, 150]max_depth=[3, 5, 7]の組み合わせでGrid Searchを実行し、最良のパラメータを見つけてください。

ヒント: GridSearchCVを使います。

解答例

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# データ準備
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# パラメータグリッド
param_grid = {
    'n_estimators': [50, 100, 150],
    'max_depth': [3, 5, 7]
}

# Grid Search
grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid, cv=5, n_jobs=-1
)

grid_search.fit(X_train, y_train)

# 結果
print("最良のパラメータ:", grid_search.best_params_)
print("最良のCV Score:", grid_search.best_score_)

# テストデータで評価
test_accuracy = grid_search.best_estimator_.score(X_test, y_test)
print("テスト精度:", test_accuracy)

問題5(難易度:Hard) - 完全なMLパイプライン

問題: カリフォルニア住宅データで、以下のステップを含む完全なMLパイプラインを構築してください:

  1. データの80/20分割
  2. StandardScalerでスケーリング
  3. ランダムフォレスト回帰で訓練
  4. RMSE、MAE、R²を計算
  5. 予測vs実測のプロット作成

ヒント: これまで学んだコード例を組み合わせます。

解答例

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np
import matplotlib.pyplot as plt

# 1. データ読み込み
housing = fetch_california_housing()
X, y = housing.data, housing.target

# 2. データ分割
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 3. スケーリング
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 4. モデル訓練
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train_scaled, y_train)

# 5. 予測
y_pred = model.predict(X_test_scaled)

# 6. 評価
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print("評価結果:")
print(f"  RMSE: {rmse:.3f}")
print(f"  MAE:  {mae:.3f}")
print(f"  R²:   {r2:.3f}")

# 7. 可視化
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_pred, alpha=0.5, edgecolor='black')
plt.plot([y_test.min(), y_test.max()],
         [y_test.min(), y_test.max()],
         'r--', lw=2, label='Perfect Prediction')
plt.xlabel('Actual Price')
plt.ylabel('Predicted Price')
plt.title('Random Forest: Predictions vs Actual')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

参考文献

  1. Pedregosa, F., et al. (2011). “Scikit-learn: Machine Learning in Python.” Journal of Machine Learning Research , 12, 2825-2830.
  2. VanderPlas, J. (2016). Python Data Science Handbook. O’Reilly Media.
  3. Géron, A. (2019). Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow (2nd ed.). O’Reilly Media.
  4. scikit-learn Documentation. (2024). “User Guide.” URL: https://scikit-learn.org/stable/user_guide.html
  5. Kaggle. (2024). “Titanic: Machine Learning from Disaster.” URL: https://www.kaggle.com/c/titanic