Employee Blog
社員ブログ

Pythonで機械学習を勉強してみた(scikit-learn)②

Pythonで機械学習を勉強してみた(scikit-learn)」では、機械学習の基本について学びました。
今回は、そのときのコードをベースに、データやモデルの設定を変更しながら、さらに理解を深めていきます。


1. 前回のコード

まずは、前回のコードを振り返ります。


# 必要なライブラリのインポート
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# Irisデータセットの読み込み
iris = load_iris()
X = iris.data      # 特徴量
y = iris.target    # ラベル(クラス)

# 訓練データとテストデータに分割(テストサイズは全体の20%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 決定木分類器の作成
clf = DecisionTreeClassifier()

# モデルの学習
clf.fit(X_train, y_train)

# テストデータでの予測
y_pred = clf.predict(X_test)

# 予測精度の計算と表示
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)

改めて出力を確認すると、以下のようになります。


Accuracy: 1.0

学習モデルの正解率が100%であることが分かります。
ここから、コードの一部を変更し、結果がどのように変化するか試してみます。

2. データ分割方法の変更

ここでは学習方法は変えずに、学習用とテスト用のデータ分割の方法を変えるとどのような影響があるのか見ていきたいと思います。


2.1. 学習データの数

# 訓練データとテストデータに分割(テストサイズは全体の20%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

現在、150 サンプルのうち 120 サンプルを学習用、30 サンプルをテスト用として使用しています。
test_size を大きくすると、学習データが減少するため、学習の精度が下がると予想できます。

実際に test_size の値を変えて試した結果は以下の通りです。

test_sizetrain_samplestest_samplesaccuracy
0.0514281
0.1135151
0.15127231
0.2120301
0.25112381
0.3105451
0.3597531
0.490600.967
0.4582680.971
0.575750.973
0.5567830.94
0.660900.978
0.6552980.898
0.7451050.924
0.75371130.929
0.8301200.933
0.85221280.938
0.9151350.956
0.9571430.727

学習用サンプルが少なくなると正解率が下がる傾向が確認できました。
また、学習データが多いと学習時間も伸びることが予想できるので試してみたいと思います。

Iris データセットではサンプル数が少なく変化が分かりにくいため、ここからはscikit-learn の make_classification() を用いて大規模なデータセットを生成しテストしたいと思います。
以下のコードで、サンプル数100,000、30の特徴量、3つのクラスのデータセットを生成します。


X, y = make_classification(
    n_samples=100000, n_features=30, n_informative=20, n_classes=3, random_state=42
)

以降はこのデータセットを用いて実験を進めます。(特に指定がなければtest_sizeは0.2)
このデータセットで実施した実験結果は以下の通りです。

test_sizetrain_samplestest_samplesaccuracytrain_time (s)
0.059500050000.8014.605
0.190000100000.7924.1906
0.1585000150000.7883.833
0.280000200000.7883.649
0.2575000250000.7883.849
0.370000300000.7853.6895
0.3565000350000.7762.921
0.460000400000.7812.921
0.4555000450000.7762.436
0.550000500000.7722.066
0.5544999550010.7682.0056
0.640000600000.7681.62
0.6535000650000.761.362
0.730000700000.7591.1062
0.7525000750000.7551.059
0.820000800000.7410.703
0.8515000850000.7310.484
0.910000900000.7150.312
0.955000950000.6890.134

この表から、学習用データが多いほど学習時間が長くなる一方で、必ずしも正解率が大幅に向上するわけではないことが分かります。
また、Iris データセットでは正解率が100%に達したのに対し、この大規模データセットでは最高でも約80%にとどまり、データの量が全てではないこともわかりました。


2.2 データ分割のランダム性

次に、random_state の影響を確認します。
random_state を固定すると、毎回同じようにデータを分割できます。

では、random_state を固定しない場合はどうなるのでしょうか。
以下は、サンプル数を5000から100000まで変化させ、random_state を固定せずにそれぞれ100回ずつ試行した結果です。

この結果から、random_state を固定しないと、試行ごとに結果にばらつきが生じることが分かりました。
また、サンプル数が増えるほど、ばらつきが少なくなる傾向も確認できます。

したがって、再現性や再利用性を高めるには random_state を固定することが重要であり、一定の結果に収束させるためには十分なデータ量が必要であることが分かります。


3. 学習手法の変更

ここでは学習方法の違いによる影響を見ていきたいと思います。


3.1. 決定木分類のパラメータ

# 決定木分類器の作成
clf = DecisionTreeClassifier()

まず、決定木分類の手法でパラメータを変更すると、どの程度違いが出るのかを確認してみます。
すべてのパラメータを試すのは大変なため、下記リファレンスを参考に、一部のオプションを組み合わせて実験を行います。

DecisionTreeClassifier — scikit-learn 1.6.1 documentation


今回は、以下の8つのパラメータを選択しました。


  • criterion: 分割品質を評価する指標(例: ‘gini’, ‘entropy’, ‘log_loss’)
  • splitter: 各ノードで最適またはランダムな分割を選ぶ戦略
  • max_depth: 決定木の最大深さ(Noneの場合は制限なし)
  • min_samples_split: 内部ノードを分割するための最小サンプル数
  • min_samples_leaf: 葉ノードに必要な最小サンプル数(過学習防止に有用)
  • max_features: 分割時に検討する特徴量数(Noneは全特徴量を使用)
  • max_leaf_nodes: 葉ノードの最大数で木の複雑さを制限
  • ccp_alpha: コスト複雑度剪定パラメータ(大きいほど剪定が強くなる)

これらのパラメータがどのような影響を与えるのか、適切な値が何かはまだ分かりません。
そこで、まずは以下の値を設定し、すべての組み合わせを総当たりで試してみます。


criterions = ['gini', 'entropy', 'log_loss']
splitters = ['best', 'random']
max_depths = [None, 5, 10, 15]
min_samples_splits = [2, 5, 10]
min_samples_leafs = [1, 5, 10]
max_features_list = [None, "sqrt", "log2"]
max_leaf_nodes_list = [None, 10, 20]
ccp_alphas = [0.0, 0.01]

上記オプションの組み合わせは3,888通りあってすべて載せると多すぎるので、ここでは特徴的な結果のみを抜粋して記載します。

criterionsplittermax_depthmin_samples_splitmin_samples_leafmax_featuresmax_leaf_nodesccp_alphaAccuracyTraining Time (sec)
Best Accuracyentropybest15210nan00.8087.06518
Worst Accuracyginibestnan21log2100.010.331750.336704
Longest Training Timelog_lossbest151010nan0.010.6289510.3131
Shortest Training Timeginirandomnan25log21000.49070.0380671
Default Combinationginibestnan21nan00.789755.87902

この結果から、パラメータを変えるだけで正解率の最小値と最大値には 0.47625 の差があり、学習時間は最長と最短で 10.275秒 の違いがあることが分かります。
また、学習時間を長くすれば正解率が必ず向上するわけではなく、デフォルト設定が必ずしも最も高いスコアを出すとは限りません。

ただし、今回の結果を見る限りでは、デフォルトの設定が正解率と学習時間のバランスが良いように感じました。

したがって、目的や要件に応じてデータの特性や正解率と学習時間のバランスを考慮しながら、適切なパラメータを選択することが重要だと言えます。


3.2. 様々なアルゴリズム

決定木を用いた分類でさまざまなパラメータを試しましたが、ここでは決定木以外のアルゴリズムも試してみます。

今回使用するデータセットは、scikit-learn の make_classification 関数を用いて生成したもので、教師あり学習の分類タスクを想定しています。
そのため、教師あり学習の分類アルゴリズムを使用してモデルを学習させます。

今回試すアルゴリズムは以下の通りです。


  • 線形モデル:入力データの各特徴を単純に組み合わせ、直線的な境界で分類します。
    • Ridge Classifier:L2正則化を用いて、過学習を抑えながら線形な分類境界を学習します。
    • SGD Classifier:確率的勾配降下法により、大規模データでも高速に学習できます。
    • Passive Aggressive:誤分類した場合にのみパラメータを更新し、効率的に学習を行います。
    • Perceptron:基本的な線形分類アルゴリズムで、データが順次更新される中で学習を進めます。
    • Logistic Regression:各クラスの所属確率を計算し、最も高い確率のクラスを予測します。
  • 識別分析:各クラスのデータ分布を前提として、分布の違いを利用し分類境界を作ります。
    • Quadratic Discriminant Analysis (QDA):クラスごとの分散を個別に考慮し、より柔軟な非線形な境界を構築します。
    • Linear Discriminant Analysis (LDA):データ全体の分散とクラスごとの分散の比から線形の境界を見つけます。
  • SVM系:データと分類境界との距離(マージン)を最大化し、誤分類を最小化する強力な手法です。
    • Support Vector Machine (SVC):カーネル手法により非線形な境界も学習でき、複雑なデータに対応します。
    • Linear SVC:線形な境界を高速に求める方法で、特に高次元データで有効です。
    • NuSVC:サポートベクターの数や誤分類率のバランスを直接調整できる柔軟な手法です。
  • 近傍法:近くのデータ点の情報をもとに、新しいデータのクラスを直感的に決める方法です。
    • K-Nearest Neighbors (KNN):指定された近傍のデータ点の多数決でクラスを決定します。
    • Nearest Centroid:各クラスの重心(セントロイド)との距離を計算し、最も近いクラスに分類します。
  • ナイーブベイズ:各特徴が互いに独立であると仮定し、確率に基づいてクラスを予測します。
    • GaussianNB:各特徴が正規分布に従うとみなし、クラスごとの確率を計算します。
    • BernoulliNB:特徴が0か1のような二値情報の場合に適した手法です。
  • 木ベースモデル:条件に基づいた分割(ルール)でデータをツリー状に分類する方法です。
    • Decision Tree:データの各特徴に対して閾値を設定し、順次分割することで分類ルールを作成します。
  • アンサンブル:複数のモデルの予測結果を組み合わせ、個々の弱点を補完し高い精度を目指します。
    • HistGradient Boosting:データをビン分けして計算負荷を軽減しながら、勾配ブースティングを効率化します。
    • Extra Trees:ランダム性を強化した複数の決定木を作り、結果を統合して予測します。
    • AdaBoost:誤分類されたデータに重点を置き、後続のモデルで修正を試みます。
    • Bagging:データのサブセットを使って複数のモデルを構築し、平均化により安定した予測を実現します.
    • Random Forest:多数の決定木を構築し、その結果の多数決または平均で予測を行います.
    • Gradient Boosting:前のモデルの誤差を補う形で、順次モデルを追加し強力な予測器を作ります。
  • ニューラルネットワーク:多層構造で複雑な非線形関係を学習し、抽象的なパターンを捉えます。
    • MLP Classifier:複数の隠れ層を持つネットワークで、入力データの複雑なパターンを学習して分類します。
  • メタ推定器:複数の基本モデルの予測を組み合わせ、単独のモデルよりも高い性能を実現します。
    • Voting Classifier:各モデルの予測結果を集約し、多数決や平均で最終的な予測を行います。
    • Stacking Classifier:各モデルの出力を新たな入力特徴として利用し、別の学習器で最終判断を下します。
  • キャリブレーション:モデルの予測する確率の信頼度を調整し、実際のクラス分布に近づけるための手法です。
    • Calibrated Classifier:元の分類器の出力確率を補正し、より現実的な予測を実現します。
  • ベースライン:高度な手法と比較するための、シンプルな予測モデルです。
    • Dummy Classifier:単純なルールに基づいて予測し、他のモデルの性能評価の参考とします。

正直、これだけでは何を言ってるのかさっぱりわかりません。
そのため実際にそれぞれのアルゴリズムで正解率や学習時間にどのような違いが出るのかを確認してみます。
基本的にデフォルトのパラメータを使用していますが、一部のモデルでは警告を回避するために最低限のパラメータを指定しています。

また、基本モデルとしてメタ推定器には Logistic Regression, Decision Tree, Support Vector Machine を、キャリブレーションには Logistic Regression を使用しています。

各モデルの正解率と学習時間は以下の通りです。

GroupModelAccuracyTraining Time (sec)
線形モデルRidge Classifier0.70370.3199
線形モデルSGD Classifier0.67165.5137
線形モデルPassive Aggressive0.64340.8979
線形モデルPerceptron0.56361.1462
線形モデルLogistic Regression0.70181.3891
識別分析Quadratic Discriminant Analysis0.95880.3409
識別分析Linear Discriminant Analysis0.70340.7772
SVM系Support Vector Machine0.9762144.432
SVM系Linear SVC0.70383.6537
SVM系NuSVC0.9075674.639
近傍法K-Nearest Neighbors0.96842.8635
近傍法Nearest Centroid0.65281.6627
ナイーブベイズGaussianNB0.70870.4032
ナイーブベイズBernoulliNB0.62930.3276
木ベースモデルDecision Tree0.78956.9506
アンサンブルHistGradient Boosting0.94077.6067
アンサンブルExtra Trees0.94715.6462
アンサンブルAdaBoost0.637825.6874
アンサンブルBagging0.8847.3136
アンサンブルRandom Forest0.937284.8234
アンサンブルGradient Boosting0.8366415.221
ニューラルネットワークMLP Classifier0.9813213.853
メタ推定器Voting Classifier0.8959548.771
メタ推定器Stacking Classifier0.774264.9572
キャリブレーションCalibrated Classifier0.70184.0351
ベースラインDummy Classifier0.33170.0466

この結果から、アルゴリズムによって正解率や学習時間が大きく異なることが分かります。
同じグループのモデルでも、計算コストや精度に差があり、学習時間が長いからといって必ずしも高精度とは限らないことが確認できました。
例えば、Gradient Boosting は学習に 415秒以上かかって正解率が0.8366の一方で、Quadratic Discriminant Analysis は 0.34秒で0.9588の正解率を達成しており、モデル選択では精度と計算コストのバランスを考慮することが重要だと言えます。

・Dummy Classifier はスコアが低いですが、これは他のモデルの性能を測るための「ベースライン」です。
 ランダムな分類よりも高い精度が出ているかを確認する指標として機能します。


今回は教師あり学習の分類アルゴリズムを使用しましたが、データの特性によっては教師なし学習や時系列モデルなど、別の手法を選択する必要があります。
アルゴリズムを選ぶときはデータの特性に応じた選定はもちろん、精度が高いからという理由だけでなく、学習時間やモデルの解釈のしやすさも考慮することが大切だと感じました。


4. 最後に

アルゴリズムによって結果に差が出るのはもちろん、同じアルゴリズムでもパラメータの設定次第で大きく性能が変わることが分かりました。
そのため、データセットに応じて最適なアルゴリズムやパラメータを選ぶのは容易ではありません。
そして単純な精度だけでなく、要件に応じて計算時間やコストのバランスを考慮しながら適切に選択することも重要です。

ぜひ、さまざまなパターンを試しながら、最適な組み合わせを見つけてみてください!