在第三篇学习笔记中,我们实现了两个简单的模型,线性回归和logistic回归,对于模型结构简单的网络,我们不需要去处理他们的结构关系,但是在复杂的模型中,我们需要更好的安排模型的结构,这样方便我们debug和良好的可视化,接下来,我们就讨论一下如何结构化我们的模型。
TensorFlow中结构化模型
一般我们的模型都是由一下的两步构成,第一步是构建计算图,第二步是执行计算图,下面我们就来依次看看这两步操作中如何结构化模型。
构建计算图
在构建计算图中,一般分为下面5个步骤:
定义输入和输出的占位符(placeholder)
定义模型中需要用到的权重
定义推断模型,构建网络
定义损失函数作为优化对象
定义优化器进行优化
执行计算图
定义好了计算图之后,我们就可以构建session去进行运算,一般也分为下面5个步骤:
第一次进行运算的时候,初始化模型的所有参数
传入训练数据,可以打乱顺序
网络前向传播,计算出当前参数下的网络输出
根据网络输出和目标计算出loss
通过loss方向传播更新网络中的参数
下面是一个可视化的示意图
实例介绍
上面是一个基本的一般性描述,下面我们使用词向量和skip-gram这个具体的例子来介绍一下如何结构化模型,如果对词向量不熟悉的同学,可以查看一下我的这篇文章的简单介绍,更加详细的介绍可以阅读这篇博文或者是cs224n的课件。
词向量的简单介绍
词向量简单来说就是用一个向量去表示一个词语,但是这个向量并不是随机的,因为这样并没有任何意义,所以我们需要对每个词有一个特定的向量去表示他们,而有一些词的词性是相近的,比如"(love)喜欢"和"(like)爱",对于这种词性相近的词,我们需要他们的向量表示也能够相近,如何去度量和定义向量之间的相近呢?非常简单,就是使用两个向量的夹角,夹角越小,越相近,这样就有了一个完备的定义。
虽然我们知道了如何定义词向量的相似性,但是我们仍然不知道如何得到词向量,因为这显然不可能人为去赋值,为了得到词向量,需要介绍skip-gram模型。
skip-gram模型的简单介绍
skip-gram模型简单来讲就是在一大段话中,我们给定其中一个词语,希望预测它周围的词语,将词向量作为参数,通过这种方式来训练词向量,最后能够得到满足要求的词向量。而一般来讲,skip-gram模型都是比较简单的线性模型。另外cs224n中还介绍了Noise Contrastive Estimation(不知道怎么翻译)的方法,这里就不再详细介绍了,这只是一种负样本的取样方法。
TensorFlow实现
下面使用tensorflow的实现来具体讲解一下如何结构化模型,首先我们会实现一个非结构化版本,看看他的局限性和不足性,然后讲解一下如何结构化模型。
数据集
这里使用的是text8数据集,这是一个大约100 MB的清理过的数据集,当然这个数据集非常小并不足以训练词向量,但是我们可以得到一些有趣的结果。
构建计算图
首先定义好一些超参数。
VOCAB_SIZE = 50000
BATCH_SIZE = 128
EMBED_SIZE = 128 # dimension of the word embedding vectors
SKIP_WINDOW = 1 # the context window
NUM_SAMPLED = 64 # Number of negative examples to sample.
LEARNING_RATE = 1.0
NUM_TRAIN_STEPS = 20000
SKIP_STEP = 2000 # how many steps to skip before reporting the loss
- 建立输入和输出的占位符(placeholder)
首先,我们将数据集中的所有语句按顺序排在一起,那么我们输入的是其中一个词语,比如说是第300个,那么要预测的就是他周围的词,比如第301个词,或者299个词,当然这个范围并不一定是1,一般来讲可以预测左边3个词和右边3个词中的任何一个,所以输入和输出的占位符定义如下。
center_word = tf.placeholder(tf.int32, [BATCH_SIZE],
name='center_words')
y = tf.placeholder(tf.int32, [BATCH_SIZE, SKIP_WINDOW],
name='target_words')
这里SKIP_WINDOW表示预测周围词的数目,超参数里面取值为1。
- 定义词向量矩阵
接下来需要定义词向量,使用下面的代码。
embed_matrix = tf.get_variable(
"WordEmbedding", [VOCAB_SIZE, EMBED_SIZE],
tf.float32,
initializer=tf.random_uniform_initializer(-1.0, 1.0))
这里相当于新建一个Variable,维数分别是总的词数x词向量的维度。
- 构建网络模型
我们可以通过下面的操作取到词向量矩阵中所需要的每一个词的词向量。
embed = tf.nn.embedding_lookup(embed_matrix, center_word, name='embed')
这里embed_matrix和center_word分别表示词向量矩阵和需要提取词向量的单词,我们都已经定义过了。
- 定义loss函数
NCE已经被集成进了tensorflow,所以我们可以非常方便地进行使用,下面就是具体的api。
tf.nn.nce_loss(weights, biases, labels, inputs, num_sampled, num_classes, num_true=1, sampled_values=None, remove_accidental_hits=False, partition_strategy='mod', name='nce_loss')
labels和inputs分别是target和输入的词向量,前面有两个参数分别时weights和biases,因为词向量的维度一般不等于分类的维度,需要将词向量通过一个线性变换映射到分类下的维度。有了这个定义之后,我们就能够简单地进行实现了。
nce_weight = tf.get_variable(
'nce_weight', [VOCAB_SIZE, EMBED_SIZE],
initializer=tf.truncated_normal_initializer(
stddev=1.0 / (EMBED_SIZE**0.5)))
nce_bias = tf.get_variable(
'nce_bias', [VOCAB_SIZE], initializer=tf.zeros_initializer())
nce_loss = tf.nn.nce_loss(nce_weight, nce_bias, y, embed,
NUM_SAMPLED,
VOCAB_SIZE)
loss = tf.reduce_mean(nce_loss, 0)
- 定义优化函数
接下来我们就可以定义优化函数了,非常简单,我们使用随机梯度下降法。
optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
执行计算图
构建完成计算图之后,我们就开始执行计算图了,下面就不分开讲了,直接放上整段session里面的内容。
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
total_loss = 0.0 # we use this to calculate the average loss in the last SKIP_STEP steps0
writer = tf.summary.FileWriter('./graphs/no_frills/', sess.graph)
for index in range(NUM_TRAIN_STEPS):
centers, targets = next(batch_gen)
train_dict = {center_word: centers, y: targets}
_, loss_batch = sess.run([optimizer, loss], feed_dict=train_dict)
total_loss += loss_batch
if (index + 1) % SKIP_STEP == 0:
print('Average loss at step {}: {:5.1f}'.format(
index, total_loss / SKIP_STEP))
total_loss = 0.0
writer.close()
通过阅读代码,也能看到非常清晰的结构,一步一步去运行结果。
最后放上tensorboard中网络结构的示意图。
可以发现整体的网络结构是非常混乱的,所以我们需要结构化我们的模型。
结构化网络
结构化网络非常简单,只需要加入Name Scope,下面是一个简单的事例。
with tf.name_scope(name_of_taht_scope):
# declare op_1
# declare op_2
# ...
举一个例子,比如我们定义输入输出的占位符的时候,可以如下方式定义
with tf.name_scope('data'):
center_word = tf.placeholder(
tf.int32, [BATCH_SIZE], name='center_words')
y = tf.placeholder(
tf.int32, [BATCH_SIZE, SKIP_WINDOW], name='target_words')
然后我们运行相同的代码,就能够在tensorboard里面得到下面的结果。
是不是结构非常的清楚,所以我们平时需要结构化我们的模型,以便于更好的可视化和debug。
词向量可视化
最后在介绍一下词向量的可视化,现在tensorboraad也支持词向量的可视化了,进行一系列复杂的操作,就能够在tensorboard中得到下面的结果。
输入每个词,都能够在右边看到与之词性相近的词语分别是什么,特别方便,这个可视化的代码在这个文件中。
本文的全部代码都在github
欢迎访问我的博客
欢迎查看我的知乎专栏,深度炼丹