PyTorch [Tabular] — 다중 클래스 분류

이 블로그 게시물은 PyTorch를 사용하여 테이블 형식 데이터에 대한 다중 클래스 분류 구현을 안내합니다.

신경망 훈련 방법 [이미지 [0]]

Kaggle에서 제공 되는 와인 데이터 세트를 사용합니다. 이 데이터 세트에는 12 개의 열이 있으며 처음 11 개는 특성이고 마지막 열은 대상 열입니다. 데이터 세트에는 1599 개의 행이 있습니다.

라이브러리 가져 오기

tqdm교육 및 테스트 루프에 대한 진행률 표시 줄을 활성화 하는 데 사용하고 있습니다.

import numpy as np
import pandas as pd
import seaborn as sns
from tqdm.notebook import tqdm
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler

from sklearn.preprocessing import MinMaxScaler    
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report

df = pd.read_csv("data/tabular/classification/winequality-red.csv")
df.head()
Input data [Image [2]]

데이터를 신경망에 적합하게 만들려면 몇 가지 조정이 필요합니다.

클래스 분배

먼저 출력 행을 플로팅하여 클래스 분포를 관찰합니다. 여기에는 많은 불균형이 있습니다. 클래스 3, 4 및 8에는 샘플 수가 매우 적습니다.

sns.countplot(x = 'quality', data=df)
Class distribution bar plot [Image [3]]

다음으로 출력 레이블이 3에서 8까지임을 알 수 있습니다. PyTorch는 0부터 시작하는 레이블을 지원하므로 변경해야합니다. 즉 , [0, n] 입니다. 0부터 시작하도록 레이블을 다시 매핑해야합니다.

이를 위해라는 사전을 만들고 Pandas 라이브러리 class2idx.replace()메서드를 사용하여 변경해 보겠습니다 . idx2classID를 원래 클래스로 다시 변환하는 라는 역 매핑도 만들어 보겠습니다 .

역 매핑을 생성하기 위해 사전 이해를 생성하고 단순히 키와 값을 반전합니다.

class2idx = {
    3:0,
    4:1,
    5:2,
    6:3,
    7:4,
    8:5
}

idx2class = {v: k for k, v in class2idx.items()}

df['quality'].replace(class2idx, inplace=True)

train_test_splitSklearn을 사용하여 데이터를 훈련, 검증 및 테스트 세트로 분할하려면 입력과 출력을 분리해야합니다.

입력 X은 마지막 열을 제외하고 모두입니다. 출력 y은 마지막 열입니다.

X = df.iloc[:, 0:-1]
y = df.iloc[:, -1]

train-val-test 분할을 생성하기 위해 train_test_split()Sklearn에서 사용할 것입니다.

먼저 데이터를 train + val 및 테스트 세트로 분할합니다. 그런 다음 train + val 세트를 추가로 분할하여 train 및 val 세트를 생성합니다.

클래스 불균형이 있기 때문에 학습, 검증 및 테스트 세트의 모든 출력 클래스를 균등하게 분배하려고합니다. 이를 위해 stratifyfunction 의 옵션을 사용합니다 train_test_split().

# Split into train+val and test
X_trainval, X_test, y_trainval, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=69)

# Split train into train-val
X_train, X_val, y_train, y_val = train_test_split(X_trainval, y_trainval, test_size=0.1, stratify=y_trainval, random_state=21)

신경망에는 (0,1) 범위 사이에있는 데이터가 필요합니다. 우리가 그것을해야하는 이유에 대해 온라인에서 많은 자료를 이용할 수 있습니다.

가치를 확장하기 위해 MinMaxScaler()Sklearn에서 사용합니다 . MinMaxScaler변환은 우리의 경우이다 주어진 범위 (0,1)에 각각 기능을 스케일링함으로써 특징으로한다.

x_scaled = (x-min (x)) / (max (x) –min (x))

on 및을 사용 .fit_transform()하는 X_train동안 on 을 사용 .transform()합니다 .X_valX_test

데이터 유출을 방지하기 위해 기차 세트와 동일한 매개 변수를 사용하여 검증 및 테스트 세트를 확장하려고하기 때문입니다. fit_transform스케일링 값을 계산 .transform하고 계산 된 값만 적용 하면서 적용합니다.

scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_val = scaler.transform(X_val)
X_test = scaler.transform(X_test)
X_train, y_train = np.array(X_train), np.array(y_train)
X_val, y_val = np.array(X_val), np.array(y_val)
X_test, y_test = np.array(X_test), np.array(y_test)

데이터를 학습, 검증 및 테스트 세트로 분할했으면 클래스 분포가 세 세트 모두에서 동일한 지 확인하겠습니다.

이를 위해라는 함수를 만들어 보겠습니다 get_class_distribution(). 이 함수는 obj를 입력으로받습니다 y. y_train, y_val또는 y_test. 함수 내에서 출력 클래스를 키로, 개수를 값으로 포함하는 사전을 초기화합니다. 카운트는 모두 0으로 초기화됩니다.

그런 다음 y객체 를 반복 하고 사전을 업데이트합니다.

def get_class_distribution(obj):
    count_dict = {
        "rating_3": 0,
        "rating_4": 0,
        "rating_5": 0,
        "rating_6": 0,
        "rating_7": 0,
        "rating_8": 0,
    }
    
    for i in obj:
        if i == 0: 
            count_dict['rating_3'] += 1
        elif i == 1: 
            count_dict['rating_4'] += 1
        elif i == 2: 
            count_dict['rating_5'] += 1
        elif i == 3: 
            count_dict['rating_6'] += 1
        elif i == 4: 
            count_dict['rating_7'] += 1  
        elif i == 5: 
            count_dict['rating_8'] += 1              
        else:
            print("Check classes.")
            
    return count_dict

fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(25,7))
# Train
sns.barplot(data = pd.DataFrame.from_dict([get_class_distribution(y_train)]).melt(), x = "variable", y="value", hue="variable",  ax=axes[0]).set_title('Class Distribution in Train Set')
# Validation
sns.barplot(data = pd.DataFrame.from_dict([get_class_distribution(y_val)]).melt(), x = "variable", y="value", hue="variable",  ax=axes[1]).set_title('Class Distribution in Val Set')
# Test
sns.barplot(data = pd.DataFrame.from_dict([get_class_distribution(y_test)]).melt(), x = "variable", y="value", hue="variable",  ax=axes[2]).set_title('Class Distribution in Test Set')
Class distribution in train, val, and test sets [Image [4]]

우리는 이제 우리 모두가 기다리던 것에 도달했습니다!

맞춤 데이터 세트

먼저 사용자 지정 데이터 세트를 정의하겠습니다. 이 데이터 세트는 데이터 로더에서 데이터를 모델로 전달하는 데 사용됩니다.

X와 y를 입력으로 전달하여 데이터 세트를 초기화합니다. X는 a float이고 y는 long.

class ClassifierDataset(Dataset):
    
    def __init__(self, X_data, y_data):
        self.X_data = X_data
        self.y_data = y_data
        
    def __getitem__(self, index):
        return self.X_data[index], self.y_data[index]
        
    def __len__ (self):
        return len(self.X_data)


train_dataset = ClassifierDataset(torch.from_numpy(X_train).float(), torch.from_numpy(y_train).long())
val_dataset = ClassifierDataset(torch.from_numpy(X_val).float(), torch.from_numpy(y_val).long())
test_dataset = ClassifierDataset(torch.from_numpy(X_test).float(), torch.from_numpy(y_test).long())

클래스 불균형이 있기 때문에 계층화 된 분할을 사용하여 학습, 검증 및 테스트 세트를 만듭니다.

도움이 되긴하지만, 우리 모델의 각 미니 배치가 우리의 모든 클래스를 확인하는 것은 아닙니다. 더 적은 수의 값으로 클래스를 오버 샘플링해야합니다. 이를 위해 WeightedRandomSampler.

먼저 target_list모든 출력을 포함하는 라는 목록을 얻습니다 . 이 목록은 텐서로 변환되고 섞입니다.

target_list = []
for _, t in train_dataset:
    target_list.append(t)
    
target_list = torch.tensor(target_list)
target_list = target_list[torch.randperm(len(target_list))]

class_count = [i for i in get_class_distribution(y_train).values()]
class_weights = 1./torch.tensor(class_count, dtype=torch.float)
print(class_weights)

###################### OUTPUT ######################
tensor([0.1429, 0.0263, 0.0020, 0.0022, 0.0070, 0.0714])

class_weights_all = class_weights[target_list]

weighted_sampler = WeightedRandomSampler(
    weights=class_weights_all,
    num_samples=len(class_weights_all),
    replacement=True
)

더 진행하기 전에 아래에서 사용할 몇 가지 매개 변수를 정의하겠습니다.

EPOCHS = 300
BATCH_SIZE = 16
LEARNING_RATE = 0.0007
NUM_FEATURES = len(X.columns)
NUM_CLASSES = 6

이제 데이터 로더를 초기화하겠습니다.

들어 train_dataloader 우리가 사용하는 것 batch_size = 64과 우리의 샘플러를 전달합니다. 우리는 이미 샘플러를 사용 shuffle=True하고 train_dataloader있기 때문에 우리는 사용하지 않습니다 . 이 두 가지는 상호 배타적입니다.

들어 test_dataloaderval_dataloader우리가 사용합니다 batch_size = 1.

train_loader = DataLoader(dataset=train_dataset,
                          batch_size=BATCH_SIZE,
                          sampler=weighted_sampler
)
val_loader = DataLoader(dataset=val_dataset, batch_size=1)
test_loader = DataLoader(dataset=test_dataset, batch_size=1)

dropout 및 batch-norm을 사용하여 간단한 3 계층 피드 포워드 네트워크를 정의 해 보겠습니다.

class MulticlassClassification(nn.Module):
    def __init__(self, num_feature, num_class):
        super(MulticlassClassification, self).__init__()
        
        self.layer_1 = nn.Linear(num_feature, 512)
        self.layer_2 = nn.Linear(512, 128)
        self.layer_3 = nn.Linear(128, 64)
        self.layer_out = nn.Linear(64, num_class) 
        
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(p=0.2)
        self.batchnorm1 = nn.BatchNorm1d(512)
        self.batchnorm2 = nn.BatchNorm1d(128)
        self.batchnorm3 = nn.BatchNorm1d(64)
        
    def forward(self, x):
        x = self.layer_1(x)
        x = self.batchnorm1(x)
        x = self.relu(x)
        
        x = self.layer_2(x)
        x = self.batchnorm2(x)
        x = self.relu(x)
        x = self.dropout(x)
        
        x = self.layer_3(x)
        x = self.batchnorm3(x)
        x = self.relu(x)
        x = self.dropout(x)
        
        x = self.layer_out(x)
        
        return x

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

###################### OUTPUT ######################
cuda:0
손실 함수 meme [그림 [5]]

model = MulticlassClassification(num_feature = NUM_FEATURES, num_class=NUM_CLASSES)
model.to(device)

criterion = nn.CrossEntropyLoss(weight=class_weights.to(device))
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
print(model)

###################### OUTPUT ######################
MulticlassClassification(
  (layer_1): Linear(in_features=11, out_features=512, bias=True)
  (layer_2): Linear(in_features=512, out_features=128, bias=True)
  (layer_3): Linear(in_features=128, out_features=64, bias=True)
  (layer_out): Linear(in_features=64, out_features=6, bias=True)
  (relu): ReLU()
  (dropout): Dropout(p=0.2, inplace=False)
  (batchnorm1): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (batchnorm2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (batchnorm3): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)

훈련을 시작하기 전에 에포크 당 정확도를 계산하는 함수를 정의 해 보겠습니다.

이 함수는 y_predy_test입력 인수로 사용합니다. 우리는 그 적용 log_softmaxy_pred더 높은 확률을 가지고있는 클래스의 압축을 풉니 다.

그 후 예측 된 클래스와 실제 클래스를 비교하여 정확도를 계산합니다.

def multi_acc(y_pred, y_test):
    y_pred_softmax = torch.log_softmax(y_pred, dim = 1)
    _, y_pred_tags = torch.max(y_pred_softmax, dim = 1)    
    
    correct_pred = (y_pred_tags == y_test).float()
    acc = correct_pred.sum() / len(correct_pred)
    
    acc = torch.round(acc) * 100
    
    return acc

accuracy_stats = {
    'train': [],
    "val": []
}
loss_stats = {
    'train': [],
    "val": []
}
교육 meme [이미지 [6]]

print("Begin training.")
for e in tqdm(range(1, EPOCHS+1)):
    
    # TRAINING
    train_epoch_loss = 0
    train_epoch_acc = 0
model.train()
    for X_train_batch, y_train_batch in train_loader:
        X_train_batch, y_train_batch = X_train_batch.to(device), y_train_batch.to(device)
        optimizer.zero_grad()
        
        y_train_pred = model(X_train_batch)
        
        train_loss = criterion(y_train_pred, y_train_batch)
        train_acc = multi_acc(y_train_pred, y_train_batch)
        
        train_loss.backward()
        optimizer.step()
        
        train_epoch_loss += train_loss.item()
        train_epoch_acc += train_acc.item()
        
        
    # VALIDATION    
    with torch.no_grad():
        
        val_epoch_loss = 0
        val_epoch_acc = 0
        
        model.eval()
        for X_val_batch, y_val_batch in val_loader:
            X_val_batch, y_val_batch = X_val_batch.to(device), y_val_batch.to(device)
            
            y_val_pred = model(X_val_batch)
                        
            val_loss = criterion(y_val_pred, y_val_batch)
            val_acc = multi_acc(y_val_pred, y_val_batch)
            
            val_epoch_loss += val_loss.item()
            val_epoch_acc += val_acc.item()
loss_stats['train'].append(train_epoch_loss/len(train_loader))
    loss_stats['val'].append(val_epoch_loss/len(val_loader))
    accuracy_stats['train'].append(train_epoch_acc/len(train_loader))
    accuracy_stats['val'].append(val_epoch_acc/len(val_loader))
                              
    
    print(f'Epoch {e+0:03}: | Train Loss: {train_epoch_loss/len(train_loader):.5f} | Val Loss: {val_epoch_loss/len(val_loader):.5f} | Train Acc: {train_epoch_acc/len(train_loader):.3f}| Val Acc: {val_epoch_acc/len(val_loader):.3f}')

###################### OUTPUT ######################
Epoch 001: | Train Loss: 1.38551 | Val Loss: 1.42033 | Train Acc: 38.889| Val Acc: 43.750
Epoch 002: | Train Loss: 1.19558 | Val Loss: 1.36613 | Train Acc: 59.722| Val Acc: 45.312
Epoch 003: | Train Loss: 1.12264 | Val Loss: 1.44156 | Train Acc: 79.167| Val Acc: 35.938
.
.
.
Epoch 299: | Train Loss: 0.29774 | Val Loss: 1.42116 | Train Acc: 100.000| Val Acc: 57.812
Epoch 300: | Train Loss: 0.33134 | Val Loss: 1.38818 | Train Acc: 100.000| Val Acc: 57.812

왜 그렇게해야합니까? 학습 및 평가 중에 다르게 작동하는 Dropout또는 같은 레이어를 사용하는 경우 BatchNorm( 예 : 평가 중에 드롭 아웃을 사용하지 않음 ) PyTorch에게 그에 따라 작동하도록 지시해야합니다.

마찬가지로 model.eval()모델을 테스트 할 때 호출 합니다. 아래에서 확인할 수 있습니다.

교육으로 돌아 가기; for 루프를 시작합니다 . 이 for-loop 의 맨 위에서 우리는 epoch 당 손실과 정확도를 0으로 초기화합니다. 모든 epoch 후에 손실 / 정확도를 출력하고 다시 0으로 재설정합니다.

그런 다음 또 다른 for-loop가 있습니다. 이 for 루프train_loader.

우리는 optimizer.zero_grad()예측하기 전에합니다. 이 backward()함수는 기울기를 누적 하므로 미니 배치 당 수동으로 0으로 설정해야합니다.

우리의 정의 모델에서, 우리는 다음, 예측을 획득하는 미니 배치에 대한 손실 (정확성)을 얻을 사용하여 역 전파을 수행 loss.backward()하고 optimizer.step().

마지막으로 모든 미니 배치 손실 (및 정확도)을 추가하여 해당 세대의 평균 손실 (및 정확도)을 얻습니다. 각 미니 배치에 대한 모든 손실 / 정확도를 합산하고 마지막으로 미니 배치 수로 나눕니다. train_loader에포크 당 평균 손실 / 정확도를 얻기위한 길이입니다 .

우리가 훈련을 위해 따르는 절차는 우리가 그것을 포장하고 torch.no_grad역 전파를 수행하지 않는다는 사실을 제외하고는 유효성 검사를 위해 똑같습니다 . torch.no_grad()PyTorch에 역 전파를 수행하지 않기 때문에 메모리 사용량을 줄이고 계산 속도를 높입니다.

손실 및 정확도 시각화

손실 및 정확도 선 플롯을 표시하기 위해 accuracy_statsloss_stats사전 에서 데이터 프레임을 다시 만듭니다 .

# Create dataframes
train_val_acc_df = pd.DataFrame.from_dict(accuracy_stats).reset_index().melt(id_vars=['index']).rename(columns={"index":"epochs"})
train_val_loss_df = pd.DataFrame.from_dict(loss_stats).reset_index().melt(id_vars=['index']).rename(columns={"index":"epochs"})
# Plot the dataframes
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20,7))
sns.lineplot(data=train_val_acc_df, x = "epochs", y="value", hue="variable",  ax=axes[0]).set_title('Train-Val Accuracy/Epoch')
sns.lineplot(data=train_val_loss_df, x = "epochs", y="value", hue="variable", ax=axes[1]).set_title('Train-Val Loss/Epoch')
Loss and accuracy plots [Image 7]]

훈련이 끝나면 모델이 어떻게 진행되었는지 테스트해야합니다. model.eval()테스트 코드를 실행하기 전에 사용했습니다 . 추론하는 동안 역 전파를 수행하고 싶지 않다고 PyTorch에 알리기 torch.no_grad()위해 위의 유효성 검사 루프에서했던 것처럼를 사용 합니다.

예측을 보관할 목록을 정의하는 것으로 시작합니다. 그런 다음 test_loader. 각 배치에 대해 —

  • 입력 미니 배치를 GPU로 이동합니다.
  • 훈련 된 모델을 사용하여 예측합니다.
  • log_softmax예측에 활성화를 적용 하고 확률이 가장 높은 지수를 선택합니다.
  • 배치를 CPU에서 GPU로 이동합니다.
  • 텐서를 numpy 객체로 변환하고 목록에 추가합니다.
  • confusion_matrix및에 대한 입력으로 사용할 수 있도록 목록을 평평하게 classification_report만듭니다.

y_pred_list = []
with torch.no_grad():
    model.eval()
    for X_batch, _ in test_loader:
        X_batch = X_batch.to(device)
        y_test_pred = model(X_batch)
        y_pred_softmax = torch.log_softmax(y_test_pred, dim = 1)
        _, y_pred_tags = torch.max(y_pred_softmax, dim = 1)
        y_pred_list.append(y_pred_tags.cpu().numpy())
y_pred_list = [a.squeeze().tolist() for a in y_pred_list]

confusion_matrix_df = pd.DataFrame(confusion_matrix(y_test, y_pred_list)).rename(columns=idx2class, index=idx2class)

sns.heatmap(confusion_matrix_df, annot=True)
Confusion Matrix [Image [8]]

마지막으로 정밀도, 재현율 및 F1 점수가 포함 된 분류 보고서를 인쇄합니다.

print(classification_report(y_test, y_pred_list))

###################### OUTPUT ######################
precision    recall  f1-score   support

           0       0.00      0.00      0.00         2
           1       0.14      0.27      0.19        11
           2       0.70      0.65      0.67       136
           3       0.63      0.57      0.60       128
           4       0.49      0.60      0.54        40
           5       0.00      0.00      0.00         3

    accuracy                           0.59       320
   macro avg       0.33      0.35      0.33       320
weighted avg       0.62      0.59      0.60       320

이 블로그 게시물은 "신경망 훈련 방법"시리즈의 일부입니다. 여기 에서 시리즈를 찾을 수 있습니다 .

LinkedInTwitter 에서 저를 찾을 수 있습니다 . 이 글이 마음에 들면 다른 블로그 게시물을 확인하세요 .

Suggested posts

IBM Watson Studio의 AutoAI 노트북으로 마술사 코드 깨기

IBM Watson Studio의 AutoAI 노트북으로 마술사 코드 깨기

가장 유명한 행동 강령 중 하나입니다. "마술사는 절대 비밀을 밝히지 않습니다." 그러나 청중이 너무나 놀라워 서 '방금 일어난 일'이라는 느낌에 놀라서 비밀을 밝히지 않는 것이 거의 잔인한 순간이 있습니다.

Python 코드 한 줄로 여러 시계열 예측 모델 학습

Auto-TS 라이브러리를 사용하여 ARIMA, SARIMAX, FB Prophet, VAR 및 ML 모델 개발

Python 코드 한 줄로 여러 시계열 예측 모델 학습

Automated Machine Learning (AutoML)은 기계 학습 파이프 라인의 일부 구성 요소를 자동화하는 것을 말합니다. AutoML은 일부 모델 개발 프로세스를 자동화하여 데이터 과학자의 워크 플로를 가속화합니다.

Related posts

데이터 과학을위한 필수 수학 : 기초 및 기초 변경

데이터 과학을위한 필수 수학 : 기초 및 기초 변경

고유 분해 및 SVD에 유용한 기저의 선형 대수 개념 이해이 기사에서는 고유 분해 또는 특이 값 분해 (SVD)와 같은 행렬 분해 방법을 이해하는 흥미로운 방법 인 기저 개념에 대해 알아 봅니다. 정의 기본은 벡터 공간 (벡터 집합)을 설명하는 데 사용되는 좌표계입니다.

판다와 에라스무스 연구 교환 분석

판다와 에라스무스 연구 교환 분석

Erasmus 프로그램 2011-12에서 발생한 20 만 개의 연구 교환으로 데이터 세트를 분석 한 결과 1987 년 이후 Erasmus 프로그램은 매년 수십만 명의 유럽 학생들에게 한 학기 또는 1 년을 해외에서 보낼 기회를 다른 유럽에서 보낼 수있는 기회를 제공합니다. 경제적 인 지원뿐만 아니라 쉬운 교환 과정을 제공합니다. 유럽의 다양한 사람, 언어 및 문화에 대한 마음과 마음을 열어주는 정말 귀중한 경험입니다.

데이터 과학을위한 8 가지 기본 통계 개념

… 평범한 영어로 설명

데이터 과학을위한 8 가지 기본 통계 개념

통계는 "수량 데이터의 수집, 분석, 해석 및 표현을 다루는 수학의 한 분야"입니다. 프로그래밍과 기계 학습을 혼합하면 데이터 과학의 핵심 기술에 대해 꽤 잘 설명 할 수 있습니다.

데이터 과학의 9 가지 거리 측정

일반적인 거리 측정의 장점과 함정

데이터 과학의 9 가지 거리 측정

감독되거나 감독되지 않는 많은 알고리즘은 거리 측정을 사용합니다. 유클리드 거리 또는 코사인 유사성과 같은 이러한 측정은 종종 k-NN, UMAP, HDBSCAN 등과 같은 알고리즘에서 찾을 수 있습니다.