ここ数日暑い、暑すぎる。
あまりにも外が暑いので家でプログラムをします。
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
-----------------------------
できた!
バックプロパゲーションとかも誤差関数固定ならば数時間で作れるんですね。