6.1. 从全连接层到卷积
练习1:假设卷积层 覆盖的局部区域∆=0。在这种情况下,证明卷积内核为每组通道独立地实现一个全连接层。
首先:
卷积是什么?
①
②
通过公式①和②,我们可以进一步推出二维的卷积公式③
在本题中,当∆=0时,a,b的遍历就会变成0,X表示卷积核,V表示输入数据。
H(i,j,d)是输出特征图在位置(i,j)和通道d的值。
当 Δ=0 时,卷积操作在每个空间位置 (i,j) 上独立进行,且每个位置的输入 X (i,j,c)和输出 Y(i,j,k)之间的关系与全连接层的线性变换一致。因此,卷积核W 在每个位置上实现了与全连接层相同的操作。
练习2:为什么平移不变性可能也不是好主意呢?
某些局部特征的上下文依赖,某些任务需要模型对位置敏感,而平移不变性会降低模型的性能。
练习3:从图像边界像素获取隐藏表示时,需要特别关注以下问题:
1.边界像素的感受野不完整性。
2.填充方式的选择及其对特征表示的影响。
3.边界像素的特征表示偏差。
4.边界像素对任务性能的影响。
5.计算效率的优化。
6.边界像素的语义信息保护。
7.数据增强对边界像素的影响。
通过合理设计模型结构、填充方式和数据处理策略,可以有效解决这些问题,提升模型的性能和鲁棒性。
练习5:卷积层也适合于文本数据吗?为什么?
是的,卷积层也可以用于文本数据,并且在许多自然语言处理(NLP)任务中表现出色。尽管卷积层最初是为图像数据设计的,但其局部特征提取的能力同样适用于文本数据。
练习6:
6.2. 图像卷积
练习1:构建一个具有对角线边缘的图像X。
X =torch.tensor( [
[1, 0, 0, 0, 1],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 0, 0, 1]
])
构建5*5的对角线边缘图
如果将本节中举例的卷积核K应用于X,会发生什么情况?
可以发现垂直翻转相同
如果转置X会发生什么?
可以发现垂直翻转相同
如果转置K会发生什么?
可以发现水平翻转相同
练习2:在我们创建的Conv2D自动求导时,有什么错误消息?
只能计算二维张量
练习3:如何通过改变输入张量和卷积核张量,将互相关运算表示为矩阵乘法?
使输入张量和卷积核张量size相同
练习4:手工设计一些卷积核。
二阶导数的核的形式是什么?
积分的核的形式是什么?
得到d次导数的最小核的大小是多少?
6.3. 填充和步幅
练习1:对于本节中的最后一个示例,计算其输出形状,以查看它是否与实验结果一致。
输入形状是nhnw,卷积核形状是khkw,填充(padding)是ph和pw,步幅是sh*sw。
nh=8,kh=3,ph=0,sh=3
nw=8,kw=5,pw=1,sw=4
练习2:在本节中的实验中,试一试其他填充和步幅组合。
conv2d = nn.Conv2d(1, 1, kernel_size=(2, 2), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
torch.Size([3, 3])
将卷积核形状改为22,可以发现输出的形状则是33
练习3:对于音频信号,步幅2说明什么?
import torch
from torch import nn
# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):
# 这里的(1,1)表示批量大小和通道数都是1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# 省略前两个维度:批量大小和通道
return Y.reshape(Y.shape[2:])
# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1,stride=2)
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape
torch.Size([4, 4])
步幅为2则宽度高度减半
练习4:步幅大于1的计算优势是什么?
减少计算量
6.4. 多输入多输出通道
练习1:假设我们有两个卷积核,大小分别为k1和k2(中间没有非线性激活函数)。
①证明运算可以用单次卷积来表示。
如果没有非线性激活函数,所以卷积核的运算也算是线性的。
②这个等效的单个卷积核的维数是多少呢?
(k1+k2-1)(k1+k2-1)
③反之亦然吗?
不可以,可能不一定分解成k1和k2的卷积核
练习2:假设输入为cihw,卷积核大小为cocikhkw,填充为(ph,pw),步幅为(sh,sw)。
前向传播的计算成本(乘法和加法)是多少?
内存占用是多少?
反向传播的内存占用是多少?
反向传播的计算成本是多少?
练习3:如果我们将输入通道ci和输出通道co的数量加倍,计算数量会增加多少?如果我们把填充数量翻一番会怎么样?
练习4:如果卷积核的高度和宽度是kh=kw=1,前向传播的计算复杂度是多少?
当卷积核为1×1时,前向传播的计算复杂度为O(H⋅W⋅Cin⋅Cout)。
练习5:本节最后一个示例中的变量Y1和Y2是否完全相同?为什么?
看见别人说的不一样,但是我自己看是对的
result = torch.eq(Y1, Y2)
print(result)
tensor([[[True, True, True],
[True, True, True],
[True, True, True]],
[[True, True, True],
[True, True, True],
[True, True, True]]])
练习6:当卷积窗口不是1*1时,如何使用矩阵乘法实现卷积?
参考别人的链接https://zhuanlan.zhihu.com/p/698732548
def corr2d_matmul(X, K):
h, w = K.shape
# 将输入X中每次与卷积核做互相关运算的元素展平成一行
# X_reshaped的每一行元素均为与卷积核做互相关的元素
X_reshaped = torch.zeros((X.shape[0] - h + 1)*(X.shape[1] - w + 1), h*w)
k = 0
for i in range(X.shape[0] - h + 1):
for j in range(X.shape[1] - w + 1):
X_reshaped[k,:] = X[i:i + h, j:j + w].reshape(1,-1)
k += 1
# 将卷积核K转为列向量,并与X_reshaped做矩阵乘法,再reshape成输出尺寸即可
Y = X_reshaped@K.view(-1)
return Y.reshape(X.shape[0] - h + 1, X.shape[1] - w + 1)
def corr2d_matmul_multi_in(X, K):
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_matmul_multi_in(X, K)
tensor([[ 56., 72.],
[104., 120.]])
6.5. 汇聚层
练习1:尝试将平均汇聚层作为卷积层的特殊情况实现。
def pool2d_conv_avg(X, K_shape):
K = torch.ones(K_shape)
h, w = K.shape
Y = torch.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] = (X[i:i + h, j:j + w] * K).sum()/(h*w)
return Y
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
Y = pool2d_conv_avg(X, (2, 2))
Y
0 1 2
3 4 5
6 7 8
tensor([[2., 3.],
[5., 6.]])
练习2:尝试将最大汇聚层作为卷积层的特殊情况实现。
def pool2d_conv_max(X, K_shape):
K = torch.ones(K_shape)
h, w = K.shape
Y = torch.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] = (X[i:i + h, j:j + w] * K).max()
return Y
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
Y = pool2d_conv_max(X, (2, 2))
Y
tensor([[4., 5.],
[7., 8.]])
练习3:假设汇聚层的输入大小为chw,则汇聚窗口的形状为khkw,填充为(phpw),步幅为(shsw)。这个汇聚层的计算成本是多少?
一次汇聚的成本是khkw
总操作次数:[(h-kh+ph+sh)/sh][(wkw+pw+sw)/sw]
总计算成本:[(h-kh+ph+sh)/sh][(wkw+pw+sw)/sw]khkw
练习4:为什么最大汇聚层和平均汇聚层的工作方式不同?
最大汇聚层:用于提取每个局部区域最显著的特征,对小的位置变化具有鲁棒性,可以一定程度上增加对输入数据的平移不变性,并且对噪声不敏感。
平均汇聚层:用于保留每个局部区域的总体分布信息,提供一种更平滑的特征表示,通常可以减少特征的空间尺寸,同时尽量保持特征的统计信息,受噪声影响较大。
练习5:我们是否需要最小汇聚层?可以用已知函数替换它吗?
当需要提取局部较暗的像素点时,可能需要最小汇聚层。
可以将数据× -1,应用最大汇聚层,再将结果× -1,即可实现最小汇聚层的作用。
def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
elif mode == 'min':
Y[i, j] = (X[i: i + p_h, j: j + p_w]*-1).max()*-1
return Y
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
Y = pool2d(X, (2, 2), 'min')
Y
tensor([[0., 1.],
[3., 4.]])
练习6:除了平均汇聚层和最大汇聚层,是否有其它函数可以考虑(提示:回想一下softmax)?为什么它不流行?
Softmax Pooling 是一种基于 softmax 函数的汇聚方法,其核心思想是对汇聚窗口内的所有输入值进行加权求和,权重由 softmax 函数计算得出。
主要原因如下:
计算成本高:Softmax 函数涉及指数运算和归一化,计算复杂度较高,尤其是在大窗口或高分辨率输入时。相比之下,最大汇聚和平均汇聚的计算非常简单,只需取最大值或平均值。
缺乏明显的性能优势:在大多数计算机视觉任务中,最大汇聚和平均汇聚已经能够很好地提取特征。
Softmax Pooling 的额外灵活性并未显著提升模型性能,反而增加了计算负担。
梯度问题:Softmax 函数可能导致梯度饱和问题(当输入值较大时,softmax 的输出接近 0 或 1),这会影响反向传播的效率。最大汇聚和平均汇聚的梯度计算更稳定。
实用性有限:最大汇聚更适合提取显著特征(如边缘、纹理),而平均汇聚更适合平滑特征。Softmax Pooling 的加权求和方式在实际任务中并没有明确的优势,甚至可能引入不必要的噪声。
6.6. 卷积神经网络(LeNet)
练习1:将平均汇聚层替换为最大汇聚层,会发生什么?
将nn.AvgPool2d(kernel_size=2, stride=2)改为nn.MaxPool2d(kernel_size=2, stride=2)后,训练损失会降低,训练精度和测试精度均升高,过拟合缩小。
练习2:尝试构建一个基于LeNet的更复杂的网络,以提高其准确性。
1.调整卷积窗口大小。
2.调整输出通道的数量。
3.调整激活函数(如ReLU)。
4.调整卷积层的数量。
5.调整全连接层的数量。
6.调整学习率和其他训练细节(例如,初始化和轮数)。
import torch
from torch import nn
from d2l import torch as d2l
'''
1.调整卷积窗口大小。即修改kernel_size
2.调整输出通道的数量。即修改out_channels
3.调整激活函数(如ReLU)。即修改ReLU()
4.调整卷积层的数量。再次增加一个卷积层
5.调整全连接层的数量。再增加一个全连接层
6.调整学习率和其他训练细节(例如,初始化和轮数)。
'''
# 调整A-E
net3 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, padding=2), nn.ReLU(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(16, 32, kernel_size=3), nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(64 * 3 * 3, 256), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(256, 128), nn.ReLU(),nn.Dropout(0.2),
nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(64, 10))
# 调整F
def train_ch6_2(net, train_iter, test_iter, num_epochs, lr, device):
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
# He初始化在ReLU激活函数中表现良好
nn.init.kaiming_normal_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
lr, num_epochs = 0.3, 20
train_ch6_2(net3, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
练习3:在MNIST数据集上尝试以上改进的网络。
import torchvision
from torch.utils.data import DataLoader
mnist_train = torchvision.datasets.MNIST(
root="../data", train=True, transform=torchvision.transforms.ToTensor(), download=True)
mnist_test = torchvision.datasets.MNIST(
root="../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)
mnist_train_iter = torch.utils.data.DataLoader(mnist_train, batch_size, shuffle=True)
mnist_test_iter = torch.utils.data.DataLoader(mnist_test, batch_size, shuffle=True)
train_ch6(net3, mnist_train_iter, mnist_test_iter, num_epochs, lr, d2l.try_gpu())
练习4:显示不同输入(例如毛衣和外套)时,LeNet第一层和第二层的激活值。
def train_ch6_3(net, train_iter, test_iter, num_epochs, lr, device):
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
x_first_Sigmoid_layer = net[0:2](X)[0:9, 1, :, :]
d2l.show_images(x_first_Sigmoid_layer.reshape(9, 28, 28).cpu().detach(), 1, 9)
x_second_Sigmoid_layer = net[0:5](X)[0:9, 1, :, :]
d2l.show_images(x_second_Sigmoid_layer.reshape(9, 10, 10).cpu().detach(), 1, 9)
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
train_ch6_3(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())