使用句子中出现单词的Vector加权平均进行文本相似度分析虽然简单,但也有比较明显的缺点:没有考虑词序且词向量区别不明确。如下面两个句子:
“北京的首都是中国”与“中国的首都是北京”的相似度为1。
“学习容易”和“学习困难”的相似度很容易也非常高。
为解决这类问题,需要用其他方法对句子进行表示,LSTM是常用的一种方式,本文简单使用单层LSTM对句子重新表示,并通过若干全连接层对句子相似度进行衡量。
数据准备 训练和测试数据包括两个待比较句子以及其相似度(0-1): 测试数据格式相似。
语料编码 自然语言无法直接作为神经网络输入,需进行编码该部分包括以下步骤:
读人训练和测试数据,分词,并给每个词编号。
根据词编号,进一步生成每个句子的编号向量,句子采用固定长度,不足的位置补零。
保存词编号到文件,保存词向量矩阵方便预测使用。
中文分词使用jieba分词工具,词的编号则使用Keras的Tokenizer:
1 2 3 4 5 6 7 8 print("Fit tokenizer...") tokenizer = Tokenizer(num_words=MAX_NB_WORDS, lower=False) tokenizer.fit_on_texts(texts_1 + texts_2 + test_texts_1 + test_texts_2) if save: print("Save tokenizer...") if not os.path.exists(save_path): os.makedirs(save_path) cPickle.dump(tokenizer, open(os.path.join(save_path, tokenizer_name), "wb"))
其中texts_1 、texts_2 、test_texts_1 、 test_texts_2的元素分别为训练数据和测试数据的分词后的列表,如:
经过上面的过程 tokenizer保存了语料中出现过的词的编号映射。
1 2 > print tokenizer.word_index {"我": 2, "是":1, "谁":3}
利用tokenizer对语料中的句子进行编号
1 2 3 > sequences_1 = tokenizer.texts_to_sequences(texts_1) > print sequences_1 [[2 1 3], ...]
最终生成固定长度(假设为10)的句子编号列表
1 2 3 > data_1 = pad_sequences(sequences_1, maxlen=MAX_SEQUENCE_LENGTH) > print data_1 [[0 0 0 0 0 0 0 2 1 3], ...]
data_1即可作为神经网络的输入。
词向量映射 在对句子进行编码后,需要准备句子中词的词向量映射作为LSTM层的输入。这里使用预训练的词向量(这里 )参数,生成词向量映射矩阵:
1 2 3 4 5 6 word2vec = Word2Vec.load(EMBEDDING_FILE) embedding_matrix = np.zeros((nb_words, EMBEDDING_DIM)) for word, i in word_index.items(): if word in word2vec.wv.vocab: embedding_matrix[i] = word2vec.wv.word_vec(word) np.save(embedding_matrix_path, embedding_matrix)
网络结构 该神经网络采用简单的单层LSTM+全连接层对数据进行训练,网络结构图: 网络由Keras实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 大专栏 LSTM 句子相似度分析 def () : embedding_layer = Embedding(nb_words, EMBEDDING_DIM, weights=[embedding_matrix], input_length=MAX_SEQUENCE_LENGTH, trainable=False ) lstm_layer = LSTM(num_lstm, dropout=rate_drop_lstm, recurrent_dropout=rate_drop_lstm) sequence_1_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32' ) embedded_sequences_1 = embedding_layer(sequence_1_input) y1 = lstm_layer(embedded_sequences_1) sequence_2_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32' ) embedded_sequences_2 = embedding_layer(sequence_2_input) y2 = lstm_layer(embedded_sequences_2) merged = concatenate([y1, y2]) merged = Dropout(rate_drop_dense)(merged) merged = BatchNormalization()(merged) merged = Dense(num_dense, activation=act)(merged) merged = Dropout(rate_drop_dense)(merged) merged = BatchNormalization()(merged) preds = Dense(1 , activation='sigmoid' )(merged) model = Model(inputs=[sequence_1_input, sequence_2_input], outputs=preds) model.compile(loss='binary_crossentropy' , optimizer='nadam' , metrics=['acc' ]) model.summary() return model
该部分首先定义embedding_layer作为输入层和LSTM层的映射层,将输入的句子编码映射为词向量列表作为LSTM层的输入。两个LSTM的输出拼接后作为全连接层的输入,经过Dropout和BatchNormalization正则化,最终输出结果进行训练。
训练与预测 训练采用nAdam以及EarlyStopping,保存训练过程中验证集上效果最好的参数。最终对测试集进行预测。
1 2 3 4 5 6 7 8 9 10 11 model = get_model() early_stopping = EarlyStopping(monitor='val_loss', patience=3) bst_model_path = STAMP + '.h5' model_checkpoint = ModelCheckpoint(bst_model_path, save_best_only=True, save_weights_only=True) hist = model.fit([data_1, data_2], labels, validation_data=([val_1, val_2], labels), epochs=100, batch_size=10, shuffle=True, callbacks=[early_stopping, model_checkpoint]) predicts = model.predict([data_1, data_2], batch_size=10, verbose=1) for i in range(len(test_ids)): print "t1: %s, t2: %s, score: %s" % (test_1[i], test_2[i], predicts[i])
小结 该网络在Kaggle Quora数据集val验证可达到80%左右的准确率,应用于中文,由于数据集有限,产生了较大的过拟合。此外在Tokenizer.fit_on_texts应用于中文时,不支持Unicode编码,可以对其源码方法进行重写,加入Ascii字符和Unicode的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ''' this part is solve keras.preprocessing.text can not process unicode ''' def text_to_word_sequence(text, filters='!"#$%&()*+,-./:;<=>?@[]^_`{|}~tn', lower=True, split=" "): if lower: text = text.lower() if type(text) == unicode: translate_table = {ord(c): ord(t) for c, t in zip(filters, split * len(filters))} else: translate_table = keras.maketrans(filters, split * len(filters)) text = text.translate(translate_table) seq = text.split(split) return [i for i in seq if i] keras.preprocessing.text.text_to_word_sequence = text_to_word_sequence
更多关注公众号: