MNISTデータの画像化

投稿者: | 2018年12月22日

MNIST(エムニスト)データとは、手書き数字の画像データを集めたもので、機械学習の材料として頻繁に登場します。

MNIST とは Modifed National Institute of Standards and Technology の略で、MNIST Databseとはアメリカ国立標準技術研究所で構築された手書き文字データベースを、機械学習により適合するように統合し直したものです。

 

MNISTデータの現物は、下記のサイトから入手することができます。

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

 

ここで公開されているファイルは全部で4つあり、「訓練用」の画像とラベルのデータがそれぞれ60,000件、「検証用」の画像とラベルのデータがそれぞれ10,000件収められています。

  • train-images-idx3-ubyte.gz :訓練用の画像データ
  • train-labels-idx1-ubyte.gz :訓練用のラベルデータ
  • t10k-images-idx3-ubyte.gz :検証用の画像データ
  • t10k-labels-idx1-ubyte.gz :検証用のラベルデータ

 

ファイルはgz形式ですが、これを解凍しても、画像データが”jpg”や”png”といったお馴染みの形式で表れるわけではありません。
データは全て、一つのバイナリデータとしてまとめて格納されています。

試しに、訓練用の画像データである”train-images-idx3-ubyte.gz”を解凍してみましょう。中身は次のように、通常では開けない拡張子のファイルが一つだけ入っています。

 

 

これをバイナリエディタで表示してみましょう。エディタとしてはStirlingを使用しました。

 

このままでは何が何だか分かりませんが、データのフォーマット自体は先ほど示した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

Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

となっており、最初の16バイトのヘッダ部分から、このファイルには28*28サイズの画像が60000枚格納されていることがわかります。 17バイト目から画像データが開始されます。

こんなふうに。

 

今回は、このデータに何が描かれているのか、画像で可視化してみたいと思います。解析の言語はJavaを使いました。

コードはこんな感じです。

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;

import javax.imageio.ImageIO;

public class Sample {
	private static FileInputStream fis;

	public static void main (String[] args) throws Exception {
		String path = "C:\\sample\\train-images-idx3-ubyte\\train-images.idx3-ubyte";

		byte[] b0 = new byte[16]; // 画像以外の部分
		byte[] b1 = new byte[28*28]; // 1枚目の画像

		fis = new FileInputStream(path);
		fis.read(b0);
		fis.read(b1);

		int width = 28;
		int height = 28;
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

		for (int i = 0; i < 784; i++) {
			int c = b1[i];
			int r = r(c);
			int g = g(c);
			int b = b(c);
			int rgb = rgb(r,g,b);

			image.setRGB(i%28,i/28,rgb); // 縦横28pixelずつで再構成
		}

		ImageIO.write(image, "png", new File("C:\\sample\\test.png"));

	}

	public static int r(int c){
		return c>>16&0xff;
	}

	public static int g(int c){
		return c>>8&0xff;
	}

	public static int b(int c){
		return c&0xff;
	}

	public static int rgb (int r,int g,int b){
		return 0xff000000 | r <<16 | g <<8 | b;
	}

}

 

保存した画像を表示してみました。1枚目の画像には”5″が格納されていたことがわかります。

 

 

 

 

もう少しプログラムを改修してみましょう。最初の100個を10×10で表示してみることにします。

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;

import javax.imageio.ImageIO;

public class Sample {
	private static FileInputStream fis;

	public static void main (String[] args) throws Exception {
		String path = "C:\\sample\\train-images-idx3-ubyte\\train-images.idx3-ubyte";

		byte[] b0 = new byte[16];
		byte[] b1 = new byte[28*28];
		fis = new FileInputStream(path);
		fis.read(b0);
		//fis.read(b1);

		int width = 28*10;
		int height = 28*10;
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

		// 28*28の画像を10個横に並べたものを縦につなげていく
		for (int y = 0; y < 10; y++) {
			// 28*28の画像を横につなげていく
			for (int x = 0; x < 10; x++) {
				fis.read(b1);
				for (int i = 0; i < 784; i++) {
					int c = b1[i];
					int r = r(c);
					int g = g(c);
					int b = b(c);
					int rgb = rgb(r,g,b);

					image.setRGB(x*28+i%28,y*28+i/28,rgb); // 縦横28pixelずつで再構成
				}
			}
		}

		ImageIO.write(image, "png", new File("C:\\sample\\test2.png"));

	}

	public static int r(int c){
		return c>>16&0xff;
	}

	public static int g(int c){
		return c>>8&0xff;
	}

	public static int b(int c){
		return c&0xff;
	}

	public static int rgb (int r,int g,int b){
		return 0xff000000 | r <<16 | g <<8 | b;
	}

}

 

保存した画像を開くと、100文字が表示されます。このようにMNISTデータには、手書き文字データが格納されていたことが分かりました。

カテゴリー: AI