类型
通过tf.constant()可以创建3中数据类型,分别是数值、布尔、字符串
# 标量
tf.constant(2., dtype=tf.float16)
# 向量
tf.constant([2,3], dtype=tf.int16)
# 张量 维度>2
tf.constant([[[1,2], [3,4]], [[5,6], [7,8]]])
数值精度
TensorFlow支持一下几种数据类型,一般在数据定义的时候指定dtype来确定数据类型。
- tf.float16
- tf.float32
- tf.float64 也是tf.double
- tf.int16
- tf.int32
- tf.int64
tf.constant([1,2,3], dtype=tf.float32)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([1., 2., 3.], dtype=float32)>
类型转换
进行类型转换时,需要保证转换的合法性,例如从高精度转换为低精度时,可能会出现数据溢出
a = tf.constant(123456789, dtype=tf.int32) tf.cast(a, tf.int16) # 转换为低精度整型
<tf.Tensor: id=38, shape=(), dtype=int16, numpy=-13035>
待优化的张量
有些数值型数据需要计算张量,而有些不需要。TensorFlow增加了一种专门的数据类型来记录支持梯度信息的数据:tf.Variable(),包含name,trainable等属性来支持计算图的构建。
a = tf.Variable(12, name='hahatest')
a.name
'hahatest:0'
a.trainable
True
其中trainble属性用来表示当前数据是否需要被优化,默认值是True,也可设置成False来确保该数据不被优化。
创建张量
在TensorFlow中有多种方式创建张量:
- python列表
- numpy数组
- 采样自某种已知分布
从numpy数组或python列表等容器中创建
tf.convert_to_tensor([1, 2.])
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 2.], dtype=float32)>
tf.convert_to_tensor(np.array([[1, 2.], [3,4]]))
<tf.Tensor: shape=(2, 2), dtype=float64, numpy=
array([[1., 2.],
[3., 4.]])>
numpy的浮点数默认是64位
创建全0/全1张量
通过tf.ones()/tf.zeros()来快速创建全0/全1的张量。
tf.zeros([2,2])
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[0., 0.],
[0., 0.]], dtype=float32)>
tf.ones([2,3])
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[1., 1., 1.],
[1., 1., 1.]], dtype=float32)>
还可一通过tf.zeros_like(),创建与某张量shape一致的全0张量。
tf.ones_like()同理。
自定义数值张量
实际上除了初始化全0,全1向量外,我们偶尔也会用到其他数值的向量,TensorFlow同样提供了函数快速创建, tf.fill()。
tf.fill([2, 3], -1)
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[-1, -1, -1],
[-1, -1, -1]], dtype=int32)>
创建已知分布的张量
在深度学习中创建采样自某种分布的张量非常有用。例如在卷积网络中卷积核W初始化为正太分布会有利于神经网络的训练。
通过tf.random.nomal(shape, mean=0.0, stddev=1.0)可以创建形状为shape,均值为mean,标准差为stddev的正态分布张量。
通过tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自 [minval, maxval)区间的均匀分布的张量。
tf.random.uniform([2,3], minval=0, maxval=10)
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[7.98023 , 5.8388844, 3.1424844],
[1.8403566, 5.9672403, 4.1040087]], dtype=float32)>
创建序列
在python中可以通过range(limit)创建一个[0,limit)的列表,在TensorFlow中可以通过tf.range()实现类似的功能。
tf.range(10)
<tf.Tensor: shape=(10,), dtype=int32, numpy=array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)>
# 同下
tf.constant([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=tf.int32)
<tf.Tensor: shape=(10,), dtype=int32, numpy=array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)>
张量的应用
前面介绍了张量的创建和部分属性,后面将介绍下不同张量的典型应用,希望能够直观的理解不同张量的无力意义和用途。
标量
标量是指没有维度的,简单的一个数字。可以用作各种测量指标的表示,例如loss值,准确率(Accuracy,acc),精度(Precision)和召回率(Recall)等。
经过 tf.keras.losses.mse(或 tf.keras.losses.MSE,两者相同功能)返回每个样本上的误差值:
out = tf.random.uniform([4, 10])
<tf.Tensor: shape=(4, 10), dtype=float32, numpy=
array([[0.84483695, 0.93099034, 0.5165094 , 0.36359835, 0.45167565,
0.53549457, 0.35292995, 0.72738886, 0.86134624, 0.926762 ],
[0.60033536, 0.4691106 , 0.15967238, 0.84809935, 0.01495636,
0.9536545 , 0.9755062 , 0.08139396, 0.2499156 , 0.99979615],
[0.42064786, 0.6357846 , 0.11822903, 0.13693142, 0.02659714,
0.9410871 , 0.49241388, 0.2925855 , 0.08305645, 0.7115592 ],
[0.33314943, 0.9935945 , 0.5966996 , 0.23640716, 0.6664102 ,
0.09254563, 0.1807183 , 0.08634591, 0.10667193, 0.32951713]],
dtype=float32)>
y = tf.constant([2,3,1,0])
y = tf.one_hot(y, depth=10)
<tf.Tensor: shape=(4, 10), dtype=float32, numpy=
array([[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
loss = tf.keras.losses.mse(out, y)
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([0.46916837, 0.35590047, 0.20699708, 0.24566011], dtype=float32)>
loss = tf.reduce_sum(loss) # 此时loss即为标量
<tf.Tensor: shape=(), dtype=float32, numpy=1.2777259>
向量
向量也是一种非常常用的数据,如全连接层中的偏置bias就是使用向量来表示的。
fc = tf.keras.layers.Dense(3)
fc.build(input_shape=(2,4))
fc.bias
<tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>
全连接层中的bias就是一种典型的向量,而且是Variable的数据,即默认可优化的数据。
矩阵
全连接层中的W就是一种典型的矩阵,而且是Variable的数据,即默认可优化的数据。
fc = tf.keras.layers.Dense(3)
fc.build(input_shape=(2,4))
fc.kernel
<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
array([[ 0.09304154, 0.7754861 , -0.6330199 ],
[-0.68237156, 0.8677367 , -0.6535898 ],
[-0.33738226, -0.44297722, 0.3789575 ],
[ 0.31737828, 0.08265066, 0.33587444]], dtype=float32)>
三维张量
三维张量的一个典型应用就是序列数据。例如信号数据、文本数据等。
X = [batch_size, sequence_len, feature_len]
其中sequence_len表示数据在时间维度上的采样点数,例如文本里的文字长度。
feature_len表示每个时间步的特征长度,例如每个单词的embedding长度。
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=10000)
# 将文本填充/截取成等长的
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, max_len=100)
x_train.shape
(25000, 100)
embedding=tf.keras.layers.Embedding(10000, 80)
out = embedding(x_train)
out.shape
TensorShape([25000, 100, 80])
其中out即为三维张量,维度分别表示文本的数量,每个文本的单词长度,每个单词的embedding维度。
四维张量
神经网络中,一般将n张彩色图片用张量表示成:[n, h, w, 3],其中3维通道数量。在卷积的过程中,通道数还会发生变化。
x = tf.random.normal([4,32,32,3])
layer = tf.keras.layers.Conv2D(16,kernel_size=3)
out = layer(x)
x.shape
TensorShape([4, 32, 32, 3])
out.shape
TensorShape([4, 30, 30, 16])
索引与切片
索引和切片可以很方便的提取张量的部分数据。
索引
在TensorFlow中支持两种索引方式:
- 标准索引方式:[i][j]...
- 通过都好分隔索引号的方式
如下所示,两种方式显示结果是一样的
x = tf.random.normal([4,32,32,3])
x[0][1]
x[0, 1]
切片
切片只有一种方式,即start: end: step
x = tf.random.normal([4,32,32,3])
# 取第一张图片的所有信息
x[0,::]
# 取第一,二,三张图片
x[0:3]
维度变换
在神经网络中,维度变换是最核心的操作之一,通过维度变换可以将数据任意的切换形式,满足不同的计算需要。
TensorFlow中基本的维度变换函数包括:
- 改变视图reshape
- 插入新维度expand_dim
- 删除维度squeeze
- 交换维度transpose
- 复制数据tile等
改变视图
张量中存在两个很重要的概念,存储(Storage)和视图(View)。
张量的视图就是我们理解张量的方式,比如shape为[2,4,4,3]的张量,我们从逻辑上可以理解为有两张图片,每张图片有RGB三个通道,每个通道是4x4个像素。
张量的存储体现在张量在内存上保存为一段连续的内存区域。
同一个存储,从不同的角度观察数据,可以产生不同的视图。这就是存储和视图的关系。视图的产生是十分灵活的,但必须是合理的。
x = tf.range(10)
x = tf.reshape(x, shape=[2,5])
x
<tf.Tensor: shape=(2, 5), dtype=int32, numpy=
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]], dtype=int32)>
x = tf.reshape(x, shape=[2,5,1])
x
array([[[0],
[1],
[2],
[3],
[4]],
[[5],
[6],
[7],
[8],
[9]]], dtype=int32)>
增删维度
增加维度,是指增加一个长度为1的维度,也就是给原有的数据增加一个维度。通过tf.expand_dims(x, axis)实现。
x = tf.random.uniform([28, 28])
x.shape
TensorShape([28, 28])
x = tf.expand_dims(x, axis=2)
x.shape
TensorShape([28, 28, 1])
当axis为正时,是指在指定的位置之前加一个维度。
当axis为负时,是指在指定的位置之后加一个维度。
删除维度可以理解为增加维度的逆操作。只能删除长度为1的维度,不会改变张量的存储。通过tf.squeeze(x, axis)实现。
x.shape
TensorShape([28, 28, 1])
tf.squeeze(x, axis=2).shape
TensorShape([28, 28])
axis为要删除的维度。如果不指定axis,那么将默认删除所有长度为1的维度。
维度交换
改变视图,增删维度都是在保持维度顺序不变的条件下进行的,因此这些操作仅仅是改变了张量的理解方式,并没有改变张量的存储。但有时候我们需要改变张量的维度顺序,改变其存储。例如在TensorFlow中图片的默认默认存储格式是通道后行格式[b, h, w, c],但有些数据库中提供的图片是通道先行格式[b, c, h, w],需要[b, c, h, w]到[b, h, w, c]的转换。需要使用tf.transpose(x, perm),其中perm为新维度的顺序list。
x = tf.random.uniform(shape=[2,3,32,32])
x.shape
TensorShape([2, 3, 32, 32])
tf.transpose(x, perm=[0,2,3,1]).shape
TensorShape([2, 32, 32, 3])
复制数据
当我们对数据增加维度后,可能会希望在新的维度上复制若干份数据,来满足后续算法的格式要求。
tf.tile(x, multiples)可以实现以上操作,其中multiples 分别指 定了每个维度上面的复制倍数。
b = tf.constant([1, 2])
b = tf.expand_dims(b, axis=0)
b.shape
TensorShape([1, 2])
b = tf.tile(b, multiples=[2,1])
b
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 2],
[1, 2]], dtype=int32)>
其中multiples=[2, 1]是说,对第0维数据复制1份,对第1维数据维持原样,即数量*1(不变)。
Broadcasting
Broadcasting称为广播机制(自动扩展机制),它是一种轻量级的张量复制手段,在逻辑上扩展张量数据的形式,但只有在需要时才会执行实际的存储复制操作。
x = tf.random.uniform([2, 4])
w = tf.random.uniform([4, 3])
b = tf.random.normal([3])
y = x @ w + b
tf.broadcast_to(b, [2,3])
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[-0.5195319 , 2.6407762 , 0.00587736],
[-0.5195319 , 2.6407762 , 0.00587736]], dtype=float32)>
上例中b的shape与x@w的shape并不相同,但却能相加,没有发生逻辑错误。因为自动调用了tf.broadcast_to(x, new_shape),将两者的shape扩展成一样的。
注意:tf.broadcast_to()操作是自动进行的。
数学运算
- TensorFlow 已经重载了+、 − 、 ∗ 、/运算符,一般推荐直接使用运算符来完成 加、减、乘、除运算。
- 整除和余除也是常见的运算之一,分别通过//和%运算符实现。
- tf.pow(x, a) 与x ** a都为乘方运算。
- 对于常见的平方和平方根运算,可以使用 tf.square(x)和 tf.sqrt(x)实现。
- 对于自然指数e𝑥,可以通过 tf.exp(x)实现。
- 自然对数loge 可以通过 tf.math.log(x)实现。
矩阵相乘
特别要注意一下矩阵相乘
通过@运算符可以方便的实现矩阵相乘
还可以通过 tf.matmul(a, b)函数实现
根据矩阵相乘的定义,𝑨和𝑩能够矩阵相乘的条件是,𝑨的倒数第一个维度长度(列)和𝑩 的倒数第二个维度长度(行)必须相等