SGDClassifier : 이전에 알려지지 않은 레이블이있는 온라인 학습 / partial_fit


9

내 훈련 세트에는 초기 학습에 사용되는 약 50 만 개의 항목이 포함되어 있습니다. 매주 ~ 5k 개의 항목이 추가됩니다. 그러나 동일한 양이 "사라집니다"(시간이 지나면 삭제해야하는 사용자 데이터이므로).

따라서 나중에 전체 데이터 세트에 액세스 할 수 없으므로 온라인 학습을 사용합니다. 현재 SGDClassifier작동 하는 것을 사용하고 있지만 큰 문제는 새로운 범주가 나타나고 초기에 없었기 때문에 더 이상 모델을 사용할 수 없다는 것 fit입니다.

SGDClassifier다른 모델 과 다른 방법이 있습니까? 딥 러닝?

NOW를 처음부터 시작해야하는지 (즉 SGDClassifier, 이외의 것을 사용해야하더라도) 중요하지 않지만 새로운 레이블로 온라인 학습을 가능하게하는 무언가가 필요합니다.


1
새로운 카테고리가 있다고 말할 때 외인성 변수의 새로운 카테고리에 대해 이야기하고 있습니까?Y) 또는 내인성 변수 (X)?
Juan Esteban de la Calle

답변:


9

새 레이블 카테고리가 나타날 때마다 모델 재교육을 시작하고 싶지 않은 것 같습니다. 과거 데이터의 최대 정보를 유지 하는 가장 쉬운 방법은 카테고리 당 하나의 분류기를 훈련시키는 것입니다.

이런 식으로 재 분류 할 SGDClassifier필요없이 같은 방식으로 각 분류기를 점차적으로 ( "온라인") 훈련 할 수 있습니다. 새 범주가 나타날 때마다 해당 범주에 대한 새로운 이진 분류기를 추가합니다. 그런 다음 분류 자 ​​세트 중에서 확률 / 점수가 가장 높은 클래스를 선택합니다.

scikit's SDGClassifier이미 여러 개의 "One vs All"분류자를 조정하여 멀티 클래스 시나리오를 처리 하기 때문에 이는 현재 수행중인 작업과 크게 다르지 않습니다 .

많은 새로운 카테고리가 계속 등장한다면,이 접근법은 관리하기가 약간 까다로울 수 있습니다.


1
영리한! 이 방법은 warm_start옵션 이있는 다른 scikit 분류 기와도 잘 작동 할 수 있습니다 .
Simon Larsson

5

새로운 카테고리가 거의 도착하지 않는다면 @oW_에서 제공하는 "one vs all"솔루션을 선호합니다 . 각각의 새 범주에 대해 새 범주의 X 샘플 수 (클래스 1)와 나머지 범주의 X 샘플 수 (클래스 0)에 대해 새 모델을 학습합니다.

그러나 새 범주가 자주 도착 하고 단일 공유 모델 을 사용하려는 경우 신경망을 사용하여이를 수행 할 수있는 방법이 있습니다.

요약하면, 새 범주가 도착하면 가중치가 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 대 전체"접근 방식에서 2 개의 이진 분류기와 비교하여 3 개의 이진 분류기 중에서 선택할 때도 이러한 감소가 예상됩니다.


1

이 주제에 관한 문헌을 찾지 못했다고 말해야합니다. 내가 아는 한, 당신이 묻는 것은 불가능합니다. 이것을 알고 있어야하며 제품 소유자도 알고 있어야합니다. 그 이유는 손실 함수가 알려진 레이블에 의존하기 때문에 학습 데이터에없는 레이블을 예측할 수있는 방법이 없기 때문입니다. 또한 머신 러닝 알고리즘이 훈련되지 않은 것을 예측할 수있는 공상 과학

그렇게 말하면 해결 방법이있을 수 있다고 생각합니다 (이것은 공식 문헌을 기반으로하지 않는 의견임을 지적하겠습니다). 분류 기가 확률 론적이면 출력은 각 클래스가 참일 확률이고, 결정은 더 높은 확률입니다. 모든 확률이 해당 임계 값 미만인 경우 모형에서 "알 수 없음"을 예측하도록 해당 확률에 대한 임계 값을 설정할 수 있습니다. 예를 들어 보겠습니다.

허락하다 M(x) 다음과 같은 모델이어야합니다. x, 결정 여부 x 세 가지 범주 중 하나에 속합니다 c1,c2,c3. 의 출력M 확률의 벡터입니다 p. 결정은 가장 높은 조사를 통해 이루어집니다p. 그래서 출력M(x)=p(x)=(0.2,0.76,0.5) 결정에 해당합니다 x 에 속한다 c2. A를 설정하여이 결정을 수정할 수 있습니다τ 그렇지 않은 경우 piτ 그때 결정은 x 미지의 클래스에 속한다

알 수없는 것들로 무엇을합니까 탈취 논리에 달려 있습니다. 중요한 경우 풀을 생성하고 사용 가능한 데이터를 사용하여 모델을 다시 학습 할 수 있습니다. 출력의 차원을 변경하여 훈련 된 모델에서 일종의 "전송 학습"을 수행 할 수 있다고 생각합니다. 그러나 이것은 내가 직면하지 않은 것이므로 그냥 말하고 있습니다.

확률 알고리즘 이 아닌 아래 를 SGDClassifier사용 하는 것을 세어 보세요. 다음 문서 당신은 수정할 수 에 인수 또는 확률 출력을 얻기 위해.SVMSGDClassifierlossmodified_huberlog


0

두 가지 옵션이 있습니다.

  1. 알 수없는 또는 unk범주에 속하는 데이터 요소의 확률을 예측합니다 . 스트림에 나타나는 새로운 카테고리는로 예측해야합니다 unk. 단어 스트림에는 항상 새로운 단어 토큰이 표시되므로 NLP (자연어 처리)에서 일반적입니다.

  2. 새 카테고리가 나타날 때마다 모델을 재교육하십시오.

을 언급 SGDClassifier했으므로 scikit-learn을 사용한다고 가정합니다. Scikit-learn은 온라인 학습을 잘 지원하지 않습니다. Spark 와 같은 스트리밍 및 온라인 학습을 더 잘 지원하는 프레임 워크를 전환하는 것이 좋습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.