본문 바로가기

Data_Analysis_Track_33/Python

Python_Deeplearning_pytorch_05-2(모델저장_문제 유형별 모델 생성(이진분류), 정리)

이진분류(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라고 예측한것 중 맞은 것의 비율