word2vec + manage experiments

关键词:model base, variable sharing, model sharing

word2vec

  • 文本的分布式表示是许多自然语言处理任务的基础
  • word2vec是用来生成词语分布式表示的一组模型
  • 主要有两种模型:skip-gram和CBOW
  • 算法角度:CBOW模型从上下文词预测目标词,skip-gram模型从目标词预测上下文词
  • 统计角度:CBOW模型将an entire context as one observation,这样做smoothes over了许多分布信息,适合小一些的数据集上;skip-gram模型则是将each context-target pair as a new observation,在larger datasets上表现更好
  • 训练词向量:定义一个单层网络,任务是给定中心词预测词典中的词作为上下文词的概率,我们最后要的是隐含层的权重参数
  • 使用 softmax 来获得可能的目标词的分布,分母要对字典中的所有词取指数再求和,计算是瓶颈
  • 规避瓶颈的方法:层次化的softmax 和基于采样的 softmax
  • 文章**Distributed Representations of Words and Phrases and their Compositionality **指出,训练skip-gram模型时,与更复杂的分层softmax相比,负采样可以加快训练速度,为频繁词汇提供更好的向量表示
  • 负采样实际上是一种称为噪声对比估计(NCE)的简化模型,基于假设,如噪声样本的数量k和噪声样本的分布Q满足kQ(w) = 1,来简化计算;理论上不能保证其导数和softmax梯度一致
  • NCE则随着noise样本增多,提供了这种保证
  • 负采样和NCE只在训练时有用

Implementing

  • 词的indices作为输入(一个scalar),
  • BATCH_SIZE的样本,输入维度为[BATCH_SIZE],输出维度为[BATCH_SIZE,1]
  • 词向量矩阵维度为[VOCAB_SIZE,EMBED_SIZE],每一行代表一个词向量
  • 利用tf.nn.embedding_lookup()找中心词对应的向量,免去了不必要的计算(matrix and onehot vector)
  • loss使用tf.nn.nce_loss(),optimizer使用GradientDescentOptimizer

Structure TF models

定义图

  • 导入数据(placeholder or tf.data)
  • 定义权重
  • 定义模型
  • 定义损失函数
  • 定义优化器

执行图

  • 初始化所有变量
  • 初始化迭代器或者feed in训练数据
  • 数据经过模型得到结果
  • 计算cost
  • 调整模型参数使得cost最小或者最大

build model as a class in order to reuse easily.

Variable sharing

Name scope

将相关的ops放在一个name_scope下,这样得到的图在TensorBoard上是一块一块的,更加整洁。
TensorBoard图中三种边:

  1. 灰实边:数据流
  2. 橙实边:参考边,op_lest影响op_right
  3. 灰虚边:控制依赖边,op_left依赖于op_right

Variable scope

和Name scope一样都创建了namespace,调用tf.variable_scope(“name”)会隐式地调用tf.name_scope(“name”),Variable scope主要功能是促进变量共享(facilitate variable sharing)
为实现变量共享:

  1. 使用 tf.get_variable(),它会在创建变量之前检查其是否存在
  2. 将所用到的变量放到一个VarScope,将这个VarScope设置为可复用的(reusable)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fully_connected(x,output_dim,scope_name): #  基础组件:全连接层
with tf.variable_scope(scope_name):
w = tf.get_variable("weights",[x.shape[1],output_dim],initializer=tf.random_normal_initializer())
b = tf.get_variable("bias",[output_dim],initializer= tf.constant_initializer(0.0))
return tf.matmul(x,w) + b

def two_hidden_layer(x): #网络结构:两个全连接层
h1 = fully_connected(x,50,'h1')
h2 = fully_connected(h1,10,'h2')

with tf.variable_scope('two_layers') as scope: #调用网络,输入x1,x2
logits1 = two_hidden_layer(x1)
scope.reuse_variables()
logits2 = two_hidden_layer(x2)

以上代码模式,基础组件可以定义更多,比如conv,relu,网络结构可以更复杂,比如放一个ResNet,非常容易scale。
由于使用了变量共享,多次传入x,网络TensorBoard图的复杂程度不会爆炸式增加。

Graph collections

使用这个,可以获取满足一定条件的所有变量,tf.get_collection(key,scope=None)
比如执行optimizer的时候,默认情况下它会获取key=tf.GraphKeys.TRAINABLE_VARIABLES的变量,即所有可训练的变量(当然也可以传入指定的、要训练的变量
获取某个scope下的所有变量,tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope=’scope_name’)
key还有许多其它的值,见官网。

Manage experiments

实验总是很久,中断随时发生,因此训练可以随时随地停止、像没事一样恢复,非常重要。
另外一个问题是论文结果复现,控制实验的随机因子对复现结果非常关键。

tf.train.Saver()

周期性地保存模型参数是个好习惯
tf.train.Saver()类将图的变量保存(不是整张图)到二进制文件,也就是一个checkpoint(变量名到tensors的映射)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#定义模型
...
global_steps = tf.Variable(0, trainable=False,name='global_steps')
optimizer = tf.train.GradientDescentOptimizer(lr).minimize(loss,global_step=global_steps)
#创建一个saver对象
saver = tf.train.Saver()

#启动一个会话来执行计算
with tf.session() as sess:
for step in range(1,training_steps):
sess.run([optimizer]) #global_step会在每一步训练后自加1
if step%1000==0:
#这里传入step也差不多吧...默认保存图中所有变量
saver.save(sess,'checkpoints/model-name',global_step=global_steps)

生成的checkpoint名,像这样,’checkpoints/skip-gram-10000’
在恢复模型时可以直接传入checkpoint名(如果有的话

1
2
3
ckpt = tf.train.get_checkpoint_state(os.path.dirname('checkpoints/model-name'))
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess,ckpt.model_checkpoint_path) #恢复的是最近的一个check point

恢复的时候,网络图还得自己重新搭(still have to create the graph ourselves)之后再加载变量
当然,经常的做法是到目前为止表现最好的参数也保存下来(不止是最近的一次)

tf.summary

记录模型训练过程中指标变化,包括loss,accuracy等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建summaries
with tf.name_scope("summaries"):
tf.summary.scalar("loss",loss)
tf.summary.histogram("histogram loss",loss)
tf.summary.scalar("accuracy",accuracy)
summary_op = tf.summary.merge_all()

#执行summary op
with tf.Session() as sess:
for step in range(steps):
_loss,_,summary = sess.run([model.loss,model.optimizer,model.summary_op],feed_dict={...})
writer = tf.summary.FileWriter('path'+str(lr),sess.graph) #将lr写入路径,方便tensorboard对比
writer.add(summary,global_step=step) #记录每一个step的summary
writer.close()

control randomization

为了使得别人在实验时结果会与你一致

  1. op级别
    所有的tensor初始化时都传入seed参数
    session记录了随机状态,每一个新的session都会重新start the random state

    1
    2
    3
    4
    5
    6
    c = tf.random_uniform([],-10,10,seed=2)
    d = tf.random_uniform([],-10,10,seed=2)
    with tf.Session() as sess:
    print(sess.run(c)) #value: a
    print(sess.run(d)) #same value: a
    print(sess.run(c)) #value: b
  2. graph级别
    比如demo1.py和demo2.py代码相同,设置了tf.set_random_seed(seed)的话执行结果是相同的

    1
    2
    3
    4
    5
    6
    tf.set_random_seed(2)
    c = tf.random_uniform([],-10,10)
    d = tf.random_uniform([],-10,10)
    with tf.Session() as sess:
    print(sess.run(c)) # 都是a
    print(sess.run(d)) # 都是b