x = tf.compat.v1.placeholder(tf.float32, shape=[None, w, h, c], name='x')
y_ = tf.compat.v1.placeholder(tf.int32, shape=[None, ], name='y_')
placeholder函数定义如下:
tf.placeholder(dtype, shape=None, name=None),placeholder是占位符,在tensorflow中类似于函数参数,运行时必须传入值。
dtype:数据类型。常用的是tf.float32,tf.float64等数值类型。
shape:数据形状。默认是None,就是一维值,也可以是多维,比如[2,3], [None, 3]表示列是3,行不定, 此参数可以根据提供的数据推导得到,不一定要给出。。
name:名称, 比如常在上边的x, y_。
比如计算3*4=12
import tensorflow as tf
import numpy as np
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.multiply(input1, input2)
with tf.Session() as sess:
print sess.run(output, feed_dict = {input1:[3.], input2: [4.]})
计算矩阵相乘x*y
import tensorflow as tf
import numpy as np
x = tf.placeholder(tf.float32, shape=(1024, 1024))
y = tf.matmul(x, x)
with tf.Session() as sess:
# print(sess.run(y)) # ERROR: x is none now
rand_array = np.random.rand(1024, 1024)
print(sess.run(y, feed_dict={x: rand_array})) # Will succeed.
使用库函数进行矩阵运算
import tensorflow as tf
# 定义placeholder
input1 = tf.placeholder(tf.float32,shape=(1, 2),name="input-1")
input2 = tf.placeholder(tf.float32,shape=(2, 1),name="input-2")
# 定义矩阵乘法运算(注意区分matmul和multiply的区别:matmul是矩阵乘法,multiply是点乘)
output = tf.matmul(input1, input2)
# 通过session执行乘法运行
with tf.Session() as sess:
# 执行时要传入placeholder的值
print sess.run(output, feed_dict = {input1:[1,2], input2:[3,4]})
# 最终执行结果 [11]
2#:卷积和池化
卷积层
tf.nn.conv2d(input, filter, strides=, padding=, name=None)
计算给定4-D input和filter张量的2维卷积
*
input:给定的输入张量,具有[batch, heigth, width, channel],类型为float32, 64
*
filter:指定过滤器的大小,[filter_height, filter_width, in_channels, out_channels]. out_channels:窗口数量
*
strides:strides = [1, stride, stride, 1],步长
*
padding:“SAME”, “VALID”,使用的填充算法的类型,使用“SAME”。其中”VALID”表示滑动超出部分舍弃,“SAME”表示填充,使得变化后height, width一样大
新的激活函数-Reluf(x) = max(0, x)
tf.nn.relu(features, name=None)
features: 卷积后加上偏置的结果
return: 结果
1.
采用sigmoid等函数,反向传播求误差梯度时,计算量相对大,而采用Relu激活函数,整个过程的计算量节省很多
1.
对于深层网络,sigmoid函数反向传播时,很容易就会出现梯度消失的情况(求不出权重和偏置)
池化层(Pooling)计算
Pooling层主要的作用是特征提取,通过去掉Feature Map中不重要的样本,(这里如何确定什么参数不重要是个很难的问题,哪些样本不重要,这个是很不好判断的,)进一步减少参数数量。Pooling的方法很多,最常用的是Max Pooling。
tf.nn.max_pool(value, ksize=, strides=, padding=,name=None)
输入上执行最大池数
*
value: 4-D Tensor形状[batch, height, width, channels]
*
ksize: 池化窗口大小,[1, ksize, ksize, 1]
*
strides:步长大小,[1, strides, strides, 1]
*
padding: “SAME”, “VALID”,使用的填充算法的类型,使用“SAME”
Full Connected层(全连接层)
前面的卷积和池化相当于做特征工程,后面的全连接相当于做特征加权。最后的全连接层在整个卷积神经网络中起到“分类器”的作用。
函数的作用是将tensor变换为参数shape的形式。
其中shape为一个列表形式,特殊的一点是列表中可以存在-1。-1代表的含义是不用我们自己指定这一维的大小,函数会自动计算,但列表中只能存在一个-1。(当然如果存在多个-1,就是一个存在多解的方程了)
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 输入层# 准备占位符with tf.variable_scope('data'):
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.float32, [None, 10])
# 卷积层# 卷积1with tf.variable_scope('conv1'):
# 初始化权重 窗口3*3 步长1 32个窗口
weight1 = tf.Variable(tf.random_normal(shape=[3, 3, 1, 32]))
bias1 = tf.Variable(tf.constant(1.0, shape=[32]))
x_reshaped = tf.reshape(x, [-1, 28, 28, 1])
# x [None, 28, 28, 1] ----> [None, 28, 28, 32]
conved1 = tf.nn.conv2d(input=x_reshaped, filter=weight1, strides=[1, 1, 1, 1], padding='SAME')
print(conved1)
relu1 = tf.nn.relu(conved1) + bias1
print(relu1)# 池化1with tf.variable_scope('pool'):
# 窗口2*2 步长2 x [None, 28, 28, 32] ----> [None, 14, 14, 32]
pool1 = tf.nn.max_pool(value=relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
print(pool1)
# 卷积2with tf.variable_scope('conv2'):
# 窗口3*3 步长1 64个窗口
weight2 = tf.Variable(tf.random_normal([3, 3, 32, 64]))
bias2 = tf.Variable(tf.constant(1.0, shape=[64]))
# x [None, 14, 14, 32] ----> [None, 14, 14, 64]
conved2 = tf.nn.conv2d(input=pool1, filter=weight2, strides=[1, 1, 1, 1], padding='SAME')
print(conved2)
relu2 = tf.nn.relu(conved2) + bias2
# 池化2with tf.variable_scope('pool2'):
# 窗口2*2 步长2 x [None, 14, 14, 64] ----> [None, 7, 7, 64]
pool2 = tf.nn.max_pool(value=relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
# 全连接层 x[None, 7, 7, 64] -> x[None, 7*7*64] * ([7*7*64, 10]) ----> y[None, 10]with tf.variable_scope('full_coon'):
x_fc = tf.reshape(pool2, [-1, 7*7*64])
weight_fc = tf.Variable(tf.random_normal([7*7*64, 10]))
bias_fc = tf.Variable(tf.constant(1.0, shape=[10]))
y_predict = tf.matmul(x_fc, weight_fc) + bias_fc
# 交叉熵损失函数with tf.variable_scope('loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))
train_op = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
# 计算准确度with tf.variable_scope('acc'):
equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))
init_var = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init_var)
mnist = input_data.read_data_sets('./input_data', one_hot=True)
for i in range(1000):
image, label = mnist.train.next_batch(100)
sess.run(train_op, feed_dict={x: image, y_true: label})
print('第%s步,准确率为: %s' % (i, sess.run(accuracy, feed_dict={x: image, y_true: label})))
一个模型例子:::
def model(input_tensor, train,
regularizer):
with tf.variable_scope('layer1-conv1'): # 定义一个作用域:layer1-conv1,在该作用域下面可以定义相同名称的变量(用于变量)
conv1_weights = tf.get_variable("weight", [5, 5, 3, 32],
initializer=tf.truncated_normal_initializer(stddev=0.1))
# 定义变量权重:weight,名称是weight;5,5代表卷积核的大小,3代表输入的信道数目,32代表输出的信道数目;initializer代表神经网络权重和卷积核的推荐初始值,生成截断正态分布随机数,服从标准差为0.1
conv1_biases = tf.get_variable("bias", [32], initializer=tf.constant_initializer(0.0))
# 定义变量偏置:bias,名称bias,[32]代表当前层的深度;initializer代表偏置的初始化,用函数tf.constant_initializer将其初始化为0,也可以初始化为tf.zeros_initializer或者tf.ones_initializer
conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
# 上面为定义卷积层:input_tensor为当前层的节点矩阵;conv1_weights代表卷积层的权重;strides为不同方向上面的步长;padding标识填充,有两种方式,SAME表示用0填充,“VALID”表示不填充。
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases)) # 定义激活函数:利用bias_add给每个节点都加上偏置项,然后利用relu函数去线性化
with tf.name_scope("layer2-pool1"): # 定义一个:layer2-pool1(用于op)
# 池化层可以优先缩小矩阵的尺寸,从而减小最后全连接层当中的参数;池化层既可以加快计算速度,也可以防止过拟合。
pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
# ksize代表pool窗口的尺寸,首尾两个数必须是1,ksize最常用[1,2,2,1]和[1,3,3,1];strides代表filter的步长,首尾两个数必须为1;padding代表填充方式;
with tf.variable_scope("layer3-conv2"): # 定义作用域(用于变量)
# 定义权重
conv2_weights = tf.get_variable("weight", [5, 5, 32, 64],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv2_biases = tf.get_variable("bias", [64], initializer=tf.constant_initializer(0.0)) # 定义偏置
conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME') # 定义卷积层
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases)) # 定义激活函数
with tf.name_scope("layer4-pool2"): # 定义命名空间(用于op)
pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID') # 定义池化层
with tf.variable_scope("layer5-conv3"): # 定义作用域 (用于变量)
# 定义权重
conv3_weights = tf.get_variable("weight", [3, 3, 64, 128],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv3_biases = tf.get_variable("bias", [128], initializer=tf.constant_initializer(0.0)) # 定义偏置
conv3 = tf.nn.conv2d(pool2, conv3_weights, strides=[1, 1, 1, 1], padding='SAME') # 定义卷积层
relu3 = tf.nn.relu(tf.nn.bias_add(conv3, conv3_biases)) # 定义激活函数
with tf.name_scope("layer6-pool3"): # 定义命名空间(用于op)
pool3 = tf.nn.max_pool(relu3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID') # 定义池化层
with tf.variable_scope("layer7-conv4"): # 定义作用域(用于变量)
# 定义权重
conv4_weights = tf.get_variable("weight", [3, 3, 128, 128],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv4_biases = tf.get_variable("bias", [128], initializer=tf.constant_initializer(0.0)) # 定义偏置
conv4 = tf.nn.conv2d(pool3, conv4_weights, strides=[1, 1, 1, 1], padding='SAME') # 定义卷积层
relu4 = tf.nn.relu(tf.nn.bias_add(conv4, conv4_biases)) # 定义激活函数
with tf.name_scope("layer8-pool4"): # 定义命名空间(用于op)
pool4 = tf.nn.max_pool(relu4, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID') # 定义池化层
nodes = 6 * 6 * 128
reshaped = tf.reshape(pool4, [-1, nodes])
print("shape of reshaped:", reshaped.shape) # reshape函数将pool4的输出转化成向量
# 定义作用域:
with tf.variable_scope('layer9-fc1'):
# 定义全连接层的权重:
fc1_weights = tf.get_variable("weight", [nodes, 1024], initializer=tf.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.add_to_collection('losses', regularizer(fc1_weights))
# 给全连接层的权重添加正则项,tf.add_to_collection函数可以把变量放入一个集合,把很多变量变成一个列表
fc1_biases = tf.get_variable("bias", [1024], initializer=tf.constant_initializer(0.1)) # 定义全连接层的偏置:
fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases) # 定义激活函数:
if train: fc1 = tf.nn.dropout(fc1, 0.5) # 针对训练数据,在全连接层添加dropout层,防止过拟合
with tf.variable_scope('layer10-fc2'):
fc2_weights = tf.get_variable("weight", [1024, 512], initializer=tf.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.add_to_collection('losses', regularizer(fc2_weights))
fc2_biases = tf.get_variable("bias", [512], initializer=tf.constant_initializer(0.1))
fc2 = tf.nn.relu(tf.matmul(fc1, fc2_weights) + fc2_biases)
if train: fc2 = tf.nn.dropout(fc2, 0.5)
with tf.variable_scope('layer11-fc3'):
fc3_weights = tf.get_variable("weight", [512, 5],
initializer=tf.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.add_to_collection('losses', regularizer(fc3_weights))
fc3_biases = tf.get_variable("bias", [5], initializer=tf.constant_initializer(0.1))
logit = tf.matmul(fc2, fc3_weights) + fc3_biases
return logit
def inference(input_tensor, train, regularizer):
with tf.compat.v1.variable_scope('layer1-conv1'):
# 定义变量权重:weight,名称是weight;5,5代表卷积核的大小,3代表输入的信道数目,32代表输出的信道数目;
# initializer代表神经网络权重和卷积核的推荐初始值,生成截断正态分布随机数,服从标准差为0.1
conv1_weights = tf.compat.v1.get_variable("weight", [5, 5, 3, 32],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
# 初始化偏置值为0
conv1_biases = tf.compat.v1.get_variable("bias", [32], initializer=tf.constant_initializer(0.0))
conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))
with tf.name_scope("layer2-pool1"):
pool1 = tf.compat.v1.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
logging.info(f"First convolutional layer:{pool1}")
with tf.compat.v1.variable_scope("layer3-conv2"):
conv2_weights = tf.compat.v1.get_variable("weight", [5, 5, 32, 64],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
conv2_biases = tf.compat.v1.get_variable("bias", [64], initializer=tf.compat.v1.constant_initializer(0.0))
conv2 = tf.compat.v1.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME')
relu2 = tf.compat.v1.nn.relu(tf.nn.bias_add(conv2, conv2_biases))
with tf.name_scope("layer4-pool2"):
pool2 = tf.compat.v1.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
logging.info(f"Second convolutional layer:{pool2}")
with tf.compat.v1.variable_scope("layer5-conv3"):
conv3_weights = tf.compat.v1.get_variable("weight", [3, 3, 64, 128],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
conv3_biases = tf.compat.v1.get_variable("bias", [128], initializer=tf.compat.v1.constant_initializer(0.0))
conv3 = tf.compat.v1.nn.conv2d(pool2, conv3_weights, strides=[1, 1, 1, 1], padding='SAME')
relu3 = tf.compat.v1.nn.relu(tf.nn.bias_add(conv3, conv3_biases))
with tf.compat.v1.name_scope("layer6-pool3"):
pool3 = tf.compat.v1.nn.max_pool(relu3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
logging.info(f"Third convolutional layer:{pool3}")
with tf.compat.v1.variable_scope("layer7-conv4"):
conv4_weights = tf.compat.v1.get_variable("weight", [3, 3, 128, 128],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
conv4_biases = tf.compat.v1.get_variable("bias", [128], initializer=tf.compat.v1.constant_initializer(0.0))
conv4 = tf.compat.v1.nn.conv2d(pool3, conv4_weights, strides=[1, 1, 1, 1], padding='SAME')
relu4 = tf.compat.v1.nn.relu(tf.nn.bias_add(conv4, conv4_biases))
with tf.compat.v1.name_scope("layer8-pool4"):
pool4 = tf.compat.v1.nn.max_pool(relu4, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
logging.info(f"The fourth convolutional layer:{pool4}")
nodes = 6 * 6 * 128
# 展开
reshaped = tf.compat.v1.reshape(pool4, [-1, nodes])
with tf.compat.v1.variable_scope('layer9-fc1'):
fc1_weights = tf.compat.v1.get_variable("weight", [nodes, 1024],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.compat.v1.add_to_collection('losses', regularizer * tf.nn.l2_loss(fc1_weights))
fc1_biases = tf.compat.v1.get_variable("bias", [1024], initializer=tf.compat.v1.constant_initializer(0.1))
fc1 = tf.compat.v1.nn.relu(tf.compat.v1.matmul(reshaped, fc1_weights) + fc1_biases)
logging.info(f"The first fully connected layer:{fc1}")
if train: fc1 = tf.compat.v1.nn.dropout(fc1, 0.5)
with tf.compat.v1.variable_scope('layer10-fc2'):
fc2_weights = tf.compat.v1.get_variable("weight", [1024, 512],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.compat.v1.add_to_collection('losses', regularizer * tf.nn.l2_loss(fc2_weights))
fc2_biases = tf.compat.v1.get_variable("bias", [512], initializer=tf.compat.v1.constant_initializer(0.1))
fc2 = tf.compat.v1.nn.relu(tf.matmul(fc1, fc2_weights) + fc2_biases)
if train: fc2 = tf.compat.v1.nn.dropout(fc2, 0.5)
logging.info(f"The second fully connected layer:{fc2}")
with tf.compat.v1.variable_scope('layer11-fc3'):
fc3_weights = tf.compat.v1.get_variable("weight", [512, 5],
initializer=tf.compat.v1.truncated_normal_initializer(stddev=0.1))
if regularizer != None: tf.compat.v1.add_to_collection('losses', regularizer * tf.nn.l2_loss(fc3_weights))
fc3_biases = tf.compat.v1.get_variable("bias", [5], initializer=tf.compat.v1.constant_initializer(0.1))
logit = tf.compat.v1.matmul(fc2, fc3_weights) + fc3_biases
return logit
conv1 = tf.nn.conv2d(input_tensor,conv1_weights,strides=[1,1,1,1],padding='SAME')
这是一个常见的卷积操作,其中strides=【1,1,1,1】表示滑动步长为1,padding=‘SAME’表示填0操作
当我们要设置步长为2时,strides=【1,2,2,1】,很多同学可能不理解了,这四个参数分别代表了什么,
strides在官方定义中是一个一维具有四个元素的张量,其规定前后必须为1。所以我们可以改的是中间两个数,中间两个数分别代表了水平滑动和垂直滑动步长值,于是就很好理解了。在卷积核移动逐渐扫描整体图时候,因为步长的设置问题,可能导致剩下未扫描的空间不足以提供给卷积核的,大小扫描 比如有图大小为5*5,卷积核为2*2,步长为2,卷积核扫描了两次后,剩下一个元素,不够卷积核扫描了,这个时候就在后面补零,补完后满足卷积核的扫描,这种方式就是same。如果说把刚才不足以扫描的元素位置抛弃掉,就是valid方式。
函数参数的解释:
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
除去name参数用以指定该操作的name,与方法有关的一共五个参数:
input:
指需要做卷积的输入图像,它要求是一个Tensor,具有[batch, in_height, in_width, in_channels]这样的shape,具体含义是[训练时一个batch的图片数量, 图片高度, 图片宽度, 图像通道数],注意这是一个4维的Tensor,要求类型为float32和float64其中之一
filter:
相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数input相同,有一个地方需要注意,第三维in_channels,就是参数input的第四维。【有时候也叫ksize或卷积核】
* strides:卷积时在图像每一维的步长,这是一个一维的向量,长度4。【注意:一般两边为1.形如[1,height,weight,1]】。
padding:
string类型的量,只能是”SAME”,”VALID”其中之一,这个值决定了不同的卷积方式【same:不够还加。valid:不够舍弃。】
use_cudnn_on_gpu:
bool类型,是否使用cudnn加速,默认为true
batch和Eposh
神经网络中Batch和Epoch之间的区别是什么? 随机梯度下降法是一种具有大量超参数的学习算法。两个超参数: Batch大小和Epoch数量,它们都是整数值。batch字面上是批量的意思,在深度学习中指的是计算一次cost需要的输入数据个数。 这意味着数据集将分为40个Batch,每个Batch有5个样本。每批五个样品后,模型权重将更新。 这也意味着一个epoch将涉及40个Batch或40个模型更新。 有1000个Epoch,模型将暴露或传递整个数据集1,000次。 一个 batch 的样本通常比单个输入更接近于总体输入数据的分布,batch 越大就越近似。然而,每个 batch 将花费更长的时间来处理,并且仍然只更新模型一次。
Sample: 样本,数据集中的一个元素,一条数据。
例1: 在卷积神经网络中,一张图像是一个样本。
例2: 在语音识别模型中,一段音频是一个样本。
Batch: 批,含有 N 个样本的集合。每一个 batch 的样本都是独立并行处理的。在训练时,一个 batch 的结果只会用来更新一次模型。
一个 batch 的样本通常比单个输入更接近于总体输入数据的分布,batch 越大就越近似。然而,每个 batch 将花费更长的时间来处理,并且仍然只更新模型一次。在推理(评估/预测)时,建议条件允许的情况下选择一个尽可能大的 batch,(因为较大的 batch 通常评估/预测的速度会更快)。
Epoch: 轮次,通常被定义为 「在整个数据集上的一轮迭代」,用于训练的不同的阶段,这有利于记录和定期评估。
当在 Keras 模型的 fit 方法中使用 validation_data 或 validation_split 时,评估将在每个 epoch 结束时运行。
在 Keras 中,可以添加专门的用于在 epoch 结束时运行的 callbacks 回调。例如学习率变化和模型检查点(保存)。
这也意味着一个epoch将涉及40个Batch或40个模型更新。
有1000个Epoch,模型将暴露或传递整个数据集1,000次。在整个培训过程中,总共有40,000Batch。
用一个小例子来说明这一点。
假设您有一个包含200个样本(数据行)的数据集,并且您选择的Batch大小为5和1,000个Epoch。
这意味着数据集将分为40个Batch,每个Batch有5个样本。每批五个样品后,模型权重将更新。
池化
池化过程在一般卷积过程后。池化(pooling) 的本质,其实就是采样。Pooling 对于输入的 Feature Map,选择某种方式对其进行降维压缩,以加快运算速度。
池化的作用:
(1)保留主要特征的同时减少参数和计算量,防止过拟合。
(2)invariance(不变性),这种不变性包括translation(平移),rotation(旋转),scale(尺度)。
Pooling 层说到底还是一个特征选择,信息过滤的过程。也就是说我们损失了一部分信息,这是一个和计算性能的一个妥协,随着运算速度的不断提高,我认为这个妥协会越来越小。
现在有些网络都开始少用或者不用pooling层了。