Сиамская сеть с 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
631   2  

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

    Ничего не найдено.