-
pytorch - Conv2D 알아보기 ( CNN )AI-ML 2024. 3. 3. 12:15728x90반응형
- 목차
키워드.
- - CNN
- - Convolution
- - conv2d
들어가며.
CNN 을 구성하기 위해서 사용되는 nn.Conv2d 의 구체적인 동작 방식에 대해서 알아보려고 합니다.
함께 보면 좋은 글.
https://westlife0615.tistory.com/765
Kernel or Filter.
CNN 은 원본 이미지와 커널이라고 불리는 작은 크기의 Matrix 를 곱하여 새로운 Matrix 인 Feature Map 을 생성하게 됩니다.
아래 두개의 이미지를 첨부했는데요.
CNN 에서 원본 이미지와 커널 간의 연산을 잘 보여주는 이미지라서 첨부하게 되었습니다.
커널은 Kernel 또는 Filter 라고도 불리웁니다.
이 커널은 원본 이미지의 각 픽셀을 순회하면서 element-wise 방식으로 곱셈 연산을 수행하게 되는데요.
예를 들어, 아래와 같인 image_tensor 와 kernel_tensor 가 존재한다고 가정하겠습니다.
image_tensor = torch.tensor([ [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], ], dtype=torch.float32) # tensor([[1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]]) kernel_tensor = torch.tensor([ [1, 0, 0], [0, -1, 0], [0, 0, 1], ], dtype=torch.float32) # tensor([[ 1., 0., 0.], # [ 0., -1., 0.], # [ 0., 0., 1.]])
그리고 Conv2d 레이어를 생성합니다.
아래 Conv2d 의 의미는 커널의 크기를 3x3 인 커널로 생성한다는 의미이구요.
conv = nn.Conv2d(1, 1, 3, 1)
생성된 Conv2d 의 weight 는 아래와 같습니다.
이 weight 가 곧 커널에 해당하는 Matrix 를 의미합니다.
conv.weight # Parameter containing: # tensor([[[[ 0.2041, 0.0130, -0.3247], # [-0.0896, 0.3322, 0.2517], # [-0.2145, -0.0163, -0.0718]]]], requires_grad=True)
그리고 생성된 Conv2d 레이어의 weight 를 아래와 같이 변경한 후에 Convolution 을 적용합니다.
참고로 콘볼루션에 사용되는 이미지 텐서와 커널 텐서는 4차원 또는 3차원의 Dimension 을 가져야합니다.
conv.weight = nn.Parameter(kernel_tensor.view(1, 1, 3, 3)) conv.bias = nn.Parameter(torch.tensor([0.0])) conv(image_tensor.view(1, 1, 5, 5))
5x5 이미지와 3x3 인 커널이 콘볼루션된 후의 결과값은 아래와 같습니다.
tensor([[[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]]], grad_fn=<ConvolutionBackward0>)
이러한 결론에 도달하게 된 과정의 연산을 풀어보면 아래와 같습니다.
연산의 내용은 이미지 텐서와 커널 텐서의 각 자리의 Item 을 곱한 후 모두 더하게 됩니다.
이 과정에서 element-wise 방식으로 곱셈과 뎃셈의 연산이 적용되구요.
1x1 + 1x0 + 1x0 + 1x0 + 1x-1 + 1x0 + 1x0 + 1x0 + 1x1 = 1
이해를 돕고자 관련된 이미지를 첨부합니다.
Channel.
Channel 이라는 개념도 중요합니다.
위에서 설명하길 이미지 텐서와 커널 텐서는 4차원 또는 3차원의 Dimension 을 사용해야한다고 말씀드렸습니다.
각 차원은 batch, channel, width, height 로 구성된 Dimension 이구요.
batch 는 딥러닝 학습과정에서 DataLoader 에 의해서 산출되는 batch 를 의미합니다.
즉, 이미지 텐서의 갯수, 배치 사이즈를 의미하죠.
그리고 channel 은 RGB 와 같이 이미지의 조합을 의미하는데요.
일반적인 이미지는 RGB 와 같이 3개의 이미지의 조합으로 이루어지죠 ?
이미지를 구성하는 각 픽셀이 0 ~ 255 의 값으로 구성되는데,
하나의 픽셀은 빨강의 정도, 파랑의 정도, 그리고 녹색의 정도를 수치적으로 표현합니다.
그래서 하나의 이미지, 또는 하나의 픽셀은 3개의 값을 취합니다.
만약 흑백 이미지라고 한다면 이는 1개의 Channel 을 가지게 되는 점도 이해하시면 좋을 것 같네요.
커널 또한 Channel 을 가집니다.
커널의 Channel 은 조금 다른 의미입니다.
일반적으로 커널은 이미지에서 특정 패턴을 추출하기 위해서 사용됩니다.
예를 들어, 이미지에서 굴곡진 선을 잘 추출한다던지 가로 또는 세로의 수직선을 추출한다던지 와 같은 역할을 수행하죠.
아래의 이미지 텐서는 대각선으로 이뤄진 Tensor 이구요.
대각선의 패턴을 추출하는 커널과 콘볼루션을 계산한 결과입니다.
이미지는 아래와 같이 표현됩니다.
아래의 결과처럼 이미지와 커널가 서로 상호작용하는 관계인 경우에 결과의 Tensor 의 강도가 더 커지게 됩니다.
image_tensor = torch.tensor([ [1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1], ], dtype=torch.float32) kernel_tensor = torch.tensor([ [1, 0, 0], [0, 1, 0], [0, 0, 1], ], dtype=torch.float32)
아래의 예시는 대각선의 이미지와 수평선의 패턴을 인식하는 커널의 조합입니다.
이미지와 커널의 관계가 유사하지 않을 때에는 아래의 결과처럼 이미지의 패턴을 잘 인식하지 못하게 됩니다.
image_tensor = torch.tensor([ [1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1], ], dtype=torch.float32) kernel_tensor = torch.tensor([ [0, 0, 0], [1, 1, 1], [0, 0, 0], ], dtype=torch.float32)
그리고 아래의 예시는 원형의 이미지와 원형의 패턴을 인식하기 위한 커널의 조합입니다.
image_tensor = torch.tensor([ [0, 0.1, 0.1, 0.1, 0], [0.1, 0, 0, 0, 0.1], [0.1, 0, 0, 0, 0.1], [0.1, 0, 0, 0, 0.1], [0, 0.1, 0.1, 0.1, 0], ], dtype=torch.float32) kernel_tensor = torch.tensor([ [0, 1, 0], [1, 0, 1], [0, 1, 0], ], dtype=torch.float32)
이렇듯 CNN 의 커널은 이미지의 패턴을 인식하여 콘볼루션 결과로써 강도가 높은 이미지 텐서가 출력되는 방식입니다.
그리고 인식하고자 하는 다양한 패턴의 갯수만큼 커널의 Channel 을 늘려주어야합니다.
Conv2d 인자 알아보기.
nn.Conv2d 클래스를 통해서 Conv2d Layer 를 생성하기 위해서 여러가지 인자들이 사용됩니다.
각 인자들의 특징과 사용법에 대해서 설명합니다.
padding.
padding 인자는 Image Tensor 의 외과에 새로운 Item 들을 추가합니다.
예를 들어, 4x4 Image Tensor 에 Padding 1 을 추가하게 되면, 6x6 인 Image Tensor 가 됩니다.
아래와 같은 4x4 이미지 텐서는 padding 1 이 추가된다면, 0 으로 구성된 새로운 Item 들이 4x4 Image Tensor 를 둘러싸게 됩니다.
data = torch.tensor([ [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1] ], dtype=torch.float32) # tensor([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]]) --> Padding 1 적용 이후. tensor([[0., 0., 0., 0., 0., 0.], [0., 1., 1., 1., 1., 0.], [0., 1., 1., 1., 1., 0.], [0., 1., 1., 1., 1., 0.], [0., 1., 1., 1., 1., 0.], [0., 0., 0., 0., 0., 0.]])
이렇게 Padding 을 사용함으로써 얻을 수 있는 이점은 커널의 크기가 이미지 텐서의 크기보다 큰 경우에 Convolution 을 수행할 수 있다는 점입니다.
만약 이미지 텐서보다 커널의 사이즈가 더 큰 경우에 아래와 같은 에러가 발생합니다.
data = torch.tensor([ [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1] ], dtype=torch.float32).view(1, 1, 4, 4) conv = nn.Conv2d(1, 1, 5, 1) conv(data)
RuntimeError: Calculated padded input size per channel: (4 x 4). Kernel size: (5 x 5). Kernel size can't be greater than actual input size
이러한 경우에 Padding 은 1 이상을 주어 이미지 텐서가 커널의 사이즈보다 크거나 같도록 변경할 수 있습니다.
data = torch.tensor([ [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1] ], dtype=torch.float32).view(1, 1, 4, 4) conv = nn.Conv2d(1, 1, 5, 1, padding=1, padding_mode='zeros', bias=False) conv(data)
tensor([[[[ 0.1318, -0.3695], [ 0.1329, -0.1852]]]], grad_fn=<ConvolutionBackward0>)
nn.Conv2d 사용해보기.
nn.Conv2d 를 사용하여 간단한 CNN 예시를 작성해보겠습니다.
사용할 이미지 텐서는 torch.tensor 함수를 사용하여 생성해보았구요.
아래의 사진과 같은 형식의 Tensor 들로 구성됩니다.
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim one_image_1 = torch.tensor([ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], dtype=torch.float32) one_image_2 = torch.tensor([ [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], ], dtype=torch.float32) one_image_3 = torch.tensor([ [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], ], dtype=torch.float32) one_image_4 = torch.tensor([ [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], ], dtype=torch.float32) two_image_1 = torch.tensor([ [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], dtype=torch.float32)
그리고 CNN 모델을 생성합니다.
1개의 Conv2d 레이어와 ReLU Activation Function 그리고 Linear 레이어로 구성됩니다.
커널의 갯수는 10개로 구성하였고, 커널의 사이즈를 5x5 입니다.
그리고 Linear 레이어는 10 * 6 * 6 인 in_features 를 가지는데요.
그 이유는 10개의 Channel 과 6x6 인 Feature Map 의 사이즈의 곱해야 하기 때문입니다.
6x6 Feature Map 이 생성되는 이유는 5x5 커널과 10x10 이미지가 콘볼루션된 결과 6x6 인 Feature Map 이 생성됩니다.
class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(1, 10, 5, 1) self.linear = nn.Linear(10 * 6 * 6, 10) def forward(self, x): image_tensor = x.view(1, 1, 10, 10) output = F.relu(self.conv1(image_tensor)) return self.linear(output.view(1, -1)) model = CNN() optimizer = optim.Adam(model.parameters(), lr=0.01) loss_function = nn.CrossEntropyLoss() for epoch in range(100): for image in [one_image_1, one_image_2, one_image_3, one_image_4]: model.train() label_tensor = torch.tensor([0, 1, 0, 0, 0, 0, 0, 0, 0, 0], dtype=torch.float32).view(1, 10) output = model(image) optimizer.zero_grad() loss = loss_function(output, label_tensor) loss.backward() optimizer.step() print(loss.item()) for image in [two_image_1]: model.train() label_tensor = torch.tensor([0, 0, 1, 0, 0, 0, 0, 0, 0, 0], dtype=torch.float32).view(1, 10) output = model(image) optimizer.zero_grad() loss = loss_function(output, label_tensor) loss.backward() optimizer.step() print(loss.item()) print(f"one_image_1 : {torch.argmax(model(one_image_1))}") print(f"one_image_2 : {torch.argmax(model(one_image_2))}") print(f"one_image_3 : {torch.argmax(model(one_image_3))}") print(f"one_image_4 : {torch.argmax(model(one_image_4))}") print(f"two_image_1 : {torch.argmax(model(two_image_1))}")
생성된 CNN 모델은 학습 데이터에 대해서 아래와 같은 추론 결과를 보여줍니다.
one_image_1 : 1 one_image_2 : 1 one_image_3 : 1 one_image_4 : 1 two_image_1 : 2
반응형'AI-ML' 카테고리의 다른 글
Cross Entropy 알아보기 (0) 2024.03.08 [pytorch] nn.Embedding 알아보기 (0) 2024.03.08 [torchvision] ToTensor 알아보기 (0) 2024.03.01 [pytorch] NLLLoss 알아보기 (Negative Log Likelihood) (0) 2024.02.22 Sigmoid Activation Saturation 알아보기 (0) 2024.01.10