将图的定义和图的运行完全分开。因此TensorFlow被认为是一个"符号主义"的库。符号式计算一般是先定义各种变量,然后建立一个数据流图,在数据流图中规定各个变量之间的计算关系,最后需要对数据流图进行编译,但此时的数据流图还是一个空壳,里面没有任何实际数据,只有把需要计算的输入放进去后,才能在整个模型中形成数据流,才能在整个模型中形成数据流,从而形成输出值
# 传统程序操作
t = 8 + 9
print(t) # 定义了t的运算,在运行时就执行了,并输出17,
# tensorflow
import tensorflow as tf
t = tf.add(8, 9)
print(t) # 输出 Tensor("Add_1:0", shape=(), dtype=int32)
# 数据流图中的节点,实际上对应的是TensorFlow API中的一个操作,并没有真正去运行
TensorFlow中涉及的运算都要放在图中,而图的运行只发生在会话(session)中,开启会话后,就可以用数据去填充节点,进行运算,关闭会话后,就不能进行计算了,因此,会话提供了操作运行和Tensor求值的环境。
import tensorflow as tf
# 创建图
a = tf.constant([1.0, 2.0])
b = tf.constant([3.0, 4.0])
c = a * b
# 创建会话
sess = tf.Session()
# 计算c
print(sess.run(c))
sess.close()
TensorFlow是用数据流图做计算的,因此需要先创建一个数据流图。一个简单的回归模型中的元素有:输入(input)、塑形(reshape)、Relu层(Relu Layer)、Logit层(Logit Layer)、Softmax、交叉熵(cross entropy)、梯度(gradient)、SGD训练(SGD Trainer)等部分。
它的计算过程是,首先从输入开始,经过塑形后,一层一层进行前向传播运算。Relu层里会有两个参数,即Wh1和bh1,在输出前是用ReLU(Rectified Linear Units)激活函数做非线性处理。然后进入Logit层(输出层),学习两个参数Wsm和bsm。用Softmax来计算输出结果中各个类别的概率分布,用交叉熵来度量两个概率分布(源样本的概率分布和输出结果的概率分布)之间的相似性,然后开始计算梯度,这里是需要前面的四个参数,以及交叉熵后的结果。随后进入SGD训练,也就是反向传播的过程,从上往下计算每一层的参数,依次进行更新。
顾名思义,TensorFlow是指“张量的流动”。TensorFlow的数据流图是由节点(node)和边(edge)组成的有向无环图。TensorFlow由Tensor和Flow组成,Tensor(张量)代表了数据流图中的边,Flow(流动)这个动作就代表了数据流图中节点所做的操作。
TensorFlow的边有两种连接关系:数据依赖和控制依赖。其中,实线边表示数据依赖,代表数据,即张量。任意维度的数据统称为张量。在机器学习算法中,张量在数据流图中从前往后流动一遍就完成了一次前向传播,而残差(在数理统计中,残差是指实际观察值与训练的估计值之间的差)从后向前流动一遍就完成了一次反向传播。TensorFlow支持的张量具有下表所示的数据属性
数据类型 | Python类型 | 描述 |
---|---|---|
DT_FLOAT | tf.float32 | 32位浮点型 |
DT_DOUBLE | tf.float64 | 64位浮点型 |
DT_INT64 | tf.int64 | 64位有符号整型 |
DT_INT32 | tf.int32 | 32位有符号整型 |
DT_INT16 | tf.int16 | 16位有符号整型 |
DT_INT8 | tf.int8 | 8位有符号整型 |
DT_UINT8 | tf.uint8 | 8位无符号整型 |
DT_STRING | tf.string | 可变长度的字节数组,每一个张量元素都是一个字节数组 |
DT_BOOL | tf.bool | 布尔型 |
DT_COMPLEX64 | tf.complex64 | 由两个32位浮点数组成的复数:实部和虚部 |
DT_QINT32 | tf.qint32 | 用于量化操作的32位有符号整型 |
DT_QINT8 | tf.qint8 | 用于量化操作的8位有符号整型 |
DT_QUINT8 | tf.quint8 | 用于量化操作的8位无符号整型 |
节点又称为算子,它代表一个操作,一般用来表示施加的数学运算,也可以表示数据输入的起点以及输出的终点,或者是读取/写入持久变量的终点。下表列举了一些TensorFlow实现的算子,算子支持下表所示的张量的各种数据属性,并且需要在建立图的时候确定下来。
类别 | 示例 |
---|---|
数学运算操作 | Add、Substract、Multiply、Div、Exp、Log、Greater、Less、Equal |
数组运算操作 | Concat、Slice、Split、Constant、Rank、Shape、Shuffle |
矩阵运算操作 | MatMul、MatrixInverse、MatrixDeterminant |
有状态的操作 | Variable、Assign、AssignAdd |
神经网络构建操作 | SoftMax、Sigmoid、ReLU、Convolution2D、MaxPool |
检查点操作 | Save、Restore |
队列和同步操作 | Enqueue、Dequeue、MutexAcquire、MutexRelease |
控制张量流动的操作 | Merge、Switch、Enter、Leave、NextIteration |
图
操作任务可以描述成一个有向无环图,如何构建一个图,第一步是创建各个节点
import tensorflow as tf
# 创建一个常量运算操作,产生一个1x2矩阵
matrix1 = tf.constant([[3., 3.]])
# 创建另外一个常量运算操作,产生一个2x1矩阵
matrix2 = tf.constant([[2.], [2.]])
# 创建一个矩阵乘法运算,把matrix1和matrix2作为输入
# 返回值result代表矩阵乘法的结果
result = tf.matmul(matrix1, matrix2)
启动图的第一步是创建一个Session对象,会话(Session)提供在途中执行操作的一些方法。一般的模式是,建立会话,此时会生成一张空图,在会话中添加节点和边,形成一张图,然后执行。
with tf.Session() as sess:
result = sess.run(result)
print(result)
在调用Session对象的run()方法来执行图时,传入一些tensor,这个过程叫填充,返回的结果类型根据输入的类型而定,这个过程叫取回。
会话是图交互的一个桥梁,一个会话可以有多个图,会话可以修改图的结构,也可以往图中注入数据进行计算。因此会话主要有两个API接口:Extend和Run。Extend操作是在图中添加节点和边,Run操作是输入计算的节点和填充必要的数据后,进行运算,并输出结果
设备
设备是指一块可以用来运算并且拥有自己的地址空间的硬件,如GPU和CPU,TensorFlow为了实现分布式执行操作,充分利用资源,可以明确指定操作在哪个设备上执行
with tf.Session() as sess:
# 指定在第二个gpu上执行
with tf.device("/gpu:1"):
...
变量
变量是一种特殊的数据,它在图中有固定的位置,不像普通张量那样可以流动。创建一个变量张量,使用tf.Variable()构造函数,这个构造函数需要一个初始值,初始值的形状和类型决定了这个变量的形状和类型
# 创建一个变量,初始化为标量0
state = tf.Variable(0, name='counter')
TensorFlow还提供了填充机制,可以在构建图时使用tf.placeholder()临时替代任意操作的张量,在调用Session对象的run()方法去执行图时,使用填充数据作为调用的参数,调用之后,填充数据就消失
TensorFlow的计算表现为数据流图,所以tf.Graph类中包含一系列表示计算的操作对象(tf.Operation),以及在操作之间流动的数据–张量对象(tf.Tensor)。与图相关的API均位于tf.Graph类中
操作 | 描述 |
---|---|
tf.Graph.__ init__() | 创建一个空图 |
tf.Graph.as_default() | 将某图设置成默认图,并返回一个上下文管理器,如果不显式添加一个默认图,系统会自动设置一个全局的默认图,所设置的默认图,在模块范围内定义的节点都将默认加入默认图中 |
tf.Graph.device(deice_name_or_function) | 定义运行图所使用的的设备,并返回一个上下文管理器 |
tf.Graph.name_scope(name) | 为节点创建层次化的名称,并返回一个上下文管理器 |
tf.Operation类代表图中的一个节点,用于计算张量数据。该类型由节点构造器(如tf.matmul()或者Graph.create_op())产生。与操作相关的API均位于tf.Operation类中
操作 | 描述 |
---|---|
tf.Operation.name | 操作的名称 |
tf.Operation.type | 操作的类型 |
tf.Operation.inputs/outputs | 操作的输入与输出 |
tf.Operation.control_inputs | 操作的依赖 |
tf.Operation.run(feed_dict=None, session=None) | 在会话中运行该操作 |
tf.Operation.get_attr(name) | 获取操作的属性值 |
tf.Tensor类是操作输出的符号句柄,它不包含操作输出的值,而是提供了一种在tf.Session中计算这些值的方法。这样就可以在操作之间构建一个数据流连接,使TensorFlow能够执行一个表示大量多步计算的图形。与张量相关的API均位于tf.Tensor类中
操作 | 描述 |
---|---|
tf.Tensor.dtype | 张量的数据类型 |
tf.Tensor.name | 张量的名称 |
tf.Tensor.value_index | 张量在操作输出中的索引 |
tf.Tensor.graph | 张量所在的图 |
tf.Tensor.op | 产生该张量的操作 |
tf.Tensor.consumers() | 返回使用该张量的操作列表 |
tf.Tensor.eval(feed_dict=None, session=None) | 在会话中求张量的值,需要使用sess.as_default或eval(session=sess) |
tf.Tensor.get_shape() | 返回用于表示张量的形状(维度)的类TensorShape |
tf.Tensor.set_shape(shape) | 更新张量的形状 |
tf.Tensor.device | 设置计算该张量的设备 |
可视化时,需要在程序中给必要的节点添加摘要(summary),摘要会收集该节点的数据,并标记上第几步、时间戳等标识,写入事件文件(event file)中。tf.summary.FileWriter类用于在目录中创建事件文件,并且向文件中添加摘要和事件,用来在TensorBoard中展示
API | 描述 |
---|---|
tf.summary.FileWriter.__ init__(logdir, graph=None, max_queue=10, flush_secs=120, graph_def=None) | 创建FileWriter和事件文件,会在logdir中创建一个新的事件文件 |
tf.summary.FileWriter.add_summary(summary, global_step=None) | 将摘要添加到事件文件 |
tf.summary.FileWriter.add_event(event) | 向事件文件中添加一个事件 |
tf.summary.FileWriter.add_graph(graph, global_step=None, graph_def=None) | 向事件中添加一个图 |
tf.summary.FileWriter.get_logdir() | 获取事件文件的路径 |
tf.summary.FileWriter.flush() | 将所有事件都写入磁盘 |
tf.summary.FileWriter.close() | 将事件写入磁盘,并关闭文件操作符 |
tf.summary.scalar(name, tensor, collections=None) | 输出包含单个标量值的摘要 |
tf.summary.histogram(name, values, collections=None) | 输出包含直方图的摘要 |
tf.summary.audio(name, tensor, sample_rate, max_outputs=3, collections=None) | 输出包含音频的摘要 |
tf.summary.image(name, tensor, max_outputs=3, collections=None) | 输出包含图片的摘要 |
tf.summary.merge(inputs, collections=None, name=None) | 合并摘要,包含所有输入摘要的值 |
在TensorFlow中有两个作用域(scope),一个是name_scope,一个是variable_scope。variable_scope主要是给variable_name加前缀,也可以给op_name加前缀;name_scope是给op_name加前缀。
v = tf.get_variable(name, shape, dtype, initializer) # 通过所给的名字创建或返回一个变量
tf.variable_scope(<scope_name>) # 为变量指定命名空间
当tf.get_variable_scope().reuse==False时,variable_scope作用域只能用来创建新变量:
import tensorflow as tf
with tf.variable_scope("foo"):
v = tf.get_variable("v", [1])
v2 = tf.get_variable("v", [1])
assert v.name == "foo/v:0"
上述程序会抛出ValueError错误,因为v这个变量已经被定义过了,但tf.get_variable_scope().reuse默认为False,故不能重用
当tf.get_variable_scope().reuse==True时,作用域可以共享变量:
import tensorflow as tf
with tf.variable_scope("foo") as scope:
v = tf.get_variable("v", [1])
with tf.variable_scope("foo", reuse=True):
# 也可以写成:
# scope.reuse_variables()
v1 = tf.get_variable("v", [1])
assert v1 == v
可以直接通过tf.get_variable_scope()来获取变量作用域
import tensorflow as tf
with tf.variable_scope("foo") as foo_scope:
v = tf.get_variable("v", [1])
with tf.variable_scope(foo_scope):
w = tf.get_variable("w", [1])
如果在开启的一个变量作用域里使用之前预先定义的一个作用域,则会跳过当前变量的作用域,保持预先存在的作用域不变
import tensorflow as tf
with tf.variable_scope("foo") as foo_scope:
assert foo_scope.name == "foo"
with tf.variable_scope("bar"):
with tf.variable_scope("baz") as other_scope:
assert other_scope.name == "bar/baz"
with tf.variable_scope(foo_scope) as foo_scope2:
assert foo_scope2.name == "foo" # 保持不变
变量作用域可以默认携带一个初始化器,在这个作用域中的子作用域或变量都可以继承或者重写父作用域初始化器中的值
import tensorflow as tf
with tf.variable_scope("foo", initializer=tf.constant_initializer(0.4)):
v = tf.get_variable("v", [1])
assert v.eval() == 0.4 # 被作用域初始化
w = tf.get_variable("w", [1], initializer=tf.constant_initializer(0.3))
assert w.eval() == 0.3 # 重写初始化器的值
with tf.variable_scope("bar"):
v = tf.get_variable("v", [1])
assert v.eval() == 0.4 # 继承默认的初始化器
with tf.variable_scope("baz", initializer=tf.constant_initializer(0.2)):
v = tf.get_variable("v", [1])
assert v.eval() == 0.2 # 重写父作用域的初始化器的值
上面讲的是variable_name,对于op_name,在variable_scope作用域下的操作,也会被加上前缀
import tensorflow as tf
with tf.variable_scope("foo"):
x = 1.0 + tf.get_variable("v", [1])
assert x.op.name == "foo/add"
TensorFlow中常常有数以千计的节点,在可视化的过程中很难一下子展示出来,因此用name_scope为变量划分范围,在可视化中,这表示在计算图中的一个层级。name_scope会影响op_name,不会影响用get_variable()创建的变量,而会影响通过Variable()创建的变量
import tensorflow as tf
with tf.variable_scope("foo"):
with tf.name_scope("bar"):
v = tf.get_variable("v", [1])
b = tf.Variable(tf.zeros([1]), name='b')
x = 1.0 + v
assert v.name == "foo/v:0"
assert b.name == "foo/bar/b:0"
assert x.op.name == "foo/bar/add"
批标准化(batch normalization, BN)是为了克服神经网络层数加深导致难以训练而诞生的。随着神经网络的深度加深,训练会越来越困难,收敛速度会很慢,常常会导致梯度消失问题。
梯度消失问题:在神经网络中,当前隐藏层的学习速率低于后面隐藏层的学习速率,即随着隐藏层数目的增加,分类准确率反而下降了。这种现象叫梯度消失问题
传统机器学习中有一个ICS理论,这是一个经典假设:源域(source domain)和目标域(target domain)的数据分布是一致的,也就是说,训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障
Covariate Shift是指训练集的样本数据和目标样本集分布不一致时,训练得到的模型无法很好地泛化(泛化:通俗来讲就是指学习到的模型对位置数据的预知能力)。它是分布不一致假设之下的一个分支问题,也就是指源域和目标域的条件概率是一致的,但是边缘概率不同。对于神经网络的各层输出,在经过了层内的操作后,各层输出分布就会与对应的输入信号分布不同,而且差异会随着网络深度增大而增大,但是每一层所指向的样本标记仍然是不变的。
解决思路一般是根据训练样本的比例对训练样本做一个矫正,因此,通过引入批标准化来规范化某些层或者所有层的输入,从而固定每层输入信号的均值与方差。
批标准化一般用在非线性映射(激活函数)之前,对x=Wu+b做规范化,使结果(输出信号各个维度)的均值为0,方差为1。让每一层的输入有一个稳定的分布会有利于网络的训练
批标准化通过规范化让激活函数分布在线性区间,结果就是加大了梯度,让模型更加大胆地进行梯度下降,于是有如下优点:
对每一层的Wx_plus_b进行批标准化,这个步骤放在激活函数之前
# 计算Wx_plus_b的均值和方差,其中axes = [0]表示想要标准化的维度
fc_mean, fc_var = tf.nn.moments(Wx_plus_b, axes=[0], )
scale = tf.Variable(tf.ones([out_size]))
shift = tf.Variable(tf.zeros([out_size]))
epsilon = 0.001
Wx_plus_b = tf.nn.batch_normalization(Wx_plus_b, fc_mean, fc_var, shift, scale, epsilon)
# 也就是在做:
# Wx_plus_b = (Wx_plus_b - fc_mean)/tf.sqrt(fc_var + 0.001)
# Wx_plus_b = Wx_plus_b * scale + shift
激活函数运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层的神经网络。神经网络之所以能解决非线性问题(如语音、图像识别),本质上就是激活函数加入了非线性因素,弥补了线性模型的表达力,把“激活的神经元的特征”通过函数保留并映射到下一层
激活函数不会更改输入数据的维度,也就是输入和输出的维度是相同的。TensorFlow中有如下激活函数,输入均为要计算的x(一个张量),输出均为与x数据类型相同的张量。常见的有sigmoid、tanh、relu和softplus
sigmoid函数:
S ( x ) = 1 1 + e − x S(x)=\frac{1}{1+e^-x} S(x)=1+e−x1
使用方法:
a = tf.constant([[1.0, 2.0], [1.0, 2.0], [1.0, 2.0]])
sess = tf.Session()
print(sess.run(tf.sigmoid(a)))
sigmoid函数优点在于,它的输出映射在(0, 1)内,单调连续,非常适合于用作输出层,并且求导比较容易。缺点在于,因为软饱和性(软饱和性:是指激活函数h(x)在取值趋于无穷大时,它的一阶导数趋于0.硬饱和是指当|x|>c时,其中c为常数,f’(x)=0),一旦落入软饱和区,f’(x)就会变得接近于0,很容易产生梯度消失。(梯度消失:是指在更新模型参数时采用链式求导法则反向求导,越往前梯度越小。最终的结果是到达一定深度后梯度对于模型的更新就没有任何贡献了)
tanh函数:
S ( x ) = 1 − e − 2 x 1 + e − 2 x S(x)=\frac{1-e^-2x}{1+e^-2x} S(x)=1+e−2x1−e−2x
tanh函数也具有软饱和性,因为它的输出以0为中心,收敛速度比sigmoid要快,但是仍无法解决梯度消失的问题
relu函数
目前最受欢迎的激活函数。softplus可以看作是relu的平滑版本。relu定义为f(x)=max(x, 0)。softplus定义为f(x)=log(1+exp(x))。使用示例如下:
a = tf.constant([-1.0, 2.0])
with tf.Session() as sess:
b = tf.nn.relu(a)
print(sess.run(b))
除了relu本身之外,TensorFlow还定义了relu6以及crelu。
dropout函数
一个神经元将以概率keep_prob决定是否被抑制。如果被抑制,该神经元的输出就为0;如果不被抑制,那么该神经元的输出值将被放大到原来的1/keep_prob倍。
在默认情况下,每个神经元是否被抑制是相互独立的。但是否被抑制也可以通过noise_shape来调节。当noise_shape[i] == shape(x)[i]时,x中的元素是相互独立的。如果shape(x) = [k, l, m, n],x中的维度的顺序分别为批、行、列和通道,如果noise_shape = [k, l, l, n],那么每个批和通道相互独立,行跟列相互关联,也就是说,要不都为0,要不都还是原来的值。使用示例如下:
a = tf.constant([[-1.0, 2.0, 3.0, 4.0]])
with tf.Session() as sess:
b = tf.nn.dropout(a, 0.5, noise_shape=[1, 4])
print(sess.run(b))
b = tf.nn.dropout(a, 0.5, noise_shape=[1, 1])
print(sess.run(b))
卷积函数是构建神经网络的重要支架,是在一批图像上扫描的二维过滤器。
tf.nn.convolution(input, filter, padding, strides=None, dilation_rate=None, name=None, data_format=None) 这个函数是计算N维卷积的和
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)这个函数的作用是对一个四维的输入数据input和四维的卷积核filter进行操作,然后对输入数据进行一个二维的卷积操作,最后得到卷积之后的结果
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
# 输入:
# input:一个tensor。数据类型必须是float32或者float64
# filter:一个tensor。数据类型必须与input相同
# strides:一个长度是4的一维整数类型数组,每一维度对应的是input中每一维的对应移动步数
# padding:一个字符串,取值为SAME或VALID
# padding='SAME':仅适用于全尺寸操作,即输入数据维度和输出数据维度相同
# padding='VALID':适用于部分窗口,即输入数据维度和输出数据维度不同
# use_cudnn_on_gpu:一个可选布尔值,默认是True
# name:(可选)为该操作取一个名字
使用示例如下:
input_data = tf.Variable(np.random.rand(10,9,9,3), dtype = np.float32)
filter_data = tf.Variable(np.random.rand(2,2,3,2), dtype=np.float32)
y = tf.nn.conv2d(input_data, filter_data, strides=[1,1,1,1], padding='SAME')
tf.nn.depthwise_conv2d(input, filter, strides, padding, rate=None, name=None, data_format=None)这个函数输入张量的数据维度是[batch, in_height, in_width, in_channels],卷积核的维度是[filter_height, filter_width, in_channels, channel_multiplier],在通道in_channels上面的卷积深度是1,depthwise_conv2d函数将不同的卷积核独立地应用在in_channels的每个通道上(从通道1到通道channel_multiplier),然后把所有的结果进行汇总。最后输出通道的总数是in_channels*channel_multiplier。
使用示例如下:
input_data = tf.Variable(np.random.rand(10, 9, 9, 3), dtype=np.float32)
filter_data = tf.Variable(np.random.rand(2, 2, 3, 5), dtype=np.float32)
y = tf.nn.depthwise_conv2d(input_data, filter_data, strides=[1, 1, 1, 1], padding='SAME')
tf.nn.separable_conv2d(input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None, data_format=None)是利用几个分离的卷积核去做卷积。在这个API中,将应用一个二维的卷积核,在每个通道上,以深度channel_multiplier进行卷积
def separable_conv2d(input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None, data_format=None)
# 特殊参数:
# depthwise_filter:一个张量。数据维度是四维[filter_height, filter_width, in_channels, channel_multiplier]。其中,in_channels的卷积深度是1
# pointwise_filter:一个张量,数据维度是四维[1, 1, channel_multiplier*in_channels, out_channels]。其中,pointwise_filter是在depthwise_filter卷积之后的混合卷积
使用示例如下:
input_data = tf.Variable(np.random.rand(10, 9, 9, 3), dtype=np.float32)
depthwise_filter = tf.Variable(np.random.rand(2, 2, 3, 5), dtype=np.float32)
pointwise_filter = tf.Variable(np.random.rand(1, 1, 15, 20), dtype=np.float32)
y = tf.nn.separable_conv2d(input_data, depthwise_filter, pointwise_filter, strides=[1, 1, 1, 1], padding='SAME')
tf.nn.atrous_conv2d(value, filters, rate, padding, name=None)计算Atrous卷积,又称孔卷积或者扩张卷积。
使用示例如下:
input_data = tf.Variable(np.random.rand(1, 5, 5, 1), dtype=np.float32)
filters = tf.Variable(np.random.rand(3, 3, 1, 1), dtype=np.float32)
y = tf.nn.atrous_conv2d(input_data, filters, 2, padding='SAME')
tf.nn.conv2d_transpose(value, filter, output_shape, strides, padding=‘SAME’, data_format=‘NHWC’, name=None)在解卷积网络中有时称为“反卷积”,但实际上是conv2d的转置,而不是实际的反卷积
def conv2d_transpose(value, filter, output_shape, strides, padding='SAME', data_format='NHWC', name=None)
# 特殊参数:
# output_shape:一维的张量,表示反卷积运算后输出的形状
# 输出:和value一样维度的Tensor
使用示例如下:
x = tf.random_normal(shape=[1, 3, 3, 1])
kernel = tf.random_normal(shape=[2, 2, 3, 1])
y = tf.nn.conv2d_transpose(x, kernel, output_shape=[1, 5, 5, 3], strides=[1, 2, 2, 1], padding='SAME')
tf.nn.conv1d(value, filters, stride, padding, use_cudnn_on_gpu=None, data_format=None, name=None)和二维卷积类似。这个函数是用来计算给定三维的输入和过滤器的情况下的一维卷积。不同的是,它的输入是三维,如[batch, in_width, in_channels]。卷积核的维度也是三维,少了一维filter_height,如[filter_width, in_channels, out_channels]。stride是一个正整数,代表卷积核向右移动每一步的长度
tf.nn.conv3d(input, filter, strides, padding, name=None)和二维卷积类似,这个函数用来计算给定五维的输入和过滤器的情况下的三维卷积。和二维卷积相对比:
tf.nn.conv3d_transpose(value, filter, output_shape, strides, padding=‘SAME’, name=None)和二维反卷积类似
在神经网络中,池化函数一般跟在卷积函数的下一层,池化操作是利用一个矩阵窗口在张量上进行扫描,将每个矩阵窗口中的值通过取最大值或平均值来减少元素个数。每个池化操作的矩阵窗口大小是由ksize指定的,并且根据步长strides决定移动步长
tf.nn.avg_pool(value, ksize, strides, padding, data_format=‘NHWC’, name=None)这个函数计算池化区域中元素的平均值
def avg_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
# value:一个四维的张量。数据维度是[batch, height, width, channels]
# ksize:一个长度不小于4的整形数组。每一位上的值对应于输入数据张量中每一维的窗口对应值
# strides:一个长度不小于4的整形数组,该参数指定滑动窗口在输入数据张量每一维上的步长
# padding:一个字符串,取值为SAME或者VALID
# data_format:'NHWC'代表输入张量维度的顺序,N为个数,H为高度,W为宽度,C为通道数(RGB三通道或者灰度单通道)
# name:为这个操作取一个名字
# 输出:一个张量,数据类型和value相同
使用示例如下:
input_data = tf.Variable(np.random.rand(10, 6, 6, 3), dtype=np.float32)
filter_data = tf.Variable(np.random.rand(2, 2, 3, 10), dtype=np.float32)
y = tf.nn.conv2d(input_data, filter_data, strides=[1, 1, 1, 1], padding='SAME')
out_put = tf.nn.avg_pool(value=y, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding='SAME')
tf.nn.max_pool(value, ksize, strides, padding, data_format=‘NHWC’, name=None)这个函数是计算池化区域中元素的最大值。使用示例如下:
input_data = tf.Variable(np.random.rand(10, 6, 6, 3), dtype=np.float32)
filter_data = tf.Variable(np.random.rand(2, 2, 3, 10), dtype=np.float32)
y = tf.nn.conv2d(input_data, filter_data, strides=[1, 1, 1, 1], padding='SAME')
out_put = tf.nn.max_pool(value=y, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding='SAME')
tf.nn.max_pool_with_argmax(input, ksize, strides, padding, Targmax, name=None)这个函数的作用是计算池化区域中元素的最大值和该最大值所在的位置
在计算位置argmax的时候,我们将input平铺了进行计算,所以,如果input = [b, y, x, c],那么索引位置是
((b * height + y) * width + x) * channels + c。使用示例如下:
input_data = tf.Variable(np.random.rand(10, 6, 6, 3), dtype=np.float32)
filter_data = tf.Variable(np.random.rand(2, 2, 3, 10), dtype=np.float32)
y = tf.nn.conv2d(input_data, filter_data, strides=[1, 1, 1, 1], padding='SAME')
out_put, argmax = tf.nn.max_pool_with_argmax(input=y, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding='SAME')
# 返回结果是一个张量组成的元组(output, argmax),output表示池化区域的最大值,argmax的数据类型是Targmax,维度是四维
tf.nn.avg_pool3d()和tf.nn.max_pool3d()分别是在三维下的平均池化和最大池化
tf.nn.fractional_avg_pool()和tf.nn.fractional_max_pool()分别是Benjamin Graham在论文中提出的池化技术,池化后的图片大小可以成非整数倍缩小,如$ \sqrt 2 , , , \sqrt 3 $
tf.nn.pool(input, window_shape, pooling_type, padding, dilation_rate=None, strides=None, name=None, data_format=None)这个函数执行一个N维的池化操作
tf.nn.sigmoid_cross_entropy_with_logits(logits, targets, name=None)
tf.nn.sigmoid_cross_entropy_with_logits(logits, targets, name=None)
# 输入:logits:[batch_size, num_classes],targets:[batch_size, size].logits用最后一层的输入即可
# 最后一层不需要进行sigmoid运算,此函数内部进行了sigmoid操作
# 输出:loss[batch_size, num_classes]
这个函数的输入要格外注意。如果采用此函数作为损失函数,在神经网络的最后一层不需要进行sigmoid运算
tf.nn.softmax(logits, dim=-1, name=None)计算SoftMax激活,也就是
softmax = exp(logits) / reduce_sum(exp(logits), dim)
tf.nn.log_softmax(logits, dim=-1, name=None)计算log softmax激活,也就是
logsoftmax = logits - log(reduce_sum(exp(logits), dim))
tf.nn.softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, dim=-1, name=None)
softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, dim=-1, name=None)
# 输入:logits and labels均为[batch_size, num_classes]
# 输出:loss[batch_size],里面保存的是batch中每个样本的交叉熵
tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels, name=None)
tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels, name=None)
# 输入:logits:[batch_size, num_classes] labels:[batch_size],必须在[0, num_classes]
# 输出:loss[batch_size],里面保存的是batch中每个样本的交叉熵
目前加速训练的优化方法基本都是基于梯度下降的,只是细节上有差异。梯度下降是求函数极值的一种方法,学习到最后就是求损失函数的极值问题
BGD法
BGD的全称是batch gradient descent,即批梯度下降。这种方法是利用现有参数对训练集中每一个输入生成一个估计输出yi,然后跟实际输出yi比较,统计所有误差,求平均后得到平均误差,以此作为更新参数的一句。它的迭代过程为:
提取训练集中的所有内容{x1, ···, xn},以及相关的输出yi
计算梯度和误差并更新参数
这个方法的优点是,使用所有训练数据计算,能够保证收敛,并且不需要逐渐减少学习率;缺点是,每一步都需要使用所有的训练集,随着训练的进行,速度会越来越慢
SGD法
SGD的全称是stochastic gradient descent,即随机梯度下降。因为这种方法的主要思想是将数据集拆分成一个个批次(batch),随机抽取一个批次来计算并更新参数
SGD在每一次迭代计算mini-batch的梯度,然后对参数进行更新。与BGD相比,SGD在训练数据集很大时,仍能以较快的速度收敛,但仍有以下两个缺点
Momentum法
Momentum是模拟物理学中动量的概念,更新时会在一定程度上保留之前的更新方向,利用当前批次再微调本次的更新参数,因此引入了一个新的变量v(速度),作为前几次梯度的累加。因此,Momentum能够更新学习率,在下降初期,前后梯度方向一致时,能够加速学习,在下降的中后期,在局部最小值的附近来回震荡时,能够抑制震荡,加快收敛
Nesterov Momentum法
是对Momentum的改进,Momentum法首先计算一个梯度,然后在加速更新梯度的方向进行一个大的跳跃;Nesterov项首先在原来加速的梯度方向进行一个大的跳跃,然后在该位置计算梯度值,然后用这个梯度值修正最终的更新方向。
Adagrad法
Adagrad法能够自适应地为各个参数分配不同的学习率,能够控制每个维度的梯度方向。这种方法的优点是能够实现学习率的自动更改;如果本次更新时梯度大,学习率就衰减得快一些;如果这一次更新时梯度小,学习率就衰减得慢一些。
Adadelta法
Adadelta法仍然存在一些问题:其学习率单调递减没在训练的后期学习率非常小,并且需要手动设置一个全局的初始学习率
RMSProp法
RMSProp法与Momentum法类似,通过引入一个衰减系数,使每一回合都衰减一定比例,在实践中,对循环神经网络(RNN)效果很好
Adam法
Adam的名称来源于自适应矩估计(adaptive moment estimation)。Adam法根据损失函数针对每个参数的梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。(矩估计就是利用样本来估计总体中相应的参数)