C++でニューラルネットワークによる手書き文字認識その③(学習の記憶) for VS2017 VC++
の続きで、今回は訓練データで学習した「重みとバイアス」を
外部ファイルに保存・読み込み出来るクラスを作成する第③回目。
ちなみに、このバイアスと重みを保存・読み込みする
機能はNetwork.pyで書かれたコードにはない。
C++でニューラルネットワーク その① ・・PythonのコードをVC++に書き換え
C++でニューラルネットワーク その② ・・PythonのコードをVC++に書き換え
C++でニューラルネットワーク その③ ・・重みとバイアスを外部に記録
C++でニューラルネットワーク その④ ・・オリジナル訓練データの追加
C++でニューラルネットワーク その⑤ ・・まとめ、文字認識アプリ
さて、訓練データとテストデータで学習した「重みとバイアス」を
外部に保存、読み込みできるようになれば
手書きデータを素早く判定出来るようになります。以下のような感じ。
その準備段階として、今回の記事では
「バイアスと重み」の保存・読み込みを紹介します。
それでは、もう少し
学習した「バイアスと重み」について説明します。
例えば 、ネットワークを「784 500 10」とした場合に
訓練データとテストデータの正答率は、文字認識その①の記事で書いたように
80%に近づいていました。
この正答率はネットワーク構成(隠れ層500等)と
ハイパーパラメータ(学習率とか)によって与えられた
「バイアスと重み」の値の変化によるものでした。
(詳細については、こことかこことか文字認識その②の記事を参照して下さい。)
ここでは、ネットワーク構成とハイパーパラメータは変更せずに
「バイアスと重み」の値を外部ファイルとして保存し一旦プログラムを終了させます。
(バイアス=と、重み=)
次にプログラムを起動したときに、保存した「バイアスと重み」を読み込んで
ネットワークの「バイアスと重み」の値に入れます。
すると、以下のようにテストデータ100個のうち89個が正答し、
正答率89%となっていることが分かります。
これは、本来初回40%前後の正答率になるはずだったのが
初回から学習10回以上したのに相当する正答率約80%になっている
ことが分かります。
以上より学習した「バイアスと重み」を保存し活用することで、
学習を記憶していることに相当していることが分かります。
(単に外部ファイルに保存して、再読み込みしているだけです。)
※ 但し、当然ながら同じネットワークでないと再利用はできません。
隠れ層「500」で作った「バイアスと重み」は隠れ層500でしか使えない。
ではソースコードを紹介していきます。
ここでは、バイアスと重みを保存・読み込みが出来る
MYSTRクラスとfileクラスを用意しました。(下記の方を参照。)
これは、neuronクラスのDelta型で
保存された重みとバイアスを読み込み保存するクラスとなっている。
MYSTRクラス
//重みとバイアスの保存
void save_weight_and_biases(string filepath, Delta test_data, int Epoch);
//重みとバイアスの読み込み
bool read_weight_and_biases(string filepath, Delta *delta_file, neurons neu);
Delta型(重みとバイアスを保存する型=器)
struct WEIGHT
{
vector<vector<double>> J;
};struct Delta
{
vector<vector<double>> _biases;vector<WEIGHT> _weights;
};
データ型は例えばネットワークが[784, 3, 4, 10]の場合
Pythonで書かれたコードをプリントアウトすると以下のような感じとなる。
バイアスの配列
[array([[ 1.60680811],
[-0.91776427],
[ 2.39356009]]),
array([[-0.36326466],
[-0.77759203],
[ 0.80462692],
[ 0.55610158]]),
array([[-2.89399282],
[ 1.12189747],
[-0.09063474],
[ 0.19409676],
[-0.52739619],
[-1.41494714],
[ 0.83719107],
[ 0.29296509],
[-0.9796348 ],
[-0.9919744 ]])]
重みの配列 784x3,3x4、4x10
[array([
[-0.51721912, -1.32663246, 0.61860472, ..., -0.14808348, 0.47365758, -1.67055342],
[-1.82324755, 2.45671693, -0.4074048 , ..., -1.22299543, -0.60028638, -0.72343723],
[ 0.13751237, -2.20392184, -0.5635813 , ..., 1.47505309, -1.08143278, 0.55418631]]),
array([[ 1.01398068, 0.71675172, -0.98611525],
[-0.91658198, -0.89796351, -0.51228011],
[ 0.53529015, 0.04383624, -0.41019621],
[-0.64914817, 1.29586205, -0.95359815]]),
array([[ 1.49524657, -0.11535072, -0.29128393, 0.60778149],
[ 0.22230999, 0.63995748, -0.43956315, -1.01877885],
[-0.37976193, 0.18921609, -1.20175081, -0.42824686],
[-0.27321251, 0.86174003, 0.51706354, -0.99385186],
[-0.5942666 , -0.72616386, 0.10107404, 1.81730394],
[ 1.72687002, -2.42612697, 1.57296988, -0.50415091],
[-0.36752011, -0.00892462, -1.78011645, -0.98530298],
[-0.26814078, -1.0660397 , -2.05035737, -0.7745915 ],
[ 0.34035524, 0.2669593 , -1.22003159, -0.70460095],
[-2.27451737, 0.94988465, -0.15863418, -2.20939092]])]
この形式(フォーマット)をまねてバイアスと重みを保存することにした。
そのために、文字列を料理するオリジナル関数をいくつか用意した。
// ser_mae,ser_ato で囲まれた部分を取得する。
vector<string> split(const std::string &str, const char *ser_mae, const char *ser_ato);
//指定文字列1、文字列2の間の文字を抜き出す
string mid(const string linebuff, string key1, string key2);
//文字セパレート
std::vector<std::string> split(const std::string &str, const char *sep);
split()関数は、確かperlとか、Javaとかの言語で使われる機能を模した。
mid()関数は懐かしい。VB関数で用いられるのを模した。
ここは、自分の使いやすい関数や名称を使うとよいでしょう。
※文字列操作に慣れていない方には難しいかもしれません( ゚Д゚)。
ちなみに、VC++のメニュウの
プロジェクト>プロパティ>構成プロパティ>全般
では、マルチバイト文字にしてます。
さて、この2つのクラスの利用例。
文字認識その①で紹介したNNmodel.cppのmain()部分を以下のように変更する。
ニューラルネットワークで訓練するnntest()関数をコメントアウトして
shortcut()関数を追加した。
int main(int argc, char **argv) {
vector<int> nets;
for (int i = 1; argc > i; i++)
{
nets.push_back(atoi(argv[i]));
}//----------------------------
//ニューラルネットワーク
//----------------------------
// nntest(nets);//----------------------------
//以前のバイアスと重みを使って評価
//----------------------------
shortcut(nets);}
shortcut()関数は以下のような感じとした。
void shortcut(vector<int> nets)
{//----------------------------
//ネットワークを作成
//----------------------------
network net;
net.Network(nets);
//----------------------------
//トレーニングデータ読み込み
//----------------------------
mnist mnist;
int testdata_n = 1000; //訓練データの上限
MNIST_dataset training_data;
int index = 3;
cout << "tradingdata reading.. index=" << index << endl;
training_data.training_images = mnist.read_training_images("train-images.idx3-ubyte", index);
training_data.training_labels = mnist.read_training_labels("train-labels.idx1-ubyte", index);
//indexを割り振る
vector<int> indexint;
for (int i = 0; training_data.training_images.size() > i; i++)training_data.index.push_back(i);
//イメージとラベルをマージする
vector<MNIST_compact> trainingdata_ = net.marge(&training_data);
// シャッフル
std::shuffle(trainingdata_.begin(), trainingdata_.end(), std::mt19937());
//先頭100を取り出し
vector<MNIST_compact> trainingdata;
for (int i = 0; testdata_n > i; i++) trainingdata.push_back(trainingdata_[i]);
cout << "trainingdata.size=" << trainingdata.size() << endl;
cout << "nntest3" << endl;
//----------------------------
//テストデータ作成
//テスト用画像をトレーニングデータからn_test個、ランダムに抽出
//----------------------------
int n_test = 100;//テストデータ数
vector<MNIST_compact> test_data;
test_data = mnist.test_images_rnd(trainingdata, n_test);
cout << "nntest3.5" << endl;
//トレーニングデータから選択したテストデータを除外
net.delete_test_images(&trainingdata, test_data);
//----------------------------------------------
//ファイルから重みとバイアスを初期化
//----------------------------------------------
Delta delta_file;
if (net.read_weight_and_biases(BWFILE, &delta_file, net._neurons[0]))
{
net._neurons[0]._biases = delta_file._biases;
net._neurons[0]._weights = delta_file._weights;
cout << "以前の重みとバイアスを読み込み" << endl;
}//----------------------------------------------
//テスト画像データの要素数
//----------------------------------------------
cout << "テスト画像データの要素数=" << n_test << endl;
net._strEpochresult[0] = '\0';
sprintf(net._strEpochresult,"Epoch {%d} : {%d} / {%d}\n", 0, net.evaluate(test_data), n_test);
cout << net._strEpochresult << endl;}
バイアスと重みの保存は文字認識その①で紹介した
networkクラスのSGD関数の学習回数のfor文の中に埋め込んだ。
以下のような感じ。なので、
shortcut(nets);
を実行する前に、nntest(nets);関数で一旦、
訓練データを用いて学習した「バイアスと重み」を適当なファイル名で保存する。
その後、nntest(nets);をコメントアウトしshortcut(nets);を実行する。
そうすると、shortcut(nets);で
バイアスと重みを保存したファイルをread_weight_and_biases()で
「バイアスと重み」を読み込み
sprintf(net._strEpochresult,"Epoch {%d} : {%d} / {%d}\n", 0, net.evaluate(test_data), n_test);
にてテストデータを判定している仕組みだ。
ここの流れは、
コメントアウトしてビルド。コメントアウトを外してビルドと、忙しいのですが・・
学習した「バイアスと重み」のファイルを作成(保存)し、
それを、活用(読み込み)して、初回から正答率が高くなることを
確認することが目的となります。
なので、注意が必要なのはネットワークの取り方や
ハイパーパラメータの取り方ではうまく正答率が上がらないことです。
初回と、十分学習した結果の正答率に差異がなければ
「バイアスと重み」のファイルの保存と読み込みが
うまく出来ているか確認出来ません。
Pythonで書かれたコードではネットワーク「748 30 10」で
正答率9割超えていましたが、私の環境で試したところ40%台前後でした。
そこで試しにネットワーク「748 500 10」にしたところ
正答率が約8割になったことは文字認識その①の記事で記しました。
もしかしたら、テスト環境によって差異があるかもしれませんので
差異が大きくない場合、色々ネットワークを試してみて下さい。
(それでもうまくいかない場合は、
プログラムにエラーがあるかもしれませんね( ゚Д゚))
void network::SGD(vector<MNIST_compact> training_data, //訓練データ int epochs, //世代(学習回数) int mini_batch_size, //ミニバッチサイズ double eta, //学習率 vector<MNIST_compact> test_data) //テストデータ { Delta wb; //保存用 //------------------------------- //重みとバイアスを保存する。テスト //testWB_1.txt と testWB_2.txt は同じになる。 //------------------------------- /* wb._biases = _neurons[0]._biases; wb._weights = _neurons[0]._weights; save_weight_and_biases("testWB_1.txt", wb, 0); Delta read_test; if (read_weight_and_biases("testWB_1.txt", &read_test, _neurons[0])) print_weight(read_test._weights); save_weight_and_biases("testWB_2.txt", read_test, 0); return; //test */ //---------------------------------------------- //ファイルから重みとバイアスを初期化 //---------------------------------------------- Delta delta_file; if (read_weight_and_biases(BWFILE, &delta_file, _neurons[0])) { _neurons[0]._biases = delta_file._biases; _neurons[0]._weights = delta_file._weights; cout << "以前の重みとバイアスを読み込み" << endl; } //---------------------------------------------- //トレーニング画像データの要素数 //---------------------------------------------- int n = training_data.size(); cout << "トレーニング画像データの要素数=" << n << endl; //---------------------------------------------- //テスト画像データの要素数 //---------------------------------------------- int n_test = test_data.size(); cout << "テスト画像データの要素数=" << n_test << endl; //---------------------------------------------- //学習回数 //---------------------------------------------- _strEpochresult[0] = '\0'; vector<string> rlt; for (int i = 0; epochs > i; i++) { cout << "epochs:" << i << "/" << epochs << endl; //---------------------------------------------- // シャッフル //---------------------------------------------- std::shuffle(training_data.begin(), training_data.end(), std::mt19937()); //---------------------------------------------- //# 0~nまでをmini_batch_sizeずつとばしで繰り返す //mini_batch_size毎に要素を切り出して格納 //mini_batches = [ // training_data[k:k + mini_batch_size] // for k in range(0, n, mini_batch_size)] //---------------------------------------------- vector<vector<MNIST_compact>> mini_batches; for (int j = 0; n > j; j += mini_batch_size) { vector<MNIST_compact> mini_batch; for (int k = 0; mini_batch_size > k; k++) { if (k+j > n - 1)break; mini_batch.push_back(training_data.at(k + j)); } if(j % 1000 == 0)fprintf(stderr, "make mini_batches %d\r", j); mini_batches.push_back(mini_batch); } int lenth = mini_batches.size(); //cout << "mini_batches.size()=" << lenth << endl; //---------------------------------------------- //バッチの更新 //for mini_batch in mini_batches : // self.update_mini_batch(mini_batch, eta) //---------------------------------------------- Delta delta; for (int j = 0; lenth > j; j++) { if(j % 10 == 0)fprintf(stderr, "update_mini_batch %d / %d \r", j, lenth); vector<MNIST_compact> mini_batch = mini_batches.at(j); for (int k = 0; mini_batch.size() > k; k++) { delta = update_mini_batch(mini_batch, eta); } } //------------------------------- //重みとバイアスを保存する。 //------------------------------- wb._biases = _neurons[0]._biases; wb._weights = _neurons[0]._weights; save_weight_and_biases(BWFILE, wb , i); //---
省略
以下のコードを
SGD関数のfor文に入る前にいれてテストしてみてください。
//-------------------------------
//重みとバイアスを保存する。テスト
//testWB_1.txt と testWB_2.txt は同じになる。
//-------------------------------
wb._biases = _neurons[0]._biases;
wb._weights = _neurons[0]._weights;
save_weight_and_biases("testWB_1.txt", wb, 0);
Delta read_test;
if (read_weight_and_biases("testWB_1.txt", &read_test, _neurons[0]))
print_weight(read_test._weights);
save_weight_and_biases("testWB_2.txt", read_test, 0);
return; //test
ここでは「バイアスと重み」を保存したtestWB_1.txtと、
testWB_1.txtを読み込んで保存したtestWB_2.txtのファイルが一致していれば
保存。読み込み。の機能がちゃんと出来ているのをざっくり確認できる。
ファイルが同じか確かめる際、WinMergeというフリーソフト
を使うのをお勧めする。
また、ネットワークの変更に従い
バイアスと重みの配列が変化するので
それに柔軟に対応出来るように作った。(つもりだ。)
以下、ソースコード。
MYSTR.h
#pragma once #include "neurons.h" #include "file.h" #include <string> #include <iostream> #include <fstream> #include <vector> #include <random> using namespace std; class MYSTR : public neurons,file { public: MYSTR(); ~MYSTR(); // ser_mae,ser_ato で囲まれた部分を取得する。 vector<string> split(const std::string &str, const char *ser_mae, const char *ser_ato); //指定文字列1、文字列2の間の文字を抜き出す string mid(const string linebuff, string key1, string key2); //文字セパレート std::vector<std::string> split(const std::string &str, const char *sep); //重みとバイアスの保存 void save_weight_and_biases(string filepath, Delta test_data, int Epoch); //重みとバイアスの読み込み bool read_weight_and_biases(string filepath, Delta *delta_file, neurons neu); };
MYSTR.cpp
#include "stdafx.h" #include "MYSTR.h" MYSTR::MYSTR() { } MYSTR::~MYSTR() { } //save biases and weight void MYSTR::save_weight_and_biases(string filepath, Delta wb, int epoch) { file file; char num[20]; string buf = to_string(epoch); buf += "\n"; buf += "network=[array([784,"; for (int i = 0; wb._biases.size() > i; i++) { buf += to_string(wb._biases[i].size()); if (i < wb._biases.size() - 1) { buf += ","; } else { buf += "])]"; } } buf += "\n"; //バイアス buf += "biases=["; for (int i = 0; wb._biases.size() > i; i++) { buf += "array("; for (int j = 0; wb._biases[i].size() > j; j++) { buf += "["; sprintf(num, "%.15lf", wb._biases[i][j]); buf += num; if (j < wb._biases[i].size() - 1) { buf += "],"; } else { buf += "]"; } } if (i < wb._biases.size() - 1) { buf += "),"; } else { buf += ")"; } } buf += "]\n"; //---------------- //重み //---------------- // cout << "save_weight_and_biases()重みの表示" << endl; buf += "weights=["; for (int L = 0; wb._weights.size()>L; L++) { buf += "array(["; WEIGHT weight = wb._weights[L]; for (int j = 0; weight.J.size()>j; j++) { buf += "["; vector<KATA> weight2 = weight.J[j]; for (int k = 0; weight2.size()>k; k++) { sprintf(num, "%.15lf", weight2[k]); buf += num; if (k < weight2.size() - 1) { buf += ","; } else { } } if (j < weight.J.size() - 1) { buf += "],"; } else { buf += "]"; } } if (L < wb._weights.size() - 1) { buf += "]),"; } else { buf += "])"; } } buf += "]\n"; file.putfile(buf, filepath); } //read biases and weight bool MYSTR::read_weight_and_biases(string filepath, Delta *delta_file, neurons neu) { file file; vector<string> buff2; vector<vector<string>> buff3; //----------------------- // ファイルがあるか //----------------------- string buff = ""; if (file.readall(filepath, &buff) == -1) { cout << "file not open " << filepath << endl; return false; } //----------------------- // ネットワークが同じか //----------------------- //network=[array([784,8,3,11])] string buff_network = mid(buff, "network=[", "]\n"); buff_network = mid(buff_network, "array([", "]"); buff2 = split(buff_network, ","); if (buff2.size() - 1 != neu._biases.size()) { cout << "ネットワークが同じではない。" << endl; return false; } vector<int> wei; for (int j = 0; neu._weights.size() > j; j++) { wei.push_back(neu._weights[j].J[0].size()); } for (int i = 0; buff2.size()-1 > i; i++) { if (wei[i] != atoi(buff2[i].c_str())) { cout << "ネットワークが同じではない。" << endl; return false; } } //----------------------- // biasesの組み立て //----------------------- string buff_biases = mid(buff, "biases=[", "]\n"); buff2 = split(buff_biases, "array(", ")"); // for (int i = 0; buff2.size() > i; i++)cout << "tes i=" << i << "," << buff2[i] << endl; //----------------------- //それぞれのarray()の中の[ ]の中身を取得 //----------------------- for (int i = 0; buff2.size() > i; i++) { vector<double> d; char num[20]; vector<string> buff31; buff31 = split(buff2[i], "[", "]"); for (int j = 0; buff31.size() > j; j++) { d.push_back(atof(buff31[j].c_str())); } delta_file->_biases.push_back(d); } //----------------------- //テスト表示 //----------------------- /* cout << "copy rlt." << endl; for (int i = 0; rlt._biases.size() > i; i++) { cout << "i=" << i << endl; for (int j = 0; rlt._biases[i].size() > j; j++) printf("(%d,%d) %.15lf\n", i, j, rlt._biases[i][j]); } */ buff2.clear(); buff3.clear(); //----------------------- // weightsの組み立て //----------------------- string buff_weight = mid(buff, "weights=[", ")]"); buff2 = split(buff_weight, "array(["); buff2.erase(buff2.begin()); //先頭は空なので削除 //----------------------- //それぞれのarray()の中の[ ]の中身を取得 //----------------------- for (int i = 0; buff2.size() > i; i++) { vector<string> buff31; buff31 = split(buff2[i], "[", "]"); buff3.push_back(buff31); } //----------------------- //テスト表示 //----------------------- /* for (int i = 0; buff3.size()>i; i++) { for (int j = 0; buff3[i].size() > j; j++) { cout << buff3[i][j] << ","; } } */ vector<vector<string>> buff4; for(int i=0; buff3.size()>i; i++) { WEIGHT dd; vector<string> buff41; for (int j = 0; buff3[i].size() > j; j++) { buff41 = split(buff3[i][j], ","); //cout << "buff3=" << buff3[i][j] << endl; vector<double> d; char num[20]; for (int k = 0; buff41.size() > k; k++) { d.push_back(atof(buff41[k].c_str())); //cout << "buff41=" << buff41[k] << endl; } dd.J.push_back(d); } delta_file->_weights.push_back(dd); } //----------------------- //テスト表示 //----------------------- /* for (int i = 0; delta_file->_weights.size() > i; i++) { cout << "i=" << i << endl; for (int j = 0; delta_file->_weights[i].J.size() > j; j++) { for (int k = 0; delta_file->_weights[i].J[j].size() > k; k++) { cout << delta_file->_weights[i].J[j][k] << ","; } } cout << endl; } */ return true; } //------------------- // ser_mae ser_atoで囲まれた部分を取得する。 //------------------- vector<string> MYSTR::split(const std::string &str, const char *ser_mae, const char *ser_ato) { vector<string> rlt;// 分割結果を格納するベクター string search1 = ser_mae; string search2 = ser_ato; int p0 = 0, p1 = 0, p2 = 0, len = 0; int i=0; while ((p1 = str.find(search1, p1)) != std::string::npos) { string dmy; p2 = str.find(search2, p1 + search1.length() + 1); if (p2 == std::string::npos || p1 == std::string::npos) { break; } else { len = p2 - p1 - search1.length(); dmy = str.substr(p1 + search1.length(), len); rlt.push_back(dmy); } p1 = p2; i++; } return rlt; } /*---------------------------------------------------------- 指定文字列1、文字列2の間の文字を抜き出す ------------------------------------------------------------*/ string MYSTR::mid(const string linebuff, string key1, string key2) { string str1, str2; str1 = linebuff; int f1 = 0, f2 = 0; int f1_siz = 0, f2_siz = 0, len = 0; f1 = str1.find(key1); //".dat\">"); f2 = str1.find(key2, f1); //".dat</a>", f1); f1_siz = key1.size(); f2_siz = key2.size(); len = f2 - f1 - f1_siz; if (f1 == -1 || f2 == -1 || len < f2_siz) { } else { str2 = str1.substr(f1 + f1_siz, len); } return str2; } //文字セパレート std::vector<std::string> MYSTR::split(const std::string &str, const char *sep) { std::vector<std::string> v; // 分割結果を格納するベクター auto first = str.begin(); // テキストの最初を指すイテレータ int size = std::strlen((const char *)sep); auto last = first; // 分割文字列末尾へのイテレータ auto middle = first; while (first != str.end()) { // テキストが残っている間ループ int i = 0; int cnt = 0; int p = 0; while (last != str.end()) // 末尾 or セパレータ文字まで進める { i = 0; cnt = 0; if (*last == sep[i]) { middle = last; for (int j = 0; size>j; j++) { if (last != str.end()) { if (*last == sep[j])cnt++; } ++last; p++; } if (cnt == size) { v.push_back(std::string(first, middle)); // 分割文字を出力 --last; first = last; // 次の処理のためにイテレータを設定 break; } else { last = middle; } } ++last; //セパレータ文字列の最後までいったら出る //if(i==size)break; } if (cnt != size) v.push_back(std::string(first, last)); // 分割文字を出力 if (last != str.end())++last; first = last; // 次の処理のためにイテレータを設定 } return v; }
file.h
#pragma once #include <string> #include <iostream> #include <windows.h> #include <WinInet.h> //HINTERNETの定義 using namespace std; class file { public: file(); ~file(); void putfile(string buff, string path); int read(string filename); int readall(string filename, string *result); /* in 読み取りのためにオープン。 out 書き込みのためにオープン。 app 書き込みを、常にファイルの末尾へ行う。 ate オープン後、ファイルの末尾へ移動する(名前は "at end" の略) trunc ファイルの元の内容を削除する。 binary バイナリモードでオープン(改行文字などの変換を行わない) */ void putfile(string buff, string path, int mode); void printf_ex(LPCTSTR pszFormat, ...); };
file.cpp
#include "stdafx.h" #include "file.h" #include <string> #include <iostream> #include <fstream> #include <windows.h> #include <WinInet.h> //HINTERNETの定義 using namespace std; file::file() { } file::~file() { } //mode std::ios_base void file::putfile(string buff, string path, int mode) { int i = 0; ofstream ofs; ofs.open(path.c_str(), mode); ofs << buff; ofs.close(); } void file::putfile(string buff, string path) { int i = 0; ofstream ofs; ofs.open(path.c_str()); ofs << buff; ofs.close(); } int file::read(string filename) { std::ifstream ifs(filename); std::string str; if (ifs.fail()) { std::cerr << "失敗" << std::endl; return -1; } while (getline(ifs, str)) { std::cout << "[" << str << "]" << std::endl; } return 0; } int file::readall(string filename, string *result) { std::ifstream ifs(filename); if (ifs.fail()) { std::cerr << "失敗" << std::endl; return -1; } std::string str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); // std::cout << "[" << str << "]" << std::endl; *result = str; return 0; } void file::printf_ex(LPCTSTR pszFormat, ...) { va_list argp; TCHAR pszBuf[256]; va_start(argp, pszFormat); _vstprintf(pszBuf, pszFormat, argp); va_end(argp); OutputDebugString(pszBuf); }
文字列操作関数は以前個人的に作成したのを、そのまま流用しているので
イテレータ使っていたり、使ってなかったり一貫性がなかったりする
細かい部分は気にしないで下さい。 ( ゚Д゚)
以上。
参考
Visual C++でデバッグ時の出力をIDE(Visual Studio)上に出す方法 - Gobble up pudding