Data Science/ML | DL

[Pytorch] Classification 모델 구현하고 MNIST 데이터로 훈련하기

402번째 거북이 2022. 8. 15. 19:00

Pytorch로 Classification 모델들을 구현하고, MNIST 데이터를 가지고 모델 훈련, 하이퍼파라미터 튜닝 등을 해 성능을 높여보는 실습을 했다.

구현한 모델들은 아래와 같다.

 

✔️ Soft-Margin SVM (Binary Classification)

✔️ MLP (Binary Classification)

✔️ MLP (Multiclass Classification)

✔️ k-NN (Multiclass Classification)

 


MNIST Data Split😶

 

MNIST 데이터는 784개의 attribute와 10개의 label(0부터 9)로 이루어진 handwirtten digit 이미지 데이터다.

먼저 데이터를 digit 분포가 고르게 train/validation/test로 split하는 함수를 만들었다.

# Making Dictionary with Answer Label as Key and its Indices as Value
# so that we can split them into train/validation/test with the same digit propotion.
y_dict = dict(zip([i for i in tar.unique()], [list(tar[tar.values==i].index) for i in tar.unique()]))
y_dict = dict(zip([i for i in tar.unique()], [list(np.random.choice(y_dict[i], len(y_dict[i]), replace = False)) for i in y_dict.keys()])) # Shuffling

train_idx=[]
val_idx=[]
test_idx=[]
train_tmp = [y_dict[i][:int(round(len(y_dict[i])*0.7,-1))] for i in sorted(y_dict.keys())] # Trimming train set (70%)
val_tmp = [y_dict[i][int(round(len(y_dict[i])*0.7,-1)):int(round(len(y_dict[i])*0.85,-1))] for i in sorted(y_dict.keys())] #Ttrimming validation set (15%)
test_tmp = [y_dict[i][int(round(len(y_dict[i])*0.85,-1)):] for i in sorted(y_dict.keys())] # Trimming test set (15%)

for i in range(len(sorted(y_dict.keys()))):
  train_idx+=train_tmp[i]
  val_idx+=val_tmp[i]
  test_idx+=test_tmp[i]

# Converting into Tensor Type & Normalizing
X_train, y_train = torch.tensor(attr.loc[train_idx].to_numpy())/255, torch.tensor(tar.loc[train_idx].to_numpy())
X_val, y_val = torch.tensor(attr.loc[val_idx].to_numpy())/255, torch.tensor(tar.loc[val_idx].to_numpy())
X_test, y_test = torch.tensor(attr.loc[test_idx].to_numpy())/255, torch.tensor(tar.loc[test_idx].to_numpy())

데이터의 70%는 train, 나머지 30%는 각각 15%씩 validation과 test set으로 나눠줬다.

다 나눈 후에는 데이터를 Pytorch Tensor Type으로 바꾸고, Normalization을 적용해줬다.

 


Binary Classification via soft-margin SVM😶

Binary 분류문제이기 때문에 데이터에서 label이 2, 3인 것만 추렸다.

SVM 알고리즘에 맞춰 label을 -1, 1로 바꿔주는 작업을 했다.

 

✔️구현

모델 구현 중 주요한 사항은 아래와 같다.

def hinge_loss(y_actual, y_pred):
    return torch.clamp(1-y_pred*y_actual, min=0)

기본구조는 nn.Linear로 초기화했고, epoch와 batch를 돌면서 모델을 훈련할 때 loss를 계산하도록 loss function을 위에 정의해주었다. SVM은 hinge loss 사용하는 것이 가장 포인트.

 

✔️하이퍼파라미터 튜닝

batch size, optimizer 등 여러 하이퍼파라미터 조합으로 training하는 과정을 통해 validation set accuracy를 높여 모델성능을 올리는 작업을 했다.

 

✨Epoch Number & Batch size

처음에 batch size를 64로, epoch 수는 10으로 세팅해줬더니 validation accuracy가 0.95부터 올라가는 것을 확인할 수 있었다.

batch size를 너무 크게 하니까(256) accuracy가 떨어져서, 원래 세팅인 batch size 64 / epoch 10으로 돌려줬다.

 

✨gamma

soft-margin SVM에서 slack을 어느정도 허용할지를 조정하는 하이퍼파라미터다.

아래처럼 loss function에 gamma를 함께 작성해줬다.

loss = gamma*torch.norm(list(model.parameters())[0])/2 + torch.mean(hinge_loss(answer, prediction))

초기 gamma 값을 0.2로 세팅해줬는데, 값을 올리니까 성능이 떨어져서 값을 낮춰줬다.

epoch 10까지 도는 동안 validation accuracy가 0.97까지 올라갔다.

 

이밖에도 Optimizer(SGD, Adam)와 그 learning rate 등 여러 하이퍼파라미터 조합들로 훈련을 시도해봤다.

 

✔️결과

하이퍼파라미터 튜닝결과 아래와 같은 하이퍼파라미터 조합이 성능이 가장 높게 나왔다.

해당 조합을 바탕으로 final test accuracy를 계산했더니 0.98이 나왔다.

 


Binary Classification via MLP😶

Activation function을 sigmoid로 설정해줄 것이기 때문에, 라벨을 0 과 1로 바꿔주고 모델을 만들었다.

 

✔️구현

class MLP_binary(nn.Module):
  def __init__(self, input_dim, output_dim):
    super(MLP_binary, self).__init__()
    self.module1 = nn.Linear(input_dim, 256, bias=True) #input layer -> hidden layer1
    self.module2 = nn.Linear(256, 256, bias=True) #hidden layer1 -> hidden layer2
    self.module3 = nn.Linear(256, output_dim, bias=True) #hidden layer 2 -> output layer

  def forward(self, x):
    x = F.relu(self.module1(x))
    x = F.relu(self.module2(x))
    x = self.module3(x)
    y_pred = torch.sigmoid(x) # activation function for binary (0~1)
    return y_pred

256개 unit짜리, hidden layer가 2층 있는 MLP모델을 만들었다.

Binary Classification이기 때문에, activation function을 sigmoid로 지정해주었다.

 

criterion = nn.BCELoss() # loss function: cross entropy for binary classification

loss function은 cross entropy로 설정해줬다.

 

✔️결과

validation set으로 성능을 높였고, final test accuracy는 0.99 가 나왔다!

가장 test performance가 좋았던 하이퍼파라미터 조합은 아래와 같다.

 


Multiclass Classification via MLP😶

 

✔️구현

Binary Classification에서 썼던 모델을 그대로 썼다.

다른점은, Multiclass Classification이기 때문에 activation function으로 sigmoid function 대신에  softmax를 썼다는 점.

 

criterion = nn.CrossEntropyLoss() # loss function

파이토치에서 CrossEntropyLoss를 쓰면 Softmax를 적용해준다.

 

✔️하이퍼파라미터 튜닝

✨Optimizer

원래 SGD로 validation accuracy를 계산했는데, 0.93정도 나왔다.

Adam Optimizer로 바꿔주니까 성능이 0.96까지 올랐다.

 

Learning Rate

Learning Rate를 0.001 정도로 줄여주었더니 성능이 0.98까지 올랐다.

 


Multiclass Classification via k-NN😶

k-NN은 non-parametric 모델이기 때문에, 훈련 이전에 모델의 파라미터 형태를 정의해줄 필요가 없다.

모델 정의 없이, 훈련 데이터를 넣고 바로 성능을 확인해줬다.

 

for i in range(len(X_val)):
    if distance_choose == 'L2Norm':
      distance = torch.norm(X_train - X_val[i], dim=1, p='fro') #L2 norm for distance calculating (Frobenius norm)
    elif distance_choose == 'L1Norm':
      distance = torch.norm(X_train - X_val[i], dim=1, p=1 ) #L1 norm for distance calculating (nuclear norm)
    knn_idx = torch.topk(distance, k, largest=False)[1]
    k_neighbors = y_train.numpy()[knn_idx]
    pred = Counter(k_neighbors).most_common(1)[0][0]
    if pred==y_val[i]:
          acc_count+=1

이웃과의 distance를 계산하고, 가까운 k개의 이웃을 선택해서 다수결로 label을 정하는 식으로 짰다.

 

✔️하이퍼파라미터 튜닝

✨k

이웃의 수를 바꿔가면서 성능을 비교해봤다.

k=5~10까지는 성능변화가 크게 없다나 50으로 크게 바꿔줬더니 성능이 0.95까지 낮아졌다.

 

✨Distance 계산법

L1 norm, L2 norm 중 하나를 선택할 수 있도록 했는데, L1 norm의 성능이 살짝 더 낮게 나왔다.

 

 


 

전체 코드 및 원본 리포트는 아래 깃에 올려두었다.

https://github.com/Jiyeong-Oh/Classification-Model-Implementation-with-Pytorch

 

GitHub - Jiyeong-Oh/Classification-Model-Implementation-with-Pytorch: This covers the process of designing multiple models and t

This covers the process of designing multiple models and tuning hyperparameters, starting with loading datasets using the library, to broaden the understanding of the model itself and the factors t...

github.com