SGDClassifier: Nauka online / częściowy_fit z nieznaną wcześniej etykietą


9

Mój zestaw treningowy zawiera około 50 000 wpisów, z którymi przeprowadzam wstępną naukę. Co tydzień dodaje się około 5 000 wpisów; ale ta sama ilość „znika” (ponieważ są to dane użytkownika, które należy usunąć po pewnym czasie).

Dlatego korzystam z nauki online, ponieważ później nie mam dostępu do pełnego zestawu danych. Obecnie używam tego, SGDClassifierco działa, ale mój duży problem: pojawiają się nowe kategorie, a teraz nie mogę już używać mojego modelu, ponieważ nie były na początku fit.

Czy jest jakiś sposób na SGDClassifierinny model? Głęboka nauka?

Nie ma znaczenia, czy muszę zacząć od nowa TERAZ (tj. Użyć czegoś innego niż SGDClassifier), ale potrzebuję czegoś, co umożliwi naukę online z nowymi etykietami.


1
Kiedy mówisz, że masz nowe kategorie, czy mówisz o nowych kategoriach w swoich zmiennych egzogenicznych (Y) lub w zmiennych endogenicznych (X)?
Juan Esteban de la Calle

Odpowiedzi:


9

Wygląda na to, że nie chcesz przekwalifikowywać modelu za każdym razem, gdy pojawia się nowa kategoria etykiet. Najprostszym sposobem, aby zachować maksymalną informacje o ostatnich danych byłoby pociąg jeden klasyfikator każdej kategorii.

W ten sposób możesz kontynuować szkolenie każdego klasyfikatora stopniowo („online”) z czymś w rodzaju, SGDClassifierbez konieczności ich przekwalifikowywania. Ilekroć pojawia się nowa kategoria, dodajesz nowy binarny klasyfikator tylko dla tej kategorii. Następnie wybierasz klasę o najwyższym prawdopodobieństwie / wyniku wśród zestawu klasyfikatorów.

Nie różni się to zbytnio od tego, co robisz dzisiaj, ponieważ scikit's SDGClassifierjuż obsługuje scenariusz wieloklasowy, umieszczając pod maską wiele klasyfikatorów „jeden kontra wszyscy”.

Oczywiście, jeśli pojawi się wiele nowych kategorii, takie podejście może być nieco trudne do zarządzania.


1
Sprytny! Ta metoda może również działać dobrze z innymi klasyfikatorami scikit, które mają taką warm_startopcję.
Simon Larsson,

5

Jeśli nowe kategorie pojawiają się bardzo rzadko, ja osobiście wolę rozwiązanie „jeden kontra wszystkie” dostarczone przez @oW_ . Dla każdej nowej kategorii trenujesz nowy model na X liczbie próbek z nowej kategorii (klasa 1) i X liczbie próbek z pozostałych kategorii (klasa 0).

Jeśli jednak często pojawiają się nowe kategorie i chcesz skorzystać z jednego wspólnego modelu, istnieje sposób na osiągnięcie tego za pomocą sieci neuronowych.

Podsumowując, po nadejściu nowej kategorii dodajemy odpowiedni nowy węzeł do warstwy softmax o zerowych (lub losowych) wagach i utrzymujemy nietknięte stare wagi, a następnie trenujemy model rozszerzony o nowe dane. Oto wizualny szkic pomysłu (narysowany przeze mnie):

Oto implementacja pełnego scenariusza:

  1. Model jest szkolony w dwóch kategoriach,

  2. Nadchodzi nowa kategoria,

  3. Formaty modeli i docelowych są odpowiednio aktualizowane,

  4. Model jest szkolony na nowych danych.

Kod:

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))

które wyjścia:

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

Powinienem wyjaśnić dwie kwestie dotyczące tego wyniku:

  1. Wydajność modelu jest zmniejszana od 0.9275do 0.8925tylko przez dodanie nowego węzła. Jest tak, ponieważ dane wyjściowe nowego węzła są również uwzględniane przy wyborze kategorii. W praktyce dane wyjściowe nowego węzła należy uwzględnić dopiero po przeszkoleniu modelu na sporej próbce. Na przykład [0.15, 0.30, 0.55]na tym etapie powinniśmy osiągnąć najwyższą z dwóch pierwszych pozycji , tj. 2. klasy.

  2. Wydajność modelu rozszerzonego w dwóch (starych) kategoriach 0.88jest mniejsza niż w starym modelu 0.9275. Jest to normalne, ponieważ teraz model rozszerzony chce przypisać dane wejściowe do jednej z trzech kategorii zamiast dwóch. Ten spadek jest również oczekiwany, gdy wybieramy spośród trzech klasyfikatorów binarnych w porównaniu do dwóch klasyfikatorów binarnych w podejściu „jeden kontra wszyscy”.


1

Muszę powiedzieć, że nie znalazłem żadnej literatury na ten temat. O ile mi wiadomo, to, o co prosisz, jest niemożliwe. Powinieneś być tego świadomy, a właściciel produktu również powinien. Powodem jest to, że każda funkcja utraty opiera się na znanych etykietach, więc nie można przewidzieć etykiety, której nie ma w danych treningowych. Jest też science fiction, że algorytm uczenia maszynowego może przewidzieć coś, do czego nie został przeszkolony

Powiedziawszy to, myślę, że może istnieć obejście problemu (pozwolę sobie zauważyć, że jest to opinia nieoparta na formalnej literaturze). Jeśli klasyfikator jest probabilistyczny, wyjściem jest prawdopodobieństwo, że każda klasa jest prawdziwa, a decyzja jest wyższym prawdopodobieństwem. Być może możesz ustawić próg dla tego prawdopodobieństwa, tak że model przewiduje „nieznane”, jeśli wszystkie prawdopodobieństwa są poniżej tego progu. Dam ci przykład.

Pozwolić M.(x) być wzorem, który: biorąc pod uwagę x, decyduje, czy x należy do jednej z trzech kategorii do1,do2),do3). Dane wyjścioweM. jest wektorem prawdopodobieństw p. Decyzja jest podejmowana na podstawie najwyższego prawdopodobieństwap. Więc wynikM.(x)=p(x)=(0.2,0,76,0,5) odpowiada decyzji x należy do do2). Możesz zmodyfikować tę decyzję, ustawiającτ takie, jeśli żaden z nich pjaτ wtedy decyzja jest x należy do nieznanej klasy

To, co robisz z tymi nieznanymi , zależy od logiki biznesowej. Jeśli są one ważne, możesz utworzyć ich pulę i ponownie wyszkolić model przy użyciu dostępnych danych. Myślę, że możesz zrobić coś w rodzaju „transferu uczenia się” z wyuczonego modelu, zmieniając wymiar wyniku. Ale z tym się nie spotkałem, więc po prostu mówię

Weź pod uwagę, że SGDClassifierużywa SVMpod spodem, który nie jest algorytmem probabilistycznym. Zgodnie z SGDClassifierdokumentacją możesz zmodyfikować lossargument do modified_huberlub logw celu uzyskania wyników probabilistycznych.


0

Istnieją dwie opcje:

  1. Przewiduj, że punkt danych należy do nieznanej lub unkkategorii. Wszelkie nowe kategorie pojawiające się w strumieniu należy przewidzieć jako unk. Jest to powszechne w przetwarzaniu języka naturalnego (NLP), ponieważ zawsze pojawiają się nowe tokeny słów w strumieniach słów.

  2. Ponownie trenuj model za każdym razem, gdy pojawi się nowa kategoria.

Odkąd wspomniałeś SGDClassifier, zakładam, że używasz scikit-learn. Scikit-learn nie bardzo dobrze wspiera naukę online. Lepiej byłoby zmienić platformę, która lepiej obsługuje transmisję strumieniową i naukę online, taką jak Spark .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.