처음부터 하는 딥러닝

[딥러닝 기초] 일단 만들어보기

빈이름 2023. 4. 24. 12:55

안녕하세요. 딥러닝 기초 강의 포스팅을 시작해 보겠습니다.

제가 생각했을 때 뭐든 처음 시작할 때는 일단 흥미를 붙이는 것이 가장 중요하다고 생각됩니다. 그렇기 때문에 우선 직접 코드를 작성해보고, 학습을 수행해 보고, 결과를 눈으로 확인하는 것으로 시작하고자 합니다.

 

Colab

실습은 우선 colab으로 시작하고자 합니다. colab은 인터넷 브라우저에서 python 스크립트를 작성하고 실행할 수 있도록 해주는 사이트입니다. 딥러닝을 하기 위해선 gpu도 필요하고, gpu 사용을 위한 환경설정도 필요하고, 가상 환경 설정도 해야 하고.. 미리 준비해야 하는 것이 많고 복잡합니다. Colab은 이런 설정들이 미리 되어 있는 컴퓨터의 서버를 빌리는 겁니다. 이런 복잡한 준비 절차들을 생략할 수 있고, 누구나 인터넷만 되면 실행할 수 있기 때문에 많이 사용합니다.

https://colab.research.google.com/

 

Colab은 주피터 노트북 형태로 제공됩니다. 주피터 노트북은 하나의 코드지만 셀로 분리되어 있어 중간중간 결과를 확인하기에 매우 용이합니다. 우선 위 링크로 들어간 뒤 '새 노트'로 새로운 코드를 작성해 보겠습니다.

 

새 노트를 켠 뒤, 우선 gpu 설정을 하도록 하겠습니다. 아래 그림과 같이 '런타임-런타임 유형 변경'을 클릭해 줍니다.

그 뒤에 하드웨어 가속기를 gpu로 바꿔준 뒤 저장을 합니다.

이 설정은 colab에서 gpu를 사용해서 코드를 돌리기 위함입니다. gpu는 딥러닝 학습속도에 큰 영향을 미칩니다. Colab에서 딥러닝 학습을 진행하고자 한다면 무조건, 저 하드웨어 가속기 설정부터 설정합시다.

목표

처음으로 만들 인공지능은 '숫자 손글씨 인식하기' 입니다. 0부터 9까지 숫자를 다양한 사람들이 직접 손으로 쓴 이미지가 주어질건데, 저희는 인공지능을 이용해서 각 이미지에 써져 있는 숫자가 어떤 숫자인지를 인식하는 작업을 할겁니다.

코드와 함께 간단하게 설명을 같이 작성할 건데, 처음이니 이해가 안되더라도 '그렇구나~' 하고 넘어가 주시면 되겠습니다. 우선 흥미를 붙이는데 중점을 둡시다.

1. 데이터셋 다운 받기

인공지능 학습을 위해선 대량의 데이터가 필요합니다. 직접 손으로 몇만장씩 숫자를 그려도 좋지만 저희는 다른 사람이 이미 만들어 둔 데이터셋을 다운 받아 사용할 겁니다. MNIST 데이터셋은 다양한 사람들이 손으로 쓴 숫자 이미지 6만장을 갖고 있습니다. 사실 이 데이터셋은 딥러닝 처음 시작할 때 연습용으로 많이 사용하는 굉장히 유명한 데이터셋입니다. 유명한 만큼 다운로드도 파이썬 코드로 쉽게 다운 받을 수 있습니다.

import torchvision.datasets as datasets


mnist_train = datasets.MNIST(root='MNIST_data/',
                          train=True,
                          download=True)

mnist_test = datasets.MNIST(root='MNIST_data/',
                         train=False,
                         download=True)

Colab의 첫번째 셀에 위 코드를 복사 붙여넣기 한 뒤에 실행하면 데이터셋을 다운로드 받기 시작할 것입니다. 데이터셋은 train과 test셋으로 나뉩니다. Train셋은 인공지능 학습에 사용되는 데이터셋을 말하고, Test셋은 학습에 사용하지 않고 인공지능 모델의 성능을 테스트하는데 사용되는 데이터셋을 말합니다.

Train과 Test셋을 구분하는 이유는 인공지능 모델의 실성능을 측정하기 위함입니다. 말하자면 Train셋은 모의고사고, Test셋은 실제 수능과 같은 역할을 한다고 볼 수 있습니다. 만약에 수능에 모의고사에서 나왔던 문제가 똑같이 나온다면 문제를 읽지 않고도 모의고사에 썼던 답과 똑같이 쓰면 정답을 맞출 수 있을 것입니다. 인공지능 모델도 마찬가지입니다. 따라서 학습에 사용하지 않은 이미지로 실제 성능을 확인하는 것이 더 객관적인 성능을 평가할 수 있습니다.

 

글을 읽는동안 다운로드가 완료되었나요? 그러면 다운로드된 데이터셋이 어떻게 생겨먹었는지 확인해 보겠습니다. MNIST는 이미지 데이터셋입니다. matplotlib이라는 라이브러리를 사용해 이미지를 시각화 해보도록 하겠습니다.

# matplotlib은 파이썬에서 이미지를 시각화하거나 그래프를 그리는데 쓰이는 라이브러리입니다!
import matplotlib.pyplot as plt

# idx의 값을 바꿔가면서 다른 이미지들도 확인해 보세요.
idx = 0

plt.imshow(mnist_train.data[idx].unsqueeze(-1))
plt.title(f"label : {mnist_train.targets[idx]}")

위 코드를 실행하면 MNIST 데이터셋의 0번째 이미지를 확인할 수 있습니다. mnist_train은 이미지를 담고 있는 data와 해당 이미지의 라벨을 담고 있는 targets를 갖고 있습니다. 이들은 리스트와 같이 접근이 가능합니다.

데이터셋을 다운 받았으니, 이제 본격적으로 인공지능을 만들어 봅시다. 이번 실습에서는 PyTorch를 이용할 것입니다. 딥러닝을 위한 대표적인 라이브러리로 PyTorch와 Tensorflow 2가지가 있습니다. 둘 다 좋은 라이브러리고 많이 사용하지만 당분간은 제가 편한 PyTorch로 실습을 진행할 거고, Tensorflow에 대해서도 한 번 포스팅으로 다루도록 하겠습니다.

2. 모델 만들기

지금부터 인공지능 모델을 만들건데 정말 간단하게 만들겁니다. 모델 코드는 아래와 같습니다.

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(28*28, 256),
    nn.Linear(256, 10)
)

끝입니다. 7줄만에 인공지능 모델이 만들어졌어요. 정말 쉽죠?

간단하게 설명하자면, 2개의 Linear 레이어로 이루어진 모델을 만든 겁니다. 첫번째 레이어는 28*28 크기의 1차원 행렬을 입력 받아 256 차원의 1차원 행렬을 반환합니다. 두번째 레이어는 이 256 차원의 행렬을 입력 받아 다시 10차원 행렬을 반환합니다.

즉, 이미지를 28*28 크기의 이미지를 입력 받아, 10차원의 행렬을 반환하는 것입니다. 이 10차원의 결과 행렬의 값들은 각각 0부터 9까지의 숫자 중 어느 숫자일 확률이 제일 높을지를 나타낼 겁니다.

모델 구성

3. 데이터 전처리

다음으로는 인공지능이 학습할 수 있도록 데이터셋을 가공해주는 작업을 하겠습니다.

from torch.utils.data import Dataset, DataLoader


class mnist_dataset(Dataset):
    def __init__(self, data):
        super(mnist_dataset, self).__init__()
        self.images = data.data
        self.labels = data.targets

    def __len__(self):
        return len(self.images)

    def __getitem__(self, idx):
        return {
            "images": self.images[idx].view(-1)/255, 
            "labels": self.labels[idx]
        }

train_dataset = mnist_dataset(mnist_train)
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)

example = next(iter(train_loader))
print(example["images"].shape)
print(example["labels"].shape)

PyTorch는 torch.utils.data의 Dataset과 DataLoader 모듈을 이용해 데이터셋을 가공할 수 있습니다. Dataset은 클래스로 상속하여 사용 가능합니다.

전 mnist_dataset이라는 클래스를 만들고 Dataset을 상속 받는 식으로 구현했습니다. Dataset을 상속 받아 만들 땐 '__len__'과 '__getitem__' 함수를 내 데이터셋에 맞춰 새롭게 정의 해줘야 합니다.

'__len__'는 데이터셋의 길이를 반환해야 합니다. MNIST의 경우 train셋은 6만개, test셋은 1만개 이므로 각각 6만과 1만을 반환하도록 코드를 작성하면 됩니다.

'__getitem__'은 idx번째 데이터셋에 대한 요청이 들어왔을 때, 해당 데이터를 반환할 수 있도록 코드를 작성해 주면 됩니다. 앞서 mnist_train을 살펴볼 때 각각 이미지와 라벨을 리스트와 같이 접근할 수 있다는 것을 확인했으므로 위 코드와 같이 작성해 주면 됩니다.


return {
    "images": self.images[idx].view(-1)/255,
    ...
}

여기서 이미지는 ".view(-1)/255" 라는 코드가 더 추가되어 있습니다. ".view(-1)"이 의미하는 것은 이미지를 일자로 쭉 펴라는 의미입니다. 원래 이미지는 (28x28) 2차원 행렬이지만, 제가 만든 모델은 1차원 행렬만 입력 받을 수 있습니다... 따라서 이미지를 1차원 행렬 형태로 바꿔줄 필요가 있습니다. 그래서 추가된 코드이고, 뒤에 "/255"는 정규화를 위해서 각 픽셀 값을 255로 나눠준 것입니다. 이미지의 픽셀 값들은 모두 0에서 255 사이의 값으로 되어 있는데 이를 0에서 1사이 값으로 정규화를 해준 것입니다.

train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)

마지막으로 DataLoader를 통해 데이터셋을 한번에 batch_size만큼 가져올 수 있도록 처리해 주면 됩니다. 학습은 보통 컴퓨터 자원의 한계로 데이터 전체를 한번에 학습하는 것이 아니라 batch_size개씩 나눠서 학습을 하게 됩니다.

shuffle은 말 그대로 데이터 순서를 섞는다는 의미입니다. 처음 구구단을 처음 외울 때 2단부터 시작해서 9단까지 순서대로 외우면 쉬운데 순서를 섞어서 6단부터 시작하면 헷갈리고 그러지 않았나요? 인공지능 모델도 마찬가지로 학습 순서를 섞어서 학습하는 것이 좀 더 좋은 성능을 보이는 경우가 많습니다.

4. Loss와 optimizer 설정, 모델 GPU에 할당하기

다음으로 학습에 필요한 기본 설정을 합니다. 학습을 위해선 criterion(손실 함수)와 optimizer가 필요합니다.

from torch.optim import Adam

criterion = nn.CrossEntropyLoss()
optimizer = Adam(model.parameters(), lr=1e-4)

 

마지막으로 모델을 gpu에 할당하여 학습이 gpu를 이용해 이뤄지도록 하겠습니다.

device = 'cuda' if torch.cuda.is_available() else 'cpu'

model.to(device)

5. 학습하기

이제 준비는 끝났습니다! 이제 학습만 진행하면 됩니다. 

from tqdm import tqdm

train_losses = []

for epoch in range(3):
    print("epoch :", epoch)
    model.train()
    train_loss = []
    for step, data in enumerate(tqdm(train_loader)):
        data = {k: v.to(device) for k, v in data.items()}

        images = data["images"].float()
        output = model(images)
        loss = criterion(output, data["labels"])
        train_loss.append(loss.detach().cpu().item())

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if step%1000==0 and step > 0:
            step_loss = sum(train_loss)/len(train_loss)
            print(f"{step} loss :", step_loss)
            train_losses.append(step_loss)
            train_loss = []

6만 개의 데이터를 총 3번 학습하겠습니다. train_loader로부터 이미지와 라벨을 16개씩 받습니다. 모델이 gpu에 할당되어 있기 때문에 데이터도 똑같이 gpu에 할당해야 합니다.

data = {k: v.to(device) for k, v in data.items()}

그리고 모델에 이미지를 입력합니다.

images = data["images"].float()
output = model(images)

다음으로 모델이 출력한 결과와 실제 라벨 결과를 비교하여 모델이 얼마나 틀렸는지를 측정합니다.

loss = criterion(output, data["labels"])

측정된 결과를 토대로 모델이 스스로 어떻게 답을 예측해야 하는지 올바른 방향으로 파라미터를 업데이트합니다.

optimizer.zero_grad()
loss.backward()
optimizer.step()

 

그 아래는 1,000step 마다 loss가 얼마나 줄어들었는지 확인하기 위한 코드입니다. 학습과는 전혀 관계가 없습니다.

if step%1000==0 and step > 0:
    step_loss = sum(train_loss)/len(train_loss)
    print(f"{step} loss :", step_loss)
    train_losses.append(step_loss)
    train_loss = []

6. 결과 확인하기

학습이 완료되었다면, 학습이 잘 진행되었는지를 확인하기 위해 loss를 그래프로 출력해 보겠습니다. 학습이 진행될수록 loss가 줄어들었다면 잘 되었다고 볼 수 있을 것입니다.

plt.plot(train_losses)

 

학습 그래프를 보면 loss가 점차 줄어드는 것을 확인할 수 있습니다! 그러면 실제로 잘 예측하는지 결과도 확인해 봅시다!

처음에 말했듯이 결과 확인은 모델이 학습하지 않은 test셋으로 진행하겠습니다.

batch_size=16

test_dataset = mnist_dataset(mnist_test)
test_loader = DataLoader(test_dataset, batch_size=batch_size)

test_iter = iter(test_loader)

train셋과 똑같이 Dataset과 DataLoader를 만들어 줍니다. 그리고 아래 코드를 통해 모델이 예측한 결과를 확인할 수 있습니다. 아래 셀은 실행할 때마다 다른 데이터셋에 대한 결과를 보여줍니다.

data = next(test_iter)
data = {k: v.to(device) for k, v in data.items()}

images = data['images'].view(batch_size, -1).float()
with torch.no_grad():
    output = model(images)
pred = torch.argmax(output, dim=-1)

fig, ax = plt.subplots(4, 4, figsize=(10, 10))
for i in range(16):
    ax[i//4][i%4].imshow(data['images'][i].view(28, 28).cpu().tolist())
    ax[i//4][i%4].set_title(pred[i].cpu().item())

테스트셋에 대한 예측 결과

어때요 결과가 잘 나오나요? 신기하지 않습니까? 그러면 과연 테스트셋 1만개의 이미지에 대해선 몇%의 정확도를 보일까요?

from sklearn.metrics import accuracy_score

outputs = []
for data in tqdm(test_loader):
    data = {k: v.to(device) for k, v in data.items()}

    images = data["images"].view(batch_size, -1).float()
    with torch.no_grad():
        output = model(images)
    outputs.append(output.cpu())

outputs = torch.cat(outputs, dim=0)
preds = torch.argmax(outputs, dim=-1)

acc = accuracy_score(preds, mnist_test.targets)
print("accuracy on test set :", acc)

저는 0.9251이 나왔습니다. 이게 랜덤 요소가 있기 때문에 여러분과는 좀 다른 결과가 나올 수도 있습니다. 여러분들은 몇%나 나왔나요?

마무리

이렇게 코드를 직접 작성해 보고, 손글씨 숫자를 인식하는 인공지능을 만들어 봤습니다. 코드만 쓰기 허전해서 약간 설명도 덧붙였는데 어려웠나요..? 어려워도 머리 싸매지말고 일단 넘어갑시다. 중요한건 직접 만들어 봤다는 거니까요.

이를 통해서 흥미가 생겼길 바라면서 이제 딥러닝이 어떤 식으로 이뤄지는 것인지에 대한 포스팅으로 돌아오겠습니다. 아마 다음 포스트는 설명 위주가 될 것 같습니다.

 

마지막으로 실습 코드 공유 드리면서 마치도록 하겠습니다. 감사합니다.

https://colab.research.google.com/drive/1KdjBmrpzOF2ja6dSzf7bdPbVge3ImpMy?usp=sharing

 

일단 만들어보자!.ipynb

Colaboratory notebook

colab.research.google.com