[Embedded System Lab] Chapter 4 - Introduction To Machine Learning

"Introduction To Machine Learning"

[Objective]

Python으로 Multi-Layer Perceptrons을 구현하여 이미지 분류를 통해 Machine Learning 이해하기

[Theory]

▣ Machine Learning
· 인공지능 : 사람의 지능과 연관된 인지 기능을 모방하는 System
· Machine Learning : 대규모 데이터, 통계로부터 학습한 정보에 기반하여 의사결정을 내리는 알고리즘
· Deep Learning : Machine Learning의 신경망 깊이에 해당하는 Layer를 여러 개 쌓아 만든 깊은 신경망을 이용한 학습

> Machine Learning Model
· Neural Networks & Multi-Layer Perceptrons (인공 신경망)
· Convolutional Neural Network (합성곱 신경망)
· Recurrent Neural Network (순환 신경망)
· Large Language Model (대규모 언어 모델)

▣ Multi-Layer Perceptrons
> Single-Layer Perceptron
· Model의 입력 층과 출력 층만 있는 단순한 회귀식
· 각 Weight Node들은 입력 값에 따른 Weight를 보유 (Ex. W1-1, W1-2, W1-3, W1-4)

> Multi-Layer Perceptrons (= Neural Network)
· 입력 층과 출력 층 사이에 Hidden Layer(은닉 층)을 여러 층 추가한 형태
→ 복잡한 비선형 관계를 학습하는데 유용

> Activation Function
· Neural Network에서 각 Node의 출력값을 결정하는 함수
· Activation Function 덕분에 Neural Network는 비선형적이고 복잡한 패턴을 학습 가능
· 목적에 따라 Sigmoid, ReLU, Softmax 등 다양한 Activation Function 사용 가능

> Forward Propagation
                       
                       
· MLP의 입력 층부터 출력 층까지 순서대로 변수들을 계산하고 저장
· MLP의 각 Node들은 사전에 정의된 Weight를 이용해 출력을 계산
· "f"는 활성화 함수를 의미

> Backward Propagation
· Forward Propagation을 통해 얻은 결과물(예측값)과 실제 입력에 따른 결과값을 비교
· 예측값과 실제값의 차이와 해당 Gradient를 이용하여 MLP Weight에 대한 Update 진행
· Chaine Rule에 따라 여러 층에 걸쳐 Forward Propagation의 반대방향으로 진행

> Training MLPs
· Forward Propagation을 이용해 입력값에 대한 출력값을 예측
· Backward Propagation을 통해 예측값과 실제값을 비교하며 각 층의 Weight를 Update
↑ 여러 차례 반복하는 것 = MLP Model을 Training
→ MLP Model을 훈련함으로써 Model은 입력값에 대해 실제값과 가까운 출력값을 갖게 됨

> Inference Using MLPs
<Ex. 신발 매장의 특징(입력값)에 대한 매출, 판매량(출력값)을 예측>
· 훈련이 완료되면, Neural Network는 학습 데이터를 기반으로 입력값에 대한 출력값을 잘 예측 가능
· 이후, Neural Network를 실제로 사용하여 결과물을 얻을 때에는 Forward Propagation만 사용

▣ Machine Learning Models
> Convolutional Neural Network
· 인간의 시신경 구조를 모방한 기술
· 이미지 안의 인접한 부분의 특징, 멀리 떨어진 부분에 대한 전반적인 인식을 하는데 있어 합성곱 사용
· 합성곱 연산의 경우 일반적인 행렬곱이 아닌 행렬의 원소끼리만 곱하여 결과를 출력 (Elementwise Product)
· 합성곱 연산에 사용되는 Weight는 Kernel 또는 Filter라고 지칭
· 이미지 분류나 영상 데이터 처리 등, 컴퓨터 비전 분야에서 많이 사용

· 합성곱 연산과 Pooling 연산을 통해 이미지의 특징을 추출
· Pooling 연산은 결과 행렬에서 최대값 또는 평균값을 추출하는 과정
· 합성곱, Pooling 층을 여러 개 쌓은 후, 마지막 몇 층을 MLP를 이용하여 구성

> Large Language Model
<Transformer 기반 생성형 LLM>
· 단어(Token)들은 일정 크기의 Vector로 표현 가능
· 지금까지 사용한 단어들을 모아놓고 문법, 의미 등의 문맥을 Weight를 통해 얻어냄
· 지금까지 사용된 단어들에 대한 문법, 의미 등 다양한 문맥을 고려했을 때, 적절한 다음 단어를 출력
· 출력한 다음 단어를 포함하는 지금까지 사용된 단어들은 Model의 다음 입력 값으로 사용
· Transformer라는 Machine Learning Model 사용

[Code Description & Result]

▣ Library Setting
1
2
3
4
5
6
7
8
9
10
11
# Model
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
# Visualizing
import matplotlib.pyplot as plt
# etc
from google.colab import drive
drive.mount('/content/drive')
cs


· Google Colab, PyTorch를 이용하여 MLP를 구현하기 위해 먼저 위 코드 실행

2 : PyTorch Library 전체를 "torch" 이름으로 불러오기 (x = torch.tensor([1.0, 2.0, 3.0]))
3 : PyTorch의 nn 모듈을 "nn" 이름으로 불러오기 (nn.Linear, nn.Conv2d, nn.ReLU, nn.CrossEntropyLoss)
4 : torch.nn.functional 모듈을 "F" 이름으로 불러오기 (F.relu(), F.softmax(), F.cross_entropy(), F.max_pool2d())
5 : torch.optim 모듈을 "optim" 이름으로 불러오기 (optim.SGD, optim.Adam, optim.RMSprop)
6 : torchvision Library에서 "datasets"와 "transforms"만 불러오기
    (datasets : 자주 쓰는 이미지 데이터셋을 쉽게 불러오기 위한 기능)
    → MNIST, CIFAR-10, FashionMNIST
    (transforms : 이미지 데이터를 모델에 넣기 전에 형태를 바꾸거나 전처리하는 기능)
    → transforms.ToTensor(), Transforms.Normalize(), transforms.Resize()
8 : matplotlib Library의 pyplot 모듈을 "plt" 이름으로 불러오기
10 : Google Colab 환경에서 Google Drive를 연결하기 위한 기능을 불러오기
11 : Google Drive를 Colab의 파일 System에 연결

▣ Device Setting
· Code가 GPU에서 실행될 수 있도록 설정

▣ Check Device
1
2
3
4
5
# Check GPU
is_cuda = torch.cuda.is_available()
device = torch.device('cuda' if is_cuda else 'cpu')
 
print('CUDA Device :', device)
cs


· GPU(CUDA)를 사용할 수 있는지 확인하고, 사용 가능한 Device를 설정

2 : 현재 환경에서 GPU(CUDA)를 사용할 수 있는지 True/False로 확인
3 : 사용할 Device 설정 (GPU 가능 = True → device = GPU / GPU 불가능 = False → device = CPU)

▣ Load MNIST Dataset
1
2
3
4
5
6
# load MNIST dataset
train_data = datasets.MNIST(root = '/content/drive/MyDrive/MNIST', train = True, download = True, transform = transforms.ToTensor())
test_data = datasets.MNIST(root = '/content/drive/MyDrive/MNIST', train = False, download = True, transform = transforms.ToTensor())
 

print(len(train_data), len(test_data))
cs


· AI Model 학습에 필수적인 MNIST 손글씨 Dataset을 불러오고 준비하는 과정

2 : root 경로에서 학습용 데이터를 가져옴
3 : root 경로에서 평가용 데이터를 가져옴 → 학습에는 절대 보여주지 않고 오직 시험용으로만 쓸 이미지 데이터 1만장을 가져옴
6 : 데이터를 에러 없이 잘 불러왔는지 최종적으로 확인하기 위해 훈련 데이터 개수와 테스트 데이터 개수를 출력

· root : 데이터를 저장하거나 이미 다운로드 된 데이터를 찾을 폴더 경로 지정
· train : True = 학습용 데이터 / False = 평가용 데이터
· download : True = 만약 지정한 root 경로에 데이터 파일이 없다면, PyTorch가 알아서 인터넷에서 다운로드하여 폴더에 저장
· transforms.ToTensor( ) : Python의 기본 이미지 파일 형태를 PyTorch가 계산할 수 있는 숫자 행렬 형태인 Tensor로 변환
                                                + 0~255 볌위였던 Pixel 색상 값을 모델이 학습하기 좋은 0~1 사이의 소수점 범위로 압축(정규화)

▣ Check Size of Image
1
2
3
# check size of image
image, label = train_data[0]
print(f'Shape of image: {image.squeeze().numpy().shape}')
cs


· 첫 번째 데이터를 대표로 꺼내와서 이미지의 구조(크기)가 어떻게 되는지 확인

2 : 훈련용 데이터의 첫 번째 Data Sample을 불러옴
3 : 이미지 구조 출력

· image, label : PyTorch의 이미지 Dataset은 기본적으로 (이미지 데이터, 정답 라벨) 2가지로 구성 → 분리해서 저장
· image : PyTorch에서 이미지를 [채널 수, 세로 Pixel, 가로 Pixel] 형태의 Tesnsor → [1, 28, 28]
· squeeze( ) : 차원의 크기가 '1'인 불필요한 차원을 제거 → [28, 28]
· numpy( ) : PyTorch 전용 데이터 형태인 Tensor를 수학/배열 계산에 사용하는 NumPy 배열로 변환
· shape : NumPy 배열의 형태(크기)를 알려주는 속성 → (28, 28)

▣ Fix Random Seed Number for Experiment Replication
1
2
# fix random seed number for experiment replication
torch.manual_seed(1)
cs


· 실험 결과를 언제나 똑같이 재현하기 위해 무작위 요소들을 통제

2 : 난수 Seed 고정

· torch : PyTorch Library의 기능을 사용
· manual_seed(1) : 난수를 만들어낼 때 사용하는 시작점(Seed)을 수동으로 1로 지정

▣ Design MLP Model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#################### fill the codes under annotation ####################
 
# make mlp model
class MLP(nn.Module):
    def __init__(self, input_dim=784, hidden1=256, hidden2=64, output_dim=10):
        super(MLP, self).__init__()
        # First hidden layer: Linear -> BatchNorm -> ReLU (fill this part!)
        self.layer1 = nn.Sequential(
            nn.Linear(input_dim, hidden1),
            nn.BatchNorm1d(hidden1),
            nn.ReLU()
        )
 
        # Second hidden layer: Linear -> BatchNorm -> ReLU (fill this part!)
        self.layer2 = nn.Sequential(
            nn.Linear(hidden1, hidden2),
            nn.BatchNorm1d(hidden2),
            nn.ReLU()
        )
 
        # Output layer: Linear mapping to classes (fill this part!)
        self.out = nn.Linear(hidden2, output_dim)
 
    def forward(self, x):
        # Flatten the input: [batch_size, 1, 28, 28] (fill this part!)
        x = x.view(x.size(0), -1)
 
        # Pass through two hidden layers (fill this part!)
        x = self.layer1(x)
        x = self.layer2(x)
 
        # Final output layer (fill this part!)
        x = self.out(x)
 
        return x
cs

· PyTorch를 사용하여 MLP Model 설계

4 : "nn.Module" Class 상속
5 : 입력 데이터 크기 = 784, 은닉층1의 뉴런 개수 = 256, 은닉층2의 뉴런 개수 = 64, 최종 출력 값의 개수 = 10
6 : 상속받은 부모 Class인 nn.Module의 초기화 함수 실행
8 : 여러 개의 부품을 순서대로 묶는 역할
9 : 784개의 입력을 받아 256개로 변환
10 : 데이터의 Scale을 일정하게 맞춰 학습 속도를 높이고 안정적으로 만들기 위한 배치 정규화 실행
11 : 음수 값은 0, 양수는 그대로 통과시키는 비선형 함수 ReLU 적용
15 : 여러 개의 부품을 순서대로 묶는 역할
16 : 256개의 데이터를 받아 64개로 변환
17 : 데이터의 Scale을 일정하게 맞춰 학습 속도를 높이고 안정적으로 만들기 위한 배치 정규화 실행
18 : 음수 값은 0, 양수는 그대로 통과시키는 비선형 함수 ReLU 적용
22 : 64개의 데이터를 받아 10개로 변환
26 : [배치 크기, 1, 28, 28]인 2차원 이미지 데이터를 [배치 크기, 784] 형태의 1차원 데이터로 변환
29 : 1차원으로 펴진 데이터 x를 은닉층1 함수에 할당
30 : 은닉층1을 통과한 데이터를 은닉층2 함수에 할당
33 : 은닉층2를 통과한 데이터를 출력층 함수에 할당
35 : 최종 계산된 10개의 점수(예측값)를 반환

· (x.size(0), -1) : 배치 크기(한 번에 들어오는 이미지 256장)를 그대로 유지하며, 나머지 차원은 컴퓨터가 알아서 하나의 긴 줄로 계산

▣ Set Hyperparameter for Training
1
2
3
4
5
6
# load MLP model to GPU
# set hyperparameters for training
model = MLP().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
num_epochs = 50
cs

· 학습하기 전 Hyperparameter 설정

3 : 설계한 MLP를 CPU/GPU 중 위에서 설정한 장치에 올려 계산할 준비
4 : 오차 계산 공식
5 : 최적화 알고리즘 Adam을 사용하여 오차를 바탕으로 Model 안에 있는 Weight와 Bias 수정
6 : 전체 Dataset을 50번 반복해서 Weight를 Update(학습) → 너무 많이 반복 시 훈련 데이터만 달달 외우는 Overfitting 발생 가능

· lr : 학습율로, 한 번 틀렸을 때 Weight를 수정하는 '보폭 크기'를 의미 → 너무 크면 최적점을 지나치고, 너무 작으면 학습이 오래 걸림

▣ Use Data Loader for Training Data
1
2
3
# use data loader for training data
from torch.utils.data import DataLoader
train_loader = DataLoader(dataset=train_data, batch_size=256, shuffle=True)
cs

· 방대한 양의 데이터를 Model이 학습하기 좋게 적절한 크기로 나눠서 전달

2 : PyTorch에서 데이터를 다루는 데 유용한 도구들이 있는 Module에서 DataLoader Class를 가져옴
3 : DataLoader를 이용하여 실제로 훈련용 데이터를 관리할 변수 생성

· dataset : 어떤 데이터를 처리할 지 지정 → 훈련용 Dataset 지정
· batch_size : Model에게 데이터를 한 번에 몇 개씩 묶어서(Batch) 전달할지 결정 → 256장씩 1 묶음 (60,000 / 256 ≒ 235번 전달)
· shuffle : Model이 전체 Data를 한 번 다 볼 때마다 데이터의 순서를 무작위로 섞을지 결정 → 정확한 학습을 위해 반드시 True

▣ Training Data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#################### fill the codes under annotation ####################
 
# run 50 iterations of training
for epoch in range(num_epochs):
    # train model (fill this part!)
    model.train() # 모델을 학습 모드로 설정
    running_loss = 0.0
 
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device) # 데이터를 GPU/CPU로 이동
 
        optimizer.zero_grad()       # 1. 이전 루프의 기울기 초기화
        outputs = model(images)     # 2. 순전파 (예측)
        loss = criterion(outputs, labels) # 3. 오차(Loss) 계산
        loss.backward()             # 4. 역전파 (기울기 계산)
        optimizer.step()            # 5. 가중치 업데이트
 
        running_loss += loss.item() # 손실 누적
 
    # compute loss over epochs (fill this part!)
    epoch_loss = running_loss / len(train_loader)
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.4f}')
cs



· 실제 Model이 학습용 데이터를 보고 학습하는 훈련 Loop

4 : 전체 Dataset을 50번 반복하여 훈련
6 : Model을 학습 Mode로 설정
7 : 전체를 1회 훈련하는 동안 발생한 오차(Loss)를 모두 더해서 기록할 변수를 0으로 초기화
9 : train_loader가 한 번에 256장씩 이미지와 정답 라벨을 묶어서 가져옴 → 6만 개의 데이터를 읽기 위해 235번 반복
10 : 가져온 256장의 이미지와 정답 라벨을 Model이 올라간 연산 장치로 전송 (모델과 데이터가 같은 장치 안에 있어야만 계산 가능)
12 : PyTorch는 기본적으로 기울기를 누적하는 특징이 있어 새로운 256장 문제를 풀기 전, 이전 문제에서 계산했던 기울기를 초기화
13 : 256장의 이미지를 Model에 통과(Forward Pass) → 각 이미지에 대해 예측 값 10개를 계산
14 : 예측 값과 정답 라벨을 채점 기준(CrossEntropyLoss)에 넣고 비교하여 Loss 계산
15 : 오차를 바탕으로 출력층부터 입력층까지 거꾸로 되돌아가며, 기울기 계산
16 : Backward로 계산된 기울기 정보와 학습율을 이용해 Weight Update
18 : Update 완료 시, 256장 묶음에서 발생한 오차(Loss)를 running_loss에 누적
21 : 235번 반복이 모두 끝나서 60,000장을 다 확인하면 1 Epoch 종료 후, 평균 오차 계산
22 : Epoch 번호와 계산된 평균 오차를 소수점 4자리까지 출력 → 학습이 잘 되고 있다면 뒤로 갈수록 Loss 값 감소

· item( ) : PyTorch Tensor로 감싸져 있는 숫자를 순수한 Python 숫자(float)로만 뽑아내는 함수 ∵ 불필요한 계산 기록 제거

▣ Test Data Using Original Model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#################### fill the codes under annotation ####################
 
# use data loader for test data
test_loader = DataLoader(dataset=test_data, batch_size=256, shuffle=False)
 
# check accuracy of the trained model (fill this part!)
model.eval() # 모델을 평가 모드로 설정
model.to(device) # Ensure the model is on the correct device
correct = 0
total = 0
with torch.no_grad(): # 평가 시에는 기울기 계산을 비활성화하여 메모리 절약 및 속도 향상
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device) # 데이터를 GPU/CPU로 이동
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1# 가장 높은 확률을 가진 클래스 추출
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
 
# Calculate accuracy (fill this part!)
accuracy = 100 * correct / total
print(f"Original Model Test Accuracy: {accuracy:.2f}%")
cs


· 50번의 반복 학습을 마친 Model이 실제로 처음 보는 Test Data를 얼마나 잘 맞추는지 평가

4 : 학습할 때와 마찬가지로 Test Data를 256장씩 묶어서 가져오는 test_loader 생성
7 : Model을 평가 Mode로 설정
8 : Model이 연산을 수행할 장치에 올라가 있는지 확인
9 : Model이 정답을 맞춘 개수를 세기 위한 변수 초기화
10 : Model이 푼 전체 문제의 개수를 세기 위한 변수 초기화
11 : Test 할 때는 기울기 계산(Backward)을 비활성화하여 메모리 절약 및 속도 향상
12 : Test용 Data Loader에서 256장씩 이미지와 정답을 꺼내옴 → 10,000 / 256 ≒ 40번 전달
13 : Test 이미지와 정답 라벨을 Model이 올라간 장치로 전달
14 : 256장의 이미지를 Model에 통과시켜 각 이미지에 대한 10개의 예측 값 계산
15 : output에 담긴 10개의 점수 중 가장 점수가 높은 Index 찾기
16 : 현재 Batch에 들어있는 데이터의 개수(일반적으로 256)를 total에 누적하여 총 몇 문제를 풀었는지 기록
17 : 예측 값과 정답이 똑같은지 비교하여 256개의 문제 중 맞춘 것의 개수를 전부 누적
20 : 모든 Test 데이터를 다 확인한 후, 백분율로 정확도 계산
21 : 최종 계산된 정확도를 소수점 둘째 자리까지 출력

· dataset : 어떤 데이터를 처리할 지 지정 → Test용 Dataset 지정
· batch_size : Model에게 데이터를 한 번에 몇 개씩 묶어서(Batch) 전달할지 결정 → 256장씩 1 묶음 (10,000 / 256 ≒ 40번 전달)
· shuffle : Test 할 때는 굳이 순서를 섞을 필요가 없으므로 False로 설정
· torch.max : 1번째 차원을 기준으로 최댓값을 찾기
· _, : 몇 점인지 궁금하지 않고, Index만 필요하여 안 쓰는 값인 첫 번째 반환 값을 버리고 두 번째 반환값을 "predicted"에 저장
· item( ) : PyTorch Tensor로 감싸져 있는 숫자를 순수한 Python 숫자(float)로만 뽑아내는 함수 ∵ 불필요한 계산 기록 제거

▣ Setting for Quantization Model
1
2
3
4
5
6
7
8
9
10
11
12
# 8bit quantization of the model
model.to('cpu')
 
import torch.quantization
quantized_model = torch.quantization.quantize_dynamic(
    model,              # the original model
    {nn.Linear},        # layers to quantize
    dtype=torch.qint8   # quantize to 8-bit integers
)
 
quantized_model.eval()
quantized_model.to('cpu')
cs

· 학습이 완료된 크고 무거운 DL Model을, 성능은 최대한 유지하면서 크기는 줄이고 속도는 빠르게 만드는 Quantization 적용

2 : 학습을 마친 Original Model을 GPU에서 CPU 메모리로 이동 (∵ CPU 환경에서 추론 속도를 극대화하기 위해 설계)
4 : PyTorch 내부에 준비된 양자화 전용 함수 불러옴
5 : Model 압축을 실행하고, 해당 결과물을 새로운 변수에 저장
6 : CPU로 옮겨둔 학습된 Original Model 사용
7 : Linear 층들만 압축 (∵ MLP Model은 Weight가 꽉 들어찬 선형계층이 용량의 대부분을 차지)
8 : 32-bit Float 형태를 8-bit Integer 형태로 압축
11 : 양자화 Model을 추론 Mode로 설정 (양자화된 Model은 추가적인 학습을 할 수 없고 오직 추론만 하는 용도로 사용)
12 : 양자화된 Model이 CPU에 배치되었는지 확인

· quantize_dynamic : 동적 양자화를 수행하는 함수
→ Model의 Weight는 미리 압축하고, 데이터가 흐를 때 발생하는 Activation들은 계산할 때마다 실시간으로 압축하여 계산

▣ Test Data Using Quantized Model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# check accuracy of the quantized model
correct = 0
total = 0
with torch.no_grad():
    for images, labels in test_loader:
        # Ensure images are on CPU since the quantized model works on CPU
        images = images.to('cpu')
        outputs = quantized_model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
 
accuracy = 100 * correct / total
print(f"Quantized Model Test Accuracy: {accuracy:.2f}%")
cs


· 8-bit로 압축한 Model의 정확도 평가

2 : Model이 정답을 맞춘 횟수를 기록할 변수 초기화
3 : Model이 푼 전체 문제 수를 기록할 변수 초기화
4 : 추론 Mode이므로 기울기 계산(Backward) 기능을 Off하여 메모리를 절약하고 속도를 높임
5 : test_loader에서 256장씩 이미지와 정답을 꺼내움
7 : 양자화 Model은 CPU에 있으므로 데이터도 반드시 CPU로 올라가야 에러 발생 X
8 : 256장의 이미지를 양자화된 모델에 통과시켜 각 이미지에 대한 10개의 예측 값 계산
9 : 10개의 예측 값 중 가장 점수가 큰 Index를 찾아 predicted 변수에 저장
10 : 묶음에 들어있는 데이터의 개수(일반적으로 256)를 total에 누적
11 : 예측 값과 정답 라벨이 일치하는 개수를 누적
13 : 10,000장의 데이터를 다 확인한 후, 정확도를 백분율로 정확도 계산
14 : 최종 계산된 정확도를 소수점 둘째 자리까지 출력

▣ Print Test Data Result Using Quantized Model 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
 
# Select one test sample.
image, label = test_data[1]
plt.imshow(image.squeeze(), cmap='gray')
plt.title(f"True Label: {label}")
plt.axis('off')
plt.show()
 
# run single inference
with torch.no_grad():
    image = image.to('cpu')
    output = quantized_model(image.unsqueeze(0))
 
# convert probabilities to a NumPy array.
probabilities = torch.softmax(output, dim=1)
probabilities_np = probabilities.cpu().numpy()[0]
 
# display the probability for each class as a percentage.
for digit, prob in enumerate(probabilities_np):
    print(f"{prob*100:.2f}% that the number is {digit}")
cs



· 10,000장의 Test Data 중 1장의 이미지를 뽑아 화면에 보여주고, 양자화된 모델이 해당 이미지를 보고 숫자를 추론

1 : 이미지 출력을 위해 Library 선언
4 : 10,000장의 Test Dataset 중 1번째 Index(2번째 데이터)를 추론
5 : 2차원 숫자 배열을 이미지로 출력
6 : 이미지의 위에 정답 라벨을 텍스트로 출력
7 : 축 눈금 Off
8 : 설정한 옵션을 적용하여 화면에 출력
11 : Weight Update가 필요 없으므로, 기울기 계산(Backward) 기능을 꺼서 메모리와 속도를 최적화
12 : 이미지를 CPU 메모리로 이동
13 : 예측 값 계산
16 : Softmax 함수를 사용하여 전체의 합이 100%가 되도록 확률 값으로 변환
17 : PyTorch Tensor 형태인 결과를 Python에서 다루기 쉬운 NumPy 배열로 변환
20 : 각 숫자에 대한 정답 확률 표시
21 : 확률에 100을 곱해서 보기 편한 백분율로 표현 → 0 ~ 9 총 10줄

· unsqueeze(0) : PyTorch Model은 [배치 크기, 채널, 세로, 가로] 형태의 4차원 입력을 기대
→ 이미지는 [1, 28, 28] 형태의 3차원이므로 0번째 자리에 차원 하나 추가
· dim = 1 : 1번째 차원(Class 10개)을 기준으로 확률 계산
· [0] : 1장짜리 묶음([1, 10])으로 넣었으므로 결과도 묶음 형태
→ 유일한 결과물인 0번째 배열만 사용하여 10개의 확률 숫자 리스트([10]) 생성
· enumerate : List를 반복할 때, '순서 번호'와 '해당 값'을 동시에 가져옴
· digit : 0 ~ 9가 차례대로 할당
· prob : 각 숫자에 해당하는 확률 할당

· 왼쪽은 2574번째 Test Data(Index = 2573), 오른쪽은 8140번째 Test Data(Index = 8139)를 추론한 결과

[Discussion]

· 양자화된 Model이 아닌 Original Model로 추론 가능하며 마지막 이미지와 확률 출력 코드에서 약간의 수정이 필요
12 : image.to(device) → Original Model은 GPU에 있을 수 있으므로 이미지를 Model과 같은 장치에 위치시킴
13 : model(image.unsqueeze(0)) → quantized_model이 아닌 Original Model을 통과시킴
17 : probabilities.cpu( ).numpy( )[0] → NumPy로 바꾸려면 GPU에 있는 것을 CPU로 반드시 가져와야 하므로 .cpu( ) 필수

· MNIST의 데이터 중 다른 데이터를 추론하고 싶으면 마지막 이미지와 확률 출력 코드에서 test_data의 Index 수정

· 다른 이미지 데이터(CIFAR-10, FashionMNIST)를 사용하여 실험
→ train_data와 test_data를 Load 할 때 datasets.CIFAR-10, datasets.FashionMNIST으로 설정

· Original Model이 사용하는 데이터의 크기는 32-bit Float 형태로 GPU에서 계산하는 것이 압도적으로 빠름
· Quantized Model이 사용하는 데이터의 크기는 8-bit Integer 형태로 CPU에서 계산하는 것이 훨씬 빠름

· Quantized Model 사용 : 추론 정확도가 크게 감소하지 않지만, 메모리 사용량과 전력 소모가 감소하고 모델 크기(용량)이 작아져 추론(계산) 속도가 훨씬 빨라짐
→ 성능 하락 대비 얻는 가성비가 압도적으로 큼

[Reference]

· Intro_to_ML_4 (Embedded System Lab : Chapter4) - William J. Song

댓글

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)