이진분류(Binary Classification)
- 이진 분류 문제 처리 모델의 두가지 방법
1. positive(1)일 확률을 출력하도록 구현
- output layer: units=1, activation='sigmoid'
- loss: binary_crossentropy
2. negative(0)일 확률과 positive(1)일 확률을 출력하도록 구현 => 다중분류 처리 방식으로 해결
- output layer: units=2, activation='softmax', y(정답)은 one hot encoding 처리
- loss: categorical_crossentropy
- 위스콘신 대학교에서 제공한 종양의 악성/양성여부 분류를 위한 데이터셋
- Feature
- 종양에 대한 다양한 측정값들 - Target의 class
- 0 - malignant(악성종양)
- 1 - benign(양성종양)
import
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
device
device = "cuda" if torch.cuda.is_available() else "cpu"
device
Dataset, DataLoader 생성
X, y 데이터 불러오기
X, y = load_breast_cancer(return_X_y=True)
print(type(X), type(y))
print(X.shape, y.shape)
print(np.unique(y)) # y값 -> 0, 1 이진분류
# y shape을 2차원으로 변경 ==> 모델 출력 shape과 맞춰준다.
# (batch_size, 1)
y = y.reshape(-1, 1)
y.shape
학습/테스트 셋 분리
# train/test set 분리
X_train, X_test, y_train, y_test = train_test_split(X, y # 나눌 대상 X,y
,test_size=0.25 # 나눌 비율
,stratify=y # class 별 비율을 맞춰서 나눔.
)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
전처리
# 전처리 - Feature Scaling (컬럼들의 scale(척도)를 맞춘다.)
# StandardScaler => 평균:0, 표준편차:1 을 기준으로 맞춤
scaler = StandardScaler()
# scaler.fit(X_train)
# X_train_scaled = scaler.transform(X_train)
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # Trainset으로 fit한 scaler를 이용해 변환.
ndarray => Tensor 변환 => Dataset을 구성 => DataLoader 구성
Tensor 변환
# ndarray => Tensor 변환 => Dataset을 구성 => DataLoader 구성
# ndarray => torch.Tensor
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을 구성
# Dataset 생성 => 메모리의 Tensor를 Dataset으로 생성 => TensorDataset
trainset = TensorDataset(X_train_tensor, y_train_tensor)
testset = TensorDataset(X_test_tensor, y_test_tensor)
DataLoader 구성
# DataLoader
train_loader = DataLoader(trainset, batch_size=200, shuffle=True, drop_last=True)
test_loader = DataLoader(testset, batch_size=len(testset))
Model 클래스 정의
class BCModel(nn.Module):
def __init__(self):
super().__init__()
self.lr1 = nn.Linear(30, 32)
self.lr2 = nn.Linear(32, 8)
# 출력 Layer: 이진분류 - positive의 확률 값 한개를 출력. out_features=1
self.lr3 = nn.Linear(8, 1)
def forward(self, X):
# X (입력) shape: (batchsize, 30)
# out = self.lr1(X)
# out = nn.ReLU()(out)
out = nn.ReLU()(self.lr1(X)) # 주석처리한 위 두 줄 -> 한줄로
out = nn.ReLU()(self.lr2(out))
# 이진분류 출력값 처리 -> Linear()는 한개의 값을 출력 => 확률값으로 변경 => Sigmoid/Logistic 함수를 Activation 함수로 사용
out = self.lr3(out)
out = nn.Sigmoid()(out)
return out
model = BCModel()
tmp_x = torch.ones(5, 30)
print(tmp_x.shape)
tmp_y = model(tmp_x)
tmp_y # 1(양성-positive)일 확률
Train(학습/훈련)
- 다중분류 파트의 가장 좋은 성능을 내는 모델을 저장하는 코드 참고
- 모델명과 변수명들을 수정하여 구현
import time
# 하이퍼파라미터
LR = 0.001
N_EPOCH = 1000
# 모델 생성
model = BCModel().to(device)
# loss 함수
loss_fn = nn.BCELoss() # binary cross entropy loss
# optimizer
optimizer = torch.optim.Adam(model.parameters(), lr = LR)
s = time.time()
#########################
# 에폭별 검증 -> train loss, validation loss, validation accuracy
# 조기종료(Early Stopping) - 성능 개선이 안되면 학습을 중단
# 가장 좋은 성능을 내는 에폭의 모델을 저장.
###### 모델 저장을 위한변수
# 학습 중 가장 좋은 성능 평가지표를 저장. 현 epoch의 지표가 이 변수값보다 좋으면 저장
# 평가지표: validation loss
best_score = torch.inf
save_model_path = "models/BCModel_best_model.pth"
###### 조기 종료를 위한 변수: 특정 epoch동안 성능 개선이 없으면 학습을 중단
patience = 5 # 성능이 개선 될지를 기다릴 epoch 수. patience 번 만큼 개선이 안되면 중단.(보통 10이상 지정)
trigger_cnt = 0 # 성능 개선을 몇번 째 기다리는 지 정할 변수. patience==trigger_cnt : 중단
# train
## 각 에폭별 학습이 끝나고 모델 평가한 값을 저장.
train_loss_list = []
valid_loss_list = []
valid_acc_list = [] # test set의 정확도 검증 결과 => 전체데이터 중 맞은데이터의 개수
s = time.time()
for epoch in range(N_EPOCH):
######### train
model.train()
train_loss = 0.0 # 현재 epoch의 train set의 loss
for X_train, y_train in train_loader:
# 1. device로 옮기기. model과 같은 device로 옮긴다.
X_train, y_train = X_train.to(device), y_train.to(device)
# 2. 예측 - 순전파
pred_train = model(X_train)
# 3. Loss 계산
loss = loss_fn(pred_train, y_train) # (예측, 정답)
# 4 모델 파라미터 업데이트
## 4-1 gradient 초기화
optimizer.zero_grad()
## 4-2 grad 계산 - (오차) 역전파
loss.backward()
## 4-3 파라미터 업데이트
optimizer.step()
# train loss를 누적
train_loss += loss.item()
# 1에폭 학습 종료 => train_loss의 평균을 list에 저장.
train_loss /= len(train_loader) # 누적_train_loss/step수
train_loss_list.append(train_loss)
######### validation
model.eval()
valid_loss = 0.0 # 현재 epoch의 validation loss 저장할 변수
valid_acc = 0.0 # 현재 epoch의 validation accuracy(정확도)를 저장할 변수
### 정확도: 맞은것의 개수 / 전체 개수
with torch.no_grad(): # 도함수 구할 필요가 없으므로 no grad context manager에서 실행.
for X_valid, y_valid in test_loader:
# 1. device로 옮기기
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
# 2. 예측
pred_valid = model(X_valid) # class별 정답일 가능성을 출력 (batch, 10)
pred_label = pred_valid.argmax(dim=-1) # 정답 class를 조회. (pred_valid에서 가장 큰값을 가진 index)
# 3. 평가
## 3.1 loss 계산
loss_valid = loss_fn(pred_valid, y_valid) ## loss_fn() batch만큼 평균을 계산.
valid_loss += loss_valid.item()
## 3.2 정확도 계산
valid_acc += torch.sum(pred_label == y_valid).item()
# 한 epoch에 대한 평가 완료 => valid_loss_list, valid_acc_list에 추가
valid_loss /= len(test_loader) # step수로 나눠서 평균을 계산
valid_acc /= len(test_loader.dataset) # testset의 총 데이터 개수로 나눔.
valid_loss_list.append(valid_loss)
valid_acc_list.append(valid_acc)
print(f"[{epoch+1:02d}/{N_EPOCH}] train loss: {train_loss} valid loss: {valid_loss} valid acc: {valid_acc}")
##################################
# 조기종료여부, 모델 저장 처리
# 저장: 현 epoch valid_loss 가 best_score 보다 개선된 경우 저장(작으면 개선)
#################################
if valid_loss < best_score: # 성능이 개선된 경우.
#저장 로그 출력
print(f"====> 모델저장: {epoch+1} Epoch - 이전 valid_loss: {best_score}, 현재 valid_loss: {valid_loss}")
# best_score교체
best_score = valid_loss
# 저장
torch.save(model, save_model_path)
# trigger_cnt 를 0으로 초기화
trigger_cnt = 0
else: # 성능개선이 안된경우.
# trigger_cnt를 1 증가
trigger_cnt += 1
if patience == trigger_cnt: # patience 만큼 대기 ==> 조기 종료
#로그
print(f"=====> {epoch+1} Epoch에서 조기종료-{best_score}에서 개선 안됨")
break
e = time.time()
print(f"학습시간: {e-s}초")
코드 실행
plt.plot(train_loss_list, label="train")
plt.plot(valid_loss_list, label="validation")
plt.legend()
plt.ylim(0, 0.1)
plt.show()

- 결과를 보면 validation_loss의 값이 상승할때 쯤에 끝난다.
- 이는 과대적합이 발생하기전 학습을 종료했기에 나타나는 현상이다.
모델 Train 결과 확인
저장한 모델을 불러온다.
best_model = torch.load(save_model_path)
- 모델이 학습한 데이터는 전처리 된 것.(Standard Scaling)
- 예측(추론) 할 데이터도 같은 전처리를 해야한다.
X_test_tensor.shape
예측값
pred_new = best_model(X_test_tensor)
pred_new.shape
pred_new[:5] # positive(1)일 확률
예측값의 결과 (positive인지 아닌지)
# 확률-> class index
pred_new_label = (pred_new > 0.5).type(torch.int32)
pred_new_label[:5]
실제값의 결과
y_test_tensor[:5]
정리
공통
- input layer(첫번째) : in_features - 입력데이터의 feature(속성)개수에 맞춰준다.
- hidden layer -> 사용자가 경험적(감-art)으로 정한다. Linear -> feature수를 줄여나간다.(핵심특성들을 추출해나가는 과정의 개념)
문제 유형별
회귀
- output layer의 출력 unit개수(out_features): 정답의 개수
- 집값: 1, (아파트가격, 단독가격, 빌라가격): 3 => y의 개수 - 출력의 activation 함수: Default-None, 값의 범위가 설정되어 있고 그 범위의 값을 출력하는 함수가 있을 경우 사용
- 0 ~ 1: logistic(Sigmoid), -1 ~ 1: hyperbolic tangent(Tanh) - loss함수: MSELoss
- 평가지표: MSE, RMSE, R square(R**2) - 평균으로 예측하는것 대비 얼마나 성능이 좋은지에 대한 점수.
다중분류
- output layer의 출력 unit개수 지정 - 정답 class(고유값)의 개수
- 출력의 activation 함수: 클래스별 확률을 출력 --> Softmax
- loss함수: categorical crossentropy
- pytorch 함수
- CrossEntropyLoss = NLLLoss + LogSoftmax
- Softmax와 one hot encoding 둘 다 안되있을 경우 학습시 CrossEntropyLoss 사용
- NLLLoss => 정답(y)에 One Hot Encoding 처리후 categorical cross entropy 계산
- Model의 출력 Layer에 Softmax를 적용. 학습데이터셋의 정답(y)이 one hot encoding 처리가 안되있 을 경우 학습시 NLLLoss 사용
- LogSoftmax => 모델 추정결과에 Softmax를 처리한 후 categorical cross entropy 계산
- 학습데이터셋의 정답(y)가 one hot encoding되어 있고 Model 출력값에 Softmax가 적용이 안되있을 경우 LogSoftmax사용
이진분류
- output layer의 출력 unit개수 - 1개 (positive일 확률)
- 출력의 activation함수: logistic(Sigmoid) 함수-> 0 ~ 1
- loss함수: Binary crossentropy
- pytorch 함수
- BCELoss
분류의 평가지표
- 정확도(Accuracy) - 맞은것의개수/전체개수
- 재현율/민감도(Recall) - positive중에 맞은것의개수/positive의 개수 (이진분류평가지표)
- 정답이 positive인것 중 맞은 비율 - 정밀도(Precision) - 모델이 positive로 예측한것 중 맞은것의개수/모델이 positive로 예측한 전체 개수 (이진분류평가지표)
- 모델이 positive라고 예측한것 중 맞은 것의 비율
'Data_Analysis_Track_33 > Python' 카테고리의 다른 글
| Python_Deeplearning_pytorch_07(CNN_Model 정의) (0) | 2023.10.30 |
|---|---|
| Python_Deeplearning_pytorch_06(딥러닝모델_성능개선) (0) | 2023.10.25 |
| Python_Deeplearning_pytorch_05(모델저장_문제 유형별 모델 생성(회귀, 다중분류)) (1) | 2023.10.23 |
| Python_Deeplearning_pytorch_04(Dataset 과 DataLoader) (0) | 2023.10.19 |
| Python_Deeplearning_pytorch_03(딥러닝-MLP구현) (0) | 2023.10.17 |