머신러닝(AI학습 30)

이유진·2024년 7월 8일

--12.MNIST 이미지 손글씨 인식.ipynb--

MNIST 이미지 손글씨 인식

손글씨 데이터 MNIST

MNIST (Modified National Institute of Standards and Technology database) --> 구글링 해서 위키 참ㅈ

"민스트"

http://yann.lecun.com/exdb/mnist/

머신러닝 연습에 자주 사용되는 손글씨 데이터인데, 학습전용 6만개, 테스트 전용 1만개 정도 공개됨.

4개의 압축데이터

train-images-idx3-ubyte.gz: training set images (9912422 bytes)

train-labels-idx1-ubyte.gz: training set labels (28881 bytes)

t10k-images-idx3-ubyte.gz: test set images (1648877 bytes)

t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)

데이터를 다운로드 받아 압축 풀기

import urllib.request as req
import gzip, os, os.path

savepath = "./mnist"
baseurl = "http://yann.lecun.com/exdb/mnist"

files = [
"train-images-idx3-ubyte.gz", # 학습용 데이터 (6만개)
"train-labels-idx1-ubyte.gz", # 학습용 target 값 (레이블)
"t10k-images-idx3-ubyte.gz", # 테스트용 데이터 (1만개)
"t10k-labels-idx1-ubyte.gz", # 테스트용 target값 (레이블)
]

다운로드 폴더 생성

if not os.path.exists(savepath) :
os.mkdir(savepath)

for f in files :
url = baseurl + "/" + f
loc = savepath + "/" + f
print("download : ", url)
if not os.path.exists(loc) :
req.urlretrieve(url, loc) # url 파일을 다운로드 받아 loc으로 저장.

압축해제

gzip.open()

for f in files :
gz_file = savepath + "/" + f
raw_file = savepath + "/" + f.replace(".gz", "") # 압축 해제한 결과 파일명

gzip.open() 으로 압축파일 열기

with gzip.open(gz_file, "rb") as fp :
body = fp.read() # 압축 푼후 읽기
with open(raw_file, "wb") as w :
w.write(body) # 읽은 내용을 저장하기

print('압축해제 완료')

압축 해제한 파일은 idx3_ubyte 파일임

위 4개의 파일에는 수많은 손글씨 데이터가 있다.

#그런데 왜 파일이 하나씩일까? --> MNIST 데이터 세트의 데이터는 자체적인 데이터베이스 형식이기 때문

그래서 위 데이터베이스 구조를 알아야 활용 가능합니다.

MNIST 페이지에서 확인 가능 ↓

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[offset][type] [value][description]

0000 32 bit integer 0x00000801(2049) magic number (MSB first)

0004 32 bit integer 60000 number of items

0008 unsigned byte ?? label

0009 unsigned byte ?? label

........

xxxx unsigned byte ?? label

The labels values are 0 to 9.

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset][type] [value][description]

0000 32 bit integer 0x00000803(2051) magic number

0004 32 bit integer 60000 number of images

0008 32 bit integer 28 number of rows

0012 32 bit integer 28 number of columns

0016 unsigned byte ?? pixel

0017 unsigned byte ?? pixel

........

xxxx unsigned byte ?? pixel

CSV 변환

왜 굳이 CSV로 전환하나?

머신러닝할때 바이너리 상태의 데이터로 가공/학습시키기는 적잖케 어렵다.

텍스트 상태의 데이터(cf. CSV0로 변환한뒤 학습시키는게 훨씬 직관적이고 수월하다

파이썬에서 binary 데이터 다루는 모듈인 struct 를 사용해서 CSV 로 전환한다

import struct

struct 소개

bibary file 읽기 : read(), open()

train 용 : 레이블 파일

lbl_f = open("./mnist/train-labels-idx1-ubyte", "rb")

lbl_f.read(8) # '현재 파일 위치'로 부터 8byte 읽기

lbl_f.read(8) # 그 다음의 8byte 읽기

다시 처음부터 (혹은 특정 지점으로 현재 파일 위치 이동하려면?)

seek(offset, from)

lbl_f.seek(0) # 파일의 맨 처음으로 '현재 파일 위치 이동
lbl_f.read(4)

lbl_f.seek(1) # '현재 파일 위치'를 파일 처음에서 1byte 뒤로 이동
lbl_f.read(4)

struct.unpack() 함수

unpack(fmt, buffer) -> (v1, v2, ...)

tuple 을 리턴한다

https://docs.python.org/2/library/struct.html

>II 의 의미는 big-endian 4byte Interger 2개

big endian ? little endian ?

바이너리 데이터 (파일, 네트워크) 을 다루면 꼭 다루는 이슈

https://ko.wikipedia.org/wiki/%EC%97%94%EB%94%94%EC%96%B8

처음 8byte를 읽어서 big-endian 방식 4byte Integer 2개로 읽기

MNIST 예제 파일은 big-endian 으로 저장되었기 때문

lbl_f.seek(0)
(mag, lbl_count) = struct.unpack('>II', lbl_f.read(8))
mag, lbl_count

이 다음부터는 한 바이트 씩 읽어 들임

"B" unsigned byte

struct.unpack("B", lbl_f.read(1))[0]

60000개의 label을 모두 불러 들이려면

lbl_f.seek(0)
(mag, lbl_count) = struct.unpack('>II', lbl_f.read(8))

for idx in range(lbl_count) :
label = struct.unpack("B", lbl_f.read(1))[0]

struct 사용하여 data(이미지) 읽기

train용 이미지(데이터) 파일

img_f = open("./mnist/train-images-idx3-ubyte", "rb")

img_f.seek(0)
mag, img_count = struct.unpack(">II", img_f.read(8))
print(mag, img_count)

row, col 정보

rows, cols = struct.unpack(">II", img_f.read(8))
print(rows, cols)

이미지 한개당 pixels 의 개수는

pixels = rows * cols
pixels

이미지 하나당 28 x 28 pixel 즉 784 pixel(784byte)

첫번째 이미지 읽어오기

bdata = img_f.read(pixels) # bytes 객체 리턴

bdata
type(bdata)

binary 데이터를 문자열 str의 list로 변환 -> map() 사용

bdata[0]

sdata = list(map(lambda n:str(n), bdata))
print(sdata)

CSV 변환을 위해 join()사용

",".join(sdata)

to_csv() 함수를 만들자

학습시킬 분량 (maxdata)를 정해서 CSV파일로 추출

def to_csv(name, maxdata) : # name(CSV파일명) 으로 저장

# 레이블 파일과 이미지 파일 열기
lbl_f = open("./mnist/" + name + "-labels-idx1-ubyte", "rb")
img_f = open("./mnist/" + name + "-images-idx3-ubyte", "rb")
csv_f = open("./mnist/" + name + ".csv", "w", encoding="utf-8")

# 다운받은 MNIST 데이터는 label 과 데이터(image) 가 각각 따로 있었지만
# 이를 하나의 CSV 파일에 저장

# 헤더 정보 읽기
mag, lbl_count = struct.unpack(">II", lbl_f.read(8))
mag, img_count = struct.unpack(">II", img_f.read(8))

# 이미지의 경우
# rows. cols 정보 읽기
rows, cols = struct.unpack(">II", img_f.read(8))
pixel = rows * cols

# 이미지의 '레이블'과 '데이터'를 읽고 CSV파일로 저장
res = []
for idx in range(lbl_count) : # 레이블의 개수만큼 (데이터의 개수와 동일)
  if idx > maxdata : break

  # 레이블
  label = struct.unpack("B", lbl_f.read(1))[0]

  # 데이터 (binary -> str)
  bdata = img_f.read(pixels)
  sdata = list(map(lambda n:str(n), bdata))

  # CSV만들기
  csv_f.write(str(label)+ ",")  # label을 첫번째 컬럼으로 지정
  csv_f.write(",".join(sdata) + "\n")   # 28x28 = 784 개의 컬럼으로 이미지 데이터 저장

csv_f.close()
lbl_f.close()
img_f.close()

to_csv("train", 1000)
to_csv("t10k", 500)

import pandas as pd

pd.read_csv("./mnist/train.csv", header=None)

이미지 데이터 학습시키기

from sklearn import model_selection, svm, metrics

산술 연산 가능한 '숫자'로 구성된 n차원 행렬 데이터로 변환해야함

현재 저장된 데이터

학습용 --> "./mnist/train.csv"

테스트용 --> "./mnist/t10k.csv"

다음과 같은 형태의 dict 로 가공하고 이를 학습시킬것이다

{

"labels" : [0, 3, 2, 1, 2, ....],

"images" : [

[첫번째 이미지 숫자값들 0.0 ~ 1.0],

[0.1, 0.32, 0.21, ...],

[세번째].

[네번째], ....] # 2차원 행렬

}

전처리 : normalizing

0.0 ~ 1.0 사이의 값으로 변환

CSV 파일 읽어 들이고 전처리까지 수행 후, dict 포맷으로 리턴

def load_csv(fname) :
labels = []
images = []

with open(fname, "r") as f :
for line in f :
cols = line.split(",")
labels.append(int(cols.pop(0))) # 첫번째 컬럼 (label) 추출

  # 나머지 28 x 28 개의 픽셀 데이터
  vals = list(map(lambda n:int(n) / 256, cols))     # normalizing
  images.append(vals)

return {"labels": labels, "images": images}

data = load_csv("./mnist/train.csv")
test = load_csv("./mnist/t10k.csv")

len(data["labels"])

학습하기

clf = svm.SVC()
clf.fit(data["images"], data["labels"])

학습데이터 저장하고 불러오기

import joblib
import os

savefile = "mnist.pkl"

pkfile = os.path.join(savepath, savefile)
joblib.dump(clf, pkfile)

불러오기

clf = None
clf = joblib.load(pkfile)

예측하기

predict = clf.predict(test["images"])

predict

점수 확인

ac_score = metrics.accuracy_score(test['labels'], predict)

ac_score

cl_report = metrics.classification_report(test['labels'], predict)
print(cl_report)

더 많은 데이터 학습

to_csv("train", 60000)

data = load_csv("./mnist/train.csv")
test = load_csv("./mnist/t10k.csv")

clf = svm.SVC()
clf.fit(data['images'], data['labels'])

predict = clf.predict(test["images"])
ac_score = metrics.accuracy_score(test['labels'], predict)
print('정답률 = ', ac_score)

cl_report = metrics.classification_report(test['labels'], predict)
print(cl_report)

임의의 손글씨 이미지

from PIL import Image
import PIL.ImageOps as ops

img = Image.open(os.path.join(savepath, "test.bmp"))
img

img.size # (rows, cols)

img.getdata() # pixel 데이터

list(img.getdata())[:10] # (R,G,B) 채널

len(list(img.getdata())) # pixel의 개수 rows x cols (300x300)

이미지 모드 변환 24bit --> 8bit mono (gray scale)

mono8img = img.convert('L')
mono8img

이미지 반전

invImg = ops.invert(mono8img)
invImg

이미지 사이즈 변경 --> 28 x 28

resizeImg = invImg.resize((28,28))
resizeImg

pixel data 보기

print(list(resizeImg.getdata())) # 0 ~ 255

normalizing

dataImg = list(map(lambda n: int(n)/256, list(resizeImg.getdata())))
print(dataImg)

clf.predict([dataImg])

profile
독해지자

0개의 댓글