barus's diary

とても真面目なblogですにゃ

VC++とPythonで文字画像訓練データ(MNIST dataset)を読み込む for VS2017

Pythonで書かれたニューラルネットワーク

サンプルコード(Network.py)を前々回の記事と、前回の記事

ざっくり見た。

 

Pythonで書かれたコードをC++に落とし込む。 

その事前準備段階として訓練データをVC++で読み込んでみた。

今回はVC++で訓練データを読み込むサンプルコードを紹介する。

 

まずは、Pythonの場合 

from chainer import serializers, Variable

import gzip, numpy, sys
import _pickle as cPickle
if len(sys.argv) != 2:
  quit()
data_index=int(sys.argv[1])
f=gzip.open('mnist_expanded.pkl.gz','rb')
train_set, valid_set, test_set=cPickle.load(f, encoding='latin1')
train_set_x, train_set_y=train_set # setの中身が
for i in range(data_index,data_index+1):
  for y in range(0,28):
    for x in range(0,28):
      if train_set_x[i][y*28+x]<0.5:
        sys.stdout.write(" ")
      elif train_set_x[i][y*28+x]<0.8:
        sys.stdout.write("+")
      else:
        sys.stdout.write("*")
    sys.stdout.write("\n")
  print ("correct =",train_set_y[i])
  print ("--------------------------------")

 となる。

  

 VC++の場合

ニューラルネットワークと深層学習で紹介されていた

訓練データmnist.pkl.gzを用いず、 訓練データはここ

train-images.idx3-ubyte

train-labels.idx1-ubyte
 を用いる。

 

理由は、同サイトにC++のサンプルコードが

紹介されていたので、これを利用する。

 

mnist.pkl.gzと、上記のファイルのフォーマット形式は異なるが、

我々が欲しいデータは手書きデータなので

どちらかが読み込めれば問題ないかと思う。

 

 

訓練データはヘッダ部分と、

本体28x28ピクセルの文字データがバイナリデータとしてある。

 

実行する際は

train-images.idx3-ubyte

train-labels.idx1-ubyte

を実行ファイルと同じディレクトリに置く。

 

実行結果は以下のような感じとなる。 

>readmnist.exe 5

f:id:hatakeka:20170704203228p:plain

 

 BMPファイル

f:id:hatakeka:20170704195800p:plain

 

 

 

 

訓練データは、6万行。

そこから1~6万の間の数字を指定すると 

文字を抜き出す。

ちなみに、訓練データは米国の学生さんとか

そういう人達の手書きのデータらしい。

 

 

ちなみに、

train-images.idx3-ubyte  は、画像データ(28x28)

train-labels.idx1-ubyte    は、画像データに書かれた数字 

2つはペアとなっている。 

 

この、画像データと、画像データの正解を利用して

ニューラルネットワークに学習させている仕組みとなっている。

 

 

プロジェクトはコンソール画面を作成。

 

readmnist.cpp

bitmap.cpp

bitmap.h

mnist.cpp

mnist.h

 

を作成する。

(それ以外のファイルはプロジェクト作成時に

 デフォで自動生成されたもので変更なし。)

 

f:id:hatakeka:20170704203358p:plain

 

readmnist.cpp

  

// readmnist.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "bitmap.h"
#include "mnist.h"


void traingdata_test(char **argv);

/*
VC++ for VS2017
実行
>readmnist.exe 20
*/
int main(int argc, char **argv) {
	//----------------------------
	//トレーニングデータ読み込みテスト
	//----------------------------
	traingdata_test(argv);

}


void traingdata_test(char **argv)
{

	mnist mnist;

	//----------------------------
	//トレーニングデータ読み込み
	//----------------------------
	MNIST_dataset dataset;
	int index = atoi(argv[1]);
	cout << "tradingdata reading.. index=" << index << endl;
	dataset.training_images = mnist.read_training_images("train-images.idx3-ubyte", index);
	dataset.training_labels = mnist.read_training_labels("train-labels.idx1-ubyte", index);

	//----------------------------
	//画像に変換
	//----------------------------
	bitmap bitmap;
	bitmap.convert_to_bitmap(dataset.training_images, dataset.training_labels, index);
}

 

bitmap.cpp

 

#include "stdafx.h"
#include "bitmap.h"


bitmap::bitmap()
{
}


bitmap::~bitmap()
{
}


//filenameのBitmapファイルを読み込み、高さと幅、RGB情報をimg構造体に入れる
Image* bitmap::Read_Bmp(char *filename)
{
	int i, j;
	int real_width;					//データ上の1行分のバイト数
	unsigned int width, height;			//画像の横と縦のピクセル数
	unsigned int color;			//何bitのBitmapファイルであるか
	FILE *fp;
	unsigned char header_buf[HEADERSIZE];	//ヘッダ情報を取り込む
	unsigned char *bmp_line_data;  //画像データ1行分
	Image *img;

	if ((fp = fopen(filename, "rb")) == NULL) {
		fprintf(stderr, "Error: %s could not read.", filename);
		return NULL;
	}

	fread(header_buf, sizeof(unsigned char), HEADERSIZE, fp); //ヘッダ部分全てを取り込む

															  //最初の2バイトがBM(Bitmapファイルの印)であるか
	if (strncmp((const char*)header_buf, "BM", 2)) {
		fprintf(stderr, "Error: %s is not Bitmap file.", filename);
		return NULL;
	}

	memcpy(&width, header_buf + 18, sizeof(width)); //画像の見た目上の幅を取得
	memcpy(&height, header_buf + 22, sizeof(height)); //画像の高さを取得
	memcpy(&color, header_buf + 28, sizeof(unsigned int)); //何bitのBitmapであるかを取得

														   //24bitで無ければ終了
	if (color != 24) {
		fprintf(stderr, "Error: %s is not 24bit color image", filename);
		return NULL;
	}

	//RGB情報は画像の1行分が4byteの倍数で無ければならないためそれに合わせている
	real_width = width * 3 + width % 4;

	//画像の1行分のRGB情報を取ってくるためのバッファを動的に取得
	if ((bmp_line_data = (unsigned char *)malloc(sizeof(unsigned char)*real_width)) == NULL) {
		fprintf(stderr, "Error: Allocation error.\n");
		return NULL;
	}

	//RGB情報を取り込むためのバッファを動的に取得
	if ((img = Create_Image(width, height)) == NULL) {
		free(bmp_line_data);
		fclose(fp);
		return NULL;
	}

	//BitmapファイルのRGB情報は左下から右へ、下から上に並んでいる
	for (i = 0; i<height; i++) {
		fread(bmp_line_data, 1, real_width, fp);
		for (j = 0; j<width; j++) {
			img->data[(height - i - 1)*width + j].b = bmp_line_data[j * 3];
			img->data[(height - i - 1)*width + j].g = bmp_line_data[j * 3 + 1];
			img->data[(height - i - 1)*width + j].r = bmp_line_data[j * 3 + 2];
		}
	}

	free(bmp_line_data);

	fclose(fp);

	return img;
}

int bitmap::Write_Bmp(char *filename, Image *img)
{
	int i, j;
	FILE *fp;
	int real_width;
	unsigned char *bmp_line_data; //画像1行分のRGB情報を格納する
	unsigned char header_buf[HEADERSIZE]; //ヘッダを格納する
	unsigned int file_size;
	unsigned int offset_to_data;
	unsigned long info_header_size;
	unsigned int planes;
	unsigned int color;
	unsigned long compress;
	unsigned long data_size;
	long xppm;
	long yppm;

	if ((fp = fopen(filename, "wb")) == NULL) {
		fprintf(stderr, "Error: %s could not open.", filename);
		return 1;
	}

	real_width = img->width * 3 + img->width % 4;

	//ここからヘッダ作成
	file_size = img->height * real_width + HEADERSIZE;
	offset_to_data = HEADERSIZE;
	info_header_size = INFOHEADERSIZE;
	planes = 1;
	color = 24;
	compress = 0;
	data_size = img->height * real_width;
	xppm = 1;
	yppm = 1;

	header_buf[0] = 'B';
	header_buf[1] = 'M';
	memcpy(header_buf + 2, &file_size, sizeof(file_size));
	header_buf[6] = 0;
	header_buf[7] = 0;
	header_buf[8] = 0;
	header_buf[9] = 0;
	memcpy(header_buf + 10, &offset_to_data, sizeof(file_size));
	header_buf[11] = 0;
	header_buf[12] = 0;
	header_buf[13] = 0;

	memcpy(header_buf + 14, &info_header_size, sizeof(info_header_size));
	header_buf[15] = 0;
	header_buf[16] = 0;
	header_buf[17] = 0;
	memcpy(header_buf + 18, &img->width, sizeof(img->width));
	memcpy(header_buf + 22, &img->height, sizeof(img->height));
	memcpy(header_buf + 26, &planes, sizeof(planes));
	memcpy(header_buf + 28, &color, sizeof(color));
	memcpy(header_buf + 30, &compress, sizeof(compress));
	memcpy(header_buf + 34, &data_size, sizeof(data_size));
	memcpy(header_buf + 38, &xppm, sizeof(xppm));
	memcpy(header_buf + 42, &yppm, sizeof(yppm));
	header_buf[46] = 0;
	header_buf[47] = 0;
	header_buf[48] = 0;
	header_buf[49] = 0;
	header_buf[50] = 0;
	header_buf[51] = 0;
	header_buf[52] = 0;
	header_buf[53] = 0;

	//ヘッダの書き込み
	fwrite(header_buf, sizeof(unsigned char), HEADERSIZE, fp);

	if ((bmp_line_data = (unsigned char *)malloc(sizeof(unsigned char)*real_width)) == NULL) {
		fprintf(stderr, "Error: Allocation error.\n");
		fclose(fp);
		return 1;
	}

	//RGB情報の書き込み
	for (i = 0; i<img->height; i++) {
		for (j = 0; j<img->width; j++) {
			bmp_line_data[j * 3]     = img->data[(img->height - i - 1)*img->width + j].b;
			bmp_line_data[j * 3 + 1] = img->data[(img->height - i - 1)*img->width + j].g;
			bmp_line_data[j * 3 + 2] = img->data[(img->height - i - 1)*img->width + j].r;
		}
		//RGB情報を4バイトの倍数に合わせている
		for (j = img->width * 3; j<real_width; j++) {
			bmp_line_data[j] = 0;
		}
		fwrite(bmp_line_data, sizeof(unsigned char), real_width, fp);
	}

	free(bmp_line_data);

	fclose(fp);

	return 0;
}


Image* bitmap::Create_Image(int width, int height)
{
	Image *img;

	if ((img = (Image *)malloc(sizeof(Image))) == NULL) {
		fprintf(stderr, "Allocation error\n");
		return NULL;
	}

	if ((img->data = (Rgb*)malloc(sizeof(Rgb)*width*height)) == NULL) {
		fprintf(stderr, "Allocation error\n");
		free(img);
		return NULL;
	}

	img->width = width;
	img->height = height;

	return img;
}


void bitmap::convert_to_bitmap(
	std::vector<std::vector<Pixel>> training_images,
	std::vector<Label> training_labels,
	int index
)
{

	std::vector<Image> img;


	int size = training_images.size();
	int p = 0;
	for (int row = 0; row < size; ++row)
	{
		if (index == row)
		{
			Image image;
			image.height = 28;
			image.width = 28;
			image.data = new Rgb[28 * 28];
			int size2 = training_images[row].size();
			p = 0;
			for (int col = 0; col < size2; ++col)
			{
				int pixel = static_cast<int>(training_images[row][col]);

				if (pixel > 0)
				{
					//Pixel pix = static_cast<Pixel>(training_images[row][col]);
					cout << "*";
					image.data[p].r = 0;
					image.data[p].g = 0;
					image.data[p].b = pixel;
				}
				else
				{
					cout << " ";
					image.data[p].r = 255;
					image.data[p].g = 255;
					image.data[p].b = 255;
				}

				if (col % 28 == 0)cout << "," << endl;
				p++;
			}

			img.push_back(image);

			//同じ行数あるものとして処理
			int label_int = static_cast<int>(training_labels[row]);
			cout << "label=" << label_int << endl;

			//画像として保存
			char str[100];
			sprintf(str, "%05d_%d.bmp", row, label_int);
			Write_Bmp(str, &image);

			break;
		}
	}


}

bitmap.h

 

#pragma once


#include<stdlib.h>
#include<stdio.h>

#include <string>
#include <iostream>
#include <fstream>
#include <vector>

/*
Bitmapは大きく3つの部分に分かれています.

ファイルヘッダ	オフセット(0)	ファイルタイプ	BMといれる
オフセット(2)	ファイルサイズ	ファイル全体のサイズをバイト単位でいれる(ヘッダ含む)
オフセット(6)	予約領域1	常に0を入れておく
オフセット(8)	予約領域2	常に0を入れておく
オフセット(10)	画像データまでのオフセット	24bitのBitmapならカラーパレットがないため54となる
情報ヘッダ	オフセット(14)	情報ヘッダのサイズ	40を入れる
オフセット(18)	画像の幅	ピクセル単位
オフセット(22)	画像の高さ	ピクセル単位
オフセット(26)	プレーン数	常に1を入れる
オフセット(28)	何ビットのカラー画像であるか	今回は24と入れる
オフセット(30)	圧縮形式	非圧縮なので0を入れる
オフセット(34)	画像データのサイズ	ヘッダ部分を含まない(単純に幅と高さとRGBの3を掛ける)
オフセット(38)	水平解像度	0で問題ない
オフセット(42)	垂直解像度	0で問題ない
オフセット(46)	パレットの色数	パレットは使わないので0
オフセット(50)	重要なパレットのインデックス	使わないので0

*/

using namespace std;


#define FILEHEADERSIZE 14					//ファイルヘッダのサイズ
#define INFOHEADERSIZE 40					//情報ヘッダのサイズ
#define HEADERSIZE (FILEHEADERSIZE+INFOHEADERSIZE)

#define  Pixel uint8_t
#define  Label uint8_t

typedef struct {
	unsigned char b;
	unsigned char g;
	unsigned char r;
}Rgb;

typedef struct {
	unsigned int height;
	unsigned int width;
	Rgb *data;
}Image;


class bitmap
{
public:
	bitmap();
	~bitmap();
	void bitmap::convert_to_bitmap(
		std::vector<std::vector<Pixel>> training_images,
		std::vector<Label> training_labels,
		int index
	);
	Image *Read_Bmp(char *filename);
	int Write_Bmp(char *filename, Image *img);
	Image* bitmap::Create_Image(int width, int height);
};

 

mnist.cpp

 

#include "stdafx.h"
#include "mnist.h"





mnist::mnist()
{
}


mnist::~mnist()
{
}




/*
std::unique_ptr は、動的に確保されたポインタを格納し
std::unique_ptr がスコープから外れたとき、メモリを delete します。
std::unique_ptr<int> p1(new int(5));

std::unique_ptr<int> p2 = p1; // これは、コンパイルエラーになる。

std::unique_ptr<int> p3 = std::move(p1); // 所有権を移動する。
// p3は、メモリを所有する。p1は、無効になる。

p3.reset(); //メモリが delete される
p1.reset(); // なにもしません。

S:\plog\VS_c_mylib\simple C++reader for MNIST\mnist-master\include\mnist\mnist_reader_common.hpp
*/

/*!
* \brief Extract the MNIST header from the given buffer
* \param buffer The current buffer
* \param position The current reading positoin
* \return The value of the mnist header
*/
inline uint32_t mnist::read_header(const std::unique_ptr<char[]>& buffer, size_t position) {
	auto header = reinterpret_cast<uint32_t*>(buffer.get());

	auto value = *(header + position);
	return (value << 24) | ((value << 8) & 0x00FF0000) | ((value >> 8) & 0X0000FF00) | (value >> 24);
}


/*!
* \brief Read a MNIST file inside a raw buffer
* \param path The path to the image file
* \return The buffer of byte on success, a nullptr-unique_ptr otherwise
https://github.com/wichtounet/mnist
*/
inline std::unique_ptr<char[]> mnist::read_mnist_file(const std::string& path, uint32_t key) {
	std::ifstream file;
	file.open(path, std::ios::in | std::ios::binary | std::ios::ate);

	if (!file) {
		std::cout << "Error opening file" << std::endl;
		return {};
	}


	auto size = file.tellg();
	std::unique_ptr<char[]> buffer(new char[size]);
	//char[] buffer(new char[size]);

	//Read the entire file at once
	file.seekg(0, std::ios::beg);
	file.read(buffer.get(), size);
	file.close();

	auto magic = read_header(buffer, 0);
	cout << "magic=" << magic << endl;

	if (magic != key) {
		std::cout << "Invalid magic number, probably not a MNIST file" << std::endl;
		return {};
	}

	auto count = read_header(buffer, 1);

	if (magic == 0x803) {
		auto rows = read_header(buffer, 2);
		auto columns = read_header(buffer, 3);

		cout << "count=" << count << ",rows=" << rows << ",cols=" << columns << endl;

		if (size < count * rows * columns + 16) {
			std::cout << "The file is not large enough to hold all the data, probably corrupted" << std::endl;
			return {};
		}
	}
	else if (magic == 0x801) {
		if (size < count + 8) {
			std::cout << "The file is not large enough to hold all the data, probably corrupted" << std::endl;
			return {};
		}
	}

	return buffer;
}

/*!
* \brief Read a MNIST image file and return a container filled with the images
* \param path The path to the image file
* \return A std::vector filled with the read images
*/
std::vector<std::vector<Pixel>> mnist::read_mnist_image_file(const std::string& path, int index) {
	auto buffer = read_mnist_file(path, 0x803);

	if (buffer) {
		auto count = read_header(buffer, 1);
		auto rows = read_header(buffer, 2);
		auto columns = read_header(buffer, 3);

		//Skip the header
		//Cast to unsigned char is necessary cause signedness of char is
		//platform-specific
		auto image_buffer = reinterpret_cast<unsigned char*>(buffer.get() + 16);

		std::vector<std::vector<Pixel>> images;
		images.reserve(count);

		for (size_t i = 0; i < count; ++i) {
			images.emplace_back(rows * columns);

			for (size_t j = 0; j < rows * columns; ++j)
			{
				auto pixel = *image_buffer++;
				images[i][j] = static_cast<Pixel>(pixel);
			}
			if (i % 1000 == 0)fprintf(stderr, "%d\r", i);
		}

		return images;
	}

	return {};
}

/*!
* \brief Read all training images and return a container filled with the images.
*
* The dataset is assumed to be in a mnist subfolder
*
* \return Container filled with the images
*/
std::vector<std::vector<Pixel>> mnist::read_training_images(string filename, int index) {
	return read_mnist_image_file(filename, index);
}



//------------------------
//ラベル読み込み
//------------------------

/*!
* \brief Read a MNIST label file and return a container filled with the labels
* \param path The path to the image file
* \return A std::vector filled with the read labels
*/
std::vector<Label> mnist::read_mnist_label_file(const std::string& path, int index) {
	auto buffer = read_mnist_file(path, 0x801);

	if (buffer) {
		auto count = read_header(buffer, 1);

		//Skip the header
		//Cast to unsigned char is necessary cause signedness of char is
		//platform-specific
		auto label_buffer = reinterpret_cast<unsigned char*>(buffer.get() + 8);

		std::vector<Label> labels(count);
		
		for (size_t i = 0; i < count; ++i) {
			auto label = *label_buffer++;
			labels[i] = static_cast<Label>(label);
			if (i % 1000 == 0)fprintf(stderr, "%d\r", i);
		}
		
		return labels;
	}

	return {};
}


/*!
* \brief Read all training labels and return a container filled with the labels.
*
* The dataset is assumed to be in a mnist subfolder
*
* \return Container filled with the labels
*/
std::vector<Label> mnist::read_training_labels(string filename, int index) {
	return read_mnist_label_file(filename, index);
}


//-----------------
// テスト用データのImageとLabelをランダムに抽出 
//-----------------
vector<MNIST_compact> mnist::test_images_rnd(vector<MNIST_compact> trainingdata,
												 int n_test)
{
	
	int size = trainingdata.size();

	//------------------------
	//乱数発生
	//------------------------
	std::random_device rnddev;

	//メルセンヌ・ツイスターの使用
	std::mt19937 mt(rnddev());

	//1~60000の乱数
	std::uniform_real_distribution<double> rnd(1.0, size);

	
	vector<MNIST_compact> testdata;
	for (int i = 0; n_test > i; i++)
	{
		int index = rnd(mt);
	
		MNIST_compact com;
		com.index = trainingdata.at(index).index;
		com.images = trainingdata.at(index).images;
		com.labels = trainingdata.at(index).labels;
		testdata.push_back(com);

		//テスト表示
		cout << "index=" << com.index << endl;
		print_images(com.images.back(), com.labels.back());

	}

	return testdata;

}

//テスト表示
void mnist::print_images(vector<Pixel> image, Label label)
{
	
	for (int i = 0; PIXCEL_HEIGHT*PIXCEL_WIDTH > i; i++)
	{
		int pixel = static_cast<int>(image[i]);
		if (pixel > 0)
			cout << "*";
		else
			cout << " ";
		if (i % PIXCEL_WIDTH == 0)cout << "," << endl;
	}
	int intlabel = static_cast<int>(label);
	cout << "label=" << intlabel << endl;
}

mnist.h

 

#pragma once



#include <string>
#include <iostream>
#include <fstream>
#include <vector>

#include <exception>
#include <memory>

#include <random>


using namespace std;

#define  Pixel uint8_t
#define  Label uint8_t

#define PIXCEL_WIDTH  28
#define PIXCEL_HEIGHT 28


struct MNIST_compact
{
	int index;				//何番目の要素か
	vector<vector<Pixel>> images;	//< The training images
	vector<Label> labels;			//< The training labels
};

/*!
* \brief Represents a complete mnist dataset
* \tparam Pixel The type of a pixel
* \tparam Label The type of a label
*/
//template <typename Pixel = uint8_t, typename Label = uint8_t>
struct MNIST_dataset {
	std::vector<std::vector<Pixel>> training_images; ///< The training images
	std::vector<std::vector<Pixel>> test_images;     ///< The test images
	std::vector<Label> training_labels;              ///< The training labels
	std::vector<Label> test_labels;                  ///< The test labels
	vector<int> index;								 //何番目の要素か
};

class mnist
{
public:
	mnist();
	~mnist();
	//-----------------
	// Read部分
	//-----------------
	inline uint32_t read_header(const std::unique_ptr<char[]>& buffer, size_t position);

	inline std::unique_ptr<char[]> read_mnist_file(const std::string& path, uint32_t key);

	//-----------------
	// Image
	//-----------------
	std::vector<std::vector<Pixel>> read_mnist_image_file(const std::string& path, int index);

	std::vector<std::vector<Pixel>> read_training_images(string filename, int index);

	//-----------------
	// label 
	//-----------------
	std::vector<Label> read_training_labels(string filename, int index);

	std::vector<Label> read_mnist_label_file(const std::string& path, int index);

	//-----------------
	// テスト用データのImageとLabelをランダムにindex抽出 
	//-----------------
	vector<MNIST_compact> test_images_rnd(vector<MNIST_compact> trainingdata, int n_test);

	//-----------------
	// Imageをコンソールに表示する 
	//-----------------
	void mnist::print_images(vector<Pixel> image, Label label);

};


 

 

 

 

 

参考URL

GitHub - wichtounet/mnist: Simple C++ reader for MNIST dataset

MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

Bitmapファイルを入出力してみる - [物理のかぎしっぽ]

 bluewidz nota: mnist.pkl.gz の読み込み方

 

以上。