记录自己学习TensorFlow的过程,因为正在做的项目需要将机器学习与esp32结合使用,想要将机器学习的过程也放在esp32上实现。因为tensorflow lite可以实现代码向嵌入式设备的转换,所以开始学习tensorflow。目前内容都基于郑泽宇、顾思宇老师的著作:《TensorFlow实战Google深度学习框架》
一、基础知识:
简而言之,TF是一种开源计算框架,该框架可以很好的实现各种深度学习的算法。
- 最大优势之一:对于大规模模型训练,可以简单实现并行,同时用不同的硬件资源,同步或异步地更新模型状态和参数。将一个串行的TF算法转变为并行成本也很低。
- 缺点:有些复杂,实现同样功能pytorch的代码量可能只有十分之一。
1.1 计算模型--计算图
- tensor是张量,可以理解为多维数组
- flow是流动,因为张量之间通过计算互相转化
TF中所有计算,都会被转化为计算图上的节点。节点之间的边(连线),描述了计算之间的以来关系。
这里表示了a+b。a,b指向加法算子add,add依赖于a,b但是ab不依赖与add。
TF会自动将定义的计算转化为计算图上的节点
在TF中系统会自动维护一个默认的计算图,可以通过tf.get_default_graph获取
a= tf.constant([1,2],name="a")
print(a.graph is tf.get_default_graph())
还可以通过tf.Graph创建新的计算图,并且不用图上的张量和运算不会共享。
# 在计算图g1中,定义变量v,设初值为0
g1=tf.Graph()
with g1.as_default():
v=tf.get_variable("v",shape=[1],initializer=tf.zeros_initializer)
# 在计算图g2,也定义变量v,但设初值为1
g2=tf.Graph()
with g2.as_default():
v=tf.get_variable("v",shape=[1],initializer=tf.ones_initializer)
# 读取g1计算图中的v
with tf.Session(graph=g1) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("",reuse=True):
print(sess.run(tf.get_variable("v")))
# 读取g2计算图中的v
with tf.Session(graph=g2) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("",reuse=True):
print(sess.run(tf.get_variable("v")))
shape=[1]代表变量的大小,若为shape[1,4],会产生四个变量。
计算图还可以通过tf.Graph.device指定运行计算的设备。下面的程序将加法计算跑在GPU上:
g=tf.Graph()
with g.device('/gpu:0'):
result = 1 + 2
1.2 数据模型——张量
1.2.1 张量概念
零阶张量表示标量,也就是一个数。一阶张量为向量,也就是一个一维数组。但张量在TF中的实现并不是直接采用数组的形式,他只是对运行算结果的引用。在张量中并没有真正保存数字,他保存的是如何得到这些数字的计算过程。例如运行如下计算加法的代码并不会的道家发的结果,而是对结果的引用:
a= tf.constant([1,2],name="a")
b= tf.constant([2,3],name="b")
result=tf.add(a,b,name="add")
print(result)
结果如下:
Tensor("add:0", shape=(2,), dtype=int32)
从结果可以看出:一个张量中主要保存了三个属性:名字,维度,类型。
名字不仅是一个张量的唯一标识符,它同样也给出了这个张量是如何计算出来的。因为TF所有计算都可以通过计算图的模型来建立,而计算图上的每一个节点代表了一个计算,计算的结果就保存在张量之中。所以张量和计算图上节点所代表的计算结果是对应的。这样张量的命名就可以通过:'node:src_output' 的形式来给出。node为节点的名称,src_output表示当前张量来自节点的第几个输出。比如上面代码的结果:“add:0”代表add节点的第一个输出。
维度,上面结果中的shape=(2,)说明张量是一个一维数组,这个数组的长度为2。
类型,每一个张量会有一个唯一的类型。TF会对所有参与运算的张量进行类型的检查,不匹配时会报错。如下:一个是整数,一个是浮点
import tensorflow as tf
a = tf.constant([1,2],name='a')
b = tf.constant([3.0,2.0],name='b')
result = a+b
可以通过a = tf.constant([1,2],name='a',dtype=tf.float32)对a的类型进行改变。
TF中共有十四种不同类型:
- tf.float32,tf.float64.
- tf.int8,tf.int16,tf.int32,tf.int64,tf.uint8(八位无符号整型)
- tf.bool
- tf.complex64,tf.complex128
1.2.2 张量的使用
张量使用主要可以分为两大类:
- 对中间计算结果的引用:以下为使用张量和不适用张量记录中间结果的代码对比:
#使用张量记录中间结果:
a = tf.constant([1.0,2.0],name="a")
b = tf.constant([2.0,3.0],name="b")
result = a+b
#直接计算
result = tf.constant([1.0,2.0],name="a")+tf.constant([2.0,3.0],name="b")
a,b就是对于常量生成这个运算结果的引用。当计算的复杂度增加时,通过张量来引用中间结果可以大大提升代码可阅读性
2.当计算图构造完成后,张量用来获得计算结果,也就是会得到真实的数字,虽然张良本身没有存储具体数字,但是根据之后将会介绍的会话(session),就可以得到具体的数字。
1.3 运行模型--会话(session)
会话用来执行定义好的计算。session拥有并管理TF程序运行时的所有资源。当计算完成之后需要关闭会话来帮助系统回收资源。
利用会话的一般格式:
- 创建会话
- 用run计算出会话中感兴趣的值
- 结束会话
a=tf.constant(1,name="a")
b=tf.constant(2,name="b")
result=a+b
with tf.Session() as sess: #不必再调用session.close
print(sess.run(result))
还可以用eval方法直接计算一个张量的值。
注意:
- eval是张量的方法,run是会话的方法,而会话一般属于默认运算图(如果没有指定)。
- TF会自动生成默认运算图,但不会自动生成默认的会话,必须指定。
a=tf.constant(1,name="a")
b=tf.constant(2,name="b")
result=a+b
sess=tf.Session()
print(result.eval(session=sess)) # 必须有session=sess选项,No default session.
sess.close()
a=tf.constant(1,name="a")
b=tf.constant(2,name="b")
result=a+b
sess=tf.InteractiveSession() # 该函数自动将生成的会话注册为默认会话
print(result.eval())
sess.close()
注意:
- 指定会话,在会话中run
- 指定会话,在会话中eval目标张量
- 指定默认会话,直接eval目标张量
sess = tf.Session()
with sess.as_default():
with版本的设置默认会话。
ConfigProto Protocol Buffer可以增强配置
该结构可以配置并行的线程数,GPU分配策略,运算超时时间等参数。
其中最常用的是以下两个参数:
- 布尔型参数log_device_placement
当其为true时,日志将会记录在每个节点被安排在哪个设备上,方便调试。
- 布尔型参数allow_soft_placement
默认为False,当其为true时,只要以下任意一个条件成立,GPU上的运算都会放到CPU上运行:
- 运算在GPU上无法运行
- 没有指定GPU资源,比如只有一个GPU,但运算指定在第二个GPU上执行
- 运算输入包含对CPU运算结果的引用。
config = tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True)
sess1 = tf.InteractiveSession(config=config) # 创建默认会话
sess2 = tf.Session(config=config) # 创建一般会话