《Tensorflow深度学习》一书看了2遍,但是仍然有些地方不熟悉,因此决定写博客记录一下自已的学习历程。关键是要用到这些知识的时候可以随时来这里复习。
1.Tensorflow的数据类型
Tensorflow中的基本数据类型包括数值类型、字符串类型和布尔类型。
数值类型
- 标量:单个的实数,如1,2,3,4等,维度数为0,shape为[]
a = tf.constant(1)
- 向量:向量的形式是这样的[1,2],[1,2,3,4]。维度数为1. shape为[n]
a = tf.constan([1,2])
- 矩阵:n行m列实数的有序集合,形式如[[1,2],[3,4]],维度数为2,shape为[n,m]
a = tf.constant([[1,2],[3,4]]) - 张量:所有维度dim>2的数组统称为张量,张量的每个维度也叫做轴,一般维度代表了具体的物理含义,比如Shape为[2,32,32,3]的张量共的4维,如果表示图片数据的话,每个维度/轴代表的含义分别是图片数量、图片高度、图片宽度、图片通道数,其中2代表了2张图片,32代表了高、宽均为32,3代表了RGB共3个通道。
另外,在Tensorflow中间,为了表达方便,一般把标量、向量、矩阵也统称为张量,不作区分。
字符串类型
通过传入字符串对象即可创建字符串类型的张量。
a = tf.constant('Hello, Deep Learning.')
在tf.strings模块中,提供了常见的字符串类型的工具函数,如小写化lower()、拼接join()、长度length()、切分split()等
深度学习算法主要还是以数值类型的张量运算为主,字符串类型的数据使用频率较低。
布尔类型
布尔类型的标量形式如下:
a = tf.constant(True)
布尔类型的向量形式如下:
a = tf.constant([True, False])
需要注意的是,Tensorflow的布尔类型和Python的布尔类型并不等价,不能通用。例如:
a = tf.constant(True)
a is True #TF布尔类型张量与python布尔类型比较
a == True #仅数值比较
Out[11]:
False #对象不等价
<tf.Tensor: id=8, shape=(), dtype=bool, numpy=True> # 数值比较结果
2.Tensorflow的数值精度
Tensorflow常用的精度类型有tf.int16、tf.int32、tf.int64、tf.float16、tf.float32、tf.float64等,其中tf.float64即为tf.double
在创建张量时,可以指定张量的保存精度,例如:
tf.constant(123456789, dtype=tf.int16)
tf.constant(123456789, dtype=tf.int32)
<tf.Tensor: shape=(), dtype=int16, numpy=-13035>
<tf.Tensor: shape=(), dtype=int32, numpy=123456789>
可以看到,保存精度过低时,数据123456789发生了溢出,得到了错误的结果,一般使用tf.int32,tf.int64精度,对于浮点数,高精度的张量可以表示更精准的数据,例如用tf.float32精度保存π时,实际的数据为3.1415927 。
import numpy as np
tf.constant(np.pi,dtype=tf.float32)
<tf.Tensor: shape=(), dtype=float32, numpy=3.1415927>
如果采用tf.float64精度保存π时,则能获得更高的精度,实现如下。
tf.constant(np.pi, dtype=tf.float64)
<tf.Tensor: shape=(), dtype=float64, numpy=3.141592653589793>
对于大部分深度学习算法,一般使用tf.int32和tf.float32即可满足大部分场合下的运算要求。
2.1 读取张量的精度
通过访问张量的dtype成员属性可以判断张量的保存精度。
a = tf.constant(1., dtype=tf.float16)
print('before:',a.dtype)
if a.dtype != tf.float32:
a = tf.cast(a, dtype=tf.float32)
print('after:',a.dtype)
2.2 张量类型转换
张量的类型转换可以使用tf.cast函数来进行操作
a = tf.constant(np.pi,dtype=tf.float16)
tf.cast(a,dtype=tf.double)
<tf.Tensor: shape=(), dtype=float64, numpy=3.140625>
在进行类型转换时,需要保证转换操作的合法性,例如将高精度的张量转换为低精度的张量时,可能会发生数据溢出的错误。
布尔类型与整型之间相互转换也是合法的。
a = tf.constant([True, False])
tf.cast(a, tf.int32)
<tf.Tensor: shape=(2,), dtype=int32, numpy=array([1, 0], dtype=int32)>
注意:一般默认0表示False,1表示True,在Tensorflow中,将非0数字都视为True。
3.待优化张量
为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,TensorFlow增加了一种专门的数据类型来支持梯度信息的记录:tf.Variable。tf.Variable类型在普通的张量类型基础上添加了name,trainable等属性来支持计算图的构建。由于梯度运算会消耗大量的计算资源,而且会自动更新相关参数,对于不需要优化的张量,如神经网络的输入X,不需要通过tf.Variable封装;相反,对于需要计算梯度并优化的张量,如神经网络的W和b,需要通过tf.Variable包裹以便TensorFlow跟踪相关梯度信息。
通过tf.Variable()函数可以将普通张量转换为待优化张量,例如:
a = tf.constant([-1,0,1,2])
aa = tf.Variable(a)
aa.name,aa.trainable
('Variable:0', True)
其中张量的name和trainable属性是Variable特有的属性,name属性用于命名计算图中的变量,这套命名体系是TensroFlow内部维护的,一般不需要用户关注name属性;trainable属性表征当前张量是否需要被优化,创建Variable对象时是默认启用优化标志,可以设置trainable=False来设置张量不需要优化。
除了通过普通张量方式创建Variable,也可以直接创建,例如:
a = tf.Variable([[1,2],[3,4]]) #直接创建Variable张量
print(a)
<tf.Variable 'Variable:0' shape=(2, 2) dtype=int32, numpy=
array([[1, 2],
[3, 4]], dtype=int32)>
待优化张量可视为普通张量的特殊类型,普通张量其实也可以通过GradientTape.watch()方法临时加入跟踪梯度信息的列表,从而支持自动求导功能。
4.创建张量
在TensorFlow中,可以通过多种方试创建张量,如从Python列表对象创建,从Numpy数组创建,或者创建采样自某种已知分布的张量等。
4.1 从数组、列表对象创建
a = tf.constant([1,2])#从列表创建张量
a = tf.constant(np.array([[1,2],[3,4]]))#从数组中创建张量
4.2 创建全0或全1张量
将张量创建为全0或全1数据是非常常见的张量初始化手段。考虑线性变换y = Wx + b,将权值矩阵W初始化为全1矩阵,偏置b初始化为全0向量,此时线性变化层输出y = x,因此是一种比较好的层初始化状态,通过tf.zeros()和tf.ones()即可创建任意形状,且内容全0或全1的张量。例如,创建为0和为1的标量。
tf.ones([])#创建全1标量
tf.zeros([])#创建全0标量
创建全0和全1的向量。
tf.zeros([1])#创建全0向量
tf.ones([1])#创建全1向量
创建全0和全1的矩阵,例如:
tf.zeros([2,2])#全0矩阵
tf.ones([2,2])#全1矩阵
通过tf.zeros_like,tf.ones_like可以方便地新建与某个张量shape一致,且内容为全0或全1的张量。
a = tf.ones([2,3])#创建一个全1矩阵
tf.zeros_like(a)#创建一个与a形状相同,但是全0的新矩阵
a = tf.zeros([3,2])#创建一个全0矩阵
tf.ones_like(a)#创建一个与a形状相同,但是全1的新矩阵
tf.*_like是一系列的便捷函数,可以通过tf.zeros(a.shape)等方式实现。
4.3创建自定义数值张量
除了初始化为全0或全1的张量之外,有时也需要全部初始化为某个自定义数值的张量,比如将张量的数值全部初始化为-1等。
通过tf.fill(shape,value)可以创建全为自定义数值value的张量。例如,创建元素为-1的标量。
tf.fill([],-1)#创建-1的标量
tf.fill([1],-1)#创建-1的向量
tf.fill([2,2],99)#创建所有元素为99的矩阵
4.4创建已知分布的张量
正态分布和均匀分布是最常见的分布之一,创建采样自这两种分布的张量非常有用,比如在卷积神经网络中,卷积核张量W初始化为正态分布有利于网络的训练;在生成对抗网络中,隐藏变量z一般采样自均匀分布。
通过tf.random.normal(shape,mean=0.0,stddev=1.0)可以创建形状为shape,均值为mean,标准差为stddev的正态分布N(mean,stddev**2)。例如,创建均值为0,标准差为1的正态分布。
tf.random.normal([2,2])#创建标准正态分布的张量
tf.random.normal([2,2],mean=1,stddev=2)#创建均值为1,标准差为2的正态分布
通过tf.random.uniform(shape,minval=0,maxval=None,dtype=tf.float32)可以创建采样自[minval,maxval)区间的均匀分布的张量。例如创建采样自区间[0,1),shape为[2,2]的矩阵。
tf.random.uniform([2,2])
tf.random.uniform([2,2],maxval=10)#创建采样自[0,10),均匀分布的矩阵
如果需要均匀采样整形类型的数据,必须指定采样区间的最大值maxval参数,同时指定数据类型为tf.int*型:
tf.random.uniform([2,2],maxval=100,dtype=tf.int32)#创建采样自[0,100)均匀分布的整型数据
创建序列
在循环计算或者对张量进行索引时,经常需要创建一段连续的整型序列,可以通过tf.range()函数实现。tf.range(limit,delta=1)可以创建[0,limit)之间,步长为delta的整型序列,不包含limit本身。例如,创建0~10,步长为1的整型序列:
tf.range(10) #0~10,不包含10
tf.range(10,delta=2)#0~10,步长为2的整型序列
通过tf.range(start,limit,delta=1)可以创建[start,limit),步长为delta的序列,不包含limit本身:
tf.range(1,10,delta=2)#1~10,步长为2的整型序列
5.张量的典型应用
在介绍张量的典型应用时不可避免地会提及后续将要学习的网络模型或算法,学习时不需要完全理解,有初步印象即可。
5.1 标量的应用
在TensorFlow中,标量最容易理解,它就是一个简单的数字,维度数为0,shape为[]。标量的一些典型用途是误差值的表示、各种测量指标的表示,比如准确度(Accuracy,简称acc),精度(Precision)和召回率(Recall)等。
以均方差误差函数为例,经过tf.keras.losses.mes返回每个样本上的误差值,最后取误差值的均值作为当前Batch的误差,它是一个标量:
import tensorflow as tf
out = tf.random.normal([4,10])#随机模拟网络输出
y = tf.constant([2,3,2,0])#随机构造样本真实标签
y = tf.one_hot(y,depth=10)#one_hot编码
loss = tf.keras.losses.mse(y,out)#计算每个样本的MSE
loss = tf.reduce_mean(loss)#平均MSE,loss应是标量
print(loss)
5.2向量的应用
向量是一种非常常见的数据载体,如在全连接层和卷积神经网络层中,偏置张量b就是使用向量来表示。
考虑2个输出结点的网络层,我们创建长度为2的偏置向量b,并累加在每个输出结点上:
z = tf.random.normal([4,2])# z=wx,模拟获得激活函数的输入z
b = tf.zeros([2])#创建偏置向量
out = z+b#累加上偏置向量·
print(out)
通过高层接口类Dense()方式创建的网络层,张量W和b存储在类的内部,由类自动创建并管理。可以通过全连接层的bias成员变量查看偏置变量b,例如创建输入结点数为4,输出结点数为3的线性层网络,那么它的偏置向量b的长度应为3,实现如下:
from tensorflow.keras import layers
fc = layers.Dense(3)#创建一层wx+b,输出结点为3
fc.build(input_shape=(2,4))#通过build函数创建w,b张量,输入结点为4
fc.bias#查看偏置向量
<tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>
可以看到,类的偏置成员bias为长度为3的向量,初始化为全0,这也是偏置b的默认初始化方案。同时偏置向量b的类型为Variable,这是因为W和b都是待优化参数。
5.3矩阵的应用
矩阵也是非常常见的张量类型,比如全连接层的批量输入张量X的形状为[b,din],其中b表示输入样木的个数,即Batch Size,din表示输入特征的长度。例如特征长度为4,一共包含2个样本的输入可以表示为矩阵。
x = tf.random.normal([2,4])#2个样本,特征长度为4的张量
令全连接层的输出结点数为3,则它的权值张量W的shape为[4,3],我们利用张量X,W和向量b可以直接实现一个网络层,代码如下:
w = tf.ones([4,3])#定义w张量
b = tf.zeros([3])#定义b张量
o = x@w + b# x@w+b运算
print(o)
tf.Tensor(
[[ 3.2366664 3.2366664 3.2366664 ]
[-0.56299406 -0.56299406 -0.56299406]], shape=(2, 3), dtype=float32)
其中X和W张量均是矩阵,上述代码实现了一个线性变换的网络层,激活函数为空。一般地,𝞼(X@W+b)网络层称为全连接层,在TensorFlow中可以通过Dense类直接实现,特别地,当激活函数𝞼为空时,全连接层也称为线性层。我们通过Dense类创建输入4个节点,输出3个节点的网络层,并通过全连接层的kernel成员名查看其权值矩阵W:
fc = layers.Dense(3)#定义全连接层的输出结点为3
fc.build(input_shape=(2,4))#定义全连接层的输入结点为4
fc.kernel#查看权值矩阵W
<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
array([[-0.89720654, -0.38539422, -0.837082 ],
[-0.5322888 , -0.3831355 , 0.32437778],
[ 0.2899388 , -0.5300534 , -0.41874212],
[ 0.8843045 , 0.3853227 , 0.13318992]], dtype=float32)>
5.4三维张量的应用
三维张量的一个典型应用是表示序列信号,它的格式是:
X = [b, sequence len, feature len]
其中b表示序列信号的数量,sequence len表示序列信号在时间维度上的采样点数或步数。feature len表示每个点的特征长度。
考虑自然语言处理(Natural Language Processing,简称NLP)中句子的表示,如评价句子是否为正面情绪的情感分类网络。为了能够方便字符串被神经网络处理,一般将单词通过嵌入层(Embedding Layer)编码为固定长度的向量,比如‘a’编码为某个长度为3的向量,那么2个等长(单词数量为5)的句子序列可以表示为shape为[2,5,3]的3维张量,其中2表示句子个数,5表示单词数量,3表示单词向量的长度。我们通过IMDB数据集来演示如何表示句子,代码如下:
from tensorflow import keras
(x_train,y_train),(x_test,y_test) = keras.datasets.imdb.load_data(num_words=10000)
x_train = keras.preprocessing.sequence.pad_sequences(x_train,maxlen=80)
x_train.shape
Out [46]: (25000, 80)
可以看到x_train张量的shape为[25000,80],其中25000表示句子个数,80表示每个句子共80个单词,每个单词使用数字编码方式表示。我们通过layers.Embedding层将数字编码的单词转换为长度为100的词向量。
embedding = layers.Embedding(10000,100)#创建词向量Embedding层类
out = embedding(x_train)#将数字编码的单词转换为词向量
out.shape
Out[47]: TensorShape([25000, 80, 100])
可以看到,经过Embedding层编码后,句子张量的shape变为[25000,80,100],其中100表示每个单词编码为长度是100的向量。
5.5 四维张量的应用
四维张量在卷积神经网络中应用非常广泛,它用于保存特征图(Feature maps)数据,格式一般定义为
[b,h,w,c]
其中b表示输入样本的数量,h/w分别表示特征图的高/宽,c表示特征图的通道数。图片数据是特征图的一种,对于含有RGB 3个通道的彩色图片,每张图片包含了h行w列像素点,每个点需要3个数值表示RGB通道的颜色强度,因此一张图片可以表示为[h,w,3]。
神经网络一般并行计算多个输入以提高计算效率,故b张图片的张量可表示为[b,h,w,3],例如:
x = tf.random.normal([4,32,32,3])#创建32x32的彩色图片输入,图片个数为4
layer = layers.Conv2D(16,kernel_size=3)#创建卷积神经网络
out = layer(x)#前向计算
out.shape
TensorShape([4, 30, 30, 16])
其中卷积核张量也是4维张量,可以通过kernel成员变量访问。
layer.kernel.shape#访问卷积核张量
TensorShape([3, 3, 3, 16])