sklearn.datasets

Load_Boston 데이터셋

sklearn에서는 간단한 머신러닝 알고리즘 분석과 테스트를 위해서 작은 규모의 데이터셋(small toy datasets)을 datasets이라는 패키지에 담아두었습니다. 해당 데이터셋들은 데이터에 대한 정보와 특징들이 포함되어 있어 데이터셋을 이용하는 사용자로 하여금 해당 데이터셋이 어떤 의미인가를 쉽게 이해할 수 있도록 해주고 있습니다.

https://scikit-learn.org/stable/datasets/index.html#toy-datasets

공식 홈페이지에 들어가보시면 제공하는 데이터의 내용들을 확인 하실 수 있습니다. 간략한 정보는 아래와 같습니다.

  • load_boston : 보스턴 지역의 집값 데이터(회귀분석)
  • load_iris : 붗꽃 데이터(분류)
  • load_diabetes : 당뇨병 데이터(회귀분석)
  • load_digits : 숫자 이미지 데이터(분류)
  • load_linnerud : 20대 중반 남성의 신체정보와 운동정보(다변량분석)
  • load_wine : 와인의 특징에 따른 종류(분류)
  • load_breast_cancer : 유방암 데이터(분류)
from sklearn.datasets import load_boston
boston = load_boston()

데이터셋을 로딩하는데는 load라는 명령어와 함께 해당 데이터셋의 이름을 적어주면 간단히 로딩할 수 있습니다.
데이터를 로딩한 후에는 DESCR 을 통해서 해당 데이터셋이 어떤 특징이 있는지 살펴볼 수 있습니다.

print(boston.DESCR)
.. _boston_dataset:

Boston house prices dataset
---------------------------

**Data Set Characteristics:**  

    :Number of Instances: 506 

    :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000's

    :Missing Attribute Values: None

    :Creator: Harrison, D. and Rubinfeld, D.L.

This is a copy of UCI ML housing dataset.
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/
...

DESCR 외에도 해당 boston 데이터셋에는 몇가지의 key 값이 더 있습니다. 해당 키값을 모두 보기 위해서는 boston.key() 명령을 사용합니다.

boston.keys()
# dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
  • data : 데이터셋의 정보가 numpy.ndarray 형태로 저장
  • target : 레이블 정보
  • feature_names : 데이터셋의 컬럼정보
  • DESCR : 데이터셋 설명
  • filename : 해당 데이터셋의 저장 위치 정보 표시

이렇게 입력된 데이터는 컬럼 정보와 분리되어 있기 때문에 보기에 좀 불편하고 어려울 수 있습니다. 이럴 때에 pandas 패키지를 통해 데이터프레임을 만들면 탐색적데이터분석에 좀 더 유리합니다.

import pandas as pd
df = pd.DataFrame(data=boston.data, columns = boston.feature_names)
df['TARGET'] = boston.target
df.head()

pandas의 데이터프레임으로 해당 데이터를 변경해보면 위와 같은 형태로 보여지기 때문에 데이터를 보기가 좀 더 편리합니다. 좀 더 자세한 정보를 보시고 싶으시면 df.info(), df.describe()와 같은 함수를 사용해보시면 각 컬럼의 데이터 타입과 기본적인 통계정보를 확인 할 수 있습니다. 추가로 matplotlib과 같은 시각화 함수를 사용하면 데이터를 내용을 더 쉽게 파악할 수 있습니다.

import matplotlib.pyplot as plt
plt.figure(figsize=(12,5))
plt.scatter(scaled_df['AGE'],scaled_df['DIS'])
corr = scaled_df.corr()
corr.style.background_gradient(cmap='coolwarm')

시각화로 데이터셋의 내용을 살펴본 후에 학습을 위해서 데이터를 나눠줄 필요가 있을 경우에 sklearn은 훌륭한 유틸 함수를 제공합니다. 아래와 같이 model_selection 패키지에서 train_test_split을 사용해서 학습용, 훈련용 데이터셋을 만들 수 있습니다.

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(boston.data, boston.target, test_size=0.2, shuffle=False, random_state=701)
print(x_train.shape) #404,13
print(x_test.shape) #102,13

Load_Digit 데이터셋

boston 데이터셋은 연속된 값을 가지는 회귀분석에서 많이 사용하는 예제입니다. 이번에는 분류 문제에 사용하는 데이터셋인 숫자(digit) 데이터셋을 살펴보겠습니다. 이 데이터의 shape은 (1797, 64) 입니다. 이것은 8 x 8 이미지를 1차원(64)으로 생성한 값인데 이러한 이미지가 1,797개가 있다는 의미입니다.

from sklearn.datasets import load_digits
digits = load_digits()
digits.data[0]
array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,
       15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,
       12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,
        0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,
       10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])
plt.imshow(digits.data[0].reshape(8,8))

해당 배열의 첫번째 값을 가지고 8×8배열로 만든 후에 이것을 이미지로 표시해보면 위와 같은 이미지 “0” 값이 나옵니다. 즉 이런 데이터가 1,797개가 있다는 의미입니다. 이러한 데이터를 훈련시키면 숫자가 입력될 때 이것이 어떤 숫자인지 예측할 수 있게 됩니다.

from sklearn.decomposition import PCA
pca = PCA(n_components=2) # 시각화를 위해 2차원으로 성분을 추출
dim2data = pca.fit_transform(digits.data)
plt.figure(figsize=(6,6))
plt.scatter(dim2digit[:,0], dim2digit[:, 1], c=digits.target, linewidth=1)
plt.show()

CNN MNIST 테스트 (PyTorch)

CNN 알고리즘을 MNIST 데이터셋을 활용해서 테스트해봅니다.

CNN 알고리즘에 대한 다양한 많은 설명이 있으니 자세한 내용은 아래의 강의를 참고하시기 바랍니다. 비록 작은 부분의 차이들은 있을 수 있지만 본 예제 역시 인터넷에 많은 소스 코드와 다르지 않습니다.
단, 아래의 영상은 텐서플로우로 설명하는 영상이지만 본 예제는 파이토치로 구현되어 있으며 학습도 전체 데이터를 대상으로 하지 않고 첫번째 미니배치만 학습하는 것으로 작성했습니다.

필요한 라이브러리를 임포트하고 GPU 사용 설정하는 부분과 MNIST 데이터를 로드하는 부분에 대해서는 자세한 설명을 하지 않고 지나가겠습니다. GPU 설정이 필요 없는 CPU 상에서 예제를 구동하는 경우는 device 설정을 하지 않고 넘어가셔도 무방합니다.

GPU 서버가 없는 경우는 무료로 Colab을 이용하시는 것도 추천합니다.

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets

import matplotlib.pyplot as plt

USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")

BATCH_SIZE = 128

train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data',
                   train=True,
                   download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data',
                   train=False, 
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=BATCH_SIZE, shuffle=True)

MNIST 데이터는 DataLoader로 불러왔습니다. 데이터는 (128,1,28,28) 형태로 149개로 분리되어 있습니다. 테스트 데이터 역시 마찬가지입니다. 128은 배치사이즈, 1은 채널 사이즈, (28*28)은 이미지의 크기입니다.

본 테스트에서는 전체 149개 데이터를 모두 학습하지 않고 1세트만 301회 학습을 수행합니다. 더 높은 정확도 얻고자 하시는 분은 전체 데이터를 통해 더 많은 학습을 해보시기 바랍니다.

dataset = next(iter(train_loader)) # 학습 데이터
x_data = dataset[0] # x 데이터
y_data = dataset[1] # label 데이터

dataset = next(iter(test_loader)) # 검증 데이터
x_test = dataset[0].to(DEVICE) # x 데이터
y_test = dataset[1].to(DEVICE) # label 데이터

각 데이터 셋에 어떤 이미지가 있는지 확인해보기 위해서 아래와 같은 코드를 수행합니다. MNIST 데이터 셋은 동일한 크기의 손글씨 이미지가 들어있기 때문에 각각의 이미지를 표시해보면 0-9까지의 손글씨 이미지가 저장되어 있는 것을 확인 할 수 있습니다.
아래와 같이 0번째 배열에 숫자 5가 있는 것을 확인 할 수 있습니다.

plt.imshow(x_data[0,0,:])

참고로 CNN의 입력 데이터의 Shape은 아래의 그림과 같습니다. 입력 데이터는 4개의 차원으로 구성되어 있습니다. 가장 먼저는 Batch_Size로 해당 이미지의 갯수를 의미합니다. 다음에 나오는 것은 이미지의 Channel입니다. 3인경우는 RGB 값을 가지고 1인 경우는 대부분 단일 색상으로 표현하는 값입니다. 그리고 나오는 값은 Height, Width 값입니다.

이런 상태에서 [0,0,:]의 의미는 0번째 이미지에서 첫번째 채널의 이미지 데이터를 가지고 온다는 의미가 됩니다.

이제 학습을 위한 모델을 구성합니다. 본 모델을 크게 두부분으로 되어 있습니다. self.convs는 convolution을 수행하는 부분으로 원본 이미지에서 특징정보를 추출하는 부분입니다. 이때 중요한 것은 각 Conv2d를 수행하며 어떤 형태의 아웃풋이 나오는지 확인하는 것이 중요합니다.

예를 들어 28*28 이미지를 kernel_size 3으로 계산하면 출력되는 이미지 사이즈는 (26*26)입니다. 어떻게 이렇게 나오는지는 아래 식에서 확인하실 수 있습니다.

https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html

하지만 매번 위의 식을 통해서 계산하는 것은 좀 귀찮고 힘든 입니다. 위의 공식을 간단한 함수로 구현한 내용을 공유해드립니다.

def conv_output_shape(h_w, kernel_size=1, stride=1, pad=0, dilation=1):
    from math import floor
    if type(kernel_size) is not tuple:
        kernel_size = (kernel_size, kernel_size)
    h = floor( ((h_w[0] + (2 * pad) - ( dilation * (kernel_size[0] - 1) ) - 1 )/ stride) + 1)
    w = floor( ((h_w[1] + (2 * pad) - ( dilation * (kernel_size[1] - 1) ) - 1 )/ stride) + 1)
    return h, w

위와 같은 식을 거쳐서 self.convs 레이어의 최종 output_shape은 총 5*5 이미지 사이즈를 가진 40장의 이미지 데이터를 얻을 수 있습니다.

이렇게 얻은 데이터는 self.layers를 거치다 보면 최종 0~9까지의 숫자 정보를 얻을 수 있습니다.

PyTorch의 Conv2d 패키지는 프로그래머가 간단히 Convolution Layer를 구성할 수 있도록 해줍니다. 프로그래머는 간단히 입력 채널의 수와 출력 채널의 수 그리고 커널 사이즈와 스트라이드 정보만 맞춰주면 자동으로 이미지를 구성해줍니다.

아래의 경우는 최초 28×28 이미지를 입력하고 커널을 3으로 맞춰서 앞선 함수를 통해서 출력 shape을 보면 26×26의 이미지를 출력한다는 것을 확인 할 수 있습니다. 또 다음 레이어는 커널을 3, 스트라이드를 2로 정의하고 이전에 입력된 이미지의 크기를 입력하면 출력 이미지는 12×12로 표시되는 것을 확인 할 수 있습니다. 이런 방법으로 마지막 이미지가 출력되는 크기는 5×5의 이미지가 됩니다.

그렇게 되면 마지막의 fully-connected layer에 들어가는 값은 40×5×5의 입력 값이 됩니다. 그리고 맨 마지막까지 Linear Layer를 거치게 되면 10개의 값으로 출력되고 이에 Softmax를 취하면 0-9 중에 하나의 값을 예측하게 됩니다.

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        
        self.convs = nn.Sequential(
            nn.Conv2d(1, 10, kernel_size=3), # input_channel, output_channel, kernel_size
            nn.ReLU(),
            nn.BatchNorm2d(10),
            nn.Conv2d(10, 20, kernel_size=3, stride=2),
            nn.ReLU(),
            nn.BatchNorm2d(20),
            nn.Conv2d(20, 40, kernel_size=3, stride=2)
        )
        
        self.layers = nn.Sequential(
            nn.Linear(40*5*5, 500),
            nn.ReLU(),
            nn.BatchNorm1d(500),
            nn.Linear(500,250),
            nn.Linear(250,100),
            nn.ReLU(),
            nn.BatchNorm1d(100),
            nn.Linear(100,50),
            nn.Linear(50, 10),
            nn.Softmax(dim=-1)
        )

    def forward(self, x):
        x = self.convs(x)
        x = x.view(-1, 40*5*5)
        return self.layers(x)
    
cnn = Net().to(DEVICE)

이제 학습을 위한 모든 준비가 완료되었고 아래와 같이 학습을 수행합니다. 예제에서는 간단히 301회 학습을 수행했습니다. 학습이 진행되면서 loss와 accuracy 정보의 변화를 기록하기 위해서 list 변수를 각각 선언해줍니다.

또 100회 학습이 완료될 때마다 변화되는 loss와 accuracy 값을 화면에 출력해줍니다.

optimizer = optim.Adam(cnn.parameters())
criterion = nn.CrossEntropyLoss()

hist_loss = []
hist_accr = []

epochs = 301
for epoch in range(epochs):
    cnn.train()
    output = cnn(x_data)
    loss = criterion(output, y_data)
    
    predict = torch.argmax(output, dim=-1) == y_data
    accuracy = predict.float().mean().item()
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    hist_loss.append(loss)
    hist_accr.append(accuracy)
    
    if epoch % 100 == 0:
        print('epoch{}, {:.5f}, {:.5f}'.format(epoch, loss.item(), accuracy))

학습이 완료되고 loss와 accuracy 값을 그래프로 그려줍니다.
그래프를 보니 학습의 곡선이 완만하게 내려가고 정확도는 1에 가까운 값을 나타내어 학습이 잘이뤄지는 것을 확인할 수 있습니다.

그러나 training 데이터를 통한 학습정확도이기 때문에 검증용 데이터를 통해서 정확도 계산을 다시 할 필요가 있습니다.

fig, ax = plt.subplots(2,1)
fig.set_size_inches((12,8))

ax[0].set_title('Loss')
ax[0].plot(hist_loss, color='red')
ax[0].set_ylabel('Loss')
ax[1].set_title('Accuracy')
ax[1].plot(hist_accr, color='blue')
ax[1].set_ylabel('Accuracy')
ax[1].set_xlabel('Epochs')

검증용 데이터셋(test_dataloader)를 통해서 한 배치 정보를 얻어서 방금 수행한 모델의 정확도를 테스트해봅니다. 테스트 결과 0.7109375 값을 얻을 수 있었습니다. 높은 값은 아니지만 전체 469개 미니배치 중에서 1개 데이터셋만 테스트 했기 때문에 전체 데이터를 대상으로 테스트하면 보다 높은 정확도를 얻을 수 있을 것입니다.

cnn.eval()

with torch.no_grad():
    output = cnn(x_test)
    loss = criterion(output, y_test)
    
    predict = torch.argmax(output, dim=-1) == y_test
    accuracy =  predict.float().mean().item()
    
    print(accuracy)