首先是Tensorflow的安装,由于可能会出现版本冲突,最好在conda环境安装,同时,目前windows版本好像只支持2.10,更高的版本目前只支持linux系统。
conda 安装, 配置以及使用_conda安装配置-CSDN博客
23.10.02更新 windows系统下的Tensorflow安装(图多详细)_windows 安装tensorflow-CSDN博客
23.10.02更新 Windows下CUDA和CUDNN的安装和配置(图多详细)_cuda百度云-CSDN博客
GPU涉及到显卡,这里可以在命令行输入nvidia-smi
获取GPU信息
安装顺序为:查看显卡信息 -> 安装CUDA,CUDNN -> 安装Tensorflow GPU版
list 在 numpy 中叫 ndarray,在 tensorflow 中叫 tensor,其性能上的主要区别是:ndarray只能在CPU上计算,而tensorflow不仅可以在CPU上计算,也可以在GPU/TPU上计算。
tensor 和 numpy 一样,有三个属性:TensorObject.ndim、 TensorObject.shape、 TensorObject.dtype。分别表示数据维度,数据形状,数据类型。只需要在tensor后面加一个numpy(),如:TensorObject.numpy()。就可以很轻松的将tensor转化为numpy。
import tensorflow as tf
基本操作
数据转换和数据生成
首先是将已有数据转化为tensor,其中tensor有两种,一种是常数,一种是变量,其中常数是不可以对数值进行更改的,而变量的值是可以更改的。
lst = [1.123, 2.333, 4.12212] # 获得常数 tensor = tf.constant(lst) # 获得变量 tensor_variable = tf.Variable(lst) ## 更改变量 tensor_variable.assign([1.3,1.3,1.3]) # tensor转换为numpy array = tensor.numpy() # numpy转化为tensor tensor = tf.constant(array) tensor_variable = tf.Variable(array) tensor = tf.convert_to_tensor(array) # 拓展:创建不规则张量 tensor = tf.ragged.constant([ [0, 1, 2, 3], [4, 5], [6, 7, 8], [9]])
数据生成主要是random,range,linspace;这三个函数对应于numpy中的random,arange,linspace
## range 生成0-100之间步长为3的tensor tf.range(0,100,3) ## linspace 在0-100之间等距离生成3个数的tensor tf.linspace(0,100,3) ## random示例:normal 生成形状为[10,20]的正态分布矩阵 tf.random.normal(shape=[10,20])
操作形状
操作形状与numpy不一样,numpy可以直接调用,tensor不能,需要tf.reshape()
tf_const = tf.random.normal(shape=[10,20]) ## 展开为1维向量 tf_const = tf.reshape(tf_const, -1)
tensorflow 采取 行优先
内存访问顺序,一般来说,tf.reshape 唯一合理的用途是合并或者拆分相邻轴
进行轴变换需要用到tf.transpose()
tf_const = tf.random.normal(shape=[10,20,30]) ## 使原来的0,1,2 变成 1,2,0 tf.transpose(tf_const, [1,2,0])
数据提取和保存
张量切片与NumPy切片一样,也是基于索引。切片或者索引是Python语言中针对字符串、元祖或者列表进行读写的魔法方法,在第1章介绍NumPy的时候也提到过,针对NumPy数组,我们也可以进行索引或者切片操作。同样的,我们也可以对TensorFlow里面的张量进行索引或者切片操作,并且遵循Python语言或者说NumPy数组的索引规则。
- 索引从下标0开始。
- 负索引按照倒叙进行索引,比如 -1表示倒数第一个元素。
- 切片的规则是start:stop:step。
- 通过制定多个索引,可以对多维度张量进行索引或者切片。
变量
tensorflow 与 numpy 最不同的一点就是 tensorflow 有变量。
变量是深度学习在训练模型时用来存储和更新参数的,在创建的时候必须要初始化才能使用,即一定要赋值。变量和常量除了定义方式以外以及相关操作一致。
Numpy和Tensorflow的比较
操作类别 | NumPy | TensorFlow 2+ |
数据类型 | np.ndarray | tf.Tensor |
np.float32 | tf.float32 | |
np.float64 | tf.double | |
np.int64 | tf.int64 | |
从已有数据构建 | np.array([3.2, 4.3], dtype=np.float16) | a=tf.constant([3.2, 4.3], dtype=tf.float16)#常量 v=tf.Variable([3.2, 4.3], type=tf.float16)#变量 |
x.copy() | tf.identity(x);tf.tile(a,(n,m))# 元组里的每个数值对应该轴复制次数 | |
np.concatenate | tf.concat((a,b),axis)# 待拼接的轴对应的维度数值可以不等,但其他维度形状需一致 | tf.stack((a,b),axis)# 带堆叠张量的所有维度数值必须相等 |
线性代数 | np.dot #内积 np.multiply(*)#逐元素相乘或哈达玛积 | tf.matmul(x, y, name=None) 或(@)#内积tf.multiply(x, y, name=None),或(*)#逐元素相乘或哈达玛积 |
属性 | x.ndim | x.ndim |
x.shape | x.shape | |
x.size | tf.size(x) | |
改变形状 | x.reshape | tf.reshape(x,(n,(-1)))#-1表示自动计算其他维度 |
np.transpose(x, [新的轴顺序] ) | tf.transpose(x, [新的轴顺序] ) | |
x.flatten() | tf.reshape(x,[-1]);tf.keras.layers.Flatten() | |
维度增减 | np.expand_dims(arr, axis) | tf.expend_dims(a,axis) |
np.squeeze(arr, axis) | tf.squeeze(a,axis),#如果不声明axis,那么将压缩所有数值为1的维度。 | |
类型转换 | np.floor(x) | x=tf.cast(x,dtype=XX) x=x.numpy()=>np.array |
比较 | np.less | tf.less(x,threshold) |
np.less_equal | tf.less_equal(x, threshold) | |
np.greater_equal | tf.greater_equal(x, threshold) | |
随机种子 | np.random.seed | tf.random.set_seed(n) |
显示详细信息
计算图
计算图类似于一个计算过程,具体可以看机器学习入门(10)— 浅显易懂的计算图、链式法则讲解_请画出该函数的计算图,请用方形节点表示-CSDN博客
Tensorflow目前有三种图:很拉跨的静态图,效率低的动态图以及方便的自动图。
TensorFlow有3种计算图:TensorFlow1.0时代的静态计算图,TensorFlow 2.0时代的动态计算图和Autograph。静态计算图,需要先使用TensorFlow的各种算子创建计算图,再开启一个会话(Session)执行计算图。 而在TensorFlow 2.0时代,默认采用的是动态计算图,即每使用一个算子后,该算子会被动态加入隐含的默认计算图中立即执行并获取返回结果,而无须执行Session。 使用动态计算图(即Eager Excution立即执行)的好处是方便调试程序,执行TensorFlow代码犹如执行Python代码一样,而且可以使用Python,非常便捷。不过使用动态计算图的坏处是运行效率相对会低一些,因为在执行动态图期间会有许多次Python进程和TensorFlow的C++进程之间的通信。而静态计算图不通过Python这个中间环节,基本在TensorFlow内核上使用C++代码执行,效率更高。 为了兼顾速度与性能,在TensorFlow 2.0中可以使用@tf.function装饰器将普通Python函数转换成对应的TensorFlow计算图构建代码。与执行静态图方式类似,使用@tf.function构建静态图的方式叫作Autograph(自动图)
静态图
其中静态图是1.0版本的产物,很拉,大概是这样子
import tensorflow as tf #定义计算图 grap = tf.compat.v1.Graph() with grap.as_default(): #placeholder为占位符,执行会话时候指定填充对象 x = tf.compat.v1.placeholder(tf.float32,shape=[],name='x') y = tf.compat.v1.placeholder(tf.float32,shape=[],name='y') b = tf.compat.v1.Variable(15.0,dtype=tf.float32) z=tf.multiply(x,y,name='c')+b #初始化参数 init_op = tf.compat.v1.global_variables_initializer() #执行计算图 with tf.compat.v1.Session(graph = grap) as sess: sess.run(init_op) print(sess.run(fetches = z,feed_dict = {x:20,y:36}))
可以看到,先要创建Graph,然后要把计算写入Graph中,接着再进行计算。
动态图
以上代码如果采用动态计算图的方式实现,需要做如下处理。
1)把占位符改为其他张量,如tf.constant或tf.Variable。
2)无须显式创建计算图。
3)无须变量的初始化。
4)无须执行Session,把sess.run中的feed_dict改为传入函数的参数,fetches改为执行函数即可。
采用TensorFlow 2.0动态图执行的方式,代码如下:
import tensorflow as tf #定义常量或变量 x=tf.constant(20,dtype=tf.float32) y=tf.constant(36,dtype=tf.float32) #定义函数 def mul(x,y): #定义常量或变量 b=tf.Variable(15 ,dtype=tf.float32) z=tf.multiply(x,y,name='c')+b return z #执行函数 print(mul(x,y).numpy())
与静态计算图相比,可以看到动态计算图虽然调试编码效率高但是执行效率偏低
自动图
TensorFlow 2.0 之后的自动图(AutoGraph)可以将动态计算图转换成静态计算图,兼顾开发效率和执行效率。通过给函数添加@tf.function装饰器就可以实现AutoGraph功能,但是在编写函数时需要遵循一定的编码规范,否则可能达不到预期的效果,这些编码规范主要包括如下几点。
- 避免在函数内部定义变量(tf.Variable)。
- 函数体内应尽可能使用TensorFlow中的函数而不是Python语言自有函数。比如使用tf.print而不是print,使用tf.range而不是range,使用tf.constant(True)而不是True。
- 函数体内不可修改该函数外部的Python列表或字典等数据结构变量。
用@tf.fuction装饰函数,把动态计算图转换为自动图如下:
import tensorflow as tf #定义常量或变量 x=tf.constant(20,dtype=tf.float32) y=tf.constant(36,dtype=tf.float32) b=tf.Variable(15 ,dtype=tf.float32) #定义函数 @tf.function def mul(x,y): # 定义常量或变量 # b=tf.Variable(15 ,dtype=tf.float32) # 不可以在自动图里定义变量 z=tf.multiply(x,y,name='c')+b return z #执行函数 print(mul(x,y).numpy())
这样看起来不爽,可以用类包装一下就好看了
import tensorflow as tf #定义一个类 class Test_Mul: def __init__(self): super(Test_Mul, self).__init__() self.b=tf.Variable(15 ,dtype=tf.float32) @tf.function def mul(self,x,y): z=tf.multiply(x,y,name='c')+self.b return z #执行函数 x=tf.constant(20,dtype=tf.float32) y=tf.constant(36,dtype=tf.float32) Test=Test_Mul() print(Test.mul(x,y).numpy())
自动微分
构建计算图后,肯定是需要计算微分求导数的,tensorflow深度学习架构帮助我们自动地完成了求梯度运算。 Tensorflow一般使用梯度磁带tf.GradientTape来记录正向运算过程,然后使用反播磁带自动得到梯度值。
按照上述流程进行一次微分
import tensorflow as tf import numpy as np # f(x) = a*x**2 + b*x + c的导数 #缺省情况,张量tf.constant为常量,只有变量tf.Variable作为参数更新 x = tf.Variable(0.0,name = "x",dtype = tf.float32) a = tf.constant(1.0) b = tf.constant(5.0) c = tf.constant(2.0) with tf.GradientTape() as tape: tape.watch([a,b,c]) # 这里abc是张量,如果不需要观察张量的导数,可以删掉 y = a*tf.pow(x,2) + b*x + c dy_dx,dy_da,_,dy_dc = tape.gradient(y,[x,a,b,c]) print(dy_da) print(dy_dc)
二次微分可以使用嵌套的方式:
with tf.GradientTape() as tape2: with tf.GradientTape() as tape1: y = a*tf.pow(x,2) + b*x + c dy_dx = tape1.gradient(y,x) dy2_dx2 = tape2.gradient(dy_dx,x) print(dy2_dx2)
这里要注意的是:梯度磁带会自动监视 tf.Variable,但不会监视 tf.Tensor。如果无意中将变量(tf.Variable)变为常量(tf.Tensor)(如tf.Variable 与一个tf.Tensor相加,其和就变成常量了),梯度磁带将不再监控tf.Tensor。 为避免这种情况,可使用 Variable.assign 给tf.Variable赋值
x = tf.Variable(2.0) for epoch in range(2): with tf.GradientTape() as tape: y = x+1 dy_x=tape.gradient(y, x) #print(type(x).__name__, ":", tape.gradient(y, x)) print(dy_x) #变量变为常量tf.Tensor x = x + 1 # This should be `x.assign_add(1)`
最后要记得删除tape!!!
del tape
使用Tensorflow 实现回归
上一节numpy一样,设置一样的函数如下:
y = 3 x 2 + 2 x + 1 y=3x^2+2x+1 y=3x2+2x+1
图像如下:
假设知道最高项为3,设函数为: y = a x 2 + b x + c y=ax^2+bx+c y=ax2+bx+c
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt ## 准备数据 np.random.seed(42) x = np.linspace(-10, 10, 50) y = 3 * np.power(x, 2) + 2 * x + 1 a = np.random.random(size=(1, 1)) b = np.random.random(size=(1, 1)) c = np.random.random(size=(1, 1)) ## 定义模型 class LinearRegression: def __init__(self): self.a = tf.Variable(a) self.b = tf.Variable(b) self.c = tf.Variable(c) def __call__(self, x): return self.a * tf.square(x) + self.b * x + self.c model = LinearRegression() ## 定义损失 @tf.function def compute_loss(y, y_pred): return tf.reduce_mean(tf.square(y-y_pred)) ## 定义训练过程 def train_one_epoch(x, y, lr=1e-4): with tf.GradientTape() as tape: y_pred = model(x) loss = compute_loss(y, y_pred) a, b, c = model.a, model.b, model.c da, db, dc = tape.gradient(loss, [a, b, c]) a.assign(a - lr*da) b.assign(b - lr*db) c.assign(c - lr*dc) return loss.numpy() ## 计算loss loss_list = [] for i in range(30): loss = train_one_epoch(x, y) loss_list.append(loss) ## 画出loss图 plt.plot(loss_list)
得到的损失变化如下:
这里学习率过高会导致不收敛,出现loss反而变大的情况,同时学习率过低可以会导致loss下降得很慢,因此这里可以使用官方定义的优化器来进行梯度更新,这会减少assign代码。
## 定义训练过程 def train_one_epoch(x, y, lr=1e-4): with tf.GradientTape() as tape: y_pred = model(x) loss = compute_loss(y, y_pred) a, b, c = model.a, model.b, model.c da, db, dc = tape.gradient(loss, [a, b, c]) a.assign(a - lr*da) b.assign(b - lr*db) c.assign(c - lr*dc) return loss.numpy() # 替换成 ## 定义优化器 opt = tf.keras.optimizers.Adam(learning_rate=1e-1) ## 定义训练过程 def train_one_epoch(x, y): with tf.GradientTape() as tape: y_pred = model(x) loss = compute_loss(y, y_pred) a, b, c = model.a, model.b, model.c da, db, dc = tape.gradient(loss, [a, b, c]) opt.apply_gradients(grads_and_vars=zip([da,db,dc], [a,b,c])) return loss.numpy()
如果要使用minimize这种更为简单的形式,这里我们需要把loss计算整合一下:
@tf.function def get_loss(): y_pred = model(x) loss = compute_loss(y, y_pred) return loss ## 定义训练过程 def train_one_epoch(x, y): a, b, c = model.a, model.b, model.c opt.minimize(get_loss, [a, b, c]) return get_loss()