[Python] k近傍法 (2) 標準化

前回からの続きです。

scikit-learn という有名なモジュールを用いて、有名なあやめの分類問題を、k近傍法で解いてみます。 IPython で行います。 k近傍法 k近傍法(ケイきんぼうほう、英:k-nearest neighbor algorithm,k-NN)は、特徴空...

あやめの分類問題は素での正答率が高すぎたので、今回は同じくscikit-learn に含まれるワインのデータセットを使い、 k近傍法で予測を行います。

その後、説明変数の標準化を行ってから k近傍法で予測を行い、予測がどのように異なるか、標準化の効果を確認します。

ワインのデータセット

sklearn.datasets.load_wine

13個の説明変数からワインの品種(3種類)を予測するデータセットです。

それぞれの項目名は良く分かりません…。

標準化

平均が0、標準偏差(SD)が1になるように、全ての説明変数を変換します。

$$ z = \frac {x – \mu } {s} $$

この標準化された値は、Z得点とも呼ばれます。

標準得点

標準化の必要性については下記が詳しいですが、k近傍法では一般的に標準化した方が良いと言われます。

距離を考えるのなら標準化した方がやりやすそうだなというのは、直感的に理解はできます。

Feature Scalingはなぜ必要?

ワインの分類問題を解く

まずは、普通にワインの分類問題を解きます。

データの読み込みと基本統計量の確認を行います。

In [1]: from sklearn import datasets
In [2]: wine = datasets.load_wine()
In [3]: import pandas as pd
In [4]: df = pd.DataFrame(wine.data, columns=wine.feature_names)
In [5]: df.describe()
Out[5]: 
          alcohol  malic_acid         ash  ...         hue  od280/od315_of_diluted_wines      proline
count  178.000000  178.000000  178.000000  ...  178.000000                    178.000000   178.000000
mean    13.000618    2.336348    2.366517  ...    0.957449                      2.611685   746.893258
std      0.811827    1.117146    0.274344  ...    0.228572                      0.709990   314.907474
min     11.030000    0.740000    1.360000  ...    0.480000                      1.270000   278.000000
25%     12.362500    1.602500    2.210000  ...    0.782500                      1.937500   500.500000
50%     13.050000    1.865000    2.360000  ...    0.965000                      2.780000   673.500000
75%     13.677500    3.082500    2.557500  ...    1.120000                      3.170000   985.000000
max     14.830000    5.800000    3.230000  ...    1.710000                      4.000000  1680.000000

散布図行列を作成します。

In [11]: df['classes'] = np.array([wine.target_names[i] for i in wine.target])
    ...: sns.pairplot(df, hue='classes');

説明変数が多く細かいです。

データの分割を行います。

In [15]: from sklearn.model_selection import train_test_split
    ...: X_train, X_test, y_train, y_test = train_test_split(df[wine.feature_names],
    ...:                                                     wine.target,
    ...:                                                     test_size = 0.3,
    ...:                                                     stratify=wine.target,
    ...:                                                     random_state=0
    ...:                                                     )

学習を行います。

In [17]: from sklearn.neighbors import KNeighborsClassifier
    ...: knn = KNeighborsClassifier()
    ...: knn.fit(X_train, y_train)
Out[17]: 
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=5, p=2,
                     weights='uniform')

予測とその評価を行います。

In [22]: knn.score(X_test, y_test)
Out[22]: 0.7222222222222222
In [24]: predicted = knn.predict(X_test)
    ...: from sklearn.metrics import confusion_matrix
    ...: confusion_matrix = pd.DataFrame(confusion_matrix(y_test, predicted),
    ...:                                 columns=wine.target_names,
    ...:                                 index=wine.target_names)
    ...: confusion_matrix
Out[24]: 
         class_0  class_1  class_2
class_0       18        0        0
class_1        2       13        6
class_2        4        3        8

データを標準化しワインの分類問題を解く

データを標準化し、ワインの分類問題を解いてみます。

Python での標準化の実装方法は、下記に一通り載っています。

Pythonで正規化・標準化(リスト、NumPy配列、pandas.DataFrame)

不偏分散を使わないのはどうなのだろうか?という考えはありますが、ここでは、sickit-learn を使います。

sklearn.preprocessing.StandardScaler

In [32]: from sklearn.preprocessing import StandardScaler
    ...: scaler = StandardScaler()
    ...: scaler.fit(wine.data)
    ...: wine_data_std = scaler.transform(wine.data)
    ...: df_sd1 = pd.DataFrame(wine_data_std, columns=wine.feature_names)
    ...: df_sd1.describe()
Out[32]: 
          alcohol    malic_acid           ash  alcalinity_of_ash     magnesium  total_phenols    flavanoids  nonflavanoid_phenols  proanthocyanins  color_intensity           hue  od280/od315_of_diluted_wines       proline
count  178.000000  1.780000e+02  1.780000e+02       1.780000e+02  1.780000e+02   1.780000e+02  1.780000e+02          1.780000e+02     1.780000e+02     1.780000e+02  1.780000e+02                    178.000000  1.780000e+02
mean     0.000000 -3.991813e-17  6.112464e-17       3.991813e-17 -3.991813e-17   2.395088e-16  7.983626e-17         -7.983626e-17     5.987720e-17     2.494883e-17 -1.995907e-16                      0.000000 -7.983626e-17
std      1.002821  1.002821e+00  1.002821e+00       1.002821e+00  1.002821e+00   1.002821e+00  1.002821e+00          1.002821e+00     1.002821e+00     1.002821e+00  1.002821e+00                      1.002821  1.002821e+00
min     -2.434235 -1.432983e+00 -3.679162e+00      -2.671018e+00 -2.088255e+00  -2.107246e+00 -1.695971e+00         -1.868234e+00    -2.069034e+00    -1.634288e+00 -2.094732e+00                     -1.895054 -1.493188e+00
25%     -0.788245 -6.587486e-01 -5.721225e-01      -6.891372e-01 -8.244151e-01  -8.854682e-01 -8.275393e-01         -7.401412e-01    -5.972835e-01    -7.951025e-01 -7.675624e-01                     -0.952248 -7.846378e-01
50%      0.061000 -4.231120e-01 -2.382132e-02       1.518295e-03 -1.222817e-01   9.595986e-02  1.061497e-01         -1.760948e-01    -6.289785e-02    -1.592246e-01  3.312687e-02                      0.237735 -2.337204e-01
75%      0.836129  6.697929e-01  6.981085e-01       6.020883e-01  5.096384e-01   8.089974e-01  8.490851e-01          6.095413e-01     6.291754e-01     4.939560e-01  7.131644e-01                      0.788587  7.582494e-01
max      2.259772  3.109192e+00  3.156325e+00       3.154511e+00  4.371372e+00   2.539515e+00  3.062832e+00          2.402403e+00     3.485073e+00     3.435432e+00  3.301694e+00                      1.960915  2.971473e+00

平均がほぼ0に、標準偏差がほぼ1に正規化されています。

同様に、下の関数も使ってみます。

sklearn.preprocessing.scale

In [33]: from sklearn.preprocessing import scale
    ...: df_sd2 = pd.DataFrame(wine.data, columns=wine.feature_names)
    ...: df_sd2.apply(scale, copy=False)
    ...: df_sd2.describe()
Out[33]: 
          alcohol    malic_acid           ash  alcalinity_of_ash     magnesium  total_phenols  flavanoids  nonflavanoid_phenols  proanthocyanins  color_intensity           hue  od280/od315_of_diluted_wines     proline
count  178.000000  1.780000e+02  1.780000e+02         178.000000  1.780000e+02   1.780000e+02  178.000000            178.000000       178.000000     1.780000e+02  1.780000e+02                    178.000000  178.000000
mean     0.000000  1.197544e-16  3.243348e-17           0.000000 -3.991813e-17  -7.983626e-17    0.000000              0.000000         0.000000     2.494883e-17  3.991813e-17                      0.000000    0.000000
std      1.002821  1.002821e+00  1.002821e+00           1.002821  1.002821e+00   1.002821e+00    1.002821              1.002821         1.002821     1.002821e+00  1.002821e+00                      1.002821    1.002821
min     -2.434235 -1.432983e+00 -3.679162e+00          -2.671018 -2.088255e+00  -2.107246e+00   -1.695971             -1.868234        -2.069034    -1.634288e+00 -2.094732e+00                     -1.895054   -1.493188
25%     -0.788245 -6.587486e-01 -5.721225e-01          -0.689137 -8.244151e-01  -8.854682e-01   -0.827539             -0.740141        -0.597284    -7.951025e-01 -7.675624e-01                     -0.952248   -0.784638
50%      0.061000 -4.231120e-01 -2.382132e-02           0.001518 -1.222817e-01   9.595986e-02    0.106150             -0.176095        -0.062898    -1.592246e-01  3.312687e-02                      0.237735   -0.233720
75%      0.836129  6.697929e-01  6.981085e-01           0.602088  5.096384e-01   8.089974e-01    0.849085              0.609541         0.629175     4.939560e-01  7.131644e-01                      0.788587    0.758249
max      2.259772  3.109192e+00  3.156325e+00           3.154511  4.371372e+00   2.539515e+00    3.062832              2.402403         3.485073     3.435432e+00  3.301694e+00                      1.960915    2.971473

上とほぼ同じように標準化されました。

StandaerScaler を用いて標準化したデータを使い、学習、予測を行います。

データの分割、学習を行います。

In [35]: X_train, X_test, y_train, y_test = train_test_split(df_sd1[wine.feature_names],
    ...:                                                     wine.target,
    ...:                                                     test_size=0.3,
    ...:                                                     stratify=wine.target,
    ...:                                                     random_state=0)

In [36]: knn_std = KNeighborsClassifier()
    ...: knn_std.fit(X_train, y_train)
Out[36]: 
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=5, p=2,
                     weights='uniform')

モデルの評価を行います。

In [46]: knn_std.score(X_test, y_test)
Out[46]: 0.9629629629629629

In [47]: predicted_std = knn_std.predict(X_test)
    ...: from sklearn.metrics import confusion_matrix
    ...: confusion_matrix_std = pd.DataFrame(confusion_matrix(y_test, predicted_std),
    ...:                                 columns=wine.target_names,
    ...:                                 index=wine.target_names)
    ...: confusion_matrix_std
Out[47]: 
         class_0  class_1  class_2
class_0       18        0        0
class_1        1       19        1
class_2        0        0       15

今回の場合は、データの標準化を行うことで、正解率が 0.722から0.962へと劇的に改善しました。