表題の通り、k-NN Feature Extraction という特徴量抽出の手法に使う「gokinjo」という Python のライブラリを作った。 今回はライブラリの使い方について紹介してみる。
k-NN Feature Extraction で得られる特徴量は、Otto Group Product Classification Challenge という Kaggle のコンペで優勝者が使ったものの一つ。 手法自体の解説は以下のブログ記事を参照のこと。 なお、ブログ記事の中でも Python のコードを書いてるけど、よりナイーブな実装になっている。
以降、紹介に用いる環境は次の通り。 なお、ライブラリ自体にプラットフォームへの依存はない。 また、Python のバージョンは 3.6 以降をサポートしている。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.2 BuildVersion: 18C54 $ python -V Python 3.7.2
インストール
ライブラリは pip を使ってインストールできる。
$ pip install gokinjo
基本的な使い方
まずは簡単な例を使って使い方を解説する。
データを可視化するために matplotlib をインストールしておく。
$ pip install matplotlib
Python のインタプリタを起動する。
$ python
まずは次のような二次元の特徴変数と二値の目的変数を持ったダミーデータを生成する。
>>> import numpy as np >>> x0 = np.random.rand(500) - 0.5 >>> x1 = np.random.rand(500) - 0.5 >>> X = np.array(list(zip(x0, x1))) >>> y = np.array([1 if i0 * i1 > 0 else 0 for i0, i1 in X])
このデータを可視化すると次のようになる。 データはラベルごとに XOR 的に配置されており、線形分離できないようになっている。
>>> from matplotlib import pyplot as plt >>> plt.scatter(X[:, 0], X[:, 1], c=y) <matplotlib.collections.PathCollection object at 0x11d47d048> >>> plt.show()
上記のデータから、今回作ったライブラリを使って特徴量を抽出する。 抽出した特徴量は、近傍数 k とラベルの種類 C をかけた次元数を持っている。 今回であればデフォルトの近傍数 k=1 とラベル数 C=2 から k * C = 2 となって二次元のデータになる。
>>> from gokinjo import knn_kfold_extract >>> X_knn = knn_kfold_extract(X, y)
抽出した上記のデータを可視化してみる。
plt.scatter(X_knn[:, 0], X_knn[:, 1], c=y) plt.show()
元々のデータに比べると、もうちょっとで線形分離できそうなくらいの特徴量になっていることが分かる。
Iris データセットを使った例
先ほどと同様に Iris データセットでも特徴量を抽出して可視化してみることにしよう。 サンプルコードは次の通り。
#!/usr/bin/env python # -*- coding: utf-8 -*- from sklearn import datasets from matplotlib import pyplot as plt from mpl_toolkits.mplot3d import Axes3D # noqa from gokinjo import knn_kfold_extract def main(): # データセットを読み込む dataset = datasets.load_iris() X = dataset.data y = dataset.target # k-NN 特徴量を取り出す extracted_features = knn_kfold_extract(X, y) # 取り出した特徴量を可視化する fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(extracted_features[:, 0], extracted_features[:, 1], extracted_features[:, 2], c=y) ax.set_title('extracted features (3D)') axes = plt.gcf().get_axes() for axe in axes: axe.grid() plt.show() if __name__ == '__main__': main()
上記に適当な名前をつけて実行する。
$ python knn_iris.py
抽出した特徴量を可視化したグラフは次の通り。 Iris データセットは多値分類問題なので k=1, C=3 より 3 次元のデータになっている。
こちらも良い感じに分離できそうな特徴量が抽出できている。
Digits データセットを使った例
可視化の次は実際に抽出した特徴量を分類問題に適用してみる。 以下のサンプルコードでは Digits データセットを使って特徴量をランダムフォレストで分類している。 Digits データセットというのは 0 ~ 9 までの手書き数字の画像が含まれたもので MNIST のミニチュアみたいな感じ。 パターンとしては「生データそのまま」「k-NN 特徴量」「t-SNE 特徴量」「t-SNE -> k-NN 特徴量」で試している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from sklearn import datasets from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import cross_validate from sklearn.manifold import TSNE from gokinjo import knn_kfold_extract def main(): # データセットを読み込む dataset = datasets.load_digits() X = dataset.data y = dataset.target # 下準備 clf = RandomForestClassifier(n_estimators=100, random_state=42) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 生データをそのまま使う場合 score = cross_validate(clf, X, y, cv=skf) print('mean accuracy (raw):', score['test_score'].mean()) # k-NN 特徴量に変換した場合 X_knn_raw = knn_kfold_extract(X, y, random_state=42) score = cross_validate(clf, X_knn_raw, y, cv=skf) print('mean accuracy (raw -> k-NN):', score['test_score'].mean()) # t-SNE に変換した場合 tsne = TSNE(random_state=42) X_tsne = tsne.fit_transform(X) score = cross_validate(clf, X_tsne, y, cv=skf) print('mean accuracy (raw -> t-SNE):', score['test_score'].mean()) # t-SNE をさらに k-NN 特徴量に変換した場合 X_knn_tsne = knn_kfold_extract(X_tsne, y, random_state=42) score = cross_validate(clf, X_knn_tsne, y, cv=skf) print('mean accuracy (raw -> t-SNE -> k-NN):', score['test_score'].mean()) if __name__ == '__main__': main()
上記を実行した結果が以下の通り。 わずかな改善ではあるものの k-NN 特徴量が効いていることが分かる。
$ python knn_digits.py mean accuracy (raw): 0.975493504158074 mean accuracy (raw -> k-NN): 0.9777156040302326 mean accuracy (raw -> t-SNE): 0.9888661465635314 mean accuracy (raw -> t-SNE -> k-NN): 0.9894313077402828
バックエンドの切り替え
ライブラリの特徴として、k-NN アルゴリズムの実装が切り替えられる。 デフォルトではバックエンドが scikit-learn になっているけど、オプションとして Annoy にもできる。
Annoy をバックエンドとして使うときはインストールするときに環境として [annoy]
を指定する。
$ pip install "gokinjo[annoy]"
そして、knn_kfold_extract()
関数の backend
オプションに annoy
を指定すれば良い。
knn_kfold_extract(..., backend='annoy')
バックエンドを切り替えることで、使うデータや環境によっては高速化につながるかもしれない。
分割しながらの生成が不要な場合
ちなみに k-NN Feature Extraction は目的変数にもとづいた特徴量抽出なので、生成するときに過学習を防ぐ工夫が必要になる。
具体的には、特徴量を生成する対象のデータを学習の対象に含めてはいけないという点。
ようするに Target Encoding するときと同じように k-Fold で分割しながら特徴量を生成する必要がある。
そのため、ここまでのサンプルコードでは、データを内部的に自動で分割しながら生成してくれる knn_kfold_extract()
という関数を使ってきた。
もしデータ分割がいらない、もしくは自分でやるという場合には knn_extract()
という関数が使える。
典型的なユースケースは Kaggle の test データに対して特徴量を生成する場合。
このときは train データを全て使って test データの特徴量を生成すれば良いため。
以下は knn_extract()
を使って自前で分割しながら特徴量を生成する場合のサンプルコード。
生成した特徴量のソートとかでコードが分かりにくくなるので、ここでは Stratified でない k-Fold をシャッフルせずに使っている。
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np from sklearn import datasets from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import KFold from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import cross_validate from gokinjo import knn_extract def main(): # データセットを読み込む dataset = datasets.load_digits() X, y = dataset.data, dataset.target # 生成した特徴量を保存する場所 number_of_classes = np.unique(y) k = 1 X_knn = np.empty([0, len(number_of_classes) * k]) # Leakage を防ぐために k-Fold で分割しながら生成する kf = KFold(n_splits=20, shuffle=False) # 処理の単純化のためにシャッフルなし for train_index, test_index in kf.split(X, y): X_train, X_test = X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index] # noqa # 学習用データで学習して、テストデータに対して特徴量を生成する X_test_knn = knn_extract(X_train, y_train, X_test, k=k) # 生成した特徴量を保存する X_knn = np.append(X_knn, X_test_knn, axis=0) # 生成した特徴量をランダムフォレストで学習してみる clf = RandomForestClassifier(n_estimators=100, random_state=42) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) score = cross_validate(clf, X, y, cv=skf) print('mean accuracy (raw data)', score['test_score'].mean()) score = cross_validate(clf, X_knn, y, cv=skf) print('mean accuracy (k-NN feature)', score['test_score'].mean()) if __name__ == '__main__': main()
上記の実行結果は次の通り。
$ python knn_nokfold.py mean accuracy (raw data) 0.975493504158074 mean accuracy (k-NN feature) 0.9782572815114076
scikit-learn インターフェース
ちなみに scikit-learn の Transformer を実装したインターフェースも用意してある。 こちらも、使うときは特徴量を生成するときに自前で k-Fold する必要がある点に注意が必要。
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np from sklearn import datasets from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import KFold from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import cross_validate from gokinjo.backend_sklearn import ScikitTransformer # from gokinjo.backend_annoy import AnnoyTransformer # Annoy 使うなら def main(): # データセットを読み込む dataset = datasets.load_digits() X, y = dataset.data, dataset.target # k-NN 特徴量を抽出する scikit-learn の Transformer として実装したやつ k = 1 transformer = ScikitTransformer(n_neighbors=k) # 生成した特徴量を保存する場所 number_of_classes = np.unique(y) X_knn = np.empty([0, len(number_of_classes) * k]) # Leakage を防ぐために k-Fold で分割しながら生成する kf = KFold(n_splits=20, shuffle=False) # 処理の単純化のためにシャッフルなし for train_index, test_index in kf.split(X, y): X_train, X_test = X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index] # noqa # 学習用データで学習する transformer.fit(X_train, y_train) # テストデータに対して特徴量を生成する X_test_knn = transformer.transform(X_test) # 生成した特徴量を保存する X_knn = np.append(X_knn, X_test_knn, axis=0) # 生成した特徴量をランダムフォレストで学習してみる clf = RandomForestClassifier(n_estimators=100, random_state=42) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) score = cross_validate(clf, X, y, cv=skf) print('mean accuracy (raw data)', score['test_score'].mean()) score = cross_validate(clf, X_knn, y, cv=skf) print('mean accuracy (k-NN feature)', score['test_score'].mean()) if __name__ == '__main__': main()
実行結果は次の通り。
$ python knn_sklearn.py mean accuracy (raw data) 0.975493504158074 mean accuracy (k-NN feature) 0.9782572815114076
いじょう。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る