AI测试 深度学习基础 (十七)-- TensorFlow 中的变量

孙高飞 · 2018年04月01日 · 最后由 孙高飞 回复于 2018年04月03日 · 1756 次阅读

变量的使用场景

使用 tf 的时候我们需要初始化权重 (w) 和偏差 (b), 还记得我们 y=wx+b 这个公式么。 在我们训练模型的时候,需要初始化这些变量,之后随着梯度下降算法每一轮迭代再不停地变幻 w 和 b 的值。 下面看看一个变量的使用场景。假设我们要构建一个只有 2 个特征,一个隐藏层的二分类神经网络场景。那么我们如何初始化这些变量呢。

权重 (w) 的初始化

import tensorflow as tf

w1 = tf.Variable(tf.random_normal([2,3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3,1], stddev=1, seed=1))

上面我们使用 tf.Variable 来声明一个变量, 方式是 random_normal。 意思是随机生成,里面有 3 个参数, 第一个是 shape,也就是这个变量的维度,也叫形成。 我们使用的是 2,3. 意思是一个 2 乘 3 的矩阵。 stddev=1,代表这些数的标准差为 1. seed 是随机种子。 这些数字是满足正太随机分布的。 我们一般使用这种方式初始化权重 (w), 如果我们的输入层有 2 个特征,而第一个隐藏层有 3 个节点的话。那么就要生成一个 2 乘 3 的矩阵的权重, 也就是 w11,w12,w13 以及 w21,w22,w23(w 后的第一个数代表当前神经网络的层数,第二个数代表当前层的第几个神经元, 也就是说 w11 的意思是第一个隐藏层的第一个神经元)。 如果用 x1 和 x2 来表示这两个特征的话,那么计算神经网络的前向传播算法的话。第一个节点:a11= x1*w11+x2*w21+b . 第二个节点:a12=x1*w12+x2*w22+b 第三个节点:a13=x1*w13+x2*w23+b. 如果下一层就是输出层的话,也就是说输出层只有一个神经元。那么要生成的权重矩阵就是 3*1 的。 也就是 w21,w22,w23. 计算输出层的前向传播算法就是:y = a11*w21+a12*w22+a13*w23+b。 所以大家现在知道上面的 demo 中为什么第一行是一个 2*3 的矩阵而第二个权重是一个 3*1 的矩阵了把。 基本上可以理解为矩阵的竖排是上一层的节点数,横排是当前层的节点。 对神经网络和前向传播算法还不熟悉的同学请自行温习之前的内容。 之后在讲用 tf 实现神经网络的时候也需要用到的。

第一次接触的话一定会感觉很奇怪,为什么要用 tf.Variable 这样的形式来声明变量。 我们上一节说过,tf 是时候用计算图来进行计算的。 那么只有用 tf 提供的 Variable 方法或之后要介绍的 get_Variable 方法才会把变量加入到计算图中。 那么除了 random_normal, 我们还有其他几种方式。如下:

  • tf.truncated_normal: 正态分布,但如果随机出来的值偏离平均值超过 2 个标准差,那么这个数会重新生成
  • tf.random_uniform: 均匀分布

偏差 (b) 的初始化

在 tf 的神经网络中,初始化偏差 (bias) 通常会使用常数来设置初始值。如下:

b1 = tf.Variable(tf.zeros([3]))
b2 = tf.Variable(tf.zeros([1]))
``
上面的例子分别用0初始化了隐藏层和输出层的偏差同样也有其他几种方式来初始化偏差 如下

- tf.onses: 产生全1的数据
- tf.fill: 产生一个给定数子的数据
- tf.constant: 产生一个给定值的常量

可能有人会问为什么初始化w的时候要使用随机方式而初始化b就用常数呢 我们在早期的帖子中说到过如果w使用常数会导致所有神经元节点的特征权重都是一样的这样每个神经元计算的结果也是一样的

# 变量的初始化
tf中变量需要显示的执行初始化工作如下

```python
with tf.Session() as session:
    init_op = tf.global_variables_initializer()
    session.run(init_op)

共享变量

tf 的变量管理中还有两个函数是 tf.get_variable 和 tf.variable_scope。 tf.variable_scope 顾名思义,它是声明一个作用于。 声明在这个作用于内的变量只能在这个作用域下使用。而 tf.get_variable 与 tf.Variable 其实差不多, 都是创建变量的函数。 使用方式如下:

with tf.variable_scope("foo"):
    v = tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))

既然 variable_scope,声明了一个作用域,那么在这个作用域下,变量是可以重用的,也是不可以重名的。 如果我们在另一段代码中尝试下面这种写发,会报错。

with tf.variable_scope("foo"):
    v = tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))

ValueError: Variable foo/v already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at:

根据上面的提示, foo 作用域下的变量 v 已经存在了。 为了能够重用 v 这个变量。我们可以加入 reuse=True。如下:

with tf.variable_scope("foo", reuse=True):
    v = tf.get_variable("v")

这样我们就可以在另外的代码片段里使用这个变量了。 而 tf.Variable 相反, 如果你声明了两个名字叫 add 的变量。那么实际上在计算图中是 add0 和 add1. 那为什么需要这两个函数来管理变量呢。 一个是因为有些场景下我们需要共享变量,举个简单的例子,我们都是模块化代码,代码都是封装成一个函数一个函数的。而权重 W 和偏差 B 又是在整个神经网络中都要用到的变量, 如果没有共享变量的话,可以使用 python 的全局变量,而这样做是破坏了代码的封装性的。又或者需要把 W 和 B 不停的当做参数传递到各种函数里。如果只有 W 和 B 还好。 但是如果我们是一个比较复杂的模型比如卷积神经网络,那变量就更多了。这样的代码会很乱。所以使用共享变量是可以解决这个问题的。

placeholder(占位符)

我们在上一篇帖子初始化样本的时候是这样写的:

x = tf.constant([[0.7,0.9]])
y_ = tf.constant([[1.0]])

这是为了示例简单而这么写,但实际上我们的样本是非常大的,有时候几百万几千万行都有,全部加载进内存会很好资源会很慢。所以才有了我们早期帖子里说的 mini batch。 每次读入少量的样本数据进行训练。假如 mini batch 的块大小为 128,那就每次读取 128 个样本进行初始化 x 和 y,但是如果我们只是单纯的像上面一样读取 128 个样本就执行上面的那段代码,那么在计算图出,会多出非常多的变量 (tensor),这是不利于后期可视化和计算的。 所以在 tf 里我们初始化 x 和 y 的时候,一般都是使用占位符来做的。 如下:

x = tf.placeholder(dtype=tf.float32, name='x')
y_ = tf.placeholder(dtype=tf.float32, name='y')

占位符必须传入数据类型的参数,维度 (shape) 可以不用传,不过传了可以降低出错的概率。 这样我们在运行训练的时候,可以动态的填入这些变量的值,如下:

x = tf.placeholder(tf.float32, shape=(128, 1024))  
y = tf.matmul(x, x)  

with tf.Session() as sess:  
  print(sess.run(y))  # ERROR: 此处x还没有赋值.  

  rand_array = np.random.rand(1024, 1024)  
  print(sess.run(y, feed_dict={x: rand_array}))  # Will succeed.  

上面我们定义我们的样本 x 是一个 128*1024 的矩阵, 在运行时,我们通过在 session.run 方法中传递 fedd_dict 来传入占位符的值。也就是说当我们使用 placeholder 声明一个变量的时候,是在告诉 tf 这里只是一个占位符,具体是什么值以后在使用的时候再告诉你。

结尾

好了,具体就写这么多吧,之后的帖子会有使用这些变量管理的例子的。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

踩一脚,正在看 CS213N 的飘过。。。。~

magicyang 回复

握个抓~

ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册