2021년 6월 9일 수요일

심심할때 따라해보는 언리얼엔진 게임 개발하기

이 글은 심심할때 따라해보는 언리얼엔진 게임 개발 방법에 대한 이야기이다.

레퍼런스

2021년 6월 8일 화요일

리눅스에서 VSCode, CMake와 GDB 개발환경 만들기

이 글은 리눅스에서 VSCode, CMake와 GDB 개발환경 만들기 방법을 간단히 정리한다.
리눅스에서 개발환경은 윈도우보다 좋지는 않다. 옛날에는 Eclipse 정도가 최신이었지만, 지금은 VSCode, PyCharm 등이 있어 비쥬얼한 개발환경 지원이 좋아졌다. 이 글은 우분투에서 VSCode에 대한 설치 및 사용 방법만 언급한다.

다음을 참고해 우분투 환경에서 VS Code, CMake 및 GDB를 설치한다.

2021년 5월 27일 목요일

딥러닝 모델의 Convolution 필터 동작 구조

이 글은 딥러닝 모델의 컨볼루션 필터의 동작 구조를 설명한 것이다.

머리말
CNN(convolutional neural network)은 특정 방향의 모서리(edge)를 강조하는 edge detection, 상세를 강조해 blurring을 제거해주는 sharpness filter 등의 다양한 필터를 사용해 특징을 추출한다. 이를 컨볼루션 필터라 한다.

많은 컨볼루션 아키텍처는 채널 RGB의 입력 이미지를 일련의 내부 필터에 매핑하는 컨볼 루션으로 시작한다. 가장 많이 사용되는 딥러닝 프레임 워크에서 다음 코드는 많이 보여진다.

out_1 = Conv2d (input = image, filter = 32, kernel_size = (3,3), strides = (1,1))
relu_out = relu (out_1)
pool_out = MaxPool (relu_out, kernel_size = (2,2), strides = 2)

위의 결과는 32란 값은 일련의 필터들 갯수라는 것을 알고 있다. 다음 그림은 이 계산과정을 나타낸다.


1 단계 각 필터(filter-1, filter-2,… 등)가 3개 컨볼루션 커널(Wt-R, Wt-G 및 Wt-B)에 적용된다. 순방향 전파 중에 이미지의 R, G 및 B 픽셀 값이 각각 Wt-R, Wt-G 및 Wt-B 필터 커널에 곱해져 활성화 맵을 생성한다. 그런 다음 세 커널의 출력을 추가하여 필터 당 하나의 활성화 맵을 생성한다. 이러한 활성화는 ReLu 함수에 적용되고, 최대 풀링 계층을 통해 실행된다. 

컨볼루션 필터는 다음과 같은 종류가 있다. 
  • 1 차원 데이터에 대한 1D 컨볼 루션 (시간)
  • 2 차원 데이터에 대한 2D 컨볼 루션 (높이 및 너비)
  • 3 차원 데이터에 대한 3D 컨볼 루션 (높이, 너비 및 깊이)
Conv1D 
아이스크림 판매 예측의 경우를 보겠다. 입력은 시간적 차원에 따라 매일 간격으로 정의 될 수 있다. 제품 가격, 마케팅 지출, 외부 온도 및 주말인지 여부에 대한 정규화된 값을 가질 수 있다. 아래 경우는 총 4 개의 채널을 가진다. 
6 개의 시간에 걸쳐 4 개 채널이 있는 입력 배열

입력 데이터가 2차원처럼 보이지만, 차원 중 하나만 공간적이다. 4개 변수 (A, B, C, D)는 무작위 순서로 시작하면 A & B, B & C 및 C & D간에 유사한 공간 관계를 찾을 수 없다.
이러한 이유로 다중 채널 시간 데이터로 작업할 때는 1D Convolution을 사용하는 것이 가장 좋다. 
1x4 출력위해 4x6 입력 행렬에 적용 커널크기 3의 1D컨볼루션

컨볼루션 레이어에 입력 데이터를 필터가 순회하며 합성곱을 통해서 만든 출력을 피처맵(Feature Map) 또는 액티베이션 맵(Activation Map)이라고 한다. 액티베이션 맵은 피처맵 행렬에 활성 함수를 적용한 결과이다. 

앞의 과정은 다음 코드로 표현할 수 있다.

# input_data.shape는 (4, 6) 
# kernel.shape는 (4, 3)
conv = Conv1D (channels = 1, kernel_size = 3)
output_data = apply_conv (input_data, kernel, conv) 
print (output_data)
# [[[24. 25. 22. 15.]]] 

Conv2D 
컬러 이미지는 다중 채널 공간 데이터의 좋은 예이다. 다음 그림에서 각 위치 색상을 나타내는 3개의 채널이 있다. 
3 개 채널 표현

3x3 커널을 정의하더라도 3개 입력채널이 있어, 초기화될 때 커널의 실제 크기는 3x3x3이 될 것이다. 
3x3 출력 위해 크기 5x5인 3채널 RGB 입력에 3x3 커널을 적용한 2D 컨볼루션

# input_data.shape는 (3, 5, 5) 
# kernel.shape는 (3, 3, 3).
conv = Conv2D (channels = 1, kernel_size = (3,3))
output_data = apply_conv (input_data, 커널, conv) 
print (output_data)
# [[[[19. 13. 15.] 
# [28. 16. 20.] 
# [23. 18. 25.]]]] 
# <NDArray 1x1x3x3 @cpu (0)>

다중출력채널 컨볼루션
하나의 컨볼루션 필터만 사용하면, 너무 한정된 특징만 추출될 수 있다. 그러므로, 출력채널이 다중인 컨볼루션을 다음과 같이 사용할 수 있다.
컨볼루션 계층 4개 커널 출력 최대화

다중출력채널은 학습 시 주어진 클래스에 대한 이미지의 서로 다른 엣지, 색깔, 블롭(blob) 등에 활성화되는 필터들을 의미한다. 이를 입력 데이터와 계산하면, 결국 깊이값으로 표현되기 때문에, 깊이컬럼(depth column)이라 부르기도 한다.

여러 출력채널을 추가하면, 다음과 같이 계산된다. 
4x4 출력위해 크기 3커널 4개 1x6 입력 행렬에 적용한 1D컨볼루션

코드로 나타내면 다음과 같다.
# input_data.shape는 (1, 6) 
# kernel.shape는 (4, 1, 3)
conv = Conv2D (channels = 4, kernel_size = (3,3))
output_data = apply_conv (input_data, kernel, conv) 
print (output_data)
# [[[5. 6. 7. 2.] 
# [6. 6. 0. 2.] 
# [9. 12. 10. 3.] 
# [10. 6. 5. 5.]]] 

2021년 5월 24일 월요일

패션 이미지 데이터 활용 PyTorch 기반 모델 훈련 및 예측 방법

이 글은 패션 이미지 데이터를 활용한 PyTorch 기반 모델 훈련 및 예측 방법을 간단히 나눔한다. 
파이토치(Pytorch)는 파이썬기반 오픈 소스 머신러닝 라이브러리로 페이스북 인공지능 연구소에 의해 개발되었다. 파이토치는 NVIDIA의 GPU로 병렬처리가 가능해, 높은 실행 성능을 가진다.  토치 사용방법은 Keras와 유사하나, 좀 더 간단히 신경망을 정의하고, 지원되는 유틸리티 사용이 편리하다. 이 글은 패션 이미지를 MNIST처럼 공유한 데이터를 사용해 학습 및 예측을 진행하고, 이를 설명한다. 

PyTorch 설치 방법
여러가지 설치 방법이 있으나, 여기서는 anaconda를 사용하기로 한다. pytorch 설치 전에 아래를 참고해, NVIDIA DRIVER, CUDA, 텐서플로우 등을 설치해야 한다. 참고로, 운영체제 환경에 따라 설치 방법은 약간 다를 수 있다. 
아래 명령으로 토치를 설치한다.
conda install -c pytorch pytorch

참고로, CUDA 버전에 따라 설치되는 패키지 버전 의존성이 달라져 에러가 발생할 수도 있다. 이 경우는 정답이 없어, 구글링하면서 솔류션을 찾아야 한다.

FashionMNIST 학습 및 예측
FashionMNIST는 아래와 같은 패션 데이터이다. 
이 패션 데이터셋은 Zalando의 패션 작품 이미지로 훈련용 60,000개, 테스트용 10,000개 이미지를 제공한다. 각 이미지는 28x28 회색 이미지이며, 각 이미지별 라벨(label)이 저장되어 있다. 
라벨은 다음과 같다. 
Label Description
0         T-shirt/top
1         Trouser
2         Pullover
3         Dress
4         Coat
5         Sandal
6         Shirt
7         Sneaker
8         Bag
9         Ankle boot

아래 코드를 입력하고, 실행해본다.

# 라이브러리 임포트
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda, Compose
import matplotlib.pyplot as plt

# 패션 MNIST 훈련용 데이터셋을 다운로드한다.
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
)

# 테스트 데이터를 다운로드한다.
test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor(),
)

batch_size = 64  # 배치크기 설정

# 데이터 로더 생성
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print("Shape of X [N, C, H, W]: ", X.shape)
    print("Shape of y: ", y.shape, y.dtype)
    break

# 학습용 디바이스 정보 획득
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

# 모델 정의
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

# 모델 학습을 위한 디바이스 설정
model = NeuralNetwork().to(device)
print(model)

# 모델 학습 솔류션 수렴을 위한 최적화 모델 정의
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

# 훈련 함수 정의
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model(X)
        loss = loss_fn(pred, y)

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

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

# 테스트 함수 정의
def test(dataloader, model):
    size = len(dataloader.dataset)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= size
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

epochs = 10  # 학습을 원하는 만큼 수정요
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model)
print("Done!")

# 모델 학습 가중치 파일 저장
torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")

# 가중치 파일을 통한 모델 로드
model = NeuralNetwork()
model.load_state_dict(torch.load("model.pth"))

# 예측을 위한 클래스 정의
classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]

# 모델 평가
model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
    pred = model(x)  # 패션 MNIST 데이터 입력 후 예측
    predicted, actual = classes[pred[0].argmax(0)], classes[y]
    print(f'Predicted: "{predicted}", Actual: "{actual}"')

실행 결과는 다음과 같다.
/home/ktw/anaconda3/envs/pytorch/bin/python /home/ktw/.local/share/JetBrains/Toolbox/apps/PyCharm-C/ch-0/211.6693.115/plugins/python-ce/helpers/pydev/pydevd.py --multiproc --qt-support=auto --client 127.0.0.1 --port 39301 --file /home/ktw/Projects/pytorch/MNIST.py
Connected to pydev debugger (build 211.6693.115)
Backend Qt5Agg is interactive backend. Turning interactive mode on.
Shape of X [N, C, H, W]:  torch.Size([64, 1, 28, 28])
Shape of y:  torch.Size([64]) torch.int64
Using cuda device
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
    (5): ReLU()
  )
)
Epoch 1
-------------------------------
loss: 2.299011  [    0/60000]
loss: 2.295951  [ 6400/60000]
loss: 2.283560  [12800/60000]
loss: 2.278397  [19200/60000]
loss: 2.284869  [25600/60000]
loss: 2.267760  [32000/60000]
loss: 2.266866  [38400/60000]
loss: 2.257998  [44800/60000]
loss: 2.237380  [51200/60000]
loss: 2.209423  [57600/60000]
Test Error: 
 Accuracy: 41.4%, Avg loss: 0.034879 

...
est Error: 
 Accuracy: 57.0%, Avg loss: 0.022959 

Done!
Saved PyTorch Model State to model.pth
Predicted: "Sneaker", Actual: "Ankle boot"

이제 학습 반복 세대를 높여서 실행해보자. 세대를 10으로 수정한 후 loss는 0.018335로 줄었음을 확인할 수 있다.

파이토치 기능 및 모델구조 설명
토치는 데이터 로딩 작업을 위해, torch.utils.data.DataLoader, torch.utils.data.Dataset을 제공한다. torchvision.datasets 모듈은 CIFAR, COCO 등 유명한 데이터셋 로딩을 지원한다. 

앞의 소스에서 배치 크기는 64이므로, 데이터로더의 iterator는 64개의 이미지와 레이블을 반환하게 된다. 

모델 정의는 다음과 같다. 
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

토치의 상위 클래스, nn.Module을 파생받은 NeuralNetwork 클래스를 정의해야 한다(Torch.NN 참고).

생성자에서 모델을 정의한다. nn은 신경망 구조를 제공한다. nn.ReLU는 비선형 학습 활성화 함수를 정의한다. flatten은 주어진 2차원 벡터 이미지를 1차원으로 벡터로 변환한다. 

nn.Sequential은 정의된 순서대로 데이터가 전달되도록 하는 컨테이너이다. 이 컨테이너에 신경망 구조를 순서대로 정의하면 된다. 모델링된 결과는 다음과 같다. 28x28 이미지(784 픽셀)는 Linear 레이어로 입력되어, 512 출력노드로 맵핑된다. 이 레이어의 출력값은 ReLU로 전달되고, 다시 Linear 레이어로 512 출력노드로 맵핑되어 ReLU로 변환된다. 이 결과는 다시 10개의 출력노드를 가진 Linear 레이어로 맵핑되고 ReLU로 변환된다. 

NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
    (5): ReLU()
  )
)

역전파 알고리즘 함수는 토치에서 train의 forward함수를 이용해 자동으로 생성해준다.
역전파 알고리즘은 신경망의 입력층과 출력층의 차이를 조정하기 위해, 가중치를 얼마나 보정해야 하는 지를 계산한다. 이 보정치는 손실함수의 기울기값이라고 말하며, 이는 다음 그림에서 신경망 가중치와 바이어스를 기울기 값으로 수정할때 사용된다. 손실함수는 계산된 결과와 목표값의 차이를 계산하여, 이 차이값을 최소화할 때 사용된다.
모델 훈련은 이러한 과정을 반복해 가중치와 바이어스를 조정하는 수치 계산 과정일 뿐이다. 

앞의 소스에서는 학습 파라메터인 파이퍼 파라메터를 다음과 같이 설정했었다. 
learning_rate = 1e-3
batch_size = 64
epochs = 5

여기서 learning_rate는 옵티마이저에 설정되는 학습률이다. 

옵티마이저는 각 학습 단계에서 모델 오류를 줄이기 위해 모델 손실값을 조정해 나간다. SDG는 확률기반 경사 하강법이며, 토치에는 이외 다양한 최적화 함수가 있다. 모델의 파라메터를 통해 최적화 함수는 모델 구조, 파라메터, 역전파 함수를 얻어 이를 최적화할 때 사용한다.
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

결국, 다음과 같이 앞에서 정의한 데이터 로딩 방법, 모델, 손실 함수, 최적화 함수를 이용해 세대별로 반복 학습을 하게 된다.
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model)

훈련된 모델은 파일로 저장해 필요할 때 로딩해 사용할 수 있다. ONNX(Open Neural Network Exchange) 로딩 및 저장도 함께 제공한다.

모델 구조 수정 후 결과 비교
모델 구조를 수정해 결과를 확인해 보자. 참고로, 원래 소스 결과는 5세대 훈련 시  Accuracy: 57.0%, Avg loss: 0.022959 였다. 

1. 2개 은닉층
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=10, bias=True)
    (3): ReLU()
  )
)

Test Error: 
 Accuracy: 33.4%, Avg loss: 0.030179 

Done!
Saved PyTorch Model State to model.pth
Predicted: "Ankle boot", Actual: "Ankle boot"

2. 4개 은닉층
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=512, bias=True)
    (5): ReLU()
    (6): Linear(in_features=512, out_features=10, bias=True)
    (7): ReLU()
  )
)

Test Error: 
 Accuracy: 38.2%, Avg loss: 0.032565 

Done!
Saved PyTorch Model State to model.pth
Predicted: "Ankle boot", Actual: "Ankle boot"

3. 4개 은닉층에서 입출력을 512-256으로 매핑.
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=256, bias=True)
    (5): ReLU()
    (6): Linear(in_features=256, out_features=10, bias=True)
    (7): ReLU()
  )
)

Test Error: 
 Accuracy: 39.7%, Avg loss: 0.032889 

Done!
Saved PyTorch Model State to model.pth
Predicted: "Ankle boot", Actual: "Ankle boot"

결과와 같이 모델 구조에 따라 정확도 및 손실률이 달라진다. 이 모델은 CNN을 사용하지 않았다. 다음과 같이 CNN 모델을 사용할 경우, 정확도는 매우 높아진다. 

CNN 모델은 다음과 같이 정의한다.
x = Image(28 * 28 * 1)  # 그레이컬러 입력채널 1개, 이미지 크기 28 x 28
x = x * Conv2d(1, 8, 3)       # 입력채널 1개, 출력(필터)채널 8개, 커널크기 3
x = ReLu(x)
x = Pooling(x)
x = x* Conv2d(8, 16, 3)     # 입력채널 8개, 출력채널 16개, 커널크기 3
x = ReLu(x)
x = Pooling(x)
x = Reshape(x, -1, 7 * 7 * 16)
x = Dropout(ReLu(Linear(x, 28 * 28, 16 * 16))) # 입력 이미지 28 x 28, 출력 256
x = Dropout(ReLu(Linear(x, 16 * 16, 128)))
x = Dropout(ReLu(Linear(x, 128, 8 * 8)))
x = Linear(x, 8 * 8, 10)
 
코드 구현은 다음과 같다. 
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # convolutional layers
        self.conv1 = nn.Conv2d(1, 8, 3, padding=1)  
        self.conv2 = nn.Conv2d(8, 16, 3, padding=1)
        # linear layers
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 64)
        self.fc4 = nn.Linear(64, 10)
        # dropout
        self.dropout = nn.Dropout(p=0.2)
        # max pooling
        self.pool = nn.MaxPool2d(2, 2)

    def forward(self, x):
        # convolutional layers with ReLU and pooling
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        # flattening the image
        x = x.view(-1, 7 * 7 * 16) # reshape
        # linear layers
        x = self.dropout(F.relu(self.fc1(x)))
        x = self.dropout(F.relu(self.fc2(x)))
        x = self.dropout(F.relu(self.fc3(x)))
        x = self.fc4(x)
        return x

이 경우, 정확도는 평균 77.31%로 높아진다. 레이어가 깊다고 정확도가 높아지는 것은 아니다. 실제 Conv2d(64, 32) 층을 하나 더 추가하고 학습하면 정확도가 67.19%로 오히려 떨어지는 현상을 볼 수 있다. 

다음과 같이 레이어를 하나 더 줄이고 테스트하면 오히려 정확도가 79.98%로 높아진다. 정확도는 학습 데이터 수, 품질, 학습 횟수 등 모델 구조 이외 다른 요소들도 영향을 주므로, 이런 부분은 경험적으로 모델을 설계해야 한다.

lass Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # convolutional layers
        self.conv1 = nn.Conv2d(1, 8, 3, padding=1)
        self.conv2 = nn.Conv2d(8, 16, 3, padding=1)
        # linear layers
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 96)
        self.fc3 = nn.Linear(96, 10)
        # dropout
        self.dropout = nn.Dropout(p=0.2)
        # max pooling
        self.pool = nn.MaxPool2d(2, 2)

    def forward(self, x):
        # convolutional layers with ReLU and pooling
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        # flattening the image
        x = x.view(-1, 7 * 7 * 16)
        # linear layers
        x = self.dropout(F.relu(self.fc1(x)))
        x = self.dropout(F.relu(self.fc2(x)))
        x = self.fc3(x)
        return x

이 모델에 대한 상세한 설명은 여기(github)를 참고한다.

적절한 모델 구조를 찾는 것은 시행착오가 필요하다. 적절한 모델을 찾는 것을 Fitting 한다고 한다. 참고로, AutoML, Model Search같은 라이브러리를 사용하면, 앞서 정의한 모델을 자동으로 얻을 수 있다(AutoML tutorial).

참고 - 토치 지원 모델
토치에서 지원하는 기타 모델 및 사용 예시는 다음과 같다(참고). 
  • Containers
  • Convolution Layers
  • Pooling layers
  • Padding Layers
  • Non-linear Activations (weighted sum, nonlinearity)
  • Non-linear Activations (other)
  • Normalization Layers
  • Recurrent Layers
  • Transformer Layers
  • Linear Layers
  • Dropout Layers
  • Sparse Layers
  • Distance Functions
  • Loss Functions
  • Vision Layers
  • Shuffle Layers
  • DataParallel Layers (multi-GPU, distributed)
  • Utilities
  • Quantized Functions
  • Lazy Modules Initialization
사용 예시.
>>> rnn = nn.LSTM(10, 20, 2)
>>> input = torch.randn(5, 3, 10)
>>> h0 = torch.randn(2, 3, 20)
>>> c0 = torch.randn(2, 3, 20)
>>> output, (hn, cn) = rnn(input, (h0, c0))

케라스는 모델 정의 방법이 다음과 같이 좀 더 상세하다. 토치는 이에 비해 간략하게 모델을 정의할 수 있다.
model = Sequential()
model.add(Dense(1024, input_shape=(3072,), activation="sigmoid"))
model.add(Dense(512, activation="sigmoid"))
model.add(Dense(len(lb.classes_), activation="softmax"))
...
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128,activation='relu'),
  tf.keras.layers.Dense(10)
])
...
model = Sequential()
model.add(LSTM(4, input_shape=(1, look_back)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2)

이외 토치는 케라스보다 실행 성능이 빠르고 확장성이 좋다고 알려져 있다. 

레퍼런스

2021년 5월 17일 월요일

2021년 5월 8일 토요일

node.js 기반 Visual Flow Programming 도구 소개

이 글은 텍스트 기반 코딩이 아닌 Visual Flow Programming (비쥬얼 플로우 프로그래밍) 도구를 소개한다. 여기서는 사용하기 편리한 node.js 기반 프로그래밍 도구를 공유한다. 

NoFlo
NoFlo는 Node.js 브라우저 모두에서 실행되는 JavaScript 용 흐름 기반 프로그래밍 구현이다. NoFlo 구성요소는 ES6를 포함하여 JavaScript로 변환되는 모든 언어로 작성할 수 있다. 이 시스템은 J. Paul Morrison의 저서 Flow-Based Programming(1994)에서 크게 영감을 받았다.
J. Paul Morrison

NoFlo는 웹 프레임 워크 나 UI 툴킷이 아니다. JavaScript 애플리케이션에서 데이터 흐름을 조정하고 재구성하는 방법이다. 
NoFlo는 웹 서버 및 도구 구축에서부터 GUI 애플리케이션 내부의 이벤트 조정, 로봇 구동 또는 인터넷에 연결된 예술 설치 구축에 이르기까지 모든 작업에 사용되고 있다.

NoFlo 자체는 JavaScript에서 흐름 기반 프로그램을 구현하기 위한 라이브러리이다. 설치 방법은 다음과 같으며, 소스 코드는 github에서 다운로드가능하다.
npm install noflo --save

Rete는 시각적 프로그래밍을 위한 모듈식 프레임 워크이다. Rete를 사용하면 브라우저에서 직접 노드 기반 편집기를 만들 수 있다. 사용자가 한 줄의 코드없이 편집기에서 데이터 처리 방식을 정의할 수 있다.
설치는 다음과 같다. 설명 문서는 여기를 참고하라.
npm install rete rete-vue-render-plugin rete-connection-plugin

Drawflow를 사용하면 데이터 흐름을 쉽고 빠르게 만들 수 있다.

설치는 다음과 같다. 
npm i drawflow

Reference

2021년 5월 4일 화요일

엔비디아 Kaolin 기반 3D 딥러닝 모델 개발 방법 소개

이 글은 엔비디아 카올린 Kaolin기반 3D 딥러닝 모델 개발 방법을 간략히 소개한다.
딥러닝 처리 결과
Kaolin 사용 예시

이 글은 3D 객체 인식 분류기를 개발하는 방법을 이해한다. 3D 객체 인식은 자율 주행 자동차, 로봇, AR / VR 같은 실제 애플리케이션을 개발하는 데 중요한 부분이다. 3D 데이터는 물체 / 차량을 정확하게 위치시키는 데 사용할 수 있는 깊이 정보를 제공한다.

Kaolin- 3D 딥러닝 프레임워크
Kaolin은 3D 딥러닝 연구를 가속화하기 위해 NVIDIA 팀에서 개발한 오픈소스 PyTorch 라이브러리이다. Kaolin 프레임워크는 몇 줄의 코드로 3D 모델을 딥러닝 데이터 세트로 변환한다. kaolin은 인기있는 3D 데이터 세트를 로드하고 사전처리하는 데 쉽다.

Kaolin 프레임워크는 포인트 클라우드 데이터를 복셀 그리드로 변환하는 복잡한 3D 알고리즘, 간단한 삼각형 메시 표현을 지원한다. 카올린 프레임워크는 로봇 공학, 자율 주행 자동차, 증강 및 가상 현실과 같은 분야의 연구자들에게 도움이 된다. 
이 프레임웍은 다음 기능을 지원한다(상세 참고).
  • DGCNN
  • DIB-R
  • GEOMetrics
  • Image2Mesh
  • Occupancy Network
  • Pixel2Mesh
  • PointNet
  • PointNet++ 
  • MeshEncoder: A simple mesh encoder architecture.
  • GraphResNet: MeshEncoder with residual connections.
  • OccupancyNetworks
  • MeshCNN
  • VoxelGAN
  • AtlasNet
3D 객체 감지 분류기에서는 Kaolin 프레임 워크를 사용한다. git repo는 여기에서 찾을 수 있다. 

개발 도구
Google colaboratory 일명 colab은 무료로 GPU에 액세스 할 수있는 jupyter 노트북이다. Colaboratory에서 모델을 훈련 할 때 GPU 기반 가상 머신을 사용할 수 있다. 한 번에 최대 12 시간이 주어진다. 따라서 정기적으로 데이터 또는 체크 포인트를 저장하라. colab은 완전 무료이다.

colab에는 주요 라이브러리 (NumPy, matplotlib) 및 프레임 워크 (TensorFlow, PyTorch)가 사전 설치되어 있으며, 사용자 설치를 위해 (! pip install ) 사용할 수 있다.

3D 데이터 표현
포인트 클라우드는 XYZ 좌표에 의해 지정된 LiDAR와 같은 센서에서 얻은 원시 데이터이다. 이 데이터는 폴리곤 메시(mesh), 복셀(voxel) 등 다른 표현으로 변환된다.
데이터 세트
데이터 세트는 princeton MODELNET 에서 사용할 수 있다. ModelNet10.zip 파일은 깊은 네트워크를 훈련하는 데 사용되는 10 개 카테고리에서 CAD 모델을 포함한다. 이를 통해, 3D 물체 검출 분류, 훈련 및 테스트 분할 정보를 얻을 수 있다. 

욕조, 침대, 의자, 책상, 옷장, 모니터, 침실 용 스탠드, 소파, 테이블, 화장실

3D 객체 감지 분류기
여기에서 Google Colab을 열고 런타임을 GPU로 변경하고, 다음 코드를 코랩 쥬피터 노트북에 붙여넣고 실행한다.
!pip install trimesh
import os
import glob
import trimesh
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from matplotlib import pyplot as plt

tf.random.set_seed(1234)
%matplotlib inline

#Installing kaolin
!pip install git+https://github.com/NVIDIAGameWorks/kaolin.git

import torch
from torch.utils.data import DataLoader
import kaolin as kal
from kaolin import ClassificationEngine
from kaolin.datasets import ModelNet
from kaolin.models.PointNet import PointNetClassifier
import kaolin.transforms as tfs
from torchvision.transforms import Lambda

MODELNET10 데이터 세트 다운로드한다.
#downloading the MODELNET 10 dataset
DATA_DIR = tf.keras.utils.get_file(
    "modelnet.zip",
    "http://3dvision.princeton.edu/projects/2014/3DShapeNets/ModelNet10.zip",
    extract=True,
)
DATA_DIR = os.path.join(os.path.dirname(DATA_DIR), "ModelNet10")

X, Y, Z 3D 모델 시각화한다.
#visualizing bed .off model
mesh = trimesh.load(os.path.join(DATA_DIR, "bed/train/bed_0001.off"))
mesh1= trimesh.load(os.path.join(DATA_DIR, "chair/train/chair_0001.off"))
mesh2= trimesh.load(os.path.join(DATA_DIR, "night_stand/train/night_stand_0001.off"))

mesh.show()

데이터를 로딩한다.
Kaolin은 인기있는 3D 데이터 세트 (ModelNet10)를 로드하는 편리한 기능을 제공한다. 시작하려면 몇 가지 중요한 매개 변수를 정의한다.
model_path변수는 ModelNet10 데이터 세트의 경로를 정의한다. categories변수를 사용하여 분류할 클래스를 지정한다. num_points포인트 클라우드로 변환 할 때 메시에서 샘플링할 포인트의 수이다. 마지막으로 변환 작업에 CUDA를 사용하는 경우 다중 처리 및 메모리 고정을 비활성화한다.

modelnet_path = os.path.join(os.path.dirname(DATA_DIR), "ModelNet10")
print(modelnet_path)
categories = ['chair', 'sofa', 'bed','night_stand']
num_points=1024
device='cuda'
workers = 8

#training parameters
batch_size = 12
learning_rate = 1e-3
epochs = 10

이 명령은 transform먼저 메시 표현을 포인트 클라우드로 변환한 다음 원점 중심에 오도록 정규화하고 표준 편차를 1로 정의한다. 이미지와 마찬가지로 포인트 클라우드와 같은 3D 데이터는 더 나은 분류 성능을 위해 정규화되어야 한다.

split='train'인수는 '기차'분할을로드하고 있음을 나타냅니다. rep='pointcloud'메시를로드하고 포인트 클라우드로 변환한다. transform=norm각 점 구름에 정규화 변환을 적용한다.
def to_device(inp):
    inp.to(device)
    return inp

transform = tfs.Compose([
    to_device,
    tfs.TriangleMeshToPointCloud(num_samples=num_points),
    tfs.NormalizePointCloud()
])

num_workers = 0 if device == 'cuda' else workers
pin_memory = device != 'cuda'

train_loader = DataLoader(ModelNet(modelnet_path, categories=categories,
                                   split='train', transform=transform),
                          batch_size=batch_size, shuffle=True, 
                          num_workers=num_workers, pin_memory=pin_memory)
                          
val_loader = DataLoader(ModelNet(modelnet_path, categories=categories,
                                 split='test',transform=transform),
                        batch_size=batch_size, num_workers=num_workers,
                        pin_memory=pin_memory)

PointNet을 이용해, 모델 학습을 위한 최적화 프로그램 및 손실 기준을 설정한다.
model = PointNetClassifier(num_classes=len(categories)).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
criterion = torch.nn.CrossEntropyLoss()

포인트 클라우드 분류기를 훈련한다. 대략 20분 소요된다.
for e in range(epochs):
    print(f'{"":-<10}\nEpoch: {e}\n{"":-<10}')

    train_loss = 0.
    train_accuracy = 0.

    model.train()
    for batch_idx, (data, attributes) in enumerate(tqdm(train_loader)):
        category = attributes['category'].to(device)
        pred = model(data)
        loss = criterion(pred, category.view(-1))
        train_loss += loss.item()
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        # Compute accuracy
        pred_label = torch.argmax(pred, dim=1)
        train_accuracy += torch.mean((pred_label == category.view(-1)).float()).item()

    print('Train loss:', train_loss / len(train_loader))
    print('Train accuracy:', train_accuracy / len(train_loader))

    val_loss = 0.
    val_accuracy = 0.

    model.eval()
    with torch.no_grad():
        for batch_idx, (data, attributes) in enumerate(tqdm(val_loader)):
            category = attributes['category'].to(device)
            pred = model(data)
            loss = criterion(pred, category.view(-1))
            val_loss += loss.item()

            # Compute accuracy
            pred_label = torch.argmax(pred, dim=1)
            val_accuracy += torch.mean((pred_label == category.view(-1)).float()).item()

    print('Val loss:', val_loss / len(val_loader))
    print('Val accuracy:', val_accuracy / len(val_loader))

테스트 데이터에서 훈련 된 3D 물체 감지 모델을 평가한다.
test_loader = DataLoader(ModelNet(modelnet_path, categories=categories,
                                  split='test',transform=transform),
                        shuffle=True, batch_size=15)

data, attr = next(iter(test_loader))
data = data.to('cuda')
labels = attr['category'].to('cuda')
preds = model(data)
pred_labels = torch.max(preds, axis=1)[1]

이전 val_loader와 동일한 데이터를 셔플링으로 로드하고, 샘플 배치를 가져온다. 다음으로, 시각화 기능을 사용하여 포인트 클라우드, 레이블 및 예측을 시각화한다. 결과는 색상으로 구분한다. 녹색은 정확하고 빨간색은 부정확하다.
from mpl_toolkits.mplot3d import Axes3D     # unused import necessary to have access to 3d projection # noqa: F401
import matplotlib.pyplot as plt
%matplotlib inline


def visualize_batch(pointclouds, pred_labels, labels, categories):
    batch_size = len(pointclouds)
    fig = plt.figure(figsize=(8, batch_size / 2))

    ncols = 5
    nrows = max(1, batch_size // 5)
    for idx, pc in enumerate(pointclouds):
        label = categories[labels[idx].item()]
        pred = categories[pred_labels[idx]]
        colour = 'g' if label == pred else 'r'
        pc = pc.cpu().numpy()
        ax = fig.add_subplot(nrows, ncols, idx + 1, projection='3d')
        ax.scatter(pc[:, 0], pc[:, 1], pc[:, 2], c=colour, s=2)
        ax.axis('off')
        ax.set_title('GT: {0}\nPred: {1}'.format(label, pred))

    plt.show()
visualize_batch(data, pred_labels, labels, categories)

결과

Jupyter 노트북에 업로드된 전체 코드는 여기에서 찾을 수 있다. .

레퍼런스
상세한 내용은 다음 링크를 참고한다.