Pytorch

[Pytorch] torch.nn.Conv2d

kil_alpaca 2023. 4. 13. 23:33

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 크기로 인식하는 것을 확인할 수 있다.

참 편리하게 구현되어 있다.