728x90

2023-11-21 54th Class

Multiclass Classification

#️⃣ Sigmoid Function 의 기울기 소실 문제

딥러닝 모델의 depth(층수)가 깊어지면 activation fuction은 Sigmoid보다는 ReLU를 사용함
이는 Sigmoid는 gradient vanishing problem 때문

Sigmoid가 backpropagation을 할 때 vanishing gradient가 발생하는 이유는 아래의 그래프를 참고하면 이해 가능

Pasted image 20231121173357.png

시그모이드

  • 시그모이드 편미분값의 최댓값은 0.25
  • 오차역전파 시 시그모이드 단계에서는 loss값의 미분과 z값에 대하여 sigmoid를 편미분한 값을 곱해 affine 함수로 돌려주게되는데
  • 이때 그 결괏값의 최댓값은 0.25밖에 되지 않음
  • chain rule 로 구성된 여러 층의 레이어에서 sigmoid 단계를 거칠 수록 0.25보다 작은 값을 계속해 곱해주게 되고
  • 점점 미분계수는 작아지게 되어서 0에 수렴하게 됨
  • 레이어가 많을 수록 (network가 deep할수록) 첫 번째 affine 함수가 돌려받은 미분계수는 0과 다름없는 수라는 의미
  • 첫번째 affine 함수에서는 이 0과 다름없는 미분계수로 weight과 bias를 업데이트하게 될 것임 (학습이 안 됨)
  • weight과 bias의 변화가 없으니 모델의 학습효과는 없게 되는 문제가 발생

이러한 문제를 해결하기위해 tanh, relu, leaky relu등 다양한 activation function을 대체하여 사용 가능

  • 탄젠트 함수(tanh)의 도함수(미분함수) 최댓값은 1로 sigmoid의 최댓값 0.25보다 크기때문에
  • 기울기 소실 문제로부터 완전히 자유롭지는 않지만 경우에 따라 학습 효과가 좋음
  • 가장 많이 사용하는 활성화함수는 relu로, 0이하의 값에서는 0, 1이상의 값에서는 x값을 그대로 출력하는 함수임
  • 이 relu의 미분계수는 0일때까지는 0이고, 0보다 클 때는 1이기 때문에 기울기 소실 문제에 가장 잘 대응할 수 있는 함수 중 하나임

#️⃣ 멀티 클래스 classification

  • 다항분류는 타겟값의 종류가 0, 1 이진이 아닌 0, 1, 2, 3… 등 여러개인 문제임
  • 다항분류 모델에서 최종 출력은 class 개수
  • 다항분류는 아래와 같은 확률 벡터를 출력해야 함
  • 즉 c1일 확률, c2일 확률, c3일 확률, c4일 확률로 나오게됨
  • C1+C2+C3+C4 = 1
  • 이 확률값으로 변환해주는 과정을 normalization이라고 함
  • 마지막 레이어에서 normalization 하는 함수가 Softmax임

#️⃣ Softmax

다항분류모델의 마지막 K개의 실숫값을 확률 벡터로 바꾸기 위해 Softmax Function을 통과시킴

Pytorch에서는 마지막 레이어에 nn.Softmax()를 해주지 않아도 됨
->pytorch의 loss function이 softmax를 포함하고 있기 때문

Softmax formula

#️⃣ Crossentropy

Crossentropy formula

모델의 예측 yhat = [0.1, 0.2, 0.6, 0.1] 이고, 정답이 y = [0, 0, 1, 0]이라면
J = - [0xlog(0.1)+0xlog(0.2)+1xlog(0.6)+0xlog(0.1)] = -log(0.6) = 0.51
3번째 클래스를 잘 예측했기 때문에 오차 J가 낮음

모델의 예측 yhat = [0.1, 0.2, 0.6, 0.1] 이고, 정답이 y= [1, 0, 0, 0] 이라면
J = - [1xlog(0.1)+0xlog(0.2)+0xlog(0.6)+0xlog(0.1)] = -log(0.1) = 2.3
첫번째 클래스를 잘 예측하지 못했기 때문에 오차 J가 높게 나옴

pytorch에서 다항분류를 위한 crossentropy는 “nn.CrossEntropyLoss”

Pytorch에서는 마지막 레이어에 nn.Softmax()를 해주지 않아도 됨
->pytorch의 다항분류를 위한 loss functionnn.CrossEntropyLoss 가 softmax를 포함하고 있기 때문

#️⃣ Model

class Classifier(nn.Module):  
    def __init__(self):  
        super(Classifier, self).__init__()  
  
        self.fc1 = nn.Linear(in_features=10, out_features=16)  
        self.fc1_act = nn.ReLU()  
        self.fc2 = nn.Linear(in_features=16, out_features=32)  
        self.fc2_act = nn.ReLU()  
        self.fc3 = nn.Linear(in_features=32, out_features=10)  
  
    def forward(self, x):  
        x = self.fc1(x)  
        x = self.fc1_act(x)  
        x = self.fc2(x)  
        x = self.fc2_act(x)  
        x = self.fc3(x)  
        return x
  • 다항분류에서는 마지막 fully connected layer의 out_feature 수를 클래스의 수로 설정함

Multiclass Classification Code

#️⃣ 4개의 데이터 클래스 분류

데이터셋

picture description
231121 dataset.png 1) 4개의 centroids를 가진 데이터로 class가 4개
2) 모델의 예측값은 [0일 확률, 1일 확률, 2일 확률, 3일 확률]로 나오게 됨

학습

Pytorch 모델에서 위 데이터의 pred는 아래와 같이 나옴

tensor([[ 0.1292,  0.0239,  0.6806,  0.4073],
        [ 0.1602,  0.2131,  0.1514,  0.0284],
        [ 0.1501,  0.2055,  0.1617,  0.0122],
        [ 0.2590,  0.2869,  0.0511,  0.1853],
        [ 0.3474,  0.4395, -0.0041,  0.4642],
        [ 0.1832,  0.2455,  0.1292,  0.0750],
        [ 0.2364,  0.2700,  0.0740,  0.1494],
        [ 0.2347,  0.2687,  0.0758,  0.1467]], device='cuda:0',
       grad_fn=<AddmmBackward0>)
  • 8x4의 행렬
  • 한 줄(row)에서 칸(column)은 4개가 있는데, 각각은 클래스 0, 1, 2, 3일 확률임
tensor([3, 2, 2, 0, 1, 2, 0, 0], device='cuda:0', dtype=torch.int32)
  • y 값은 0, 1, 2, 3 중에 1개임
  • y는 8줄짜리 vector인데, pred는 4칸에 걸쳐 각 클래스에 해당할 확률이 8x4 행렬로 나오기 때문에
  • y 벡터를 pred의 shape에 맞추도록 변형해야 함
  • 따라서 (8,) 벡터인 y를 (8,4) 행렬로 (8줄 4칸으로 증강) 원핫 인코딩 필요
  • Pytorch에서는 nn.CrossEntropyLoss(pred, y) 형태로 함수를 call하면 내부에서 원핫인코딩 처리해 loss를 계산함
  • 그래서 accuracy 계산시에만 argmax를 사용해 가장 높은 확률로 뽑힌 클래스를 y_hat(pred_cls)로 변환하고
  • 타겟값인 y와 비교하여 정확도를 계산함

full code

from dataclasses import dataclass  
import torch  
import torch.nn as nn  
from torch.optim import Adam  
from sklearn.datasets import make_blobs  
  
import numpy as np  
import matplotlib.pyplot as plt  
from torch.utils.data import TensorDataset, DataLoader  
from tqdm import tqdm  
  
  
@dataclass  
class Constants:  
    N_SAMPLES: int  
    BATCH_SIZE: int  
    EPOCHS: int  
    LR: float  
    n_features: int  
    DEVICE: torch.device  
    PATH: str  
    SEED: int  
  
  
def get_device():  
    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  
    print(f"curr device = {DEVICE}")  
    return DEVICE  
  
  
def get_grid_data(X, y):  
    # xylim  
    fig, ax = plt.subplots(figsize=(10, 10))  
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='tab10')  
    ax.tick_params(labelsize=15)  
    # plt.show()  
    x_lim, y_lim = ax.get_xlim(), ax.get_ylim()  
    plt.close()  
  
    # grid  
    x1 = np.linspace(x_lim[0], x_lim[1], 100)  
    x2 = np.linspace(y_lim[0], y_lim[1], 100)  
    X1, X2 = np.meshgrid(x1, x2)  
    X_db = np.hstack([X1.reshape(-1, 1), X2.reshape(-1, 1)])  
    return X_db  
  
  
class MultiClassClassifier(nn.Module):  
    def __init__(self):  
        super(MultiClassClassifier, self).__init__()  
        self.fc1 = nn.Linear(in_features=2, out_features=4)  
        self.fc1_act = nn.ReLU()  
        self.fc2 = nn.Linear(in_features=4, out_features=8)  
        self.fc2_act = nn.ReLU()  
        self.fc3 = nn.Linear(in_features=8, out_features=4)  
  
    def forward(self, x):  
        x = self.fc1(x)  
        x = self.fc1_act(x)  
        x = self.fc2(x)  
        x = self.fc2_act(x)  
        x = self.fc3(x)  
        return x  
  
  
def get_data(c):  
    # create data  
    HALF_SAMPLES = int(c.N_SAMPLES / 2)  
    centers1 = [(-10, -4), (-7, -8)]  
    centers2 = [(-6, -3), (-2, 4)]  
    X1, y1 = make_blobs(n_samples=HALF_SAMPLES, centers=centers1, n_features=2, cluster_std=0.5, random_state=c.SEED, shuffle=True)  
    X2, y2 = make_blobs(n_samples=HALF_SAMPLES, centers=centers2, n_features=2, cluster_std=0.5, random_state=c.SEED, shuffle=True)  
    y2 += 2  
    X = np.vstack([X1, X2])  
    y = np.vstack([y1, y2]).flatten()  
  
    # grid  
    X_db = get_grid_data(X, y)  
    X_db = TensorDataset(torch.FloatTensor(X_db))  
  
    # dataloader  
    dataset = TensorDataset(torch.FloatTensor(X), torch.LongTensor(y))  
    dataloader = DataLoader(dataset, batch_size=c.BATCH_SIZE, shuffle=True)  
  
    return dataloader, X_db, X, y  
  
  
def train(dataloader, N_SAMPLES, model, loss_function, optimizer, DEVICE):  
    epoch_loss, n_corrects = 0., 0  
  
    for X, y in tqdm(dataloader):  
        X, y = X.to(DEVICE), y.to(DEVICE)  
  
        pred = model.forward(X)  
  
        loss = loss_function(pred, y)  
  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  
  
        epoch_loss += loss.item() * len(X)  
        pred_cls = torch.argmax(pred, dim=1)  
        n_corrects += (pred_cls == y).sum().item()  
  
    epoch_loss /= N_SAMPLES  
    epoch_accr = n_corrects / N_SAMPLES  
  
    return epoch_loss, epoch_accr  
  
  
def vis_losses_accs(losses, accs):  
    fig, axes = plt.subplots(2, 1, figsize=(14, 5))  
    axes[0].plot(losses)  
    axes[1].plot(accs)  
  
    axes[1].set_xlabel("Epoch", fontsize=15)  
    axes[0].set_ylabel("Loss", fontsize=15)  
    axes[1].set_ylabel("Accuracy", fontsize=15)  
    axes[0].tick_params(labelsize=10)  
    axes[1].tick_params(labelsize=10)  
    fig.suptitle("Multiclass 3-layer Model Metrics by Epoch", fontsize=16)  
  
  
def vis_meshgrid(X_db, pred, X, y):  
    fig, ax = plt.subplots(figsize=(10, 10))  
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='bwr')  
    ax.scatter(X_db[:, 0], X_db[:, 1], c=pred, cmap='bwr', alpha=0.1)  
    plt.show()  
  
  
def run_multiclass_classifier(c):  
    # Data  
    dataloader, X_db, X, y = get_data(constants)  
  
    # Model  
    model = MultiClassClassifier().to(c.DEVICE)  
    loss_function = nn.CrossEntropyLoss()  
    optimizer = Adam(model.parameters(), lr=c.LR)  
  
    # Training  
    losses, accs = list(), list()  
  
    for epoch in range(c.EPOCHS):  
        epoch_loss, epoch_acc = train(dataloader, c.N_SAMPLES, model, loss_function,  
                                      optimizer, c.DEVICE)  
  
        losses.append(epoch_loss)  
        accs.append(epoch_acc)  
  
        if epoch % 10 == 0:  
            print(f"\n EPOCH: {epoch}", end="\t")  
            print(f"Accuracy: {epoch_acc}", end="\t")  
            print(f"Loss: {epoch_loss}")  
  
  
    # Predict  
    X_db = X_db.tensors[0].to(c.DEVICE)  
    # 1번 방법(faster) torch.no_grad() autograd engine 비활성화  
    with torch.no_grad():  
        pred = model(X_db).to("cpu")  
  
    X_db = X_db.to("cpu").detach().numpy()  
    pred_cls = torch.argmax(pred, dim=1)  
  
    # Visualization  
    vis_losses_accs(losses, accs)  
    vis_meshgrid(X_db, pred_cls, X, y)  
  
  
if __name__ == '__main__':  
    constants = Constants(  
        N_SAMPLES=100,  
        BATCH_SIZE=8,  
        EPOCHS=100,  
        LR=0.01,  
        n_features=2,  
        DEVICE=get_device(),  
        PATH="model/multicls_params.pt",  
        SEED=80  
    )  
    run_multiclass_classifier(constants)
curr device = cuda
100%|██████████| 13/13 [00:00<00:00, 87.61it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.30it/s]

 EPOCH: 0	Accuracy: 0.25	Loss: 1.3930088567733765
100%|██████████| 13/13 [00:00<00:00, 1181.85it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.39it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.26it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.33it/s]
100%|██████████| 13/13 [00:00<00:00, 1181.78it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.35it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.35it/s]
100%|██████████| 13/13 [00:00<00:00, 928.64it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.22it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.45it/s]
...
 EPOCH: 10	Accuracy: 0.99	Loss: 0.14717254161834717
 EPOCH: 20	Accuracy: 1.0	Loss: 0.032098175436258314
 EPOCH: 30	Accuracy: 1.0	Loss: 0.010683839730918408
 EPOCH: 40	Accuracy: 1.0	Loss: 0.005456323828548193
 EPOCH: 50	Accuracy: 1.0	Loss: 0.00336724572815001
 EPOCH: 60	Accuracy: 1.0	Loss: 0.0022762445360422134
 EPOCH: 70	Accuracy: 1.0	Loss: 0.001638240811880678
 EPOCH: 80	Accuracy: 1.0	Loss: 0.0012462146091274918
...
 EPOCH: 90	Accuracy: 1.0	Loss: 0.0010444807377643884
100%|██████████| 13/13 [00:00<00:00, 1050.21it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.24it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.35it/s]
100%|██████████| 13/13 [00:00<00:00, 1181.83it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.33it/s]
100%|██████████| 13/13 [00:00<00:00, 1083.35it/s]
100%|██████████| 13/13 [00:00<00:00, 999.93it/s]
100%|██████████| 13/13 [00:00<00:00, 1000.05it/s]
metrics by epoch decision boundary
231121 decisionboundary.png 231121 multiclassmetrics.png

MNIST

import pickle  
from dataclasses import dataclass  
from torchvision.datasets import MNIST  
from torchvision.transforms import ToTensor  
from torch.utils.data import DataLoader  
import torch  
import torch.nn as nn  
from torch.optim import Adam  
  
import numpy as np  
import matplotlib.pyplot as plt  
from tqdm import tqdm  
  
  
@dataclass  
class Constants:  
    N_SAMPLES: int  
    BATCH_SIZE: int  
    EPOCHS: int  
    LR: float  
    n_features: int  
    DEVICE: torch.device  
    PATH: str  
    METRIC_PATH: str  
    SEED: int  
  
  
def get_device():  
    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  
    print(f"curr device = {DEVICE}")  
    return DEVICE  
  
  
class MnistClassifier(nn.Module):  
    def __init__(self):  
        super(MnistClassifier, self).__init__()  
        self.fc1 = nn.Linear(in_features=784, out_features=256)  
        self.fc1_act = nn.ReLU()  
        self.fc2 = nn.Linear(in_features=256, out_features=128)  
        self.fc2_act = nn.ReLU()  
        self.fc3 = nn.Linear(in_features=128, out_features=64)  
        self.fc3_act = nn.ReLU()  
        self.fc4 = nn.Linear(in_features=64, out_features=10)  
  
    def forward(self, x):  
        x = self.fc1(x)  
        x = self.fc1_act(x)  
        x = self.fc2(x)  
        x = self.fc2_act(x)  
        x = self.fc3(x)  
        x = self.fc3_act(x)  
        x = self.fc4(x)  
        return x  
  
  
def train(dataloader, N_SAMPLES, model, loss_function, optimizer, DEVICE):  
    epoch_loss, n_corrects = 0., 0  
  
    for X, y in tqdm(dataloader):  
        X = X.view(-1, (X.shape[-1] * X.shape[-2]))  
  
        X, y = X.to(DEVICE), y.to(DEVICE)  
  
        pred = model.forward(X)  
  
        loss = loss_function(pred, y)  
  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  
  
        epoch_loss += loss.item() * len(X)  
        pred_cls = torch.argmax(pred, dim=1)  
        n_corrects += (pred_cls == y).sum().item()  
  
    epoch_loss /= N_SAMPLES  
    epoch_accr = n_corrects / N_SAMPLES  
  
    return epoch_loss, epoch_accr  
  
  
def vis_losses_accs(losses, accs):  
    fig, axes = plt.subplots(2, 1, figsize=(14, 5))  
    axes[0].plot(losses)  
    axes[1].plot(accs)  
  
    axes[1].set_xlabel("Epoch", fontsize=15)  
    axes[0].set_ylabel("Loss", fontsize=15)  
    axes[1].set_ylabel("Accuracy", fontsize=15)  
    axes[0].tick_params(labelsize=10)  
    axes[1].tick_params(labelsize=10)  
    fig.suptitle("MNIST 4-layer Model Metrics by Epoch", fontsize=16)  
    plt.show()  
  
  
def run_mnist_classifier(c):  
    # Data  
    dataset = MNIST(root='data', train=True, download=True, transform=ToTensor())  
    dataloader = DataLoader(dataset, batch_size=c.BATCH_SIZE)  
  
    # Model  
    model = MnistClassifier().to(c.DEVICE)  
    loss_function = nn.CrossEntropyLoss()  
    optimizer = Adam(model.parameters(), lr=c.LR)  
  
    # Train  
    losses, accs = list(), list()  
  
    for epoch in range(c.EPOCHS):  
        epoch_loss, epoch_acc = train(dataloader, len(dataset), model, loss_function,  
                                      optimizer, c.DEVICE)  
  
        losses.append(epoch_loss)  
        accs.append(epoch_acc)  
  
        print(f"\n EPOCH: {epoch}", end="\t")  
        print(f"Accuracy: {epoch_acc}", end="\t")  
        print(f"Loss: {epoch_loss}")  
  
    # Save Model and Metrics by Epoch  
    with open(c.METRIC_PATH, 'wb') as f:  
        result = {  
            'losses': losses,  
            'accs': accs  
        }  
        pickle.dump(result, f, pickle.HIGHEST_PROTOCOL)  
  
    torch.save(model, c.PATH)

	# 시각화
	vis_losses_accs(metric_dict['losses'], metric_dict['accs'])  


# 수정중  
def eval_and_visualize(c):  
    with open(c.METRIC_PATH, 'rb') as f:  
        metric_dict = pickle.load(f)  
  
    # vis_losses_accs(metric_dict['losses'], metric_dict['accs'])  
  
    dataset = MNIST(root='data', train=False, download=True, transform=ToTensor())  
    dataloader = DataLoader(dataset)  
  
    for label_to_extract in range(10):  
  
        selected_image, selected_label = next(  
            (img, label) for img, label in dataloader if label == label_to_extract  
        )  
        print('here')  
  
    model = torch.load(c.PATH)  
  
    fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(14, 14))  
    axes = axes.flatten()  
    for i, ax in enumerate(axes):  
        # ax.imshow(im[i].reshape(28,28), cmap="gray")  
        pass  
  
  
  
  
if __name__ == '__main__':  
    constants = Constants(  
        N_SAMPLES=60000,  
        BATCH_SIZE=32,  
        EPOCHS=20,  
        LR=0.01,  
        n_features=784,  
        DEVICE=get_device(),  
        PATH="model/mnist.pt",  
        METRIC_PATH="model/mnist_metrics.pkl",  
        SEED=80  
    )  
    # run_mnist_classifier(constants)  
    eval_and_visualize(constants)
curr device = cuda
100%|██████████| 1875/1875 [00:05<00:00, 336.60it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 0	Accuracy: 0.9147333333333333	Loss: 0.31510896680412503
100%|██████████| 1875/1875 [00:06<00:00, 288.62it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 1	Accuracy: 0.9499166666666666	Loss: 0.2015774247773923
100%|██████████| 1875/1875 [00:05<00:00, 347.98it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 2	Accuracy: 0.9589	Loss: 0.16955820459430881
100%|██████████| 1875/1875 [00:05<00:00, 348.53it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 3	Accuracy: 0.9615833333333333	Loss: 0.162634120188312
100%|██████████| 1875/1875 [00:05<00:00, 348.00it/s]

 EPOCH: 4	Accuracy: 0.9639333333333333	Loss: 0.15582783620095578
100%|██████████| 1875/1875 [00:05<00:00, 348.19it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 5	Accuracy: 0.9688333333333333	Loss: 0.1363857058061442
100%|██████████| 1875/1875 [00:05<00:00, 348.25it/s]

 EPOCH: 6	Accuracy: 0.9673833333333334	Loss: 0.13478326454286094
100%|██████████| 1875/1875 [00:05<00:00, 348.07it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 7	Accuracy: 0.9719166666666667	Loss: 0.12213223823212174
100%|██████████| 1875/1875 [00:05<00:00, 345.16it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 8	Accuracy: 0.9704166666666667	Loss: 0.13269778553549744
100%|██████████| 1875/1875 [00:05<00:00, 317.60it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 9	Accuracy: 0.9741333333333333	Loss: 0.11610410564825677
100%|██████████| 1875/1875 [00:06<00:00, 288.75it/s]

 EPOCH: 10	Accuracy: 0.9762666666666666	Loss: 0.10536138129019576
100%|██████████| 1875/1875 [00:06<00:00, 285.03it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 11	Accuracy: 0.9770166666666666	Loss: 0.10699693690381294
100%|██████████| 1875/1875 [00:06<00:00, 287.38it/s]

 EPOCH: 12	Accuracy: 0.9725	Loss: 0.13280194665449574
100%|██████████| 1875/1875 [00:05<00:00, 354.18it/s]

 EPOCH: 13	Accuracy: 0.9774333333333334	Loss: 0.10280799146225011
100%|██████████| 1875/1875 [00:05<00:00, 354.50it/s]

 EPOCH: 14	Accuracy: 0.97665	Loss: 0.11081120831514142
100%|██████████| 1875/1875 [00:05<00:00, 343.08it/s]

 EPOCH: 15	Accuracy: 0.9797333333333333	Loss: 0.0956471509909073
100%|██████████| 1875/1875 [00:05<00:00, 355.62it/s]

 EPOCH: 16	Accuracy: 0.9809	Loss: 0.08796055115984443
100%|██████████| 1875/1875 [00:05<00:00, 354.91it/s]
  0%|          | 0/1875 [00:00<?, ?it/s]
 EPOCH: 17	Accuracy: 0.9807833333333333	Loss: 0.09144671282802229
100%|██████████| 1875/1875 [00:05<00:00, 348.63it/s]

 EPOCH: 18	Accuracy: 0.98025	Loss: 0.08673916294180768
100%|██████████| 1875/1875 [00:05<00:00, 355.82it/s]

 EPOCH: 19	Accuracy: 0.9817833333333333	Loss: 0.08354974720353706

231121-2 mnist metrics.png

반응형