SMO mit Python + NumPy implementiert

Ich habe eine SVM geschrieben, die nur mit Python + NumPy ernsthaft nach Geschwindigkeit strebte. Die Algorithmen sind LIVSVM-Dokumentation und verwandte Artikel. /~cjlin/papers/quadworkset.pdf) SMO, das verschiedene Ideen enthält.

Für die Auswahl des Arbeitssatzes (Auswahl von 2 Variablen, die für Teilprobleme in jeder Iteration verwendet werden) haben wir eine etwas alte Methode (die bis zu LIBSVM Version 2.8 verwendete) übernommen, um die einfache Implementierung zu priorisieren, aber sie ist neu. Es gibt keine Garantie, dass er schnell sein wird.

Quellcode

Eigentlich wollte ich es alleine genießen, aber ich beschloss, die Freude mit allen zu teilen, weil die Geschwindigkeit unerwartet hoch war.

Das Schreiben der Erklärung des Algorithmus scheint jedoch sehr lang zu sein, daher habe ich beschlossen, vorerst nur den Code zu kompromittieren und einzufügen. Es gibt viele Funktionen, die noch nicht implementiert wurden, aber im Grunde sollte die Verwendung fast der von scikit-learn entsprechen.

# -*- coding: utf-8 -*-

import numpy as np


def linear_kernel(x1, x2):
    x1 = np.atleast_2d(x1)
    x2 = np.atleast_2d(x2)
    return np.dot(x1, x2.T)


# k(x, y) = exp(- gamma || x1 - x2 ||^2)
def get_rbf_kernel(gamma):
    def rbf_kernel(x1, x2):
        x1 = np.atleast_2d(x1)
        x2 = np.atleast_2d(x2)
        m1, _ = x1.shape
        m2, _ = x2.shape
        norm1 = np.dot(np.ones([m2, 1]), np.atleast_2d(np.sum(x1 ** 2, axis=1))).T
        norm2 = np.dot(np.ones([m1, 1]), np.atleast_2d(np.sum(x2 ** 2, axis=1)))
        return np.exp(- gamma * (norm1 + norm2 - 2 * np.dot(x1, x2.T)))
    return rbf_kernel


# k(x1, x2) = (<x1, x2> + coef0)^degree
def get_polynomial_kernel(degree, coef0):
    def polynomial_kernel(x1, x2):
        x1 = np.atleast_2d(x1)
        x2 = np.atleast_2d(x2)
        return (np.dot(x1, x2.T) + coef0)**degree
    return polynomial_kernel


class CSvc:

    def __init__(self, C=1e0, kernel=linear_kernel, tol=1e-3, max_iter=1000,
                 gamma=1e0, degree=3, coef0=0):
        self._EPS = 1e-5
        self._TAU = 1e-12
        self._cache = {}
        self.tol = tol
        self.max_iter = max_iter
        self.C = C
        self.gamma = gamma
        self.degree, self.coef0 = degree, coef0
        self.kernel = None
        self._alpha = None
        self.intercept_ = None
        self._grad = None
        self.itr = None
        self._ind_y_p, self._ind_y_n = None, None
        self._i_low, self._i_up = None, None
        self.set_kernel_function(kernel)

    def _init_solution(self, y):
        num = len(y)
        self._alpha = np.zeros(num)
        self._i_low = y < 0
        self._i_up = y > 0

    def set_kernel_function(self, kernel):
        if callable(kernel):
            self.kernel = kernel
        elif kernel == 'linear':
            self.kernel = linear_kernel
        elif kernel == 'rbf' or kernel == 'gaussian':
            self.kernel = get_rbf_kernel(self.gamma)
        elif kernel == 'polynomial' or kernel == 'poly':
            self.kernel = get_polynomial_kernel(self.degree, self.coef0)
        else:
            raise ValueError('{} is undefined name as kernel function'.format(kernel))

    def _select_working_set1(self, y):
        minus_y_times_grad = - y * self._grad
        # Convert boolean mask to index
        i_up = self._i_up.nonzero()[0]
        i_low = self._i_low.nonzero()[0]
        ind_ws1 = i_up[np.argmax(minus_y_times_grad[i_up])]
        ind_ws2 = i_low[np.argmin(minus_y_times_grad[i_low])]
        return ind_ws1, ind_ws2

    def fit(self, x, y):
        self._init_solution(y)
        self._cache = {}
        num, _ = x.shape
        # Initialize the dual coefficients and gradient
        self._grad = - np.ones(num)
        # Start the iterations of SMO algorithm
        for itr in xrange(self.max_iter):
            # Select two indices of variables as working set
            ind_ws1, ind_ws2 = self._select_working_set1(y)
            # Check stopping criteria: m(a_k) <= M(a_k) + tolerance
            m_lb = - y[ind_ws1] * self._grad[ind_ws1]
            m_ub = - y[ind_ws2] * self._grad[ind_ws2]
            kkt_violation = m_lb - m_ub
            # print 'KKT Violation:', kkt_violation
            if kkt_violation <= self.tol:
                print 'Converged!', 'Iter:', itr, 'KKT Violation:', kkt_violation
                break
            # Compute (or get from cache) two columns of gram matrix
            if ind_ws1 in self._cache:
                qi = self._cache[ind_ws1]
            else:
                qi = self.kernel(x, x[ind_ws1]).ravel() * y * y[ind_ws1]
                self._cache[ind_ws1] = qi
            if ind_ws2 in self._cache:
                qj = self._cache[ind_ws2]
            else:
                qj = self.kernel(x, x[ind_ws2]).ravel() * y * y[ind_ws2]
                self._cache[ind_ws2] = qj
            # Construct sub-problem
            qii, qjj, qij = qi[ind_ws1], qj[ind_ws2], qi[ind_ws2]
            # Solve sub-problem
            if y[ind_ws1] * y[ind_ws2] > 0:  # The case where y_i equals y_j
                v1, v2 = 1., -1.
                d_max = min(self.C - self._alpha[ind_ws1], self._alpha[ind_ws2])
                d_min = max(-self._alpha[ind_ws1], self._alpha[ind_ws2] - self.C)
            else:  # The case where y_i equals y_j
                v1, v2 = 1., 1.
                d_max = min(self.C - self._alpha[ind_ws1], self.C - self._alpha[ind_ws2])
                d_min = max(-self._alpha[ind_ws1], -self._alpha[ind_ws2])
            quad_coef = v1**2 * qii + v2**2 * qjj + 2 * v1 * v2 * qij
            quad_coef = max(quad_coef, self._TAU)
            d = - (self._grad[ind_ws1] * v1 + self._grad[ind_ws2] * v2) / quad_coef
            d = max(min(d, d_max), d_min)
            # Update dual coefficients
            self._alpha[ind_ws1] += d * v1
            self._alpha[ind_ws2] += d * v2
            # Update the gradient
            self._grad += d * v1 * qi + d * v2 * qj
            # Update I_up with respect to ind_ws1 and ind_ws2
            self._update_iup_and_ilow(y, ind_ws1)
            self._update_iup_and_ilow(y, ind_ws2)
        else:
            print 'Exceed maximum iteration'
            print 'KKT Violation:', kkt_violation
        # Set results after optimization procedure
        self._set_result(x, y)
        self.intercept_ = (m_lb + m_ub) / 2.
        self.itr = itr + 1

    def _update_iup_and_ilow(self, y, ind):
        # Update I_up with respect to ind
        if (y[ind] > 0) and (self._alpha[ind] / self.C <= 1 - self._EPS):
            self._i_up[ind] = True
        elif (y[ind] < 0) and (self._EPS <= self._alpha[ind] / self.C):
            self._i_up[ind] = True
        else:
            self._i_up[ind] = False
        # Update I_low with respect to ind
        if (y[ind] > 0) and (self._EPS <= self._alpha[ind] / self.C):
            self._i_low[ind] = True
        elif (y[ind] < 0) and (self._alpha[ind] / self.C <= 1 - self._EPS):
            self._i_low[ind] = True
        else:
            self._i_low[ind] = False

    def _set_result(self, x, y):
        self.support_ = np.where(self._EPS < (self._alpha / self.C))[0]
        self.support_vectors_ = x[self.support_]
        self.dual_coef_ = self._alpha[self.support_] * y[self.support_]
        # Compute w when using linear kernel
        if self.kernel == linear_kernel:
            self.coef_ = np.sum(self.dual_coef_ * x[self.support_].T, axis=1)

    def decision_function(self, x):
        return np.sum(self.kernel(x, self.support_vectors_) * self.dual_coef_, axis=1) + self.intercept_

    def predict(self, x):
        return np.sign(self.decision_function(x))

    def score(self, x, y):
        return sum(self.decision_function(x) * y > 0) / float(len(y))


if __name__ == '__main__':
    # Create toy problem
    np.random.seed(0)
    num_p = 15
    num_n = 15
    dim = 2
    x_p = np.random.multivariate_normal(np.ones(dim) * 1, np.eye(dim), num_p)
    x_n = np.random.multivariate_normal(np.ones(dim) * 2, np.eye(dim), num_n)
    x = np.vstack([x_p, x_n])
    y = np.array([1.] * num_p + [-1.] * num_n)

    # Set parameters
    max_iter = 500000
    C = 1e0
    gamma = 0.005
    tol = 1e-3

    # Set kernel function
    # kernel = get_rbf_kernel(gamma)
    # kernel = linear_kernel
    # kernel = 'rbf'
    # kernel = 'polynomial'
    kernel = 'linear'

    # Create object
    csvc = CSvc(C=C, kernel=kernel, max_iter=max_iter, tol=tol)

    # Run SMO algorithm
    csvc.fit(x, y)

Vergleichen Sie die Geschwindigkeit mit LIBSVM

Ich habe das ursprüngliche LIBSVM, Scikit-Learn und meine eigene Implementierung in einem einzigen Spiel verglichen. Ich habe den Datensatz von [hier] verwendet (https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/).

Der Inhalt von scikit-learn ist LIBSVM, aber die Verbindung mit Python ist ctypes, aber scikit-learn verwendet Cython. Ich weiß nicht viel darüber.

Ich habe einen linearen Kernel verwendet, und die Einheit der numerischen Werte in der Tabelle ist sec.

Selbstimplementierung scikit-learn LIBSVM
mushrooms 0.374 0.320 0.907
a1a 0.729 0.332 0.663
diabetes 0.0795 0.015 0.0451
liver 0.0243 0.0031 0.0109
splice 2.28 0.49 1.10
svmguide3 0.175 0.483 0.148

Es ist ziemlich nah an der ursprünglichen LIBSVM, aber es ist die mysteriöse Geschwindigkeit des Scicit-Lernens. Trotzdem habe ich das Gefühl, dass Python + NumPy allein der geeigneten Umgebung standhalten kann (?). Ich denke, ich habe mein Bestes gegeben.

Zusammenfassung

Recommended Posts

SMO mit Python + NumPy implementiert
[Python] Berechnungsmethode mit numpy
Implementierter Dateidownload mit Python + Bottle
Mein Numpy (Python)
FizzBuzz in Python3
Scraping mit Python
Statistik mit Python
Scraping mit Python
Python mit Go
Twilio mit Python
In Python integrieren
Spielen Sie mit 2016-Python
AES256 mit Python
Getestet mit Python
Python beginnt mit ()
# Python-Grundlagen (#Numpy 1/2)
mit Syntax (Python)
# Python-Grundlagen (#Numpy 2/2)
Bingo mit Python
Zundokokiyoshi mit Python
"Gauß-Prozess und maschinelles Lernen" Gauß-Prozessregression nur mit Python-Numpy implementiert
Python #Numpy Basics
Excel mit Python
[Python] Numpy Memo
Mikrocomputer mit Python
Mit Python besetzen
Debuggen mit VS-Code mit Boost Python Numpy
Verfolgen Sie grüne Objekte mit Python + Numpy (Partikelfilter)
1. Mit Python 1-2 gelernte Statistiken. Berechnung verschiedener Statistiken (Numpy)
[Rust / Python] Behandle Numpy mit PyO3 (Version August 2020)
Serielle Kommunikation mit Python
Zip, entpacken mit Python
Django 1.11 wurde mit Python3.6 gestartet
Primzahlbeurteilung mit Python
Python mit Eclipse + PyDev.
Socket-Kommunikation mit Python
Datenanalyse mit Python 2
Scraping in Python (Vorbereitung)
Python- und Numpy-Tipps
Python lernen mit ChemTHEATER 03
Sequentielle Suche mit Python
Führen Sie Python mit VBA aus
Umgang mit Yaml mit Python
Löse AtCoder 167 mit Python
Serielle Kommunikation mit Python
[Python] Verwenden Sie JSON mit Python
Python lernen mit ChemTHEATER 05-1
Lerne Python mit ChemTHEATER
Führen Sie prepDE.py mit python3 aus
1.1 Erste Schritte mit Python
Tweets mit Python sammeln
Binarisierung mit OpenCV / Python
3. 3. KI-Programmierung mit Python
Python nicht implementiert Fehler
Kernel-Methode mit Python
Nicht blockierend mit Python + uWSGI
Scraping mit Python + PhantomJS
Python Basic 8 Numpy Test
Gleitender Durchschnitt mit Numpy
Tweets mit Python posten