最近在研究Siamese模型来进行文本相似度的计算,今天就做一期代码详解,每一行的代码都会做出相应的解释,对于初学LSTM的人来说读懂代码是非常有必要的,Siamese模型就是将两个句子(训练数据)通过embeding层(word2vec)分别输入到LSTM进行运算,并共享参数,利用损失函数不断的进行正向传播和反向传播来优化参数,最终达到成为一个比较优秀的模型
原理就不在细说了,这里主要是把代码解释一下
(1)数据处理部分
首先是数据预处理部分:这里主要分为“读取数据“,“调整数据格式”和“对于batch 的设置”
读取数据
将数据通过pickle工具读取出来,因为pickle序列化和反序列化的工具,可以持久化储存数据格式,将数据读取出来后,分成句子和标签,匹配句子中的每一个单词对应的wor2dvec(词向量)最后返回数据和标签
def load_set(embed, datapath, embed_dim):
#使用pickle读取数据
with open(datapath, 'rb') as f:
mylist = pkl.load(f,encoding="utf-8")
#初始化句子和标签
s0 = []
s1 = []
labels = []
#将句子一句一句的读取出来
for each in mylist:
#句子一
s0x = each[0]
#句子二
s1x = each[1]
#标签
label = each[2]
#将标签转化为float类型的
score = float(label)
#存入标签
labels.append(score)
#将句子中的每一个单词读取出来
for i, ss in enumerate([s0x, s1x]):
#将句子分成一个个的单词
words = word_tokenize(ss)
index = []
for word in words:
#如果单词存在词典中,将该词对应的词向量存入index中
if word in dtr:
index.append(embed[dtr[word]])
#如果单词没有存在词典,而是在embed中,将对应的词向量存入index中
elif word in embed.vocab:
index.append(embed[word])
#否则将该词屏蔽掉(设置为0)
else:
index.append(np.zeros(embed_dim, dtype=float))
#i=0 表示的是s0的数据,将s0的embed存入
if i == 0:
s0.append(np.array(index, dtype=float))
# 1=0 表示的是s1的数据,将s1的embed存入(embed表示的是对应的词向量)
else:
s1.append(np.array(index, dtype=float))
#返回标签和数据
return [s0, s1, labels]
调整数据格式
将数据取出来打散,这是为了提升模型的泛化能力,将数据格式统一,由于句子的长短不一,因此进行特殊化处理,保证是句子数据格式一样,设置最大长度的句子,如果句子的长度小于最大长度,末尾表示0,
# 从文件中读取数据集,并分为训练、验证和测试集合
def load_data(max_len, embed, datapath, embed_dim):
#读取数据
train_set = load_set(embed, datapath, embed_dim)
#将数据分为句子1、句子2和标签
train_set_x1, train_set_x2, train_set_y = train_set
# n_samples表示句数据的个数
n_samples = len(train_set_x1)
# 打散数据集(permutation是最常用的打散数据集的方式,可以保障数据和标签不混乱)
sidx = np.random.permutation(n_samples)
train_set_x1 = [train_set_x1[s] for s in sidx]
train_set_x2 = [train_set_x2[s] for s in sidx]
train_set_y = [train_set_y[s] for s in sidx]
# 打散划分好的训练和测试集
train_set = [train_set_x1, train_set_x2, train_set_y]
# 创建用于存放训练、测试和验证集的矩阵
#max_len 表示句子长度
# embed_dim表示的是词向量的维度
# len(train_set[0])表示的是句子的个数
new_train_set_x1 = np.zeros([len(train_set[0]), max_len, embed_dim], dtype=float)
new_train_set_x2 = np.zeros([len(train_set[0]), max_len, embed_dim], dtype=float)
new_train_set_y = np.zeros(len(train_set[0]), dtype=float)
mask_train_x1 = np.zeros([len(train_set[0]), max_len])
mask_train_x2 = np.zeros([len(train_set[0]), max_len])
#匹配数据矩阵,大于最大长度的删掉,小于的补零
def padding_and_generate_mask(x1, x2, y, new_x1, new_x2, new_y, new_mask_x1, new_mask_x2):
for i, (x1, x2, y) in enumerate(zip(x1, x2, y)):
# whether to remove sentences with length larger than maxlen
#如果句子一小于最大长度
if len(x1) <= max_len:
#前面放数据后面补零
new_x1[i, 0:len(x1)] = x1
# 将句子的最后一个词赋值为1,表示句子的结尾
new_mask_x1[i, len(x1) - 1] = 1
#标签数据不变
new_y[i] = y
else:
#只保留前max-len的值 ::??
new_x1[i, :, :] = (x1[0:max_len:embed_dim])
#最后一个就是句子结尾
new_mask_x1[i, max_len - 1] = 1
#标签数据不用变
new_y[i] = y
#和x1表示的一样
if len(x2) <= max_len:
new_x2[i, 0:len(x2)] = x2
new_mask_x2[i, len(x2) - 1] = 1 # 标记句子的结尾
new_y[i] = y
else:
new_x2[i, :, :] = (x2[0:max_len:embed_dim])
new_mask_x2[i, max_len - 1] = 1
new_y[i