Сиамская сеть с LSTM для сходства предложений в Керасе дает периодически один и тот же результат
Я новичок в Керасе и пытаюсь решить задачу сходства предложений, используя NN в Керасе.
Я использую word2vec в качестве встраивания слов, а затем сиамскую сеть, чтобы предсказать, насколько похожи два предложения.
Базовая сеть для сиамской сети - это LSTM,и для объединения двух базовых сетей я использую лямбда-слой с косинусной метрикой similairty.
В качестве набора данных я использую SICK dataset, который дает оценку каждой паре предложений, от 1(разные) до 5(очень похожие).
Я создал сеть и так работает, но у меня много сомнений :
во-первых, я не уверен, что способ, которым я кормлю LSTM предложениями, хорош. Я беру встраивание word2vec для каждого слова и создаю только один массив на предложение, заполняя его нулями в seq_len, чтобы получить массивы одинаковой длины. А затем я изменяю его следующим образом: data_A = embedding_A.reshape((len(embedding_A), seq_len, feature_dim))
Кроме того, я не уверен, что моя Сиамская сеть верна, потому что много предсказаний для разных пар равны, и потеря не сильно меняется (от 0.3300 до 0,2105 за 10 эпох, и она не меняется намного больше за 100 эпох).
Кто-нибудь может помочь мне найти и понять мои ошибки?
Большое спасибо (и извините за мой плохой английский)
Заинтересованная часть в моем коде
def cosine_distance(vecs):
#I'm not sure about this function too
y_true, y_pred = vecs
y_true = K.l2_normalize(y_true, axis=-1)
y_pred = K.l2_normalize(y_pred, axis=-1)
return K.mean(1 - K.sum((y_true * y_pred), axis=-1))
def cosine_dist_output_shape(shapes):
shape1, shape2 = shapes
print((shape1[0], 1))
return (shape1[0], 1)
def contrastive_loss(y_true, y_pred):
margin = 1
return K.mean(y_true * K.square(y_pred) + (1 - y_true) * K.square(K.maximum(margin - y_pred, 0)))
def create_base_network(feature_dim,seq_len):
model = Sequential()
model.add(LSTM(100, batch_input_shape=(1,seq_len,feature_dim),return_sequences=True))
model.add(Dense(50, activation='relu'))
model.add(Dense(10, activation='relu'))
return model
def siamese(feature_dim,seq_len, epochs, tr_dataA, tr_dataB, tr_y, te_dataA, te_dataB, te_y):
base_network = create_base_network(feature_dim,seq_len)
input_a = Input(shape=(seq_len,feature_dim,))
input_b = Input(shape=(seq_len,feature_dim))
processed_a = base_network(input_a)
processed_b = base_network(input_b)
distance = Lambda(cosine_distance, output_shape=cosine_dist_output_shape)([processed_a, processed_b])
model = Model([input_a, input_b], distance)
adam = Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
model.compile(optimizer=adam, loss=contrastive_loss)
model.fit([tr_dataA, tr_dataB], tr_y,
batch_size=128,
epochs=epochs,
validation_data=([te_dataA, te_dataB], te_y))
pred = model.predict([tr_dataA, tr_dataB])
tr_acc = compute_accuracy(pred, tr_y)
for i in range(len(pred)):
print (pred[i], tr_y[i])
return model
def padding(max_len, embedding):
for i in range(len(embedding)):
padding = np.zeros(max_len-embedding[i].shape[0])
embedding[i] = np.concatenate((embedding[i], padding))
embedding = np.array(embedding)
return embedding
def getAB(sentences_A,sentences_B, feature_dim, word2idx, idx2word, weights,max_len_def=0):
#from_sentence_to_array : function that transforms natural language sentences
#into vectors of real numbers. Each word is replaced with the corrisponding word2vec
#embedding, and words that aren't in the embedding are replaced with zeros vector.
embedding_A, max_len_A = from_sentence_to_array(sentences_A,word2idx, idx2word, weights)
embedding_B, max_len_B = from_sentence_to_array(sentences_B,word2idx, idx2word, weights)
max_len = max(max_len_A, max_len_B,max_len_def*feature_dim)
#padding to max_len
embedding_A = padding(max_len, embedding_A)
embedding_B = padding(max_len, embedding_B)
seq_len = int(max_len/feature_dim)
print(seq_len)
#rashape
data_A = embedding_A.reshape((len(embedding_A), seq_len, feature_dim))
data_B = embedding_B.reshape((len(embedding_B), seq_len, feature_dim))
print('A,B shape: ',data_A.shape, data_B.shape)
return data_A, data_B, seq_len
FEATURE_DIMENSION = 100
MIN_COUNT = 10
WINDOW = 5
if __name__ == '__main__':
data = pd.read_csv('data\train.csv', sep='t')
sentences_A = data['sentence_A']
sentences_B = data['sentence_B']
tr_y = 1- data['relatedness_score']/5
if not (os.path.exists(EMBEDDING_PATH) and os.path.exists(VOCAB_PATH)):
create_embeddings(embeddings_path=EMBEDDING_PATH, vocab_path=VOCAB_PATH, size=FEATURE_DIMENSION, min_count=MIN_COUNT, window=WINDOW, sg=1, iter=25)
word2idx, idx2word, weights = load_vocab_and_weights(VOCAB_PATH,EMBEDDING_PATH)
tr_dataA, tr_dataB, seq_len = getAB(sentences_A,sentences_B, FEATURE_DIMENSION,word2idx, idx2word, weights)
test = pd.read_csv('data\test.csv', sep='t')
test_sentences_A = test['sentence_A']
test_sentences_B = test['sentence_B']
te_y = 1- test['relatedness_score']/5
te_dataA, te_dataB, seq_len = getAB(test_sentences_A,test_sentences_B, FEATURE_DIMENSION,word2idx, idx2word, weights, seq_len)
model = siamese(FEATURE_DIMENSION, seq_len, 10, tr_dataA, tr_dataB, tr_y, te_dataA, te_dataB, te_y)
test_a = ['this is my dog']
test_b = ['this dog is mine']
a,b,seq_len = getAB(test_a,test_b, FEATURE_DIMENSION,word2idx, idx2word, weights, seq_len)
prediction = model.predict([a, b])
print(prediction)
Некоторые результаты :
my prediction | true label
0.849908 0.8
0.849908 0.8
0.849908 0.74
0.849908 0.76
0.849908 0.66
0.849908 0.72
0.849908 0.64
0.849908 0.8
0.849908 0.78
0.849908 0.8
0.849908 0.8
0.849908 0.8
0.849908 0.8
0.849908 0.74
0.849908 0.8
0.849908 0.8
0.849908 0.8
0.849908 0.66
0.849908 0.8
0.849908 0.66
0.849908 0.56
0.849908 0.8
0.849908 0.8
0.849908 0.76
0.847546 0.78
0.847546 0.8
0.847546 0.74
0.847546 0.76
0.847546 0.72
0.847546 0.8
0.847546 0.78
0.847546 0.8
0.847546 0.72
0.847546 0.8
0.847546 0.8
0.847546 0.78
0.847546 0.8
0.847546 0.78
0.847546 0.78
0.847546 0.46
0.847546 0.72
0.847546 0.8
0.847546 0.76
0.847546 0.8
0.847546 0.8
0.847546 0.8
0.847546 0.8
0.847546 0.74
0.847546 0.8
0.847546 0.72
0.847546 0.68
0.847546 0.56
0.847546 0.8
0.847546 0.78
0.847546 0.78
0.847546 0.8
0.852975 0.64
0.852975 0.78
0.852975 0.8
0.852975 0.8
0.852975 0.44
0.852975 0.72
0.852975 0.8
0.852975 0.8
0.852975 0.76
0.852975 0.8
0.852975 0.8
0.852975 0.8
0.852975 0.78
0.852975 0.8
0.852975 0.8
0.852975 0.78
0.852975 0.8
0.852975 0.8
0.852975 0.76
0.852975 0.8
2 ответов:
Вы видите последовательные равные значения, потому что форма вывода функции
cosine_distanceневерна. Когда вы беретеK.mean(...)без аргументаaxis, результат будет скалярным. Чтобы исправить это, просто используйтеK.mean(..., axis=-1)вcosine_distanceдля заменыK.mean(...).Более Подробное Объяснение:
При вызове
model.predict()выходной массивpredсначала предварительно выделяется, а затем заполняется пакетными предсказаниями. Из исходного кода training.py :if batch_index == 0: # Pre-allocate the results arrays. for batch_out in batch_outs: shape = (num_samples,) + batch_out.shape[1:] outs.append(np.zeros(shape, dtype=batch_out.dtype)) for i, batch_out in enumerate(batch_outs): outs[i][batch_start:batch_end] = batch_outВ вашем случае у вас есть только один вывод, так что
pred- это простоouts[0]в коде выше. Когдаbatch_outявляется скаляром (например, 0.847546, как видно из ваших результатов), приведенный выше код эквивалентенpred[batch_start:batch_end] = 0.847576. Поскольку размер пакета по умолчанию равен 32 дляmodel.predict(), вы можете увидеть, что в опубликованном результате появляются 32 последовательных значения 0.847576.
Другая, возможно, более серьезная проблема заключается в том, что ярлыки неверны. Вы преобразуете оценку связанности в метки с помощьюtr_y = 1- data['relatedness_score']/5. Теперь, если два предложения "очень похожи", оценка родства равна 5, поэтомуtr_yявляется 0 для этих двух предложений. Однако в случае контрастивной потери, когдаy_trueравно нулю, терминK.maximum(margin - y_pred, 0)фактически означает, что "эти два предложения должны иметь косинусное расстояние>= margin". Это противоположно тому, что вы хотите, чтобы ваша модель изучала (также я не думаю, что вам нужноK.squareв потере).
Просто чтобы это было зафиксировано в ответе где-то (я вижу это в комментариях принятого ответа), ваша функция контрастивных потерь должна быть:
loss = K.mean((1 - y) * k.square(d) + y * K.square(K.maximum(margin - d, 0)))Ваши
(1 - y) * ...иy * ...перепутались, что может сбить с толку людей, которые используют ваш пример в качестве отправной точки. В остальном это отличная отправная точка.Примечание по номенклатуре: Вы использовали
y_trueиy_predвместоyиd. Я используюyиd, потому чтоy- это ваши метки, которые должны быть либо 0, либо 1, ноdне обязательно находится в этом же диапазоне (dна самом деле находится между 0 и 2 для косинусного расстояния). На самом деле это непредсказание значенияy. Вы просто хотите минимизировать свою меру расстоянияd, Когда два входа похожи, и максимизировать ее (или выталкивать ее за пределы вашего поля), когда они отличаются. В основном контрастивная потеря не пытается получитьd, чтобы предсказатьy, просто пытается получитьd, чтобы быть маленьким, когда то же самое, большим, когда другое.
Comments