앞의 전처리 과정은 MNIST 데이터로 실습한 DNN 학습과정과 같음.
- 합성곱 네트워크 생성
class FashionCNN(nn.Module):
def __init__(self):
super(FashionCNN, self).__init__()
self.layer1 = nn.Sequential( #1
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1), #2
nn.BatchNorm2d(32), #3
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2) #4
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc1 = nn.Linear(in_features=64*6*6, out_features=600) #5 #여기선 인프런과 다르게 fully connected 이후 linear 계산까지 한 모듈에.
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(in_features=600, out_features=120)
self.fc3 = nn.Linear(in_features=120, out_features=10) #마지막 계층의 out_features는 클래스 개수를 의미
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.view(out.size(0), -1) #6 conv layer 끝날 때 flatten 해주고, 밑엔 flatten 한 걸 기반으로 linear하게 계산해나감.
out = self.fc1(out)
out = self.drop(out)
out = self.fc2(out)
out = self.fc3(out)
return out
#이렇게 모델을 설정해놓은거. 여기 1,32,64...는 데이터가 아니라 원하는대로 설정해놓은 채널 수니까. 데이터는 집어넣으면 됨.
1. nn.Sequential 은 순서를 갖는 모듈의 컨테이너.
nn.Sequential 사용하면 init()에서 사용할 네트워크 모델들을 정의해 줄 뿐만 아니라, forward() 함수에서 구현될 순전파를 계층 형태로 좀 더 가독성 있는 코드로 작성할 수 있음. (위에서 정의해놨으니까?)
즉, nn.Sequential 은 계층을 차례로 쌓을 수 있도록 Wx+b와 같은 수식과 활성화 함수(activation function)를 연결해 주는 역할을 함. 특히 데이터가 각 계층을 순차적으로 지나갈 때 사용하면 좋음.(순차적으로 연산되는 레이어만 있을 경우에는, nn.Sequential 을 통해 순서대로 각 레이어를 작성하면 그대로 실행됨, act.f도 순서에 맞게 자동 계산됨)
=> nn.Sequential은 여러 개의 계층을 하나의 컨테이너에 구현하는 방법.
2. conv layer. 합성곱층은 합성곱 연산을 통해 이미지의 특징을 추출함. 합성곱이란 커널(필터) 이라는 n x m 크기의 행렬이 높이 x 너비 크기의 이미지를 처음부터 끝까지 훑으면서 각 원소끼리 곱한 후 모두 더한 값을 출력함. (일반적으로 3 x 3이나 5 x 5 를 사용)
nn.Conv2d(in_channels=1, out_channels=2, kernel_size=3, padding =1)
- in_channels : 입력 채널의 수. 흑백 이미지는 1, RGB 값을 가진 이미지는 3을 가진 경우가 많음
*채널이란?
2D 합성곱층(필터가 방향 두 개로 움직이는 형태)에 이미지를 적용한다 해볼 때,
흑백 이미지의 경우 이미지 데이터는 w X h 형태의 행렬로 표현됨.(w는 너비, h는 높이)
컬러 이미지의 경우, Red Green Blue 라는 세 개의 채널을 가지고 있음,
-> w x h x c 형태의 행렬로 표현 가능. (c는 채널 수, 3차원으로 생각하면 채널은 결국 깊이를 의미함)
- out_channels : 출력 채널의 수를 의미
- kernel_size : 커널 크기(필터) 커널은 이미지 특징을 찾아내기 위한 공용 파라미터이며, CNN에서 학습 대상은 필터 파라미터가 됨. 커널은 입력 데이터를 스트라이드 간격으로 순회하며 합성곱을 계산함. kernel_size : 3이면 (3,3) 의미, 직사각형 하고 싶으면 (3,5)로 지정
- padding : 패딩 크기. 출력 크기를 조정하기 위해 입력 데이터 주위에 0을 채움. 패딩 값이 클수록 출력 크기도 커짐.
3. BatchNorm2d : 학습 과정에서 각 배치 단위별로 데이터가 다양한 분포를 가지더라도 평균과 분산을 이용하여 정규화 하는 것을 의미함.
배치 단위나 계층에 따라 입력 값의 분포가 모두 다르지만 정규화를 통해 분포를 가우시안 형태로 만듦. 그러면 평균은 0, 표준편차는 1로 데이터의 분포가 조정됨.
4. MaxPool2d : 이미지 크기 축소시키는 용도로 사용. 풀링 계층은 합성곱층의 출력 데이터를 입력으로 받아서 출력 데이터의 크기를 줄이거나 특정 데이터를 강조하는 용도로 사용됨.
-> max pooling, average pooling, min pooling
nn.MaxPool2d(kernel_sizer=2, stride =2)
- kernel_size : m x n 행렬로 구성된 가중치
- stride : 입력 데이터에 커널(필터)을 적용할 때 이동할 간격을 의미함. 스트라이드 값이 커지면 출력 크기는 작아짐 (Conv2d에서 stride 설정하면 필터의 간격 설정하는거고, 이건 풀링 계산하는 간격 결정하는 스트라이드 인듯)
5. 클래스를 분류하기 위해서는 이미지 형태의 데이터를 배열 형태로 변환하여 작업해야 함. 이 때, Conv2d에서 사용하는 하이퍼 파라미터 값들에 따라 출력 크기가 달라짐. 패딩과 스트라이드 값에 따라 출력 크기가 달라짐. 줄어든 출력 크기는 최종적으로 분류를 담당하는 fully connected layer로 전달됨
self.fc1 = nn.Linear(in_features=64*6*6, out_features=600)
- in_features : 입력 데이터의 크기를 의미함. 중요한 건, 이전까지 수행했던 Conv2d, MaxPool2d는 이미지 데이터를 입력으로 받아 처리했음.
but 출력 결과를 완전연결층으로 보내기 위해서는 1차원으로 변경해주어야 하는데,공식은 다음과 같음
** Conv2d 계층에서 출력 크기 구하는 공식 : 출력 크기 = (입력 데이터 크기 - 커널크기 + 2*패딩크기) / stride 크기 + 1
** MaxPool2d 계층에서 출력 크기 구하는 공식 : 출력 크기 = 입력 필터의 크기(or 바로 앞의 Conv2d의 출력 크기이기도 함) / 커널 크기(kernel_size)
- out_features : 출력 데이터의 크기
**flatten 된거 받아서, 점차 원하는 분류 갯수까지 선형회귀로 계산해나가는것.
6. 합성곱층에서 완전연결층으로 변경되기 때문에 데이터의 형태를 1차원으로 바꾸어 줌. 이 때 out.size(0)은 결국 100을 의미함(왜?)
→ 뒤에서 images.view(100, 1, 28, 28),
즉, 받아온 이미지를 Input Tensor: (𝑁,𝐶𝑖𝑛,𝐻𝑖𝑛,𝑊𝑖𝑛) 이렇게 만드는 것. 그래서 batch 사이즈가 100. 입력 채널수는 1, 데이터는 28 x 28 으로 만들었기 때문.
- 합성곱 네트워크를 위한 파라미터 정의
learning_rate = 0.001;
model = FashionCNN();
model.to(device)
criterion = nn.CrossEntropyLoss(); #마지막에 log softmax 안썼기 때문에 crossentropy가능(log softmax 는 NLLLoss() 로 진행해야 함)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate);
print(model)
FashionCNN(
(layer1): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer2): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc1): Linear(in_features=2304, out_features=600, bias=True)
(drop): Dropout2d(p=0.25, inplace=False)
(fc2): Linear(in_features=600, out_features=120, bias=True)
(fc3): Linear(in_features=120, out_features=10, bias=True)
이 모델은 마지막에 softmax 활성화함수를 쓰지 않았음.
- 모델 학습 및 성능 평가
num_epochs = 5
count = 0
loss_list = []
iteration_list = []
accuracy_list = []
predictions_list = []
labels_list = []
for epoch in range(num_epochs):
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
train = Variable(images.view(100, 1, 28, 28)) #Autograd는 Variable 을 사용해서 역전파를 위한 미분 값을 자동으로 계산해줌.
#받아온 이미지를 Input Tensor: (𝑁,𝐶𝑖𝑛,𝐻𝑖𝑛,𝑊𝑖𝑛) 이렇게 만드는 것. 그래서 batch 사이즈가 100. 입력 채널수는 1, 데이터는 28 x 28
labels = Variable(labels)
outputs = model(train)
loss = criterion(outputs, labels)
optimizer.zero_grad() #기울기를 0으로 설정
loss.backward() #텐서.backward() 를 호출하면, 연산에 연결된 각 텐서들의 미분 값을 계산하여, 각텐서객체.grad 에 저
optimizer.step()
count += 1
if not (count % 50): #count를 50으로 나누었을 때 나머지가 0이 아니라면 실행
total = 0
correct = 0
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
labels_list.append(labels)
test = Variable(images.view(100, 1, 28, 28))
outputs = model(test)
predictions = torch.max(outputs, 1)[1].to(device)
predictions_list.append(predictions)
correct += (predictions == labels).sum()
total += len(labels)
accuracy = correct * 100 / total
loss_list.append(loss.data)
iteration_list.append(count)
accuracy_list.append(accuracy)
if not (count % 500): #count를 500으로 나누었을 때 나머지가 0 이아니라면 그 이후 iteration 출력
print("Iteration: {}, Loss: {}, Accuracy: {}%".format(count, loss.data, accuracy))
Iteration: 500, Loss: 0.5520998239517212, Accuracy: 88.05000305175781%
Iteration: 1000, Loss: 0.3182446360588074, Accuracy: 88.26000213623047%
Iteration: 1500, Loss: 0.29915472865104675, Accuracy: 87.62999725341797%
Iteration: 2000, Loss: 0.1817273199558258, Accuracy: 89.5199966430664%
Iteration: 2500, Loss: 0.13265928626060486, Accuracy: 89.61000061035156%
Iteration: 3000, Loss: 0.13556359708309174, Accuracy: 90.91000366210938%
- 테스트 데이터로 검증해보기
#테스트 데이터로 검증
import random
import matplotlib.pyplot as plt
# 테스트 데이터를 사용하여 모델을 테스트한다.
with torch.no_grad(): # torch.no_grad()를 하면 gradient 계산을 수행하지 않는다.
# MNIST 테스트 데이터에서 무작위로 하나를 뽑아서 예측을 해본다
r = random.randint(0, len(test_loader) - 1)
X_single_data = test[r:r + 1].view(1, 1, 28, 28).float().to(device)
Y_single_data = labels[r:r + 1].to(device)
print('Label: ', Y_single_data.item())
single_prediction = model(X_single_data) #model() 사용*
print('Prediction: ', torch.argmax(single_prediction, 1).item())
plt.imshow(test[r:r + 1].view(28, 28), cmap='Greys', interpolation='nearest')
plt.show()
Label: 7
Prediction: 7

'Deep Learning & AI > CV' 카테고리의 다른 글
[VGG] 졸음운전 분류 프로젝트 (4) | 2024.03.11 |
---|---|
[CNN] 졸음운전 분류 프로젝트 (0) | 2024.03.04 |
[CNN] 전이 학습 - 특성 추출 기법 (1) | 2024.01.01 |
[CNN] Convolutional Neural Network (1) | 2023.12.29 |
[CNN] Image Convolution (1) | 2023.12.29 |