1. 从全连接层到卷积
卷积神经网络正是将空间不变性(spatial invariance)的这一概念系统化,从而基于这个模型使用较少的参数来学习有用的表示。
-
平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。
-
局部性(locality):神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,这就是“局部性”原则。最终,可以聚合这些局部特征,以在整个图像级别进行预测。
-
图像的平移不变性使我们以相同的方式处理局部图像,而不在乎它的位置。
-
局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。
-
在图像处理中,卷积层通常比全连接层需要更少的参数,但依旧获得高效用的模型。
-
卷积神经网络(CNN)是一类特殊的神经网络,它可以包含多个卷积层。
-
多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。
2. 图像卷积
import tensorflow as tf
#from d2l import tensorflow as d2l
def corr2d(X, K): #该函数接受两个参数:输入张量X和卷积核张量K
"""计算二维互相关运算"""
h, w = K.shape #获取卷积核张量的形状(高度和宽度)
Y = tf.Variable(tf.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1)))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j].assign(tf.reduce_sum(
X[i: i + h, j: j + w] * K))
return Y
X = tf.constant([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = tf.constant([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)
<tf.Variable 'Variable:0' shape=(2, 2) dtype=float32, numpy= array([[19., 25.], [37., 43.]], dtype=float32)>
卷积层中的两个被训练的参数是卷积核权重和标量偏置
class Conv2D(tf.keras.layers.Layer):
def __init__(self):
super().__init__()
def build(self, kernel_size):
initializer = tf.random_normal_initializer()
self.weight = self.add_weight(name='w', shape=kernel_size,
initializer=initializer)
self.bias = self.add_weight(name='b', shape=(1, ),
initializer=initializer)
def call(self, inputs):
return corr2d(inputs, self.weight) + self.bias
学习卷积核
我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y
与卷积层输出的平方误差,然后计算梯度来更新卷积核。为了简单起见,我们在此使用内置的二维卷积层,并忽略偏置。
# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = tf.keras.layers.Conv2D(1, (1, 2), use_bias=False)#use_bias=False:指定该层不使用偏置项。
# 这个二维卷积层使用四维输入和输出格式(批量大小、高度、宽度、通道),
# 其中批量大小和通道数都为1
X = tf.reshape(X, (1, 6, 8, 1))
Y = tf.reshape(Y, (1, 6, 7, 1))
lr = 3e-2 # 学习率
Y_hat = conv2d(X)
for i in range(10):
with tf.GradientTape(watch_accessed_variables=False) as g:
g.watch(conv2d.weights[0])
Y_hat = conv2d(X)#通过conv2d(X)计算出模型的预测值Y_hat
l = (abs(Y_hat - Y)) ** 2
# 迭代卷积核
update = tf.multiply(lr, g.gradient(l, conv2d.weights[0]))#计算损失函数(l)对于第一层卷积层的权重参数(conv2d.weights[0])的梯度。该梯度表示了损失函数在当前参数下的变化方向和大小。
#将梯度和学习率(lr)相乘,得到权重更新的步长。学习率代表了我们希望每次更新权重的大小,通常设置为一个比较小的数值,以避免权重更新过大导致模型不稳定。
weights = conv2d.get_weights()#获取当前卷积层的权重参数。
weights[0] = conv2d.weights[0] - update#将当前权重参数减去步长得到新的权重参数。这里使用了梯度下降法来更新权重参数,即每次将当前参数朝着损失函数下降的方向更新一小步。
conv2d.set_weights(weights)#将新的权重参数设置回卷积层中,更新模型的参数。
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {tf.reduce_sum(l):.3f}')