ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [기계학습] IRIS 데이터를 분류하는 분류기를 만들어보자(코드 실습)
    코딩(Coding)/기계학습 2022. 2. 8. 14:06
    728x90

    IRIS Classification

    오늘은 흔히 사용되는 IRIS 데이터셋을 가지고 이전까지의 포스팅을 복습하는 글을 쓰려고한다.

     

    데이터 설명

    붓꽃 데이터

    총 150개의 데이터로 이루어져있고
    Featrue는 4개, Label 1개로 (150, 5)의 Shape을 가지는 데이터이다.

    Sepal Length 꽃 받침의 길이 정보(cm)
    Sepal Width 꽃 받침의 너비 정보(cm)
    Petal Length 꽃잎의 길이 정보(cm)
    Petal Width 꽃잎의 너비 정보(cm)
    Species 꽃의 종류 정보(Setosa / Versicolor/Virgincia) 3종류

    CSV 형식으로도 다운 받을 수 있지만, Scikit-Learn에서 제공하는 "sklearn"패키지에서 iris 데이터를 불러올 수 있다.

     

    실습의 형식은 전체 150개의 데이터 중에서 85%비율은 학습으로 사용하고, 나머지 15%의 비율은 검증(Test)에 사용하여 내가 설계한 모델의 정확도를 측정한다.

    모델은 총 두개를 사용할 것이다.

    1. 일반 3계층의 선형변환 모델
    2. Self-Attetion 적용 모델

    두개의 모델을 설계하고 학습하여 정확도를 척도로 성능을 비교할 것
    (물론 Self-Attention이 월등히 높을 것... ㅎㅎ)

     

    필요 패키지 import

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    from torch.utils.data import Dataset, DataLoader
    
    from tqdm import tqdm, notebook
    
    DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    print(torch.__version__)
    print(torch.cuda.is_available())
    print(DEVICE)
    1.10.1
    True
    cuda

     

    데이터 살펴보기

    from sklearn.datasets import load_iris
    
    data = load_iris()
    data.keys()
    dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])

    scikit-learn에서 제공하는 형태는 딕셔너리 형태로 여기서 학습에 사용할 'Data'와 'target', 'target_names'를 사용한다.

    # Feature와 Label로 구분
    X = data['data']
    Y = data['target']
    label_name = data['target_names']
    n_classes = max(Y)+1
    
    print(X[:5])
    print(Y[:5])
    print(label_name)
    print(n_classes)
    [[5.1 3.5 1.4 0.2]
     [4.9 3.  1.4 0.2]
     [4.7 3.2 1.3 0.2]
     [4.6 3.1 1.5 0.2]
     [5.  3.6 1.4 0.2]]
    [0 0 0 0 0]
    ['setosa' 'versicolor' 'virginica']
    3

    Feature와 Label을 받아와 확인해보았다.

    # Y값 one_Hot Encoding
    n = np.unique(Y, axis=0).shape[0]
    Y = np.eye(n)[Y]
    
    print(Y.shape)
    (150, 3)

    Label을 모델이 구분할 수 있도록 one-hot 인코딩을 해준다.
    numpy의 unique()와 eye() 메소드를 사용하였다.

    # Train, Valid Dataset 분리
    from sklearn.model_selection import train_test_split
    train_x, valid_x, train_y, valid_y = train_test_split(X, Y, stratify = Y, random_state = 17, test_size = 0.15)
    print("Train Feature Shape : {}".format(train_x.shape))
    print("Train Label Shape : {}".format(train_y.shape))
    print("Valid Feature Shape : {}".format(valid_x.shape))
    print("Valid Label Shape : {}".format(valid_y.shape))
    Train Feature Shape : (127, 4)
    Train Label Shape : (127, 3)
    Valid Feature Shape : (23, 4)
    Valid Label Shape : (23, 3)

    총 150개의 데이터를 sklearn 패키지의 train_test_split() 메소드를 통해 85:15 비율로 Split하였다.
    Shape은 출력과 같다.

    # array to Tensor
    train_x = torch.tensor(train_x)
    train_y = torch.tensor(train_y)
    valid_x = torch.tensor(valid_x)
    valid_y = torch.tensor(valid_y)
    
    print("Train Feature Shape : {}".format(train_x.shape))
    print("Train Label Shape : {}".format(train_y.shape))
    print("Valid Feature Shape : {}".format(valid_x.shape))
    print("Valid Label Shape : {}".format(valid_y.shape))
    Train Feature Shape : torch.Size([127, 4])
    Train Label Shape : torch.Size([127, 3])
    Valid Feature Shape : torch.Size([23, 4])
    Valid Label Shape : torch.Size([23, 3])

    실습에서는 Dataset과 DataLoader를 사용할 예정이지만, 위 처럼 바로 Tensor로 바꿔서 바로 모델에 입력으로 넣을 수 있다.
    (데이터의 크기가 크지 않기 때문에 가능하다.)

     

    Dataset 정의

    class MyDataset(Dataset):
        def __init__(self, x_data, y_data):
            self.x = x_data
            self.y = y_data
    
        def __len__(self):
            return len(self.x)
    
        def __getitem__(self, idx):
            xx = self.x[idx].float()
            yy = self.y[idx].float()
            return xx, yy
    
    train = MyDataset(train_x, train_y)
    valid = MyDataset(valid_x, valid_y)
    
    train_loader = DataLoader(train, batch_size = 4, shuffle=True)
    valid_loader = DataLoader(valid, batch_size = 2)
    
    print(train_loader)
    print(valid_loader)
    <torch.utils.data.dataloader.DataLoader object at 0x0000020186E45AC8>
    <torch.utils.data.dataloader.DataLoader object at 0x0000020186E45A48>

    Dataset을 정의하고 생성한 다음 DataLoader에 대입해주었다.

     

    훈련 & 검증 함수 정의

    loss_fn = nn.CrossEntropyLoss()
    
    def calc_acc(X, Y):
        x_val, x_idx = torch.max(X, dim=1)
        y_val, y_idx = torch.max(Y, dim=1)
        return (x_idx == y_idx).sum().item()
    
    def train(EPOCHS, model, train_loader, valid_loader, opt):
        train_loss_history = []
        valid_loss_history = []
        train_acc_history = []
        valid_acc_history = []
        for epoch in range(1, EPOCHS+1):
            model.train()
            train_acc = 0
            print("<<< EPOCH {} >>>".format(epoch))
            for batch_idx, (x,y) in enumerate(notebook.tqdm(train_loader)):
                x, y = x.to(DEVICE), y.to(DEVICE)
    
                output = model(x)                 # 순전파
                loss = loss_fn(output, y)         # 오차 계산
    
                opt.zero_grad()                   # opt내부 값 초기화
                loss.backward()                   # 오차 역전파
                opt.step()                        # 가중치 갱신
    
                train_acc += calc_acc(output, y)
                if batch_idx % 10 == 0 and batch_idx != 0:
                    print("Training : [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t Acc : {:.3f}".format(
                        batch_idx * len(x), 
                        len(train_loader.dataset), 
                        100. * batch_idx / len(train_loader), 
                        loss.item(),
                        train_acc / len(train_loader.dataset)))
            print("\n{} Training : Loss: {:.6f}\t Acc : {:.3f}".format(
                        epoch,  
                        loss.item(),
                        train_acc / len(train_loader.dataset)))
    
            t_loss, t_acc = evaluate(model, valid_loader)
            print("{} Validation : Loss : {:.4f}\t Acc: {:.2f}%\n\n\n".format(epoch, t_loss, t_acc*100.))
    
            train_loss_history.append(loss.item())
            train_acc_history.append(train_acc / len(train_loader.dataset))
    
            valid_loss_history.append(t_loss.item())
            valid_acc_history.append(t_acc)
    
        return train_loss_history, train_acc_history, valid_loss_history, valid_acc_history
    
    def evaluate(model, valid_loader):
        model.eval()
        t_loss = 0
        correct = 0
    
        with torch.no_grad():
            for x, y in notebook.tqdm(valid_loader):
                x, y = x.to(DEVICE), y.to(DEVICE)
    
                output = model(x)
                t_loss += loss_fn(output, y)
    
                correct += calc_acc(output, y)
    
        t_loss /= len(valid_loader)
        t_acc = correct / len(valid_loader.dataset)
        return t_loss, t_acc

    학습 함수와, 검증 함수를 정의하였다.(+ 정확도를 계산하는 calc_acc)
    학습 중간중간에 loss와 acc를 확인하도록 출력문을 넣어주었다.

     

    모델 정의

    모델은 총 2개를 정의하였다.

    1. 단순 선형변환 모델(3 계층)
    2. Self-Attention 적용 모델

    모델 1

    class MyModel1(nn.Module):
        def __init__(self):
            super(MyModel1, self).__init__()
    
            self.fc1 = nn.Linear(4, 16)
            self.fc2 = nn.Linear(16, 8)
            self.fc3 = nn.Linear(8, n_classes)
    
            self.act_fn = nn.ReLU()
    
        def forward(self, x):
            x = self.fc1(x)
            x = self.act_fn(x)
    
            x = self.fc2(x)
            x = self.act_fn(x)
    
            x = self.fc3(x)
            return x

    모델 1은 3개의 선형 Layer를 가진다.
    정말 단순 선형변환 모델이다.

    (batch, 4) -> (batch, 16) -> (batch, 8) -> (batch, 3)
    위 순서대로 선형회귀를 3번 거친다.
    물론 활성화 함수로는 ReLU를 사용하였다.

    모델 2

    class MyModel2(nn.Module):
        def __init__(self):
            super(MyModel2, self).__init__()
    
            self.fc1 = nn.Linear(4, 16*4)
    
            self.Q = nn.Linear(16, 8)
            self.K = nn.Linear(16, 8)
            self.V = nn.Linear(16, 8)
    
            self.fc2 = nn.Linear(32, 8)
            self.fc3 = nn.Linear(8, n_classes)
    
            self.act_fn = nn.ReLU()
            self.softmax = nn.Softmax(dim=-1)
    
        def forward(self, x):
            x = self.fc1(x)
            x = self.act_fn(x)
    
            x = x.view(-1, 4, 16)
    
            q = self.Q(x) # (batch, 4, 8)
            k = self.K(x) # (batch, 4, 8)
            v = self.V(x) # (batch, 4, 8)
    
            score = torch.matmul(q, torch.transpose(k, 1, 2)) # (batch, 4, 16)
            score = self.softmax(score) / np.sqrt(8)         # (batch, 4, 16)
    
            z = torch.matmul(score, v) # (batch, 4, 8)
            z = z.view(-1, 4*8)
    
            x = self.fc2(z)
            x = self.act_fn(x)
    
            x = self.fc3(x)
            return x

    모델 2는 Self-Attention 기법을 사용하였다.
    Self-Attention은 트랜스포머(Transformer) 아키텍쳐에서 주로 사용되는 기법인데, 이는 추후에 포스팅하기로 하겠다.
    모델 1에 비해 복잡하다.

     

    모델 학습 및 성능 검증

    이제 학습을 시키고 성능을 비교해볼 차례이다. 먼저 단순 선형변환 모델을 학습시키고 성능을 확인해 보자

    단순 선형회귀

    model = MyModel1().to(DEVICE)
    opt = optim.Adam(model.parameters())
    
    print("Model :",model)
    print("model's number of Parameters: ", sum([p.numel() for p in model.parameters()]))
    Model : MyModel1(
      (fc1): Linear(in_features=4, out_features=16, bias=True)
      (fc2): Linear(in_features=16, out_features=8, bias=True)
      (fc3): Linear(in_features=8, out_features=3, bias=True)
      (act_fn): ReLU()
    )
    model's number of Parameters:  243

    사용되는 가중치의 개수는 243개이다.
    학습 회수는 10번이다.

    t_loss_his, t_acc_his, v_loss_his, v_acc_his = train(EPOCHS = 10, model = model, train_loader = train_loader, valid_loader = valid_loader, opt = opt)
    <<< EPOCH 1 >>>
    Training : [40/127 (31%)]    Loss: 1.046869     Acc : 0.189
    Training : [80/127 (62%)]    Loss: 1.130223     Acc : 0.276
    Training : [120/127 (94%)]    Loss: 1.117985     Acc : 0.370
    
    1 Training : Loss: 1.069064     Acc : 0.378
    1 Validation : Loss : 1.0696     Acc: 30.43%
    
    <<< EPOCH 2 >>>
    Training : [40/127 (31%)]    Loss: 1.101946     Acc : 0.094
    Training : [80/127 (62%)]    Loss: 1.070963     Acc : 0.205
    Training : [120/127 (94%)]    Loss: 0.989257     Acc : 0.323
    
    2 Training : Loss: 0.992205     Acc : 0.339
    2 Validation : Loss : 1.0526     Acc: 30.43%
    
    . . . . .
    . . . . .
    중간  생략
    . . . . .
    . . . . .
    
    <<< EPOCH 9 >>>
    Training : [40/127 (31%)]    Loss: 0.802042     Acc : 0.205
    Training : [80/127 (62%)]    Loss: 0.774793     Acc : 0.457
    Training : [120/127 (94%)]    Loss: 0.491868     Acc : 0.661
    
    9 Training : Loss: 0.822680     Acc : 0.669
    9 Validation : Loss : 0.6623     Acc: 65.22%
    
    <<< EPOCH 10 >>>
    Training : [40/127 (31%)]    Loss: 0.847679     Acc : 0.205
    Training : [80/127 (62%)]    Loss: 0.639355     Acc : 0.417
    Training : [120/127 (94%)]    Loss: 0.595597     Acc : 0.654
    
    10 Training : Loss: 0.383359     Acc : 0.677
    10 Validation : Loss : 0.5935     Acc: 65.22%
    plt.plot(t_loss_his, label="train")
    plt.plot(v_loss_his, label="valid")
    plt.legend()
    plt.title("Loss")
    plt.show()

    output_20_0

    plt.plot(t_acc_his, label="train")
    plt.plot(v_acc_his, label="valid")
    plt.legend()
    plt.title("Accuracy")
    plt.show()

    output_21_0

    최종 학습 종료 후

    Training : Loss: 0.383359 Acc : 0.677
    Validation : Loss : 0.5935 Acc: 65.22%

    정확도가 대략 65~67%에 달한다. 3개중에 2개는 맞춘다는 소리인데....

    셀프 어텐션 적용 모델

    model = MyModel2().to(DEVICE)
    opt = optim.Adam(model.parameters())
    
    print("Model :",model)
    print("model's number of Parameters: ", sum([p.numel() for p in model.parameters()]))
    Model : MyModel2(
      (fc1): Linear(in_features=4, out_features=64, bias=True)
      (Q): Linear(in_features=16, out_features=8, bias=True)
      (K): Linear(in_features=16, out_features=8, bias=True)
      (V): Linear(in_features=16, out_features=8, bias=True)
      (fc2): Linear(in_features=32, out_features=8, bias=True)
      (fc3): Linear(in_features=8, out_features=3, bias=True)
      (act_fn): ReLU()
      (softmax): Softmax(dim=-1)
    )
    model's number of Parameters:  1019

    사용되는 가중치는 1019개로 모델 1에 비해 대략 5배 가량많다.

    t_loss_his, t_acc_his, v_loss_his, v_acc_his = train(EPOCHS = 10, model = model, train_loader = train_loader, valid_loader = valid_loader, opt = opt)
    <<< EPOCH 1 >>>
    Training : [40/127 (31%)]    Loss: 1.113500     Acc : 0.102
    Training : [80/127 (62%)]    Loss: 1.031586     Acc : 0.189
    Training : [120/127 (94%)]    Loss: 1.065747     Acc : 0.323
    
    1 Training : Loss: 0.859659     Acc : 0.339
    1 Validation : Loss : 1.0244     Acc: 30.43%
    
    <<< EPOCH 2 >>>
    Training : [40/127 (31%)]    Loss: 1.127708     Acc : 0.142
    Training : [80/127 (62%)]    Loss: 0.841290     Acc : 0.276
    Training : [120/127 (94%)]    Loss: 1.086602     Acc : 0.472
    
    2 Training : Loss: 0.887653     Acc : 0.496
    2 Validation : Loss : 0.8946     Acc: 65.22%
    
    . . . . .
    . . . . .
    중간  생략
    . . . . .
    . . . . .
    
    <<< EPOCH 9 >>>
    Training : [40/127 (31%)]    Loss: 0.060780     Acc : 0.315
    Training : [80/127 (62%)]    Loss: 0.543211     Acc : 0.614
    Training : [120/127 (94%)]    Loss: 0.054991     Acc : 0.921
    
    9 Training : Loss: 0.023854     Acc : 0.945
    9 Validation : Loss : 0.0623     Acc: 100.00%
    
    <<< EPOCH 10 >>>
    Training : [40/127 (31%)]    Loss: 0.153605     Acc : 0.315
    Training : [80/127 (62%)]    Loss: 0.285837     Acc : 0.598
    Training : [120/127 (94%)]    Loss: 0.031138     Acc : 0.898
    
    10 Training : Loss: 0.043692     Acc : 0.921
    10 Validation : Loss : 0.0581     Acc: 100.00%
    plt.plot(t_loss_his, label="train")
    plt.plot(v_loss_his, label="valid")
    plt.legend()
    plt.show()

    output_25_0

    plt.plot(t_acc_his, label="train")
    plt.plot(v_acc_his, label="valid")
    plt.legend()
    plt.show()

    output_26_0

    성능이 모델 1에 비해 월등히 높다.
    Epoch 2부터 모델 1과 비슷한 성능을 자랑한다.


    이유를 간단히 설명하자면, Self-Attention은 Feature간의 중요도 비율을 스스로 학습하여 적용한다.
    즉, 주어진 데이터 중 4개의 Feature중 어느 Feature에 Attention할것인지를 데이터 속에서 스스로 결정한다.
    다행이 150개의 적은 데이터로도 잘 먹혔고 성능으로 결과가 나온것 같다.

     


    오늘 포스팅은 여기까지하고, 다음 포스팅은 이제 CNN을 설명해보고자 한다.
    CNN은 총 2 part로 포스팅할 예정이다.(이론, 실습)

    추가적으로 전체 코드는 아래 링크에서 확인할 수 있습니다.
    https://github.com/JoSangYeon/Machine_Learning_Project/blob/master/IRIS_Classification/IRIS_Classification.ipynb

    728x90

    댓글

Designed by black7375.