MNISTをpythonでロードしてpngに出力する手順

# やりたいこと

機械学習用の学習／検証データのサンプルを集めている。

* MNISTをダウンロードして、中身がどうなっているか調べたい。
* そのためにpythonでubyteからpngを作成する処理を実装したい。

# 前提

* pythonでのnumpyやPILの基礎知識があること
* それらの実行環境を構築済みであること

# MNISTとは？

0から9までの「手書き数字」の画像データ。
「手書きの数字をAIで識別して分類する」などの機械学習に使う。
http://yann.lecun.com/exdb/mnist/
から無料でダウンロードできる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/6ed2cd77-636b-73b4-92da-59ffbf1b7e75.png)


# ファイル構成
* train-images-idx3-ubyte.gz : 学習用の画像データ
* train-labels-idx1-ubyte.gz : 学習用のラベルデータ
* t10k-images-idx3-ubyte.gz : 検証用の画像データ
* t10k-labels-idx1-ubyte.gz : 検証用のラベルデータ

# ファイルの中身

gzファイルを解凍すると、下記のようなバイナリファイルになる。

```
t10k-images.idx3-ubyte
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/c327f718-cace-5263-7ad0-ce59a1f2f394.png)

画像データといっても、.jpg のようなフォーマットではないため、
そのままではプレビューできない。

# 画像化してプレビューする方法

たとえばpythonのコードを書いて、numpyやPILでpngに出力すれば、普通の画像ファイルとして表示できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/ec4a14fc-5c59-1ab7-9815-01a37094445d.png)


# 事前準備

wgetやunzipがまだ入っていなければインストールしておく。ubuntuの場合は下記。

```
apt-get install -y wget
```
```
apt-get install unzip
```

# ファイルのダウンロード

まずはwgetでgzをダウンロードしておく。


```
wget http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
```

```
wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
```

そして解凍する。


```
gunzip train-images-idx3-ubyte.gz
```

```
gunzip train-labels-idx1-ubyte.gz
```

すると

```
-rw-r--r--. 1 root root 47040016 Jul 21  2000 train-images-idx3-ubyte
-rw-r--r--. 1 root root    60008 Jul 21  2000 train-labels-idx1-ubyte
```

が出力される。
続いてpythonのコードを書く。










# 実装（Imagesの画像化）

vi test.py

```python
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.datasets import mnist

import os
import numpy as np
import matplotlib.pyplot as plt
import struct
from PIL import Image

trainImagesFile = open('./train-images-idx3-ubyte','rb')
trainLabelsFile = open('./train-labels-idx1-ubyte','rb')

f = trainImagesFile

magic_number = f.read( 4 )
magic_number = struct.unpack('>i', magic_number)[0]

number_of_images = f.read( 4 )
number_of_images = struct.unpack('>i', number_of_images)[0]

number_of_rows = f.read( 4 )
number_of_rows = struct.unpack('>i', number_of_rows)[0]

number_of_columns = f.read( 4 )
number_of_columns = struct.unpack('>i', number_of_columns)[0]

bytes_per_image = number_of_rows * number_of_columns

raw_img = f.read(bytes_per_image)
format = '%dB' % bytes_per_image
lin_img = struct.unpack(format, raw_img)
np_ary = np.asarray(lin_img).astype('uint8')
np_ary = np.reshape(np_ary, (28,28),order='C')

pil_img = Image.fromarray(np_ary)
pil_img.save("output.png")
```

# 実行

```
python test.py
```

# 出力結果

output.png

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/86559a06-9d2b-41f9-e13f-60fc1826e22a.png)


# 解説

学習用の画像データ
train-images-idx3-ubyte
の構造は下記の通り。

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

```
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
```

上記に従い、offsetを4ずつズラしながら順次読み込んでいく。


```
magic_number = f.read( 4 )
```
この結果は 2051 になる。


```
number_of_images = f.read( 4 )
```

この結果は 60000 になる。


```
number_of_rows = f.read( 4 )
```

この結果は 28 になる。


```
number_of_columns = f.read( 4 )
```

この結果は 28 になる。

値を実際に確認したければ、

```python
print('--------------------')
print('magic_number');
print(magic_number);
print('--------------------')
print('number_of_images');
print(number_of_images);
print('--------------------')
print('number_of_rows');
print(number_of_rows);
print('--------------------')
print('number_of_columns');
print(number_of_columns);
```

のように出力すれば確認できる。


```
--------------------
magic_number
2051
--------------------
number_of_images
60000
--------------------
number_of_rows
28
--------------------
number_of_columns
28
```
そして、

```
[offset] [type]          [value]          [description]
0016     unsigned byte   ??               pixel
```

となっているので、offset16以降には画像が入っており

```python
bytes_per_image = number_of_rows * number_of_columns
raw_img = f.read(bytes_per_image)
```

のように読み込むことができる。
あとはnumpyに突っ込んでpng形式で保存している。

# ループ処理で連続してpngを出力する

下記のようにpng出力処理をループで回せば、連続して画像化できる。
あとは、出力したい枚数が10枚であれば range(10): のように、ループ回数を任意に指定すればよい。

```python
for num in range(10):
    raw_img = f.read(bytes_per_image)
    format = '%dB' % bytes_per_image
    lin_img = struct.unpack(format, raw_img)
    np_ary = np.asarray(lin_img).astype('uint8')
    np_ary = np.reshape(np_ary, (28,28),order='C')
    pil_img = Image.fromarray(np_ary)
    pil_img.save("output" + str(num)  + ".png")

```

出力結果は下記。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/6fb9690d-799c-9b78-bb9e-3a91fb632c8f.png)


# numpy配列とpngの比較

numpy配列であるnp_aryをprint()で表示させると、下記のような配列データになっている。

```
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   3  18  18  18 126 136  175  26 166 255 247 127   0   0   0   0]
 [  0   0   0   0   0   0   0   0  30  36  94 154 170 253 253 253 253 253  225 172 253 242 195  64   0   0   0   0]
 [  0   0   0   0   0   0   0  49 238 253 253 253 253 253 253 253 253 251   93  82  82  56  39   0   0   0   0   0]
 [  0   0   0   0   0   0   0  18 219 253 253 253 253 253 198 182 247 241    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0  80 156 107 253 253 205  11   0  43 154    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0  14   1 154 253  90   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0 139 253 190   2   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0  11 190 253  70   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0  35 241 225 160 108   1    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0  81 240 253 253 119   25   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0  45 186 253 253  150  27   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  16  93 252  253 187   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 249  253 249  64   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0  46 130 183 253  253 207   2   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0  39 148 229 253 253 253  250 182   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0  24 114 221 253 253 253 253 201   78   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0  23  66 213 253 253 253 253 198  81   2    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0  18 171 219 253 253 253 253 195  80   9   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0  55 172 226 253 253 253 253 244 133  11   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0 136 253 253 253 212 135 132  16   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0    0   0   0   0   0   0   0   0   0   0]


```

これは画像ファイルを構成する各ピクセル位置と、その色情報であることが分かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/88a3c2ea-974e-b4f6-03a7-aa8995edeb9e.png)


# ラベルデータについて

前述の実装は画像データ（Images）をpng化するものだったが、
これに加えて、ラベルデータ（Labels）も確認する必要がある。
そのための実装は下記の通りである。

```python
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.datasets import mnist

import os
import numpy as np
import matplotlib.pyplot as plt
import struct
from PIL import Image

trainImagesFile = open('./train-images-idx3-ubyte','rb')
trainLabelsFile = open('./train-labels-idx1-ubyte','rb')

f = trainLabelsFile

magic_number = f.read( 4 )
magic_number = struct.unpack('>i', magic_number)[0]

number_of_images = f.read( 4 )
number_of_images = struct.unpack('>i', number_of_images)[0]

print("--------------------")
print("magic_number")
print(magic_number)
print("--------------------")
print("number_of_image")
print(number_of_images)
print("--------------------")

label_byte = f.read( 1 )
label_int = int.from_bytes(label_byte, byteorder='big')
print(label_int)
```

### 出力結果

```
--------------------
magic_number
2049
--------------------
number_of_image
60000
--------------------
5
```

ラベルデータの構造は下記の通り。

### train-labels-idx1-ubyte

```
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.
```

つまり、offset 8 以降を、1ずつ読んでいけば、ラベルが読み取れる。
ループで実装するなら下記。

```python

for num in range(10):
    label_byte = f.read( 1 )
    label_int = int.from_bytes(label_byte, byteorder='big')
    print(label_int)
```

出力結果は下記。

```
5
0
4
1
9
2
1
3
1
4
```

Imagesの出力結果と比較する。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/6fb9690d-799c-9b78-bb9e-3a91fb632c8f.png)


それぞれのpng画像が「何の数字であるか？」を、ラベルにて正しく示している。