TensorFlow基础
#这一章更加系统的讲述了TensorFlow的基础知识
(注:保存和分享一下笔记>^<)
#检验了一下自己发现自己有关机器学习的知识并不是掌握的那么好这里再重修一遍,使知识系统起来。
数据类型
数据类型,包含数值类型,字符串类型和布尔类型。
数值类型
数值类型是TensorFlow的主要数据载体,根据维度数来区分可分为:
标量:单个的实数,维度数为0,shape为[]。
向量:n个实数的有有序集合,通过中括号包裹,维度为1,长度不定,shape为[n]。
矩阵:n行m列实数的有序集合如:[[1,2],[3,4]],也可以写成[1,2]维度数为2,shape为[n,m]
[3,4]
张量:所有维度数dim>2的数组统称为张量。张量的每个维度也做轴(Axis),一般维度代表了具体的物理意义,如shape为[28,28,3],两个28分别表示图片的高和宽,而3代表的是三个RGB通道。张量的维度是需要用户自己定义的。
###在Tensorflow中为了方便表达,把向量,标量,矩阵也统称为张量,不做区分由张量的维度和形状自行判断##
import tensorflow as tf
a=1.2
aa=tf.constant(1.2) #创建一个标量
aa1=tf.constant([1.2]) #创建一个一维的张量
print(type(a),type(aa),type(aa1))
运行结果:
<class ‘float’> <class ‘tensorflow.python.framework.ops.EagerTensor’> <class ‘tensorflow.python.framework.ops.EagerTensor’>
将张量转换为numpy形式:
a=[1,2,3,4]
aa=tf.constant(a)
aa=aa.numpy()
print(aa,type(aa))
运行结果:
[1 2 3 4] <class ‘numpy.ndarray’>
##与标量不同的是,向量的定义必须通过List容器传给tf.constant()函数。##
a=tf.constant([1.2]) #创建一个一维的张量,及一个元素的向量组
print(a,a.shape)
运行结果:
tf.Tensor([1.2], shape=(1,), dtype=float32) (1,)
这里有个小技巧这个tensor量可以看成一个类,可以用.shape或.dtype来获取其的属性
a=tf.constant([1.2]) #创建一个一维的张量,及一个元素的向量组
print(a,a.shape,a.dtype)
运行结果:
tf.Tensor([1.2], shape=(1,), dtype=float32) (1,) <dtype: ‘float32’>
定义一个矩阵
a=tf.constant([[1,2],[3,4]])
print(a,a.shape)
运行结果:
tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32) (2, 2)
字符串类型
除了丰富数值类型的张量外,Tensorflow也支持字符串类型的数据用来保存图片路径字符串等其他字符串类型的数据,可以先通过记录路径字符串,再通过预处理函数根据路径读取图片张量。
例:创建一个字符串类型的张量
a=tf.constant("hello ,Deep learning") #创建字符串
print(a,type(a))
运行结果:
tf.Tensor(b’hello ,Deep learning’, shape=(), dtype=string) <class ‘tensorflow.python.framework.ops.EagerTensor’>
在tf.strings模块中,提供了常见的字符串类型的工具函数如:小写化lower(),大写化upper(),拼接*.join(),长度.length(),切分.split()** ,一个去除头部及尾部字符串的方法**strip()返回长度length()**等
#注:具体用法请参考源码
一个小例子:
a=tf.constant("hello ,Deep learning") #创建字符串
print(a,type(a))
aa=tf.strings.lower(a)
print(aa,type(aa))
aaa=tf.strings.upper(a)
print(aaa,type(aaa))
运行结果:
tf.Tensor(b’hello ,Deep learning’, shape=(), dtype=string) <class ‘tensorflow.python.framework.ops.EagerTensor’>
tf.Tensor(b’hello ,deep learning’, shape=(), dtype=string) <class ‘tensorflow.python.framework.ops.EagerTensor’>
tf.Tensor(b’HELLO ,DEEP LEARNING’, shape=(), dtype=string) <class ‘tensorflow.python.framework.ops.EagerTensor’>
布尔类型
为了方便表示比较运算操作的结果,TensorFlow还支持布尔类型(BOOL)的张量。
a=tf.constant(True)
print(a)
运行结果:
tf.Tensor(True, shape=(), dtype=bool)
创建布尔类型的向量
a=tf.constant([True,False])
print(a)
运行结果:
tf.Tensor([ True False], shape=(2,), dtype=bool)
##tensorflow的布尔类型并不等于python的布尔类型##
从本质来讲,tensorflow类型可以看成一个对象,而python的布尔类型只是表示真假。两者并不是同一种类型,所以不能比较。
a=tf.constant([True])
c=tf.constant(True)
d=tf.constant([True])
a1=True
#类型比较
if a is a1:
print("hello")
if a is c:
print("hello1")
if a is d:
print("hello2")
#仅数值比较
if a==a1:
print("hello3")
if a==c:
print("hello4")
if a==d:
print("hello5")
print(a)
运行结果:
hello3
hello4
hello5
tf.Tensor([ True], shape=(1,), dtype=bool)
数值精度
对于数值类型的张量,可以保存为不同字节长度的精度,如浮点数3.14既可以保存为16位(Bit)长度,也可以保存为32位甚至64位的精度。位越长,精度越高,同时占用的内存空间比较大。常用精度类型有:tf.int64,tf.int32,tf.int16,tf.float16,tf.float32,tf.float64等,即使tf.float64,即使tf.double。
在创建张量的时候,可以指定张量的保存精度:
a=tf.constant(123456789,dtype=tf.int64)
aa=tf.constant(123456789,dtype=tf.int16)
print(a,"\n",aa)
运行结果:
tf.Tensor(123456789, shape=(), dtype=int64)
tf.Tensor(-13035, shape=(), dtype=int16)
可以看到当保存精度过低时,数据123456789出现了溢出的现象,一般使用tf.int32,tf。int64精度。
而对于浮点数,高精度的张量可以表示更精准的数据,例如采用tf.float32保存精度保存pi时,实际保存的数据为3.1415927但是如果用tf.float64会获得更高精度。
import numpy as np
a=tf.constant(np.pi,dtype=tf.float32)
aa=tf.constant(np.pi,dtype=tf.float64)
print(a)
print(aa)
tf.Tensor(3.1415927, shape=(), dtype=float32)
tf.Tensor(3.141592653589793, shape=(), dtype=float64)
读取精度
通过dtype成员属性可以判断张量保存精度,例如:
print("before:",a.dtype) #读取原有的数值精度
if a.dtype!=tf.float32: #如果精度不满足要求,则进行转换
a=tf.cast(a,tf.float32) #tf.cast函数可以完成精度转换
print("after:",a.dtype) #打印转换后的精度
类型转换
系统的每一个使用的数据类型,数值精度可能各不相同,对于不符合要求的张量的类型及精度,需要通过tf.cast函数进行转换,例如;
a=tf.constant(np.pi,dtype=tf.float16) #创建tf.float16低精度张量
print(a)
aa=tf.cast(a,tf.double)
print(aa)
运行结果:
tf.Tensor(3.14, shape=(), dtype=float16)
tf.Tensor(3.140625, shape=(), dtype=float64)
进行类型转换的时候,需要保存转换操作的合法性,例如将高精度的张量转换为低精度的张量时,可能发生数据溢出隐患:
a=tf.constant(123456789,dtype=tf.int32)
aa=tf.cast(a,tf.int16)
print(aa)
tf.Tensor(-13035, shape=(), dtype=int16)
布尔类型与整数之间相互也是合法的,是比较常见的操作:
a=tf.constant([True,False])
aa=tf.cast(a,tf.int32)
print(aa)
tf.Tensor([1 0], shape=(2,), dtype=int32)
一般默认0表示False,1表示True,在TensorFlow中,将非0数字视为True,例如:
a=tf.constant([-1,0,1,2])
aa=tf.cast(a,tf.bool)
print(aa)
tf.Tensor([ True False True True], shape=(4,), dtype=bool)
待优化张量
为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,Tensorflow增加了一个专门的数据类型来支持梯度信息的记录:tf.Variable。tf.variable类型在普通张量类型计算上加上了,name,trainable等属性来支持计算图的构建。由于梯度运算会消耗大量的计算资源,切回自动更新相关的参数,对于不需要的优化梯度并优化的张量,如神经网络层的W和B,需要通过tf.variable包裹以便tensorflow跟踪相关梯度信息。
import tensorflow as tf
a=tf.constant([-1,0,1,2])
aa=tf.Variable(a)
print(aa.name,aa.trainable)
代码运行结果:
Variable:0 True
其中:name相当于id,这个一般是由机器自动生成,不需要人来管理,而trainable属性表示当前张量是否需要被优化,可以设为trainable=False来设置张量不需要优化。
注意张量可以直接创建
a=tf.variable([[1,2],[3,4]])
##待优化张量可以看成普通张量的特殊类型,普通张量其实也可以通过GradientTape.watch()方法临时加入跟踪梯度信息的列表,从而支持自动求导功能。
创建张量
list,和numpy.array数组,都是数据的重要载体,这个转换为张量是十分重要的
数组,列表对象创建张量
##这里用一个较老的方法,这个方法源自tensorflow1.0版本
a=tf.convert_to_tensor([1,2])
aa=tf.convert_to_tensor(np.array([[1,2],[3,4]])) #这个numpy浮点数数组默认使用tf.float64如果不合适可以找机会转换
print(a)
print(aa)
运行结果:
tf.Tensor([1 2], shape=(2,), dtype=int32)
tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32)
张量转换为numpy
aa=tf.convert_to_tensor(np.array([[1,2],[3,4]]))
c=aa.numpy()
print(type(c))
张量转换为list
张量转换为list没有直接的转换方法需要转换为numpy后再转换为list
aa=tf.convert_to_tensor(np.array([1,2]))
c=aa.numpy().tolist()
print(type(c))
运行结果:
<class ‘list’>
创建全0或全1张量
a=tf.zeros([])
aa=tf.ones([1])
print(a)
print(aa)
运行结果:
tf.Tensor(0.0, shape=(), dtype=float32) <class ‘tensorflow.python.framework.tensor_shape.TensorShape’>
tf.Tensor(1.0, shape=(1,), dtype=float32)
aaa=tf.zeros([2,2]) #创建一个两行两列的0张量
print(aaa)
运行结果:
tf.Tensor(
[[0. 0.]
[0. 0.]], shape=(2, 2), dtype=float32)
创建一个和某个矩阵相同形状的矩阵
a=tf.ones([3,2])
tf.zeros_like(a) #创建一个和a张量同样形状的零矩阵
tf.ones_like(a) #创建一个和a张量相同的一矩阵
tf.*_like是一系列的便捷函数。可以方便的创造0和1矩阵
reshapeAPI
>>> t1 = [[1, 2, 3],
... [4, 5, 6]]
>>> print(tf.shape(t1).numpy())
[2 3]
>>> t2 = tf.reshape(t1, [6])
>>> t2
<tf.Tensor: shape=(6,), dtype=int32,
numpy=array([1, 2, 3, 4, 5, 6], dtype=int32)>
>>> tf.reshape(t2, [3, 2])
<tf.Tensor: shape=(3, 2), dtype=int32, numpy=
array([[1, 2],
[3, 4],
[5, 6]], dtype=int32)>
创建自定义数值张量
设置全为0,或者1的张量之外,有时需要全部初始化为自定义数值的张量,比如将张量的数值全部转换为-1等。
通过tf.fill(shape,value)可以创建自定义数值value的张量,形状由shape参数指定
#这里附赠fill函数的源码:
a=tf.fill([1],-1) #创建-1的张量
print(a)
tf.Tensor([-1], shape=(1,), dtype=int32)
创建已知分布的张量
正态分布生成
这是其的几个参数
接下来的注释一个一个翻译一下:
输出正态分布的随机值。
每次生成一组新的随机值的示例:
>>> tf.random.set_seed(5);
>>> tf.random.normal([4], 0, 1, tf.float32)
<tf.Tensor: shape=(4,), dtype=float32, numpy=..., dtype=float32)>
输出可重现结果的示例:
>>> tf.random.set_seed(5);
>>> tf.random.normal([2,2], 0, 1, tf.float32, seed=1)
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-1.3768897 , -0.01258316],
[-0.169515 , 1.0824056 ]], dtype=float32)>
在本例中,我们将全局和操作级种子设置为
确保这个结果是可重复的。看到“tf.random。set_seed”更
信息。
参数:
**shape:**一个一维整数张量或Python数组。输出张量的形状。
**mean:**类型为’ dtype ‘的张量或Python值,可通过’ stddev '广播。正态分布的均值。(正太分布对称轴)
**stddev:**类型为’ dtype ‘的张量或Python值,可使用’ mean '进行广播。正态分布的标准差。(跟最高点有关)
**dtype:**输出类型。
seed: Python整数。用于为分发创建随机种子。看到“tf.random.set_seed”的行为。
名称:操作的名称(可选)。
返回:用随机法向值填充的指定形状的张量。
a=tf.random.normal([2,2]) #创建一个2*2的正太分布矩阵,对称轴默认为0,方差为1
aa=tf.random.normal([2,2],mean=1,stddev=2) #创建一个均值为1,方差为2的正太分布
print(a)
print(aa)
运行结果:
tf.Tensor(
[[-0.30189812 1.6794593 ]
[-1.3591436 -0.88294697]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[ 3.2295241 -1.559017 ]
[-1.1709552 -2.1874518]], shape=(2, 2), dtype=float32)
创建采样自[minval,maxval)区间均匀分布的张量。
API的几个参数:
其源码注释翻译:
"从均匀分布中输出随机值。
生成的值在范围内遵循均匀分布“[minval maxval)”。下界’ minval ‘包含在范围内,而上界’ maxval '被排除在外。
对于浮点数,默认范围是’[0,1)’。对于int类型,至少必须有’ maxval '显式地指定。
在整数的情况下,随机整数有轻微的偏置,除非
’ maxval - minval '是2的精确幂。值的偏差很小
’ maxval - minval '明显小于输出范围
“2 * * 32”或“* * 64”)。
Examples:
>>> tf.random.uniform(shape=[2])
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([..., ...], dtype=float32)>
>>> tf.random.uniform(shape=[], minval=-1., maxval=0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-...>
>>> tf.random.uniform(shape=[], minval=5, maxval=10, dtype=tf.int64)
<tf.Tensor: shape=(), dtype=int64, numpy=...>
“种子”论证产生一个确定的张量序列多个调用。要重复这个序列,使用’ tf.random.set_seed ':
>>> tf.random.set_seed(5)
>>> tf.random.uniform(shape=[], maxval=3, dtype=tf.int32, seed=10)
<tf.Tensor: shape=(), dtype=int32, numpy=2>
>>> tf.random.uniform(shape=[], maxval=3, dtype=tf.int32, seed=10)
<tf.Tensor: shape=(), dtype=int32, numpy=0>
>>> tf.random.set_seed(5)
>>> tf.random.uniform(shape=[], maxval=3, dtype=tf.int32, seed=10)
<tf.Tensor: shape=(), dtype=int32, numpy=2>
>>> tf.random.uniform(shape=[], maxval=3, dtype=tf.int32, seed=10)
<tf.Tensor: shape=(), dtype=int32, numpy=0>
没有“tf.random。set_seed ‘但是指定了’ seed '参数,小函数图或先前执行的操作的更改将改变返回值。看到“tf.random。set_seed details.”:
参数:
shape:一个一维整数张量或Python数组。输出张量的形状。
minval:一个“dtype”类型的张量或Python值,可用于广播’ shape '(对于整数类型,不支持广播,因此需要是一个标量)。下界上生成的随机值的范围(包容)。默认值为0。
maxval:类型为’ dtype ‘的张量或Python值,可用于广播’ shape ‘(对于整数类型,不支持广播,因此需要是一个标量)。要生成的随机值范围的上界(独家)。如果’ dtype '是浮点数,默认值为1。
dtype:输出类型:’ float16 ', ’ float32 ', ’ float64 ', ’ int32 ',
或“int64”。
seed: Python整数。与‘tf.random’组合使用。set_seed”跨多个调用创建可重复的张量序列。
name:操作的名称(可选)。
返回:
由随机均匀值填充的指定形状的张量。
Raises:
ValueError:如果’ dtype ‘是整型且’ maxval '未指定。
##如果需要均匀采样整形类型的数据,必须指定采样区间的最大值maxval参数,同时指定数据类型为tf.int*;
创建序列
在循环计算或者对张量进行索引时,经常需要创建一段连续的整形序列,可以通过tf.range()函数实现,tf.range(limit,delta=1)可以创建[0,limit)之间,步长为delta的整数序列,不包含limit
源码分析:
创建一个数字序列。
创建一个从’ start '开始并扩展为by的数字序列
增量’ delta ‘到但不包括’ limit '。
结果张量的dtype是从输入中推断出来的,除非
它是显式提供的。
像Python内置的’ range ‘一样,’ start ‘默认值为0,因此’ range(n) = range(0, n) '。
For example:
>>> start = 3
>>> limit = 18
>>> delta = 3
>>> tf.range(start, limit, delta)
<tf.Tensor: shape=(5,), dtype=int32,
numpy=array([ 3, 6, 9, 12, 15], dtype=int32)>
>>> start = 3
>>> limit = 1
>>> delta = -0.5
>>> tf.range(start, limit, delta)
<tf.Tensor: shape=(4,), dtype=float32,
numpy=array([3. , 2.5, 2. , 1.5], dtype=float32)>
>>> limit = 5
>>> tf.range(limit)
<tf.Tensor: shape=(5,), dtype=int32,
numpy=array([0, 1, 2, 3, 4], dtype=int32)>
参数:
start:一个0维“张量”(标量)。如果“limit”,则作为范围中的第一个条目。不是没有;否则,作为范围限制和第一个目默认为0。
limit:一个0维“张量”(标量)。序列的上限,互斥的。如果没有,当范围的第一个条目时,默认值为“start”默认值为0。
delta:一个0维“张量”(标量)。使“start”递增的数字。默认为1.即步长,结果张量的元素类型。
名称:操作的名称。默认为“范围”。
返回:
类型为dtype的1-D张量。
索引与切片
我们这里用正态分布来表示一些图片
a=tf.random.normal([4,3,6,6]) #4表示有4张图片,3表示RGB三个色层,4*4表示图片shape
#取出第一层
print(a[0])
#取出第一张图片的第一个通道的第二列
print(a[0][0][1])
#取出第一张图片的第一个通道的第二列第三行
print(a[0][0][1][2])
运行结果:
tf.Tensor(
[[[ 0.518707 1.5163158 -0.4420645 -2.481555 -0.81152815
-0.9397231 ]
[ 1.5775138 2.0486102 -0.62570584 0.8304402 0.23711066
0.53847134]
[ 0.1599692 1.798133 0.09645162 -1.1552328 2.5111668
0.09838181]
[-0.77109516 1.0529739 0.73465544 0.70902723 -0.7446708
-0.09486651]
[-0.5759095 0.57607526 -0.9822187 0.35990503 -1.2348553
1.7254637 ]
[ 0.66425496 1.4481226 -0.8432531 1.7264354 -0.63476187
-0.5393723 ]]
[[-0.19179177 0.9394503 0.7269039 -0.25555205 -1.790418
-1.1655353 ]
[ 0.1737995 -0.4393707 -0.4014472 0.8820638 1.2176535
-1.1696239 ]
[ 0.43505985 -1.697975 -1.2310885 -1.8150889 1.091729
1.5031186 ]
[-0.45266852 1.6704372 -0.76275015 -0.56150043 1.4168826
1.0748857 ]
[ 0.82299465 -1.9269519 1.8939077 -0.91542363 -0.06847822
-0.4155129 ]
[ 1.0114647 -1.1486771 -0.05396969 1.4470177 -0.21217598
0.67303246]]
[[ 0.5523337 -0.597471 -0.66123205 2.1127748 -0.44383255
0.5756219 ]
[-0.9805465 -0.3286453 -1.1696174 -0.8097877 0.7150285
1.9094592 ]
[ 1.1200994 0.42203882 -0.45964667 -0.2956707 -1.0503688
-1.0990242 ]
[-0.19401151 0.90812624 0.85977775 -0.38739848 0.36563993
-0.7651917 ]
[-1.6575507 0.54623014 -0.88202155 0.8635037 0.9887095
0.03415183]
[ 1.8023385 -1.7727107 -0.79836243 -1.151079 -0.41622043
0.65976393]]], shape=(3, 6, 6), dtype=float32)
tf.Tensor([ 1.5775138 2.0486102 -0.62570584 0.8304402 0.23711066 0.53847134], shape=(6,), dtype=float32)
tf.Tensor(-0.62570584, shape=(), dtype=float32)
当张量的维度数较高的时候,使用
[i][j][k]`````的书写不方便;可以采用[i,j,````,k,```]的方式写
#取第二张图片,第十行,第三列的数据
print(a[2,0,10,3])
切片:
a=tf.random.normal([4,4])
print(a)
print(a[2:4:1])
输出结果:
tf.Tensor(
[[ 1.1239142 -1.8242729 -3.652818 0.08764853]
[-0.97974813 -0.8028444 0.43489152 -0.24085271]
[ 0.18818058 0.25005805 -0.96541333 0.45664462]
[-0.8630165 1.0755167 -0.09772442 1.2855158 ]], shape=(4, 4), dtype=float32)
tf.Tensor(
[[ 0.18818058 0.25005805 -0.96541333 0.45664462]
[-0.8630165 1.0755167 -0.09772442 1.2855158 ]], shape=(2, 4), dtype=float32)
根据观察知道取得时a[3]和a[4]的所有值
其中切片的格式为:
a[start:end:step]
代表的是,start开头,end结尾,step步长。
当全部省略时为:a[::]代表取出这个所有数据集
根据切片操作可以方便的拿去张量中的数据
step等于负数的时候,代表逆序拿去,当step=-1的时候,start:end:-1表示从start开始,逆序读取至end结束(不包括end),索引end<<start,考虑一个0-9的简单序列向量,逆序取到1号元素:
a=tf.range(9)
print(a)
print(a[8:0:-1])
tf.Tensor([0 1 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
tf.Tensor([8 7 6 5 4 3 2 1], shape=(8,), dtype=int32)
同样逆序读取全部元素:
a[::-1] #语义同上述正序提取
来一点复杂的操作:
x=tf.random.normal([4,4,4])
print(x[0,::-1,::-1]) #行列按照逆序隔行采样
print(x[0,::-1,3:4:1]) #取出列并且按照逆序采样
运行结果:
tf.Tensor(
[[ 1.1437104 1.0706288 -1.1976969 -0.30044794]
[-1.0486609 -0.5484962 -1.2036967 -1.6851882 ]
[-1.5555099 1.5300337 -0.25154567 1.4413851 ]
[-0.45055193 0.01912273 -0.06383824 -0.34162328]], shape=(4, 4), dtype=float32)
tf.Tensor(
[[ 1.1437104 ]
[-1.0486609 ]
[-1.5555099 ]
[-0.45055193]], shape=(4, 1), dtype=float32)
当张量的维度数量较多时,不需要采样的维度一般用单冒号:表示采样所有元素,此时
有可能出现大量的:出现。继续考虑[4,32,32,3]的图片张量,当需要读取 G 通道上的数据
时,前面所有维度全部提取,此时需要写为:
ln [63]: x[:,:,:,1] # 取 G 通道数据
Out[63]:
输出结果:
<tf.Tensor: id=492, shape=(4, 32, 32), dtype=float32, numpy=
array([[[ 0.575703 , 0.11028383, -0.9950867 , …, 0.38083118,
-0.11705163, -0.13746642],
为了避免出现像 [: , : , : ,1]这样过多冒号的情况,可以使用⋯符号表示取多个维度上所
有的数据,其中维度的数量需根据规则自动推断:当切片方式出现⋯符号时,⋯符号左边
的维度将自动对齐到最左边,⋯符号右边的维度将自动对齐到最右边,此时系统再自动推
断⋯符号代表的维度数量,它的切片方式总结如表 所示
维度变换
在神经网络运算过程中,维度变换是最核心的张量操作,通过维度变换可以将数据任意地切换形式,满足不同场合的运算需求。
维度变换的一个例子:
Y = X@W + b
X 的 shape 为[2,4]
W 的 shape 为[4,3]
X@W的运算张量shape 为[2,3]
偏置b的张量为[3]
不同 shape 的 2 个张量怎么直接相加呢?
需要将shape为[3]的偏置b按样本数量复制一份,变成矩阵形式B,
这样就可以将偏置b和X@W相加了。
基本的维度变换包含了改变视图 reshape,插入新维度 expand_dims,删除维度squeeze,交换维度 transpose,复制数据 tile 等。
改变视图
x=tf.range(98)
x=tf.reshape(x,[14,7])
print(x)
运行结果:
··········
[84 85 86 87 88 89 90]
[91 92 93 94 95 96 97]], shape=(14, 7), dtype=int32)
源代码分析:
给定“tensor”,这个操作返回一个新的tf.tensor
那是一样的值作为“tensor”的顺序相同,除非使用形状`。
那个`tf.reshape不改变元素的顺序或总数这样就可以重用底层的数据缓冲区。这样就可以了与张量大小无关的快速运算。
如果“shape”的一个分量是特殊值-1,则该值的大小计算尺寸以使总尺寸保持不变。特别地,“[-1]”的“shape”变平为一维。“shape”最多只能有一个分量是-1。
tf.shape(张量) #返回他的构成及维度关系
例子:
>>> t = [[[1, 1, 1],
... [2, 2, 2]],
... [[3, 3, 3],
... [4, 4, 4]],
... [[5, 5, 5],
... [6, 6, 6]]]
>>> print(tf.shape(t).numpy())
[3 2 3]
>>> # Pass '[-1]' to flatten 't'.
>>> tf.reshape(t, [-1])
<tf.Tensor: shape=(18,), dtype=int32,
numpy=array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6],
dtype=int32)>
>>> # -- Using -1 to infer the shape --
>>> # Here -1 is inferred to be 9:
>>> tf.reshape(t, [2, -1])
<tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[1, 1, 1, 2, 2, 2, 3, 3, 3],
[4, 4, 4, 5, 5, 5, 6, 6, 6]], dtype=int32)>
>>> # -1 is inferred to be 2:
>>> tf.reshape(t, [-1, 9])
<tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[1, 1, 1, 2, 2, 2, 3, 3, 3],
[4, 4, 4, 5, 5, 5, 6, 6, 6]], dtype=int32)>
>>> # -1 is inferred to be 3:
>>> tf.reshape(t, [ 2, -1, 3])
<tf.Tensor: shape=(2, 3, 3), dtype=int32, numpy=
array([[[1, 1, 1],
[2, 2, 2],
[3, 3, 3]],
[[4, 4, 4],
[5, 5, 5],
[6, 6, 6]]], dtype=int32)>
参数分析:(你已经是一个成熟的程序猿了要学会自己翻译英文了)
Args:
tensor: A `Tensor`.
shape: A `Tensor`. Must be one of the following types: `int32`, `int64`.
Defines the shape of the output tensor.
name: Optional string. A name for the operation.
Returns:
A `Tensor`. Has the same type as `tensor`.
x.ndim,x.shape # 获取张量的维度数和形状列表
数据在创建时按着初始的维度顺序写入,改变张量的视图仅仅是改变了张量的理解方
式,并不需要改变张量的存储顺序,这在一定程度上是从计算效率考虑的,大量数据的写
入操作会消耗较多的计算资源。由于存储时数据只有平坦结构,与数据的逻辑结构是分离
的,因此如果新的逻辑结构不需要改变数据的存储方式,就可以节省大量计算资源,这也
是改变视图操作的优势。改变视图操作在提供便捷性的同时,也会带来很多逻辑隐患,这
主要的原因是改变视图操作的默认前提是存储不需要改变,否则改变视图操作就是非法
的。我们先介绍合法的视图变换操作,再介绍不合法的视图变换
通过 tf.reshape(x, new_shape),可以将张量的视图任意地合法改变,例如:
In [69]: tf.reshape(x,[2,-1])
Out[69]:<tf.Tensor: id=520, shape=(2, 48), dtype=int32, numpy=
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,…
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]])>
其中的参数−1表示当前轴上长度需要根据
张量总元素不变的法则自动推导,从而方便用户
书写。比如,上面的
−1可以推导为(2 ∙ 4 ∙ 4 ∙ 3) /2 = 48
增、删维度
增加维度
增加维度:增加一个长度为 1 的维度相当于给原有的数据添加一个新维度的概念,维度
长度为 1,故数据并不需要改变,仅仅是改变数据的理解方式,因此它其实可以理解为改
变视图的一种特殊方式。
a=tf.random.uniform([4,4],maxval=10,minval=0,dtype=tf.int32)
print(a)
a=tf.expand_dims(a,axis=1) #在第一个维度后面加入一个维度
aa=tf.expand_dims(a,axis=2) #在第二个维度后面加入一个维度
aaa=tf.expand_dims(a,axis=-1) #在第一个维度之前加入一个维度
aaaa=tf.expand_dims(a,axis=0) #在最前面加入一个维度
print(a)
代码结果:
tf.Tensor(
[[5 9 8 1]
[6 6 2 2]
[4 9 1 6]
[6 1 4 8]], shape=(4, 4), dtype=int32)
tf.Tensor(
[[[5 9 8 1]]
[[6 6 2 2]]
[[4 9 1 6]]
[[6 1 4 8]]], shape=(4, 1, 4), dtype=int32)
删除维度
删除维度:是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为 1 的维度,也不会改变张量的存储。继续考虑增加维度后 shape 为[1,28,28,1]的例子,如果希望将图片数量维度删除,可以通过tf.squeeze(x,axis)函数,axis 参数为待删除的维度的索引号,例如,图片数量的维度轴 axis=0:
x = tf.squeeze(x, axis=0) # 删除图片数量维度
##这个axis参数的使用同上expand_dims()
###如果不指定维度参数 axis,即 tf.squeeze(x),那么它会默认删除所有长度为 1 的维度
建议使用 tf.squeeze()时逐一指定需要删除的维度参数,防止 TensorFlow 意外删除某些长度为 1 的维度,导致计算结果不合法。
交换维度
改变视图、增删维度都不会影响张量的存储。在实现算法逻辑时,在保持维度顺序不变的条件下,仅仅改变张量的理解方式是不够的,有时需要直接调整的存储顺序,即交换维度(Transpose)。通过交换维度操作,改变了张量的存储顺序,同时也改变了张量的视图。
交换维度操作是非常常见的,比如在 TensorFlow 中,图片张量的默认存储格式是通道后行格式:[𝑏, ℎ, , 𝑐],但是部分库的图片格式是通道先行格式:[𝑏, 𝑐, ℎ,w],因此需要完成[𝑏, ℎ,w , 𝑐]到[𝑏, 𝑐, ℎ, w]维度交换运算,此时若简的使用改变视图函数 reshape,则新视图的存储方式需要改变,因此使用改变视图函数是不合法的。我们以[𝑏, ℎ, , 𝑐]转换到[𝑏, 𝑐, ℎ, w]为例,介绍如何使用 tf.transpose(x, perm)函数完成维度交换操作,其中参数 perm表示新维度的顺序 List。考虑图片张量 shape 为[2,32,32,3],“图片数量、行、列、通道**数”的维度索引分别为 0、1、2、3,如果需要交换为[𝑏, 𝑐, ℎ,w]格式,则新维度的排序为“图片数量、通道数、行、列”,对应的索引号为[0,3,1,2],因此参数 perm 需设置为[0,3,1,2],实现如下
x = tf.random.normal([2,32,32,3]) #这个所对应的维度为0,1,2,3
print(x)
x=tf.transpose(x,perm=[0,3,1,2]) # 交换维度
print(x)
#实例代码太长这里不在显示了
需要注意的是,通过 tf.transpose 完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有须基于新的存续顺序和视图进行。相对于改变视图操作,维度交换操作的计算代价更高。
复制数据
可以通过 tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数,对应位置为 1 表明不复制,为 2 表明新长度为原来长度的2 倍,即数据复制一份,以此类推。
这个并不是容易解释清楚所以我把源码注释复制出来了:
r"""Constructs a tensor by tiling a given tensor.
This operation creates a new tensor by replicating `input` `multiples` times.
The output tensor's i'th dimension has `input.dims(i) * multiples[i]` elements,
and the values of `input` are replicated `multiples[i]` times along the 'i'th
dimension. For example, tiling `[a b c d]` by `[2]` produces
`[a b c d a b c d]`.
>>> a = tf.constant([[1,2,3],[4,5,6]], tf.int32)
>>> b = tf.constant([1,2], tf.int32)
>>> tf.tile(a, b)
这个操作等价于:
b = tf.tile(b, multiples=[1,2])
<tf.Tensor: shape=(2, 6), dtype=int32, numpy=
array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]], dtype=int32)>
>>> c = tf.constant([2,1], tf.int32)
这个操作等价于:
b = tf.tile(b, multiples=[2,1])
>>> tf.tile(a, c)
<tf.Tensor: shape=(4, 3), dtype=int32, numpy=
array([[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6]], dtype=int32)>
>>> d = tf.constant([2,2], tf.int32)
>>> tf.tile(a, d)
这个操作等价于:
b = tf.tile(b, multiples=[2,2])
<tf.Tensor: shape=(4, 6), dtype=int32, numpy=
array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6],
[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]], dtype=int32)>
Args:
input: A `Tensor`. 1-D or higher.
multiples: A `Tensor`. Must be one of the following types: `int32`, `int64`.
1-D. Length must be the same as the number of dimensions in `input`
name: A name for the operation (optional).
Returns:
A `Tensor`. Has the same type as `input`.
Broadcasting
这里不再细说这里推荐一个博客:
通过 tf.broadcast_to(x, new_shape)函数可以显式地执行自动扩展功能,将现有 shape 扩
张为 new_shape,实现如下:
In [87]:
A = tf.random.normal([32,1]) # 创建矩阵
tf.broadcast_to(A, [2,32,32,3]) # 扩展为 4D 张量
Out[87]:
<tf.Tensor: id=13, shape=(2, 32, 32, 3), dtype=float32, numpy=
array([[[[-1.7571245 , -1.7571245 , -1.7571245 ],
[ 1.580159 , 1.580159 , 1.580159 ],
[-1.5324328 , -1.5324328 , -1.5324328 ],…
可以看到,在普适性原则的指导下,Broadcasting 机制变得直观、好理解,它的设计是非常
符合人的思维模式。
In [88]:
A = tf.random.normal([32,2])
tf.broadcast_to(A, [2,32,32,4]) # 不符合 Broadcasting 条件
Out[88]:
InvalidArgumentError: Incompatible shapes: [32,2] vs. [2,32,32,4]
[Op:BroadcastTo]
加减乘除运算
加、减、乘、除是最基本的数学运算,分别通过 tf.add, tf.subtract, tf.multiply, tf.divide函数实现,但是TensorFlow 已经重载了+、 − 、 ∗ 、/运算符,一般推荐直接使用运算符来完成加、减、乘、除运算。
整除和余除也是常见的运算之一,分别通过//和%运算符实现。
通过 **tf.pow(x, a)可以方便地完成𝑦 = 𝑎的乘方运算,也可以通过运算符实现 ∗∗ 𝑎运
算,设置指数为1/𝑎形式,可以实现根号计算。特别地,对于常见的平方和平方根运算,可以使用 **tf.square(x)**和 **tf.sqrt(x)**实现。
通过 **tf.pow(a, x)或者运算符也可以方便地实现指数运算𝑎^x特别地,对于自然指数e𝑥,可以通过 **tf.exp(x)**实现
在 TensorFlow 中,自然对数loge 可以通过 tf.math.log(x)实现
但是,tensorflow中并没有封装logx(a)的函数,所以要进行底数的运算需要进行换底操作:loge(2)/loge(10)=log10(2)
x = tf.constant([1.,2.])
x = 10**x
tf.math.log(x)/tf.math.log(10.) # 换底公式
<tf.Tensor: id=6, shape=(2,), dtype=float32, numpy=array([1., 2.],
dtype=float32)>
实现起来相对繁琐,也许 TensorFlow 以后会推出任意底数的 lo g函数
矩阵相乘的算法
神经网络中间包含了大量的矩阵相乘运算,前面我们已经介绍了通过@运算符可以方便的实现矩阵相乘,还可以通过 tf.matmul(a, b)函数实现。需要注意的是,TensorFlow 中的矩阵相乘可以使用批量方式,也就是张量𝑨和𝑩的维度数可以大于 2。当张量𝑨和𝑩维度数大于 2 时,TensorFlow 会选择𝑨和𝑩的最后两个维度进行矩阵相乘,前面所有的维度都视作Batch 维度。根据矩阵相乘的定义,𝑨和𝑩能够矩阵相乘的条件是,𝑨的倒数第一个维度长度(列)和𝑩的倒数第二个维度长度(行)必须相等。比如张量 a shape:[4,3,28,32]可以与张量 bshape:[4,3,32,2]进行矩阵相乘,代码如下:
a = tf.random.normal([4,3,28,32])
b = tf.random.normal([4,3,32,2])
print(a@b)
print(tf.matmul(a, b))
矩阵相乘函数同样支持自动 Broadcasting 机制,例如:
In [101]:
a = tf.random.normal([4,28,32])
b = tf.random.normal([32,16])
tf.matmul(a,b)
上述运算自动将变量 b 扩展为公共 shape:[4,32,16],再与变量 a 进行批量形式地矩阵相乘,得到结果的 shape 为[4,28,16]。