楽しみと利益のためのトリック


これはかなり小さなポストですが、面白いものになります.

Originally published at https://alexandruburlacu.github.io/posts/2021-06-18-kmeans-trick


K - meansはエレガントなアルゴリズムです.それを理解するのは簡単です(ランダムなポイントを作る、いくつかの既存のクラスタの中心になるために反復的に移動し、実際に動作します).私が最初にそれについて知ったとき、私は魅力的であることを思い出します.優雅.しかし、時間内に、興味が消えて、私は多くの制限に気づいていました、その中で、それは以前の球形のクラスタである、その線形性、そして、私が特にEDAシナリオで特に悩んでいるのを見つけたので、それはそれ自体で最適な数を見つけないという事実です.そして、数年前、私はK -手段を使用する方法に関するいくつかのきちんとしたトリックについて知りました.だからここに行く.

最初のトリック


まず、ベースラインを確立する必要があります.大部分は乳がんデータセットを使います、しかし、あなたは他のどんなデータセットででも遊ぶことができます.
from sklearn.cluster import KMeans
from sklearn.svm import LinearSVC
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

import numpy as np

X, y = load_breast_cancer(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=17)

svm = LinearSVC(random_state=17)
svm.fit(X_train, y_train)
svm.score(X_test, y_test) # should be ~0.93
それで、K -手段のために私の関心を再燃させたこのきちんとしたトリックは何ですか?

K-Means can be used as a source of new features.


どのように、あなたは尋ねるかもしれませんか?さて、K - meansはクラスタリングアルゴリズムですね.あなたは、新しい分類機能として推論クラスタを追加することができます.
さて、これを試してみましょう.
# imports from the example above

svm = LinearSVC(random_state=17)
kmeans = KMeans(n_clusters=3, random_state=17)
X_clusters = kmeans.fit_predict(X_train).reshape(-1, 1)

svm.fit(np.hstack([X_train, X_clusters]), y_train)
svm.score(np.hstack([X_test, kmeans.predict(X_test).reshape(-1, 1)]), y_test) # should be ~0.937

ソース:knowyourmeme.コム
これらの機能は分類的ですが、我々はすべての重心への距離を出力するモデルを求めることができます.
# imports from the example above

svm = LinearSVC(random_state=17)
kmeans = KMeans(n_clusters=3, random_state=17)
X_clusters = kmeans.fit_transform(X_train)
#                       ^^^^^^^^^
#                       Notice the `transform` instead of `predict`
# Scikit-learn supports this method as early as version 0.15

svm.fit(np.hstack([X_train, X_clusters]), y_train)
svm.score(np.hstack([X_test, kmeans.transform(X_test)]), y_test) # should be ~0.727
待って、何が悪いの?既存の特徴と重心への距離の間に相関があるのだろうか?
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

columns = ['mean radius', 'mean texture', 'mean perimeter', 'mean area',
       'mean smoothness', 'mean compactness', 'mean concavity',
       'mean concave points', 'mean symmetry', 'mean fractal dimension',
       'radius error', 'texture error', 'perimeter error', 'area error',
       'smoothness error', 'compactness error', 'concavity error',
       'concave points error', 'symmetry error',
       'fractal dimension error', 'worst radius', 'worst texture',
       'worst perimeter', 'worst area', 'worst smoothness',
       'worst compactness', 'worst concavity', 'worst concave points',
       'worst symmetry', 'worst fractal dimension',
       'distance to cluster 1', 'distance to cluster 2', 'distance to cluster 3']
data = pd.DataFrame.from_records(np.hstack([X_train, X_clusters]), columns=columns)
sns.heatmap(data.corr())
plt.xticks(rotation=-45)
plt.show()

最後の3つの列、特に最後の1つ、すべての行に色を通知します.
DataSetの機能をできるだけ独立して欲しいということを聞いたことがあります.理由は、多くの機械学習モデルがこの独立性をより単純なアルゴリズムを持っていると仮定することです.このトピックに関する詳しい情報はhere and here , しかし、それのGISTは線形モデルの冗長な情報を持つことがモデルを不安定にするということです、そして、順番に、それは混乱しそうです.多くの場合、私は非線形モデルでさえ、この問題に気がつきました、そして、相関した特徴からデータセットをパージすることは通常モデルのパフォーマンス特性のわずかな増加を与えます.
メイントピックに戻る.我々の新機能が実際に既存のもののいくつかと相関しているなら、クラスタへの距離だけが機能として意味するなら、それはそれから働きますか?
# imports from the example above

svm = LinearSVC(random_state=17)
kmeans = KMeans(n_clusters=3, random_state=17)
X_clusters = kmeans.fit_transform(X_train)

svm.fit(X_clusters, y_train)
svm.score(kmeans.transform(X_test), y_test) # should be ~0.951
より良い.この例では、kmeansを次元縮小の方法として使用できることがわかります.きちんとした.
これまでのところ良い.しかし、部分的な抵抗はまだ示されていません.

2番目のトリック


K-Means can be used as a substitute for the kernel trick


あなたは私の言うことを聞いた.例えば、K - meansアルゴリズムのためのより多くの重心を定義することができます.
# imports from the example above

svm = LinearSVC(random_state=17)
kmeans = KMeans(n_clusters=250, random_state=17)
X_clusters = kmeans.fit_transform(X_train)

svm.fit(X_clusters, y_train)
svm.score(kmeans.transform(X_test), y_test) # should be ~0.944
まあ、良いとしてではなく、かなりまともな.実際には、このアプローチの最大の利点は、多くのデータがあるときです.また、あなたのマイル数が変化する可能性があります予測性能は、私は、1つは、このメソッドを実行していたn_clusters=1000 そして、それはいくつかのクラスタだけでよりよく働きました.
SVMSは大きなデータセットを訓練するのが遅いことが知られている.とても遅い.そこに行った.そのため、例えば、カーネルトリックをより少ない計算量で近似する技術が数多くあります.
ところで、このK - meansトリックが古典的なSVMといくつかの代わりのカーネル近似法に対してどうするかを比較しましょう.
下のコードはthese two SciKitは、例を学びます.
import matplotlib.pyplot as plt
import numpy as np
from time import time

from sklearn.datasets import load_breast_cancer
from sklearn.svm import LinearSVC, SVC
from sklearn import pipeline
from sklearn.kernel_approximation import RBFSampler, Nystroem, PolynomialCountSketch
from sklearn.preprocessing import MinMaxScaler, Normalizer
from sklearn.model_selection import train_test_split
from sklearn.cluster import MiniBatchKMeans


mm = pipeline.make_pipeline(MinMaxScaler(), Normalizer())

X, y = load_breast_cancer(return_X_y=True)
X = mm.fit_transform(X)

data_train, data_test, targets_train, targets_test = train_test_split(X, y, random_state=17)
私たちは、k - meansトリックに対して、Scikit Learningパッケージで利用できるカーネル近似のための3つのメソッドをテストします、そして、ベースラインとして、私たちは、カーネル・トリックを使用する線形SVMとSVMを持ちます.
# Create a classifier: a support vector classifier
kernel_svm = SVC(gamma=.2, random_state=17)
linear_svm = LinearSVC(random_state=17)

# create pipeline from kernel approximation and linear svm
feature_map_fourier = RBFSampler(gamma=.2, random_state=17)
feature_map_nystroem = Nystroem(gamma=.2, random_state=17)
feature_map_poly_cm = PolynomialCountSketch(degree=4, random_state=17)
feature_map_kmeans = MiniBatchKMeans(random_state=17)
fourier_approx_svm = pipeline.Pipeline([("feature_map", feature_map_fourier),
                                        ("svm", LinearSVC(random_state=17))])

nystroem_approx_svm = pipeline.Pipeline([("feature_map", feature_map_nystroem),
                                        ("svm", LinearSVC(random_state=17))])

poly_cm_approx_svm = pipeline.Pipeline([("feature_map", feature_map_poly_cm),
                                        ("svm", LinearSVC(random_state=17))])

kmeans_approx_svm = pipeline.Pipeline([("feature_map", feature_map_kmeans),
                                        ("svm", LinearSVC(random_state=17))])

我々の構成の各々のためにタイミングと得点結果を集めましょう.
# fit and predict using linear and kernel svm:
kernel_svm_time = time()
kernel_svm.fit(data_train, targets_train)
kernel_svm_score = kernel_svm.score(data_test, targets_test)
kernel_svm_time = time() - kernel_svm_time

linear_svm_time = time()
linear_svm.fit(data_train, targets_train)
linear_svm_score = linear_svm.score(data_test, targets_test)
linear_svm_time = time() - linear_svm_time

sample_sizes = 30 * np.arange(1, 10)
fourier_scores = []
nystroem_scores = []
poly_cm_scores = []
kmeans_scores = []

fourier_times = []
nystroem_times = []
poly_cm_times = []
kmeans_times = []

for D in sample_sizes:
    fourier_approx_svm.set_params(feature_map__n_components=D)
    nystroem_approx_svm.set_params(feature_map__n_components=D)
    poly_cm_approx_svm.set_params(feature_map__n_components=D)
    kmeans_approx_svm.set_params(feature_map__n_clusters=D)
    start = time()
    nystroem_approx_svm.fit(data_train, targets_train)
    nystroem_times.append(time() - start)

    start = time()
    fourier_approx_svm.fit(data_train, targets_train)
    fourier_times.append(time() - start)

    start = time()
    poly_cm_approx_svm.fit(data_train, targets_train)
    poly_cm_times.append(time() - start)

    start = time()
    kmeans_approx_svm.fit(data_train, targets_train)
    kmeans_times.append(time() - start)

    fourier_score = fourier_approx_svm.score(data_test, targets_test)
    fourier_scores.append(fourier_score)
    nystroem_score = nystroem_approx_svm.score(data_test, targets_test)
    nystroem_scores.append(nystroem_score)
    poly_cm_score = poly_cm_approx_svm.score(data_test, targets_test)
    poly_cm_scores.append(poly_cm_score)
    kmeans_score = kmeans_approx_svm.score(data_test, targets_test)
    kmeans_scores.append(kmeans_score)
では、集められた結果をすべてプロットしましょう.
plt.figure(figsize=(16, 4))
accuracy = plt.subplot(211)
timescale = plt.subplot(212)

accuracy.plot(sample_sizes, nystroem_scores, label="Nystroem approx. kernel")
timescale.plot(sample_sizes, nystroem_times, '--',
               label='Nystroem approx. kernel')

accuracy.plot(sample_sizes, fourier_scores, label="Fourier approx. kernel")
timescale.plot(sample_sizes, fourier_times, '--',
               label='Fourier approx. kernel')

accuracy.plot(sample_sizes, poly_cm_scores, label="Polynomial Count-Min approx. kernel")
timescale.plot(sample_sizes, poly_cm_times, '--',
               label='Polynomial Count-Min approx. kernel')

accuracy.plot(sample_sizes, kmeans_scores, label="K-Means approx. kernel")
timescale.plot(sample_sizes, kmeans_times, '--',
               label='K-Means approx. kernel')

# horizontal lines for exact rbf and linear kernels:
accuracy.plot([sample_sizes[0], sample_sizes[-1]],
              [linear_svm_score, linear_svm_score], label="linear svm")
timescale.plot([sample_sizes[0], sample_sizes[-1]],
               [linear_svm_time, linear_svm_time], '--', label='linear svm')

accuracy.plot([sample_sizes[0], sample_sizes[-1]],
              [kernel_svm_score, kernel_svm_score], label="rbf svm")
timescale.plot([sample_sizes[0], sample_sizes[-1]],
               [kernel_svm_time, kernel_svm_time], '--', label='rbf svm')
といくつかのプロットの調整は、それをきれいにする.
# legends and labels
accuracy.set_title("Classification accuracy")
timescale.set_title("Training times")
accuracy.set_xlim(sample_sizes[0], sample_sizes[-1])
accuracy.set_xticks(())
accuracy.set_ylim(np.min(fourier_scores), 1)
timescale.set_xlabel("Sampling steps = transformed feature dimension")
accuracy.set_ylabel("Classification accuracy")
timescale.set_ylabel("Training time in seconds")
accuracy.legend(loc='best')
timescale.legend(loc='best')
plt.tight_layout()
plt.show()

ええ.それで、それは何のためにでもありませんでした?
何か知ってる?少しも.最も遅いとしても、RBFカーネルの近似としてのK -手段は、まだ良いオプションです.冗談じゃない.この特殊なK - meansを使用することができますMiniBatchKMeans をサポートするいくつかのアルゴリズムの一つです.partial_fit メソッド.これを機械学習モデルと組み合わせること.partial_fit また、aPassiveAggressiveClassifier つはかなり面白い解決策を作成することができます.
その美しさに注意してください.partial_fit 二重です.第1に、それは、コア内のやり方でアルゴリズムを訓練することを可能にします、すなわち、RAMに収まるより多くのデータで、すなわち.第二に、あなたのタイプの問題に応じて、原則(非常に非常に原則)でモデルを切り替える必要がない場合は、それが展開される権利を追加訓練される可能性があります.それはオンライン学習と呼ばれ、それは超面白いです.このようなものはwhat some Chinese companies are doing そして、一般的にAdTech , いつでもあなたの広告の推薦が正しいか間違って秒以内に情報を受け取ることができます.
あなたは何を知っている、ここでコアアウトの学習のためのこのアプローチの例です.
from sklearn.cluster import MiniBatchKMeans
from sklearn.linear_model import PassiveAggressiveClassifier

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer

import numpy as np

def batch(iterable, n=1):
    # source: https://stackoverflow.com/a/8290508/5428334
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

X, y = load_breast_cancer(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=17)

kmeans = MiniBatchKMeans(n_clusters=100, random_state=17) # K-Means has a constraint, n_clusters <= n_samples to fit
pac = PassiveAggressiveClassifier(random_state=17)

for x, y in zip(batch(X_train, n=100), batch(y_train, n=100)):
    kmeans.partial_fit(x, y)       # fit K-Means a bit
    x_dist = kmeans.transform(x)   # obtain distances
    pac.partial_fit(x_dist, y, classes=[0, 1])     # learn a bit the classifier, we need to indicate the classes
    print(pac.score(kmeans.transform(X_test), y_test))

# 0.909 after 100 samples
# 0.951 after 200 samples
# 0.951 after 300 samples
# 0.944 after 400 samples
# 0.902 after 426 samples


# VS
kmeans = MiniBatchKMeans(n_clusters=100, random_state=17)
pac = PassiveAggressiveClassifier(random_state=17)

pac.fit(kmeans.fit_transform(X_train), y_train)
pac.score(kmeans.transform(X_test), y_test)
# should be ~0.951

エピローグ


それで、あなたは終わりまでそれを作りました.今すぐあなたのMLツールセットは豊かです.たぶん、あなたはいわゆる“無料のランチ”の定理について聞いたことがあります基本的に、銀の弾丸はありません.多分、次のプロジェクトのために、このポストで概説される方法は働きません、しかし、それの後に来る1つのために、彼らはそうします.だから、実験、自分自身を参照してください.そして、もしあなたがオンラインの学習アルゴリズム/メソッドを必要とする場合は、より大きなチャンスがあることは、カーネル近似としてのKの意味は、あなたのための適切なツールです.
ところで、MLの上に、もう一つのブログ柱があります.何がより良い、それの多くの他の良いものの中で、それはK -手段を使用するかなり面白い方法について説明します.しかし、今のスポイラー.調子を合わせなさい.
最後に、これを読んでいるなら、ありがとう!いくつかのフィードバックを残したり、質問をしたい場合は、オプションのかなりのメニューを持っている(連絡先のこのページのフッターを参照してください+あなたはDisqusのコメントのセクションを持っている).

いくつかのリンクを面白いかもしれない

  • A stackexchange discussion about using K-Means as a feature engineering tool
  • A more in-depth explanation of K-Means
  • A research paper that uses K-Means for an efficient SVM
  • 謝辞


    スタイルのチェックとコンテンツのレビューのための特別なおかげで、これまで以上に任意のAIよりも徹底的にこの記事をチェックする文法をいただきありがとうございます.あなたはベスト3です
    P . S .私はあなたがこれらすべてに気づいたと信じていますrandom_state コードのs.私がなぜこれらを加えたか疑問に思っているならば、それはコードサンプルを再現可能にすることです.頻繁にチュートリアルでは、これを行うことはありませんので、それが唯一の最良の結果を提示するチェリーピッキングのためのスペースを残して、これらの複製しようとすると、読者のいずれかをすることはできませんまたはそれは多くの時間がかかります.しかし、これを知って、あなたの値を再生することができますrandom_state そして、広範囲に異なる結果を得てください.たとえば、元の機能と距離を3つの重心には、1つの0.727のスコアには、ランダムなシードの41と17の代わりに41を実行すると、0.944の精度スコアを得ることができます.そうですね.random_state または、しかし、ランダムな種が選択のあなたのフレームワークで呼ばれることは、特に研究をするとき、心に留めておく重要な局面です.