この記事はYahoo! JAPAN 18 新卒 Advent Calendar 2018 25日目の記事です。
前回は@music431perさんの「Git操作に慣れ始めた人のためのGitTips」でした。
概要
近年メディアアートにおいて、機械学習を用いた事例が多くなってきました。
先日来日していたKyle McDonaldは、機械学習を用いたメディアアートを制作しています。
discrete figures1では、AIや機械学習を用いて未知のダンス表現へ挑戦していました。
AIというメディウムに対して新しい取り組みです。このイベントの制作に関する、素晴らしい記事をカイル自身が書いているので、ぜひ読んで見てください。
“discrete figures” with @rhizomatiks and @elevenplay, a new dance performance that uses ML to generate human-like movement https://t.co/UIE3sYzPIK
— Kyle McDonald (@kcimc) 2018年10月9日
今回は、昨年から注目を集めているpix2pixというアルゴリズムを使ったデモを
openFrameworks2で実装し、機械学習を用いたメディアアートへの一歩を踏み出そうと思います。
サンプルコードはGithubにあります - shion1118/ofPix2pix
本記事では、pix2pixのアルゴリズムやモデルの実装についてではなく、openFrameworksにおけるTensorflowの活用に重点を置いています。
開発環境
自分の開発環境は以下の通りです。
PC : MacBook Pro (15-inch, 2017)
CPU : 2.8 GHz Intel Core i7
Memory: 16 GB 2133 MHz LPDDR3
Xcode : Version 10.1 (10B61)
oF : 0.10.1
Python: 3.6.5
Bazel : 0.20.0
GCC : clang-1000.11.45.5
pix2pixとは
Image-to-Image Translation with Conditional Adversarial Networksより引用
pix2pixは、2016年に「Image-to-Image Translation with Conditional Adversarial Networks3」と題して発表されました。敵対的生成ネットワーク(以下GAN)を用いた画像生成アルゴリズムです。
論文の冒頭にinputとoutputの例があります。画像を入力することで、ペアとなる画像を生成(補間)するものとなっています。
論文3の図2より引用
入力xからGeneratorが画像G(x)を生成し、Discriminatorは入力xがGeneratorによって生成された画像であるかをTrue/Falseで判別します。Generatorで生成された画像は、偽物であり、元の画像yは本物となります。つまり、GeneratorはDiscriminatorを騙す働きをし、DiscriminatorはGeneratorに騙されないような働きをします。4
通常のGANとは違い、生成後の画像G(x)だけでなく、入力xもペアとして観察し、True/Falseを判別しています。
pix2pixでは、もう一つL1正規化というのも重要なアルゴリズムです。
本記事では省略しますが、ぜひ論文を読んでみて下さい!
公式サイト:Image-to-Image Translation with Conditional Adversarial Nets
Tensorflowをビルドする
openFrameworksで実装するにあたり、まずTensorflow5のC++ APIをビルドします
Build from source | TensorFlow
Python
PythonとTensorflowの依存パッケージをインストールします。
$ python --version
Python 3.6.5
今回は、3.6.5
を使います。
必要なパッケージは、setup.pyのREQUIRED_PACKAGES
にあります。
Bazel
Tensorflowでは、ビルドツールにBazelを使用します。
今回は、Homebrewを用いてインストールします。
$ brew tap bazelbuild/tap
$ brew tap-pin bazelbuild/tap
$ brew install bazelbuild/tap/bazeln
$ bazel version
INFO: Invocation ID: 5cd106fb-0375-4904-b4ab-4a4a71772405
Build label: 0.20.0
Build target: bazel-out/darwin-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Fri Nov 30 14:38:25 2018 (1543588705)
Build timestamp: 1543588705
Build timestamp as int: 1543588705
Tensorflowリポジトリをクローン
tensorflow/tensorflow: An Open Source Machine Learning Framework for Everyone
Tensorflowのリポジトリをクローンします。300MiBくらいあるので時間がかかります、、、
$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow
次に利用するバージョンをチェックアウトします。
$ git tag --sort=version:refname
...
v1.12.0
v1.12.0-rc0
v1.12.0-rc1
v1.12.0-rc2
執筆時点(2018/12/20)では、v1.12.0
が最新です。
公式のリリースからも確認することができます。
Releases · tensorflow/tensorflow
$ git checkout -b v1.12.0 refs/tags/v1.12.0
共有ライブラリのコンパイル
共有ライブラリ(C++ API)をコンパイルします。
下記コマンドを実行することで、初期設定を行えます。
$ ./configure
途中、y/nで質問がありますが
Macを使っている場合は、Y/nやy/N(大文字がデフォルト)に従えば大丈夫です。
GPUがある場合は、CUDAノバージョンやパスを指定する必要があります。
Bazelを用いてビルドします。
$ bazel build -c opt //tensorflow:libtensorflow_cc.so
...
Target //tensorflow:libtensorflow_cc.so up-to-date:
bazel-bin/tensorflow/libtensorflow_cc.so
INFO: Elapsed time: 2231.907s, Critical Path: 214.42s
INFO: 4193 processes: 4191 darwin-sandbox, 2 local.
INFO: Build completed successfully, 4257 total actions
$ ls -lh bazel-bin/tensorflow/libtensorflow_cc.so
-r-xr-xr-x 1 shfukush wheel 177M 12 13 15:08 bazel-bin/tensorflow/libtensorflow_cc.so
環境依存ですが自分の場合は12分ほどかかりました。
ビルドが成功すると、bazel-bin/tensorflow/
配下に共有ライブラリがビルドされます。
fatal error: 'ares.h' file not found
初回にaresh.h
が見つからないとのエラーが発生しました。
external/grpc/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc:23:10: fatal error: 'ares.h' file not found
#include <ares.h>
^~~~~~~~
1 error generated.
aresは、非同期DNSリクエストに関するCライブラリです。
下記のようにインストールすることで解決しました。
$ git clone https://github.com/c-ares/c-ares.git
$ cd c-ares
$ ./configure
$ make
$ make install
Tensorflowライブラリをインストール
ビルドされた共有ライブラリlibtensorflow_cc.so
をインストールします6。
Macでは、.so
を.dylib
にする必要があるので注意してください。
$ install -m 0644 bazel-bin/tensorflow/libtensorflow_cc.so /usr/local/lib/libtensorflow_cc.dylib
$ install_name_tool -id /usr/local/lib/libtensorflow_cc.dylib /usr/local/lib/libtensorflow_cc.dylib
次にヘッダーファイルをコピーします。(今回は/usr/local/include/tensorflow/
)
Tensorflow用いたC++ソースをコンパイルする際は、このディレクトリをインクルードパスに指定します7。
下記リンクのスクリプトを実行することでコピーできます。
アプリケーション開発
いよいよ、openFrameworksを用いて開発していきます。
プロジェクト作成
プロジェクトは、いつも通り作成します。
Create a new project
作成後にプロジェクトに対してTensorflowのパスなどを設定します。
設定項目は下記の3つです。
項目 | パス |
---|---|
Header Search Paths | /usr/local/include/tensorflow/ |
Library Search Paths | /usr/local/lib/ |
Link Binary With Libraries | libtensorflow_cc.dylib |
Link Binary With Librariesの設定だけは、Build Phases
にあります。
サンプルを実行
ここまで完了したら、公式にあるサンプルコードを実行します。
C++ API | The basics
#include "ofMain.h"
#include "ofApp.h"
#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"
int main() {
using namespace tensorflow;
using namespace tensorflow::ops;
Scope root = Scope::NewRootScope();
// Matrix A = [3 2; -1 0]
auto A = Const(root, { {3.f, 2.f}, {-1.f, 0.f} });
// Vector b = [3 5]
auto b = Const(root, { {3.f, 5.f} });
// v = Ab^T
auto v = MatMul(root.WithOpName("v"), A, b, MatMul::TransposeB(true));
std::vector<Tensor> outputs;
ClientSession session(root);
// Run and fetch v
TF_CHECK_OK(session.Run({v}, &outputs));
// Expect outputs[0] == [19; -3]
LOG(INFO) << outputs[0].matrix<float>();
return 0;
}
実行します。
2018-12-17 19:09:01.822704: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.2 AVX AVX2 FMA
2018-12-17 19:09:01.839162: I .../apps/myApps/pix2pix/src/main.cpp:27] 19
-3
このように表示されれば、成功です。
これでTensorflowを自由に使うことができます!
モデル(グラフ)を読み込む
本来は、モデルを実装・学習しますが、今回はすでに学習済みのモデルを使用します。
使用するのはLabels to Street Sceneという、ラベル画像をもとにストリートの画像を生成してくれるモデルです。
Image-to-Image Translation with Conditional Adversarial Networksより引用
ReleaseのAssetsにあるlabels_2_street_scene.zip
をダウンロードして下さい。
Release v1.0.0 · shion1118/oF-pix2pix
その後、graph_l2s.pb
をプロジェクトフォルダのbin/data/
に配置します。
モデルは、GraphDefクラスで扱います。tensorflow::ReadBinaryProto
にモデルのファイルパスとモデル(グラフ)のポインタを渡すことで読み込めます。
shared_ptr<tensorflow::GraphDef> Model::load_graph() {
shared_ptr<tensorflow::GraphDef> graph(new tensorflow::GraphDef());
ofLog(OF_LOG_NOTICE, "Load graph: " + this->model_path);
tensorflow::Status status = tensorflow::ReadBinaryProto(tensorflow::Env::Default(), this->model_path, graph.get());
if (!status.ok()) {
ofLog(OF_LOG_ERROR, "Cannot load graph!!");
return nullptr;
}
return graph;
}
セッションを作成する
Tensorflowでは、モデルや推論の実行などをSession
クラスで管理します。
まず、セッションを作成します。
shared_ptr<tensorflow::Session> Model::create_session() {
tensorflow::Session* session;
ofLog(OF_LOG_NOTICE, "Create session.");
tensorflow::Status status = tensorflow::NewSession(tensorflow::SessionOptions(), &session);
if (!status.ok()) {
ofLog(OF_LOG_ERROR, "Cannot create session!!");
return nullptr;
}
return shared_ptr<tensorflow::Session>(session);
}
作成した後に、先ほどのモデルを設定します。
this->session = create_session();
tensorflow::Status status = this->session->Create(*graph);
if (!status.ok()) {
ofLog(OF_LOG_ERROR, status.ToString());
return;
}
セッションが作成できれば、実際に実行していきましょう!
セッションを実行する
実行は非常にシンプルです。
引数で必要なのは、入力テンソル・出力のオペレーション名・出力テンソルです。
実際にコードを見ていただければわかりますが、今回のモデルの出力オペレーションは"generator/generator_outputs"
となっています。
session->Run(input_tensors, output_op_names, {}, &output_tensors);
少し考えなければいけないのが、ofImage
をTensor
に(逆)変換することです。
今回は、このように実装しました。(ご指摘などはPRやIssueでお願いします)
ofImage
を参照しピクセル情報を、あらかじめ初期化しておいたTensor
に直接代入しています。
画像データなどは重いので参照渡しする方がいいように思います。
void Utils::image_to_tensor(const ofFloatImage &src, tensorflow::Tensor &dst, const ofVec2f &in_range, const ofVec2f &out_range) {
auto *in = src.getPixels().getData();
auto dst_data = dst.flat<float>().data();
int n = dst.NumElements();
float scale, offset;
calc_scale_offset(in_range, out_range, scale, offset);
for (int i = 0; i < n; i++) {
dst_data[i] = in[i] * scale + offset;
}
}
Tensor
からofImage
への変換も同じようにやっています。
void Utils::tensor_to_image(const tensorflow::Tensor &src, ofFloatImage &dst, const ofVec2f& in_range, const ofVec2f& out_range) {
auto *out = dst.getPixels().getData();
auto src_data = src.flat<float>().data();
int n = src.NumElements();
float scale, offset;
calc_scale_offset(in_range, out_range, scale, offset);
for (int i = 0; i < n; i++) {
out[i] = src_data[i] * scale + offset;
}
}
スケールとオフセットの計算は、ofxMSATensorflow8を参考に実装しました。
実行結果
同じくAssetsに256x256のテスト画像test_image.jpg
があるので、
これをofImage
で読み込めば、テスト実行できるかと思います。
Mac(CPU)の環境で、1秒前後で実行することができました!
GPUがあれば、リアルタイム処理もできると思います。
まとめ
FBOを用いて自分で書いた画像から生成できるようにしました。
カラーパレットから色を選択して描いた画像から実行することができます。
機械学習を用いたメディアアートへの一歩を踏み出せたんじゃないでしょうか?
先日参加したBRDGにおいてKeijiro Takahashiは、pix2pixをフレーム予測に応用しVJを試みていました。
深層学習を勉強してリアルタイム映像表現に使ってみた話
今回のやっているのは、あくまでpix2pixをopenFrameworksで実行できるようになっただけです。日々様々なところで使われる「AI」という言葉、驚くべき速さで進歩する深層学習、ぜひこのメディウムを問い直して欲しいと思います。このメディウムをどう扱い、どのように表現するか、その手段は意外にも簡単に手に入ります。
最終的なコードはGithubにあるので参照して見て下さい!
shion1118/ofPix2pix
ご指摘などはPRやIssueでお願いします
最後まで読んでいただき、ありがとうございました。
-
discrete figures -ELEVENPLAY×Rhizomatiks Research×Kyle McDonald.
Retrieved from https://research.rhizomatiks.com/s/works/discrete_figures/ ↩ -
openFrameworks.
Retrieved from https://openframeworks.cc/ ↩ -
Phillip Isola, Jun-Yan Zhu et al., Image-to-Image Translation with Conditional Adversarial Networks.
Retrieved from https://arxiv.org/abs/1611.07004 ↩ ↩2 -
Tensorflow cppAPI ライブラリを g++ でコンパイル.
Retrieved from https://toxweblog.toxbe.com/2017/11/10/tensorflow-cppapi-compile-by-g/ ↩ -
Tensorflow.
Retrieved from https://www.tensorflow.org/ ↩ -
高澤佑太, pix2pixの紹介 | 株式会社クロスコンパス's Blog
Retrieved from https://www.wantedly.com/companies/xcompass/post_articles/76117 ↩ -
tensorflowをソースからコンパイルしてインストールする/
Retrieved from http://memo.saitodev.com/home/tensorflow/build/ ↩ -
ofxMSATensorFlow.
Retrieved from https://github.com/memo/ofxMSATensorFlow ↩