SGDClassifier: Pembelajaran Online / partial_fit dengan label yang sebelumnya tidak dikenal

9

Set pelatihan saya berisi sekitar 50k entri yang saya gunakan untuk pembelajaran awal. Setiap minggu, ~ 5k entri ditambahkan; tetapi jumlah yang sama "menghilang" (karena ini adalah data pengguna yang harus dihapus setelah beberapa waktu).

Karena itu saya menggunakan pembelajaran online karena saya tidak memiliki akses ke dataset lengkap di lain waktu. Saat ini saya menggunakan SGDClassifieryang berfungsi, tetapi masalah besar saya: kategori baru muncul dan sekarang saya tidak dapat menggunakan model saya lagi karena mereka tidak di awal fit.

Apakah ada cara dengan SGDClassifieratau model lain? Belajar mendalam?

Tidak masalah jika saya harus mulai dari awal SEKARANG (yaitu menggunakan sesuatu selain SGDClassifier), tetapi saya membutuhkan sesuatu yang memungkinkan pembelajaran online dengan label baru.

swalkner
sumber
1
Ketika Anda mengatakan Anda memiliki kategori baru, apakah Anda berbicara tentang kategori baru dalam variabel eksogen Anda ( ) atau dalam variabel endogen Anda ( )? XYX
Juan Esteban de la Calle

Jawaban:

9

Sepertinya Anda tidak ingin mulai melatih ulang model setiap kali kategori label baru muncul. Cara termudah untuk menyimpan informasi maksimal data masa lalu adalah melatih satu classifier per kategori.

Dengan cara ini Anda dapat terus melatih setiap classifier secara bertahap ("online") dengan sesuatu seperti SGDClassifiertanpa harus melatihnya kembali. Setiap kali kategori baru muncul, Anda menambahkan classifier biner baru untuk kategori itu. Anda kemudian memilih kelas dengan probabilitas / skor tertinggi di antara set pengklasifikasi.

Ini juga tidak jauh berbeda dari apa yang Anda lakukan hari ini, karena scikit's SDGClassifiersudah menangani skenario multikelas dengan memasang beberapa pengklasifikasi "Satu vs Semua" di bawah tenda.

Jika banyak kategori baru terus muncul, tentu saja, pendekatan ini mungkin menjadi sedikit sulit untuk dikelola.

oW_
sumber
1
Pintar! Metode ini mungkin juga bekerja dengan baik dengan pengklasifikasi scikit lain yang memiliki warm_startopsi.
Simon Larsson
5

Jika kategori baru datang sangat jarang, saya sendiri lebih suka solusi "satu vs semua" yang disediakan oleh @oW_ . Untuk setiap kategori baru, Anda melatih model baru pada jumlah sampel X dari kategori baru (kelas 1), dan jumlah sampel X dari sisa kategori (kelas 0).

Namun, jika kategori baru sering datang dan Anda ingin menggunakan model bersama tunggal , ada cara untuk melakukannya dengan menggunakan jaringan saraf.

Singkatnya, setelah kedatangan kategori baru, kami menambahkan node baru yang sesuai ke lapisan softmax dengan nol (atau acak) bobot, dan menjaga bobot lama tetap utuh, kemudian kami melatih model yang diperluas dengan data baru. Berikut ini adalah sketsa visual untuk ide tersebut (digambar sendiri):

Berikut ini adalah implementasi untuk skenario lengkap:

  1. Model dilatih pada dua kategori,

  2. Kategori baru tiba,

  3. Format model dan target diperbarui sesuai,

  4. Model dilatih tentang data baru.

Kode:

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

yang keluaran:

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

Saya harus menjelaskan dua poin mengenai output ini:

  1. Performa model menurun dari 0.9275menjadi 0.8925hanya dengan menambahkan node baru. Ini karena output dari simpul baru juga termasuk untuk pemilihan kategori. Dalam prakteknya, output dari simpul baru harus dimasukkan hanya setelah model dilatih pada sampel yang cukup besar. Sebagai contoh, kita harus memuncak dua entri pertama terbesar [0.15, 0.30, 0.55], yaitu kelas 2, pada tahap ini.

  2. Kinerja model yang diperluas pada dua kategori (lama) 0.88kurang dari model yang lama 0.9275. Ini normal, karena sekarang model extended ingin menetapkan input ke salah satu dari tiga kategori, bukan dua. Penurunan ini juga diharapkan ketika kami memilih dari tiga pengklasifikasi biner dibandingkan dengan dua pengklasifikasi biner dalam pendekatan "satu vs semua".

Orang Esma
sumber
1

Saya harus mengatakan bahwa saya belum menemukan literatur mengenai topik ini. Sejauh yang saya tahu, apa yang Anda minta tidak mungkin. Anda harus mengetahui hal ini, dan pemilik produk juga harus tahu. Alasannya adalah bahwa setiap fungsi kerugian bergantung pada label yang dikenal, jadi tidak mungkin Anda dapat memprediksi label yang tidak ada dalam data pelatihan. Juga, adalah fiksi ilmiah bahwa algoritma pembelajaran mesin dapat memprediksi sesuatu yang belum dilatih

Karena itu, saya pikir mungkin ada solusi (izinkan saya menunjukkan bahwa ini adalah pendapat yang tidak didasarkan pada literatur formal). Jika classifier adalah probabilistik, output adalah probabilitas untuk setiap kelas menjadi benar dan keputusannya adalah prob yang lebih tinggi. Mungkin Anda bisa menetapkan ambang batas untuk probabilitas itu, sehingga model memprediksi "tidak diketahui" jika semua probabilitas di bawah ambang itu. Biarkan saya memberi Anda sebuah contoh.

Biarkan menjadi model sedemikian rupa sehingga: diberi , memutuskan apakah milik satu dari tiga kategori . Output dari adalah vektor probabilitas . Keputusan dibuat dengan mengambil prob tertinggi di . Jadi output dari akan sesuai dengan keputusan milik . Anda dapat mengubah keputusan ini dengan menetapkan seperti itu jika tidak ada maka keputusannya adalah milik kelas yang tidak dikenalM(x)xxc1,c2,c3MppM(x)=p(x)=(0.2,0.76,0.5)xc2τpiτx

Apa yang Anda lakukan dengan yang tidak diketahui itu tergantung pada logika bisnis. Jika mereka penting, Anda dapat membuat kumpulan mereka dan melatih kembali model menggunakan data yang tersedia. Saya pikir Anda dapat melakukan semacam "transfer pembelajaran" dari model yang terlatih dengan mengubah dimensi output. Tapi ini adalah sesuatu yang belum saya hadapi, jadi saya hanya mengatakan

Ambil hitungan yang SGDClassifiermenggunakan di SVMbawahnya, yang bukan algoritma probabilistik. Berikut SGDClassifierdokumentasi Anda dapat memodifikasi lossargumen untuk modified_huberatau loguntuk mendapatkan output probabilistik.

lsmor
sumber
0

Ada dua opsi:

  1. Memprediksi peluang titik data milik unkkategori atau tidak diketahui . Setiap kategori baru yang muncul dalam aliran harus diprediksi sebagai unk. Ini biasa terjadi di Natural Language Processing (NLP) karena selalu ada token kata baru yang muncul di aliran kata.

  2. Latih kembali model setiap kali kategori baru muncul.

Karena Anda menyebutkan SGDClassifier, saya menganggap Anda menggunakan scikit-belajar. Scikit-belajar tidak mendukung pembelajaran online dengan sangat baik. Akan lebih baik untuk beralih kerangka kerja yang lebih baik mendukung streaming dan pembelajaran online, seperti Spark .

Brian Spiering
sumber