[기계학습] IRIS 데이터를 분류하는 분류기를 만들어보자(코드 실습)
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)에 사용하여 내가 설계한 모델의 정확도를 측정한다.
모델은 총 두개를 사용할 것이다.
- 일반 3계층의 선형변환 모델
- 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개를 정의하였다.
- 단순 선형변환 모델(3 계층)
- 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()
plt.plot(t_acc_his, label="train")
plt.plot(v_acc_his, label="valid")
plt.legend()
plt.title("Accuracy")
plt.show()
최종 학습 종료 후
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()
plt.plot(t_acc_his, label="train")
plt.plot(v_acc_his, label="valid")
plt.legend()
plt.show()
성능이 모델 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