第 11 章 演習解答¶
第 11 章「画像データを用いた機械学習」の演習問題の解答例です。問題は 第 11 章 演習問題 を参照してください。
演習 11-1: 生の画素値による分類¶
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 1. データの読み込み
data = np.load('mnist_1000_raw.npz')
X = data['X'] # (1000, 784)
y = data['y'] # (1000,)
# 2. 訓練・テストに分割し、画素値を 0〜1 に正規化
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_train = X_train / 255.0
X_test = X_test / 255.0
# 3. ロジスティック回帰で学習・評価
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
acc_pixel = accuracy_score(y_test, model.predict(X_test))
print(f"生の画素値による分類精度: {acc_pixel:.4f}")
考察:生の画素値をそのまま入力しても、精度は 85% になりました。ロジスティック回帰は 784 個の画素すべての重み付き和で判断するので、1 つの画素のしきい値で分岐する決定木(本文では 500 枚で約 60%)より高い精度が出ます。ただし、生の画素値は数字の位置がずれたり形が崩れたりすると弱くなります。演習 11-2 では、HOG 特徴量でこの点を改善します。
演習 11-2: HOG 特徴量による分類¶
import numpy as np
from skimage.feature import hog
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 1. データの読み込みと分割・正規化(演習 11-1 と同じ)
data = np.load('mnist_1000_raw.npz')
X = data['X']
y = data['y']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_train = X_train / 255.0
X_test = X_test / 255.0
# 2. HOG 特徴量の抽出
def extract_hog_features(images):
"""画像配列から HOG 特徴量を抽出する"""
hog_features = []
for img in images:
feature = hog(
img.reshape(28, 28),
orientations=9,
pixels_per_cell=(4, 4),
cells_per_block=(2, 2),
block_norm="L2-Hys",
feature_vector=True
)
hog_features.append(feature)
return np.array(hog_features)
X_train_hog = extract_hog_features(X_train)
X_test_hog = extract_hog_features(X_test)
print(f"HOG特徴量の次元数: {X_train_hog.shape[1]}")
# 3. ロジスティック回帰で学習・評価
model_hog = LogisticRegression(max_iter=1000, random_state=42)
model_hog.fit(X_train_hog, y_train)
acc_hog = accuracy_score(y_test, model_hog.predict(X_test_hog))
print(f"HOG特徴量による分類精度: {acc_hog:.4f}")
# 4. 演習 11-1(生の画素値: 0.8500)との比較
diff = acc_hog - 0.8500
print(f"精度の改善: {diff:.4f} ({diff * 100:.2f}ポイント)")
考察:精度は生の画素値の 85.0% から、HOG 特徴量で 94.5% に上がりました(9.5 ポイントの改善)。HOG 特徴量は局所的なエッジの方向と強さ、つまり数字の線の向きを表すので、画素の明暗を並べただけの値より数字を見分けやすくなります。次元数は 784 から 1296 に増えていますが、精度が上がったのは次元が増えたからではなく、形の情報を取り出したためです。
演習 11-3: HOG パラメータの調整¶
import numpy as np
from skimage.feature import hog
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 演習 11-2 と同じデータ・分割
data = np.load('mnist_1000_raw.npz')
X = data['X']
y = data['y']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_train = X_train / 255.0
X_test = X_test / 255.0
def extract_hog_features(images, orientations=9, ppc=(4, 4)):
"""パラメータを変えられるHOG特徴量抽出関数"""
feats = []
for img in images:
feats.append(hog(
img.reshape(28, 28), orientations=orientations,
pixels_per_cell=ppc, cells_per_block=(2, 2),
block_norm="L2-Hys", feature_vector=True))
return np.array(feats)
def evaluate(X_tr, X_te, label):
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_tr, y_train)
acc = accuracy_score(y_test, model.predict(X_te))
print(f"{label}: 次元数 {X_tr.shape[1]}, テスト精度 {acc:.4f}")
# 演習 11-2 の設定(基準)
evaluate(extract_hog_features(X_train),
extract_hog_features(X_test), "基準 ppc=(4,4), ori=9")
# pixels_per_cell を (7, 7) に変更
evaluate(extract_hog_features(X_train, ppc=(7, 7)),
extract_hog_features(X_test, ppc=(7, 7)), "ppc=(7,7), ori=9")
# orientations を 4 に変更
evaluate(extract_hog_features(X_train, orientations=4),
extract_hog_features(X_test, orientations=4), "ppc=(4,4), ori=4")
基準 ppc=(4,4), ori=9: 次元数 1296, テスト精度 0.9450
ppc=(7,7), ori=9: 次元数 324, テスト精度 0.9200
ppc=(4,4), ori=4: 次元数 576, テスト精度 0.9450
考察:pixels_per_cell を (7, 7) にすると、次元数は 1296 から 324 に減り、精度は 94.5% から 92.0% に下がりました。セルが粗くなり、細かい線の情報が失われたためです。orientations を 4 にした場合は、次元数が 576 に減っても精度は 94.5% のままでした。この例では、勾配方向の細かさよりも、エッジの位置をどれだけ細かく見るかのほうが精度に効いています。次元数は計算コストに直結するので、精度と見比べて設定を選びます。
演習 11-4: 同一特徴量でのモデル比較¶
import numpy as np
from skimage.feature import hog
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# 演習 11-2 と同じデータ・分割
data = np.load('mnist_1000_raw.npz')
X = data['X']
y = data['y']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_train = X_train / 255.0
X_test = X_test / 255.0
# 演習 11-2 と同じ設定で HOG 特徴量を抽出
def extract_hog_features(images):
feats = []
for img in images:
feats.append(hog(
img.reshape(28, 28), orientations=9,
pixels_per_cell=(4, 4), cells_per_block=(2, 2),
block_norm="L2-Hys", feature_vector=True))
return np.array(feats)
X_train_hog = extract_hog_features(X_train)
X_test_hog = extract_hog_features(X_test)
# 3 つのモデルで学習・評価
models = {
"決定木": DecisionTreeClassifier(random_state=42),
"ロジスティック回帰": LogisticRegression(
max_iter=1000, random_state=42),
"ランダムフォレスト": RandomForestClassifier(
n_estimators=100, random_state=42),
}
for name, model in models.items():
model.fit(X_train_hog, y_train)
acc = accuracy_score(y_test, model.predict(X_test_hog))
print(f"{name}: テスト精度 {acc:.4f}")
考察:同じ HOG 特徴量でも、決定木 71.0%、ランダムフォレスト 91.5%、ロジスティック回帰 94.5% と大きな差が出ました。決定木は 1 回の分岐で 1 つの特徴量しか見ないため、1296 次元に散らばったエッジの情報をうまく組み合わせられません。ランダムフォレストは多数の木の多数決で大きく改善しますが、すべての特徴量の重み付き和で判断するロジスティック回帰には届きませんでした。アンサンブルがいつも最良になるわけではなく、特徴量に合ったモデルを選ぶ必要があります。
演習 11-5: CIFAR-10 のカラー画像分類¶
import numpy as np
from skimage.color import rgb2gray
from skimage.feature import hog
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 1. CIFAR-10 サブセットの読み込み
data = np.load('cifar_1000_color.npz')
X, y = data['X'], data['y'] # X: (1000, 32, 32, 3)
# 2. 生の画素値(カラーのまま平坦化、0〜1に正規化)
X_flat = X.reshape(len(X), -1) / 255.0
X_train, X_test, y_train, y_test = train_test_split(
X_flat, y, test_size=0.2, random_state=42, stratify=y
)
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
acc_pixel = accuracy_score(y_test, model.predict(X_test))
print(f"生の画素値による分類精度: {acc_pixel:.4f}")
# 3. グレースケール変換 → HOG特徴量
X_gray = np.array([rgb2gray(img) for img in X])
X_hog = np.array([
hog(img, orientations=9, pixels_per_cell=(4, 4),
cells_per_block=(2, 2), block_norm="L2-Hys",
feature_vector=True)
for img in X_gray
])
Xh_train, Xh_test, yh_train, yh_test = train_test_split(
X_hog, y, test_size=0.2, random_state=42, stratify=y
)
model_hog = LogisticRegression(max_iter=1000, random_state=42)
model_hog.fit(Xh_train, yh_train)
acc_hog = accuracy_score(yh_test, model_hog.predict(Xh_test))
print(f"HOG特徴量の次元数: {X_hog.shape[1]}")
print(f"HOG特徴量による分類精度: {acc_hog:.4f}")
考察:CIFAR-10 の精度は、生の画素値で 30.5%、HOG 特徴量で 42.5% でした。手書き数字(85.0%、94.5%)よりかなり低いものの、10 クラスをランダムに予測した場合の 10% よりは高く、学習はできています。手書き数字は白い背景に黒い線で書かれ、同じ数字なら画素の並びも似ています。CIFAR-10 は実際の写真で、同じ「犬」でも犬種や向き、背景、明るさがばらばらです。そのため HOG 特徴量で 12.0 ポイント上がっても、まだ十分な精度にはなりません。このような画像には、特徴量そのものをデータから学習する CNN(第14章)が向いています。