Слой встраивания Keras: сохраняйте нулевые значения как нули - PullRequest
1 голос
/ 27 июня 2019

Я думал о 0-заполнении последовательности слов и о том, как этот 0-отступ затем преобразуется в слой Embedding.На первый взгляд можно подумать, что вы также хотите сохранить вложения = 0.0.Однако слой Embedding в keras генерирует случайные значения для любого входного токена, и нет никакого способа заставить его генерировать 0.0.Обратите внимание, mask_zero делает что-то другое, я уже проверил.

Кто-то может спросить, зачем беспокоиться об этом, кажется, что код работает, даже если вложения не равны 0.0, если ониподобные.Таким образом, я придумал пример, хотя и несколько надуманный, где установка встраивания на 0,0 для токена с нулевой подкладкой имеет значение.

Я использовал набор данных из 20 групп новостей from sklearn.datasets import fetch_20newsgroups.Я делаю минимальную предварительную обработку: удаление знаков препинания, стоп-слов и цифр.Я использую from keras.preprocessing.sequence import pad_sequences для заполнения 0.Я разделил ~ 18 тыс. Постов на набор обучения и валидации с соотношением обучения / валидации = 4/1.Я создаю простую сеть с 1 плотным скрытым слоем, вход которой представляет собой сплющенную последовательность вложений:

    EMBEDDING_DIM = 300
    MAX_SEQUENCE_LENGTH = 1100
    layer_size = 25
    dropout = 0.3
    sequence_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32', name='dnn_input')
    embedding_layer = Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH, name = 'embedding_dnn')
    embedded_sequences = embedding_layer(sequence_input)
    x = Flatten(name='flatten_dnn')(embedded_sequences)
    x = Dense(layer_size, activation='relu', name ='hidden_dense_dnn')(x)
    x = Dropout(dropout, name='dropout')(x)
    preds = Dense(num_labels, activation='softmax', name = 'output_dnn')(x)

    model = Model(sequence_input, preds)
    model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

Модель имеет около 14M обучаемых параметров (этот пример немного надуманный, как я уже упоминал),Когда я тренирую его

    earlystop = EarlyStopping(monitor='val_loss', patience=5)
    history = model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=30, batch_size=BATCH_SIZE, callbacks=[earlystop])

, похоже, что в течение 4 эпох алгоритм пытается найти выход из «случайности»:

Train on 15048 samples, validate on 3798 samples
Epoch 1/30
15048/15048 [==============================] - 58s 4ms/step - loss: 3.1118 - acc: 0.0519 - val_loss: 2.9894 - val_acc: 0.0534
Epoch 2/30
15048/15048 [==============================] - 56s 4ms/step - loss: 2.9820 - acc: 0.0556 - val_loss: 2.9827 - val_acc: 0.0527
Epoch 3/30
15048/15048 [==============================] - 55s 4ms/step - loss: 2.9712 - acc: 0.0626 - val_loss: 2.9718 - val_acc: 0.0579
Epoch 4/30
15048/15048 [==============================] - 55s 4ms/step - loss: 2.9259 - acc: 0.0756 - val_loss: 2.8363 - val_acc: 0.0874
Epoch 5/30
15048/15048 [==============================] - 56s 4ms/step - loss: 2.7092 - acc: 0.1390 - val_loss: 2.3251 - val_acc: 0.2796
...
Epoch 13/30
15048/15048 [==============================] - 56s 4ms/step - loss: 0.0698 - acc: 0.9807 - val_loss: 0.5010 - val_acc: 0.8736

В итоге получается с точностью~ 0,87

print ('Best validation accuracy is ', max(history.history['val_acc']))
Best validation accuracy is  0.874934175379845

Однако, когда я явно устанавливаю вложения для дополненных 0 на 0,0

def myMask(x):
    mask= K.greater(x,0) #will return boolean values
    mask= K.cast(mask, dtype=K.floatx()) 
    return mask
layer_size = 25
dropout = 0.3
sequence_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32', name='dnn_input')
embedding_layer = Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH, name = 'embedding_dnn')
embedded_sequences = embedding_layer(sequence_input)
y = Lambda(myMask, output_shape=(MAX_SEQUENCE_LENGTH,))(sequence_input)
y = Reshape(target_shape=(MAX_SEQUENCE_LENGTH,1))(y)
merge_layer = Multiply(name = 'masked_embedding_dnn')([embedded_sequences,y])
x = Flatten(name='flatten_dnn')(merge_layer)
x = Dense(layer_size, activation='relu', name ='hidden_dense_dnn')(x)
x = Dropout(dropout, name='dropout')(x)
preds = Dense(num_labels, activation='softmax', name = 'output_dnn')(x)

model = Model(sequence_input, preds)
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

, модель с таким же количеством параметров сразу же находит выход из 'randomness ':

Train on 15048 samples, validate on 3798 samples
Epoch 1/30
15048/15048 [==============================] - 64s 4ms/step - loss: 2.4356 - acc: 0.3060 - val_loss: 1.2424 - val_acc: 0.7754
Epoch 2/30
15048/15048 [==============================] - 61s 4ms/step - loss: 0.6973 - acc: 0.8267 - val_loss: 0.5240 - val_acc: 0.8797
...
Epoch 10/30
15048/15048 [==============================] - 61s 4ms/step - loss: 0.0496 - acc: 0.9881 - val_loss: 0.4176 - val_acc: 0.8944

и заканчивается с лучшей точностью ~ 0,9.

Опять же, это несколько надуманный пример, но все же он показывает, что сохранение этих «дополненных» вложений в0.0 может быть полезным.

Я что-то здесь упускаю?И если я ничего не пропускаю, то по какой причине Keras не предоставляет эту функциональность "из коробки"?

ОБНОВЛЕНИЕ

@DanielMöller Я попробовал ваше предложение:

layer_size = 25
dropout = 0.3
init = RandomUniform(minval=0.0001, maxval=0.05, seed=None)
constr = NonNeg()



sequence_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32', name='dnn_input')
embedding_layer = Embedding(len(word_index) + 1, 
                            EMBEDDING_DIM, 
                            input_length=MAX_SEQUENCE_LENGTH, 
                            name = 'embedding_dnn', 
                            embeddings_initializer=init,
                            embeddings_constraint=constr)

embedded_sequences = embedding_layer(sequence_input)
y = Lambda(myMask, output_shape=(MAX_SEQUENCE_LENGTH,))(sequence_input)
y = Reshape(target_shape=(MAX_SEQUENCE_LENGTH,1))(y)
merge_layer = Multiply(name = 'masked_embedding_dnn')([embedded_sequences,y])
x = Flatten(name='flatten_dnn')(merge_layer)
x = Dense(layer_size, activation='relu', name ='hidden_dense_dnn')(x)
x = Dropout(dropout, name='dropout')(x)
preds = Dense(num_labels, activation='softmax', name = 'output_dnn')(x)

model = Model(sequence_input, preds)
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

К сожалению, сеть застряла в «случайности»:

Train on 15197 samples, validate on 3649 samples
Epoch 1/30
15197/15197 [==============================] - 60s 4ms/step - loss: 3.1354 - acc: 0.0505 - val_loss: 2.9943 - val_acc: 0.0496
....
Epoch 24/30
15197/15197 [==============================] - 60s 4ms/step - loss: 2.9905 - acc: 0.0538 - val_loss: 2.9907 - val_acc: 0.0496

Я также попытался без ограничения NonNeg(), тот же результат.

1 Ответ

1 голос
/ 28 июня 2019

Ну, вы исключаете вычисление градиентов весов, связанных с добавленными шагами.

Если у вас слишком много шагов дополнения, то веса вложения относительно значения отступа будут участвовать во многих вычислениях и будут существенно конкурировать с другими весами. Но тренировка этих весов - пустая трата вычислений и, безусловно, будет мешать другими словами.

Учтите также, что, например, некоторые веса для заполнения могут иметь значения между значениями для значимых слов. Таким образом, увеличение веса может сделать его похожим на другое слово, когда это не так. И уменьшается тоже ....

Эти дополнительные вычисления, дополнительные вклады в расчеты потерь и градиентов и т. Д. Создадут больше вычислительных потребностей и больше препятствий. Это как много мусора в середине данных.

Обратите внимание также, что эти нули идут непосредственно к плотному слою, что также устранит градиенты для многих плотных весов. Это может соответствовать более длинным последовательностям, хотя, если их немного по сравнению с более короткими последовательностями.


Из любопытства, что произойдет, если вы сделаете это?

from keras.initializers import RandomUniform
from keras.constraints import NonNeg

init = RandomUniform(minval=0.0001, maxval=0.05, seed=None)
constr = NonNeg()


......
embedding_layer = Embedding(len(word_index) + 1, 
                            EMBEDDING_DIM, 
                            input_length=MAX_SEQUENCE_LENGTH, 
                            name = 'embedding_dnn', 
                            embeddings_initializer=init,
                            embeddings_constraint=constr)
..........
...