【Aidemy】 ディープラーニングで手書き数字を識別してみよう⑤
前回の続きから。
Aidemyの画像認識コース。
今回は教師あり学習(分類)。
教師あり学習(分類)の基礎
分類とは
- 蓄積されたデータを元に機会が新しいデータの分類を行うこと
- 予測される値はデータのカテゴリであり、離散値
分類の種類
- 二項分類(二値分類、2クラス分類)
分類するカテゴリが2つの分類問題のことで、直線でクラス間を識別できる場合は線形分類、そうでない場合は非線形分類という - 多項分類(他クラス分類)
クラスが3つ以上の分類問題のこと。単に直線では識別できない場合が多い。
分類のながれ
- データの前処理 データの整形、操作
- モデルの選択 分類器の選択
- モデルの学習 チューニングをするハイパーパラメータの選択 / パラメータのチューニング
- モデルによる予測 未知のデータを使ってモデルの精度検証 / WEBサービスなどに組み込み、AIモデルを実運用
データの用意
- skelearn.datasetsモジュールの
make_classification()
関数を利用 - 引数は以下の通り
- n_sampels:用意するデータの個数
- n_classes:クラス数。デフォルトは2。
- n_features:データの特徴量の個数
- n_redundant:分類に不要な特徴量(余分な特徴量)の個数
- random_state:乱数のシード
from sklearn.datasets import make_classification x, y = make_classification(n_samples=XX, n_classes=XX, n_features=XX, n_reducdant=XX, random_state=XX)
学習と予測
from sklearn.linear_model import LogisticRegression from sklearn.svm import LinearSVC, SVC from skelarn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.neighbors import KNeighborsClassifier model = Classifier() # 上でインポートした分類器から選択 model.fit(train_X, train_y) # モデルの学習 model.predict(test_X) # モデルによるデータの予測 model.score(test_X, test_y) # モデルの正解率
分類手法
ロジスティック回帰
- 線形分離可能なデータの境界線を学習によって見つけてデータの分類を行う
- データがクラスに分類される確率も計算することが可能
- 今日データから学習した境界線はクラスの端にあるデータのすぐそばを通るようになるため、汎化能力が低いという欠点がある。
- 実装
from sklearn.linear_model import LogisticRegression import matplotlib.pyplot as plt import numpy model = LogisticRegression() model.fit(train_X, train_y) model.predict(test_X) model.score(test_X, test_y) # 可視化 plt.scatter(X[:, 0], X[:, 1], c=y, marker='.', cmap=matplotlib.cm.get_cmap(name='bwr'), alpha=0.7) xb = np.linspace(-10, 10) yb = -model.coef_[0][0] / model.coef_[0][1] * \ xb - model.intercept_ / model.coef_[0][1] plt.plot(xb, yb) # 諸々グラフ設定をして plt.show()
線形SVM(サポートベクターマシン)
- ロジスティック回帰と同じくデータの境界線を見つけることでデータの分類を行う。
- クラスごとの境界線に最も近いデータと境界線の距離を表すベクトルをサポートベクターと呼び、サポートベクターの距離の総和を最大化しようとする(マージン最大化)ことによって境界線を決定する。
- 境界線が2クラス間の最も離れた場所に惹かれるため一般化されやすい。
- サポートベクターのみを考えれば良いため、筋道が立ちやすい
- データ量が増えると計算量が経て学習や予測が遅くなる傾向がある
- 実装
from sklearn.svm import LinearSVC model = LinearSVC() model.fit(train_X, train_y) model.score(test_X, test_y)
非線形SVM
- 特徴カーネル関数と呼ばれる変換式に従ってデータを操作することでデータが線形分離可能な状態になる場合があり、そのような処理を行ってくれるモデルが存在する
- カーネル関数による操作はその操作の結果を知らずに分類を行うことが可能で、カーネルトリックとも呼ばれる
- 実装
from sklearn.datasets import make_gaussian_quantiles from sklearn.model_selection import train_test_split from sklarn.svm import SVC X, y = make_gaussian_quantiles(n_samples=1000, n_classes=2, n_features=2, random_state=42) train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42) model = SVC() model.fit(train_X, train_y) model.score(test_X, test_y)
決定木
- データの要素(説明変数)の一つ一つに着目し、その要素内でのある値を境にデータを分割していくことでデータのぞkするクラスを決定しようとする手法
- 説明変数の一つ一つが目的変数にどのくらいの影響を与えているのかを見ることができる
- 分割を繰り返すことで枝分かれするが、先に分割される変数ほど影響力が大きいと捉えることができる
- 線形分離可能なデータが苦手 / 学習が教師データに寄り過ぎるという欠点あり
- 実装
from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier() model.fit(train_X, train_y) model.score(test_X, test_y)
ランダムフォレスト
- 決定木の簡易版を複数作成し、分類の結果を多数決で決める
- アンサンブル学習の一手法。
- 一つ一つの決定木はランダムに決められた小数の説明変数だけを用いてデータの属するクラスを決定しようとする
- 複数の分類器をとして多数決により結果を出力するため、外れ地によって予測結果が左右されにくい
- データが少ないと二分木の分割ができず、予測の精度が下がってしまう
from sklearn.ensemble import RandomForestClassifier model = RandomForestClassifier() model.fit(train_X, train_y) model.score(test_X, test_y)
k-NN(k近傍法)
- 予測するデータと類似したデータをいくつか見つけ、多数決により分類結果を求める
- 怠惰学習と呼ばれる学習の一手法であり、学習コストが0
- 教師データから学習するわけではなく、予測時に教師データを直接参照してラベルを予測する
- 予測手順
- 教師データを予測に用いるデータとの類似度で並べ直し
- 分類器に設定されたk個分のデータを類似度の高い順に参照
- 参照された教師データが属するクラスの中で最も多かったものを予測結果として出力
- アルゴリズムは単純だが高い予測精度が出やすい
- 複雑な形の境界線も表現しやすい
- kを大きくし過ぎると識別範囲の平均化が進み予測精度が下がってしまう
- 実装
from sklearn.neighbors import KNeighborsClassifier model = KNeighborsClassifier() model.fit(train_X, train_y) model.score(test_X, test_y)
ハイパーパラメーターとチューニング
ハイパーパラメーターとは
- 機械学習のモデルが持つパラメーターの中で人が調整をしないと行けないパラメーターのこと
チューニングとは
- ハイパーパラメーターを調整すること
- 直接値をモデルに入力
- 値の範囲を指定して最適な値を探してもらう
ロジスティック回帰
- C
- モデルが学習する識別境界線が教師データの分類間違いに対してどれくらい厳しくするかの指標
- Cの値が大きいと教師データをより精度よく分類できるが、過学習もしやすくなる
- Cの値が小さいと一般化された境界線を得やすくなるが、外れ値の少ないデータでは境界線がうまく識別できない
- sklearnにおける初期値は1.0
- penalty
- モデルの複雑さに対するペナルティ
- L1/L2が入る
- L1:データの特徴量を削減することで識別境界線の一般化を測るペナルティ
- L2:データ全体の重みを減少させることで識別境界線の一般化を測るペナルティ
- 基本的にはL2を選べばOK
- multi_class
- 多クラス分類を行う際にモデルがどういった動作を行うか
- ovr/multinomial
- ovr:クラスに対して「属する/属さない」の二値で答えるような問題に適する
- multinomial:各クラスに分類される確率も考慮
- random_state
- 学習の際のデータの処理順を制御する
- 再現性を考えて固定するのが良い
線形SVM
- C / penalty
- ロジスティック回帰と同様
- multi_class
- 多項分類を行う際にモデルがどういった動作を行うか決める
- ovr / crammer_singer
- 基本的にはovrの方が動作が軽く結果が良い
- random_state
非線形SVM
- C
- ソフトマージンのペナルティ
- 学習時に分類の誤りをどの程度許容するかを指定するパラメーター
- kernel
- 受け取ったデータを操作して分類しやすい形にするための関数を定義
- linear / rbf / poly / sigmoid / precomputed
- linear:線形SVM。特殊な理由がない限りLinearSVCを使うべき
- rbf / poly:立体投影の・ようなもの。rbfは比較的高い正解率が出ることが多いので通常はデフォルトであるrbfを使用
- precomputed:データが前処理によってすでに整形済みの場合に使用
- sigmoid:ロジスティック回帰モデルと同じ処理
- decision_function_shape
- SVCにおけるmulti_classパラメータ
- ovo / ovr
- ovo:クラス同士のペアを作り、そのペアで2項分類を行い多数決で属するクラスを決定。計算量が多くデータ量の増大によっては動作が重くなることも
- ovr:一つのクラスとそれ以外という分類を行い多数決で属するクラスを決定
- random_state
- データの前処理に関係するパラメータ
- 以下のように指定する
import numpy as np from sklearn.svm import SVC # 乱数生成器を構築 random_state = np.random.RandomState() model = SVC(random_state=radom_state)
決定木
- max_depth
- 学習時にモデルが学習する気の深さの最大値
- 設定されたいない場合、木は教師データの分類がほぼ終了するまでデータを分割 -> 過学習
- 木の高さを制限すること = 決定木の枝刈り
- random_state
- 決定木の分割の際、データの分類を説明できる要素の値を見つけてデータの分割を行う
- そのような値の候補を乱数の生成により決定
ランダムフォレスト
- n_estimators
- 簡易決定木の個数
- max_depth
- 簡易決定木に関するパラメーター
- 通常の決定木より小さな値を入力する
- random_state
- 結果の固定のみならず、決定木のデータの分割や用いる要素の決定など多くの場面で乱数が寄与するため、重要なパラメーター
k-NN
- n_neighbors
- k-NNのkの値のこと
- 結果予測の際に使う類似データの個数を決める
チューニングの自動化
- グリッドサーチ
- 調整したいハイパーパラメーターの値の候補を明示的に複数指定、パラメーターセットを作成してその時のモデルの評価を繰り返すことでモデルとして最適なパラメーターセットを作成す
- 数学的に連続ではない値を取るパラメーターの探索に向いている
- 多数のパラメーターを同時にチューニングするには不向き
- 実装
from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC param = { 'C': [10 ** -i for i in range(5)], 'kernel': ['linear', 'poly', 'rbf', 'sigmoid'], 'random_state': [42], } svm = SVC() # グリッドサーチ clf = GridSearchCV(svm, param) clf.fit(train_X, train_y) # パラメーターサーチ結果の取得 best_param = clf.best_params_ print(clf.socre(test_X, test_y), best_param)
- ランダムサーチ
- パラメーターが取りうる値の範囲を指定し、確率で決定されたパラメータセットを用いてモデルの評価を行うことを繰り返すことによって最適なパラメーターセットを探す
- 値の範囲の指定はパラメーターの確率関数を指定
- 実装
from scipy import stats from sklearn.model_selection import RandomizedSearchCV from skelarn.svm import SVC param = { # 0から100までの一様確率変数 'C': stats.uniform(loc=0.0, scale=100.0), # 乱数で選ぶ必要がないものはリストで指定 'kernel':['linear', 'poly', 'rbf', 'sigmoid'], 'random_state': [42], } svm = SVC() # ランダムサーチ clf = RandomizedSearchCV(svm, param) clf.fit(train_X, train_y) best_param = clf.best_params_ # 以下略