您的位置:首页 > 其它

[[NLP]基于Simaese LSTM的句子相似度计算

2017-11-02 17:13 375 查看
句子相似度计算在问答系统以及客服机器人当中应用比较频繁,比对针对对话模型中比较频繁的问句可以先进行过滤,之后再进行对话理解。在文本检测方面也有所应用,比如作家写作风格的检测。

本文叙述的句子相似度计算方法来自论文Siamese Recurrent Architectures for Learning Sentence Similarity,论文是基于Simaese LSTM网络对成对相似句进行训练,通过encode得到句子的向量表示之后计算曼哈顿距离(0-1之间)。

模型结构如下:





从上图可以看到模型分为a、b两个LSTM网络,分别对待测的两个句子进行encode得到待测句子的表示向量,最后将得到的两个向量进行距离计算,这里采用的是曼哈顿距离。

可以看到其实模型很简单,下面本文就尝试着实现一个这样的模型。

语料预处理

本文使用的数据都是编造的,长下面这样:

你们这个卡的号码要咋看
这个号码是经常变动的么
那下次再用号码会变吗
手机会显示电话号码吗
我这个卡的号码在哪里看
我怎么查我的卡的号码
怎么才能知道电话号码是什么呢
怎么看卡的电话号码
卡的电话号码应该怎么看呢
卡的电话号码在哪里可以查到呢
在哪里可以看到电话号码
卡的电话号码如何查询
卡的电话号码怎么才能知道
我买的电话卡号码怎么知道
我怎么知道我电话号码是什么
卡身上可以找到号码吗
sim卡上有号码吗
咋查手机号啊
别人要打我手机但我怎么查手机号多少啊
如何查看我这张卡的号码呀
怎么查询卡的号码
如何知道电话卡的号码
香港电话卡的号码应该怎么知道呢
香港电话卡的号码怎么被知道


然后对他们进行两两自由组合,组合的结果如下所示:

你们这个卡的号码要咋看 这个号码是经常变动的么
你们这个卡的号码要咋看 那下次再用号码会变吗
你们这个卡的号码要咋看 手机会显示电话号码吗
你们这个卡的号码要咋看 我这个卡的号码在哪里看
你们这个卡的号码要咋看 我怎么查我的卡的号码
你们这个卡的号码要咋看 怎么才能知道电话号码是什么呢
你们这个卡的号码要咋看 怎么看卡的电话号码
你们这个卡的号码要咋看 卡的电话号码应该怎么看呢
你们这个卡的号码要咋看 卡的电话号码在哪里可以查到呢
....


得到语料之后,下一步的结果就是构造词典,单词索引。

下面结合具体的代码进行解释:

path='./data/qa_test.txt'#数据的路径
path_word2vec='/home/ruben/data/nlp/word2vec_wx'#word2vec路径
#造数据
fake_data=open(path,'r').readlines()
tain_data_l=[]
tain_data_r=[]
for line in fake_data:
for line2 in fake_data:
if(line is not line2):
print(line.replace('\n',''),line2.replace('\n',''))
tain_data_l.append(line.replace('\n',''))
tain_data_r.append(line2.replace('\n',''))
print('left length:',len(tain_data_l))
print('right length:',len(tain_data_r))
import jieba
#构造字典和weight矩阵
list_word=['UNK']
dict_word={}
tain_data_l_n=[]#左边LSTM的输入
tain_data_r_n=[]#右边LSTM的输入

for data in [tain_data_l,tain_data_r]:
for line in data:
words=list(jieba.cut(line))
for i,word in enumerate(words):
if word not in dict_word:
dict_word[word]=len(dict_word)
print(dict_word)#字典构造完毕
id2w={dict_word[w]:w for w in dict_word}#word的索引
embedding_size=256
embedding_arry=np.random.randn(len(dict_word)+1,embedding_size)#句子embedding矩阵
embedding_arry[0]=0
word2vector=KeyedVectors.load(path_word2vec)
for index,word in enumerate(dict_word):
if word in word2vector.wv.vocab:
embedding_arry[index]=word2vector.wv.word_vec(word)
print('embedding_arry shape:',embedding_arry.shape)
del word2vector
#将词组替换为索引
for line in tain_data_l:
words = list(jieba.cut(line))
for i,word in enumerate(words):
words[i]=dict_word[word]
tain_data_l_n.append(words)
print('tain_data_l_n length:',len(tain_data_l_n))
y_train=np.ones((len(tain_data_l_n),))
for line in tain_data_r:
words = list(jieba.cut(line))
for i,word in enumerate(words):
words[i]=dict_word[word]
tain_data_r_n.append(words)
print('tain_data_r_n length:',len(tain_data_r_n))
#得到语料中句子的最大长度
max_length=0
for line in tain_data_r_n:
if max_length<len(line):
max_length=len(line)
print('max length:',max_length)

# 对齐语料中句子的长度
tain_data_l_n = pad_sequences(tain_data_l_n, maxlen=max_length)
tain_data_r_n = pad_sequences(tain_data_r_n, maxlen=max_length)


对齐之后的语料长下面这样:

tain_data_l_n:[[ 0  0  0 ...,  5  6  7]
[ 0  0  0 ...,  5  6  7]
[ 0  0  0 ...,  5  6  7]
...,
[ 0  0  0 ..., 24 57 27]
[ 0  0  0 ..., 24 57 27]
[ 0  0  0 ..., 24 57 27]]
tain_data_r_n:[[ 0  0  0 ..., 10  3 11]
[ 0  0  0 ...,  4 15 16]
[ 0  0  0 ..., 19 20 16]
...,
[ 0  0  0 ..., 55  3  4]
[ 0  0  0 ..., 38  3  4]
[ 0  0  0 ..., 24 27 29]]


模型构建

#模型参数
n_hidden = 50
gradient_clipping_norm = 1.25
batch_size = 5
n_epoch = 15

#相似度计算
def exponent_neg_manhattan_distance(left, right):
return K.exp(-K.sum(K.abs(left - right), axis=1, keepdims=True))

#输入层
left_input = Input(shape=(max_length,), dtype='int32')
right_input = Input(shape=(max_length,), dtype='int32')
embedding_layer = Embedding(len(embedding_arry), embedding_size, weights=[embedding_arry], input_length=max_length,
trainable=False)

#对句子embedding
encoded_left = embedding_layer(left_input)
encoded_right = embedding_layer(right_input)

#两个LSTM共享参数
shared_lstm = LSTM(n_hidden)

left_output = shared_lstm(encoded_left)
right_output = shared_lstm(encoded_right)

malstm_distance = Merge(mode=lambda x: exponent_neg_manhattan_distance(x[0], x[1]),
output_shape=lambda x: (x[0][0], 1))([left_output, right_output])

# model
malstm = Model([left_input, right_input], [malstm_distance])

optimizer = Adadelta(clipnorm=gradient_clipping_norm)

malstm.compile(loss='mean_squared_error', optimizer=optimizer, metrics=['accuracy'])
#train
malstm.fit(x=[np.asarray(tain_data_l_n), np.asarray(tain_data_r_n)], y=y_train, batch_size=batch_size, epochs=n_epoch,
validation_data=([np.asarray(tain_data_l_n), np.asarray(tain_data_r_n)], y_train) )


预测

print(malstm.predict([np.array([1,2,3,4,5,6,7,7,0,0,0,0]).reshape(-1,12),np.array([1,2,23,4,12,6,7,22,0,0,0,0]).reshape(-1,12)]))


得到的结果:[[ 0.91436553]],从感官上看还是合理的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: