일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- VGGNet
- Python
- 경사하강법
- RNN
- Support Vector Machine
- NER
- Logistic Regression
- Ann
- Generative model
- cross domain
- Clustering
- 군집화
- 자기조직화지도
- SOMs
- LSTM
- AI 윤리
- NMF
- tensorflow
- Attention
- Gradient Descent
- MLOps
- Transfer Learning
- BERT
- nlp
- Binary classification
- gaze estimation
- ResNet
- textmining
- stemming
- TFX
- Today
- Total
juooo1117
Binary Classifier Modeling(Scratch) - Breast Cancer dataset 본문
Binary Classifier Modeling(Scratch) - Breast Cancer dataset
Hyo__ni 2023. 10. 25. 17:20Binary Classification
이진분류(Binary Classification)란 규칙에 따라 입력된 값을 두 그룹(category)으로 분류하는 작업을 의미한다. 즉, 구분하려는 결과가 'True or False' 또는 'Group A or Group B'로 나누는 경우를 말한다. 분류한 결과가 맞다면 1(T, Group A에 포함)을 반환하며, 아니라면 0(F, Group A에 포함되지 않음)을 반환하는 이분화 작업을 수행한다. 두가지 결과로 분류하기 때문에, 논리 회귀(Logistic Regression) 또는 논리 분류(Logistic Classification)라고도 명칭한다.
만약, 분류해야하는 그룹이 3종류 이상이라면 다중분류(Multiclass Classification)를 의미한다.
모델의 관측치는 0 ~ 1 범위로 예측된 값을 반환한다. (pred value가 0~1 범위의 값을 갖게하기 위해서 Activation Function은 Sigmoid Function 을 적용) 따라서 두 값중 어디에 포함될지를 선택하는 임곗값(Threshold Value)을 설정해 주어야 한다.
Binary Cross Entropy (이진 교차 엔트로피)
이진 분류(Binary Classification)에서 사용되는 sigmoid function의 예측값은 0~1의 범위이다.
일반적으로 cost function을 MSE(Mean Squared Error)를 사용하여 오차를 계산하지만, MSE를 이진분류에 적용하면 극솟값(local minimum)이 여기저기에 발생하게 된다. 따라서 이런 경우를 방지하고자 Binary Cross Entropy를 cost function으로 사용한다.
Binary Cross Entropy는 log function을 이용해서 cost function을 구현한다. log function은 로그의 진수가 0에 가까워질수록 무한대로 발산한다. 기존의 MSE 방식은 명확하게 불일치 하는 경우에도 높은 손실(loss)값을 반환하지 않지만, log function의 경우 불일치하는 비중이 높을수록 높은 손실(loss) 값을 반환한다. log function의 경우, 한 쪽 방향으로는 무한대로 이동하며 다른 방향으로는 0에 가까워지기 때문에 기울기=0이 되는 지점을 찾기 위해서 두 가지 log function을 하나로 합쳐서 사용한다.
Practice (Wisconsin Breast Cancer dataset)
Binary Classification
- Target class : << 0(악성종양, malignant) >> or << 1(양성종양, benign) >>
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
import numpy as np
import matplotlib.pyplot as plt
1. Data Load & Pre-processing
- breast_cancer data를 load한다.
- X.shape → (569, 30), y.shape →(569,) 의 size를 가지고 있는 것을 확인하였다.
- y 값은 [0 1] 이므로 모델이 input data를 넣어서 결과로써 분류할 category 값은 '0' 또는 '1' 이다.
X, y = load_breast_cancer(return_X_y=True)
print(type(X), type(y))
print(X.shape, y.shape)
print(np.unique(y)) # 분류할 category는 '0' or '1'
- 모델의 출력(output) shape과 맞춰주기 위해서 y를 2차원으로 변경한다. 즉, (569, ) -> (569,1) 로 변경!!
- train set, test set을 분리해 준다.
y = y.reshape(-1, 1) # (batch_size, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.25,
stratify=y) # class 비율 맞춰서 나눔
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
- column의 scale을 맞추는 feature scailing 을 적용한다. standardScaler을 이용해서 <평균: 0, 표준편차: 1>을 기준으로 맞춘다.
- ndarray를 tensor로 변환해서 dataset을 만들어 dataloader를 구성한다.
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # trainset으로 fitting한 scaler 이용해서 변환
# ndarray => tenosr 변환
X_train_tensor = torch.tensor(X_train_scaled, dtype=torch.float32)
X_test_tensor = torch.tensor(X_test_scaled, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32)
y_test_tensor = torch.tensor(y_test, dtype=torch.float32)
# dataset 생성
trainset = TensorDataset(X_train_tensor, y_train_tensor)
testset = TensorDataset(X_test_tensor, y_test_tensor)
# dataloader 생성
train_loader = DataLoader(trainset, batch_size=200, shuffle=True, drop_last=True)
test_loader = DataLoader(testset, batch_size=len(testset))
2. Model Class Define
먼저 모델 클래스를 정의해 준다. 결과가 2개 뿐인 binary classifier 이므로, final output feature가 1개로 나오도록 output layer를 구성한다. 각 layer 사이에는 ReLU Function을 넣어주고, 마지막 layer에서 빠져나온 뒤에는 sigmoid function을 거쳐서 확률 값으로 출력되게 구성하였다.
class BCModel(nn.Module):
def __init__(self):
super().__init__()
self.lr1 = nn.Linear(30, 32)
self.lr2 = nn.Linear(32, 8)
self.lr3 = nn.Linear(8, 1)
def forward(self, X):
out = nn.ReLU()(self.lr1(X))
out = nn.ReLU()(self.lr2(out))
out = self.lr3(out)
out = nn.Sigmoid()(out)
return out
3. Training
- 학습을 위해 우선 hyper parameters를 정의해 준다. learning rate는 0.001, epoch은 1000으로 설정해 주었다.
- binary classification model 이기 때문에, loss function은 binary cross entropy 로 지정해주었다.
LR = 0.001
N_EPOCH = 1000
model = BCModel().to(device) # model define
loss_fn = nn.BCELoss() # loss function - binary cross entropy loss
optimizer = torch.optim.Adam(model.parameters(), lr=LR) # optimizer
- early stopping 기능을 추가하여, validation loss 기준으로 만약 성능개선이 안되면 학습을 중단시키고 가장 좋은 성능을 낸 epoch의 model을 저장하도록 구현하였다.
- pred_label = (pred_val > 0.5).type(torch.int32) : 분류하는 임계값을 0.5로 부여하였다. .type(torch.int32)를 통해서 0 또는 1로 값을 반환하여 'pred_label'에 저장한다.
- validation에서 best model은 92 epoch에서 나왔으며, 결과는 <Val loss: 0.10787159204483032>, <Val accuracy: 0.965034965034965> 이었다.
import time
train_loss_list, val_loss_list, val_accuracy_list = [], [], []
best_score = torch.inf # 'valid loss' save
save_bcmodel_path = 'models/bc_best_model.pth' # 'best model' save
patience = 20 # 성능이 개선될 때까지 20epoch 기다리겠다.
trigger_cnt = 0 # 성능이 개선될 때까지 현재 몇 번째 기다렸는지. (성능이 개선될 때마다 0으로 초기화!)
sec_s = time.time()
for epoch in range(N_EPOCH):
### Train ###
model.train()
train_loss = 0.0
for X_train, y_train in train_loader:
X_train, y_train = X_train.to(device), y_train.to(device)
pred = model(X_train)
loss = loss_fn(pred, y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss_list.append(train_loss / len(train_loader))
### Validation ###
model.eval()
val_loss = 0.0
val_acc = 0.0
with torch.no_grad():
for X_valid, y_valid in test_loader:
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
pred_val = model(X_valid) # 확률값 1개만 출력됨!
val_loss += loss_fn(pred_val, y_valid).item()
pred_label = (pred_val > 0.5).type(torch.int32) # pred_label 값은 0 or 1
val_acc += torch.sum(pred_label == y_valid).item() # pred 값이 정답이랑 맞으면 val_acc로!
val_loss_list.append(val_loss / len(test_loader))
val_accuracy_list.append(val_acc / len(test_loader.dataset))
print(f'[{epoch+1: 2d}/{N_EPOCH: 2d}] Train loss: {train_loss_list[-1]}, Val loss: {val_loss_list[-1]}, Val accuracy: {val_accuracy_list[-1]}')
### model save & early stopping decide
if val_loss < best_score:
print(f'===> {epoch+1} epoch에서 model save. 이전 score: {best_score}, 현재 score: {val_loss}')
torch.save(model, save_bcmodel_path)
best_score = val_loss
trigger_cnt = 0
else:
trigger_cnt += 1
if patience == trigger_cnt:
print(f'***** Early Stop: {epoch+1}에서 종료! *****')
break
sec_e = time.time()
print("Time: ", (sec_e - sec_s))
- 모델의 validation 결과를 저장하여 확인해 보자. (train loss, valid loss 의 epoch 별 변화의 흐름 시각화)
plt.plot(train_loss_list, label="Train Loss")
plt.plot(val_loss_list, label="Validation Loss")
plt.plot(val_accuracy_list, label="Accuracy")
plt.xlabel("EPOCH")
plt.ylabel("LOSS & Accuracy")
plt.legend()
plt.show()
4. Model Evaluation
학습이 완료된 'best_model'에 값을 넣고 예측결과를 확인해 보자
best_model = torch.load(save_bcmodel_path)
X_test_tensor.shape # size: [143, 30]
pred_new = best_model(X_test_tensor)
pred_new.shape # size: [143, 1] (즉, 예측값 1개만 가지게 됨)
- pred_new 의 값은 확률값이기 때문에, 확률을 class index('0' or '1')로 변환시켜서 pred_new_label에 저장해 준다.
- pred_new_label 의 값과, y_test_tensor 의 같은 index 값이 동일하면 모델에서 한 예측이 맞은 것이다.
# probability -> class index로 변환
pred_new_label = (pred_new > 0.5).type(torch.int32)
pred_new_label[:10]
# pred_new_label 과 같은 index의 값이 동일하면 예측이 맞은 것! (값이 동일하다면, 모든 예측이 다 맞음)
y_test_tensor[:10]
[practice] - Github
https://github.com/juooo1117/practice_AI_Learning/blob/main/Binary_Classification_scratch.ipynb
'Artificial Intelligence' 카테고리의 다른 글
Text pre-processing (cranfieldDocs) (0) | 2023.10.27 |
---|---|
NLP(Natural Language Processing) (0) | 2023.10.26 |
Topic Model (MM, PLSA, LDA) (1) | 2023.10.24 |
MLP Modeling - MNIST image classification (1) | 2023.10.19 |
Clustering (k-means, silhouette coefficient) (0) | 2023.10.18 |