「Pythonで簡単に実装するマルチクラスSVM」

「Pythonで簡単に実装するマルチクラスSVMの美容とファッション」

SVMについての無料の詳しい概要付き

この記事では、サポートベクターマシン(SVM)の一般的なソフトマージンとカーネル化された形式での学習アルゴリズムを実装します。まず、SVMとその学習および推論方程式の簡単な概要を提供し、その後、これらをコードに変換してSVMモデルを開発します。その後、マルチクラスのシナリオに対応するために実装を拡張し、Sci-kit Learnを使用してモデルをテストします。

この記事の最後までには、以下が得られます:

  • 様々な重要なSVMの概念を明確に理解することができます。
  • バイナリおよびマルチクラスの場合に、本物の理解を持ってSVMモデルをゼロから実装することができます。
Van Gogh Starry Night with a Line of Symmetry and Stars— Generated by author using DALLE 2

目次

· 簡単な概要ハードマージンSVMソフトマージンSVMカーネルソフトマージンSVM· 実装基本的なインポートカーネルとSVMハイパーパラメーターの定義予測メソッドの定義予測メソッドの定義実装のテストマルチクラスへの一般化マルチクラスへの予測の一般化実装のテスト

簡単な概要

ハードマージンSVM

SVMの目標は、最大のマージン(2つのクラスの最も近い点からの距離)を達成するハイパープレーンをフィットさせることです。そのようなハイパープレーン(A)は、マージンを最大化しないハイパープレーン(B)よりも一般化の性質が良く、ノイズに対してもより堅牢です。

Figure by Ennepetaler86 on Wikimedia. CC BY-SA 3.0 Unported.

これを達成するために、SVMは以下の最適化問題を解くことで、ハイパープレーンのWとbを見つけます:

それは、最も近い点までの距離を最大化し、すべての点を正しく分類するW、bを見つけようとします(yが±1を取る制約として)。これは次の最適化問題と等価であることが示されています:

これは、以下のようなデュアル最適化問題と等価であることが分かります。

これの解は、データセット内の各点に対してラグランジュ乗数を生成します。データセットのサイズをmとします:(α, α₂, …, α_N)。目的関数は明らかにαに対して二次であり、制約は線形ですので、これは容易に二次計画法で解くことができます。解が見つかると、デュアルの導出から以下が成り立つことが分かります:

(xₛ、yₛ)はα>0の任意の点

α>0を持つ点のみがハイパープレーンを定義し(合計に寄与する)、これらはサポートベクターと呼ばれます。

したがって、新しい例xが与えられた場合に、予測値y=±1を返す予測方程式は次のようになります:

差し込んでいくことといくつかの代数的な簡略化を行う必要がある

この基本形式のSVMは、最適化問題がすべての訓練ポイントを正しく分類することを強制するため、ハードマージンSVMと呼ばれます。実際のシナリオでは、データを完全に分離するハイパープレーンの存在を妨げるノイズがあるか、制限される場合があります。その場合、最適化問題は解なしまたは劣った解を返すでしょう。

ソフトマージンSVM

Research GateのMangat et alによるSoft Margin SVMの適合。CC BY-SA 4.0 International

ハードマージンSVMを一般化するために、ソフトマージンSVMはC定数(ユーザが指定するハイパーパラメータ)を導入して、どれだけ「ハード」であるかを制御します。具体的には、以下のように、プライマル最適化問題を変更します:

青の変更

各点に一部の違反(例えば、ハイパープレーンの間違った側にある)を許可しつつも、それらの合計を目的関数内のCによって重み付けして全体的に削減しようとすることができる。Cが無限大に近づくと、ハードマージンと同等になります(一般的にはそれよりも前に)。一方、より小さいCでは、違反をより多く許可します(マージンが広くなる代わりに、より小さいwᵗw)。

非常に驚くべきことに、等価なデュアル問題は、各点のαを≤Cに制約することでのみ変化することが示されています。

違反が許可されているため、サポートベクター(α> 0の点)はもはやすべてマージンの端にありません。違反を犯した任意のサポートベクターはα=Cとなることが示されており、非サポートベクター(α=0)は違反を犯すことはできません。違反を犯した可能性のあるサポートベクター(α=C)を「非マージンサポートベクター」と呼び、その他の純粋なもの(違反を犯していない; 端にあるもの)を「マージンサポートベクター」と呼びます(0<α< C)。

<p推論の式は変わらないことが示されています。

ただし、(xₛ、yₛ)は違反を犯していないサポートベクターでなければならないため、方程式ではマージンの端にあると仮定しています(以前は任意のサポートベクターが使用できました)。

カーネル ソフトマージン SVM

ソフトマージン SVMはハードマージン SVMをノイズ処理のために拡張しますが、データが自然に非線形であるなど、ノイズ以外の要因によりハイパープレーンで分離できない場合があります。このような場合には、ソフトマージン SVM を使用できますが、最適解は実際には許容できるよりもはるかに多くのエラーを許可するハイパープレーンを含む可能性があります。

Figure by Machine Learner on Wikimedia. CC BY-SA 4.0 International

カーネル ソフトマージン SVMは、データが自然に非線形である場合に対処するために、ソフトマージン SVMを一般化します。たとえば、左に示されている例では、ソフトマージン SVMが設定に関係なく、データを適切に分離できる線形ハイパープレーンは存在しません。

ただし、データセットの各点xを、いくつかの変換関数z=Φ(x)を介してより高い次元にマッピングすることで、新しい高次元空間でデータをより線形(または完全に線形)にすることができます。これは、デュアルでxをzで置き換えることで次のようになります。

実際には、特にΦが非常に高次元空間に変換される場合、zの計算には非常に長い時間がかかる場合があります。これは、zᵗzを数学的な関数(カーネル関数と呼ばれる)の等価な計算に置き換えるカーネルトリックによって解決され、はるかに高速です(zᵗzの代数的な簡略化など)。たとえば、次にいくつかの人気のあるカーネル関数があります(それぞれがいくつかの変換Φに対応します)。

Degree of polynomial (Q) and RBF γ are hyperparameters (decided by the user)

これにより、二重最適化問題は次のようになります:

そして直感的には、推論方程式は次のようになります(代数的な操作の後):

上記のすべての方程式の完全な導出は、数学的な背景を持っていることを前提として、ここで見つけることができます。

Scott Graham氏による写真(Unsplash)

実装

実装には次のものを使用します。

基本的なインポート

最初にいくつかの基本的なライブラリをインポートします:

import numpy as np                  # 配列操作のための基本的な操作に使用
from scipy.spatial import distance  # ガウスカーネルの計算に使用
import cvxopt                       # 二重最適化問題の解決に使用
import copy                         # numpy配列のコピーに使用

カーネルとSVMのハイパーパラメータの定義

それぞれの関数を使用して、3つのカーネルを定義します

多項式の次数(Q)とRBF γはユーザーが決定するハイパーパラメータ
class SVM:    linear = lambda x, xࠤ , c=0: x @ xࠤ.T    polynomial = lambda x, xࠤ , Q=5: (1 + x @ xࠤ.T)**Q    rbf = lambda x, xࠤ, γ=10: np.exp(-γ*distance.cdist(x, xࠤ,'sqeuclidean'))    kernel_funs = {'linear': linear, 'polynomial': polynomial, 'rbf': rbf}

一貫性のために、線形カーネルは余分な無意味なハイパーパラメータを使用します。明らかなように、kernel_funsはカーネルを文字列で受け取り、対応するカーネル関数を返します。

さて、次にコンストラクタを定義します:

class SVM:    linear = lambda x, xࠤ , c=0: x @ xࠤ.T    polynomial = lambda x, xࠤ , Q=5: (1 + x @ xࠤ.T)**Q    rbf = lambda x, xࠤ, γ=10: np.exp(-γ*distance.cdist(x, xࠤ,'sqeuclidean'))    kernel_funs = {'linear': linear, 'polynomial': polynomial, 'rbf': rbf}        def __init__(self, kernel='rbf', C=1, k=2):        # ハイパーパラメータを設定します
        self.kernel_str = kernel        # カーネルを保存します
        self.kernel = SVM.kernel_funs[kernel]        # カーネル関数を設定します
        self.C = C                  # 正則化パラメータ
        self.k = k                  # カーネルパラメータ        # トレーニングデータとサポートベクトル(後で設定されます)
        self.X, y = None, None        # サポートベクトル係数(後で設定されます)
        self.αs = None        # 多クラス分類用(後で設定されます)
        self.multiclass = False        self.clfs = []                          

SVMには3つの主要なハイパーパラメータがあります。カーネル(与えられた文字列と対応するカーネル関数を保存します)、正則化パラメータC、カーネルのハイパーパラメータ(カーネル関数に渡される値)です。多項式カーネルの場合はQを表し、RBFカーネルの場合はγです。

フィットメソッドを定義する

このクラスをfitpredictの関数として別々のセルで拡張するために、次の関数を定義し、後でデコレータとして使用します:

SVMClass = lambda func: setattr(SVM, func.__name__, func) or func

SVMのフィッティングは、デュアル最適化問題を解くことによって、各点のサポートベクトルαを見つけることに対応します:

ここで、αは変数の列ベクトル(α α₂ … α_N)ᵗであり、yは定数のラベルの列ベクトル(y y₂ … y_N)ᵗであり、Kは定数の行列であり、K[n,m]は(xₙ, xₘ)におけるカーネルを計算します。ドット積、外積、二次形式のための次のインデックスによる等価性を思い出してください:

この情報を利用して、次のように行列形式でデュアル最適化問題を書くことができます:

この問題が既に前に示したように二次計画問題であることがわかるので、CVXOPTのドキュメントで二次計画法に関して読みます:

CVXOPTドキュメントから引用。GNU General Public License

角括弧があることから、これを(P,q)のみ、または(P,q,G,h)または(P,q,G,h,A,b)などで呼び出せることがわかります(与えられないものは、デフォルト値である1に設定されます)。

我々の場合、(P,q,G,h,A,b)の値を知るため、次の比較を行います:

比較を簡単にするために、最初のものを次のように書き直します:

関数を-1で乗算することにより、maxをminに変更したことに注意してください

明らかになったのは、(注:0≤αは-α≤0に等しいことに注意してください):

これにより、次のようなフィット関数を書くことができます:

@SVMClassdef fit(self, X, y, eval_train=False):    # もしユニークなラベルが2つ以上の場合は、多クラスバージョンを呼び出す    if len(np.unique(y)) > 2:        self.multiclass = True        return self.multi_fit(X, y, eval_train)        # ラベルが{0,1}で指定された場合は{-1, 1}に変更します    if set(np.unique(y)) == {0, 1}: y[y == 0] = -1    # yをNx1の列ベクトルに変換しておく(CVXOPTで必要)    self.y = y.reshape(-1, 1).astype(np.double) # 列ベクトルである必要がある    self.X = X    N = X.shape[0]  # 点の数        # データ内のすべての可能な(x, x')のペアに対してカーネルを計算し、    # Numpyのベクトル化により行列Kが得られます    self.K = self.kernel(X, X, self.k)        ### 最適化パラメータの設定    # 1/2 x^T P x + q^T x のため    P = cvxopt.matrix(self.y @ self.y.T * self.K)    q = cvxopt.matrix(-np.ones((N, 1)))        # Ax = b のため    A = cvxopt.matrix(self.y.T)    b = cvxopt.matrix(np.zeros(1))    # Gx <= h のため    G = cvxopt.matrix(np.vstack((-np.identity(N),                                 np.identity(N))))    h = cvxopt.matrix(np.vstack((np.zeros((N,1)),                                 np.ones((N,1)) * self.C)))    # 解を求める        cvxopt.solvers.options['show_progress'] = False    sol = cvxopt.solvers.qp(P, q, G, h, A, b)    self.αs = np.array(sol["x"])            # 解    # サポートベクタであることを示すブール配列    self.is_sv = ((self.αs-1e-3 > 0)&(self.αs <= self.C)).squeeze()    # いくつかのマージンサポートベクタのインデックス    self.margin_sv = np.argmax((0 < self.αs-1e-3)&(self.αs < self.C-1e-3))        if eval_train:        print(f"Finished training with accuracy{self.evaluate(X, y)}")

この問題は2進の問題であり、バイナリラベルはSVMによって(±1)として設定され、yは(N,1)の列ベクトルです。そして最適化問題を解いて(α α₂ … α_N)ᵗを求めます。

α₂ … α_N)ᵗを使用して、サポートベクトルに対応するインデックスに対して1のフラグ配列を取得し、後で予測方程式を適用するためにサポートベクトルだけを合計し、(xₛ,yₛ)のマージンサポートベクトルのインデックスも考慮します。サポートベクトル以外はαが厳密に0ではないかもしれないことに注意してください。α≤1e-3の場合、これはほぼゼロとみなされます(CVXOPTの結果は完全に正確でない可能性があることを知っています)。同様に、マージンサポートベクトル以外はα=Cとは限らないことを仮定しています。

予測メソッドの定義

予測方程式は以下の通りです:

@SVMClassdef predict(self, X_t):    if self.multiclass: return self.multi_predict(X_t)    # compute (xₛ, yₛ)    xₛ, yₛ = self.X[self.margin_sv, np.newaxis], self.y[self.margin_sv]    # find support vectors    αs, y, X= self.αs[self.is_sv], self.y[self.is_sv], self.X[self.is_sv]    # compute the second term    b = yₛ - np.sum(αs * y * self.kernel(X, xₛ, self.k), axis=0)    # compute the score    score = np.sum(αs * y * self.kernel(X, X_t, self.k), axis=0) + b    return np.sign(score).astype(int), score

以上です。また、正確さを評価するためにevaluateメソッドを実装することもできます(fitメソッドで使用)。

@SVMClassdef evaluate(self, X,y):      outputs, _ = self.predict(X)    accuracy = np.sum(outputs == y) / len(y)    return round(accuracy, 2)

実装のテスト

from sklearn.datasets import make_classificationimport numpy as np# Load the datasetnp.random.seed(1)X, y = make_classification(n_samples=2500, n_features=5,                            n_redundant=0, n_informative=5,                            n_classes=2,  class_sep=0.3)# Test Implemented SVMsvm = SVM(kernel='rbf', k=1)svm.fit(X, y, eval_train=True)y_pred, _ = svm.predict(X)print(f"Accuracy: {np.sum(y==y_pred)/y.shape[0]}")  #0.9108# Test with Scikitfrom sklearn.svm import SVCclf = SVC(kernel='rbf', C=1, gamma=1)clf.fit(X, y)y_pred = clf.predict(X)print(f"Accuracy: {sum(y==y_pred)/y.shape[0]}")    #0.9108

データセットやハイパーパラメータを変更して、それらが同じであることをさらに確認できます。理想的には、正確さではなく決定関数を比較することによって行ってください。

マルチクラスに適用する一般的なフィット

@SVMClassdef multi_fit(self, X, y, eval_train=False):    self.k = len(np.unique(y))      # number of classes    # for each pair of classes    for i in range(self.k):        # get the data for the pair        Xs, Ys = X, copy.copy(y)        # change the labels to -1 and 1        Ys[Ys!=i], Ys[Ys==i] = -1, +1        # fit the classifier        clf = SVM(kernel=self.kernel_str, C=self.C, k=self.k)        clf.fit(Xs, Ys)        # save the classifier        self.clfs.append(clf)    if eval_train:          print(f"Finished training with accuracy {self.evaluate(X, y)}")

kクラスごとにバイナリSVM分類器をトレーニングし、それぞれのクラスに対してデータを取得し、そのクラスに属するポイントを+1に、他のすべてのクラスに属するポイントを-1にラベル付けすることで、モデルをマルチクラスに汎化することができます。

トレーニングの結果は、kクラスが与えられた場合にk個の分類器があり、i番目の分類器はi番目のクラスが+1で他のすべてのクラスが-1でラベル付けされたデータで訓練されました。

多クラスへの予測の一般化

新しい例に対して予測を行うには、対応する分類器が最も自信を持っているクラス(最も高いスコアを持つ)を選択します。

@SVMClassdef multi_predict(self, X):    # すべての分類器から予測を取得する    N = X.shape[0]    preds = np.zeros((N, self.k))    for i, clf in enumerate(self.clfs):        _, preds[:, i] = clf.predict(X)        # argmaxと対応するスコアを取得します    return np.argmax(preds, axis=1), np.max(preds, axis=1)

実装のテスト

from sklearn.datasets import make_classificationimport numpy as np# データセットのロードnp.random.seed(1)X, y = make_classification(n_samples=500, n_features=2,                            n_redundant=0, n_informative=2,                            n_classes=4, n_clusters_per_class=1,                             class_sep=0.3)# SVMのテストsvm = SVM(kernel='rbf', k=4)svm.fit(X, y, eval_train=True)y_pred = svm.predict(X)print(f"精度: {np.sum(y==y_pred)/y.shape[0]}") # 0.65# Scikitでのテストfrom sklearn.multiclass import OneVsRestClassifierfrom sklearn.svm import SVCclf = OneVsRestClassifier(SVC(kernel='rbf', C=1, gamma=4)).fit(X, y)y_pred = clf.predict(X)print(f"精度: {sum(y==y_pred)/y.shape[0]}")    # 0.65

それぞれの決定領域のプロットを行うと、以下のプロットが得られます:

Figure by the author

注意:Sci-kit LearnのSVMはデフォルトでOVRをサポートしています(上記のような明示的な呼び出しは必要ありませんが)、これにはSVM固有のさらなる最適化も含まれる可能性があります。

完全なコード

import numpy as np                  # 配列の基本的な操作のためにfrom scipy.spatial import distance  # ガウスカーネルの計算のためにimport cvxopt                       # 双対最適化問題の解決のためにimport copy                         # numpy配列のコピーのために class SVM:    linear = lambda x, xࠤ , c=0: x @ xࠤ .T    polynomial = lambda x, xࠤ , Q=5: (1 + x @ 
...
```
Nathan Van EgmondさんによるUnsplashのフォト

要約すると、私たちはサポートベクトルマシン(SVM)学習アルゴリズムを実装し、一般的なソフトマージンとカーネル化の形態について取り上げました。SVMの概要を提供し、コードでモデルを開発し、マルチクラスのシナリオにも拡張し、Sci-kit Learnを使用して実装を検証しました。

このストーリーから学んだことがお仕事に役立つことを願っています。次回まで、さようなら。

リソース:

コードの大部分は、こちらのもの(MITライセンス)を参考にしています:Persson, Aladdin. “SVM from Scratch — Machine Learning Python (Support Vector Machine).” YouTube.

</p推論の式は変わらないことが示されています。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more