之前在自然语言处理技术系列的第一篇NER实战的结语中介绍过:序列标注(分词,NER),文本分类(情感分析),句子关系判断(语意相似判断),句子生成(机器翻译)是NLP领域的四大任务,之后我又陆续简单介绍了情感分析实战,和Seq2Seq生成对联。今天我们来到这个系列的终章篇——语义相似判断。语义相似判断就是判断两个句子是否具有相同的语义,其应用场景多用于问答系统:
- 判断两个问句是否具有相同的语义。
- 判断问题和检索出的答案是否匹配。
当然也可以用于其他场景比如判断两幅图片是否是一样——人脸识别,所以从广义上来说,就是语义相识判断就是判断两个东西是否具有某种相似度的任务。
语义相似判断任务简介
语义相似可以转化为一个分类问题。给模型输入两个句子,然后希望模型判断出两个句子语义是否相似。具体输入输出细节如下:
输入:
- 1.为何我无法申请开通花呗信用卡收款
- 2.支付宝开通信用卡花呗收款不符合条件怎么回事
输出:1
如果输出0表示不相似,输出1表示相似。
语义相似判断算法简介
语义相似还是NLP中的老问题,如何将句子映射到到向量空间中同时保持语义,然后我们就可以通过各种距离去衡量句子的相似程度。
- 上古时期的方式是通过bag of words,tf-idf这种词袋模型去映射句子。
- 之后出现了word2vector技术,我们就可以将一句话中每个词的的词向量求平均来表示句子向量,后面又出现了借用word2vector的思路实现的sen2vector技术。
- 最近比较火的当然就是使用深度学习技术将句子映射到向量空间。
这里我简要的介绍一下我在实战中使用的深度学习模型Siamese Network架构。
Siamese在英语中是“孪生”、“连体”的意思,而Siamese Network,如下图所示就是两个共享参数的神经网络,其中神经网络部分可以任意构建,使用CNN,RNN之类的都可以。通过同一网络将两个输入映射到向量空间,然后在去计算它们的相似性,相似性度量可以通过: - 欧式距离,
- 余弦距离,
-
或者给数据打上标签,通过神经网络去判断相似性等。
本次实战就是采用的Siamese-BiLSTM,意思就是用双向的LSTM做共享参数的network部分。
蚂蚁金服大赛实战
读入数据
数据格式如下,每条数据都是两个句子和一个标签。
执行下方代码读入数据。
import pandas as pd
train_data = pd.read_csv("./huabei/train.csv",encoding="utf-8",header=None,sep="\t")
val_data = pd.read_csv("./huabei/val.csv",encoding="utf-8",header=None,sep="\t")
train_data[1] = train_data[1].apply(lambda x : [char for char in x])
train_data[2] = train_data[2].apply(lambda x : [char for char in x])
train_data
生成字典
执行下方代码,为后续的文本转ID构建一个字典。
from itertools import chain
train_data_1 = list(chain.from_iterable(train_data[1]))
train_data_2 = list(chain.from_iterable(train_data[2]))
all_words = set(train_data_1 + train_data_2)
print(len(all_words))
vocab = { j:i+1 for i, j in enumerate(all_words)}
vocab["unk"] = 0
数据预处理
做一些简单的文本转ID,然后padding,之后方便喂给模型。
from keras.preprocessing.sequence import pad_sequences
import numpy as np
train_data[1] = train_data[1].apply(lambda x:[vocab.get(i,0) for i in x])
train_data[2] = train_data[2].apply(lambda x:[vocab.get(i,0) for i in x])
Sens_1 = pad_sequences(train_data[1],maxlen=100)
Sens_2 = pad_sequences(train_data[2],maxlen=100)
labels = np.array(train_data[3])
labels = labels.reshape(*labels.shape,1)
构建模型
下面定义了一个构建模型的function。
def SiameseBiLSTM(vocab,max_length):
K.clear_session()
embedding = Embedding(input_dim = len(vocab),output_dim = 200, input_length=max_length)
bilstm = Bidirectional(LSTM(128))
sequence_input1 = Input(shape=(max_length,))
embedded_sequences_1 = embedding(sequence_input1)
x1 = bilstm(embedded_sequences_1)
sequence_input2 = Input(shape=(max_length,))
embedded_sequences_2 = embedding(sequence_input2)
x2 = bilstm(embedded_sequences_2)
merged = concatenate([x1, x2])
merged = BatchNormalization()(merged)
merged = Dropout(0.5)(merged)
merged = Dense(100, activation="relu")(merged)
merged = BatchNormalization()(merged)
merged = Dropout(0.5)(merged)
preds = Dense(1, activation='sigmoid')(merged)
model = Model(inputs=[sequence_input1,sequence_input2], outputs=preds)
model.compile(loss='binary_crossentropy', optimizer='adam')
model.summary()
return model
model = SiameseBiLSTM(vocab,100)
执行上方代码,从Keras的模型架构可视化输出可以清楚的看到embedding_1和bidirectional_1这辆层会被两个输入共享。
其架构示意图如下,两个输入通过BiLSTM编码成两个向量之后,直接将他们拼接一下,喂给下游的全连结层去做相识度判断。
训练模型
model.fit([Sens_1,Sens_2],labels,batch_size=32,epochs=5,validation_split=0.2)
构建完模型后,将要判断相似度的句子,和标签喂给模型,定义好,batch_size,和训练轮数epoch,就让它跑起来,其模型训练过程如下图。
模型预测
model.predict([sen_pre_1,sen_pre_2])
通过上述代码就可以进行预测了,sen_pre_1和sen_pre_2是经过数据预处理,padding后的100维向量,模型会输出一个(0,1)之间的值,你可以定义一个阈值将这个值转换为[0,1]标签。
结语
至此,四大任务的自然语言处理(NLP)技术实战全部完结。任务看起来都很有趣,keras用起来也简单易上手,baseline的构建也不是很难。但是想要做好,还有大量工作要做。
这四个任务,还有很多可以优化的地方,比如
- 在输入部分,我很少预训练词向量(除了情感分析实战),对于模型的输入我也是以字为单位,要知道中文以词为单位时其表达才准确;
- 在模型上的使用,我基本上全用的LSTM或者GRU,没有加任何attention,也没使用更复杂的网络;(需要扎实的领域知识和脑洞)
- 模型训练部分,超参数的调优和模型的evaluation我基本上没做。(极其耗时耗力)
四个任务的flow都不是特别完美。笔者希望通过这个系列让大家感受到深度学习做NLP的乐趣。若想成为大神,从好奇,喜爱开始,然后苦读论文,多思考,勤做实验,多创新,这个过程就很枯燥了,坚持下去,才能有所建树。(后会有期)
参考:
https://github.com/amansrivastava17/lstm-siamese-text-similarity
https://www.jianshu.com/p/92d7f6eaacf5