minushuang 2018-08-19
在开始阅读本篇之前,希望你已经看过cnn-text-classification-tf,使用CNN做文本分类项目,start两千多。因为很经典,网上的解读也随处可见,因此就不介绍。但是看了这个项目,可以了解tensorflow构建项目的关键步骤,可以养成良好的代码习惯,这在初学者来说是很重要的。
Tensorflow中关键的两个步骤,首先对数据进行处理,转化为合适的tensor作为input输入到图中。其次使用tensorflow对神经网络进行构建。本篇文章作为深度学习实战的第一篇,会尽量用通俗的语言解释在这两部分中的关键的点。本篇任务为计算句子相似度,代码上传至github(2017年10月24日对代码进行重构),感兴趣的同学可以下载并进行仿真,加深对tensorflow的理解。代码较长,因此分为上下两篇对该实例进行讲解。上篇讲解句子相似度问题中数据处理的相关工作。
1、 读取glove词向量文件。
glove文件的共有40000个词,词在每一行开头,其余的为该词的词向量。我们用notepad打开,在第一行添加400000 50两个数字,中间用空格或者 隔开,400000代表词的个数,50代表词向量的维度。这样我们就可以方便的通过word2vec工具读取词向量。
选用glove文件是google预先训练好的文件,这里新建了两个变量sr_word2id, word_embedding。
sr_word2id的数据类型是pandas工具中的Series,在数据处理中非常常用,可以当做是一个字典,根据word来得到id,负责将数据集中的word转成id。
word_embedding负责将word转成的id转成词向量,shape为[vocabulary_size, embedding_size],在本例中是由[400000, 50],行号和sr_word2id中的id对应。
word_embedding变量存在是因为我们在实际操作过程中发现,如果直接使用词向量作为input的话,那么input中传入的shape就是[s_count, sentence_length, embedding_size],其中,s_count是数据集大小,sentence_length就是最大句子长度,embedding_length就是词向量的大小。
那么placeholder的维度就太大,然后程序就变得特别卡顿。所以,参考Text Classification(CNN)的方法,使用embedding层进行转换。即我们使用词的索引值,而不是词的向量作为input。那么input的shape为[s_count, sentence_length],以词汇在word_embedding的索引值代替词向量,传入placeholder后再通过tf.nn.embedding_lookup函数将id变为词向量,这样就大大节省了placeholder的空间。
比如句子“cat sit on the mat”,一般的思路是用“cat”,”sit”,”on”,”the”,”mat”这五个单词的词向量表示这句话:
[[1,1,1],
[2,2,2],
[3,3,3],
[4,4,4],
[5,5,5]]
那么整个数据集为9840行,placeholder就变成了9840*5*3大小的tensor,采用embedding_w以后呢,我们采用“cat”,”sit”,”on”,”the”,”mat”这五个词在embedding_w中的索引代替该词:
[1,2,3,4,5]
然后placeholder就变成了9840*5大小的tensor,然后在之后的操作中,根据索引查找embedding_w的词向量即可,当embedding_size为3时,影响不大,但是当embedding_size为50、100或者更大时,节省的时间就很可观了。
def build_glove_dic():
# 从文件中读取 pre-trained 的 glove 文件,对应每个词的词向量
# 需要手动对glove文件处理,在第一行加上
# 400000 50
# 其中400000代表共有四十万个词,每个词50维,中间为一个空格或者tab键
# 因为word2vec提取需要这样的格式,详细代码可以点进load函数查看
glove_path = 'glove.6B.50d.txt'
wv = word2vec.load(glove_path)
vocab = wv.vocab
sr_word2id = pd.Series(range(1,len(vocab) + 1), index=vocab)
sr_word2id['<unk>'] = 0
word_embedding = wv.vectors
word_mean = np.mean(word_embedding, axis=0)
word_embedding = np.vstack([word_mean, word_embedding])
return sr_word2id, word_embedding
word_embedding中有400000个词,那么肯定也会有文中出现,但是不在word_embedding中的词,这个时候通过sr_word2id查找是找不到id的,那么我们就使用这400000个词向亮的均值代表未知的词,使用<unk>
<unk>表示。
2、读取数据集。本实例中数据集选取SICK,在句子相似度领域经典的数据集。该数据集主要包括两个句子和它们之间的相似度。整个函数的想法就是从数据集中提取数据,使用s1,s2,score存储两个句子和它们之间的相似度。然后对句子进行处理,记数据集中句子的最大长度sentence_length,不够该长度的使用‘unk’进行填充。
值得一提的是,这里的s1_image和s2_image的形状是[s_count,sentence_length
],label的形状是[s_count,1]。这里说道的s1_image和s2_image刚好和placeholder的input的形状保持一致,然后通过
import pandas as pd
import numpy as np
def read_data_sets(train_dir):
#
# s1代表数据集的句子1
# s2代表数据集的句子2
# score代表相似度
# sample_num代表数据总共有多少行
#
SICK_DIR = "SICK_data/SICK.txt"
df_sick = pd.read_csv(SICK_DIR, sep=" ", usecols=[1,2,4], names=['s1', 's2', 'score'],
dtype={'s1':object, 's2':object, 'score':object})
df_sick = df_sick.drop([0])
s1 = df_sick.s1.values
s2 = df_sick.s2.values
score = np.asarray(map(float, df_sick.score.values), dtype=np.float32)
sample_num = len(score)
# 引入embedding矩阵和字典
global sr_word2id, word_embedding
sr_word2id, word_embedding = build_glove_dic()
# word2id, 多线程将word转成id
p = Pool()
s1 = np.asarray(p.map(seq2id, s1))
s2 = np.asarray(p.map(seq2id, s2))
p.close()
p.join()
# 填充句子
s1, s2 = padding_sentence(s1, s2)
new_index = np.random.permutation(sample_num)
s1 = s1[new_index]
s2 = s2[new_index]
score = score[new_index]
return s1 ,s2, score
def get_id(word):
if word in sr_word2id:
return sr_word2id[word]
else:
return sr_word2id['<unk>']
def seq2id(seq):
seq = clean_str(seq)
seq_split = seq.split(' ')
seq_id = map(get_id, seq_split)
return seq_id
3、填充句子
def padding_sentence(s1, s2):
#
# 得到句子s1,s2以后,很直观地想法就是先找出数据集中的最大句子长度,
# 然后用<unk>对句子进行填充
#
s1_length_max = max([len(s) for s in s1])
s2_length_max = max([len(s) for s in s2])
sentence_length = max(s1_length_max, s2_length_max)
sentence_num = s1.shape[0]
s1_padding = np.zeros([sentence_num, sentence_length], dtype=int)
s2_padding = np.zeros([sentence_num, sentence_length], dtype=int)
for i, s in enumerate(s1):
s1_padding[i][:len(s)] = s
for i, s in enumerate(s2):
s2_padding[i][:len(s)] = s
print "9840个句子填充完毕"
return s1_padding, s2_padding
注意:在处理数据的过程中,要把列表转化成np数组,不然传入placeholder中会一直报错“setting an array element with a sequence”。np数组的转换也很简单,使用np.array()就可以进行转换,但是np数组在插入过程不是很熟练,因此也可以采用先对列表进行操作,在转换为np数组。但一定要注意,多维的列表的每一层都需要转化成np数组,可以在tensorflow的调试过程中慢慢处理。