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;
}
-----------------------




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


0 件のコメント:

コメントを投稿