본문 바로가기
AI ML LLM/딥러닝

손글씨 데이터셋

by hyunji00pj 2025. 2. 18.

https://scikit-learn.org/stable/api/sklearn.datasets.html#module-sklearn.datasets

 

sklearn.datasets

Utilities to load popular datasets and artificial data generators. User guide. See the Dataset loading utilities section for further details. Loaders: Sample generators:

scikit-learn.org

 

1. 손글씨 데이터셋


손글씨 숫자 데이터셋은 0부터 9까지의 숫자를 손글씨로 쓴 흑백 이미지로 구성되어 있으며, 각 이미지는 8x8 픽셀 크기의 64차원 벡터로 표현됩니다. 각 픽셀 값은 0(흰색)에서 16(검은색)까지의 명암값을 가집니다. 이 데이터는 총 1797개의 샘플로 이루어져 있으며, 각 샘플에는 숫자 클래스(0~9)가 레이블로 붙어 있습니다. 주로 분류 알고리즘을 학습시키거나 데이터 시각화, 차원 축소 기법 등을 실험하는 데 사용됩니다.

 
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
digits = load_digits()
 
X_data = digits['data']
y_data = digits['target']
print(X_data.shape)
print(y_data.shape)
 
(1797, 64)
(1797,)
 
X_data
 
array([[ 0.,  0.,  5., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ..., 10.,  0.,  0.],
       [ 0.,  0.,  0., ..., 16.,  9.,  0.],
       ...,
       [ 0.,  0.,  1., ...,  6.,  0.,  0.],
       [ 0.,  0.,  2., ..., 12.,  0.,  0.],
       [ 0.,  0., 10., ..., 12.,  1.,  0.]])
 
y_data
 
array([0, 1, 2, ..., 8, 9, 8])
 
fig, axes = plt.subplots(nrows=2,ncols=5,figsize=(14,8))


for i, ax in enumerate(axes.flatten()):
    ax.imshow(X_data[i].reshape(8,8), cmap='gray')# cmap='gray' 흑백으로 찍어주기
    ax.set_title(y_data[i])
    ax.axis('off')
  • plt.subplots(nrows=2, ncols=5)
    • 2개의 행과 5개의 열을 가진 서브플롯(총 10개의 서브플롯)을 생성.
    • 이 결과로 fig(전체 그림)과 axes(각 서브플롯의 배열)가 반환됨.
  • figsize=(14,8)
    • 전체 플롯의 크기를 (14,8) 인치 단위로 설정.
    • 그래프가 너무 작으면 해상도가 낮아지고, 너무 크면 보기 어려울 수 있으므로 적절한 크기 조정.
  • axes는 (2, 5) 크기의 NumPy 배열 형태로 반환됨.
  • 즉, axes는 2행 × 5열의 서브플롯 객체 배열이다.
for i, ax in enumerate(axes.flatten()):
  • axes.flatten()을 사용하여 (2,5) 모양의 2D 배열을 1D 배열
  • enumerate(axes.flatten()):
    • i: 현재 인덱스 (0부터 시작).
    • ax: 현재 순회 중인 개별 서브플롯 객체.
  • 이 루프는 총 10번 실행됨. (2 × 5 = 10개의 서브플롯)
ax.imshow(X_data[i].reshape(8,8), cmap='gray')
  • X_data[i]: i번째 샘플의 입력 데이터.
  • .reshape(8,8): 원본 데이터가 (64,) (1차원 벡터)라면 (8,8) 형태로 변환하여 2D 이미지로 표시.
  • cmap='gray': **그레이스케일(흑백)**로 출력.
ax.set_title(y_data[i])
  • y_data[i]: i번째 샘플의 정답(클래스 레이블).
  • ax.set_title()을 사용하여 각 서브플롯에 해당 이미지의 정답(클래스) 표시.
 
ax.axis('off')
  • 기본적으로 imshow()를 사용하면 x축, y축 눈금이 표시된다.
  • axis('off')를 사용하면 축을 숨겨서 더 깔끔한 시각화 가능.
X_data = torch.FloatTensor(X_data)
y_data = torch.LongTensor(y_data)
print(X_data.shape)
print(y_data.shape)
 
torch.Size([1797, 64])
torch.Size([1797])
 
X_train,X_test,y_train,y_test = train_test_split(X_data,y_data,test_size=0.3,random_state=2025)
 
 
X_train.shape,X_test.shape
(torch.Size([1257, 64]), torch.Size([540, 64]))
 
y_train.shape,y_test.shape
 
(torch.Size([1257]), torch.Size([540]))
 

2. 데이터 로더

데이터로더(Data Loader)는 데이터셋을 효율적으로 관리하고, 모델 학습 과정에서 데이터를 쉽게 가져올 수 있도록 도와주는 도구입니다. 일반적으로 데이터셋을 배치(batch) 단위로 나누어 모델에 제공하며, 데이터의 크기가 클 경우에도 메모리 효율적으로 처리할 수 있도록 설계되었습니다. 데이터 증강, 셔플링, 병렬 처리와 같은 기능을 지원하여 학습 성능을 향상시키고, 모델 학습과 평가 시 일관된 데이터 제공 방식을 유지합니다. 딥러닝 프레임워크에서는 PyTorch의 DataLoader나 TensorFlow의 tf.data 같은 도구를 통해 쉽게 사용할 수 있습니다.

 

데이터로더의 주요 역할

  • 배치 처리: 데이터를 지정된 크기의 배치로 나누어 모델에 제공.
  • 셔플링: 데이터 순서를 무작위로 섞어 과적합 방지.
  • 병렬 처리: num_workers 옵션을 통해 데이터를 병렬로 로드하여 속도 향상.
  • 반복 처리: 학습 epoch 동안 데이터를 자동으로 반복해서 제공.
loader = DataLoader(
    dataset=list(zip(X_train,y_train)),
    batch_size=64,
    shuffle=True,
    drop_last=False
)
imgs,labels = next(iter(loader))

fig, axes = plt.subplots(nrows=8,ncols=8,figsize=(14,14))

for ax, img, label in zip(axes.flatten(),imgs,labels):
    ax.imshow(img.reshape(8,8), cmap='gray')# cmap='gray' 흑백으로 찍어주기
    ax.set_title(str(label))
    ax.axis('off')

 

axes.flatten()

axes.flatten()은 다차원 배열 형태로 구성된 Matplotlib의 서브플롯 배열을 1차원 배열로 변환하는 메서드입니다. Matplotlib에서 다수의 서브플롯을 생성할 때, plt.subplots()는 2차원 배열 형태로 서브플롯 객체를 반환합니다. 이 배열은 각 서브플롯을 접근하기 위해 행과 열의 인덱스를 사용해야 하지만, flatten() 메서드를 사용하면 이 배열을 1차원으로 펼쳐서 각 서브플롯을 단일 인덱스로 순회할 수 있게 됩니다.

 

위 시각 자료를 보면 plt.subplots(2, 2)를 사용하여 생성된 서브플롯이 2x2 형태의 배열로 구성되어 있습니다. 이 상태에서는 axes[0, 0], axes[0, 1], axes[1, 0], axes[1, 1]과 같이 2차원 인덱스를 사용해야 합니다.

 

하지만 axes.flatten()을 적용하면 이 서브플롯 배열이 1차원 리스트처럼 변환되어 flattened_axes[0], flattened_axes[1], flattened_axes[2], flattened_axes[3]처럼 단일 인덱스로 순회할 수 있게 됩니다.

 

이러한 변환을 사용하면 루프를 돌면서 서브플롯을 설정할 때 인덱싱이 훨씬 단순해져 편리합니다.

model =  nn.Sequential(
    nn.Linear(64,10)
)
optimizer = optim.Adam(model.parameters(),lr=0.01)
epochs = 50

for epoch in range(epochs+1):
    sum_losses = 0 
    sum_accs = 0
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = nn.CrossEntropyLoss()(y_pred,y_batch)

        optimizer.zero_grad() #경사하강법을 사용하는 아담에 대한 기울기를 초기화
        loss.backward()# 역전파를 이용해 미분한 값으로 웨이트와 바이오값 뽑아서
        optimizer.step()# 웨이트와 바이오스 갱신 적용

        sum_losses = sum_losses + loss
        y_prob = nn.Softmax(1)(y_pred)#실제 구하고자하는 확률값을 뽑아냄
        y_pred_index = torch.argmax(y_prob,axis=1) #가장 높은 확률의 인덱스값을 뽑아옴
        acc = (y_batch==y_pred_index).float().sum() / len(y_batch) * 100 #이거 한줄 설명도 적기
        sum_accs = sum_accs + acc
        #여기까지 배치 단위 계산
    #에폭 설정
    avg_loss = sum_losses / len(loader) #전체 데이터 평균 로스값
    avg_acc = sum_accs / len(loader) # 전체 데이터 평균 정확도
    print(f'Epoch {epoch:4d}/{epochs} Loss: {avg_loss:.6f} Accuracy: {avg_acc:.2f}%')
 
Epoch    0/50 Loss: 2.059032 Accuracy: 53.75%
Epoch    1/50 Loss: 0.306242 Accuracy: 90.07%
Epoch    2/50 Loss: 0.186663 Accuracy: 93.58%
Epoch    3/50 Loss: 0.129892 Accuracy: 96.36%
Epoch    4/50 Loss: 0.112430 Accuracy: 97.03%
Epoch    5/50 Loss: 0.090612 Accuracy: 97.49%
Epoch    6/50 Loss: 0.085378 Accuracy: 97.50%
Epoch    7/50 Loss: 0.075866 Accuracy: 98.20%
Epoch    8/50 Loss: 0.060378 Accuracy: 98.59%
Epoch    9/50 Loss: 0.065021 Accuracy: 98.05%
Epoch   10/50 Loss: 0.057262 Accuracy: 98.67%
Epoch   11/50 Loss: 0.049868 Accuracy: 98.91%
Epoch   12/50 Loss: 0.046253 Accuracy: 98.91%
Epoch   13/50 Loss: 0.044165 Accuracy: 99.06%
Epoch   14/50 Loss: 0.039842 Accuracy: 99.61%
Epoch   15/50 Loss: 0.036627 Accuracy: 99.38%
Epoch   16/50 Loss: 0.041312 Accuracy: 99.14%
Epoch   17/50 Loss: 0.038287 Accuracy: 99.30%
Epoch   18/50 Loss: 0.031735 Accuracy: 99.61%
Epoch   19/50 Loss: 0.026393 Accuracy: 99.84%
Epoch   20/50 Loss: 0.029508 Accuracy: 99.77%
Epoch   21/50 Loss: 0.025417 Accuracy: 99.69%
Epoch   22/50 Loss: 0.026414 Accuracy: 99.77%
Epoch   23/50 Loss: 0.026406 Accuracy: 99.38%
Epoch   24/50 Loss: 0.025807 Accuracy: 99.45%
Epoch   25/50 Loss: 0.019285 Accuracy: 99.77%
Epoch   26/50 Loss: 0.017832 Accuracy: 100.00%
Epoch   27/50 Loss: 0.021703 Accuracy: 99.84%
Epoch   28/50 Loss: 0.015931 Accuracy: 99.84%
Epoch   29/50 Loss: 0.015503 Accuracy: 99.92%
Epoch   30/50 Loss: 0.018646 Accuracy: 99.69%
Epoch   31/50 Loss: 0.014456 Accuracy: 99.92%
Epoch   32/50 Loss: 0.016027 Accuracy: 99.84%
Epoch   33/50 Loss: 0.013954 Accuracy: 99.84%
Epoch   34/50 Loss: 0.012925 Accuracy: 99.92%
Epoch   35/50 Loss: 0.013075 Accuracy: 99.84%
Epoch   36/50 Loss: 0.012973 Accuracy: 99.77%
Epoch   37/50 Loss: 0.010807 Accuracy: 100.00%
Epoch   38/50 Loss: 0.011722 Accuracy: 99.92%
Epoch   39/50 Loss: 0.015281 Accuracy: 99.92%
Epoch   40/50 Loss: 0.014243 Accuracy: 99.69%
Epoch   41/50 Loss: 0.014695 Accuracy: 99.69%
Epoch   42/50 Loss: 0.010457 Accuracy: 99.92%
Epoch   43/50 Loss: 0.013000 Accuracy: 99.77%
Epoch   44/50 Loss: 0.012177 Accuracy: 99.92%
Epoch   45/50 Loss: 0.008631 Accuracy: 99.92%
Epoch   46/50 Loss: 0.009301 Accuracy: 100.00%
Epoch   47/50 Loss: 0.008152 Accuracy: 100.00%
Epoch   48/50 Loss: 0.008642 Accuracy: 99.92%
Epoch   49/50 Loss: 0.007367 Accuracy: 100.00%
Epoch   50/50 Loss: 0.008092 Accuracy: 99.92%
 
plt.imshow(X_test[10].reshape((8, 8)), cmap='gray')
print(y_test[10])
 

y_pred = model(X_test)
y_pred[10]

 

  • model(X_test): X_test(테스트 데이터)를 신경망 모델에 입력하여 예측값(y_pred)을 얻습니다.
  • y_pred[10]: 예측값 중에서 10번째 샘플의 출력값을 확인합니다.
  • y_pred는 모델의 마지막 층에서 나온 로짓(logit) 값으로, 아직 확률이 아닌 가공되지 않은 점수(raw score) 입니다.

 

tensor([ -3.2551,  -8.3913,  -3.3586,  -2.7096,  -3.8491,  13.2806, -10.4161,
          2.7601,   2.1381,  -9.1885], grad_fn=<SelectBackward0>)
 
y_prob = nn.Softmax(1)(y_pred)
y_prob[10]

 

  • nn.Softmax(1): Softmax 함수는 모델의 출력값을 확률 분포로 변환하는 역할을 합니다.
  • Softmax(dim=1): dim=1은 배치 차원을 유지하면서, 각 샘플의 예측값을 클래스별 확률로 변환합니다.
  • y_prob[10]: 10번째 샘플의 확률 분포를 확인합니다.
    • 예를 들어, y_prob[10]의 결과가 [0.1, 0.05, 0.05, 0.2, ...]처럼 나올 수 있습니다.
    • 이는 각 클래스(0~9)에 대해 모델이 예측한 확률 값입니다.

 

tensor([6.5862e-08, 3.8726e-10, 5.9385e-08, 1.1365e-07, 3.6362e-08, 9.9996e-01,
        5.1129e-11, 2.6977e-05, 1.4483e-05, 1.7450e-10],
       grad_fn=<SelectBackward0>)
for i in range(10):
    print(f'숫자 {i}일 확률 : {y_prob[10][i]:.2f}')
숫자 0일 확률 : 0.00
숫자 1일 확률 : 0.00
숫자 2일 확률 : 0.00
숫자 3일 확률 : 0.00
숫자 4일 확률 : 0.00
숫자 5일 확률 : 1.00
숫자 6일 확률 : 0.00
숫자 7일 확률 : 0.00
숫자 8일 확률 : 0.00
숫자 9일 확률 : 0.00
 
y_pred_index = torch.argmax(y_prob,axis=1)
accuracy = (y_test==y_pred_index).float().sum() / len(y_test) * 100
print(f'테스트 데이터 정확도 : {accuracy:.2f}% 입니다.')

 

  • y_prob는 Softmax를 적용한 확률 값입니다.
  • torch.argmax(y_prob, axis=1): 가장 높은 확률을 가진 클래스의 인덱스를 선택합니다.
    • axis=1: y_prob의 각 행(즉, 각 샘플)에서 가장 높은 확률을 가진 클래스(0~9 중 하나)를 찾습니다.
    • 결과적으로, y_pred_index는 모델이 예측한 최종 숫자 레이블(0~9)로 이루어진 1차원 텐서가 됩니다.

 

  • y_test == y_pred_index: 정답(y_test)과 모델 예측값(y_pred_index)을 비교합니다.
    • y_test는 실제 정답 레이블이 들어 있는 텐서입니다.
    • y_pred_index는 모델이 예측한 최종 클래스 인덱스입니다.
    • 같은 값이면 True, 다르면 False가 됩니다.
  • .float(): True(정답이면) → 1.0, False(오답이면) → 0.0으로 변환합니다.
  • .sum(): 맞춘 개수를 합산합니다.
  • / len(y_test): 전체 샘플 수로 나눠서 비율을 구합니다.
  • * 100: 백분율(%) 형태의 정확도로 변환합니다.

 

테스트 데이터 정확도 : 96.48% 입니다.
 

3. 데이터 증강

데이터 증강(Data Augmentation)은 학습 데이터를 인위적으로 변환하여 데이터셋의 다양성을 높이고 모델의 일반화 성능을 향상시키는 기법입니다. 회전, 크기 조정, 반전, 블러링, 밝기 조정 등 다양한 변환을 적용하여 원본 데이터로부터 새로운 데이터를 생성합니다. 이를 통해 데이터 부족 문제를 완화하고 모델이 특정 패턴에 과적합되지 않도록 도와줍니다. 특히, 이미지나 음성 데이터와 같이 특징이 직관적인 데이터에서 효과적으로 활용되며, 증강된 데이터는 모델이 예측 대상의 다양한 변형에 대해 강하게 학습할 수 있도록 돕습니다.

from torchvision import transforms
from torch.utils.data import TensorDataset
from torch.utils.data import Dataset
X_train,X_test,y_train,y_test = train_test_split(X_data,y_data,test_size=0.3,random_state=2025)
print(X_train.shape,X_test.shape)
print(y_train.shape,y_test.shape)
torch.Size([1257, 64]) torch.Size([540, 64])
torch.Size([1257]) torch.Size([540])
 
train_dataset = TensorDataset(X_train,y_train)
test_dataset = TensorDataset(X_test,y_test)

 

transfrom = transforms.Compose([
    transforms.RandomRotation(10),
    transforms.RandomAffine(0, shear=5, scale=(0.9, 1.1))
])
 
 

transforms.Compose

여러 데이터 변환(transform) 작업을 순차적으로 적용할 수 있도록 해줍니다. 이미지 데이터 전처리와 증강 과정에서 자주 사용되며, 각 변환을 하나의 리스트로 묶어 실행합니다.

1. transforms.RandomRotation(10)

  • 기능: 이미지를 -10도에서 +10도 사이로 무작위 회전시킵니다.
    • 10은 회전 범위를 나타냅니다.
    • 각 호출 시, -10도 ~ +10도 범위에서 무작위로 각도를 선택하여 이미지를 회전합니다.

2. transforms.RandomAffine(0, shear=5, scale=(0.9, 1.1))

  • 기능: 이미지를 비틀기(shear), 크기 조정(scale) 등의 변환을 수행합니다.
    • 0: 회전(각도) 변환을 수행하지 않음을 의미합니다.
    • shear=5: 이미지를 최대 5도만큼 비스듬하게 비틀기(shear) 변환을 수행합니다.
      • 예: 정사각형이 평행사변형처럼 기울어질 수 있습니다.
    • scale=(0.9, 1.1):
      • 이미지를 0.9배(축소)에서 1.1배(확대) 범위 내에서 무작위 크기 조정을 수행합니다.
      • 각 호출 시, 무작위로 크기가 변경됩니다.

 

class AugmentedDataset(Dataset):
    def __init__(self, dataset, transform):
        self.dataset = dataset
        self.transform = transform

    #오버라이딩
    def __len__(self):
        return len(self.dataset)

    #스폐셜 메서드 기억 안나면 다시 내용 뜻 쓰자
    #인덱싱했을때 기능
    def __getitem__(self, idx):
        x, y = self.dataset[idx]
        x = x.view(8,8).unsqueeze(0) # x: 피쳐(64,) -> x,y축 네모 (8,8) -> 이미지(1,8,8)
        x = self.transform(x) #증강 적용
        return x.flatten(),y # 다시 Flatten
augmented_train_dataset = AugmentedDataset(train_dataset, transfrom)

 

  • train_dataset: 원본 학습 데이터셋
  • transfrom: 데이터 변환(transform) 적용
  • AugmentedDataset 클래스를 사용하여 train_dataset의 데이터를 변환 후 새로운 데이터셋으로 만든다.
  • augmented_train_dataset의 __getitem__()을 호출할 때 데이터 증강이 적용된다.

 

train_loader = DataLoader(augmented_train_dataset,batch_size=64,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False)
  • DataLoader는 PyTorch의 데이터셋을 batch 단위로 제공하는 역할을 한다.
  • augmented_train_dataset: 변환된 학습 데이터셋 사용.
  • batch_size=64: 한 번에 64개의 샘플을 가져옴.
  • shuffle=True:
    • 매 epoch마다 데이터 순서를 랜덤하게 섞음.
    • 학습 데이터는 shuffle=True로 설정하는 것이 일반적이다.
    • 이유: 모델이 특정 데이터 패턴을 학습하는 것을 방지하여 일반화 성능을 높일 수 있음.
  • test_dataset을 배치 단위로 로드하는 DataLoader.
  • batch_size=64: 한 번에 64개의 샘플을 가져옴.
  • shuffle=False:
    • 테스트 데이터는 순서를 유지한 채 사용.
    • 평가할 때 데이터 순서를 섞을 필요가 없기 때문.
imgs, labels = next(iter(train_loader))
fig, axes = plt.subplots(nrows=8, ncols=8, figsize=(14, 14))

for ax, img, label in zip(axes.flatten(), imgs, labels):
    ax.imshow(img.reshape((8, 8)), cmap='gray')
    ax.set_title(str(label))
    ax.axis('off')

for images, labels in train_loader:
    print(f"Image batch shape: {images.shape}")
    print(f"Label batch shape: {labels.shape}")
    break

# Image batch shape: torch.Size([64, 64]) 64개 배치 64개 이미지
# Label batch shape: torch.Size([64]) 64개 정답
 
Image batch shape: torch.Size([64, 64])
Label batch shape: torch.Size([64])
 
model = nn.Sequential(
    nn.Linear(64, 10)
)

optimizer = optim.Adam(model.parameters(), lr=0.01)

epochs = 50
for epoch in range(epochs + 1):
    sum_losses = 0
    sum_accs = 0

    for x_batch, y_batch in train_loader:
        y_pred = model(x_batch)
        loss = nn.CrossEntropyLoss()(y_pred, y_batch)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        sum_losses = sum_losses + loss

        y_prob = nn.Softmax(1)(y_pred)
        y_pred_index = torch.argmax(y_prob, axis=1)
        acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
        sum_accs = sum_accs + acc

    if epoch % 10 == 0:
        avg_loss = sum_losses / len(loader)
        avg_acc = sum_accs / len(loader)
        print(f'Epoch {epoch:4d}/{epochs} Loss: {avg_loss:.6f} Accuracy: {avg_acc:.2f}%')
 
Epoch    0/50 Loss: 2.120254 Accuracy: 55.27%
Epoch   10/50 Loss: 0.083456 Accuracy: 97.77%
Epoch   20/50 Loss: 0.044431 Accuracy: 98.63%
Epoch   30/50 Loss: 0.032165 Accuracy: 99.30%
Epoch   40/50 Loss: 0.047757 Accuracy: 98.44%
Epoch   50/50 Loss: 0.023758 Accuracy: 99.33%
plt.imshow(X_test[11].reshape((8, 8)), cmap='gray')
print(y_test[11])
 
y_pred = model(X_test)
y_pred[11]
 
tensor([ -9.5922,  -7.5206,  -0.4817,   6.3523,  -0.7500,  -2.6544, -18.8556,
         18.1157,  -5.7229,   3.4873], grad_fn=<SelectBackward0>)
y_prob = nn.Softmax(1)(y_pred)
y_prob[11]
 
tensor([9.2597e-13, 7.3500e-12, 8.3799e-09, 7.7840e-06, 6.4079e-09, 9.5417e-10,
        8.7811e-17, 9.9999e-01, 4.4362e-11, 4.4356e-07],
       grad_fn=<SelectBackward0>)
for i in range(10):
    print(f'숫자 {i}일 확률: {y_prob[11][i]:.2f}')
 
숫자 0일 확률: 0.00
숫자 1일 확률: 0.00
숫자 2일 확률: 0.00
숫자 3일 확률: 0.00
숫자 4일 확률: 0.00
숫자 5일 확률: 0.00
숫자 6일 확률: 0.00
숫자 7일 확률: 1.00
숫자 8일 확률: 0.00
숫자 9일 확률: 0.00