著者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力データがどのようなクラス(カテゴリ)であるかを予測する分類の問題を扱います。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図2 データセットの読み込みから、散布図のプロットまでを実施するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import seaborn as sns import matplotlib.pyplot as plt plt.rcParams['font.size'] = 14 penguins = sns.load_dataset('penguins') # 取り出す特徴量 features = ['bill_depth_mm', 'body_mass_g'] # (★) # features = ['bill_depth_mm', 'bill_length_mm'] # 取り出す特徴量を変える # 対象とするペンギン種 target_species = ['Adelie', 'Gentoo'] # (★) # target_species = ['Adelie', 'Chinstrap', 'Gentoo'] # 3種のペンギンにする # 今回用いる特徴量をクラスラベルと共に取り出す df = penguins[['species'] + features].copy() df.dropna(inplace=True) # NaN が含まれる行は削除 # 今回用いるペンギン種のみとする df2 = df[df['species'].isin(target_species)].copy() print(df2.shape) # (274, 3) と表示される # 現在のパレットから用いる種 (target_species) に合わせたパレットを作成 palette = {c: sns.color_palette()[k] for k, c in enumerate(df['species'].unique()) if c in target_species} fig = plt.figure(figsize=(5, 5)) sns.scatterplot(data=df2, x=features[0], y=features[1], hue='species', palette=palette) plt.show() |
図3 ロジスティック回帰の準備をするPythonコード
1 2 3 4 5 6 |
from sklearn.linear_model import LogisticRegression import numpy as np X = df2[features].values # 各個体の2次元特徴量(行数 = 個体数) y = df2['species'].values # 各個体のクラスラベル clf_lr = LogisticRegression(penalty='none') # 分類モデルの準備 |
図4 特徴量一つ(体重)のみでロジスティック回帰をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
f_idx = 1 # 用いる特徴量のインデックス # 指定した列(特徴量)だけ取り出して学習 clf_lr.fit(X[:, [f_idx]], y) # 学習されたパラメータを表示 w0 = clf_lr.intercept_[0] w1 = clf_lr.coef_[0][0] print(f'w_0 = {w0:.2f}, w_1 = {w1:.4f}, b = -w0/w1 = {-w0/w1:.2f}') # 予測値および分類結果の可視化用グリッド xmin = np.min(X[:, f_idx]) xmax = np.max(X[:, f_idx]) xs = np.linspace(xmin - (xmax - xmin) / 10, xmax + (xmax - xmin) / 10, 100) # 等間隔に100点用意 fig = plt.figure(figsize=(10, 5)) # 元データのプロット sns.scatterplot(x=X[:, f_idx], y=(y=='Gentoo'), hue=y, palette=palette) # 予測値(事後確率)の曲線をプロット y_pred = clf_lr.predict_proba(xs.reshape(-1, 1))[:, 1] plt.plot(xs, y_pred, c=palette['Gentoo']) plt.ylim(-0.1, 1.1) plt.xlabel(f'x ({features[f_idx]})') plt.ylabel('y') plt.grid() plt.show() |
図6 特徴量二つ(くちばしの高さと体重)でロジスティック回帰をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import matplotlib as mpl from sklearn.preprocessing import StandardScaler def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50): """ 分類器 clf の決定境界を描画 """ if fig is None: fig = plt.figure(figsize=(5, 5)) else: plt.figure(fig.number) # 2次元空間にグリッド点を準備 xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)] # グリッドのステップ幅 xmin = [xmin[j] - 8*xstep[j] for j in range(2)] # 少し広めに xmax = [xmax[j] + 8*xstep[j] for j in range(2)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] x0grid, x1grid = np.meshgrid(*aranges) # 各グリッド点でクラスを判定 y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()]) y_pred = y_pred.reshape(x0grid.shape) # 2次元に y_pred = np.searchsorted(np.unique(y_pred), y_pred) # 値をインデックスに clist = palette.values() if type(palette) is dict else palette cmap = mpl.colors.ListedColormap(clist) if palette is not None else None plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.legend() plt.xlim([xmin[0], xmax[0]]) plt.ylim([xmin[1], xmax[1]]) if xylabels is not None: plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) return fig Xs = StandardScaler().fit_transform(X) # 標準化によるスケーリング clf_lr.fit(Xs, y) # 二つの特徴量を両方用いて学習 print('w0:', clf_lr.intercept_) print('w1, w2:', clf_lr.coef_) xylabels = [s + ' (standardized)' for s in features] # 軸ラベル変更 plot_decision_boundary(Xs, y, clf_lr, xylabels, palette, ngrid=100) # 決定境界 plt.show() |
図9 ロジスティック回帰による各点での予測値をプロットするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from matplotlib import cm from mpl_toolkits.mplot3d import axes3d # メッシュで def gen_2dgrid(X): """ 2次元メッシュグリッドの生成 """ d = X.shape[1] xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)] # グリッドのステップ幅 xmin = [xmin[j] - 10*xstep[j] for j in range(d)] # 少し広めに xmax = [xmax[j] + 10*xstep[j] for j in range(d)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] return np.meshgrid(*aranges) # d=2のときは (x0grid, x1grid) が返る # 2次元の場合の予測値 (proba) def proba_2dgrid(clf, x0grid, x1grid): """ 2次元メッシュの各点での予測値を計算 """ return clf.predict_proba(np.c_[x0grid.ravel(), x1grid.ravel()])[:, 1] .reshape(x0grid.shape) xgrid_2d = gen_2dgrid(Xs) # xgrid_2d: (x0grid, x1grid) のような二つ組 proba_2d = proba_2dgrid(clf_lr, *xgrid_2d) # 予測値 # 3次元プロット fig = plt.figure(figsize=(14, 8)) ax = fig.add_subplot(111, projection='3d') cmap = cm.winter # カラーマップを設定 ax.plot_surface(*xgrid_2d, proba_2d, cmap=cmap) # ax.set_box_aspect((3, 3, 1)) # matplotlib のバージョンが3.3以上で利用可能 ax.set_zlim(-0.1, 1.1) ax.set_xlabel(xylabels[0]) ax.set_ylabel(xylabels[1]) ax.set_zlabel('y') ax.set_zticks([0, 0.5, 1.0]) ax.view_init(elev=60, azim=-120) # 3次元プロットの表示角度の設定 plt.show() |
図12 線形SVM による学習と決定境界の表示をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
from sklearn.svm import SVC C = 10 # 大きいほどハードマージンに近い clf_lin = SVC(kernel='linear', C=C) clf_lin.fit(Xs, y) # 決定境界とサポートベクターを可視化 plot_decision_boundary(Xs, y, clf_lin, xylabels, palette, ngrid=100) plt.scatter(clf_lin.support_vectors_[:, 0], clf_lin.support_vectors_[:, 1], s=50, facecolors='none', edgecolors='r') plt.title(f'SVM(linear) C = {C}') plt.show() |
図17 非線形SVMとパラメータCの関係を示すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from sklearn.datasets import make_moons X_moon, y_moon = make_moons(n_samples=100, noise=0.2, random_state=6) palette_o = {k: sns.color_palette()[k] for k in range(2)} # moonデータの散布図をプロット plt.figure(figsize=(5, 5)) sns.scatterplot(x=X_moon[:, 0], y=X_moon[:, 1], hue=y_moon, palette=palette_o) plt.show() # 異なるCでSVM(RBFカーネル)の学習と決定境界の表示 for C in [0.1, 10, 10000]: clf_rbf = SVC(C=C, kernel='rbf') # kernel='rbf'は省略可能 clf_rbf.fit(X_moon, y_moon) # 決定境界をプロット plot_decision_boundary(X_moon, y_moon, clf_rbf, None, palette_o, ngrid=100) plt.title(f'SVM(rbf) C = {C}') plt.show() |
図20 グリッドサーチをするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.model_selection import cross_val_score best_score = -1 for gamma in [0.1, 0.5, 1, 2, 10]: # 5通り for C in [0.1, 1, 10, 100, 1000, 10000]: # 6通り print(f'gamma: {gamma},\tC: {C}', end='') svm = SVC(gamma=gamma, C=C) cv_scores = cross_val_score(svm, X_moon, y_moon, cv=5) score = np.mean(cv_scores) print(f'\t | average: {score:.2} <- {cv_scores}') if score > best_score: best_score = score best_params = {'C': C, 'gamma': gamma} print('best_score:', best_score) print('best_params:', best_params) clf_rbf = SVC(**best_params) clf_rbf.fit(X_moon, y_moon) plot_decision_boundary(X_moon, y_moon, clf_rbf, None, palette_o, ngrid=100) title = f"C = {best_params['C']}, gamma = {best_params['gamma']}" plt.title(title) plt.show() |