手書き数字の認識・分類をするための学習器の作成

目次

  • はじめに

  • 概要、手法

  • 実行環境の情報

  • 作成したプログラムと実行結果

  • 教わったこと

  • 終わりに

はじめに

Aidemyの教師あり学習(分類)で学習した内容をもとに、グリッドサーチとランダムサーチで得られる評価の違いを学びました。

概要、手法

Scikit learn のライブラリに含まれているdigitsデータを使います。
digitsデータの一部の画像を以下に載せます。

機械学習の手法は、以下の6個を使います。

ロジスティック回帰
 
線形分離可能なデータの境界線を学習によって見つけて、データの分類を行う手法です。呼び名に「回帰」と付いていますが、分類の手法です。
 
特徴:境界線が直線。二項分類などクラスの少ないデータに使われます。
欠点:データが線形分離可能でないと分類ができない。
   ⾼次元の疎なデータ(0が多いデータ)には適さない。
   訓練データから学習した境界線がデータの近くを通るため、汎化能力      
   が低い。
Import方法:from sklearn.linear_model import LogisticRegression 

線形SVM
 
SVM(サポートベクターマシン)は、ロジスティック回帰と同じく、データの境界線を見つけ、データの分類を行う手法です。線形分離可能なSVMを線形SVMといいます。
 
特徴:ロジスティック回帰と比べて一般化されやすく、データの分類予測が
   向上する傾向がある。
欠点:データ量が増えると計算量が増え、他の手法に比べ学習や予測が遅く
   なる傾向がある。
   ロジスティック回帰と同様に、入力データが線形分離可能でないと正
    しく分類が行えない。
Import方法:from sklearn.svm import LinearSVC

非線形SVM

非線形SVMは線形SVMの欠点を取り除くため開発されたモデルです。カーネル関数と呼ばれる変換式に従って、数学的処理を行いデータを変換し、変換後の入力データが線形分離可能な状態となる場合があります。この処理を行いSVMを用いるモデルが非線形SVMです。
 
特徴:線形分離ができないデータに対して、分類ができる
Import方法:from sklearn.svm import SVC

決定木

データの要素(説明変数)の一つ一つに着目し、その要素内でのある値を境にデータを分割し、データの属するクラスを決定する手法です。
 
特徴:説明変数の一つ一つが目的変数にどのくらいの影響を与えているのか
   を見れる。
   分割を繰り返すことで枝分かれするが、先に分割される条件に用いら
   れる変数ほど影響力が大きいと捉えらる。
欠点:線形分離不可能なデータは分類が難しい。
   学習が教師データに寄りすぎる(汎化されない)。
Import方法:from sklearn.tree import DecisionTreeClassifier

ランダムフォレスト

決定木のモデルを複数作り、分類の結果をモデルの多数決で決める手法です。個々に学習した複数の学習器を組み合わせて、汎化性能を向上させる アンサンブル学習と呼ばれる学習の手法でもあります。
決定木では全ての説明変数を使用していたのに対し、ランダムフォレストの一つ一つの決定木は、ランダムに決められた少数の説明変数だけ使用してデータの属するクラスを決定します。 その上で、複数の決定木のモデルから出力されるクラスのうち、最も多いクラスを結果として出力します。
 
特徴:線形分離可能でない複雑な識別範囲を持つデータ集合の分類にも使え
   る。
欠点:決定木と同じように、説明変数の数に対してデータの数が少ないと決
   定木の分割ができず、予測の精度が下がってしまう。
Import方法:from sklearn.ensemble import RandomForestClassifier

k近傍法(k-NN)
 
予測をするデータと類似したデータをいくつか見つけ、多数決により分類結果を決める手法です。
怠惰学習と呼ばれる学習の種類の一手法であり、学習コスト(学習にかかる計算量)が0なのが特徴です。
k近傍法(k-NN)は教師データから学習するわけではなく、予測時に教師データを直接参照してラベルを予測します。 結果の予測を行う際の手法は以下の通りです。
 
(1)教師データを予測に用いるデータとの類似度で並べ直す。
(2)分類器に設定されたk個分のデータを類似度の高い順に参照する。
(3)参照された教師データが属するクラスのなかで最も多かったものを予測結果として出力する。
 
特徴:学習コストが0である
   アルゴリズムとしては比較的単純だが高い予測精度が出やすい。
   複雑な形の境界線も表現しやすい。
欠点:分類器に指定する自然数kの個数を増やしすぎると、識別範囲の平均
   化が進み、予測精度が下がってしまう。
   予測時に毎回計算を行うため、教師データや予測データの量が増える
   と計算量が増えてしまい、低速なアルゴリズムとなってしまう。
 Import方法:from sklearn.neighbors import KNeighborsClassifier

ハイパーパラメーターを調整して学習能力の高い学習器を作ります。
 
ランダムサーチで得られる一番評価の高い手法の名前、調整したパラメーター、その値を出力します。
 
同じように、グリッドサーチで得られる一番評価の高い手法の名前、調整したパラメーター、その値を出力します。

実行環境の情報

PC:Windows 11
環境:Jupyter Notebook
Python ver. : 6.4.11

作成したプログラムと実行結果

作成したプログラムは、以下の通りです。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC, SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.metrics import accuracy_score
import scipy.stats

data = load_digits()
train_X, test_X, train_y, test_y = train_test_split(
    data.data, data.target, test_size=0.3, random_state=42)
#テストデータを30%に設定

best_score = 0
best_model = None

model1 = LogisticRegression()
model1.fit(train_X, train_y)
scoer1 = model1.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "ロジスティック回帰"

model2 = LinearSVC()
model2.fit(train_X, train_y)
scoer1 = model2.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "線形SVM"

model3 = SVC()
model3.fit(train_X, train_y)
scoer1 = model3.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "非線形SVM"

model4 = DecisionTreeClassifier()
model4.fit(train_X, train_y)
scoer1 = model4.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "決定木"

model5 = RandomForestClassifier()
model5.fit(train_X, train_y)
scoer1 = model5.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "ランダムフォレスト"

model6 = RandomForestClassifier()
model6.fit(train_X, train_y)
scoer1 = model6.score(test_X, test_y)

if best_score < scoer1:
    best_scoer = scoer1
    best_model = "k-NN"



# グリッドサーチ用にモデルとパラメーターセットをまとめた辞書を用意
model_param_set_grid = {
    LogisticRegression(): {
        "C": [10 ** i for i in range(-5, 5)],
        "random_state": [42]
    },
    LinearSVC(): {
        "C": [10 ** i for i in range(-5, 5)],
        "multi_class": ["ovr", "crammer_singer"],
        "random_state": [42]
    },
    SVC(): {
        "kernel": ["linear", "poly", "rbf", "sigmoid"],
        "C": [10 ** i for i in range(-5, 5)],
        "decision_function_shape": ["ovr", "ovo"],
        "random_state": [42]
    },
    DecisionTreeClassifier(): {
        "max_depth": [i for i in range(1, 20)],
    },
    RandomForestClassifier(): {
        "n_estimators": [i for i in range(10, 20)],
        "max_depth": [i for i in range(1, 10)],
    },
    KNeighborsClassifier(): {
        "n_neighbors": [i for i in range(1, 10)]
    }
}

# ランダムサーチ用にモデルとパラメーターセットをまとめた辞書を用意
model_param_set_random = {
    LogisticRegression(): {
        "C": scipy.stats.uniform(0.00001, 1000),
        "random_state": scipy.stats.randint(0, 100)
    },
    LinearSVC(): {
        "C": scipy.stats.uniform(0.00001, 1000),
        "multi_class": ["ovr", "crammer_singer"],
        "random_state": scipy.stats.randint(0, 100)
    },
    SVC(): {
        "kernel": ["linear", "poly", "rbf", "sigmoid"],
        "C": scipy.stats.uniform(0.00001, 1000),
        "decision_function_shape": ["ovr", "ovo"],
        "random_state": scipy.stats.randint(0, 100)
    },
    DecisionTreeClassifier(): {
        "max_depth": scipy.stats.randint(1, 20),
    },
    RandomForestClassifier(): {
        "n_estimators": scipy.stats.randint(10, 100),
        "max_depth": scipy.stats.randint(1, 20),
    },
    KNeighborsClassifier(): {
        "n_neighbors": scipy.stats.randint(1, 20)
    }
}

max_score_grid = best_score
best_model_grid = None
best_param_grid = None
max_score_random = best_score
best_model_random = None
best_param_random = None

# ランダムサーチでパラメーターサーチ
for model, param in model_param_set_random.items():
    clf = RandomizedSearchCV(model, param)
    clf.fit(train_X, train_y)
    pred_y = clf.predict(test_X)
    score_random = accuracy_score(test_y, pred_y)
    # 最高評価更新時にモデルやパラメーターも更新
    if max_score_random < score_random:
        max_score_random = score_random
        best_model_random = model.__class__.__name__
        best_param_random = clf.best_params_


# グリッドサーチでパラメーターサーチ
for model, param in model_param_set_grid.items():
    clf = GridSearchCV(model, param)
    clf.fit(train_X, train_y)
    pred_y = clf.predict(test_X)
    score_grid = accuracy_score(test_y, pred_y)
    # 最高評価更新時にモデルやパラメーターも更新
    if max_score_grid < score_grid:
        max_score_grid = score_grid
        best_model_grid = model.__class__.__name__
        best_param_grid = clf.best_params_

        
print("グリッドサーチの学習モデル:{},\nグリッドサーチのパラメーター:{}".format(best_model_grid, best_param_grid))
print("グリッドサーチのベストスコア:",max_score_grid)
print()
print("ランダムサーチの学習モデル:{},\nランダムサーチのパラメーター:{}".format(best_model_random, best_param_random))
print("ランダムサーチのベストスコア:",max_score_random)

実行結果は以下の通りです。

グリッドサーチの学習モデル:SVC,
グリッドサーチのパラメーター:{'C': 10, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 42}
グリッドサーチのベストスコア: 0.9888888888888889

ランダムサーチの学習モデル:SVC,
ランダムサーチのパラメーター:{'C': 549.416824031637, 'decision_function_shape': 'ovo', 'kernel': 'rbf', 'random_state': 65}
ランダムサーチのベストスコア: 0.9888888888888889

結果としてグリッドサーチ、ランダムサーチとも約98.8%の正解率でしたが、複数回試してみると、グリッドサーチでは変わらない精度を誇りますが、ランダムサーチは約98.8%の正解率より若干落ちる場合がありました。

教わったこと

訓練データとテストデータに分ける際、デフォルトではテストデータが25%だったので、test_size=0.3,は記入せずにそのまま試しました。作成したプログラムには、以下のように30%のテストデータで作成しました。

train_X, test_X, train_y, test_y = train_test_split(
data.data, data.target, test_size=0.3, random_state=42)

何回か試した結果、出力される数値が変化しました。

グリッドサーチ、ランダムサーチとも、最初は、

model_param_set_grid = {SVC():{"kernel":["linear","poly","rbf","sigmoid"],
           "C":[10 ** i for i in range(-5,5)],
           "decision_function_shape":["ovr","ovo"],
           "random_state":[42]}}

model_param_set_random = {SVC():{"kernel":["linear","poly","rbf","sigmoid"],
             "C":[10 ** i for i in range(-5,5)],
             "decision_function_shape":["ovr","ovo"],
             "random_state":[42]}}

のSVCモデルのみのプログラムでしたが、作成したプログラムには、6個のモデルを作成して実行し、一番性能が良いモデルを選ぶようにしています。

また、プログラムの最後に、#ランダムサーチでパラメーターサーチと、# グリッドサーチでパラメーターサーチとありますが、これは最初作成した段階では、上下が逆でした。さらに、score に関しても最初はランダムサーチ、グリッドサーチも score = accuracy_score(test_y, pred_y) にしていました。何回か実行してみて、結果をみてみると、いつも大体同じような結果になることから、グリッドサーチの結果が、ランダムサーチに影響してしまっているのではないか?ということが考えられました。そこで、Aidemyのカウンセリングでのアドバイスを頂き、作成したプログラムへ変更をしました。

終わりに

まだまだコード1つ1つの意味を理解して作成できているわけではなく、何かを参考にしながら作成している段階ですので、しっかり勉強を続けながら、多様なプログラムを作れるようにします!

この記事が気に入ったらサポートをしてみませんか?