Python機械学習プログラミング:第3章
この記事では「Python機械学習プログラミング」の第3章の内容をざっくり記載します。
sklearnにすでに組み込まれているIrisデータセットの使う。特徴量「花びらの長さ・花びらの幅」を特徴行列Xにだし入試、対応する品種のクラスラベルをベクトル配列yに代入する。
from sklearn import datasets
import numpy as np
# Irisデータセットをロード
iris = datasets.load_iris()
# 3,4列目の特徴量を抽出
X = iris.data[:, [2, 3]]
# クラスラベルの取得
y = iris.target
# 一意なクラスラベルを出力
print('Class labels :', np.unique(y))
訓練したモデルの性能を未知のデータで評価するために、データセットを更に訓練データセットとテストデータセットに分割する。
from sklearn.model_selection import train_test_split
# 訓練データとテストデータに分割
# 全体の30%をテストデータにする
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)
train_test_splitを使ってX配列とy配列を30%のテストデータと70%の訓練データにランダムに分割している。訓練データセットを内部でシャッフルするためにrandom_stateを指定して、固定の乱数シードを発生させてる。このパラメータに固定の値を指定すると再現可能な結果が得られるようになる。stratify=yで層化サンプリングを利用してる。つまり、訓練サブセットとテストサブセットに含まれてるクラスラベルの比率が入力データセットと同じになっている。配列内の各値の出現回数を見る。
print('Label counts in y :', np.bincount(y))
print('Label counts in y_train :', np.bincount(y_train))
print('Label counts in y_test :', np.bincount(y_test))
sklearnのpreprocessingモジュールのStandardScalerクラスを使って特徴量を標準化する。
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
# 訓練データの平均と標準偏差を計算
sc.fit(X_train)
# 平均と標準偏差を用いて標準化
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
StandardScalerクラスをpreprocessingモジュールから読み込み、StandardScalerクラスの新しいインスタンスを変数scに代入。次にStandardScalerのfitメソッドを呼び出し、変数scに代入している。次にStandardScalerのfitメソッドを呼び出し、訓練データから特徴量ごとに平均値と標準偏差を推定している。続いてtransformメソッドを呼び出し、推定された平均値と標準偏差を使って訓練んデータを標準化している。テストデータの標準化にも同じスケーリングのパラメータを使っていることに注意。
from sklearn.linear_model import Perceptron
# エポック数40、学習率0.1でパーセプトロンのインスタンスを作成
ppn = Perceptron(eta0=0.1, random_state=1)
# 訓練データをモデルに適合させる
ppn.fit(X_train, y_train)
学習を行った後はpredictメソッドで予測を行える。
# テストデータで予測を実施
y_pred = ppn.predict(X_test_std)
# 誤分類のデータ点の個数を表示
print('Misclassified examples : %d' % (y_test != y_pred).sum())
正解率を求める。
from sklearn.metrics import accuracy_score
# 分類の正解率を表示
print('Accuracy : %.3f' % accuracy_score(y_test, y_pred))
y_testは真のクラスラベルでy_predは以前に予測したクラスラベルです。sklearnの各分類機に定義されているscoreメソッドを使うこともできます。このメソッドはpredict呼び出し先のaccuracy_scoreと組み合わせることで分類機の正解率を計算します。
print('Accuracy : %.3f' % ppn.score(X_test_std, y_test))
決定領域をプロットしてさまざまな品種のデータ点をどの程度識別できるか可視化してみます。
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
# マーカーとカラーマップの準備
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
# 決定領域のプロット
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# グリッドポイントの生成
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
# 各特徴量を1次元配列に変換して予測を実行
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
# 予測結果を元のグリッドポイントのデータサイズに変換
Z = Z.reshape(xx1.shape)
# グリッドポイントの等高線のプロット
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
# 軸の範囲の設定
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# クラスごとにデータ点をプロット
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black')
# テストデータ点を目立たせる(点を○で表示)
if test_idx:
# 全てのデータ点をプロット
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0], X_test[:, 1], c='black', edgecolors='black', alpha=1.0, linewidths=1, marker='o', s=100, label='test set')
# 訓練データとテストデータの特徴量を行方向に結合
X_combined_std = np.vstack((X_train_std, X_test_std))
# 訓練データとテストデータのクラスラベルを結合
y_combined = np.hstack((y_train, y_test))
# 決定境界のプロット
plot_decision_regions(X=X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
# 軸ラベルの設定
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
# 凡例の設定(左上に配置)
plt.legend(loc='upper left')
# グラフを表示
plt.tight_layout()
plt.show()
3つの品種を線型の決定境界で完全に区切ることはできない。このように完全な線型分離ができないデータセットではパーセプトロンアルゴリズムは決して収束しません。これが実務のデータ解析で推奨されない理由の1つです。以降ではクラスを完全に分離できない場合であってもコストの最小値に収束する、より強力な線形分類器を取り上げます。
ここからはロジスティック回帰についてみていきます。ロジスティック回帰は線型分類問題渡日分類問題に対する単純ながらより強力なアルゴリズムの1つであり、その名前とは裏腹に回帰ではなく分類のためのモデルである。
では、-7以上7未満の範囲のある値のシグモイド関数をプロットします。
import matplotlib.pyplot as plt
import numpy as np
# シグモイド関数を定義
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
# 0.1間隔で-7以上7未満のデータを生成
z = np.arange(-7, 7, 0.1)
# 生成したデータでシグモイド関数を実行
phi_z = sigmoid(z)
# 元のデータとシグモイド関数の出力をプロット
plt.plot(z, phi_z)
# 垂直線を追加(z=0)
plt.axvline(0.0, color='k')
# y軸の上限 / 下限を設定
plt.ylim(-0.1, 1.1)
# 軸のラベルを設定
plt.xlabel('z')
plt.ylabel('φ(z)')
# y軸のメモリを追加
plt.yticks([0.0, 0.5, 1.0])
# Axesクラスのオブジェクトの取得
ax = plt.gca()
# y軸のメモリに合わせて水平グリッド線を追加
ax.yaxis.grid(True)
# グラフを表示
plt.tight_layout()
plt.show()
あるデータ点に対してφ(z) = 0.8が算出される場合は、このデータ点が品種Iris-Versicolorである確率が80%であることを意味します。なので、このデータ点が品種Iris-Setosaである確率は20%として計算される。
φ(z)の様々な値に対する単一の訓練データの分類コストを具体的に示すグラフをプロットしてみます。
# y=1のコストを計算する関数
def cost_1(z):
return - np.log(sigmoid(z))
# y=0のコストを計算する関数
def cost_0(z):
return - np.log(1 - sigmoid(z))
# 0.1間隔で -10以上10未満のデータを生成
z = np.arange(-10, 10, 0.1)
# シグモイド関数を実行
phi_z = sigmoid(z)
# y=1のコストを計算する関数を実行
c1 = [cost_1(x) for x in z]
# 結果をプロット
plt.plot(phi_z, c1, label='J(w) if y=0')
# y=0のコストを計算する関数を実行
c0 = [cost_0(x) for x in z]
# 結果をプロット
plt.plot(phi_z, c0, linestyle='--', label='J(w) if y=0')
# x軸とy軸の上限 / 下限を設定
plt.ylim(0.0, 5.1)
plt.xlim([0, 1])
# 軸のラベルを設定
plt.xlabel('φ(z)')
plt.ylabel('J(w)')
# 凡例を設定
plt.legend(loc='upper center')
# グラフを表示
plt.tight_layout()
plt.show()
表示されたグラフはx軸の範囲[0, 1]でシグモイド活性化関数を表しており、y軸で関連するロジスティック回帰のコストを表している。シグモイド関数への入力は範囲[-10, 10]のz値です。
データ点がクラス1に所属していることを正しく予測した場合はコストが0に近づく事が分かります(図の実線)。同様に、y=0である(データ点がクラス0に所属している)ことを正しく予測した場合はy軸のコストも0に近付く事が分かります(図の点線)。ただし、予測が間違っていた場合コストは無限大に向かう。つまり、予測を間違えたら徐々にコストを引き上げることでペナルティを科します。
ADALINE実装をロジスティック回帰のアルゴリズムに変換してみます。
class LogisticRegressionGD(object):
"""勾配降下方に基づくロジスティック回帰分類器
パラメータ
----------------
era : float
学習率(0.0より大きく1.0以下の値)
n_iter : int
訓練データの訓練回数
random_state :
重みを初期化するための乱数シード
属性
----------------
"""
def __init__(self, eta=0.05, n_iter=100, random_state=1):
self.eta=eta
self.n_iter=n_iter
self.random_state=random_state
def fit(self,X, y):
"""訓練データを適合させる
パラメータ
----------------
X : (配列のような構造), shape = [n_examples, n_fetures]
訓練データ
n_examplesはデータ点の個数
y : 配列のようなデータ構造, shape = [n_examples]
目的変数
戻り値
----------------
self : object
"""
rgen = np.random.RandomState(self.random_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
self.cost_ = []
# 訓練回数分までの訓練データを反復処理
for i in range(self.n_iter) :
net_input = self.net_input(X)
output = self.activation(net_input)
errors = (y - output)
self.w_[1:] += self.eta * X.T.dot(errors)
self.w_[0] += self.eta * errors.sum()
# 誤差平方和のコストではなくロジスティック回帰のコストを計算することに注意
cost = -y.dot(np.log(output)) - ((1 - y).dot(np.log(1 - output)))
# エポックごとのコストを格納
self.cost_.append(cost)
return self
def net_input(self, X):
"""総入力を計算"""
return np.dot(X, self.w_[1:]) + self.w_[0]
def activation(self, z):
""" ロジスティックシグモイド活性化関数を計算"""
return 1. / (1. + np.exp(-np.clip(z, -250, 250)))
def predict(self, X):
"""1ステップ後のクラスラベルを返す"""
return np.where(self.activation(self.net_input(X)) >= 0.5, 1, 0)
ロジスティック回帰モデルを適合させる際には、そのモデルが上手くいくのは二値分類タスクに限られることを覚えておく必要があります。そこで、品種としてIris-Setosa(クラス0)とIris-Versicolor(クラス1)のみを考慮することで、ロジスティック回帰の実装を確認してみます。
X_train_01_subset = X_train_std[(y_train == 0) | (y_train ==1)]
y_train_01_subset = y_train[(y_train == 0) | (y_train == 1)]
# ロジスティック回帰のインスタンス生成
lrgd = LogisticRegressionGD(eta=0.05, n_iter=1000, random_state=1)
# モデルを訓練データに適合させる
lrgd.fit(X_train_01_subset, y_train_01_subset)
# 決定領域をプロット
plot_decision_regions(X=X_train_01_subset,y=y_train_01_subset, classifier=lrgd)
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
sklearnを使ってより簡単にロジスティック回帰を実装する方法について見てみます。この実装では他クラス分類の設定もサポートしてます。次のコードは「sklearn.linear_model LogisticRegression」クラスと、firメソッドを使って3つのクラスに分類できるヒュジュンかされた訓練データセットでモデルを訓練します。
from sklearn.linear_model import LogisticRegression
# ロジスティック回帰のインスタンスを生成
lr = LogisticRegression(C=100.0, random_state=1, solver='lbfgs', multi_class='ovr')
# 訓練データをモデルにて適合させる
lr.fit(X_train, y_train)
# 決定境界をプロット
plot_decision_regions(X_combined_std, y_combined, classifier=lr,
test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
正則化によるか学習への対応
過学習は機械学習でよくみられる問題です。過学習は訓練データでは上手く機能するもですが未知のデータ(テストデータ)では上手く汎化しないという問題です。過学習が発生しているモデルは「バリアンスが高い」とも表現されます。その原因として、パラメータの数が多すぎるためにデータに対してモデルが副ザルすぎる事が考えられます。同様にモデルは学習不足に陥ることもあります。つまり、訓練データのパターンをうまく補足するにはモデルの複雑さが十分ではなく、未知のデータに対する性能が低いことを意味する。このようなモデルは「バイアスが高い」とも呼ばれます。
一般的にバイアスとバリアンスはトレードオフで、その関係を探る方法の1つに正則化に基づいてモデルの複雑さを調整する事が挙げられます。正則化は共線性を処理する便利な手法であり、データからノイズを取り除き最終的に過学習を防ぎます。共線性とは特徴量の間の相関の高さのことです。正則化の背景にある考え方は、極端なパラメータの重みにペナルティを科すための追加情報(バイアス)を導入するというものです。もっとも一般的な正則化はL2正則化です。
# 空のリストを生成
weights, params = [], []
# 10この逆正則化パラメータに対応するロジスティック回帰モデルをそれぞれ処理
for c in np.arange(-5, 5):
lr = LogisticRegression(C=10.**c, random_state=1, solver='lbfgs', multi_class='ovr')
lr.fit(X_train_std, y_train)
# 重み係数を格納
weights.append(lr.coef_[1])
# 逆正則化パラメータを格納
params.append(10.**c)
# 重み係数をNumpy配列に格納
weights = np.array(weights)
# 横軸に逆正則化パラメータ、縦軸に重み係数をプロット
plt.plot(params, weights[:, 0], label='petal length')
plt.plot(params, weights[:, 1], linestyle='--', label='petal width')
plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.legend(loc='upper left')
# 横軸を大数スケールに設定
plt.xscale('log')
plt.show()
このコードを実行することで10個の逆正則化パラメータCに基づくロジスティック回帰モデルを訓練データに適合させてます。また、ここでは説明のために、クラス1(データセットの2つ目のクラス Iris-Versicolor)とその他のクラスを分類する分類器の重みを集めたに過ぎません。多クラス分類に一対多(OvR)手法を使っていることを思い出しましょう。結果として得られたグラフから分かるように、パラメータCが減少し、正則化の強みが増すと重み係数が0に近づいていく。
サポートベクトルマシンによる最大マージン分類
SVMでの最適化の目的は、マージンを最適化することです。マージンは超平面(決定境界)と、この超平面に最も近い訓練データの問題の距離として定義されます。超平面に最も近い訓練データはサポートベクトルと呼ばれます。
SVMを訓練してIrisデータセットの品種を分類してみます。
from sklearn.svm import SVC
# 線形SVMのインスタンスを生成
svm = SVC(kernel='linear', C=1.0, random_state=1)
# 線形 SVMのモデルに訓練データを適合させる
svm.fit(X_train_std, y_train)
plot_decision_regions(X_combined_std, y_combined, classifier=svm,
test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
確率的勾配降下法バージョンのパーセプトロン、ロジスティック回帰、SVMは次に示すようにSGDClassifierのデフォルトパラメータで初期化できます。
from sklearn .linear_model import SGDClassifier
# 確率的勾配降下法バージョンのパーセプトロンを生成
ppn = SGDClassifier(loss='perceptron')
# 確率的勾配降下法バージョンのロジスティック回帰を生成
lr = SGDClassifier(loss='log')
# 確率的勾配降下法バージョンのSVM(損失関数 = ヒンジ関数)を生成
svm = SGDClassifier(loss='hinge')
カーネルSVMを使った非線形問題の求解
SVMが人気の理由の1つが「非線形分類の問題」を得ために「カーネル化」するのが簡単であることです。最も一般的な種類のSVMはいわゆる「カーネルSVM」です。この概念について説明する前に人工的なデータセットを作成し、そうした非線形分類問題がどのようなものであるか確認します。
Numpyのlogical_xor関数を使ってXORゲート形式の単純なデータセットを作成します。このデータセットでは100個のデータ点にクラスラベル1を割り当て、その他の100個のデータ点にクラスラベル-1を割り当てます。
import matplotlib.pyplot as plt
import numpy as np
# 乱数シードを指定
np.random.seed(1)
# 標準正規分布に従う乱数で200行2列の行列を生成
X_xor = np.random.randn(200, 2)
# 2つの引数に対して排他的論理和を実行
y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0)
# 排他的論理和の値が真の場合は1、偽の場合は-1を割り当てる
y_xor = np.where(y_xor, 1, -1)
# ラベル1を青のプロットxでプロット
plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==1, 1], c='b', marker='x', label=1)
# ラベル-1を赤の四角でプロット
plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==-1, 1], c='r', marker='s', label='-1')
# 軸の範囲を設定
plt.xlim([-3, 3])
plt.ylim([-3, 3])
plt.legend(loc='best')
plt.tight_layout()
plt.show()
ここまで説明してきた線形ロジスティック回帰、または線形SVMモデルを用いた場合、線形超平面の決定境界に基づいて妖精クラスと陰性クラスのデータ点を分割することになりますが、この例ではうまくいかないことが明らかです。こうしたデータに非線形の決定境界を作ります。
# RBFカーネルによるSVMインスタンスを生成
svm = SVC(kernel='rbf', random_state=1, gamma=0.10, C=10.0)
svm.fit(X_xor, y_xor)
plot_decision_regions(X_xor, y_xor, classifier=svm)
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
カーネルSVMはXORデータをうまく分割しています。
IrisデータにカーネルSVMを適用させます。
svm = SVC(kernel='rbf', random_state=1, gamma=100.0, C=1.0)
svm.fit(X_train_std, y_train)
plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend()
plt.tight_layout()
plt.show()
このモデルは訓練データセットに非常にうまく適合するが、このような分類器では未知のデータで高い汎化誤差が生じる可能性が高いです。
決定木学習
決定木分類器は意味解釈可能性(得られた結果の意味を解釈しやすいかどうか)に配慮する場合に魅力的なモデルです。「決定木」という名前が示唆するように、このモデルについては、一連の質問に基づいて決断を下すという方法により、データを分類するモデルであると考えられます。
決定木の構築
決定木学習では特徴量空間を分割することで複雑な決定境界を構築できます。ただし、決定木が深くなればなるほど決定境界は複雑になり、過学習に陥りやすくなります。ここではsklearnを利用して不純度の条件として時に不純度を使って、最大の深さが4の決定木を訓練します。特徴量のスケーリングは可視化が目的である場合には望ましいかもしれないが、決定木アルゴリズムでは必須ではないことに注意しましょう。
from sklearn.tree import DecisionTreeClassifier
# 痔に不純度を指標とする決定木のインスタンスを生成
tree_model = DecisionTreeClassifier(criterion='gini', max_depth=4, random_state=1)
# 決定木のモデルを訓練データに適合させる
tree_model.fit(X_train, y_train)
X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X_combined, y_combined, classifier=tree_model, test_idx=range(105, 150))
plt.xlabel('petal length [cm]')
plt.ylabel('petal width [cm]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
このコードを実行すると決定木特有のものとして、軸に平行な決定境界が得られます。sklearnには訓練後の決定木モデルを可視化できる素晴らしい機能があります。
from sklearn import tree
tree.plot_tree(tree_model)
plt.show()
値の105個のデータ点を出発点として、花びらの閾値0.75cm以下であることに基づき35個と70個のデータ点を持つ2つの子ノードに分割しています。最初の分割後、左の子ノードがすでに純粋で Iris-Setosaクラスのデータ点だけを含んでる事がわかります(ジニ不純度が0)。そこで、右の子ノードでさらに分割を行い、データ点をIris-VersicolorクラスとIris-Virginicaクラスに分割してます。
ランダムフォレストを使って複数の決定木を結合する
ランダムフォレストはスケーラビリティが高く、使いやすいことで知られており、決定木のアンサンブルとみなす事ができる。ランダムフォレストはそれぞれバリアンスが高い複数の(深い)決定木を平均化することで、より汎化性能が高く科学週に対して堅牢なモデルを構築する、という考え方に基づいている。ランダムフォレストアルゴリズムは次の4つの単純な手順にまとめる事ができます。
サイズnのランダムはブーストラップ標本を復元抽出する(訓練データセットからn個のデータ点をランダムに選択します)。
ブーストラップ標本から決定木を成長させる。各ノードで以下の作業を行う。
2.1 d個の特徴量をランダムに非復元抽出する。
2.2 例えば、情報利得を最大化することにより、目的関数に従って最適な分割となる特徴量を使ってノードを分割する。手順1~2をk回繰り返す。
決定木ごとの予測をまとめ、多数決に基づいてクラスラベルを割り当てる。
決定木ごとの予測をまとめ、多数決に基づいてクラスラベルを割り当てる。
from sklearn.ensemble import RandomForestClassifier
# ジニ不純度を指標とするランダムフォレストのインスタンスを生成
forest = RandomForestClassifier(criterion='gini', n_estimators=25, random_state=1, n_jobs=2)
# 訓練データにランドムフォレストモデルを適合させる
forest.fit(X_train, y_train)
plot_decision_regions(X_combined, y_combined, classifier=forest, test_idx=range(105, 150))
plt.xlabel('petal length [cm]')
plt.ylabel('petal width [cm]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
k最近傍法:怠惰学習アルゴリズム
k最近傍法分類器、略してKNNはここまで説明してきた学習アルゴリズムとは根本的に異なります。手順は次の通りです。
kの値と距離指標を選択する。
分類したいデータ点からk個の最近傍のデータ点を見つけ出す。
多数決によりクラスラベルを割り当てる。
多数決によりクラスラベルを割り当てる。
ユークロッド距離の指標を使ってsklearnでKNNモデルを実装します。
from sklearn.neighbors import KNeighborsClassifier
# k最近傍法のインスタンスを生成
knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')
# 訓練データにk最近傍法のモデルを適合させる
knn.fit(X_train, y_train)
plot_decision_regions(X_combined, y_combined, classifier=knn, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
5つの近傍をknnモデルで指定することにより、比較的滑らかな決定境界が得られます。kを正しく選択するには過学習と学習不足のバランスを上手く取ることが大切です。ここで意識しておきたいので「次元の呪い」です。ロジスティック回帰では過学習を防ぐために「正則化」を行いましたが、正則化を適用できない決定木やKNNモデルでは特徴量線たkと次元削減の手法を用いることで、次元の呪いから逃れることができます。