前言
上一篇博客初步了解了tensorflow
中建立机器学习模型的方法:可以使用eager execution
和graph execution
两种模式,可以使用高级API estimator
中已经封装好的模型,也可以自己创建estimator
,更重要的是我们也可以使用低级API自行设计模型。这里重点研究研究如何使用低级API
主要内容包含:
- 张量、变量
- 构建计算图及其运行
- 可视化
国际惯例,参考博客:
Jupyter Notebooks里的TensorFlow图可视化
张量
**概念:**张量是对矢量和矩阵向潜在的更高维度的泛化,也就是说将一维二维扩展到N维。
**特点:**数据类型和形状(维度)
创建张量
-
tf.Variable:
tf.Variable(<initial-value>, name=<optional-name>)#初始值和名称
-
tf.constant:
tf.constant( value,#初始值 dtype=None,#数据类型 shape=None,#大小 name='Const', verify_shape=False )
-
tf.placeholder:
tf.placeholder( dtype,#类型 shape=None,#大小 name=None )
-
tf.SparseTensor:
SparseTensor(indices, values, dense_shape)
indices:是一个二维整型矩阵 [ N , n d i m s ] [N,ndims] [N,ndims],指定了稀疏张量中非零元素的索引
values:是一维向量 [ N ] [N] [N],指定了indices所指定的非零向量的值
dense_shape:一维向量 [ n d i m s ] [ndims] [ndims]指定了稀疏矩阵的维度
实例
#0阶变量:标量
mammal = tf.Variable("Elephant", tf.string)
ignition = tf.Variable(451, tf.int16)
floating = tf.Variable(3.14159265359, tf.float64)
its_complicated = tf.Variable(12.3 - 4.85j, tf.complex64)
#1阶变量:向量
mystr = tf.Variable(["Hello"], tf.string)
cool_numbers = tf.Variable([3.14159, 2.71828], tf.float32)
first_primes = tf.Variable([2, 3, 5, 7, 11], tf.int32)
its_very_complicated = tf.Variable([12.3 - 4.85j, 7.5 - 6.23j], tf.complex64)
#二阶变量:矩阵
mymat = tf.Variable([[7],[11]], tf.int16)
myxor = tf.Variable([[False, True],[True, False]], tf.bool)
linear_squares = tf.Variable([[4], [9], [16], [25]], tf.int32)
squarish_squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)
rank_of_squares = tf.rank(squarish_squares)
mymatC = tf.Variable([[7],[11]], tf.int32)
#更高阶:n维矩阵
my_image = tf.zeros([10, 299, 299, 3]) # 批 x 高 x 宽 x 通道
张量切片
-
对于0阶张量(标量),无需索引,因为它本身就是一个数字
-
对于1阶张量(向量),通过单一索引访问
a=tf.constant([0.1,0.5,0.12,0.6,0.7]) sess=tf.Session() print(sess.run(a[2]))#0.12
-
对于2阶张量(矩阵),两个索引返回标量,一个索引返回向量
a=tf.constant([[0.5,0.6,0.3,0.7],[1,6,7,2]]) print(sess.run(a[1,3]))#2.0 print(sess.run(a[1]))#[1. 6. 7. 2.] print(sess.run(a[:,1]))#[0.6 6. ] print(sess.run(a[1,:]))#[1. 6. 7. 2.]
获取张量的秩和维度
这里秩和数学上的秩不同,这个秩指的是多少维
print(sess.run(tf.rank(a)))#2
print(a.shape)#(2,4)
张量形状的改变
在保证元素个数相同的情况下改变张量维度,其实跟reshape
一样
rank_three_tensor = tf.ones([3,4,5])
matrix = tf.reshape(rank_three_tensor, [6, 10])
matrixB = tf.reshape(matrix, [3, -1])
matrixAlt = tf.reshape(matrixB, [4, 3, -1])
# 一定要保证改变维度前后,矩阵的元素个数相同
#yet_another = tf.reshape(matrixAlt, [13, 2, -1])#出错
print(matrix.shape)#(6, 10)
print(matrixB.shape)#(3, 20)
print(matrixAlt.shape)#(4, 3, 5)
张量数据类型的改变
强制类型转换,主要使用tf.cast
函数
float_tensor = tf.cast(tf.constant([1, 2, 3]), dtype=tf.float32)
张量取值
直接调用eval()
方法,注意必须在Session
启动的时候调用
a=tf.constant([1,2,3,7])
#print(a.eval())#出错
with tf.Session() as sess:
print(a.eval())#[1 2 3 7]
但是有时候也无法取值,比如变量存在容器placeholder
中的时候:
b=tf.placeholder(tf.float32)
with tf.Session() as sess:
print(b.eval())#报错
print(b.eval(feed_dict={b:3.0}))#3.0
有时候最好还是记一下报错内容,便于后期调试:
InvalidArgumentError (see above for traceback): You must feed a value for placeholder tensor 'Placeholder_2' with dtype float
张量赋值
使用上一节提到过的assign
方法,但是注意要初始化,以及赋值要在session中执行。
h=tf.Variable(10,name='h')
init=tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
print(h.eval()) #10
assign_op=tf.assign(h,5)
sess.run(assign_op)
print(h.eval()) #5
变量
在官方文档中,虽然介绍了使用tf.Variable
创建变量,但是又介绍了另一种方法tf.get_variable
,从名字上看像是获取变量,这就是它的优势所在,既能创建变量还能检测变量是否已经创建,如果创建过,就可以重复使用。看一下两种方法的调用方式:
#variable创建方式
tf.Variable(<initial-value>, name=<optional-name>)
#get_variable创建方式
tf.get_variable(
name,
shape=None,
dtype=None,
initializer=None,
regularizer=None,
trainable=True,
collections=None,
caching_device=None,
partitioner=None,
validate_shape=True,
use_resource=None,
custom_getter=None,
constraint=None
)
很明显的区别是:tf.Variable
需要指定初始值,但是名字是选填,而tf.get_variable
无需指定初始值,因为它有自己的初始器initializer
,但是名字是必填的,因为涉及到重用问题
##变量创建
使用get_variable
定义变量时必须指定变量名称,其它副本将使用此名称访问同一变量
#创建名为`my_variable`的变量,大小为[1,2,3],值将通过`glorot_uniform_initializer`随机赋值
my_variable=tf.get_variable('my_vairable',[1,2,3])
#也可以指定类型和初始化方法
my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], dtype=tf.int32,
initializer=tf.zeros_initializer)
#也可以用constant赋值,此时无需指定形状
other_variable = tf.get_variable("other_variable", dtype=tf.int32,
initializer=tf.constant([23, 42]))
##变量集合
便于以一种方式访问所有变量,默认情况下,有两种集合:
tf.GraphKeys.GLOBAL_VARIABLES
:可以在多台设备间共享变量tf.GraphKeys.TRAINABLE_VARIABLES
:将计算梯度的变量
如果不希望变量可训练,就放到tf.GraphKeys.LOCAL_VARIABLES
中,或者指定trainable=False
my_local=tf.get_variable('my_local',shape=(),collections=[tf.GraphKeys.LOCAL_VARIABLES])
my_non_trainable=tf.get_variable('my_none_trainable',shape=(),trainable=False)
这是在创建的时候丢进去,也可以先创建一个属于自己的集合,然后挨个加:
tf.add_to_collection('my_collection_name',my_local)
tf.add_to_collection('my_collection_name',my_non_trainable)
tf.get_collection('my_collection_name')
'''
[<tf.Variable 'my_local:0' shape=() dtype=float32_ref>,
<tf.Variable 'my_none_trainable:0' shape=() dtype=float32_ref>]
'''
##变量初始化
变量初始化作用在于:允许你从检查点加载模型的时候无需重新运行潜在资源消耗大的初始化器,并允许在分布式设置中共享随机初始化的变量时具有确定性。可以选择一次性初始化或者是单独初始化:
-
如需一次性初始化所有可训练变量,调用
tf.global_variables_initializer()
,函数返回一个操作,负责初始化tf.GraphKeys.GLOCAL_VARIABLES
集合中所有变量:session.run(tf.global_variables_initializer())
-
如果需要自行初始化变量,可以使用:
session.run(my_variable.initializer)
可以查询到哪些变量未初始化:
print(session.run(tf.report_uninitialized_variables()))
需要注意的一点是:tf.global_variables_initializer
不会指定变量的初始化顺序,因此,如果变量的初始值取决于另一变量的值,那么很有可能会出现错误。如果在初始化某个变量时使用了另一个变量值,最好使用variable.initialized_value()
,而非variable
:
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = tf.get_variable("w", initializer=v.initialized_value() + 1)
##变量使用
当成常规变量使用就行了,可以使用assign
、assign_add
等方法:
import tensorflow as tf
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
sess=tf.Session()
sess.run(tf.global_variables_initializer())
sess.run(assignment) #1.0
在某个事件发生后,强制读取某个变量的值:
with tf.control_dependencies([assignment]):
w=v.read_value()
sess.run(w)
此程序每运行一次,
w
w
w的值就加1,看样子这个tf.control_dependcies
是强制运行所指定语句,依据执行结果进入内部操作,这个可以看上一篇博客的Assert
条件语句部分。
##共享变量
终于还是到这里了,在theano
中是直接使用theano.shared
创建共享变量。为了理解tensorflow
中的共享变量使用方法,直接看官网的实例:
编写一个函数创建卷积/relu层:
def conv_relu(input,kernel_shape,bias_shape):
#创建权重
weights=tf.get_variable('weights',kernel_shape,initializer=tf.random_normal_initializer())
#创建偏置
biases=tf.get_variable('biases',bias_shape,initializer=tf.constant_initializer(0.0))
conv=tf.nn.conv2d(input,weights,strides=[1,1,1,1],padding='SAME')
return tf.nn.relu(conv+biases)
如果在多次卷积操作中,如果多次调用次函数,就会出现不清晰的操作,因为第一次执行此函数的时候已经创建了权重和偏置,那么下一次是重复利用还是创建新变量呢?这就导致程序报错
input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32]) # This fails.
错误内容:
ValueError: Variable weights already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at:
如果是想创建新变量,可以为每个操作添加不同的作用域:
def my_image_filter(input_images):
with tf.variable_scope('conv1'):
#"conv1/weights", "conv1/biases"
relu1=conv_relu(input_images,[5,5,32,32],[32])
with tf.variable_scope('conv2'):
#"conv2/weights", "conv2/biases"
return conv_relu(relu1,[5,5,32,32],[32])
如果是共享变量,有两种方法,一种是resuse=True
创建相同名称的作用域
#第一种写法
with tf.variable_scope('model'):
output1=my_image_filter(input1)
with tf.variable_scope('model',reuse=True):
output2=my_image_filter(input2)
#第二种写法
with tf.variable_scope('model') as scope:
output1=my_image_filter(input1)
with tf.variable_scope(scope,reuse=True):
output2=my_image_filter(input2)
或者直接调用scope.reuse_variables()
触发重用:
with tf.variable_scope('model') as scope:
output1=my_image_filter(input1)
scope.reuse_variables()
output2=my_image_filter(input2)
计算图的构建与运行
tf.Graph
包含两类信息:
- 图结构:包括节点和边缘,表示各个操作组合在一起
- 图集合:在图中存储元数据集合的通用机制。与变量集合用到的方法一样
tf.add_to_collection
构建图的时候:通过tf.Operation
构建图节点,tf.Tensor
构建图边缘
运行图的时候用tf.Session
即可
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
output = tf.nn.softmax(y)
init_op = w.initializer
with tf.Session() as sess:
sess.run(init_op)
print(sess.run(output))
y_val, output_val = sess.run([y, output])
比较方便的一点是,tf.Session.run
支持喂数据,在执行时使用Feed
字典替换张量值
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)
with tf.Session() as sess:
print(sess.run(y, {x: [1.0, 2.0, 3.0]})) # => "[1.0, 4.0, 9.0]"
print(sess.run(y, {x: [0.0, 0.0, 5.0]})) # => "[0.0, 0.0, 25.0]"
sess.run(y)#报错,必须喂数据
sess.run(y, {x: 37.0})
其实还可以多个图进行编程,但是为了便于消化,先不做了解了,做个备注,以后戳这里学习
图的可视化
使用graphviz可视化
创建一个函数,接收的是tensorflow
创建的图模型,然后分别将节点和边存储到dot中
def tf_to_dot(graph):
dot=Digraph()
for n in g.as_graph_def().node:
dot.node(n.name,labels=n.name)
for i in n.input:
dot.edge(i,n.name)
return dot
添加一个实例,调用上面的函数
g=tf.Graph()
with g.as_default():
X=tf.placeholder(tf.float32,name='X')
W1=tf.placeholder(tf.float32,name='W1')
b1=tf.placeholder(tf.float32,name='b1')
a1=tf.nn.relu(tf.matmul(X,W1)+b1)
W2=tf.placeholder(tf.float32,name='W2')
b2=tf.placeholder(tf.float32,name='b2')
a2=tf.nn.relu(tf.matmul(a1,W2)+b2)
W3=tf.placeholder(tf.float32,name='W3')
b3=tf.placeholder(tf.float32,name='b3')
y_hat=tf.matmul(a2,W3)+b3
tf_to_dot(g)
结果:
使用tensorboard可视化
只需要将第一种方法的可视化算法换成如下即可:
import tensorflow as tf
g=tf.Graph()
with g.as_default():
X=tf.placeholder(tf.float32,name='X')
W1=tf.placeholder(tf.float32,name='W1')
b1=tf.placeholder(tf.float32,name='b1')
a1=tf.nn.relu(tf.matmul(X,W1)+b1)
W2=tf.placeholder(tf.float32,name='W2')
b2=tf.placeholder(tf.float32,name='b2')
a2=tf.nn.relu(tf.matmul(a1,W2)+b2)
W3=tf.placeholder(tf.float32,name='W3')
b3=tf.placeholder(tf.float32,name='b3')
y_hat=tf.matmul(a2,W3)+b3
tf.summary.FileWriter('logs',g).close()#换成这个
检查一下logs
文件夹中是否有events
文件,最后我们就可以去logs
的上级目录打开cmd窗口输入:
tensorboard --logdir=logs
赋值网址,在浏览器中打开,便可以看到模型
有时候网络结构太大了,我们可以把每层的具体运算用scope
封装起来命个名,比如第一层,第二层啥的:
import tensorflow as tf
g=tf.Graph()
with g.as_default():
X=tf.placeholder(tf.float32,name='X')
with tf.name_scope('Layer1'):
W1=tf.placeholder(tf.float32,name='W1')
b1=tf.placeholder(tf.float32,name='b1')
a1=tf.nn.relu(tf.matmul(X,W1)+b1)
with tf.name_scope('Layer2'):
W2=tf.placeholder(tf.float32,name='W2')
b2=tf.placeholder(tf.float32,name='b2')
a2=tf.nn.relu(tf.matmul(a1,W2)+b2)
with tf.name_scope('Layer3'):
W3=tf.placeholder(tf.float32,name='W3')
b3=tf.placeholder(tf.float32,name='b3')
y_hat=tf.matmul(a2,W3)+b3
tf.summary.FileWriter('logs',g).close()
重复上述操作,粘贴网址到浏览器得到:
#后记
这篇博客感觉学的有点杂乱,打算大体印象还是重复学习和进一步探索了变量的操作、运算图的构建和Session
运行,最有用的是学了tensorboard可视化网络结构,而且很容易,就是一句话tf.summary.FileWriter('logs',g).close()
即可。
还有关于使用GPU和TPU的相关内容没看,感觉初步入门的话,先把运算搞清楚再说,内存什么的以后遇到问题再折腾,有兴趣的可以去官网看看,下面有链接,这部分内容主要包含:
- 变量的设备放置方式:如果不小心将变量放在工作器而不是参数服务器上,可能会严重减慢训练速度,最坏的情况下,可能会让每个工作器不断复制各个变量。
- 运算或张量的设备分配:自动分配,手动分配,多GPU