解析環境セットアップ

ライブラリーおよび関数の準備

このノート全体にわたって、共通して使用するパッケージ、関数やデータなどをここで呼び出して、すぐに使える状態にする。

In [1]:
%matplotlib inline

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
In [2]:
import matplotlib.colors

def plot_decision_region(X, y, clf, resolution=0.02):
  """
  決定領域を可視化するための関数
  
  1 番目の特徴量を横軸座標、2 番目の特徴量を縦軸座標として、決定領域を可視化する。なお、特徴量
  が 3 つ以上存在しても、可視化に使用するのは 1 番目および 2 番目の特徴量だけである。

  入力
    X: 特徴量
    y: 教師ラベル
    clf: 分類器
    resolution: メッシュ化間隔

  出力
    なし(図が表示される)
  """

  # 教師ラベルの色(最大で 6 クラスまで異なる色で対応可能)
  c = ('#bc3c29', '#0072b5', '#20854e', '#7876b1', '#6f99ad', '#e18727')
  cmap = matplotlib.colors.ListedColormap(c[:len(np.unique(y))])

  # 横軸および縦軸の範囲を特徴量から計算する
  x1_min = X[:, 0].min() - 1
  x1_max = X[:, 0].max() + 1
  x2_min = X[:, 1].min() - 1
  x2_max = X[:, 1].max() + 1
  
  # 横軸および縦軸を resolution=0.02 間隔でメッシュ化し、メッシュの交点座標を計算する
  xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                         np.arange(x2_min, x2_max, resolution))
  
  # メッシュの各交点座標で予測を行う
  # ただし、xx1 および xx2 は 2 次元配列となっているので、ここで両者を ravel メソッドで
  # 列ベクトルに変換してから予測を行う
  z = clf.predict(np.array([xx1.ravel(), xx2.ravel()]).T)

  # 予測結果 z を xx1 と同じ構造の 2 次元配列に変更する
  z = z.reshape(xx1.shape)

  # xx1 を横軸、xx2 を縦軸、予測結果 z を色として、等高図をプロットする
  plt.contourf(xx1, xx2, z, alpha=0.3, cmap=cmap)
  plt.xlim(xx1.min(), xx1.max())
  plt.ylim(xx2.min(), xx2.max())

  # 入力されたデータ(X と y)に点をプロットする
  for idx, cl in enumerate(np.unique(y)):
    plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=c[idx], label=cl)
  

  # グラフを調整し表示する
  plt.xlabel('X1')
  plt.ylabel('X2')
  plt.tight_layout()
  plt.show()

データセットの準備

このノートでは scikit-learn パッケージに保存されているワインのデータセットを使用する。このデータセットは、3 種類のワインに対して、アルコール、リンゴ酸などの 13 化学物質を定量した結果が記述されている。このノートで、13 種類の化学物質の定量結果を特徴量とし、ワインの名前(種類)を教師ラベルとして、分類器を作成する。ワインデータセットを取得するには、scikit-learn の datasets.load_wine() 関数を使用する。

In [3]:
import sklearn.datasets
wine = sklearn.datasets.load_wine()

print(wine.DESCR)
.. _wine_dataset:

Wine recognition dataset
------------------------

**Data Set Characteristics:**

    :Number of Instances: 178 (50 in each of three classes)
    :Number of Attributes: 13 numeric, predictive attributes and the class
    :Attribute Information:
 		- Alcohol
 		- Malic acid
 		- Ash
		- Alcalinity of ash  
 		- Magnesium
		- Total phenols
 		- Flavanoids
 		- Nonflavanoid phenols
 		- Proanthocyanins
		- Color intensity
 		- Hue
 		- OD280/OD315 of diluted wines
 		- Proline

    - class:
            - class_0
            - class_1
            - class_2
		
    :Summary Statistics:
    
    ============================= ==== ===== ======= =====
                                   Min   Max   Mean     SD
    ============================= ==== ===== ======= =====
    Alcohol:                      11.0  14.8    13.0   0.8
    Malic Acid:                   0.74  5.80    2.34  1.12
    Ash:                          1.36  3.23    2.36  0.27
    Alcalinity of Ash:            10.6  30.0    19.5   3.3
    Magnesium:                    70.0 162.0    99.7  14.3
    Total Phenols:                0.98  3.88    2.29  0.63
    Flavanoids:                   0.34  5.08    2.03  1.00
    Nonflavanoid Phenols:         0.13  0.66    0.36  0.12
    Proanthocyanins:              0.41  3.58    1.59  0.57
    Colour Intensity:              1.3  13.0     5.1   2.3
    Hue:                          0.48  1.71    0.96  0.23
    OD280/OD315 of diluted wines: 1.27  4.00    2.61  0.71
    Proline:                       278  1680     746   315
    ============================= ==== ===== ======= =====

    :Missing Attribute Values: None
    :Class Distribution: class_0 (59), class_1 (71), class_2 (48)
    :Creator: R.A. Fisher
    :Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
    :Date: July, 1988

This is a copy of UCI ML Wine recognition datasets.
https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data

The data is the results of a chemical analysis of wines grown in the same
region in Italy by three different cultivators. There are thirteen different
measurements taken for different constituents found in the three types of
wine.

Original Owners: 

Forina, M. et al, PARVUS - 
An Extendible Package for Data Exploration, Classification and Correlation. 
Institute of Pharmaceutical and Food Analysis and Technologies,
Via Brigata Salerno, 16147 Genoa, Italy.

Citation:

Lichman, M. (2013). UCI Machine Learning Repository
[https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
School of Information and Computer Science. 

.. topic:: References

  (1) S. Aeberhard, D. Coomans and O. de Vel, 
  Comparison of Classifiers in High Dimensional Settings, 
  Tech. Rep. no. 92-02, (1992), Dept. of Computer Science and Dept. of  
  Mathematics and Statistics, James Cook University of North Queensland. 
  (Also submitted to Technometrics). 

  The data was used with many others for comparing various 
  classifiers. The classes are separable, though only RDA 
  has achieved 100% correct classification. 
  (RDA : 100%, QDA 99.4%, LDA 98.9%, 1NN 96.1% (z-transformed data)) 
  (All results using the leave-one-out technique) 

  (2) S. Aeberhard, D. Coomans and O. de Vel, 
  "THE CLASSIFICATION PERFORMANCE OF RDA" 
  Tech. Rep. no. 92-01, (1992), Dept. of Computer Science and Dept. of 
  Mathematics and Statistics, James Cook University of North Queensland. 
  (Also submitted to Journal of Chemometrics).

ワインの名前、すなわち教師ラベルは wine.target で取得できる。ワインの名前を実際に取得して表示させると、以下のようにゼロからの整数値のリストとして表示されていることがわかる。scikit-learn を含むほとんどの機械学習パッケージは文字からなるラベル名をそのまま受け付けないので、そのようなラベル名を整数値に変換する必要がある。そのため、独自のデータセットを使用する際に、解析者自身で教師ラベルを整数値に変換する必要がある。

In [4]:
print(wine.target)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]

また、特徴量は wine.data で取得できる。特徴量は、NumPy の 2 次元配列として保存されている。独自のデータセットを使用する際に、特徴量をこのように 2 次元配列に変換する必要がある。

In [5]:
print(wine.data.shape)
print(wine.data)
(178, 13)
[[1.423e+01 1.710e+00 2.430e+00 ... 1.040e+00 3.920e+00 1.065e+03]
 [1.320e+01 1.780e+00 2.140e+00 ... 1.050e+00 3.400e+00 1.050e+03]
 [1.316e+01 2.360e+00 2.670e+00 ... 1.030e+00 3.170e+00 1.185e+03]
 ...
 [1.327e+01 4.280e+00 2.260e+00 ... 5.900e-01 1.560e+00 8.350e+02]
 [1.317e+01 2.590e+00 2.370e+00 ... 6.000e-01 1.620e+00 8.400e+02]
 [1.413e+01 4.100e+00 2.740e+00 ... 6.100e-01 1.600e+00 5.600e+02]]

scikit-learn で提供されているデータセットの構造を確認できたので、あとで使いやすいように、特徴量を X、教師ラベルを y に代入する。

In [6]:
X = wine.data
y = wine.target

データセットの分布を可視化して一通り傾向を見てみる。Seaborn の pairplot 関数を使用するには、データをデータフレームとして与える必要があるので、ここで Xy を一つのデータフレームにまとめて可視化を行なう。

In [7]:
wine_df = pd.DataFrame(np.concatenate([y.reshape(y.shape[0], 1), X], axis=1),
                       columns=['y' if i == 0 else 'X' + str(i) for i in range(X.shape[1] + 1)])
print(wine_df)
sns.pairplot(wine_df, hue='y')
       y     X1    X2    X3    X4     X5    X6    X7    X8    X9    X10   X11  \
0    0.0  14.23  1.71  2.43  15.6  127.0  2.80  3.06  0.28  2.29   5.64  1.04   
1    0.0  13.20  1.78  2.14  11.2  100.0  2.65  2.76  0.26  1.28   4.38  1.05   
2    0.0  13.16  2.36  2.67  18.6  101.0  2.80  3.24  0.30  2.81   5.68  1.03   
3    0.0  14.37  1.95  2.50  16.8  113.0  3.85  3.49  0.24  2.18   7.80  0.86   
4    0.0  13.24  2.59  2.87  21.0  118.0  2.80  2.69  0.39  1.82   4.32  1.04   
..   ...    ...   ...   ...   ...    ...   ...   ...   ...   ...    ...   ...   
173  2.0  13.71  5.65  2.45  20.5   95.0  1.68  0.61  0.52  1.06   7.70  0.64   
174  2.0  13.40  3.91  2.48  23.0  102.0  1.80  0.75  0.43  1.41   7.30  0.70   
175  2.0  13.27  4.28  2.26  20.0  120.0  1.59  0.69  0.43  1.35  10.20  0.59   
176  2.0  13.17  2.59  2.37  20.0  120.0  1.65  0.68  0.53  1.46   9.30  0.60   
177  2.0  14.13  4.10  2.74  24.5   96.0  2.05  0.76  0.56  1.35   9.20  0.61   

      X12     X13  
0    3.92  1065.0  
1    3.40  1050.0  
2    3.17  1185.0  
3    3.45  1480.0  
4    2.93   735.0  
..    ...     ...  
173  1.74   740.0  
174  1.56   750.0  
175  1.56   835.0  
176  1.62   840.0  
177  1.60   560.0  

[178 rows x 14 columns]
/opt/anaconda3/lib/python3.7/site-packages/statsmodels/nonparametric/kde.py:487: RuntimeWarning: invalid value encountered in true_divide
  binned = fast_linbin(X, a, b, gridsize) / (delta * nobs)
/opt/anaconda3/lib/python3.7/site-packages/statsmodels/nonparametric/kdetools.py:34: RuntimeWarning: invalid value encountered in double_scalars
  FAC1 = 2*(np.pi*bw/RANGE)**2
Out[7]:
<seaborn.axisgrid.PairGrid at 0x1a25c85950>

データセットの準備(独自データセット使用の場合)

独自のデータセットを使用する場合、あらかじめ CSV として保存されているデータセットを Pandas パッケージなどで読み込んでから教師ラベルと特徴量に分割するとよい。以下に、Pandas のデータフレームから特徴量と教師ラベルを取り出して、それぞれ _X_y 変数に代入する例を示す。(※ 必要に応じて _X_y をそれぞれ Xy に書き換えて実行してください。)

まず、独自データセットを pd.read_csv 関数で読み込んで、そのデータを csv_data に保存する。ここで、都合により、実際にファイルの読み込みを行わずに、乱数でデータフレームを生成し、1 列目を教師ラベルとし、2〜5 列目を特徴量とする。

In [8]:
csv_data = pd.DataFrame({
    'label': np.random.choice(['apple', 'orange', 'cherry'], 100),
    'X1': np.random.rand(100),
    'X2': np.random.rand(100),
    'X3': np.random.rand(100),
    'X4': np.random.rand(100) 
})

print(csv_data)
     label        X1        X2        X3        X4
0   cherry  0.142637  0.413604  0.487067  0.255008
1   cherry  0.768993  0.727730  0.474539  0.477821
2    apple  0.495773  0.518547  0.363130  0.082284
3    apple  0.821774  0.592106  0.318485  0.848396
4   cherry  0.067834  0.079203  0.346387  0.682764
..     ...       ...       ...       ...       ...
95   apple  0.511776  0.082249  0.802063  0.686838
96   apple  0.931668  0.985745  0.755576  0.275793
97  orange  0.685421  0.066215  0.736179  0.301902
98  orange  0.137632  0.921763  0.299658  0.823506
99  orange  0.610360  0.423474  0.291591  0.644886

[100 rows x 5 columns]

このデータフレームに含まれている教師ラベルは orange、apple、cherry の文字列からなっている。以下で、この文字列の教師ラベルを scikit-learn パッケージの変換器 LabelEncoder で整数値に変換する。なお、LabelEncoder を使わずに、Python のディクショナリで変換を行なってもよい。

In [9]:
import sklearn.preprocessing

# 教師ラベルの列のみ取り出す
_y_label = csv_data.iloc[:, 0].tolist()

# 教師ラベルを整数に変換するための変換器をセットアップ
le = sklearn.preprocessing.LabelEncoder()
le.fit(list(set(_y_label)))

# 変換
_y = list(le.transform(_y_label))
print(_y_label)  # 変換前
print(_y)        # 変換後


# 逆変換(整数値を教師ラベルに戻す)
_y_label2 = le.inverse_transform([0, 0, 2, 1])
print(_y_label2)
['cherry', 'cherry', 'apple', 'apple', 'cherry', 'orange', 'apple', 'orange', 'apple', 'orange', 'cherry', 'apple', 'cherry', 'apple', 'orange', 'apple', 'apple', 'cherry', 'apple', 'apple', 'orange', 'cherry', 'orange', 'apple', 'orange', 'apple', 'orange', 'cherry', 'cherry', 'orange', 'cherry', 'apple', 'orange', 'cherry', 'cherry', 'apple', 'apple', 'apple', 'apple', 'apple', 'orange', 'cherry', 'cherry', 'apple', 'cherry', 'apple', 'orange', 'orange', 'cherry', 'cherry', 'apple', 'apple', 'orange', 'apple', 'apple', 'cherry', 'orange', 'apple', 'cherry', 'apple', 'orange', 'cherry', 'cherry', 'cherry', 'cherry', 'orange', 'orange', 'orange', 'cherry', 'cherry', 'apple', 'orange', 'orange', 'cherry', 'apple', 'apple', 'apple', 'orange', 'apple', 'orange', 'cherry', 'apple', 'orange', 'apple', 'orange', 'apple', 'cherry', 'orange', 'cherry', 'apple', 'orange', 'orange', 'orange', 'orange', 'apple', 'apple', 'apple', 'orange', 'orange', 'orange']
[1, 1, 0, 0, 1, 2, 0, 2, 0, 2, 1, 0, 1, 0, 2, 0, 0, 1, 0, 0, 2, 1, 2, 0, 2, 0, 2, 1, 1, 2, 1, 0, 2, 1, 1, 0, 0, 0, 0, 0, 2, 1, 1, 0, 1, 0, 2, 2, 1, 1, 0, 0, 2, 0, 0, 1, 2, 0, 1, 0, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 0, 2, 2, 1, 0, 0, 0, 2, 0, 2, 1, 0, 2, 0, 2, 0, 1, 2, 1, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2]
['apple' 'apple' 'orange' 'cherry']
In [10]:
_X = csv_data.iloc[:, 1:]
print(_X)
          X1        X2        X3        X4
0   0.142637  0.413604  0.487067  0.255008
1   0.768993  0.727730  0.474539  0.477821
2   0.495773  0.518547  0.363130  0.082284
3   0.821774  0.592106  0.318485  0.848396
4   0.067834  0.079203  0.346387  0.682764
..       ...       ...       ...       ...
95  0.511776  0.082249  0.802063  0.686838
96  0.931668  0.985745  0.755576  0.275793
97  0.685421  0.066215  0.736179  0.301902
98  0.137632  0.921763  0.299658  0.823506
99  0.610360  0.423474  0.291591  0.644886

[100 rows x 4 columns]

k-近傍法

k-近傍法の予測器は scikit-learn パッケージの KNeighborsClassifier で構築できる。ここで、全データセットを scikit-learn の train_test_split 関数で訓練データセットとテストデータセットに分割する。この際に、テストデータセットの割合を全体の 40% とする。続いて、訓練データセットで k-近傍法予測器を構築し、テストデータセットで予測器の性能を検証する。

In [11]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.neighbors

# ワインデータセットには 13 特徴量あるが、説明しやすくために、ここでは最初の 2 特徴量のみを使用する
X_subset = X[:, 0:2]


# データセットを訓練データセットとテストデータセットに分割する
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


# 訓練データセットで学習する
clf = sklearn.neighbors.KNeighborsClassifier(n_neighbors=2)
clf.fit(X_train, y_train)


# テストデータセットで評価する
y_pred = clf.predict(X_test)
print(y_pred)


# accuracy を計算する
score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[0 0 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 2 0 2 1 1 0 0 2 0 0 1 1 0 0 1 0 1 2
 0 1 1 1 2 1 1 1 0 1 2 1 1 1 0 0 0 2 2 1 1 0 0 0 1 2 0 1 1 0 0 1 0 0 0]
0.7222222222222222
In [12]:
plot_decision_region(X, y, clf)

k-近傍法のハイパーパラメーター k を変更することで、モデル予測結果が変化する。上のプログラムを修正し、k を 2 から 20 に変化させたときの accuracy を求めよ。また、最大 accuracy を与える k を求めよ。なお、k は、n_neigbors 引数を介して指定する。

※ 説明があるまで train_test_split 関数中のパラメーターを変更しないでください。

In [13]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.neighbors

# データセットの分割
X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


# 最適な k を調べる
for i in range(19):
    k = i + 2
    clf = sklearn.neighbors.KNeighborsClassifier(n_neighbors=k)
    clf.fit(X_train, y_train)     # 学習
    y_pred = clf.predict(X_test)  # 予測
    score = sklearn.metrics.accuracy_score(y_test, y_pred) # 予測結果を評価
    print(k, score)
2 0.7222222222222222
3 0.75
4 0.7916666666666666
5 0.7916666666666666
6 0.7916666666666666
7 0.8055555555555556
8 0.7916666666666666
9 0.8055555555555556
10 0.7777777777777778
11 0.7638888888888888
12 0.7777777777777778
13 0.8055555555555556
14 0.8055555555555556
15 0.7916666666666666
16 0.8055555555555556
17 0.8055555555555556
18 0.7916666666666666
19 0.7916666666666666
20 0.7777777777777778

ロジスティック回帰

ロジスティック回帰の予測器は scikit-learn パッケージの LogisticRegression で構築できる。ここで、全データセットを scikit-learn の train_test_split 関数で訓練データセットとテストデータセットに分割する。この際に、テストデータセットの割合を全体の 40% とする。続いて、訓練データセットで ロジスティック回帰予測器を構築し、テストデータセットで予測器の性能を検証する。

In [14]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model

X_subset = X[:, 0:2]

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs')
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(y_pred)

score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[1 0 1 1 1 2 1 1 0 1 2 1 1 1 0 1 1 1 1 2 1 1 1 1 0 1 2 0 0 1 1 0 0 1 1 1 2
 1 1 1 1 1 1 1 1 0 1 2 1 1 1 0 1 0 2 2 1 2 0 0 1 1 2 1 1 1 0 0 1 2 0 1]
0.625
In [15]:
plot_decision_region(X, y, clf)

モデル構築後の LogisticRegression インスタンスには、ロジスティック回帰式における係数や定数項などの情報が含まれている。これらは clf.x_ のようにアクセスできおる。なお、多クラス比較において、scikit-learn では one-vs-rest (OvR) が取られているため、各教師ラベルに対してモデルが構築される。そのため、ロジスティック回帰式の係数も教師ラベルの種類分存在する。

In [16]:
print(clf.classes_)
[0 1 2]
In [17]:
# 2 features x 3 classes
print(clf.coef_)
[[ 0.23755703 -0.09507465]
 [-0.2467447  -0.14319319]
 [ 0.01279926  0.24228506]]
In [18]:
# 2 features x 3 classes
print(clf.intercept_)
[-3.62449434  3.1076558  -1.67002037]

ロジスティック回帰のハイパーパラメーターとして、罰則の大きさ C を制御するパラメーターがある。このハイパーパラメーターの取りうる値を調べ、最高性能を与える C の値を調べてみよう。

In [19]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


# 等差数列で最適な C を探索してもいいが、指数にすると効率がいい
score_df = []

for c in 10**np.linspace(-5, 2, 10):
    clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=c, multi_class='ovr', solver='lbfgs')
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    score = sklearn.metrics.accuracy_score(y_test, y_pred)
    
    score_df.append([c, score])

score_df = pd.DataFrame(score_df, columns=['C', 'accuracy'])
sns.lineplot(x='C', y='accuracy', data=score_df)
Out[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a2cc98f50>
In [20]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


score_df = []
for c in np.linspace(0.05, 0.5, 10):
    clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=c, multi_class='ovr', solver='lbfgs')
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    score = sklearn.metrics.accuracy_score(y_test, y_pred)
    
    score_df.append([c, score])

score_df = pd.DataFrame(score_df, columns=['C', 'accuracy'])

sns.lineplot(x='C', y='accuracy', data=score_df)
Out[20]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a2cdce090>

教師データセットが大量にあり、メモリに乗り切らない場合は、確率的勾配降下法を利用したロジスティック回帰 SGDClassifier を使用して学習を進めることができる。SGDClassifier を使用する際に、エポック数を十分に確保する必要がある。

参考 https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html

In [21]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

# model training with the training subset
# try to set random_stat to 26, 955 
clf = sklearn.linear_model.SGDClassifier(loss='log', penalty='l2', alpha=0.1, max_iter=1e9, random_state=2020)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(y_pred)

score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[1 1 2 1 1 2 1 1 1 1 2 1 1 1 1 1 2 1 2 2 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 2
 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 2 1 2 1 1 1 1 2 1 1 1 1 1 1 2 1 1]
0.4583333333333333
In [22]:
plot_decision_region(X, y, clf)

SVM

SVM の予測器は scikit-learn パッケージの SVC で構築できる。ここで、全データセットを scikit-learn の train_test_split 関数で訓練データセットとテストデータセットに分割する。この際に、テストデータセットの割合を全体の 40% とする。続いて、訓練データセットで SVM 予測器を構築し、テストデータセットで予測器の性能を検証する。

In [23]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.svm

X_subset = X[:, 0:2]

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

clf = sklearn.svm.SVC(kernel='rbf')
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(y_pred)

score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[0 2 1 1 1 2 1 1 0 0 2 1 0 1 0 1 1 1 2 2 0 2 1 1 0 0 2 0 0 1 1 2 0 1 0 1 2
 2 1 1 1 2 1 1 1 0 1 2 1 2 1 0 0 0 2 2 1 2 0 0 0 2 2 2 1 1 0 0 1 2 0 0]
0.8055555555555556
/opt/anaconda3/lib/python3.7/site-packages/sklearn/svm/base.py:193: FutureWarning: The default value of gamma will change from 'auto' to 'scale' in version 0.22 to account better for unscaled features. Set gamma explicitly to 'auto' or 'scale' to avoid this warning.
  "avoid this warning.", FutureWarning)
In [24]:
plot_decision_region(X, y, clf)

SVM のソフトマージン法は、サンプルが決定境界を超えて反対側に存在することも許容している。その許容範囲をハイパーパラメーター C で指定できる。ここで、C の取りうる値の範囲を調べて、SVM の分類性能が最大となるような C の値を調べてみよう。

In [25]:
# find the best C

複雑な問題に対して、既存の特徴量を高次元空間に写像してから分類を行うカーネルトリックが有効である。このときカーネル関数として多項式カーネルや RBF カーネルを選ぶことができる。scikit-learn の SVC 関数の使い方を調べ、カーネル関数の与え方や各カーネル関数のハイパーパラメーターの与え方などを調べて、最高性能を与えるカーネル関数およびそのハイパーパラメーターを調べてみよう。

In [26]:
# for c: 
#   for k in [rbf, poly, tanh]:
#     for  a in []:
#       for b in []:
#         clf.fit
#         clf.predict
#

メモリに乗り切らない巨大なデータセットの場合は、scikit-learn の SGDClassifier を使用する。このとき、損失関数 loss をヒンジ関数と指定する。

In [27]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model


# only use 2 features
X_subset = X[:, 0:2]


# split data into two subsets for training and validation
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


# model training with the training subset
clf = sklearn.linear_model.SGDClassifier(loss='hinge', alpha=0.1, max_iter=1e9)
clf.fit(X_train, y_train)


# prediction with the test subset
y_pred = clf.predict(X_test)
print(y_pred)


# calculate accuracy
score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[0 2 2 2 2 2 0 2 0 0 2 0 0 1 0 0 2 0 2 2 0 2 0 2 0 0 2 0 0 2 0 2 0 0 0 0 2
 2 0 1 2 2 2 0 1 0 1 2 0 2 1 2 0 0 2 2 2 2 0 0 0 2 2 2 0 0 0 0 1 2 0 0]
0.5833333333333334
In [28]:
plot_decision_region(X, y, clf)

決定木

決定木アルゴリズムによる予測器は scikit-learn パッケージの DecisionTreeClassifier で構築できる。ここで、全データセットを scikit-learn の train_test_split 関数で訓練データセットとテストデータセットに分割する。この際に、テストデータセットの割合を全体の 40% とする。続いて、訓練データセットで予測器を構築し、テストデータセットで予測器の性能を検証する。

In [29]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.tree

X_subset = X[:, 0:2]

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

clf = sklearn.tree.DecisionTreeClassifier(criterion='gini', max_depth=3)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(y_pred)

score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[0 2 1 1 1 1 0 1 0 0 0 1 0 1 0 1 1 1 1 2 0 0 1 1 0 0 2 0 0 1 1 2 0 1 0 1 2
 2 1 1 1 2 1 1 1 0 1 2 1 1 1 0 0 0 2 2 1 2 0 0 0 0 2 2 1 1 0 0 1 0 0 0]
0.8055555555555556
In [30]:
plot_decision_region(X, y, clf)

決定木は機械学習の中でも解釈可能なアルゴリズムとして知られている。ここで、決定木の判断基準を樹形図で示す。この図示には pydotplus パッケージの機能を必要とする。また、Jupyter Notebook 上で樹形図を表示させるためには Ipython パッケージの機能を必要とする。

In [31]:
import sklearn
import sklearn.externals.six
import pydot
import IPython.display

dot_data = sklearn.externals.six.StringIO()
sklearn.tree.export_graphviz(clf, out_file=dot_data,
                             feature_names=['X1', 'X2'],
                             class_names=['0', '1', '2'],
                             filled=True, proportion=True)
(graph, ) = pydot.graph_from_dot_data(dot_data.getvalue())
IPython.display.Image(graph.create_png())
/opt/anaconda3/lib/python3.7/site-packages/sklearn/externals/six.py:31: DeprecationWarning: The module is deprecated in version 0.21 and will be removed in version 0.23 since we've dropped support for Python 2.7. Please rely on the official version of six (https://pypi.org/project/six/).
  "(https://pypi.org/project/six/).", DeprecationWarning)
Out[31]:

dtreeviz パッケージによる可視化は、特徴量と教師ラベルの特徴に応じて、散布図や棒グラフなどで可視化して、全体像が把握しやすい。デフォルトで dtreeviz パッケージがインストールされていない場合があるので、適宜にインストールする。なお、決定木を表示するための関数は viz.view だが、Jupyter Notebook 上に表示させたいので、ここで viz.view の代わりに display 関数を使用する。

In [32]:
!pip install pydot
!pip install dtreeviz
Requirement already satisfied: pydot in /opt/anaconda3/lib/python3.7/site-packages (1.4.1)
Requirement already satisfied: pyparsing>=2.1.4 in /opt/anaconda3/lib/python3.7/site-packages (from pydot) (2.4.2)
Requirement already satisfied: dtreeviz in /opt/anaconda3/lib/python3.7/site-packages (0.8.1)
Requirement already satisfied: numpy in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (1.17.2)
Requirement already satisfied: colour in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (0.1.5)
Requirement already satisfied: matplotlib in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (3.1.1)
Requirement already satisfied: pandas in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (0.25.1)
Requirement already satisfied: scikit-learn in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (0.21.3)
Requirement already satisfied: graphviz>=0.9 in /opt/anaconda3/lib/python3.7/site-packages (from dtreeviz) (0.13.2)
Requirement already satisfied: cycler>=0.10 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib->dtreeviz) (0.10.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib->dtreeviz) (1.1.0)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib->dtreeviz) (2.4.2)
Requirement already satisfied: python-dateutil>=2.1 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib->dtreeviz) (2.8.0)
Requirement already satisfied: pytz>=2017.2 in /opt/anaconda3/lib/python3.7/site-packages (from pandas->dtreeviz) (2019.3)
Requirement already satisfied: joblib>=0.11 in /opt/anaconda3/lib/python3.7/site-packages (from scikit-learn->dtreeviz) (0.13.2)
Requirement already satisfied: scipy>=0.17.0 in /opt/anaconda3/lib/python3.7/site-packages (from scikit-learn->dtreeviz) (1.3.1)
Requirement already satisfied: six in /opt/anaconda3/lib/python3.7/site-packages (from cycler>=0.10->matplotlib->dtreeviz) (1.12.0)
Requirement already satisfied: setuptools in /opt/anaconda3/lib/python3.7/site-packages (from kiwisolver>=1.0.1->matplotlib->dtreeviz) (41.4.0)
In [33]:
import dtreeviz.trees  

viz = dtreeviz.trees.dtreeviz(clf, X_train, y_train,
                              target_name='wine',
                              feature_names=['X1', 'X2'],
                              class_names=['1', '2', '3']) 
#viz.view()
display(viz)
G cluster_legend node2 leaf3 node2->leaf3 leaf4 node2->leaf4 leaf5 node1 node1->node2 node6 node1->leaf5 node7 node10 leaf8 node7->leaf8 leaf9 node7->leaf9 leaf11 node10->leaf11 leaf12 node10->leaf12 node6->node7 node6->node10 node0 node0->node1 < node0->node6 legend

決定木では、各特徴量が分類にどれぐらい寄与しているかという重要度も合わせて計算される。この重要度は、feature_importances_ に保存される。

In [34]:
print(clf.feature_importances_)
[0.56786884 0.43213116]

ランダムフォレスト

ランダムフォレストによる予測器は scikit-learn パッケージの RandomForestClassifier で構築できる。ここで、全データセットを scikit-learn の train_test_split 関数で訓練データセットとテストデータセットに分割する。この際に、テストデータセットの割合を全体の 40% とする。続いて、訓練データセットで予測器を構築し、テストデータセットで予測器の性能を検証する。

In [35]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.ensemble

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

clf = sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion='gini',
                                              max_depth=3, random_state=2020)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(y_pred)

score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
[0 0 1 1 1 2 1 1 0 0 2 1 0 1 0 1 1 1 2 2 0 0 1 1 0 0 2 0 0 1 1 0 0 1 0 1 2
 2 1 1 1 2 1 1 1 0 1 2 1 1 1 0 0 0 2 2 1 2 0 0 0 2 2 2 1 1 0 0 1 2 0 0]
0.7777777777777778
In [36]:
plot_decision_region(X, y, clf)

ランダムフォレストも決定木と同様に各特徴量の重要度を算出している。その重要度は、決定木と同様に feature_importances_ から取得できる。

In [37]:
print(clf.feature_importances_)
[0.67792168 0.32207832]

ランダムフォレストを構築するとき n_estimators=10 と指定した。そのため、このランダムフォレスト中には 10 本の決定木が保存されている。これらの決定木はリストとして estimators_ に保存されている。

In [38]:
print(clf.estimators_)
[DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=2088543072, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=639299976, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=1603706179, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=522041974, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=41856707, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=944261211, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=1167525501, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=1976532611, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=1447054919, splitter='best'), DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=681670584, splitter='best')]

例えば、10 本の決定木のうち最初の決定木を図示する際に、決定木と同じような操作を行う。

In [39]:
clf_tree_0 = clf.estimators_[0]

viz = dtreeviz.trees.dtreeviz(clf_tree_0, X_train, y_train,
                              target_name='wine',
                              feature_names=['X1', 'X2'],
                              class_names=['1', '2', '3']) 

#viz.view()
display(viz)
G cluster_legend node2 node5 leaf3 node2->leaf3 leaf4 node2->leaf4 leaf6 node5->leaf6 leaf7 node5->leaf7 node1 node1->node2 node1->node5 node8 node9 leaf10 node9->leaf10 leaf11 node9->leaf11 leaf12 node8->node9 node8->leaf12 node0 node0->node1 < node0->node8 legend

ランダムフォレストで出力される重要度は特徴量選択に使用することもある。ここで、13 特徴量全部を使って、ランダムフォレストを構築し、これら 13 特徴量の重要度を可視化してみる。

In [40]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.ensemble

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X, y, test_size=0.4, random_state=2020)

clf = sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion='gini', max_depth=3)
clf.fit(X_train, y_train)
Out[40]:
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
                       max_depth=3, max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, n_estimators=10,
                       n_jobs=None, oob_score=False, random_state=None,
                       verbose=0, warm_start=False)
In [41]:
importance_df = pd.DataFrame({
    'feature': ['X{:02}'.format(i + 1) for i in range(X.shape[1])],
    'importance': clf.feature_importances_
})
print(importance_df)
importance_df = importance_df.sort_values('importance', ascending=False)
sns.barplot(x='feature', y='importance', data=importance_df)
   feature  importance
0      X01    0.102911
1      X02    0.069102
2      X03    0.000000
3      X04    0.015543
4      X05    0.004746
5      X06    0.000000
6      X07    0.160751
7      X08    0.007693
8      X09    0.042544
9      X10    0.161239
10     X11    0.165561
11     X12    0.092705
12     X13    0.177204
Out[41]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a30c0f810>

ランダムフォレストで求めた重要度の高い 13 番目と 1 番目の特徴量を使用してモデルを構築した場合と、これまでと同様に 1 番目と 2 番目の特徴量を使用してモデルを構築した場合の性能を比較してみる。ここで、分類アルゴリズムとしてロジスティック回帰を使う。

In [42]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model

# features #1 and #2
X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)
clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs')
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)

# features #1 and #13
X_subset = X[:, [0, 12]]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)
clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs')
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
0.625
0.7083333333333334

ロジスティック回帰のハイパーパラメーター C を様々な値に変化させて、性能を評価すると、常に 1 番目と 13 番目の特徴量を使って学習したモデルの方が優れていることがわかる。このように、機械学習でモデルを構築する上で、特徴量の選択が予測性能に大きく影響することがわかる。

アンサンブル学習

アンサンブル学習による予測器を作成する例をここで示す。ここで、ロジスティック回帰、SVM、および決定木を構築し、これらまとめて多数決による予測器を作成する。

In [43]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model
import sklearn.svm
import sklearn.tree
import sklearn.ensemble

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)


# ロジスティック回帰
clf_1 = sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs')

# SVM
clf_2 = sklearn.svm.SVC(kernel='linear')

# 決定木
clf_3 = sklearn.tree.DecisionTreeClassifier(criterion='gini', max_depth=3)


# アンサンブル学習
clf = sklearn.ensemble.VotingClassifier(estimators=[('lr', clf_1), ('svm', clf_2), ('dt', clf_3)], voting='hard')
clf.fit(X_train, y_train)


# モデル評価
y_pred = clf.predict(X_test)
print('ensemble: ', sklearn.metrics.accuracy_score(y_test, y_pred))
ensemble:  0.7777777777777778

アンサンブル学習により構築されたモデルに含まれる各サブ予測器は estimator_ を介してアクセスできる。各々の学習器を取り出して、単独に評価できたりもする。

In [44]:
y_pred_1 = clf.estimators_[0].predict(X_test)
print('clf_1: ', sklearn.metrics.accuracy_score(y_test, y_pred_1))

y_pred_2 = clf.estimators_[1].predict(X_test)
print('clf_2: ', sklearn.metrics.accuracy_score(y_test, y_pred_2))

y_pred_3 = clf.estimators_[2].predict(X_test)
print('clf_3: ', sklearn.metrics.accuracy_score(y_test, y_pred_3))
clf_1:  0.625
clf_2:  0.75
clf_3:  0.8055555555555556

アンサンブル学習アルゴリズムの一つである AdaBoost を使用して機械学習モデルを構築してみる。必要に応じてハイパーパラメーターを探し、最適化をしてみよう。

In [45]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model
import sklearn.svm
import sklearn.tree
import sklearn.ensemble

X_subset = X[:, 0:2]
X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X_subset, y, test_size=0.4, random_state=2020)

clf = sklearn.ensemble.AdaBoostClassifier()
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print('ensemble: ', sklearn.metrics.accuracy_score(y_test, y_pred))
ensemble:  0.6388888888888888

パイプライン

scikit-learn パッケージでは、上のように分類器を、単独で構築しても良いが、パイプラインとして構築することもできる。分類器を学習する前に、特徴量の標準化などを行う場合にとくに便利である。

ここからワインのデータセット全体を使用して分類器を作成する。このとき、各特徴量それぞれに対して平均 0、分散 1 となるように標準化を行ってから学習することにする。また、ロジスティック回帰が比較的にハイパーパラメーター数が少なく使いやすいので、ここでロジスティック回帰を例にして説明する。

In [46]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model
import sklearn.preprocessing

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X, y, test_size=0.4, random_state=2020)


# 標準化インスタンスを生成
scaler = sklearn.preprocessing.StandardScaler()
# 訓練データセットで標準化パラメーター(平均&分散)を計算
scaler.fit(X_train)
# 訓練データセットを標準化
X_train_std = scaler.transform(X_train)


clf = sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs')
clf.fit(X_train_std, y_train)


# テストデータセットを標準化
X_test_std = scaler.transform(X_test)
y_pred = clf.predict(X_test_std)


score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
0.9861111111111112

標準化を行う際の注意点として、必ず訓練データセットを用いて標準化用のパラメーターを求めること。また、テストデータセットを標準化するときは、訓練データセットで算出されたパラメーターで標準を行う必要がある。そのため、上のコードでは scaler を、X_train で標準化パラメーターを計算してから、X_trainX_test の両方を標準化している。

scaler に保存されているパラメーターは次のように確認できる。13 特徴量それぞれを標準化するための平均と分散が保存されている。

In [47]:
print(scaler.mean_)
print(scaler.var_)
[1.29916038e+01 2.30792453e+00 2.36358491e+00 1.92924528e+01
 9.96132075e+01 2.30273585e+00 2.00245283e+00 3.67547170e-01
 1.58037736e+00 5.12216980e+00 9.63679245e-01 2.58198113e+00
 7.35943396e+02]
[6.18188937e-01 1.23623154e+00 7.90305447e-02 1.16765468e+01
 2.18916429e+02 4.34536855e-01 1.09161285e+00 1.73487006e-02
 3.45984763e-01 4.81975275e+00 5.33685386e-02 5.50685698e-01
 8.96127892e+04]

これらの値は、X_train の各列の平均と分散に等しい。

In [48]:
print(X_train.mean(axis=0))
print(X_train.var(axis=0))
[1.29916038e+01 2.30792453e+00 2.36358491e+00 1.92924528e+01
 9.96132075e+01 2.30273585e+00 2.00245283e+00 3.67547170e-01
 1.58037736e+00 5.12216980e+00 9.63679245e-01 2.58198113e+00
 7.35943396e+02]
[6.18188937e-01 1.23623154e+00 7.90305447e-02 1.16765468e+01
 2.18916429e+02 4.34536855e-01 1.09161285e+00 1.73487006e-02
 3.45984763e-01 4.81975275e+00 5.33685386e-02 5.50685698e-01
 8.96127892e+04]

訓練データセットの特徴量に対して、scalercls の 2 つのインスタンスに順番に入出力している。また、同様にテストデータセットの特徴量に対しても、scalercls の 2 つのインスタンスに順番に入出力している。両者は同じ手続きを踏んでいるので、パイプラインとしてまとめることができる。以下、両者をパイプラインとして求める例を示す。

In [49]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model
import sklearn.preprocessing
import sklearn.pipeline

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X, y, test_size=0.4, random_state=2020)


# パイプラインを構築
pipe = sklearn.pipeline.Pipeline([
         ('scaler', sklearn.preprocessing.StandardScaler()),
         ('clf', sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs'))
       ])

# パイプラインを使用した学習
pipe.fit(X_train, y_train)


# 評価
y_pred = pipe.predict(X_test)
score = sklearn.metrics.accuracy_score(y_test, y_pred)
print(score)
0.9861111111111112

pipe インスタンスには scaler および clf の二つのインスタンスが保存されている。それぞのインスタンスおよびそのパラメーターには、以下のようにアクセスできる。

In [50]:
print(pipe.get_params())
{'memory': None, 'steps': [('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('clf', LogisticRegression(C=0.01, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='ovr', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False))], 'verbose': False, 'scaler': StandardScaler(copy=True, with_mean=True, with_std=True), 'clf': LogisticRegression(C=0.01, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='ovr', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False), 'scaler__copy': True, 'scaler__with_mean': True, 'scaler__with_std': True, 'clf__C': 0.01, 'clf__class_weight': None, 'clf__dual': False, 'clf__fit_intercept': True, 'clf__intercept_scaling': 1, 'clf__l1_ratio': None, 'clf__max_iter': 100, 'clf__multi_class': 'ovr', 'clf__n_jobs': None, 'clf__penalty': 'l2', 'clf__random_state': None, 'clf__solver': 'lbfgs', 'clf__tol': 0.0001, 'clf__verbose': 0, 'clf__warm_start': False}
In [51]:
print(pipe[0].mean_)
print(pipe['scaler'].var_)
[1.29916038e+01 2.30792453e+00 2.36358491e+00 1.92924528e+01
 9.96132075e+01 2.30273585e+00 2.00245283e+00 3.67547170e-01
 1.58037736e+00 5.12216980e+00 9.63679245e-01 2.58198113e+00
 7.35943396e+02]
[6.18188937e-01 1.23623154e+00 7.90305447e-02 1.16765468e+01
 2.18916429e+02 4.34536855e-01 1.09161285e+00 1.73487006e-02
 3.45984763e-01 4.81975275e+00 5.33685386e-02 5.50685698e-01
 8.96127892e+04]
In [52]:
print(pipe[1].coef_)
print(pipe['clf'].intercept_)
[[ 1.99743953e-01 -3.59096591e-02  7.75795293e-02 -1.46339297e-01
   8.38181280e-02  1.34457939e-01  1.54458830e-01 -8.90763528e-02
   1.03376973e-01  3.96175135e-02  6.53620477e-02  1.37209194e-01
   2.33534749e-01]
 [-2.40590184e-01 -1.07608323e-01 -1.04634736e-01  6.57309901e-02
  -8.98381693e-02  1.37647150e-03  2.96542806e-02 -6.10024383e-03
  -1.77519183e-04 -2.16056512e-01  1.31437152e-01  5.56755862e-02
  -1.95245222e-01]
 [ 3.74601458e-02  1.42464732e-01  3.39778214e-02  8.43141507e-02
   1.06808080e-02 -1.41807600e-01 -1.88086540e-01  9.63387736e-02
  -1.10037196e-01  1.72924495e-01 -1.99037117e-01 -1.94888546e-01
  -4.16009504e-02]]
[-0.88283358 -0.46382731 -1.10190026]

アンサンブル学習で使用した VotingClassifier に予測器のほかに、パイプラインを代入することもできる。ここで、いくつかのパイプラインを作成してアンサンブル学習を行ってみよう。

In [53]:
import sklearn.model_selection
import sklearn.metrics
import sklearn.linear_model
import sklearn.svm
import sklearn.tree
import sklearn.ensemble
import sklearn.pipeline

X_train, X_test, y_train, y_test =\
  sklearn.model_selection.train_test_split(X, y, test_size=0.4, random_state=2020)

# パイプラインを構築
pipe_1 = sklearn.pipeline.Pipeline([
         ('scaler', sklearn.preprocessing.StandardScaler()),
         ('clf', sklearn.linear_model.LogisticRegression(penalty='l2', C=0.01, multi_class='ovr', solver='lbfgs'))
       ])

pipe_2 = sklearn.pipeline.Pipeline([
         ('scaler', sklearn.preprocessing.StandardScaler()),
         ('clf', sklearn.svm.SVC(kernel='linear'))
       ])

clf_3 = sklearn.tree.DecisionTreeClassifier(criterion='gini', max_depth=3)


# アンサンブル学習
clf = sklearn.ensemble.VotingClassifier(estimators=[('pipe_1', pipe_1), ('pipe_2', pipe_2), ('clf_3', clf_3)], voting='hard')
clf.fit(X_train, y_train)


# モデル評価
y_pred = clf.predict(X_test)
print('ensemble: ', sklearn.metrics.accuracy_score(y_test, y_pred))
ensemble:  0.9861111111111112

このとき clf インスタンスには 3 つのサブ予測器が保存され、それぞれが pipe_1pipe_2、および clf_3 となっている。各サブ予測器にアクセスして、単独に予測を行うことも可能である。

In [54]:
y_pred_1 = clf.estimators_[1].predict(X_test)
print('clf_1: ', sklearn.metrics.accuracy_score(y_test, y_pred_1))
clf_1:  0.9722222222222222
In [ ]: