본문 바로가기

Study/AI

머신러닝 공부 #3

04. 머신러닝

  • 4-1. 머신러닝이란?
  • 4-2. 머신러닝 첫걸음
  • 4-3. 이미지 내부의 문자 인식
  • 4-4. 외국어 문장 판별하기
  • 4-5. 서포트 벡터 머신(SVM)
  • 4-6. 랜덤 포레스트
  • 4-7. 데이터를 검증하는 방법

4-3. 이미지 내부의 문자 인식

이번 절에서 배울 내용

  • 이미지 내부의 문자 인식
  • MNIST 데이터를 이용한 손글씨 숫자 인식

알고리즘과 툴

  • MNIST 손글씨 숫자 데이터
  • sckit-learn 라이브러리
  • SVM 알고리즘

손글씨 숫자 인식하기

MNIST - 손글씨 숫자 데이터

4개의 파일을 내려받고 GZip 압축을 해제하는 프로그램을 만들어봅시다.

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",
        "train-labels-idx1-ubyte.gz",
        "t10k-images-idx3-ubyte.gz",
        "t10k-labels-idx1-ubyte.gz"]
        
# 다운로드
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)

# GZip 압축 해제
for f in files:
    gz_file = savepath + "/" + f
    raw_file = savepath + "/" + f.replace(".gz", "")
    print("gzip: ", f)
    with gzip.open(gz_file, "rb") as fp:
        body = fp.read()
        with open(raw_file, "wb") as w:
            w.write(body)
print("ok")

gzip: train-images-idx3-ubyte.gz

gzip: train-labels-idx1-ubyte.gz

gzip: t10k-images-idx3-ubyte.gz

gzip: t10k-labels-idx1-ubyte.gz

ok

 

".gz"로 압축돼 있는 파일을 해제하면 각각 파일 하나밖에 없습니다.

MNIST 데이터 세트의 데이터는 자체적인 데이터베이스 형식이기 때문입니다.따라서 해당 데이터베이스 형식을 분석해야 이미지 같은 데이터를 뽑아낼 수 있습니다.

 

이미지 데이터는 각 픽셀을 그레이스케일 256단계로 나타내며, 왼쪽 위부터 오른쪽 아래로 차례차례 픽셀이 나열된 형태입니다.이때 0은 기본 배경색(흰색)이고, 1~255가 실제 손글씨가 적힌 부분을 나타냅니다.숫자가 클수록 짙은 부분을 나타냅니다.

 

따라서 일단 파이썬으로 바이너리 데이터를 분석하고, 바이너리 데이터는 다루기 힘들므로 CSV 파일로 변환하겠습니다.

 

<레이블> <28x28의 픽셀 데이터, ...>5, 0,0,0,0,0,0,0 30, 80, 100, 120, 0, 0, 0, ...

 

import struct

def to_csv(name, maxdata):
    # 레이블 파일과 이미지 파일 열기
    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")
    
    # 헤더 정보 읽기 *1
    mag, lbl_count = struct.unpack(">II", lbl_f.read(8))
    mag, imag_count = struct.unpack(">II", img_f.read(8))
    rows, cols = struct.unpack(">II", img_f.read(8))
    pixels = rows * cols
    
    # 이미지 데이터를 읽고 CSV 저장하기 *2
    res = []
    for idx in range(lbl_count):
        if idx > maxdata: break
        label = struct.unpack("B", lbl_f.read(1))[0]
        bdata = img_f.read(pixels)
        sdata = list(map(lambda n: str(n), bdata))
        
        csv_f.write(str(label) + ",")
        csv_f.write(",".join(sdata) + "\r\n")
        
        # 잘 저장됐는지 이미지 파일로 저장해서 테스트 하기 *3
        if idx < 10:
            s = "P@ 28 28 255\n"
            s += " ".join(sdata)
            iname = "./mnist/{0}-{1}-{2}.pgm".format(name, idx, label)
            with open(iname, "w", encoding="utf-8") as f:
                f.write(s)
    csv_f.close()
    lbl_f.close()
    img_f.close()
    
 # 결과를 파일로 출력하기 *4
to_csv("train", 1000)
to_csv("t10k", 500)
$ python3 mnist-tocsv.py

프로그램을 실행하면 "train.csv"와 "t10k.csv"라는 2개의 CSV 파일이 출력됩니다.

또한 정말로 제대로 된 데이터를 출력하는지 확인할 수 있게 앞의 10개를 이미지 파일로 저장했습니다.

 

파이썬으로 바이너리 처리를 하기 위해 struct 모듈을 사용했습니다.

여기서 포인트는 open() 으로 열 때 "r"이 아니라, 바이너리 파일 열기를 나타내는 "br"을 사용했다는 것입니다.

또한 원하는 바이너리 수만큼 읽어 들이고, 정수로 변환할 때는 struct.unpack()을 사용합니다.

 

*1 에서는 헤더 정보를 읽어 들입니다. 데이터는 리틀 엔디안으로 저장돼 있습니다.

struct 모듈로 리틀 엔디안 데이터를 읽어 들일 때는 ">" 라는 기호를 지정합니다.

 

*2 에서는 이미지 개수만큼 반복해서 데이터를 읽어 들입니다. 다만 6만 개의 이미지 데이터를 처리하는 데는 시간이 너무 오래 걸리므로 일단 학습 전용 데이터로 1000개, 테스트 데이터로 500개 csv에 출력했습니다.

 

*3 에서는 PGM 형식으로 이미지 데이터를 저장했습니다.

PGM 형식의 파일은 글자 파일로 이미지를 나타낼 수 있습니다. 간단하게 이미지를 생성하고자 활용했습니다.

 

*4 에서는 파일명과 갯수를 지정했습니다.

 

 이미지 데이터 학습시키기

이미지 픽셀 데이터를 24x24(576)의 벡터로 그대로 넣어 학습시키는 프로그램입니다.

 

(1) CSV 파일에서 학습 데이터와 테스트 데이터를 읽습니다.

(2) 학습 데이터를 사용해 이미지 픽셀을 학습시킵니다.

(3) 테스트 데이터를 활용해 예측합니다.

(4) 예측 결과와 답을 비교해서 정답률을 구합니다.

 

from sklearn import model_selection, svm, metrics

# CSV 파일을 읽어 들이고 가공하기 *1
def load_csv(filename):
    labels = []
    images = []
    with open(filename, "r") as f:
        for line in f:
            cols = line.split(",")
            if len(cols) < 2: continue
            labels.append(int(cols.pop(0)))
            vals = list(map(lambda n: int(n) / 256, cols))
            images.append(vals)
    return {"labels":labels, "images":images}
    
data = load_csv("./mnist/train.csv")
test = load_csv("./mnist/t10k.csv")

# 학습하기 *2
clf = svm.SVC()
clf.fit(data["images"], data["labels"])

# 예측하기 *3
predict = clf.predict(test["images"])

# 결과 확인하기 *4
ac_score = metrics.accuracy_score(test["labels"], predict)
cl_report = metrics.classification_report(test["labels"], predict)
print("정답률 =", ac_score)
print("리포트 =")
print(cl_report)

*1 에서는 CSV 파일을 읽어 들이고, 레이블과 이미지 데이터를 배열로 만듭니다.

이때 이미지 데이터의 각 픽셀은 0부터 255까지의 정수인데, 이를 256으로 나누기 때문에 0이상이고 1미만인 실수 벡터가 됩니다.

 

*2 에서는 scikit-learn의 SVM(SVC) 알고리즘을 사용하고, fit() 메소드로 학습을 합니다.

*3 에서는 predict() 메소드로 에측합니다.

*4 예측 결과와 실제 정답 레이블을 비교해서 정답률을 화면에 출력합니다.

 

6만 개의 모든 데이터 학습시키기

앞서 1000개의 데이터를 학습시키고, 500개의 테이터로 테스트 했는데, 이번에는 6만 개의 모든 데이터를 사용해봅시다.

이전의 CSV 생성 프로그램 "mnist-tocsv.py" *4에서의 "train" 옆의 매개변수를 9999 등으로 설정해 모든 데이터를 CSV 파일로 출력합니다