SGDClassifier: Online Learning / Partial_fit mit einem bisher unbekannten Label


9

Mein Trainingsset enthält ungefähr 50.000 Einträge, mit denen ich ein erstes Lernen mache. Wöchentlich werden ~ 5.000 Einträge hinzugefügt. aber der gleiche Betrag "verschwindet" (da es sich um Benutzerdaten handelt, die nach einiger Zeit gelöscht werden müssen).

Daher verwende ich Online-Lernen, da ich zu einem späteren Zeitpunkt keinen Zugriff auf den vollständigen Datensatz habe. Momentan verwende ich eine, SGDClassifierdie funktioniert, aber mein großes Problem: Es erscheinen neue Kategorien und jetzt kann ich mein Modell nicht mehr verwenden, da sie nicht in der Anfangszeit waren fit.

Gibt es einen Weg mit SGDClassifieroder einem anderen Modell? Tiefes Lernen?

Es spielt keine Rolle, ob ich JETZT von vorne anfangen muss (dh etwas anderes als verwenden muss SGDClassifier), aber ich brauche etwas, das Online-Lernen mit neuen Labels ermöglicht.


1
Wenn Sie sagen, dass Sie neue Kategorien haben, sprechen Sie über neue Kategorien in Ihren exogenen Variablen ( ) oder in Ihren endogenen Variablen ( X )? YX
Juan Esteban de la Calle

Antworten:


9

Es hört sich so an, als ob Sie das Modell nicht jedes Mal neu trainieren möchten, wenn eine neue Etikettenkategorie angezeigt wird. Der einfachste Weg, um maximale Informationen über vergangene Daten zu erhalten, besteht darin, einen Klassifikator pro Kategorie zu trainieren.

Auf diese Weise können Sie jeden Klassifikator schrittweise ("online") mit so etwas wie trainieren, SGDClassifierohne ihn neu trainieren zu müssen. Immer wenn eine neue Kategorie angezeigt wird, fügen Sie einen neuen binären Klassifikator nur für diese Kategorie hinzu. Anschließend wählen Sie die Klasse mit der höchsten Wahrscheinlichkeit / Punktzahl unter den Klassifizierern aus.

Dies unterscheidet sich auch nicht wesentlich von dem, was Sie heute tun, da das Multiklassen-Szenario bereits durch Anbringen mehrerer "One vs All" -Klassifikatoren unter der Haube scikit's SDGClassifierbewältigt wird .

Wenn immer wieder neue Kategorien auftauchen, kann es natürlich schwierig werden, diesen Ansatz zu verwalten.


1
Klug! Diese Methode funktioniert möglicherweise auch gut mit anderen Scikit-Klassifizierern, die diese warm_startOption haben.
Simon Larsson

5

Wenn sehr selten neue Kategorien eintreffen, bevorzuge ich selbst die von @oW_ bereitgestellte "one vs all" -Lösung . Für jede neue Kategorie trainieren Sie ein neues Modell mit X Anzahl von Stichproben aus der neuen Kategorie (Klasse 1) und X Anzahl von Stichproben aus den übrigen Kategorien (Klasse 0).

Wenn jedoch häufig neue Kategorien eintreffen und Sie ein einzelnes gemeinsames Modell verwenden möchten , gibt es eine Möglichkeit, dies mithilfe neuronaler Netze zu erreichen.

Zusammenfassend lässt sich sagen, dass wir beim Eintreffen einer neuen Kategorie der Softmax-Ebene einen entsprechenden neuen Knoten mit null (oder zufälligen) Gewichten hinzufügen und die alten Gewichte intakt halten. Anschließend trainieren wir das erweiterte Modell mit den neuen Daten. Hier ist eine visuelle Skizze für die Idee (von mir gezeichnet):

Hier ist eine Implementierung für das gesamte Szenario:

  1. Das Modell wird in zwei Kategorien trainiert:

  2. Eine neue Kategorie kommt an,

  3. Modell- und Zielformate werden entsprechend aktualisiert.

  4. Das Modell wird auf neue Daten trainiert.

Code:

from keras import Model
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam
from sklearn.metrics import f1_score
import numpy as np


# Add a new node to the last place in Softmax layer
def add_category(model, pre_soft_layer, soft_layer, new_layer_name, random_seed=None):
    weights = model.get_layer(soft_layer).get_weights()
    category_count = len(weights)
    # set 0 weight and negative bias for new category
    # to let softmax output a low value for new category before any training
    # kernel (old + new)
    weights[0] = np.concatenate((weights[0], np.zeros((weights[0].shape[0], 1))), axis=1)
    # bias (old + new)
    weights[1] = np.concatenate((weights[1], [-1]), axis=0)
    # New softmax layer
    softmax_input = model.get_layer(pre_soft_layer).output
    sotfmax = Dense(category_count + 1, activation='softmax', name=new_layer_name)(softmax_input)
    model = Model(inputs=model.input, outputs=sotfmax)
    # Set the weights for the new softmax layer
    model.get_layer(new_layer_name).set_weights(weights)
    return model


# Generate data for the given category sizes and centers
def generate_data(sizes, centers, label_noise=0.01):
    Xs = []
    Ys = []
    category_count = len(sizes)
    indices = range(0, category_count)
    for category_index, size, center in zip(indices, sizes, centers):
        X = np.random.multivariate_normal(center, np.identity(len(center)), size)
        # Smooth [1.0, 0.0, 0.0] to [0.99, 0.005, 0.005]
        y = np.full((size, category_count), fill_value=label_noise/(category_count - 1))
        y[:, category_index] = 1 - label_noise
        Xs.append(X)
        Ys.append(y)
    Xs = np.vstack(Xs)
    Ys = np.vstack(Ys)
    # shuffle data points
    p = np.random.permutation(len(Xs))
    Xs = Xs[p]
    Ys = Ys[p]
    return Xs, Ys


def f1(model, X, y):
    y_true = y.argmax(1)
    y_pred = model.predict(X).argmax(1)
    return f1_score(y_true, y_pred, average='micro')


seed = 12345
verbose = 0
np.random.seed(seed)

model = Sequential()
model.add(Dense(5, input_shape=(2,), activation='tanh', name='pre_soft_layer'))
model.add(Dense(2, input_shape=(2,), activation='softmax', name='soft_layer'))
model.compile(loss='categorical_crossentropy', optimizer=Adam())

# In 2D feature space,
# first category is clustered around (-2, 0),
# second category around (0, 2), and third category around (2, 0)
X, y = generate_data([1000, 1000], [[-2, 0], [0, 2]])
print('y shape:', y.shape)

# Train the model
model.fit(X, y, epochs=10, verbose=verbose)

# Test the model
X_test, y_test = generate_data([200, 200], [[-2, 0], [0, 2]])
print('model f1 on 2 categories:', f1(model, X_test, y_test))

# New (third) category arrives
X, y = generate_data([1000, 1000, 1000], [[-2, 0], [0, 2], [2, 0]])
print('y shape:', y.shape)

# Extend the softmax layer to accommodate the new category
model = add_category(model, 'pre_soft_layer', 'soft_layer', new_layer_name='soft_layer2')
model.compile(loss='categorical_crossentropy', optimizer=Adam())

# Test the extended model before training
X_test, y_test = generate_data([200, 200, 0], [[-2, 0], [0, 2], [2, 0]])
print('extended model f1 on 2 categories before training:', f1(model, X_test, y_test))

# Train the extended model
model.fit(X, y, epochs=10, verbose=verbose)

# Test the extended model on old and new categories separately
X_old, y_old = generate_data([200, 200, 0], [[-2, 0], [0, 2], [2, 0]])
X_new, y_new = generate_data([0, 0, 200], [[-2, 0], [0, 2], [2, 0]])
print('extended model f1 on two (old) categories:', f1(model, X_old, y_old))
print('extended model f1 on new category:', f1(model, X_new, y_new))

welche Ausgänge:

y shape: (2000, 2)
model f1 on 2 categories: 0.9275
y shape: (3000, 3)
extended model f1 on 2 categories before training: 0.8925
extended model f1 on two (old) categories: 0.88
extended model f1 on new category: 0.91

Ich sollte zwei Punkte in Bezug auf diese Ausgabe erklären:

  1. Die Modellleistung wird durch einfaches Hinzufügen eines neuen Knotens von 0.9275bis verringert 0.8925. Dies liegt daran, dass die Ausgabe des neuen Knotens auch für die Kategorieauswahl enthalten ist. In der Praxis sollte die Ausgabe eines neuen Knotens erst aufgenommen werden, nachdem das Modell an einer großen Stichprobe trainiert wurde. Zum Beispiel sollten wir [0.15, 0.30, 0.55]zu diesem Zeitpunkt den größten der ersten beiden Einträge in der 2. Klasse erreichen.

  2. Die Leistung des erweiterten Modells in zwei (alten) Kategorien 0.88ist geringer als die des alten Modells 0.9275. Dies ist normal, da das erweiterte Modell nun einer von drei Kategorien anstelle von zwei eine Eingabe zuweisen möchte. Dieser Rückgang wird auch erwartet, wenn wir aus drei binären Klassifikatoren im Vergleich zu zwei binären Klassifikatoren im Ansatz "Eins gegen Alle" auswählen.


1

Ich muss sagen, dass ich zu diesem Thema keine Literatur gefunden habe. Soweit ich weiß, ist das, was Sie fragen, unmöglich. Sie sollten sich dessen bewusst sein, und der Product Owner sollte es auch sein. Der Grund dafür ist, dass jede Verlustfunktion auf bekannten Labels beruht, sodass Sie kein Label vorhersagen können, das nicht in den Trainingsdaten enthalten ist. Es ist auch Science-Fiction, dass ein Algorithmus für maschinelles Lernen etwas vorhersagen kann, für das er nicht trainiert wurde

Trotzdem denke ich, dass es eine Problemumgehung geben kann (lassen Sie mich darauf hinweisen, dass dies eine Meinung ist, die nicht auf formaler Literatur basiert). Wenn der Klassifikator probabilistisch ist, ist die Ausgabe die Wahrscheinlichkeit, dass jede Klasse wahr ist, und die Entscheidung ist die höhere Wahrscheinlichkeit. Möglicherweise können Sie einen Schwellenwert für diese Wahrscheinlichkeit festlegen, sodass das Modell "unbekannt" vorhersagt, wenn alle Wahrscheinlichkeiten unter diesem Schwellenwert liegen. Lassen Sie mich Ihnen ein Beispiel geben.

M(x)xxc1,c2,c3MppM(x)=p(x)=(0.2,0.76,0.5)xc2τpiτx

Was Sie mit diesen Unbekannten machen , hängt von der Geschäftslogik ab. Wenn sie wichtig sind, können Sie einen Pool von ihnen erstellen und das Modell anhand der verfügbaren Daten neu trainieren. Ich denke, Sie können eine Art "Transferlernen" aus dem trainierten Modell durchführen, indem Sie die Dimension der Ausgabe ändern. Aber das ist etwas, mit dem ich nicht konfrontiert bin, also sage ich nur

Nehmen Sie die Zählung an, SGDClassifierdie SVMdarunter verwendet wird, was kein probabilistischer Algorithmus ist. In der folgenden SGDClassifierDokumentation können Sie das lossArgument ändern modified_huberoder ändern log, um probabilistische Ausgaben zu erhalten.


0

Es gibt zwei Möglichkeiten:

  1. Prognostizieren Sie die Wahrscheinlichkeit, dass ein Datenpunkt zu einem Unbekannten oder einer unkKategorie gehört. Alle neuen Kategorien, die im Stream angezeigt werden, sollten als vorhergesagt werden unk. Dies ist bei der Verarbeitung natürlicher Sprache (Natural Language Processing, NLP) üblich, da in Wortströmen immer neue Wort-Token angezeigt werden.

  2. Trainieren Sie das Modell jedes Mal neu, wenn eine neue Kategorie angezeigt wird.

Da Sie erwähnen SGDClassifier, gehe ich davon aus, dass Sie Scikit-Learn verwenden. Scikit-learn unterstützt das Online-Lernen nicht sehr gut. Es wäre besser, ein Framework zu wechseln, das Streaming und Online-Lernen besser unterstützt, wie z. B. Spark .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.