[Pytorch] torch.nn.Conv2d
0. 개요
pytorch.nn 의 Conv2d 클래스 사용법을 알아본다.
convolution 개념을 알고 있어야 하므로, 모른다면 아래 글을 읽어보자.
(https://kalpaca.tistory.com/116)
1. torch.nn.Conv2d
torch.nn.Conv2d 는 pytorch 의 신경망 모듈인 nn 에 속해있는 클래스이고,
2차원 convolution layer 를 구현해 놓은 것이다.
주로 사용하는 클래스의 prototype 은 다음과 같다.
torch.nn.Conv2d(in_channels,
out_channels,
kernel_size,
stride=1,
padding=0,
dilation=1,
bias=True)
인자를 하나씩 살펴보도록 하자.
in_channels (필수)
forward 할 때 input 으로 들어올 2D 텐서의 채널 수를 의미한다.
out_channels (필수)
forward 할 때 output 으로 생성될 2D 텐서의 채널 수를 의미한다.
kernel_size (필수)
각 채널별로 convolution 연산을 실행할 때의 커널 크기를 의미한다.
2D convolution 이므로 kernel 도 2차원으로 생성된다.
kernel_size = 3 처럼 정수 하나만 넣으면,
$3 \times 3$ 크기의 정사각형 커널이 만들어진다.
커널이 반드시 정사각형일 이유는 없으므로,
kernel_size = ($H_{kernel}, W_{kernel}$) 처럼 튜플로 정수 2개를 넣으면,
$H_{kernel} \times W_{kernel}$ 크기의 직사각형 커널이 만들어진다.
stride
커널이 한 번에 이동하는 거리를 의미한다.
default 는 1로 설정되어 있다.
kernel_size 인자처럼, 정수 하나 또는 튜플로 된 정수 2개를 전달할 수 있다.
정수 하나만 넣으면 세로 / 가로 모두 한 번에 같은 거리만큼 이동하고,
튜플로 넣으면 세로 / 가로 각각 다른 거리만큼 이동시킬 수 있다.
padding
input 테두리의 주변으로 padding 값을 추가할 범위를 말한다.
인자로 전달된 값 만큼의 범위가 각 테두리마다 추가된다.
default 는 0이므로, padding 이 없는 상태다.
kernel_size 인자처럼, 정수 하나 또는 튜플로 된 정수 2개를 전달할 수 있다.
정수 하나만 넣으면 세로 / 가로 모두 같은 범위의 padding 을 추가한다.
튜플로 넣으면 세로 / 가로 각각 다른 범위의 padding 을 추가한다.
padding 에는 숫자 대신 'valid' 또는 'same' 이라는 문자열도 넣을 수 있다.
'valid' 는 padding 이 없는 것을 의미해서 사실상 쓸 이유는 없다.
'same' 은 output 이 input 크기와 동일하게 생성되도록, 자동으로 커널 크기를 설정해준다.
한 가지 주의사항이 있다.
'same' 으로 padding 인자를 설정하면 편한데, stride 가 1일 때에만 'same' 이 작동한다.
따라서 stride 를 증가시키고 싶다면, 'same' 을 쓰지 말자.
dilation
커널 원소 사이의 거리를 의미한다.
인자로 전달된 값 만큼 커널 원소의 세로 / 가로 방향의 거리가 멀어진다.
default 는 1이므로, 커널의 모든 원소가 연속해서 붙어있는 상태다.
kernel_size 인자처럼, 정수 하나 또는 튜플로 된 정수 2개를 전달할 수 있다.
정수 하나만 넣으면 세로 / 가로 모두 같은 거리만큼 커널 원소를 떨어뜨린다.
튜플로 넣으면 세로 / 가로 각각 다른 거리로 커널 원소를 떨어뜨릴 수 있다.
bias
편향을 사용할지 여부를 의미한다.
default 는 True 로 되어 있다.
2. weight / bias
Conv2d 는 학습이 가능한 layer 이다.
따라서 가중치와 편향이 존재하는데, 이 중 가중치는 kernel 이라고 생각하면 된다.
가중치와 커널의 shape 를 확인해보자.
먼저 아래와 같은 conv layer 를 만든다.
conv2d = nn.Conv2d(in_channels=5, out_channels=2, kernel_size=3)
print(conv2d)
>>>
Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
input 의 채널 수가 5, output 의 채널 수를 3으로 맞추는 layer 이다.
kernel 크기는 $3 \times 3$ 크기의 정사각형 모양이다.
이제 가중치와 편향의 shape 를 확인해보자.
# Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
print("weight:", conv2d.weight.shape)
print("bias:", conv2d.bias.shape)
>>>
weight: torch.Size([2, 5, 3, 3]) # out_channels X in_channels X H_kernel X W_kernel
bias : torch.Size([3]) # out_channels
우선 커널은 2차원이므로 $H_{kernel} \times W_{kernel}$ 모양이 된다.
input channel 마다 커널이 하나씩 있어야 하기 때문에, $\text{in_channels} \times H_{kernel} \times W_{kernel}$ 가 된다.
그리고 이게 또 output channel 마다 하나씩 있어야 한다.
결론적으로 $\text{out_channels} \times \text{in_channels} \times H_{kernel} \times W_{kernel}$ 모양의 가중치가 생성된다.
편향은 output channel 각각에 값 하나씩을 모두 똑같이 더해주면 된다.
따라서 $\text{out_channels}$ 크기만큼만 있으면 된다.
3. forward
이제 특정 input 을 Conv2d 에 통과시키면 어떤 output 이 나오는지 확인해보자.
output 의 값은 지금 당장은 의미 없으므로, shape 위주로 확인해보겠다.
먼저 input 을 만들어준다.
# Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
tensor_in = torch.rand(size=(5, 10, 12), dtype=torch.float32)
print(tensor_in.shape)
>>>
torch.Size([5, 10, 12]) # in_channels X H_in X W_in
shape 만 볼 것이므로, 랜덤하게 $5 \times 10 \times 12$ 크기의 텐서를 만들었다.
우리가 만든 Conv2d 는 in_channels 를 5 로 설정했음을 기억해보자.
torch.nn.Conv2d 는 특이하게도 channel 이 맨 처음에 나와야 한다.
그래서 input channel 수를 맞추기 위해 tenser_in 의 첫번째 차원 수를 5로 해준 것이다.
그리고 하나 더 눈여겨볼 것은,
우리는 Conv2d 객체를 만들 때 input 과 output 의 shape 를 통제하지 않았다는 점이다.
정확히 말하면 통제하는 것이 불가능했다.
우리는 그저 kernel_size 나 padding 등으로
"이러이러한 input 이 들어올 것이다~" 라는 가정을 통해 output shape 를 조절할 뿐이다.
그러면 이제 위에서 만든 input 을 conv 객체에 forward 해보자.
# Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
tensor_out = conv2d(tensor_in)
print(tensor_out.shape)
>>>
torch.Size([2, 8, 10]) # out_channels X H_out X W_out
$2 \times 8 \times 10$ 크기의 텐서가 출력되었다.
맨 앞의 2는 out_channels 로 지정한 값이고,
output 의 height 와 width 는 공식에 의해 결정된다.
공식에 관한 내용은 이 글의 맨 위 링크에 나와있다.
만약 batch 가 있다면 어떻게 될까?
# Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
tensor_in = torch.rand(size=(64, 5, 10, 12), dtype=torch.float32)
print(tensor_in.shape)
>>>
torch.Size([64, 5, 10, 12]) # batch_size X in_channels X H_in X W_in
이번에는 batch 크기를 64로 설정해서 input 을 만들었다고 가정하겠다
batch 크기는 첫 번째 차원에 나오는 것이 일반적이다.
# Conv2d(5, 2, kernel_size=(3, 3), stride=(1, 1))
tensor_out = conv2d(tensor_in)
print(tensor_out.shape)
>>>
torch.Size([64, 2, 8, 10]) # batch_size X out_channels X H_out X W_out
64 배치 크기의 input 을 넣고 forward 했더니 위와 같은 결과가 나왔다.
앞에서는 channel 이 가장 처음에 나와야 한다고 했지만,
4차원 텐서인 경우, 맨 앞 차원은 batch 크기로 인식하는 것을 확인할 수 있다.
참 편리하게 구현되어 있다.