SGDClassifier: Önceden bilinmeyen bir etikete sahip Çevrimiçi Öğrenme / partial_fit


9

Eğitim setim, ilk öğrenmeyi yaptığım yaklaşık 50 bin girdi içeriyor. Haftalık olarak ~ 5 bin giriş eklenir; ancak aynı miktar "kaybolur" (bir süre sonra silinmesi gereken kullanıcı verileri olduğu için).

Bu nedenle çevrimiçi öğrenmeyi kullanıyorum çünkü daha sonra tam veri kümesine erişimim yok. Şu anda SGDClassifierçalışan bir tane kullanıyorum , ama büyük sorunum: yeni kategoriler ortaya çıkıyor ve şimdi modelimi başlangıçta olmadığı için kullanamıyorum fit.

Bir yolu var mı SGDClassifierveya başka bir modeli? Derin öğrenme?

ŞİMDİ sıfırdan başlamak zorundayım (yani başka bir şey kullanmam SGDClassifier) önemli değil, ancak yeni etiketlerle çevrimiçi öğrenmeyi sağlayan bir şeye ihtiyacım var.


1
Yeni kategorileriniz olduğunu söylediğinizde, eksojen değişkenlerinizdeki yeni kategorilerden mi bahsediyorsunuz (Y) veya endojen değişkenlerinizde (X)?
Juan Esteban de la Calle

Yanıtlar:


9

Her yeni etiket kategorisi göründüğünde modeli yeniden eğitmek istemediğiniz anlaşılıyor. En kolay yolu kategorisi başına Tren bir sınıflandırıcı olurdu geçmiş verilerin maksimal bilgileri korumak için.

Bu şekilde, her sınıflandırıcıyı SGDClassifieryeniden eğitmek zorunda kalmadan bir adım adım ("çevrimiçi") olarak eğitmeye devam edebilirsiniz. Ne zaman yeni bir kategori belirirse o kategori için yeni bir ikili sınıflandırıcı eklersiniz. Daha sonra sınıflandırıcılar kümesi arasında en yüksek olasılık / puana sahip sınıfı seçersiniz.

Bu aynı zamanda bugün yaptığınızdan çok da farklı değil, çünkü scikit's SDGClassifierzaten çoklu sınıf senaryosunu kaputun altına birden fazla "Bire Karşı" sınıflandırıcısı takarak zaten ele alıyor .

Elbette birçok yeni kategori gelmeye devam ederse, bu yaklaşım yönetilmesi biraz zor olabilir.


1
Zeki! Bu yöntem, warm_startseçeneği olan diğer scikit sınıflandırıcılarıyla da iyi çalışabilir .
Simon Larsson

5

Yeni kategoriler çok nadir geliyorsa, kendim @oW_ tarafından sağlanan "bire karşı" çözümünü tercih ederim . Her yeni kategori için, yeni kategoriden (sınıf 1) X örnek sayısı ve kategorilerin geri kalanından (sınıf 0) X örnek sayısı için yeni bir model eğitirsiniz.

Ancak, yeni kategoriler sık sık geliyorsa ve paylaşılan tek bir kategori kullanmak istiyorsanız model , bunu sinir ağları kullanarak gerçekleştirmenin bir yolu vardır.

Özetle, yeni bir kategorinin gelmesi üzerine, sıfır (veya rastgele) ağırlıklarla softmax katmanına karşılık gelen yeni bir düğüm ekliyoruz ve eski ağırlıkları sağlam tutuyoruz, ardından genişletilmiş modeli yeni verilerle eğitiyoruz. Fikir için görsel bir kroki (kendim çizdim):

İşte tüm senaryo için bir uygulama:

  1. Model iki kategoride eğitilir,

  2. Yeni bir kategori geliyor,

  3. Model ve hedef biçimleri buna göre güncellenir,

  4. Model yeni veriler konusunda eğitilmiştir.

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

hangi çıktılar:

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

Bu çıktıyla ilgili iki noktayı açıklamalıyım:

  1. Model performans indirilmiştir 0.9275için 0.8925sadece yeni bir düğüm ilave edilmesiyle hazırlandı. Bunun nedeni, kategori seçimi için yeni düğümün çıktısının da dahil edilmesidir. Uygulamada, yeni düğümün çıktısı ancak model büyük bir örnek üzerinde eğitildikten sonra dahil edilmelidir. Örneğin, [0.15, 0.30, 0.55]bu aşamada, yani 2. sınıftaki ilk iki girişin en büyüğüne zirve yapmalıyız .

  2. Genişletilmiş modelin iki (eski) kategoride performansı 0.88eski modelden daha azdır 0.9275. Bu normaldir, çünkü şimdi genişletilmiş model iki yerine üç kategoriden birine bir girdi atamak istiyor. Bu düşüş, "bire karşı" yaklaşımında iki ikili sınıflandırıcıya kıyasla üç ikili sınıflandırıcı arasından seçim yaptığımızda da beklenir.


1

Bu konuyla ilgili herhangi bir literatür bulamadığımı söylemeliyim. Bildiğim kadarıyla sorduğunuz şey imkansız. Bunun farkında olmalısınız ve ürün sahibi de olmalıdır. Bunun nedeni, herhangi bir kayıp fonksiyonunun bilinen etiketlere dayanmasıdır, bu nedenle eğitim verilerinde olmayan bir etiketi tahmin etmenin hiçbir yolu yoktur. Ayrıca, bir makine öğrenme algoritmasının, eğitim görmediği bir şeyi tahmin edebileceği bilim kurgu

Bunu söyledikten sonra, bir çözüm olabileceğini düşünüyorum (bunun resmi literatüre dayanmayan bir görüş olduğunu belirtmeme izin verin). Sınıflandırıcı olasılıklı ise, çıktı her sınıfın doğru olma olasılığıdır ve karar daha yüksek olasılıktır. Belki de bu olasılık için bir eşik ayarlayabilirsiniz, böylece tüm olasılıklar bu eşiğin altındaysa model "bilinmeyen" değerini tahmin eder. Sana bir örnek vereyim.

İzin Vermek M(x) öyle bir model olun: verilen bir x, x üç kategoriden birine aittir c1,c2,c3. ÇıktıM olasılıkların bir vektörüdür p. Karar,p. Yani bir çıktıM(x)=p(x)=(0.2,0.76,0.5) karara karşılık gelir x ait olmak c2. Bu kararı birτ eğer hiçbiri pbenτ o zaman karar x bilinmeyen sınıfa ait

Bu bilinmeyenlerle ne yaparsınız iş mantığına bağlıdır. Eğer önemliyse, bir havuz oluşturabilir ve mevcut verileri kullanarak modeli yeniden eğitebilirsiniz. Çıktının boyutunu değiştirerek eğitimli modelden bir çeşit “transfer öğrenimi” yapabileceğinizi düşünüyorum. Ama bu yüzleşmediğim bir şey, bu yüzden sadece söylüyorum

Olasılıksal bir algoritma olmayan , altında SGDClassifierkullanan sayın . Aşağıdaki değiştirebileceğiniz belgelerine göre argüman veyaSVMSGDClassifierlossmodified_huberlog olasılık çıkışları almak için.


0

İki seçenek vardır:

  1. Bilinmeyen veya unkkategoriye ait bir veri noktasının şansını tahmin edin . Akışta görünen yeni kategoriler olarak tahmin edilmelidir unk. Bu, Doğal Dil İşlemede (NLP) yaygındır, çünkü sözcük akışlarında her zaman yeni sözcük belirteçleri vardır.

  2. Her yeni kategori görüntülendiğinde modeli yeniden eğitin.

Bahsettiğinden beri SGDClassifier , sana scikit-öğrenme kullanarak varsayalım. Scikit-learn çevrimiçi öğrenmeyi çok iyi desteklemez. Spark gibi çevrimiçi akışları ve çevrimiçi öğrenmeyi daha iyi destekleyen bir çerçeveyi değiştirmek daha iyi olur .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.