2020年8月18日火曜日

C言語の標準ライブラリだけでMNISTをつくる

前回、TensorFlowのC++APIでMNISTを作ったときに気づいたのですが、MNISTってほとんどがPythonで書かれていて、C言語で書かれたもはほとんどみあたりません。

MNISTって簡単だからC言語の標準ライブラリ関数だけでかけるんじゃない?

おじさん、世の中にないものは作りたくなってしまう・・・。


大学生とかが課題ででそうだけどなぁ。

数時間でできそうなので、C言語で書かれたシンプルなMNISTを作ってみました。


コードはこちら。

https://github.com/yomei-o/Tiny-MNIST


---------------------------

[train 100] 0.610000

[train 200] 0.720000

[train 300] 0.810000

[train 400] 0.860000

[train 500] 0.900000

[train 600] 0.890000

[train 700] 0.830000

[train 800] 0.900000

[train 900] 0.880000

[train 1000] 0.930000

[train 1100] 0.920000

[train 1200] 0.920000

[train 1300] 0.890000

[train 1400] 0.910000

[train 1500] 0.840000

[train 1600] 0.860000

[train 1700] 0.930000

[train 1800] 0.930000

[train 1900] 0.870000

[train 2000] 0.960000

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------11111111----------

----------------------------------111111111111----------

--------------------------1111111111111111--------------

----------------111111111111111111111111----------------

--------------111111111111111111111111------------------

--------------111111111111----11111111------------------

----------------------------11111111--------------------

----------------------------11111111--------------------

--------------------------11111111----------------------

--------------------------111111------------------------

------------------------111111--11----------------------

----------------11111111111111111111--------------------

----------------11111111111111111111--------------------

------------------1111111111----------------------------

--------------------111111------------------------------

--------------------111111------------------------------

--------------------111111------------------------------

------------------111111--------------------------------

------------------111111--------------------------------

------------------1111----------------------------------

--------------------------------------------------------

--------------------------------------------------------


0: 0.000501

1: 0.160337

2: 0.023977

3: 0.000703

4: 0.005110

5: 0.005704

6: 0.002674

7: 0.715114

8: 0.043781

9: 0.042101

------------------------


なんかできた。

学習がめんどくさいので2000回くらいしかやっていませんが、10000回くらいやればもっと正解率が上がるはず。

C言語でTensorflowとか使わなくてもMNISTって簡単に作れんだね。



2020年8月15日土曜日

TensorflowのC++APIで学習をする

ここ数日暑い、暑すぎる。

あまりにも外が暑いので家でプログラムをします。


TensorflowのC++APIはあまりマニュアルが整備されていないため、ソースコードがほとんどありません。

その中でも特にC++APIを使った学習に関するサンプルはGoogleが作った簡単なもの以外まったくない。

一番ありそうなMNISTの学習サンプルもないのです。

なんで?


世の中でまだ誰もTensorflowのC++APIを使ったMNISTの学習サンプルがないので作ってみました。


作っている途中でわかったのですが、TensorflowのC++APIにはオプティマイザーとか最急降下法の便利な関数がありません。

手動でネットワークの微分とかバックプロパゲーションをしないといけないみたいです。

だからMNISTのC++の学習サンプルがないのか!


微分とか言っても高校生でできる程度の簡単なものだし、行列の演算も内積と四則演算と転置くらいしかありません。

バックプロパゲーションって結果を使って計算するだけならばそんな難しくなです。


バックプロパゲーションを理解する

https://postd.cc/2015-08-backprop/


いまさらMNIST分類器を自前実装する

https://qiita.com/soramimi_jp/items/d557d2d7adf8cc555d8b


上記のサイトを参考にしてTensorflowのC++APIでMNISTを実装してみました。


全ソースコードはこちら

https://drive.google.com/file/d/1bqBK5B8xTR0K_0C1z7yOg5yKk-teq-9I/view?usp=sharing

-----------------------------

#define NOMINMAX

#define WIN32_LEAN_AND_MEAN


#include <iostream>

#include <fstream>

#include <vector>

#include "mnistutil.h"


#include <tensorflow_cc_client_client_session.h>

#include <tensorflow_cc_ops_standard_ops.h>

#include <tensorflow_core_framework_tensor.h>

#include <tensorflow_cc_framework_gradients.h>

#include <tensorflow_core_platform_init_main.h>


#ifdef _MSC_VER

#pragma comment(lib,"ws2_32.lib")

#endif


#define tf tensorflow



void print_tensor(const char* tt,tf::Tensor& out)

{

int sz,i,w=1,h;

printf("%s\n",tt);

sz = out.dims();

printf("dims()=%d\n", sz);

for (i = 0; i < sz; i++) {

w *= out.dim_size(i);

printf("dim_size(%d)=%d\n",i,out.dim_size(i));

}

if (w > 10)w = 10;

for (i = 0; i < w; i++) {

float* p = (float*)out.data();

printf("data(%d)=%f \n", i, p[i]);

}

if (sz > 1) {

h = out.dim_size(0);

if (h > 10)h = 10;


for (i = 1; i < h; i++) {

float* p = (float*)out.data();

printf("data(%d)=%f \n", i * out.dim_size(1), p[i * out.dim_size(1)]);

}

}

printf("\n");

}



int main(int argc, char** argv)

{

int batch_num = 100;

int epoch_num = 10000;

float rate = 0.1;

int i;


tf::Scope root=tf::Scope::NewRootScope();

tf::ClientSession session{ root };


tf::Output x = tf::ops::Placeholder(root, tf::DataType::DT_FLOAT);

tf::Output t = tf::ops::Placeholder(root, tf::DataType::DT_FLOAT);


tf::Output w1 = tf::ops::Variable(root, {784,50}, tf::DataType::DT_FLOAT);

auto assign_w1 = tf::ops::Assign(root, w1, tf::ops::RandomNormal(root, { 784,50 }, tf::DataType::DT_FLOAT));


tf::Output b1 = tf::ops::Variable(root, { 50 }, tf::DataType::DT_FLOAT);

auto assign_b1 = tf::ops::Assign(root, b1, tf::ops::RandomNormal(root, { 50 }, tf::DataType::DT_FLOAT));


tf::Output w2 = tf::ops::Variable(root, { 50,10 }, tf::DataType::DT_FLOAT);

auto assign_w2 = tf::ops::Assign(root, w2, tf::ops::RandomNormal(root, { 50,10 }, tf::DataType::DT_FLOAT));


tf::Output b2 = tf::ops::Variable(root, { 10 }, tf::DataType::DT_FLOAT);

auto assign_b2 = tf::ops::Assign(root, b2, tf::ops::RandomNormal(root, { 10 }, tf::DataType::DT_FLOAT));


auto a1=tf::ops::Add(root,tf::ops::MatMul(root, x, w1),b1);


auto z1 = tf::ops::Sigmoid(root,a1);

auto a2 = tf::ops::Add(root, tf::ops::MatMul(root, z1, w2), b2);

auto y = tf::ops::Softmax(root,a2);


tf::Tensor x_data;

tf::Tensor t_data;


auto dy = tf::ops::Div(root, tf::ops::Sub(root, y, t), {(float)batch_num});


auto z1t = tf::ops::Transpose(root, z1, {1,0});

auto w2_ = tf::ops::MatMul(root, z1t, dy);

auto b2_ = tf::ops::Sum(root, dy, {0});


auto w2t = tf::ops::Transpose(root, w2, { 1,0 });

auto dz1 = tf::ops::MatMul(root,dy,w2t);

auto dsg = tf::ops::Mul(root, tf::ops::Sub(root, {(float)1}, tf::ops::Sigmoid(root, a1)), tf::ops::Sigmoid(root, a1));

auto da1 = tf::ops::Mul(root,dsg,dz1);


auto xt = tf::ops::Transpose(root, x, { 1,0 });

auto w1_= tf::ops::MatMul(root,xt,da1);

auto b1_ = tf::ops::Sum(root, da1, { 0 });

 

auto w1_g = tf::ops::Mul(root, w1_, {(float)rate});

auto w2_g = tf::ops::Mul(root, w2_, { (float)rate });

auto b1_g = tf::ops::Mul(root, b1_, { (float)rate });

auto b2_g = tf::ops::Mul(root, b2_, { (float)rate });


auto w1_n = tf::ops::Sub(root, w1, w1_g);

auto w2_n = tf::ops::Sub(root, w2, w2_g);

auto b1_n = tf::ops::Sub(root, b1, b1_g);

auto b2_n = tf::ops::Sub(root, b2, b2_g);


auto a_w1 = tf::ops::Assign(root, w1, w1_n);

auto a_w2 = tf::ops::Assign(root, w2, w2_n);

auto a_b1 = tf::ops::Assign(root, b1, b1_n);

auto a_b2 = tf::ops::Assign(root, b2, b2_n);


std::vector<tf::Tensor> outputs {};


//init

auto ret1 = session.Run({ assign_w1,assign_w2,assign_b1,assign_b2 }, nullptr);


std::vector<float> train_img, test_img, train_lbl, test_lbl,bat_img,bat_lbl;

load_image_file(TRAIN_IMAGE, train_img);

load_label_file(TRAIN_LABEL, train_lbl);

load_image_file(TEST_IMAGE, test_img);

load_label_file(TEST_LABEL, test_lbl);




//train

for (i = 0; i < epoch_num; i++) {


create_batch(batch_num, train_img, train_lbl, bat_img, bat_lbl);


tf::Tensor x_data(tf::DT_FLOAT, tf::TensorShape({ batch_num,784 }));

auto dst_x_data = x_data.flat<float>().data();

memcpy(dst_x_data, bat_img.data(), sizeof(float)*bat_img.size());


tf::Tensor t_data(tf::DT_FLOAT, tf::TensorShape({ batch_num,10 }));

auto dst_t_data = t_data.flat<float>().data();

memcpy(dst_t_data, bat_lbl.data(), sizeof(float) * bat_lbl.size());


//ok  auto ret0 = session.Run({ { x, x_data },{ t, t_data } }, { y }, &outputs);

//print_tensor("y", outputs[0]);

//break;


auto ret2 = session.Run({ { x, x_data },{ t, t_data } }, { a_w1, a_b1, a_w2, a_b2 }, nullptr); 

}


// predict

create_batch(1, test_img, test_lbl, bat_img, bat_lbl);


tf::Tensor xt_data(tf::DT_FLOAT, tf::TensorShape({ 1,784 }));

auto dst_xt_data = xt_data.flat<float>().data();

memcpy(dst_xt_data, bat_img.data(), sizeof(float) * bat_img.size());


tf::Tensor tt_data(tf::DT_FLOAT, tf::TensorShape({ 1,10 }));

auto dst_tt_data = tt_data.flat<float>().data();

memcpy(dst_tt_data, bat_lbl.data(), sizeof(float) * bat_lbl.size());


auto ret3 = session.Run({ { x, xt_data },{ t, tt_data } }, { y }, &outputs);


float* pc = (float*)(outputs[0].data());

print_image(bat_img.data());

print_label(pc);


return 0;

}

-----------------------------


結果はこちら

-----------------------------

2020-08-14 14:07:44.312280: I C:\trash\mnist\tensorflow_2_2_mnist_001\src_core\tensorflow_core_platform_cpu_feature_guard.cc:143] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX AVX2

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

--------------------------------------------------------

----------------------------------------11111111--------

--------------111111------------------1111111111--------

--------------111111----------------111111111111--------

------------11111111----------------1111111111----------

------------11111111----------------1111111111----------

----------11111111--------------111111111111------------

----------11111111--------------1111111111--------------

----------11111111--------------1111111111--------------

----------1111111111------------11111111----------------

------------1111111111111111111111111111----------------

----------------111111111111111111111111----------------

------------------1111111111111111111111----------------

--------------------111111111111111111------------------

----------------------------1111111111------------------

------------------------------11111111------------------

----------------------------11111111--------------------

----------------------------11111111--------------------

----------------------------11111111--------------------

----------------------------111111----------------------

----------------------------111111----------------------

--------------------------------------------------------

--------------------------------------------------------


0: 0.000000

1: 0.000000

2: 0.000014

3: 0.000000

4: 0.997556

5: 0.000003

6: 0.000017

7: 0.000059

8: 0.000143

9: 0.002207


-----------------------------


できた!

バックプロパゲーションとかも誤差関数固定ならば数時間で作れるんですね。


2020年8月4日火曜日

Variational Autoencoder(VAE)をやってみた。

おじさんの本日のディープラーニングはオートエンコーダーです。
いつも認識系ばっかりやっているので、たまには生成系もやってみます。

論文はこれ。
https://arxiv.org/pdf/1312.6114.pdf

MNISTでいうと、通常は画像から数値に変換していました。
オートエンコーダーでは逆で数値から画像を作ります。

日本語の解説はこの辺に載ってます。
https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24

このVAEですが、C++言語のサンプルが世の中にない!
なんでだよー。

なのでC++でVAEができるか試してみました。

まずこのKerasのVAEのサンプルコードを改良して.pbファイルを出力するようにします。
https://github.com/keras-team/keras/blob/master/examples/variational_autoencoder_deconv.py

vae.save_weights()の行を以下のように修正すれば、.pbファイルを出力してくれるので、そのファイルをfreezeすればC++で学習済みデータを利用できます。
--------------------
        #vae.save_weights('vae_mlp_mnist.h5')
        vae.save('vae_mlp_mnist.h5')
        decoder.save('vae_mlp_mnist_decoder.h5')
--------------------


いろいろな数字の認識が二次元空間にマップされます。


これをもとに2次元空間から画像を生成してみます。
z[0] = 1;z[1] = 0.4;
2次元空間のこのポイントが「5」と「8」の中間くらいのはずなので、
このポイントから画像を生成すると「5」と「8」の中間くらいの画像ができるはず。

-----------------------
#include <iostream>
#include <cstdio>

#include "opencv2_core.hpp"
#include "opencv2_imgproc.hpp"
#include "opencv2_imgproc_imgproc_c.h"
#include "opencv2_imgcodecs.hpp"

#include <tensorflow_c_c_api.h>
#include "tf_utils.hpp"

#ifdef _MSC_VER
#pragma comment(lib,"ws2_32.lib")
#endif

#define MODEL_FILENAME "vae_mlp_mnist_decoder.pb"

static int displayGraphInfo()
{
TF_Graph *graph = tf_utils::LoadGraphDef(MODEL_FILENAME);
if (graph == nullptr) {
std::cout << "Can't load graph" << std::endl;
return 1;
}

size_t pos = 0;
TF_Operation* oper;
printf("--- graph info ---\n");
while ((oper = TF_GraphNextOperation(graph, &pos)) != nullptr) {
printf("%s\n", TF_OperationName(oper));
}
printf("--- graph info ---\n");

TF_DeleteGraph(graph);
return 0;
}

int main()
{
printf("Hello from TensorFlow C library version %s\n", TF_Version());

#if 0
/* read input image data */
cv::Mat image = cv::imread("4.jpg");
//cv::imshow("InputImage", image);
/* convert to 28 x 28 grayscale image (normalized: 0 ~ 1.0) */
cv::cvtColor(image, image, CV_BGR2GRAY);
cv::resize(image, image, cv::Size(28, 28));
image = ~image;
//cv::imshow("InputImage for CNN", image);
image.convertTo(image, CV_32FC1, 1.0 / 255);
#endif

/* get graph info */
displayGraphInfo();

TF_Graph *graph = tf_utils::LoadGraphDef(MODEL_FILENAME);
if (graph == nullptr) {
std::cout << "Can't load graph" << std::endl;
return 1;
}

/* prepare input tensor */
TF_Output input_op = { TF_GraphOperationByName(graph, "z_sampling"), 0 };
if (input_op.oper == nullptr) {
std::cout << "Can't init input_op" << std::endl;
return 2;
}

const std::vector<std::int64_t> input_dims = { 1,2 };
std::vector<float> input_vals;

input_vals.resize(2);
input_vals[0] = 1;
input_vals[1] = 0.4;

TF_Tensor* input_tensor = tf_utils::CreateTensor(TF_FLOAT,
input_dims.data(), input_dims.size(),
input_vals.data(), input_vals.size() * sizeof(float));

/* prepare output tensor */
TF_Output out_op = { TF_GraphOperationByName(graph, "dense_3/Sigmoid"), 0 };
if (out_op.oper == nullptr) {
std::cout << "Can't init out_op" << std::endl;
return 3;
}

TF_Tensor* output_tensor = nullptr;

/* prepare session */
TF_Status* status = TF_NewStatus();
TF_SessionOptions* options = TF_NewSessionOptions();
TF_Session* sess = TF_NewSession(graph, options, status);
TF_DeleteSessionOptions(options);

if (TF_GetCode(status) != TF_OK) {
TF_DeleteStatus(status);
return 4;
}

/* run session */
TF_SessionRun(sess,
nullptr, // Run options.
&input_op, &input_tensor, 1, // Input tensors, input tensor values, number of inputs.
&out_op, &output_tensor, 1, // Output tensors, output tensor values, number of outputs.
nullptr, 0, // Target operations, number of targets.
nullptr, // Run metadata.
status // Output status.
);

if (TF_GetCode(status) != TF_OK) {
std::cout << "Error run session";
TF_DeleteStatus(status);
return 5;
}

TF_CloseSession(sess, status);
if (TF_GetCode(status) != TF_OK) {
std::cout << "Error close session";
TF_DeleteStatus(status);
return 6;
}

TF_DeleteSession(sess, status);
if (TF_GetCode(status) != TF_OK) {
std::cout << "Error delete session";
TF_DeleteStatus(status);
return 7;
}

const auto probs = static_cast<float*>(TF_TensorData(output_tensor));

//for (int i = 0; i < 28 * 28; i++) {
// printf("prob of %d: %.3f\n", i, probs[i]);
//}

cv::Mat out = cv::Mat::zeros(28, 28, CV_32F);
memcpy(out.data, probs, sizeof(float) * 28 * 28);
out *= 256;
cv::imwrite("out.png", out);

TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);

return 0;
}
-----------------------




なんかできた。
これを人の顔とか物でやると二つの顔の間の顔とかが生成できるのね。
それにしてもオートエンコーダって地味だね。