2023-11-21 54th Class
Multiclass Classification
#️⃣ Sigmoid Function 의 기울기 소실 문제
딥러닝 모델의 depth(층수)가 깊어지면 activation fuction은 Sigmoid보다는 ReLU를 사용함
이는 Sigmoid는 gradient vanishing problem 때문
Sigmoid가 backpropagation을 할 때 vanishing gradient가 발생하는 이유는 아래의 그래프를 참고하면 이해 가능
시그모이드
- 시그모이드 편미분값의 최댓값은 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 function인 nn.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 |
---|---|
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 |
---|---|
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
'Education > 새싹 TIL' 카테고리의 다른 글
새싹 AI데이터엔지니어 핀테커스 12주차 (목) - Sobel Filtering 2 (1) | 2023.11.23 |
---|---|
새싹 AI데이터엔지니어 핀테커스 12주차 (수) - Sobel Filtering (1) | 2023.11.22 |
새싹 AI데이터엔지니어 핀테커스 12주차 (월) - Pytorch Tutorial2 (Moon & Make Blobs XOR Dataset) (0) | 2023.11.20 |
새싹 AI데이터엔지니어 핀테커스 11주차 (금) - Pytorch Tutorial (1) | 2023.11.17 |
새싹 AI데이터엔지니어 핀테커스 11주차 (목) - MLP Visualization (0) | 2023.11.16 |