Scikit Lernen Sie die SVC-Entscheidungsfunktion kennen und sagen Sie sie voraus

71

Ich versuche, die Beziehung zwischen Entscheidungsfunktion und Vorhersage zu verstehen, die Instanzmethoden von SVC sind ( http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html ). Bisher habe ich festgestellt, dass die Entscheidungsfunktion paarweise Bewertungen zwischen Klassen zurückgibt. Ich hatte den Eindruck, dass Predict die Klasse auswählt, die ihre paarweise Punktzahl maximiert, aber ich habe dies getestet und unterschiedliche Ergebnisse erzielt. Hier ist der Code, mit dem ich versucht habe, die Beziehung zwischen den beiden zu verstehen. Zuerst habe ich die paarweise Bewertungsmatrix generiert und dann die Klasse ausgedruckt, die eine maximale paarweise Bewertung aufweist, die sich von der von clf.predict vorhergesagten Klasse unterscheidet.

        result = clf.decision_function(vector)[0]
        counter = 0
        num_classes = len(clf.classes_)
        pairwise_scores = np.zeros((num_classes, num_classes))
        for r in xrange(num_classes):
            for j in xrange(r + 1, num_classes):
                pairwise_scores[r][j] = result[counter]
                pairwise_scores[j][r] = -result[counter]
                counter += 1

        index = np.argmax(pairwise_scores)
        class = index_star / num_classes
        print class
        print clf.predict(vector)[0]

Kennt jemand die Beziehung zwischen diesen Vorhersagen und Entscheidungsfunktionen?

Peter Tseng
quelle
1
"Entscheidungsfunktion gibt paarweise Bewertungen zwischen Klassen zurück" ist falsch. Es sollte die "Punktzahl für jede Klasse" sein, wie sie auf der Dokumentationsseite für decision_functionTeil: "Abstand der Proben X zur trennenden Hyperebene" geschrieben ist.
halb
7
@justhalf: nein, das OP ist korrekt. sklearn.svm.SVCVerwendet standardmäßig eine paarweise (Eins-gegen-Eins) Zerlegung und gibt für jede Stichprobe Abstände zu allen n (n-1) / 2-Hyperebenen zurück.
Fred Foo
1
Ups, ja, ich erinnerte mich, dass ich das irgendwo gelesen hatte. wurde aber durch die Dokumentation irregeführt. Es tut uns leid!
halb
Nachdem ich versucht habe zu antworten, denke ich, dass die Antwort von bcorso die sein sollte. Die Beziehung basiert tatsächlich auf dem Code, den er aus der C ++ - Implementierung übersetzt hat : decision = decision_function(params, sv, nv, a, b, X); votes = [(i if decision[p] > 0 else j) for p,(i,j) in enumerate((i,j) for i in range(len(cs)) for j in range(i+1,len(cs)))]. Die höchste Stimme von votesist im Grunde, was predicttut.
Eric Platon

Antworten:

46

Ich verstehe Ihren Code nicht vollständig, aber lassen Sie uns das Beispiel der Dokumentationsseite durchgehen, auf die Sie verwiesen haben:

import numpy as np
X = np.array([[-1, -1], [-2, -1], [1, 1], [2, 1]])
y = np.array([1, 1, 2, 2])
from sklearn.svm import SVC
clf = SVC()
clf.fit(X, y) 

Wenden wir nun sowohl die Entscheidungsfunktion als auch die Vorhersage auf die Stichproben an:

clf.decision_function(X)
clf.predict(X)

Die Ausgabe, die wir erhalten, ist:

array([[-1.00052254],
       [-1.00006594],
       [ 1.00029424],
       [ 1.00029424]])
array([1, 1, 2, 2])

Und das ist leicht zu interpretieren: Die Desionsfunktion sagt uns, auf welcher Seite der Hyperebene, die vom Klassifikator erzeugt wird, wir sind (und wie weit wir davon entfernt sind). Basierend auf diesen Informationen beschriftet der Schätzer die Beispiele mit dem entsprechenden Etikett.

Martin Böschen
quelle
23
Es ist leicht zu interpretieren, da dies ein binäres Beispiel ist. Im Mehrklassenfall SVC.decision_functionwird es komplizierter.
Fred Foo
24

Für Interessierte werde ich ein kurzes Beispiel für die predictvon C ++ ( hier ) in Python übersetzte Funktion veröffentlichen :

# I've only implemented the linear and rbf kernels
def kernel(params, sv, X):
    if params.kernel == 'linear':
        return [np.dot(vi, X) for vi in sv]
    elif params.kernel == 'rbf':
        return [math.exp(-params.gamma * np.dot(vi - X, vi - X)) for vi in sv]

# This replicates clf.decision_function(X)
def decision_function(params, sv, nv, a, b, X):
    # calculate the kernels
    k = kernel(params, sv, X)

    # define the start and end index for support vectors for each class
    start = [sum(nv[:i]) for i in range(len(nv))]
    end = [start[i] + nv[i] for i in range(len(nv))]

    # calculate: sum(a_p * k(x_p, x)) between every 2 classes
    c = [ sum(a[ i ][p] * k[p] for p in range(start[j], end[j])) +
          sum(a[j-1][p] * k[p] for p in range(start[i], end[i]))
                for i in range(len(nv)) for j in range(i+1,len(nv))]

    # add the intercept
    return [sum(x) for x in zip(c, b)]

# This replicates clf.predict(X)
def predict(params, sv, nv, a, b, cs, X):
    ''' params = model parameters
        sv = support vectors
        nv = # of support vectors per class
        a  = dual coefficients
        b  = intercepts 
        cs = list of class names
        X  = feature to predict       
    '''
    decision = decision_function(params, sv, nv, a, b, X)
    votes = [(i if decision[p] > 0 else j) for p,(i,j) in enumerate((i,j) 
                                           for i in range(len(cs))
                                           for j in range(i+1,len(cs)))]

    return cs[max(set(votes), key=votes.count)]

Es gibt viele Eingabeargumente für predictund decision_function, aber beachten Sie, dass diese beim Aufrufen alle vom Modell intern verwendet werden predict(X). Tatsächlich sind alle Argumente innerhalb des Modells nach dem Anpassen für Sie zugänglich:

# Create model
clf = svm.SVC(gamma=0.001, C=100.)

# Fit model using features, X, and labels, Y.
clf.fit(X, y)

# Get parameters from model
params = clf.get_params()
sv = clf.support_vectors
nv = clf.n_support_
a  = clf.dual_coef_
b  = clf._intercept_
cs = clf.classes_

# Use the functions to predict
print(predict(params, sv, nv, a, b, cs, X))

# Compare with the builtin predict
print(clf.predict(X))
bcorso
quelle
1
Hallo ! Vielen Dank für Ihre Antwort. Ich habe jedoch Ihre Lösung ausprobiert und die Ergebnisse sind unterschiedlich ...
Lilouch
Hallo, bcorso! Vielen Dank für Ihre Antwort, aber gerade als @lilouch zeigt, kann ich nicht die gleichen Werte erhalten. Die Entscheidungsfunktion wird als $ \ langle \ mathbf {w}, \ mathbf {x} \ rangle + b $ beschrieben, und dieser Wert muss für die positive Klasse größer als 1 und für die negative Klasse kleiner als -1 sein. Das Problem ist, dass ich nicht herausfinden kann, wie Sie das Punktprodukt zwischen dem neuen Beispiel und dem Hyperebenenvektor erstellen. Könnten Sie mir helfen?
Vhcandido
sklearn scheint zwei komplementäre Paare von dual_coef zu haben und Intercept, ändern a = clf.dual_coef_zu a = clf._dual_coef_und die Ausgabe von decision_functiondem gleiche ist wie clf._decision_function, und das Ergebnis der predictauch Kongruenz mitclf.predict
TurtleIzzy
19

Unter datascience.sx gibt es eine wirklich gute Frage und Antwort für das Mehrklassen-Eins-gegen-Eins-Szenario:

Frage

Ich habe einen SVM-Klassifikator für mehrere Klassen mit den Bezeichnungen 'A', 'B', 'C', 'D'.

Dies ist der Code, den ich ausführe:

>>>print clf.predict([predict_this])
['A']
>>>print clf.decision_function([predict_this])
[[ 185.23220833   43.62763596  180.83305074  -93.58628288   62.51448055  173.43335293]]

Wie kann ich die Ausgabe der Entscheidungsfunktion verwenden, um die Klasse (A / B / C / D) mit der höchsten Wahrscheinlichkeit und wenn möglich ihrem Wert vorherzusagen? Ich habe https://stackoverflow.com/a/20114601/7760998 besucht, aber es ist für binäre Klassifizierer und konnte keine gute Ressource finden, die die Ausgabe von entscheidungsfunktion für Klassifizierer mit mehreren Klassen mit der Form ovo (Eins gegen Eins) erklärt.

Bearbeiten:

Das obige Beispiel gilt für die Klasse 'A'. Für eine andere Eingabe sagte der Klassifizierer 'C' voraus und gab das folgende Ergebnis in entscheidungsfunktion

[[ 96.42193513 -11.13296606 111.47424538 -88.5356536 44.29272494 141.0069203 ]]

Für eine andere andere Eingabe, die der Klassifikator als 'C' vorhergesagt hat, ergab sich das folgende Ergebnis aus entscheidungsfunktion:

[[ 290.54180354 -133.93467605  116.37068951 -392.32251314 -130.84421412   284.87653043]]

Wäre es ovr (Eins gegen Ruhe) gewesen, würde es einfacher werden, wenn man das mit dem höheren Wert auswählt, aber in ovo (Eins gegen Eins) gibt es (n * (n - 1)) / 2Werte in der resultierenden Liste.

Wie kann man anhand der Entscheidungsfunktion ableiten, welche Klasse ausgewählt wird?

Antworten

Ihr Link verfügt über ausreichende Ressourcen. Gehen wir also Folgendes durch:

Wenn Sie entscheidungsfunktion () aufrufen, erhalten Sie die Ausgabe von jedem der paarweisen Klassifizierer (n * (n-1) / 2 Zahlen insgesamt). Siehe Seiten 127 und 128 von "Support Vector Machines for Pattern Classification".

Klicken Sie auf den Link "Seite 127 und 128" (hier nicht gezeigt, aber in der Stackoverflow-Antwort). Das solltest du sehen:

Geben Sie hier die Bildbeschreibung ein

  • Die SVM-Implementierung von Python verwendet Eins-gegen-Eins. Genau darum geht es in dem Buch.
  • Für jeden paarweisen Vergleich messen wir die Entscheidungsfunktion
  • Die Entscheidungsfunktion ist nur die reguläre binäre SVM-Entscheidungsgrenze

Was hat das mit Ihrer Frage zu tun?

  • clf.decision_function () gibt Ihnen die $ D $ für jeden paarweisen Vergleich
  • Die Klasse mit den meisten Stimmen gewinnt

Zum Beispiel,

[[96.42193513 -11.13296606 111.47424538 -88.5356536 44.29272494 141.0069203]]

vergleicht:

[AB, AC, AD, BC, BD, CD]

Wir kennzeichnen jeden von ihnen mit dem Schild. Wir bekommen:

[A, C, A, C, B, C]

Zum Beispiel ist 96.42193513 positiv und somit ist A die Bezeichnung für AB.

Jetzt haben wir drei C, C wäre Ihre Vorhersage. Wenn Sie meine Prozedur für die beiden anderen Beispiele wiederholen, erhalten Sie Pythons Vorhersage. Versuch es!

serv-inc
quelle
Sollen die Intercept-Werte ( b) zum Punktprodukt addiert oder subtrahiert werden? Ich habe auf Wikipedia geschaut und es wird subtrahiert, aber in dem Artikel wird es hinzugefügt. Ist das überhaupt so wichtig? Ich bin ziemlich besorgt, weil ich die Entscheidungsfunktion als w.x + bstatt berechne w.x - b.
fabda01
Während Sie dies im Original fragen könnten, sollte die Verwendung von + b anstelle von -b intuitiv zu einem invertierten b führen. Dies sollte eigentlich kein Problem sein.
serv-inc
18

Wenn Sie anrufen decision_function(), erhalten Sie die Ausgabe von jedem der paarweisen Klassifizierer (n * (n-1) / 2 Nummern insgesamt). Siehe Seiten 127 und 128 von "Support Vector Machines for Pattern Classification" .

Jeder Klassifikator gibt eine Abstimmung darüber ab, wie die richtige Antwort lautet (basierend auf dem Vorzeichen der Ausgabe dieses Klassifikators). predict()gibt die Klasse mit den meisten Stimmen zurück.

RomanCobra
quelle
2
Danke Roman! Ich habe das getestet und es sieht größtenteils so aus, als würde man vorhersagen, dass man die Klasse auswählt, die die meisten Stimmen erhält. Was ich anfangs falsch gemacht habe, war die Auswahl der Klasse mit der besten kumulativen Margin-Punktzahl.
Peter Tseng
3

Sie haben wahrscheinlich eine etwas komplizierte mathematische Beziehung. Wenn Sie jedoch den decision_functionIn- LinearSVCKlassifikator verwenden, wird die Beziehung zwischen diesen beiden klarer! Denn dann erhalten decision_functionSie Punkte für jedes Klassenlabel (nicht identisch mit SVC) und sagen voraus, dass die Klasse die beste Punktzahl erhält.

Bilal Dadanlar
quelle
1

Predict () folgt einem paarweisen Abstimmungsschema, das die Klasse mit den meisten Stimmen über alle paarweisen Vergleiche zurückgibt. Wenn zwei Klassen die gleiche Punktzahl erzielen, wird die Klasse mit dem niedrigsten Index zurückgegeben.

Unten ein Python-Beispiel, das dieses Abstimmungsschema auf die paarweisen Punktzahlen (n * (n-1) / 2) anwendet, die von einer Eins-gegen-Eins-Entscheidungsfunktion () zurückgegeben werden.

from sklearn import svm
from sklearn import datasets
from numpy import argmax, zeros
from itertools import combinations

# do pairwise comparisons, return class with most +1 votes
def ovo_vote(classes, decision_function):
    combos = list(combinations(classes, 2))
    votes = zeros(len(classes))
    for i in range(len(decision_function[0])):
        if decision_function[0][i] > 0:
            votes[combos[i][0]] = votes[combos[i][0]] + 1
        else:
            votes[combos[i][1]] = votes[combos[i][1]] + 1
    winner = argmax(votes)
    return classes[winner]

# load the digits data set
digits = datasets.load_digits()

X, y = digits.data, digits.target

# set the SVC's decision function shape to "ovo"
estimator = svm.SVC(gamma=0.001, C=100., decision_function_shape='ovo')

# train SVC on all but the last digit
estimator.fit(X.data[:-1], y[:-1])

# print the value of the last digit
print("To be classified digit: ", y[-1:][0])

# print the predicted class
pred = estimator.predict(X[-1:])
print("Perform classification using predict: ", pred[0])

# get decision function
df = estimator.decision_function(X[-1:])

# print the decision function itself
print("Decision function consists of",len(df[0]),"elements:")
print(df)

# get classes, here, numbers 0 to 9
digits = estimator.classes_

# print which class has most votes
vote = ovo_vote(digits, df)
print("Perform classification using decision function: ", vote)

Robin van Emden
quelle