标准卷积的初始化和详细计算步骤,在代码中哪一步开始更新卷积核(权重)
flyfish
卷积 - 感受野(Receptive Field)
在卷积神经网络(CNN)中为什么可以使用多个较小的卷积核替代一个较大的卷积核,以达到相同的感受野
卷积操作转换为矩阵乘法
动画展示卷积的计算过程
卷积层的输出
卷积的计算 - numpy的实现 1
卷积的计算 - numpy的实现 2
卷积的计算 - im2col 1
卷积的计算 - im2col 2
卷积的计算 - im2col 3
卷积核的初始化
实践经验,卷积核(权重)常用的初始化方法包括:
-
随机初始化 :
使用标准正态分布(均值为0,标准差为1)的随机数初始化权重。
例如:nn.init.normal_(tensor, mean=0, std=1)
-
均匀分布初始化 :
使用均匀分布的随机数初始化权重。
例如:nn.init.uniform_(tensor, a=0, b=1)
-
Xavier初始化(Glorot初始化) :
适用于Sigmoid和Tanh激活函数。
公式:U[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]
例如:nn.init.xavier_uniform_(tensor)
-
He初始化(Kaiming初始化) :
适用于ReLU激活函数。
公式:N(0, sqrt(2 / fan_in))
例如:nn.init.kaiming_normal_(tensor, mode='fan_in', nonlinearity='relu')
-
常数初始化 :
使用常数值初始化权重。
例如:nn.init.constant_(tensor, val=0)
卷积核初始化代码示例
一个简单的卷积神经网络中初始化卷积核。这里使用了Xavier初始化方法来初始化权重,并将偏置初始化为零。
import torch
import torch.nn as nn
import torch.nn.init as init
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
# Initialize weights using Xavier initialization
init.xavier_uniform_(self.conv1.weight)
init.xavier_uniform_(self.conv2.weight)
# Initialize biases (if using) to zero
if self.conv1.bias is not None:
init.constant_(self.conv1.bias, 0)
if self.conv2.bias is not None:
init.constant_(self.conv2.bias, 0)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
return x
# Instantiate the network
net = ConvNet()
# Print initialized weights
print("Conv1 weights:", net.conv1.weight)
print("Conv2 weights:", net.conv2.weight)
一个完整的训练示例
import torch
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的卷积神经网络
import torch
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的卷积神经网络
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
return x
# 初始化网络、损失函数和优化器
net = SimpleCNN()
criterion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 示例输入和目标
input_tensor = torch.randn(1, 1, 5, 5)
target_tensor = torch.randn(1, 1, 1, 1)
# 示例训练步骤
num_epochs = 3
for epoch in range(num_epochs):
print(f'Epoch {
epoch+1}/{
num_epochs}',":start")
optimizer.zero_grad() # 清空梯度
outputs = net(input_tensor) # 前向传播
loss = criterion(outputs, target_tensor) # 计算损失
loss.backward() # 反向传播,计算梯度
print(f'Updated before optimizer.step() conv1 weights: {
net.conv1.weight}')
optimizer.step() # 使用优化器更新参数
# 打印损失
print(f'Epoch {
epoch+1}/{
num_epochs}, Loss: {
loss.item()}')
# 打印卷积核的梯度
print(f'Gradient of conv1 weights after backward: {
net.conv1.weight.grad}')
# 打印更新后的卷积核权重
print(f'Updated after optimizer.step()conv1 weights: {
net.conv1.weight}')
代码解释
对每个 epoch:
1. optimizer.zero_grad()
:清空之前计算的梯度。
2. outputs = net(input_tensor)
:前向传播,计算模型输出。
3. loss = criterion(outputs, target_tensor)
:计算损失。
4. loss.backward()
:反向传播,计算梯度,并将其存储在参数的 .grad
属性中。
5. optimizer.step()
:优化器根据存储的梯度和学习率更新模型的参数。
打印输出 :
Epoch 1/3 :start
Updated before optimizer.step() conv1 weights: Parameter containing:
tensor([[[[-0.0435, -0.0704, 0.0776],
[-0.2496, -0.2133, 0.1125],
[-0.0481, -0.2408, 0.1175]]]], requires_grad=True)
Epoch 1/3, Loss: 0.12892843782901764
Gradient of conv1 weights after backward: tensor([[[[ 0.0707, -0.2553, -0.1103],
[ 0.3029, 0.4249, 0.3205],
[-0.2261, -0.0902, 0.1702]]]])
Updated after optimizer.step()conv1 weights: Parameter containing:
tensor([[[[-0.0442, -0.0679, 0.0787],
[-0.2527, -0.2175, 0.1093],
[-0.0459, -0.2399, 0.1158]]]], requires_grad=True)
Epoch 2/3 :start
Updated before optimizer.step() conv1 weights: Parameter containing:
tensor([[[[-0.0442, -0.0679, 0.0787],
[-0.2527, -0.2175, 0.1093],
[-0.0459, -0.2399, 0.1158]]]], requires_grad=True)
Epoch 2/3, Loss: 0.11386992782354355
Gradient of conv1 weights after backward: tensor([[[[ 0.0681, -0.2368, -0.1066],
[ 0.2894, 0.4049, 0.2971],
[-0.2128, -0.0811, 0.1610]]]])
Updated after optimizer.step()conv1 weights: Parameter containing:
tensor([[[[-0.0449, -0.0655, 0.0797],
[-0.2556, -0.2216, 0.1063],
[-0.0437, -0.2391, 0.1142]]]], requires_grad=True)
Epoch 3/3 :start
Updated before optimizer.step() conv1 weights: Parameter containing:
tensor([[[[-0.0449, -0.0655, 0.0797],
[-0.2556, -0.2216, 0.1063],
[-0.0437, -0.2391, 0.1142]]]], requires_grad=True)
Epoch 3/3, Loss: 0.10046691447496414
Gradient of conv1 weights after backward: tensor([[[[ 0.0655, -0.2198, -0.1027],
[ 0.2760, 0.3853, 0.2757],
[-0.2002, -0.0729, 0.1523]]]])
Updated after optimizer.step()conv1 weights: Parameter containing:
tensor([[[[-0.0455, -0.0633, 0.0808],
[-0.2583, -0.2254, 0.1036],
[-0.0417, -0.2383, 0.1127]]]], requires_grad=True)
根据输出结果
Updated after optimizer.step()conv1 weights: Parameter containing:
tensor([[[[-0.0442, -0.0679, 0.0787],
[-0.2527, -0.2175, 0.1093],
[-0.0459, -0.2399, 0.1158]]]], requires_grad=True)
Epoch 2/3 :start
Updated before optimizer.step() conv1 weights: Parameter containing:
tensor([[[[-0.0442, -0.0679, 0.0787],
[-0.2527, -0.2175, 0.1093],
[-0.0459, -0.2399, 0.1158]]]], requires_grad=True)
net.conv1.weight.grad
存储了 conv1
层的权重梯度,
optimizer.step()
则真正的更新模型的参数。
更新模型参数
在卷积神经网络中,每一层都有若干卷积核。这些卷积核的权重和偏置是在训练过程中更新的。
通过反向传播算法(backpropagation),计算每个参数的梯度,并使用优化算法(如SGD、Adam等)更新这些权重和偏置,以最小化损失函数。其中特征图是通过卷积运算得到的输出,不是模型的参数。使用优化器更新参数 optimizer.step()这步更新的就是卷积核 (权重)和偏置 (如果有的话)
标准卷积操作详细计算
参数
-
输入特征图:5x5,内容为 1 到 25
-
卷积核:3x3,内容为 1 到 9
-
步幅:1
-
填充:0
输入特征图和卷积核
输入特征图 :
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
卷积核 :
1 2 3
4 5 6
7 8 9
使用 PyTorch 计算结果
import torch
import torch.nn as nn
# 定义输入特征图和卷积核
input_tensor = torch.tensor([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]
], dtype=torch.float32).unsqueeze(0).unsqueeze(0) # 添加批次维度和通道维度
# 定义卷积核
kernel = torch.tensor([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
], dtype=torch.float32)
# 转换卷积核为与Conv2d兼容的形状
weight = kernel.view(1, 1, 3, 3) # 1个输出通道,1个输入通道,3x3卷积核
# 定义卷积层
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0, bias=False)
# 设置卷积层的权重为我们定义的卷积核
conv.weight = nn.Parameter(weight)
# 执行卷积操作
output_tensor = conv(input_tensor)
# 打印输出特征图
print(output_tensor)
运行结果应该如下:
tensor([[[[411., 456., 501.], [636., 681., 726.], [861., 906., 951.]]]], grad_fn=<ConvolutionBackward0>)
计算输出特征图尺寸
根据公式:
H out = ⌊ H in + 2 P − K S ⌋ + 1 H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} + 2P - K}{S} \right\rfloor + 1 Hout=⌊SHin+2P−K⌋+1
W out = ⌊ W in + 2 P − K S ⌋ + 1 W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} + 2P - K}{S} \right\rfloor + 1 Wout=⌊SWin+2P−K⌋+1由于步幅 S = 1 S=1 S=1,填充 P = 0 P=0 P=0,卷积核大小 K = 3 K=3 K=3,输入特征图大小 5 × 5 5 \times 5 5×5:
H out = ⌊ 5 + 0 − 3 1 ⌋ + 1 = 3 H_{\text{out}} = \left\lfloor \frac{5 + 0 - 3}{1} \right\rfloor + 1 = 3 Hout=⌊15+0−3⌋+1=3
W out = ⌊ 5 + 0 − 3 1 ⌋ + 1 = 3 W_{\text{out}} = \left\lfloor \frac{5 + 0 - 3}{1} \right\rfloor + 1 = 3 Wout=⌊15+0−3⌋+1=3
所以输出特征图的尺寸是 3x3。
详细逐位置计算输出特征图
第一位置(左上角,起始点 (0,0))
输入区域:
1 2 3
6 7 8
11 12 13
卷积计算:
1*1 + 2*2 + 3*3 + 6*4 + 7*5 + 8*6 + 11*7 + 12*8 + 13*9
= 1 + 4 + 9 + 24 + 35 + 48 + 77 + 96 + 117 = 411
第二位置(水平移动一格,起始点 (0,1))
输入区域:
2 3 4
7 8 9
12 13 14
卷积计算:
2*1 + 3*2 + 4*3 + 7*4 + 8*5 + 9*6 + 12*7 + 13*8 + 14*9
= 2 + 6 + 12 + 28 + 40 + 54 + 84 + 104 + 126 = 456
第三位置(水平移动一格,起始点 (0,2))
输入区域:
3 4 5
8 9 10
13 14 15
卷积计算:
3*1 + 4*2 + 5*3 + 8*4 + 9*5 + 10*6 + 13*7 + 14*8 + 15*9
= 3 + 8 + 15 + 32 + 45 + 60 + 91 + 112 + 135 = 501
第四位置(垂直移动一格,起始点 (1,0))
输入区域:
6 7 8
11 12 13
16 17 18
卷积计算:
6*1 + 7*2 + 8*3 + 11*4 + 12*5 + 13*6 + 16*7 + 17*8 + 18*9
= 6 + 14 + 24 + 44 + 60 + 78 + 112 + 136 + 162 = 636
第五位置(水平移动一格,起始点 (1,1))
输入区域:
7 8 9
12 13 14
17 18 19
卷积计算:
7*1 + 8*2 + 9*3 + 12*4 + 13*5 + 14*6 + 17*7 + 18*8 + 19*9
= 7 + 16 + 27 + 48 + 65 + 84 + 119 + 144 + 171 = 681
第六位置(水平移动一格,起始点 (1,2))
输入区域:
8 9 10
13 14 15
18 19 20
卷积计算:
8*1 + 9*2 + 10*3 + 13*4 + 14*5 + 15*6 + 18*7 + 19*8 + 20*9
= 8 + 18 + 30 + 52 + 70 + 90 + 126 + 152 + 180 = 726
第七位置(垂直移动一格,起始点 (2,0))
输入区域:
11 12 13
16 17 18
21 22 23
卷积计算:
11*1 + 12*2 + 13*3 + 16*4 + 17*5 + 18*6 + 21*7 + 22*8 + 23*9
= 11 + 24 + 39 + 64 + 85 + 108 + 147 + 176 + 207 = 861
第八位置(水平移动一格,起始点 (2,1))
输入区域:
12 13 14
17 18 19
22 23 24
卷积计算:
12*1 + 13*2 + 14*3 + 17*4 + 18*5 + 19*6 + 22*7 + 23*8 + 24*9
= 12 + 26 + 42 + 68 + 90 + 114 + 154 + 184 + 216 = 906
第九位置(水平移动一格,起始点 (2,2))
输入区域:
13 14 15
18 19 20
23 24 25
卷积计算:
13*1 + 14*2 + 15*3 + 18*4 + 19*5 + 20*6 + 23*7 + 24*8 + 25*9
= 13 + 28 + 45 + 72 + 95 + 120 + 161 + 192 + 225 = 951
最终输出特征图
通过上述计算步骤,我们得到输出特征图为:
411 456 501
636 681 726
861 906 951
文章评论