第 4 章 演習解答¶
第 4 章「機械学習の基礎」の演習問題の解答例です。問題は 第 4 章 演習問題 を参照してください。
演習 4-1: NumPy 配列の作成と統計量¶
import numpy as np
# 1. 10 から 100 まで(100 を含まない)10 刻みの配列を作成
x = np.arange(10, 100, 10)
# 2. 形状を表示
print(f"x の形状: {x.shape}")
# 3. 平均・最大・標準偏差
print(f"平均: {x.mean()}")
print(f"最大値: {x.max()}")
print(f"標準偏差: {x.std():.2f}")
# 4. 2 次元配列に変換
X = x.reshape(-1, 1)
print(f"X の形状: {X.shape}")
考察:np.arange(start, stop, step) は stop を含まないので、10 から 90 までの 9 要素になります。平均や標準偏差は x.mean() のように配列のメソッドで求められます。reshape(-1, 1) の -1 は「要素数から自動で決める」という意味です。これで scikit-learn が特徴量に求める(サンプル数, 特徴量数)の 2 次元配列になります。
演習 4-2: 単回帰モデルで売上を予測する¶
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# データ
X = np.array([22, 24, 26, 28, 30]).reshape(-1, 1)
y = np.array([45, 52, 61, 70, 78])
# 1. モデルの作成と学習
model = LinearRegression()
model.fit(X, y)
# 2. 傾きと切片の表示
print(f"傾き: {model.coef_[0]:.2f}")
print(f"切片: {model.intercept_:.2f}")
# 3. 気温 32 度のときの売上を予測
pred = model.predict([[32]])
print(f"気温 32℃ のときの予測売上: {pred[0]:.1f} 個")
# 4. 散布図と回帰直線の可視化
plt.scatter(X, y, label="訓練データ")
plt.plot(X, model.predict(X), color="red", label="回帰直線")
plt.xlabel("気温(℃)")
plt.ylabel("売上(個)")
plt.legend()
plt.show()
実行すると、ほぼ一直線に並んだ 5 つの訓練データの散布図と、その傾向をとらえた回帰直線が表示されます。
考察:傾き 4.20 は、気温が 1℃ 上がると売上が約 4.2 個増えることを表します。切片 −48.00 は、データのない 0℃ まで直線を延ばしたときの値で、実際の売上としての意味はありません。予測は訓練データに近い 20〜30℃ 台で使います。model.coef_ 属性は配列なので [0] で取り出します。予測したいデータも [[32]] のように 2 次元配列で渡します。
演習 4-3: データ分割と性能評価¶
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
ad_cost = [2, 3, 5, 6, 7, 8, 10, 12, 14, 15, 18, 20]
sales = [40, 50, 55, 62, 65, 68, 72, 78, 82, 85, 90, 95]
# 1. NumPy 配列に変換し、X は reshape(-1, 1) で 2 次元に整形
X = np.array(ad_cost).reshape(-1, 1)
y = np.array(sales)
# 2. 訓練データとテストデータに分割(テスト 25%, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
# 3. モデルの作成と学習
model = LinearRegression()
model.fit(X_train, y_train)
# 4. テストデータで予測
y_pred = model.predict(X_test)
# 5. RMSE を計算して表示
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"RMSE: {rmse:.2f}")
続いて、test_size を変えながら同じ評価を繰り返します。
for ts in [0.2, 0.3, 0.5]:
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=ts, random_state=42
)
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"test_size={ts}: 訓練 {len(X_train)} 件, "
f"テスト {len(X_test)} 件, RMSE {rmse:.2f}")
test_size=0.2: 訓練 9 件, テスト 3 件, RMSE 5.98
test_size=0.3: 訓練 8 件, テスト 4 件, RMSE 5.19
test_size=0.5: 訓練 6 件, テスト 6 件, RMSE 4.73
発展として、random_state を変えて評価のばらつきを見ます。
for rs in [0, 1, 2, 3, 4]:
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=rs
)
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"random_state={rs}: RMSE {rmse:.2f}")
random_state=0: RMSE 3.66
random_state=1: RMSE 3.34
random_state=2: RMSE 3.59
random_state=3: RMSE 3.54
random_state=4: RMSE 3.95
考察:学習は訓練データだけで行い、評価はテストデータで行います。RMSE 5.98 は売上と同じ単位(万円)なので、予測がおよそ 6 万円ずれると読めます。test_size を大きくするとテストの件数が増えて評価は安定しますが、訓練に使えるデータは減ります。今回は全体が 12 件しかないので、test_size=0.2 でもテストは 0.25 と同じ 3 件です。random_state を変えるだけでも、RMSE は 3.34〜5.98 の範囲で動きます。データが少ないときは、第 5 章で学ぶ交差検証で評価を安定させます。
演習 4-4: fit・predict・transform の役割¶
-
fit():モデルの「学習」を行うメソッドです。教師あり学習では特徴量 X とラベル y を与えてモデルのパラメータ(回帰係数など)を求め、前処理では標準化に使う平均や標準偏差といった統計量をデータから計算します。「データから何かを覚える」段階です。 -
predict():学習済みモデルを使って「予測」を行うメソッドです。分類ではクラスラベル、回帰では数値を返します。fit()メソッドで覚えた内容を新しいデータに適用する段階です。 -
transform():データの「変換」を行うメソッドです。標準化(StandardScalerクラス)や次元削減(PCA)などで、fit()メソッドで計算した統計量を使ってデータを変換します。
テストデータに fit_transform() メソッドを使ってはいけない理由:fit_transform() メソッドはそのデータから統計量を計算し直してから変換します。テストデータに対して使うと、テストデータの平均や標準偏差で変換されてしまい、(1) 訓練データと異なる基準で変換されるため予測が正しく機能しなくなり、(2) 本来「未知」であるはずのテストデータの情報がモデル構築の過程に漏れ込む(データリーケージ)ことになります。テストデータには、訓練データで fit() メソッドで学習した変換器の transform() メソッドのみを適用します。
演習 4-5: 共通 API の体験¶
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
# データの準備(入力4.4の住宅価格予測と同じ)
X = [[80, 5], [60, 15], [100, 2], [75, 8],
[90, 3], [50, 20], [110, 1], [65, 12]]
y = [4500, 3200, 5800, 4200, 5100, 2800, 6200, 3500]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# LinearRegression を Ridge に差し替えるだけで、そのまま動く
model = Ridge()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"誤差(RMSE): {rmse:.1f}")
考察:書き換えたのは import 文とモデルを作る行の2か所だけで、学習・予測・評価のコードはそのまま動きます。scikit-learn のモデルが共通のインタフェース(Estimator インタフェース)を持っているためです。RMSE は LinearRegression クラスのときの約245.7から236.3に変わりました。Ridge クラスは、係数が大きくなりすぎないように制約(正則化)を加えた線形回帰だからです。正則化は第6章で扱います。
発展課題 1: クラスタ数を変えた k-means 法¶
from sklearn.cluster import KMeans
import seaborn as sns
import numpy as np
# データの読み込み(ラベルは使わない)
df = sns.load_dataset("iris")
X = df[["sepal_length", "sepal_width",
"petal_length", "petal_width"]].values
# n_clusters を変えてクラスタリング
for k in [2, 3, 4]:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X)
counts = np.bincount(kmeans.labels_)
print(f"n_clusters={k}: 各クラスタのデータ数 {counts}")
n_clusters=2: 各クラスタのデータ数 [97 53]
n_clusters=3: 各クラスタのデータ数 [62 50 38]
n_clusters=4: 各クラスタのデータ数 [40 50 28 32]
考察:n_clusters=2 では、ほかと離れている setosa を中心とする53件と、versicolor と virginica をまとめた97件に分かれます。n_clusters=3 では、3品種におおよそ対応します。n_clusters=4 では、境界のあいまいな versicolor と virginica のあたりがさらに分けられます。k-means法は指定した数のクラスタを必ず作るので、クラスタ数は自分で決める必要があります。決め方は第9章のエルボー法とシルエット分析で学びます。
発展課題 2: 人工データで回帰モデルを評価する¶
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
# 1. 乱数シードを固定
np.random.seed(0)
# 2. X を生成(形状 (50, 1))
X = np.linspace(0, 10, 50).reshape(-1, 1)
# 3. y を生成(真の関係 y = 3x + 5 にノイズを加える)
noise = np.random.normal(0, 2, 50)
y = 3 * X.flatten() + 5 + noise
# 4. 訓練・テストに分割
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=0
)
# 5. モデルの学習と傾き・切片の表示
model = LinearRegression()
model.fit(X_train, y_train)
print(f"傾き: {model.coef_[0]:.3f}(真の値 3)")
print(f"切片: {model.intercept_:.3f}(真の値 5)")
# 6. R² スコアを表示
y_pred = model.predict(X_test)
print(f"R² スコア: {r2_score(y_test, y_pred):.4f}")
# 7. 可視化
plt.scatter(X_train, y_train, color="blue", label="訓練データ")
plt.scatter(X_test, y_test, color="green", label="テストデータ")
x_line = np.linspace(0, 10, 100).reshape(-1, 1)
plt.plot(x_line, model.predict(x_line), color="red", label="回帰直線")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.show()
実行すると、青い訓練データと緑のテストデータが、赤い回帰直線の周りに散らばるグラフが表示されます。
考察:np.random.normal(0, 2, 50) は、平均 0、標準偏差 2 の正規分布からノイズを 50 個作ります。ノイズがあっても、傾き 2.775 と切片 6.248 は真の値(3 と 5)に近くなりました。テストの R² も 0.9354 と高い値です。y を作るときは X.flatten() メソッドで 1 次元に戻します。戻さないと y は (50, 50) の 2 次元配列になってしまいます。