SGDClassifier: Інтернет-навчання / часткова_фіксація з раніше невідомою міткою


9

Мій навчальний набір містить близько 50 тис. Записів, з якими я навчаюсь. Щотижня додається ~ 5k записів; але така ж кількість "зникає" (так як це дані користувачів, які доводиться видаляти через деякий час).

Тому я використовую онлайн-навчання, оскільки в подальшому не маю доступу до повного набору даних. В даний час я використовую функцію, SGDClassifierяка працює, але моя велика проблема: з’являються нові категорії, і тепер я більше не можу використовувати свою модель, оскільки їх не було в початковій fit.

Чи є спосіб з SGDClassifierякоюсь чи іншою моделлю? Глибоке навчання?

Не має значення, чи потрібно починати з нуля (тобто використовувати щось інше, ніж SGDClassifier), але мені потрібно щось, що дозволяє навчатись в Інтернеті з новими мітками.


1
Коли ви говорите, що у вас є нові категорії, чи говорите ви про нові категорії у своїх екзогенних змінних (Y) або у ваших ендогенних змінних (Х)?
Хуан Естебан де ла Калле

Відповіді:


9

Здається, ви не хочете починати перекваліфікувати модель щоразу, коли з’являється нова категорія етикетки. Самий простий спосіб зберегти максимальну інформацію про минулі даних буде залізно один класифікатором для кожної категорії.

Таким чином, ви можете продовжувати тренувати кожен класифікатор поетапно ("онлайн") чимось на кшталт, SGDClassifierне потребуючи їх перекваліфікації. Щоразу, коли з’являється нова категорія, ви додаєте новий двійковий класифікатор саме для цієї категорії. Потім ви вибираєте клас з найбільшою ймовірністю / балом серед набору класифікаторів.

Це також не сильно відрізняється від того, що ви робите сьогодні, тому що scikit's SDGClassifierвже обробляє багатокласовий сценарій, помістивши під капот кілька класифікаторів "One vs All".

Якщо багато нових категорій продовжують з'являтися, звичайно, такий підхід може стати дещо складним в управлінні.


1
Розумний! Цей метод також може добре працювати з іншими класифікаторами scikit, які мають warm_startможливість.
Саймон Ларссон

5

Якщо нові категорії надходять дуже рідко, я сам вважаю за краще рішення "один проти всіх", яке надає @oW_ . Для кожної нової категорії ви навчаєте нову модель на X кількість зразків з нової категорії (клас 1) та X кількість зразків з решти категорій (клас 0).

Однак якщо нові категорії надходять часто і ви хочете використовувати єдину спільну модель, є спосіб досягти цього за допомогою нейронних мереж.

Підсумовуючи це, після появи нової категорії ми додаємо відповідний новий вузол у шар softmax з нульовими (або випадковими) вагами, а старі ваги залишаємося недоторканими, потім ми навчаємо розширену модель з новими даними. Ось візуальний ескіз ідеї (намальований власноруч):

Ось реалізація для повного сценарію:

  1. Модель готується за двома категоріями,

  2. Приходить нова категорія,

  3. Модельний та цільовий формати відповідно оновлюються,

  4. Модель навчається за новими даними.

Код:

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

який виводить:

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

Я повинен пояснити два моменти щодо цього результату:

  1. Продуктивність моделі зменшується 0.9275до 0.8925, просто додаючи новий вузол. Це тому, що вихід нового вузла також включений для вибору категорії. На практиці вихід нового вузла повинен бути включений лише після того, як модель буде навчена на значному зразку. Наприклад, нам слід досягти найбільшого з перших двох записів [0.15, 0.30, 0.55], тобто 2-го класу, на цьому етапі.

  2. Продуктивність розширеної моделі у двох (старих) категоріях 0.88менша, ніж у старої 0.9275. Це нормально, тому що тепер розширена модель хоче призначити вхід одній із трьох категорій замість двох. Таке зменшення також очікується, коли ми вибираємо з трьох двійкових класифікаторів порівняно з двома двійковими класифікаторами у підході «один проти всіх».


1

Я повинен сказати, що я не знайшов жодної літератури щодо цієї теми. Наскільки я знаю, те, що ви просите, неможливо. Ви повинні знати про це, і власник продукту також повинен бути. Причина полягає в тому, що будь-яка функція втрати покладається на відомі мітки, тому немає можливості передбачити ярлик, який відсутній у навчальних даних. Крім того, науково-фантастичним є те, що алгоритм машинного навчання може передбачити щось, для чого він не був навчений

Сказавши це, я думаю, що може бути рішення (дозвольте зазначити, що це думка, яка не ґрунтується на офіційній літературі). Якщо класифікатор є імовірнісним, то вихід - це ймовірність того, що кожен клас є істинним, а рішення - вищою проблемою. Можливо, ви можете встановити поріг такої ймовірності, щоб модель передбачила "невідомо", якщо всі ймовірності знаходяться нижче цього порогу. Дозвольте навести вам приклад.

Дозволяє М(х) бути такою моделлю, що: дано х, вирішує, чи х належить до однієї з трьох категорій c1,c2,c3. ВихідМ є вектором ймовірностей p. Рішення приймається шляхом прийняття найвищого рівня вp. Отже, вихідМ(х)=p(х)=(0,2,0,76,0,5) відповідало б рішенню х належить c2. Ви можете змінити це рішення, встановивши aτ таких, якщо жодна з piτ то рішення х належить до невідомого класу

Що ви робите з тими невідомими , залежить від логіки бізнесу. Якщо вони важливі, ви можете створити пул з них і переучити модель, використовуючи наявні дані. Я думаю, ви можете зробити своєрідне "перенесення навчання" з навченої моделі, змінивши розмірність результату. Але з цим я не стикався, тому просто кажу

Візьміть підрахунок, який SGDClassifierвикористовується SVMпід ним, що не є ймовірнісним алгоритмом. Після SGDClassifierдокументації ви можете змінити lossаргумент modified_huberабо logдля того , щоб отримати імовірнісні висновки.


0

Є два варіанти:

  1. Прогнозуйте шанс даних, що належать до невідомої або unkкатегорії. Будь-які нові категорії, що з’являються у потоці, слід прогнозувати як unk. Це є звичайним для обробки природних мов (NLP), оскільки у потоках слів завжди з’являються нові лексеми слів.

  2. Перевчіть модель щоразу, коли з’являється нова категорія.

Оскільки ви згадуєте SGDClassifier, я припускаю, що ви використовуєте scikit-learn. Scikit-learn не дуже добре підтримує онлайн-навчання. Було б краще переключити рамки, які краще підтримують потокове та онлайн-навчання, наприклад, Spark .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.