| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- Python
- 힙정렬
- CNN
- TTS
- SQL
- RNN
- 머신러닝
- 에이전트
- 데이터엔지니어
- 트랜스포머
- RDBMS
- Transformer
- 생성형 인공지능
- 랭그래프
- 데이터 시각화
- 딥러닝
- 객체지향
- ASR
- LangGraph
- 소프트웨어 개발
- 캐글
- 기초
- python 기초
- python기초
- 정보처리기사
- CLIP
- 알고리즘
- 자연어처리
- dementional reduction
- UMAP
Archives
- Today
- Total
수달이네 기술 블로그
4. 손글씨 숫자 데이터셋 처리 본문
0~9까지의 숫자를 손글씨로 쓴 흑백 이미지
- 8×8 픽셀 64차원의 벡터
- 각 픽셀값은 0~16까지의 명암값을 가진다.
- 1797개의 샘플로 이루어져있으며 0~9의클래스가 레이블로 붙어있다.
- 분류 알고리즘을 학습시키거나 데이터 시각화, 차원 축소 기법등을 실험하는데 사용한다.
데이터 불러오기
# 시작전 import 파일들
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader
digits = load_digits()
digits
# {'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.]], shape=(1797, 64)),
# 'target': array([0, 1, 2, ..., 8, 9, 8], shape=(1797,)),
# 'frame': None,
# 'feature_names': ['pixel_0_0',
# 'pixel_0_1',
# 'pixel_0_2',
# 'pixel_0_3',
# 'pixel_0_4',
# 'pixel_0_5',
# 'pixel_0_6',
# 'pixel_0_7',
# 'pixel_1_0',
# 'pixel_1_1',
# 'pixel_1_2',
# 'pixel_1_3',
# 'pixel_1_4',
# 'pixel_1_5',
# 'pixel_1_6',
# 'pixel_1_7',
# ...
# ...,
# [ 0., 4., 16., ..., 16., 6., 0.],
# [ 0., 8., 16., ..., 16., 8., 0.],
# [ 0., 1., 8., ..., 12., 1., 0.]]], shape=(1797, 8, 8)),
# 'DESCR': ".. _digits_dataset:\\n\\nOptical recognition of handwritten digits dataset\\n--------------------------------------------------\\n\\n**Data Set Characteristics:**\\n\\n:Number of Instances: 1797\\n:Number of Attributes: 64\\n:Attribute Information: 8x8 image of integer pixels in the range 0..16.\\n:Missing Attribute Values: None\\n:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)\\n:Date: July; 1998\\n\\nThis is a copy of the test set of the UCI ML hand-written digits datasets\\nhttps://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits\\n\\nThe data set contains images of hand-written digits: 10 classes where\\neach class refers to a digit.\\n\\nPreprocessing programs made available by NIST were used to extract\\nnormalized bitmaps of handwritten digits from a preprinted form. From a\\ntotal of 43 people, 30 contributed to the training set and different 13\\nto the test set. 32x32 bitmaps are divided into nonoverlapping blocks of\\n4x4 and the number of on pixels are counted in each block. This generates\\nan input matrix of 8x8 where each element is an integer in the range\\n0..16. This reduces dimensionality and gives invariance to small\\ndistortions.\\n\\nFor info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.\\nT. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.\\nL. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,\\n1994.\\n\\n.. dropdown:: References\\n\\n - C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their\\n Applications to Handwritten Digit Recognition, MSc Thesis, Institute of\\n Graduate Studies in Science and Engineering, Bogazici University.\\n - E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.\\n - Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.\\n Linear dimensionalityreduction using relevance weighted LDA. School of\\n Electrical and Electronic Engineering Nanyang Technological University.\\n 2005.\\n - Claudio Gentile. A New Approximate Maximal Margin Classification\\n Algorithm. NIPS. 2000.\\n"}
# Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
- ‘data’:에서 보이는 첫번째 2차 배열은 이미지를 나타낸다.
- target은 정답을 나타낸다.
- feature_name은 컬럼명을 나타낸다.
X_data = digits['data']
y_data = digits['target']
print(X_data.shape)
print(y_data.shape)
# (1797, 64)
# (1797,)
- X_data에 독립변수를 y_data에 종속변수를 입력한다.
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')
ax.set_title(y_data[i])
ax.axis('off')
x_data를 실제로 시각화 하면

위와 같이 표시된다.
더보기
💡axes.flatten()
다차원 배열 형태로 구성된 Matplotlib의 서브플롯 배열을 1차원 배열로 변환하는 메서드
- 위에서 subplot으로 2행 5열로 만들었다. 그래서 10개의 subplot을 만들어 줄 수 있었는데.
- axes.flatten을 이용해 일단 데이터를 1열로 처리하고 보여줄땐 2차원으로 보여줄 수 있게 한다.
- 2중 for문을 사용하지 않아도 된다.
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])
- Tensor로 변환해준다.
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size = 0.2, random_state=42)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
# torch.Size([1437, 64]) torch.Size([1437])
# torch.Size([360, 64]) torch.Size([360])
- test데이터와 train데이터로 데이터를 분리해준다.
데이터 로더
데이터셋을 효율적으로 관리하고, 학습 과정에서 데이터를 쉽게 가져오게 도와주는 도구
batch단위로 데이터셋을 나누어 모델에 제공(미니배치)
데이터의 크기가 클 경우 메모리를 효율적으로 처리할 수 있도록 설계됨.
- 데이터 증강, 셔플링, 병렬처리 같은 기능또한 지원하여 학습 성능을 향상시킴.
- 데이터를 한 개씩 넣어주는 것. > SGD(Stochastic Gradient Descent, 확률적 경사하강법)
- 배치단위로 넣어주는것. > Mini-batch gradient Descent
데이터 로더의 역할
배치처리
- 데이터를 지정된 크기의 배치로 나누어 모델에 제공한다.
셔플링
- 데이터 순서를 무작위로 섞어 과적합을 방지한다
병렬처리
- num_workers옵션으로 데이터를 병렬로 로드해서 속도를 향상한다(cpu의 core를 활용)
반복처리
- 학습 epoch동안 데이터를 자동으로 반복해서 제공한다.
loader = DataLoader(
dataset = list(zip(X_train, y_train)),
batch_size = 64, # 모델에 데이터를 넣을때 몇개씩?
shuffle=True
drop_last=False
)
- dataset: 데이터셋을 넣어줌
- batch_size: 모델에 넣은 배치의 크기
- shuffle: 데이터를 섞어줄 것인가?
- drop_last: 배치 단위로 넣어줬을때 나머지 (나누어 떨어지지 않은 것)을 버릴것인가
imgs, labels = next(iter(loader))
- loader안에 전체 데이터 크기 / 64 개의 데이터가 들어있다.
- 이때 imgs와 labels에 iterator로 하나씩 꺼내 놓으니까 배치의 크기인 64개의 이미지와 64개의 라벨이 담기게 된다.
- 이제 이 데이터셋을 가져온다면
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')

- 위와 같이 출력된다.
- 하나씩 배치를 꺼내서 출력할 수 있다.
모델 학습
model = nn.Sequential(
nn.Linear(64,10)
)
- 64 배치가 0~9까지의 클래스로 나뉨
optimizer = optim.Adam(model.parameters(), lr = 0.01)
- optimizer는 Adam을 사용 (미니배치) - 잘 모를땐 Adam을 사용하는게 좋음
epochs = 50
for epoch in range (epochs + 1):
sum_losses = 0
sum_accs = 0
for x_batch, y_batch in loader: #1. 데이터개수 / 64번
y_pred = model(x_batch)
loss = nn.CrossEntropyLoss()(y_pred, y_batch) # 2. CE
optimizer.zero_grad() # 기울기 초기화(원래는 누적됨)
loss.backward() # 초기화 후 연산 지금까지 계산했던 기울기를 미분
optimizer.step() # 기울기, 편향을 적용
sum_losses += loss # 3. loss값 합
y_prob = nn.Softmax(1)(y_pred) # 4. softmax
y_pred_index = torch.argmax(y_prob, axis = 1)
acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
# 5. 몇개 맞췄는지 accuracy를 구함
sum_accs += acc # 6. acc합
# 하나의 loader끝
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}%')
# 7. 시각화
# Epoch 47/50 Loss: 0.011587 Accuracy: 99.86%
# Epoch 48/50 Loss: 0.011645 Accuracy: 99.86%
# Epoch 49/50 Loss: 0.012596 Accuracy: 99.80%
# Epoch 50/50 Loss: 0.015511 Accuracy: 99.59%
- loader에서 하나씩 꺼내 x_batch(독립변수)와 y_batch(종속변수)를 꺼내줌
- 모델로 예측하고, CE로 Loss값을 계산
- 배치당 loss값을 합해준다. (시각화를 위해 평균내기 위함)
- softmax 연산을 통해 정규화
- 정규화된 값으로 예측후 해당 값을 실제 값과 비교하여 accuracy를 구함
- 해당 accuracy를 더해줌해평균내기 위함)
- epoch별 평균낸 loss와 acc를 확인
예측
plt.imshow(X_test[15].reshape((8,8)), cmap='gray')
print(y_test[15])
# tensor(2)

2를 나타낸 손글씨이며 정답또한 위와 같이 출력된다.
해당 값을 모델에 넣어보면
y_pred = model(X_test)
y_prob = nn.Softmax(1)(y_pred) # 정규화
y_prob[15] # 15번의 소수점
for i in range(10):
print(f'숫자{i}일 확률: {y_prob[15][i]:.2f}')
# 숫자0일 확률: 0.00
# 숫자1일 확률: 0.00
# 숫자2일 확률: 1.00
# 숫자3일 확률: 0.00
# 숫자4일 확률: 0.00
# 숫자5일 확률: 0.00
# 숫자6일 확률: 0.00
# 숫자7일 확률: 0.00
# 숫자8일 확률: 0.00
# 숫자9일 확률: 0.00
- 위와 같이 잘 맞춘 것을 알 수 있다. 이제 test데이터로 accuracy를 구해보자
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}% 입니다')
# 테스트 정확도는 97.22% 입니다
- 위처럼 테스트 정확도가 표현된다.
데이터 증강(Data Augumentation)
학습 데이터를 인위적으로 변환하여 데이터셋의 다양성을 높이고 모델의 일반화 성능을 향상시키는 기법
- 회전
- 크기조정
- 반전
- 밝기조정
- 블러링
데이터증강으로 데이터 부족문제를 완화하고, 과적합을 방지한다.
- 이미지, 음성데이터와 같이 특징이 직관적인 데이터에서 효과적으로 활용된다.
- 증강된 데이터는 모델이 예측 대상의 다양한 변형에 대해 강하게 학습할 수 있도록 돕는다.
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.2, random_state=42)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
# dataset = list(zip(X_train, y_train)) 이것의기능을 모두 포함하지만 추가 기능 존재
print(len(train_dataset)) #len으로 데이터셋의 크기를 확인 가능
print(train_dataset[0]) #데이터셋의 인덱싱도 가능하다
TensorDataset: zip을 이용하여 데이터 셋으로 만들어 주는 기능 외에도 길이나 인덱싱도 가능하게 한다.
Transforms.Compose
데이터 변환 작업을 순차적으로 적용할 수 있도록 해준다.
- 이미지 데이터 전처리, 증강에서 사용함
- 각 변환을 리스트로 묶어 실행
transform = transforms.Compose([
transforms.RandomRotation(10),
transforms.RandomAffine(0, shear = 5, scale = (0.9,1.1))
])
데이터를 꺼낼때 마다 위의 Compose를 적용시킴
- RandomRotation(10): 데이터를 -10~10도 사이로 무작위로 회전한다
- RandomAffine(0, shear =5, scale= (0.9, 1.1))
- 0 : 회전 각도(이미 10도 회전했으므로 시켜주지 않았음)
- shear: 이미지를 10도 비튼다(비스듬히, 정사각형 > 평행사변형)
- scale: 이미지를 0.9배~1.1배범위 내에서 크기를 조정한다
class AugmentedDataset(Dataset): #Dataset상속으로 데이터셋을 만든다.
def __init__(self, dataset, transform):
self.dataset = dataset
self.transform = transform
def __len__(self):
return len(self.dataset)
def __getitem__(self, index):
x,y = self.dataset[index]
x = x.view(8,8).unsqueeze(0)
# view: 데이터를 변경하진 않고 8x8로 보이게 함
# unsqueeze: 64개의 벡터를 이미지로 만들기 위해 한차원을 더 더한다.
x = self.transform(x)
return x.flatten(), y # 다시 1차원으로 펼쳐서 리턴
augmentedDataset = AugmentedDataset(train_dataset, transform)
클래스를 만들어어 데이터 transform을 적용해주어야 한다.
train_loader = DataLoader(augmentedDataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=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')
- 해당 데이터셋을 만들어 시각화 하면

- 위와 같이 표시된다. (커진 숫자들, 비틀린 숫자들이 보인다.)
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}%')
이제 증강된 데이터셋을 활용해서 이미지를 학습하면
- 구조는 원래와 같음
plt.imshow(X_test[12].reshape((8, 8)), cmap='gray')
print(y_test[12])
# tensor(4)

y_prob = nn.Softmax(1)(y_pred)
y_prob[12]
for i in range(10):
print(f'숫자 {i}일 확률: {y_prob[12][i]:.2f}')
# 숫자 0일 확률: 0.00
# 숫자 1일 확률: 0.00
# 숫자 2일 확률: 0.00
# 숫자 3일 확률: 0.00
# 숫자 4일 확률: 1.00
# 숫자 5일 확률: 0.00
# 숫자 6일 확률: 0.00
# 숫자 7일 확률: 0.00
# 숫자 8일 확률: 0.00
# 숫자 9일 확률: 0.00
- 위와 같이 잘 학습된 것을 확인할 수 있다.
'AI공부 > 딥러닝' 카테고리의 다른 글
| 6. 다층 퍼셉트론 (0) | 2026.02.01 |
|---|---|
| 5. ANN과 퍼셉트론 (0) | 2026.01.31 |
| 3. 파이토치 논리회귀(단항, 다중 논리회귀) (0) | 2026.01.29 |
| 2. 다중선형회귀 (0) | 2026.01.28 |
| 1. 단순 선형 회귀(손실함수, 최적화, 경사하강법, 학습률) (0) | 2026.01.27 |