其实所有的教程,官方已经给的很是清楚了,在这里我只是进行学习和记录这个过程,所有的东西以官方内容为准。
首先Tensorflow 是以静态图来构建整个计算过程的,这个计算过程就是前向传播,加入loss 就是在图表中添加生成损失(loss)所需要的操作(ops),而training往损失图表中添加计算并应用梯度(gradients)所需的操作。这整个过程下来之后我们就有一个可以训练的神经网络,学习数据中所具有的知识内容。
现在我们要做的就是对图的构建进行学习。首先构建图要有变量。
一. 变量
1.变量声明方式与变量在图中生成的过程。
首先我们模型所学习的参数需要是一个变量,才可以根据反向传播来改变参数内容。变量的声明方法如下:
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
name="weights")
将一个张量
作为初始值传入构造函数Variable()
其中shape为[784,200] ,name是这个操作自己取的名字。
调用tf.Variable()
添加一些操作(Op, operation)到graph:
- 一个
Variable
操作存放变量的值。 - 一个初始化op将变量设置为初始值。这事实上是一个
tf.assign
操作. - 初始值的操作,例如示例中对weights变量的random.normal操作也被加入了graph
2.变量的初始化过程。
(1)由于Tensorflow 是采用静态图,这个操作在你申明的时候并没有真正进行,所以在你使用你声明的变量时要对其进行初始化操作。初始化操作要在tf.Session()里进行:
# Create two variables.
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
name="weights")
biases = tf.Variable(tf.zeros([200]), name="biases")
...
# Add an op to initialize the variables.
init_op = tf.initialize_all_variables()
# Later, when launching the model
with tf.Session() as sess:
# Run the init operation.
sess.run(init_op)
...
# Use the model
...
(2)由于tf.initialize_all_variables()
是并行地初始化所有变量,所以在有这种需求的情况下需要小心。用其它变量的值初始化一个新的变量时,使用其它变量的initialized_value()
属性。你可以直接把已初始化的值作为新变量的初始值,或者把它当做tensor计算得到一个值赋予新变量。
# Create a variable with a random value.
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
name="weights")
# Create another variable with the same value as 'weights'.
w2 = tf.Variable(weights.initialized_value(), name="w2")
3.保存参数和加载参数。
(1)保存
最简单的保存和恢复模型的方法是使用tf.train.Saver
对象。构造器给graph的所有变量,或是定义在列表里的变量,添加save
和restore的
ops。saver对象提供了方法来运行这些ops。
保存的文件是二进制文件,变量存储在二进制文件里,主要包含从变量名到tensor值的映射关系。当你创建一个Saver
对象时,你可以选择性地为文件中的变量挑选变量名。默认情况下,将每个变量Variable.name
属性的值。
# Create some variables.
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# Add an op to initialize the variables.
init_op = tf.initialize_all_variables()
# Add ops to save and restore all the variables.
saver = tf.train.Saver()
#Create some variables,these variables cann't be save by saver in the session
v3 = tf.Variable(..., name = "v3"
# Later, launch the model, initialize the variables, do some work, save the
# variables to disk.
with tf.Session() as sess:
sess.run(init_op)
# Do some work with the model.
..
# Save the variables to disk.
save_path = saver.save(sess, "/tmp/model.ckpt")
print "Model saved in file: ", save_path
(2).恢复变量
用同一个Saver
对象来恢复变量。注意,当你从文件中恢复变量时,不需要事先对它们做初始化。
# Create some variables.
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# Add ops to save and restore all the variables.
saver = tf.train.Saver()
# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
with tf.Session() as sess:
# Restore variables from disk.
saver.restore(sess, "/tmp/model.ckpt")
print "Model restored."
# Do some work with the model
...
可以通过给
tf.train.Saver()
构造函数传入Python字典,很容易地定义需要保持的变量及对应名称:键对应使用的名称,值对应被管理的变量。
saver = tf.train.Saver({"my_v2": v2})
附加:
(1)对于要保存多时间点的的变量到文件中,可选的参数起到作用:global_step
saver.save(sess, 'my-model', global_step=0) ==> filename: 'my-model-0'
# Create a saver.
saver = tf.train.Saver(...variables...)
# Launch the graph and train, saving the model every 1,000 steps.
sess = tf.Session()
for step in xrange(1000000):
sess.run(..training_op..)
if step % 1000 == 0:
# Append the step number to the checkpoint name:
saver.save(sess, 'my-model', global_step=step)
(2)另外一些可选参数
max_to_keep: 默认是0 ,所有的文件都保持,如何赋值5,那么就是最近的5个文件保存。
keep_checkpoint_every_n_hours: 如何赋值为2 ,那么2小时保存一次,默认值是10000,实际上是没有作用的。
二. 变量的共享
问题提出就是在同一个过滤器中的变量使用tf.Variable()进行声明的话,下传入同意一张img时,得到的结果是不一样的。通常解决此类问题的方法之一就是使用类来创建模块,在需要的地方使用类来小心地管理他们需要的变量.但是Tensorflow 提供了一个更好的解决方法————变量作用域机制,当构建一个视图时,很容易就可以共享命名过的变量.。
变量作用域机制在TensorFlow中主要由两部分组成:
tf.get_variable(<name>, <shape>, <initializer>)
: 通过所给的名字创建或是返回一个变量.tf.variable_scope(<scope_name>)
: 通过tf.get_variable()
为变量名指定命名空间.
有一些在TensorFlow中使用的初始化变量:
tf.constant_initializer(value)
初始化一切所提供的值,tf.random_uniform_initializer(a, b)
从a到b均匀初始化,tf.random_normal_initializer(mean, stddev)
用所给平均值和标准差初始化均匀分布.
实现共享:
为了了解tf.get_variable()
怎么解决前面所讨论的问题,先写如下方法:
def conv_relu(input, kernel_shape, bias_shape):
# Create variable named "weights".
weights = tf.get_variable("weights", kernel_shape,
initializer=tf.random_normal_initializer())
# Create variable named "biases".
biases = tf.get_variable("biases", bias_shape,
initializer=tf.constant_intializer(0.0))
conv = tf.nn.conv2d(input, weights,
strides=[1, 1, 1, 1], padding='SAME')
return tf.nn.relu(conv + biases)
这两个变量的写法,但是不同的变量需要不同的名字.这就是
tf.variable_scope()
变量起作用的地方.他为变量指定了相应的命名空间.
def my_image_filter(input_images):
with tf.variable_scope("conv1"):
# Variables created here will be named "conv1/weights", "conv1/biases".
relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
with tf.variable_scope("conv2"):
# Variables created here will be named "conv2/weights", "conv2/biases".
return conv_relu(relu1, [5, 5, 32, 32], [32])
现在,让我们看看当我们调用 my_image_filter()
两次时究竟会发生了什么.
result1 = my_image_filter(image1)
result2 = my_image_filter(image2)
# Raises ValueError(... conv1/weights already exists ...)
就像你看见的一样,tf.get_variable()
会检测已经存在的变量是否已经共享.如果你想共享他们,你需要像下面使用的一样,通过reuse_variables()
这个方法来指定.
with tf.variable_scope("image_filters") as scope:
result1 = my_image_filter(image1)
scope.reuse_variables()
result2 = my_image_filter(image2)
用这种方式来共享变量是非常好的,轻量级而且安全.
三.共享变量的理解
理解tf.get_variable()
v = tf.get_variable(name, shape, dtype, initializer)
此调用做了有关作用域的两件事中的其中之一,方法调入.总的有两种情况.
- 情况1:当
tf.get_variable_scope().reuse == False
时,作用域就是为创建新变量所设置的.
这种情况下,v
将通过tf.Variable
所提供的形状和数据类型来重新创建.创建变量的全称将会由当前变量作用域名+所提供的名字
所组成,并且还会检查来确保没有任何变量使用这个全称.如果这个全称已经有一个变量使用了,那么方法将会抛出ValueError
错误.如果一个变量被创建,他将会用initializer(shape)
进行初始化,所以变量在创建完成后就已经完成了初始化的工作了。
- 情况2:当
tf.get_variable_scope().reuse == True
时,作用域是为重用变量所设置
这种情况下,调用就会搜索一个已经存在的变量,他的全称和当前变量的作用域名+所提供的名字
是否相等.如果不存在相应的变量,就会抛出ValueError
错误.如果变量找到了,就返回这个变量.如下:
with tf.variable_scope("foo"):
v = tf.get_variable("v", [1])
with tf.variable_scope("foo", reuse=True):
v1 = tf.get_variable("v", [1])
assert v1 == v
理解tf.variable_scope():
它将会作为前缀用于变量名,并且带有一个重用标签来区分以上的两种情况.嵌套的作用域附加名字所用的规则和文件目录的规则很类似:
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"
当前变量作用域可以用tf.get_variable_scope()
进行检索并且reuse
标签可以通过调用tf.get_variable_scope().reuse_variables()
设置为True
.
with tf.variable_scope("foo"):
v = tf.get_variable("v", [1])
tf.get_variable_scope().reuse_variables()
v1 = tf.get_variable("v", [1])
assert v1 == v
在上面的所有例子中,我们共享参数只因为他们的名字是一致的,那是因为我们开启一个变量作用域重用时刚好用了同一个字符串.在更复杂的情况,他可以通过变量作用域对象来使用,而不是通过依赖于右边的名字来使用.为此,变量作用域可以被获取并使用,而不是仅作为当开启一个新的变量作用域的名字.
with tf.variable_scope("foo") as foo_scope:
v = tf.get_variable("v", [1])
with tf.variable_scope(foo_scope)
w = tf.get_variable("w", [1]) #声明的变量也是在tf.variable_scope("foo")这个变量作用域
with tf.variable_scope(foo_scope, reuse=True)
v1 = tf.get_variable("v", [1]) #这里reuse 的值为True 可以共享在 tf.variable_scope("foo")中的变量值
w1 = tf.get_variable("w", [1])
assert v1 == v
assert w1 == w
当开启一个变量作用域,使用一个预先已经存在的作用域时,我们会跳过当前变量作用域的前缀而直接进入另外一个不同的作用域.这就是我们做得完全独立的地方.
with tf.variable_scope("foo") as foo_scope:
assert foo_scope.name == "foo"
with tf.variable_scope("bar")
with tf.variable_scope("baz") as other_scope:
assert other_scope.name == "bar/baz"
with tf.variable_scope(foo_scope) as foo_scope2: #这个变量作用域跳转到到了“foo”中,与前面的作用域 完全不同
assert foo_scope2.name == "foo" # Not changed.
在tf.variable_scope()
中ops的名称
我们讨论 tf.variable_scope
怎么处理变量的名字.但是又是如何在作用域中影响到 其他ops的名字的呢?ops在一个变量作用域的内部创建,那么他应该是共享他的名字,这是很自然的想法.出于这样的原因,当我们用with tf.variable_scope("name")
时,这就间接地开启了一个tf.name_scope("name")
.比如:
with tf.variable_scope("foo"):
x = 1.0 + tf.get_variable("v", [1])
assert x.op.name == "foo/add"
名称作用域可以被开启并添加到一个变量作用域中,然后他们只会影响到ops的名称,而不会影响到变量.
with tf.variable_scope("foo"):
with tf.name_scope("bar"):
v = tf.get_variable("v", [1])
x = 1.0 + v
assert v.name == "foo/v:0"
assert x.op.name == "foo/bar/add"
当用一个引用对象而不是一个字符串去开启一个变量作用域时,我们就不会为ops改变当前的名称作用域.