Nếu các danh mục mới rất hiếm khi xuất hiện, bản thân tôi thích giải pháp "một so với tất cả" được cung cấp bởi @oW_ . Đối với mỗi danh mục mới, bạn huấn luyện một mô hình mới về số lượng mẫu X từ danh mục mới (loại 1) và số lượng mẫu X từ các loại còn lại (loại 0).
Tuy nhiên, nếu các danh mục mới đến thường xuyên và bạn muốn sử dụng một mô hình được chia sẻ duy nhất , có một cách để thực hiện điều này bằng cách sử dụng các mạng thần kinh.
Tóm lại, khi xuất hiện một danh mục mới, chúng tôi thêm một nút mới tương ứng vào lớp softmax với trọng số bằng không (hoặc ngẫu nhiên) và giữ nguyên trọng số cũ, sau đó chúng tôi huấn luyện mô hình mở rộng với dữ liệu mới. Dưới đây là một bản phác thảo trực quan cho ý tưởng (được vẽ bởi chính tôi):
Đây là một triển khai cho kịch bản hoàn chỉnh:
Mô hình được đào tạo trên hai loại,
Một danh mục mới đến,
Các định dạng mô hình và đích được cập nhật tương ứng,
Mô hình được đào tạo về dữ liệu mới.
Mã số:
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))
đầu ra nào:
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
Tôi nên giải thích hai điểm liên quan đến đầu ra này:
Hiệu suất mô hình bị giảm từ 0.9275
đến 0.8925
chỉ bằng cách thêm một nút mới. Điều này là do đầu ra của nút mới cũng được đưa vào để lựa chọn danh mục. Trong thực tế, đầu ra của nút mới chỉ được đưa vào sau khi mô hình được đào tạo trên một mẫu khá lớn. Ví dụ, chúng ta nên đạt đỉnh lớn nhất trong hai mục đầu tiên [0.15, 0.30, 0.55]
, tức là lớp 2, ở giai đoạn này.
Hiệu suất của mô hình mở rộng trên hai loại (cũ) 0.88
ít hơn mô hình cũ 0.9275
. Điều này là bình thường, bởi vì bây giờ mô hình mở rộng muốn gán đầu vào cho một trong ba loại thay vì hai. Mức giảm này cũng được dự kiến khi chúng ta chọn từ ba phân loại nhị phân so với hai phân loại nhị phân theo cách tiếp cận "một so với tất cả".