【python】カーネル主成分分析を試してみる
カーネル主成分分析(Kernel PCA)はカーネル法と主成分分析を組み合わせて用い、データを非線形次元圧縮する方法です(こんな説明で良いのか・・・)。
カーネル法のことは勉強中・・・というか正直勉強しようとしてもよくわからないで跳ね返されるのをこれまで4回くらい繰り返してきたのですが、とりあえず使ってみました。
試してみた
非線形データが手元にあると良いのですが、あいにくありません。輪っか状のデータなどを生成してやってみるのは簡単にできますが、面白くなさそうです。だいたいsklearnの公式サンプルにすらあります。
Kernel PCA — scikit-learn 0.21.2 documentation
そこで、分類問題での適用を考えます。これならいつものようにPCA+CLFとKPCA+CLFで比較するだけなので、簡単そうです。更に、カーネルのgammaはグリッドサーチして最適値を探すだけ・・・。
ただし、irisやdigitsで散々色々試してみましたが、ぶっちゃけ普通にやるとなかなかPCAを上回る性能が得られませんでした。最終的に、「digitsを3次元に次元削減し、LDAで分類する」という問題でどうにかそれなりに性能が上回ることがわかりましたが、実用的な意味はあまりありません。
たぶん、sklearnのtoy datasetは低次元で線形分離できるタチの良いデータばっかりなのだと思います。それはそれで良いことですが、ちょっとタチの悪いデータも混ぜておいてもらえると嬉しいところです(かといって20newsgroupsのBoWだとタチが悪すぎるし・・・2000データ400次元くらいのちょうど良いデータはどこかにないものか)。
コードを以下に示します。
# coding: UTF-8 import numpy as np from sklearn.datasets import load_digits from sklearn.decomposition import PCA, KernelPCA as KPCA from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV, StratifiedKFold as SKF from sklearn.model_selection import cross_val_score def main(): dataset = load_digits() print(dataset.data.shape) pca = PCA(n_components=3) kpca = KPCA(kernel="rbf", n_components=3) lda = LDA() pl_pca = Pipeline([("pca", pca), ("lda", lda)]) pl_kpca = Pipeline([("kpca", kpca), ("lda", lda)]) parameters = {"kpca__gamma" : np.arange(0.00001, 0.003, 0.0001)} clf = GridSearchCV(pl_kpca, parameters, verbose=0, n_jobs=-1) print(cross_val_score(pl_pca, dataset.data, dataset.target, cv=SKF(shuffle=True, random_state=0), scoring="f1_macro").mean()) print(cross_val_score(clf, dataset.data, dataset.target, cv=SKF(shuffle=True, random_state=0), scoring="f1_macro").mean()) if __name__ == "__main__": main()
PCAでは0.68らい、KPCAでは0.71くらいのF1値が得られました。
だから? って言われると、返す言葉は思いつきませんが・・・。
まとめ
やってみた記事ですが、何かの参考になればと思います。意外と上手く使うのは難しいと感じました。というか分類の次元削減としてはたぶんそんなに適当ではないです。
どんな問題に応用されてるんだろうか。やっぱり可視化?
追記
文字列の編集距離の可視化に使ってみました。
文字列カーネルというのもあるらしいのですが、sklearnで対応していないし、未確認。編集距離を使う分には無難に使えます。