卷积层在深度学习中常常用到。下面介绍一下卷积层中的参数,并使用一个DQN中的例子来推算卷积层输出张量的形状。
1. 卷积层参数及其意义
卷积层的意义在于提取给定的数据的特征,池化层的意义在于减少数据的大小,减少运算量。
1.1 卷积参数计算的过程分析
[
l
]
[l]
[l]:层数l
f
[
l
]
f^{[l]}
f[l]:filter的大小
p
[
l
]
p^{[l]}
p[l]:padding
s
[
l
]
s^{[l]}
s[l]:步长stride
n
c
[
l
]
n^{[l]}_c
nc[l]:filter的数量(也就是输出数据Channel的数量)
n
c
[
l
−
1
]
n^{[l-1]}_c
nc[l−1]:输入数据Channel的数量
bias:每层的偏离值,是一个实数,即为逻辑回归中的b参数
输入张量形状:
n
H
[
l
−
1
]
∗
n
W
[
l
−
1
]
∗
n
[
l
−
1
]
n^{[l-1]}_H * n^{[l-1]}_W * n^{[l-1]}
nH[l−1]∗nW[l−1]∗n[l−1]
输出张量形状:
n
H
[
l
]
∗
n
W
[
l
]
∗
n
c
[
l
]
n^{[l]}_H * n^{[l]}_W * n^{[l]}_c
nH[l]∗nW[l]∗nc[l]
其中可以计算获得
n
H
[
l
]
=
⌊
n
H
[
l
−
1
]
+
2
p
[
l
]
−
f
[
l
]
s
[
l
]
+
1
⌋
n^{[l]}_H = \lfloor \frac{n^{[l-1]}_H + 2p^{[l]} - f^{[l]}}{s^{[l]}} + 1 \rfloor
nH[l]=⌊s[l]nH[l−1]+2p[l]−f[l]+1⌋
n
W
[
l
]
=
⌊
n
W
[
l
−
1
]
+
2
p
[
l
]
−
f
[
l
]
s
[
l
]
+
1
⌋
n^{[l]}_W = \lfloor \frac{n^{[l-1]}_W + 2p^{[l]} - f^{[l]}}{s^{[l]}} + 1 \rfloor
nW[l]=⌊s[l]nW[l−1]+2p[l]−f[l]+1⌋
1.2 Valid与Same卷积
padding的作用是防止边缘的数据计算次数过少,导致计算结果难以显示边缘的特征。
- Valid卷积中无padding;
- Same卷积的输入与输出大小一致,padding的数值可由 p = f − 1 2 p = \frac{f - 1}{2} p=2f−1确定。其中f一般是奇数。
2. DQN数据处理模型
在Dueling DQN的forward函数中,先卷积运算了两次,并将获得的结果展开至一维,之后分别使用价值函数和优势函数计算Value和Advantage,再用Value和Advantage预测出Q值。
卷积过程: nn.Conv2d(conv1) -> ReLU -> nn.Conv2d(conv2) -> ReLU
Value计算过程: NoisyLinear(fc_h_v) -> ReLU -> NoisyLinear(fc_z_v) -> ReLU
Advantage计算过程: NoisyLinear(fc_h_a) -> ReLU -> NoisyLinear(fc_z_a) -> ReLU
class DQN(nn.Module):
def __init__(self, args, action_space):
super().__init__()
self.atoms = args.atoms
self.action_space = action_space
# conv1,conv2与ReLU
self.convs = nn.Sequential(nn.Conv2d(args.history_length, 32, 5, stride=2, padding=0), nn.ReLU(),
nn.Conv2d(32, 32, 5, stride=2, padding=0), nn.ReLU())
self.conv_output_size = 70688
self.fc_h_v = NoisyLinear(self.conv_output_size, args.hidden_size, std_init=args.noisy_std)
self.fc_h_a = NoisyLinear(self.conv_output_size, args.hidden_size, std_init=args.noisy_std)
self.fc_z_v = NoisyLinear(args.hidden_size, self.atoms, std_init=args.noisy_std)
self.fc_z_a = NoisyLinear(args.hidden_size, action_space * self.atoms, std_init=args.noisy_std)
def forward(self, x, log=False) -> torch.Tensor:
x = self.convs(x)
x = x.view(-1, self.conv_output_size)
v = self.fc_z_v(F.relu(self.fc_h_v(x))) # Value stream
a = self.fc_z_a(F.relu(self.fc_h_a(x))) # Advantage stream
v, a = v.view(-1, 1, self.atoms), a.view(-1, self.action_space, self.atoms)
q = v + a - a.mean(1, keepdim=True) # Combine streams
if log: # Use log softmax for numerical stability
q = F.log_softmax(q, dim=2) # Log probabilities with action over second dimension
else:
q = F.softmax(q, dim=2) # Probabilities with action over second dimension
return q
3. 卷积层输出张量大小计算
以上述DQN中的卷积方式为例,计算最终输出至线性层中的张量的大小。
输入NN中的张量大小为4 * 200 * 200。
3.1 nn.Conv2d(conv1)
3.1.1 输入(即Env模块获得的State)
- Channel: 4
- Height: 200
- Width: 200
3.1.2 卷积参数
- Kernel(Filter)大小:5*5*5
- Kernel(Filter)数量:32
- 步长(Stride):2
- 无Padding
3.1.3 输出
- Channel: 32
- Height: (200 - 5) / 2 + 1 = 98
- Width: (200 - 5) / 2 + 1 = 98
3.2 nn.Conv2d(conv2)
3.2.1 输入
- Channel: 32
- Height: 98
- Width: 98
3.2.2 卷积参数
- Kernel(Filter)大小:5*5*5
- Kernel(Filter)数量:32
- 步长(Stride):2
- 无Padding
3.2.3 输出
- Channel: 32
- Height: (98 - 5) / 2 + 1 = 47
- Width: (98 - 5) / 2 + 1 = 47
3.3 结果展开
将conv2卷积获得的结果展开,获得的一维向量大小为Channel * Height * Width = 32 * 47 * 47 = 70688。之后将这个向量输入NoisyLinear进行处理,最后获得Q值。