mnist 숫자를 파일로 저장

손으로 쓴 숫자를 딥러닝으로 분류하고 싶다.
손으로 쓴 다음 카메라로 찍거나
그림판에서 동일한 크기로 마우스로 숫자를 그리거나 해보면
결과가 잘 나오지 않는다.
그렇다고 검색한 결과를 캡쳐하는 것은 바보 같다는 생각이 들고.

결과가 잘 나오지 않는 것은
이미지가 너무 작아서 축소하는 과정에서
mnist 원본과 같은 분포의 픽셀이 잘 만들어지지 않기 때문이다.
잘 할 수 있는 사람도 있겠지만
나는 이런저런 실패만 맛보았다.

이걸 하고 싶었던 이유는
스마트폰에 딥러닝 모델을 얹고 이미지로 결과를 보고 싶은데
mnist 모델이 가장 쉽고 단순하기 때문이다.
사진을 촬영하는 것 대신
다운로드한 파일을 읽어서 처리하면 같은 방식이기 때문에
숫자 이미지 파일이 필요했다.

구현하다 보니까
점점 정교한 이미지가 필요해서 결국 3가지 형태로 발전했다.
완전 정교하진 않지만 마지막 방법을 가장 선호하지 않을까 싶다, 나처럼!

먼저 공통 함수 몇 개를 정의했다.
티스토리 에디터가 업그레이드되고 나서 코드에 색상이 들어가지 않는다.
코드 블록을 넣어서 처리하도록 되었는데
편집할 때는 존재하던 색상이 외부에서 접근하게 되면 사라진다.
티스토리, 대단하다.
이런 코드를 보게 해줘서 고맙고 카카오의 미래는 참 밝아 보인다.

from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import numpy as np
import os


# mnist 데이터셋 중에서 test 데이터셋 사용
# 'mnist' 폴더에 파일 다운로드
def get_dataset():
    mnist = input_data.read_data_sets('mnist')
    return mnist.test.images, mnist.test.labels


# 파일을 저장할 폴더 확인 후 생성
def make_directory(dir_path):
    if not os.path.exists(dir_path):
        os.mkdir(dir_path)


# 파일 이름은 한 자리 숫자가 앞에 오고, 세 자리 일련번호가 뒤에 온다.
# 일련번호 최대 숫자는 999이기 때문에 1천장을 넘어가면 형식이 어긋난다.
def get_file_path(digit, order, dir_path):
    filename = '{}_{:03d}.png'.format(digit, order)
    return os.path.join(dir_path, filename)

 

첫 번째 저장 코드는 가장 단순한 형태.
test 데이터셋의 처음부터 지정한 개수만큼 저장한다.
이때 파일 이름은 앞에 정답에 해당하는 label이 오고 뒤에 몇 번째 파일인지를 가리키는 일련번호가 온다.

# mnist의 test 데이터셋으로부터 지정한 개수만큼 파일로 변환해서 저장
def make_digit_images_from_mnist(count, dir_path):
    images, labels = get_dataset()

    make_directory(dir_path)

    if count > len(images):
        count = len(images)

    for i in range(count):
        file_path = get_file_path(labels[i], i, dir_path)   # i는 일련번호 + 배열 인덱스
        plt.imsave(file_path, images[i].reshape(28, 28), cmap='gray')


make_digit_images_from_mnist(30, './mnist/new_data')

 

두 번째는 난수 개념을 추가했다.
매번 앞에서만 가져오면 항상 똑같은 순서와 똑같은 숫자만 가져오니까.
사람이라면 이런 생각이 나지 않을 수 없다.

# 난수 샘플링 적용
def make_random_images_from_mnist(count, dir_path):
    images, labels = get_dataset()

    make_directory(dir_path)

    if count > len(images):
        count = len(images)

    # 중복되지 않는 인덱스 추출
    series = np.arange(len(images))
    indices = np.random.choice(series, count, replace=False)

    for i, idx in enumerate(indices):
        # i는 일련번호, idx는 배열의 인덱스
        file_path = get_file_path(labels[idx], i, dir_path)
        plt.imsave(file_path, images[idx].reshape(28, 28), cmap='gray')


make_random_images_from_mnist(30, './mnist/new_data')

 

세 번째는 생성되는 숫자 개수를 똑같이 맞췄다.
앞의 방법들로 가져와서 보면 샘플 숫자의 개수가 일정하지 않아서
예측에 사용하기에는 많이 불편했다.
코드는 길어졌지만, 복사해서 사용할거니까 이걸 써야겠다.

# 숫자별로 지정한 개수만큼 동일하게 추출. 추출 개수는 digit_count * 10.
def make_random_images_from_mnist_by_digit(digit_count, dir_path):
    images, labels = get_dataset()

    make_directory(dir_path)

    if digit_count > len(images) // 10:
        digit_count = len(images) // 10

    # 동일 개수를 보장해야 하므로 전체 인덱스 필요
    indices = np.arange(len(images))
    np.random.shuffle(indices)

    # 0~9까지 10개 key를 사용하고, value는 0에서 시작
    extracted_digit = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}
    extracted_total = 0

    for idx in indices:
        digit = labels[idx]

        # 숫자별 최대 개수를 채웠다면, 처음으로.
        if extracted_digit[digit] >= digit_count:
            continue

        # 현재 숫자 증가
        extracted_digit[digit] += 1

        file_path = get_file_path(digit, extracted_total, dir_path)
        plt.imsave(file_path, images[idx].reshape(28, 28), cmap='gray')

        # 추출 숫자 전체와 추출해야 할 개수 비교
        extracted_total += 1
        if extracted_total >= digit_count * 10:
            break


make_random_images_from_mnist_by_digit(digit_count=2, dir_path='./mnist/new_data')

 

생성된 파일을 안드로이드 에뮬레이터에 넣는 방법은 아래 사이트를 참고하자.
아이폰 시뮬레이터는 직접 찾아보자.
요즘엔 대부분의 테스트를 안드로이드에서 하고 있다. ^^

[Cordova] android studio emulater에 이미지 파일 넣는 방법