SGDClassifier:以前は未知のラベルを使用したオンライン学習/ partial_fit


9

私のトレーニングセットには、約5万のエントリが含まれています。毎週、5,000エントリが追加されます。しかし、同じ量が「消えます」(しばらくしてから削除する必要のあるユーザーデータであるため)。

したがって、後で完全なデータセットにアクセスできないため、オンライン学習を使用します。現在は機能するを使用してSGDClassifierいますが、大きな問題があります。新しいカテゴリが表示され、最初のモデルにはなかったため、モデルを使用できなくなりましたfit

SGDClassifierまたは他のモデルで方法はありますか?ディープラーニング?

今からゼロから始めなければならないかどうか(つまりSGDClassifier、以外のものを使用するかどうか)は関係ありませんが、新しいラベルでオンライン学習を可能にするものが必要です。


1
新しいカテゴリがあると言うとき、外生変数()または内生変数(X)の新しいカテゴリについて話していますか?YX
ファン

回答:


9

新しいラベルカテゴリが表示されるたびにモデルの再トレーニングを開始したくないようです。最も簡単な方法は、カテゴリごとに、列車1つの分類器となり、過去のデータの最大の情報を保持します。

このようSGDClassifierにして、再分類する必要なしに、各分類子を段階的に(「オンライン」で)トレーニングし続けることができます。 新しいカテゴリが表示されるたびに、そのカテゴリのみの新しいバイナリ分類子を追加します。次に、分類子のセットの中で確率/スコアが最も高いクラスを選択します。

これは、今日実行しているものと大差ありません。これは、複数の「1つ対すべて」の分類子を内部で適合させることにより、マルチクラスシナリオをscikit's SDGClassifierすでに処理しているためです。

もちろん、多くの新しいカテゴリが登場し続ける場合、このアプローチは管理が少し難しいかもしれません。


1
賢い!この方法は、warm_startオプションがある他のscikit分類器でもうまく機能する可能性があります。
Simon Larsson

5

新しいカテゴリがめったに届かない場合、私自身は@oW_によって提供される「1つ対すべて」のソリューションを好みます。新しいカテゴリごとに、新しいモデルからのX個のサンプル(クラス1)と、残りのカテゴリからのX個のサンプル(クラス0)で新しいモデルをトレーニングします。

ただし、新しいカテゴリが頻繁に到着し、単一の共有モデルを使用する場合は、ニューラルネットワークを使用してこれを実現する方法があります。

要約すると、新しいカテゴリが到着したら、対応する新しいノードをゼロ(またはランダム)の重みを持つsoftmaxレイヤーに追加し、古い重みをそのままにして、新しいデータで拡張モデルをトレーニングします。これは、アイデアのビジュアルスケッチです(自分で描いたもの)。

以下は、完全なシナリオの実装です。

  1. モデルは2つのカテゴリでトレーニングされます。

  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

この出力に関する2つのポイントを説明します。

  1. 新しいノードを追加するだけで、モデルのパフォーマンスがから0.9275に低下し0.8925ます。これは、新しいノードの出力もカテゴリ選択に含まれるためです。実際には、新しいノードの出力は、モデルがかなりのサンプルでトレーニングされた後にのみ含める必要があります。たとえば[0.15, 0.30, 0.55]、この段階で、最初の2つのエントリのうち最大のもの、つまり2番目のクラスをピークにする必要があります。

  2. 2つの(古い)カテゴリでの拡張モデルのパフォーマンスは0.88、古いモデルよりも低くなり0.9275ます。これは正常な動作です。これは、拡張モデルが入力を2つではなく3つのカテゴリのいずれかに割り当てたいためです。この減少は、「1つまたはすべて」のアプローチで2つのバイナリ分類子と比較して3つのバイナリ分類子から選択した場合にも予想されます。


1

私はこのトピックに関する文献を見つけていないと言わざるを得ません。私の知る限り、あなたが尋ねることは不可能です。あなたはこれを知っているべきであり、そして製品の所有者もそうであるはずです。その理由は、損失関数は既知のラベルに依存しているため、トレーニングデータにないラベルを予測することはできないためです。また、機械学習アルゴリズムは訓練されていないものを予測できるというサイエンスフィクションです

そうは言っても、回避策はあると思います(これは正式な文献に基づいていない意見であることを指摘しておきます)。分類子が確率的である場合、出力は各クラスが真である確率であり、決定はより高い確率です。おそらく、すべての確率がそのしきい値を下回る場合にモデルが「不明」を予測するように、その確率のしきい値を設定できます。例を挙げましょう。

M(x)xxc1,c2,c3MppM(x)=p(x)=(0.2,0.76,0.5)xc2τpiτx

これらのunknownをどうするかは、ビジネスロジックに依存します。それらが重要な場合は、それらのプールを作成し、使用可能なデータを使用してモデルを再トレーニングできます。出力の次元を変更することで、トレーニング済みモデルから一種の「転移学習」を実行できると思います。しかし、これは私が直面していないことなので、私は言っています

確率的アルゴリズムではない、下をSGDClassifier使用するカウントを採用しSVMます。ドキュメンテーションに従って、確率的出力を取得するために、またはそのために引数を変更できます。SGDClassifierlossmodified_huberlog


0

2つのオプションがあります。

  1. 不明またはunkカテゴリに属するデータポイントの確率を予測します。ストリームに表示される新しいカテゴリはすべてとして予測されunkます。ワードストリームには常に新しいワードトークンが現れるため、これは自然言語処理(NLP)で一般的です。

  2. 新しいカテゴリが表示されるたびにモデルを再トレーニングします。

あなたが言及したのでSGDClassifier、私はあなたがscikit-learnを使用していると思います。Scikit-learnはオンライン学習をあまりサポートしていません。Sparkなど、ストリーミングとオンライン学習をより適切にサポートするフレームワークに切り替えることをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.