やりたいこと
機械学習用の学習/検証データのサンプルを集めている。
- MNISTをダウンロードして、中身がどうなっているか調べたい。
- そのためにpythonでubyteからpngを作成する処理を実装したい。
前提
- pythonでのnumpyやPILの基礎知識があること
- それらの実行環境を構築済みであること
MNISTとは?
0から9までの「手書き数字」の画像データ。
「手書きの数字をAIで識別して分類する」などの機械学習に使う。
http://yann.lecun.com/exdb/mnist/
から無料でダウンロードできる。
ファイル構成
- 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
画像データといっても、.jpg のようなフォーマットではないため、
そのままではプレビューできない。
画像化してプレビューする方法
たとえばpythonのコードを書いて、numpyやPILで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
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
解説
学習用の画像データ
train-images-idx3-ubyte
の構造は下記の通り。
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 になる。
値を実際に確認したければ、
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以降には画像が入っており
bytes_per_image = number_of_rows * number_of_columns
raw_img = f.read(bytes_per_image)
のように読み込むことができる。
あとはnumpyに突っ込んでpng形式で保存している。
ループ処理で連続してpngを出力する
下記のようにpng出力処理をループで回せば、連続して画像化できる。
あとは、出力したい枚数が10枚であれば range(10): のように、ループ回数を任意に指定すればよい。
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")
出力結果は下記。
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]
これは画像ファイルを構成する各ピクセル位置と、その色情報であることが分かる。
ラベルデータについて
前述の実装は画像データ(Images)をpng化するものだったが、
これに加えて、ラベルデータ(Labels)も確認する必要がある。
そのための実装は下記の通りである。
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ずつ読んでいけば、ラベルが読み取れる。
ループで実装するなら下記。
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の出力結果と比較する。
それぞれのpng画像が「何の数字であるか?」を、ラベルにて正しく示している。