2021年9月27日月曜日

C言語の"Hello, World!"をGoogle Colaboratoryでやってみた。

おじさん、ふと思ったのですが、

コロナ渦での大学とかのプログラミング演習ってどうやってやってんだろ?

だって大学に行かないとコンピュータの端末とかないじゃん!


このように思って調べてみました。

なんと、今どきの理系の若者は、Google Colaboratoryを使って、スマホやタブレットでプログラムを書くらしい。

そしてGoogleドライブでそのまま提出!

なにそれ?おじさんついていけない・・・・。

おじさんが大学生の時は、情報センターのワークステーションを何時間も占領してプログラムを書いていたのに。

いまはスマホやタブレットで家や電車の中などどこでもプログラムができるんですね。

時代の違いを感じる・・・。





ということで、Google Colaboratoryを使ってC言語の"Hello, World!"をやってみました。


1. Google Colaboratoryで新しいノートブックを作成


2. コードのセルを追加して以下のコードを入力

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

%%file hello.c


#include  <stdio.h>

int main(void) {

   printf("Hello, World!\n");

   return 0;

}


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


3. コードのセルをもう一つ追加して以下のコードを入力

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

!gcc hello.c -o hello && ./hello

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


4. 上記の2つのセルを実行。

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

Hello, World!

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



おーすげー、コンパイラをインストールとかしなくていいんだ。

ボダンを押すだけでコンパイルして実行してくれます。


文法とか間違うとどうなるんだろ?

やってみました。

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

%%file hello.c

#include  <stdio.h>

int main(void) {

    hogehoge

   printf("Hello, World!\n");

   return 0;

}

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


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

!gcc hello.c -o hello && ./hello

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


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

hello.c: In function ‘main’:

hello.c:3:5: error: unknown type name ‘hogehoge’

     hogehoge

     ^~~~~~~~

hello.c:4:11: error: expected declaration specifiers or ‘...’ before string constant

    printf("Hello, World!\n");

           ^~~~~~~~~~~~~~~~~

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


すげー。ちゃんとコンパイルエラーも出る。




C言語のGPUとかも使えるのかな???

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

!nvcc hello.c -o hello && ./hello

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


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

Hello, World!

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


おーなんかGPUも使えるっぽい。

スマホではコーディングをやりにくい人もFireHD10のタブレットを1万円で買えば、

ほとんど初期投資ゼロ円で簡単なディープラーニングまで学べるのね。

Google Colaboratoryすごすぎる。


コンピュータ将棋ソフトを動かす。

 先日のブログでも触れたように、おじさんonnxruntimeを自由自在に使えるようになりました。

いつもMNISTばっかりだとつまらないので、ディープラーニングのふかうら王をビルドしてみました。


https://yaneuraou.yaneu.com/

https://github.com/yaneurao/YaneuraOu


このソースコード本当によく整備されています。

ディープラーニング系も非ディープラーニング系も誰でも簡単にいろいろなビルドオプションでビルドができます。


囲碁とか将棋のディープラーニングのソフトは、モンテカルロツリー検索 (MCTS)をしてゲームプレイの手を決定するんですね。

ソースコードを見ているだけで本当にいろいろ勉強になります。

ソースコード少ないし、簡単にビルドできるし、将棋ソフトってディープラーニングや強化学習の勉強にとっても良い?


いろんな条件で学習させたデータで将棋ソフト同士を対戦させるのも面白いね。

elmo囲い強いなぁとか将棋の勉強にもなります。


BERTを使った将棋ソフトもあるようです。

https://github.com/nyoki-mtl/bert-mcts-youtube

いままで全然気づかなかったけれど、たしかに、NLPの文章の単語入力と将棋の盤面データ入力はデータ構造的には似てるもんね。


実際に将棋ソフトをカスタマイズしてビルドして動かしてみてわかったのですが、

これ学習結果を利用してコンピュータ将棋ソフトを動かすだけでも相当スペックの高いPCがいるね。

人間よりめちゃくちゃ強いけど人間よりめちゃくちゃ遅い。

それで藤井聡太さんがRyzenThreadripperを買うのね。

さらに、学習結果を利用するだけでこれこんだけ遅いから、将棋を学習させるのに1000万円くらいのGPUがいる。


将棋に限らず、ディープラーニング系のソフトを動かすといつも思うのですが、このブログで書いているようなことを学習させるのに、だいたい100万円位のGPUが8枚くらいいります。

腕時計に例えると、

ロレックスサブマリーナ、ロレックスサブマリーナ、

ロレックスサブマリーナ、ロレックスサブマリーナ、

ロレックスサブマリーナ、ロレックスサブマリーナ、

ロレックスサブマリーナ、ロレックスサブマリーナ、


ロレックスをじゃんじゃん買えるような人でないと最先端のディープラーニングの学習ってできないのです。


おじさんの持ってるGPUは腕時計でいうとSeiko5

見た目は似てるよねー。

おじさんも日本語のBERTやりたかったのですが、ディープラーニングって一部のお金持ちしか学習させることができないのでしょうか?

一般の人が買えるGPUで学習させられるのはMNISTとRESNET50くらいだよね。

みんなどうやって学習させてるの???


2021年9月13日月曜日

SIMDライブラリ、libsimdppを使う

 最近、カメラの動画で画像処理やディープラーニングなどの計算をやることが増えています。

その時に常に課題にあがるのが、処理速度の高速化。



ディープラーニングのネットワークってそもそも計算の量を減らしながら良い結果が得られるように設計されているし、ディープラーニングのフレームワークも相当高速化されているから、そう簡単にこれ以上早くならないっつーの。


最近はいろいろな機械学習フレームワークが、学習の計算はGPUでやって、その結果を利用するのはSIMD命令でやって高速化するようになっているようです。

SIMD命令ってコンパイラやCPUに依存するからあんまり使いたくないんだよねぇと思っていたら、SIMD命令を抽象化するライブラリを見つけました。

いろいろなプロジェクトがSIMD命令のところをどう書くのか、いろいろ試行錯誤しているようですが、なかなか読みやすくて、移植性があるように書くのが難しいようです。

いろいろ調べていたらついに便利なライブラリを見つけました。

https://qiita.com/engineer/items/f47a54bffe2691f9784a


なんだ、ちょー簡単にSIMD命令を抽象化できる、libsimdppライブラリがあるじゃないか!


https://github.com/p12tic/libsimdpp


これ本当によくできています。

SIMD命令を使わない設定やSIMD命令がないコンパイラのときはSIMD命令をエミュレーションしてくれるので、PCの高度なIDE環境で開発を行って、実際には組み込みで動かすというようなことも簡単にできます。

簡単にSIMDを使う場合と使わない場合を切り替えられるので、実行速度を比較しながらパフォーマンスチューニングを行うこともできます。


こんだけ便利なライブラリなのになぜか使い方のサンプルがないので、サンプルを作ってみました。


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

#define SIMDPP_ARCH_X86_SSE4_1 true

//#define SIMDPP_ARCH_POWER_ALTIVEC true

//#define SIMDPP_ARCH_NULL true


#include <iostream>

#include <chrono>

#include <simdpp/simd.h>

//#include "simdpp_simd.h"


//example where i got this from

//https://github.com/p12tic/libsimdpp/tree/2e5c0464a8069310d7eb3048e1afa0e96e08f344


// Initializes vector to store values

void init_vector(float* a, float* b, size_t size) {

    for (int i=0; i<size; i++) {

        a[i] = i * 1.0;

        b[i] = (size * 1.0) - i - 1;

    }

}




using namespace simdpp;

int main() {

    //1048576

    const unsigned long SIZE = 4 * 1500000;


    float* vec_a=new float[SIZE];

    float* vec_b= new float[SIZE];

    float* result= new float[SIZE];


///////////////////////////*/

//LibSIMDpp

    //*

    auto t1 = std::chrono::high_resolution_clock::now();


    init_vector(vec_a, vec_b, SIZE);

    for (int i=0; i<SIZE; i+=4) {

        float32<4> xmmA = load(vec_a + i);  //loads 4 floats into xmmA

        float32<4> xmmB = load(vec_b + i);  //loads 4 floats into xmmB

        float32<4> xmmC = add(xmmA, xmmB);  //Vector add of xmmA and xmmB

        //float32<4> xmmD = div(xmmA, xmmB);  //Vector add of xmmA and xmmB

        store(result + i, xmmC);            //Store result into the vector

    }


    auto t2 = std::chrono::high_resolution_clock::now();


    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count()

              << " milliseconds\n";

    //*/



///////////////////////////*/

//standard

    //*

    init_vector(vec_a, vec_b, SIZE);

    t1 = std::chrono::high_resolution_clock::now();


    for (auto i = 0; i < SIZE; i++) {

        result[i] = vec_a[i]  + vec_b[i];

    }


    t2 = std::chrono::high_resolution_clock::now();


    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count()

              << " milliseconds\n";

    //*/



    delete[] vec_a;

    delete[] vec_b;

    delete[] result;

    return 0;

}


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


2021年9月10日金曜日

ONNX RuntimeでMNISTをする。

 少し前、将棋の藤井颯太さんが、70万円もするパソコンでコンピュータ将棋ソフトを動かしているらしいと話題になりました。


これ、どのように動いているのだろう?


調べてみると、オープンソースの将棋ソフトは、「やねうら王」ベースのソフトと「dlsyogi」ベースの二つのものが主流らしい。

そして、onnxruntimeを使えば、「やねうら王」でも「dlsyogi」のonnxフォーマットのデータが読めるらしい。


すごいねー。

機械学習系のソフトってだいたいバージョンがちょっとでも合わないと動かないのに、将棋ソフトはonnxruntimeを使っているので、onnxruntimeとonnxのデータのバージョンが違ってもきちんと動きます。


onnxruntimeソースを見てみると、onnxのバージョンごとに使う関数を変えて互換性を保っているのですね。




このように、最近onnxruntimeを使った面白いソフトがたくさん出てきているので、onnxruntimeの使い方を勉強がてら、いつものようにMNISTを作ってみました。


ONNX Runtime

https://github.com/microsoft/onnxruntime


ONNX MNIST

https://a-kawashiro.hatenablog.com/entry/2019/03/07/201304


onnxruntimeってMLASというMicrosoftBLAS?みたいなライブラリを使っているのですが、IntelのCPUとVisualStudioのコンパイラーでしか動かなかったいものを、無理やり他のプラットフォームやコンパイラーでも動くようにした感が満載です。

一部アセンブラがないとビルドできないとか、SIMD命令の書き方がきちんと分離できてないとか。なんなんだこのへたくそライブラリ。


おじさん、onnxruntimeをC/C++言語だけで動くように改造してみました。

本当はSIMD命令もきちんと分離したかったのですが、これはめんどくさくて途中で断念。

でもC++のMNISTできた!


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

#include <onnxruntime_cxx_api.h>

#include <algorithm>

#include <iostream>


struct MNIST {

MNIST() {

auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);

input_tensor_ = Ort::Value::CreateTensor<float>(memory_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());

output_tensor_ = Ort::Value::CreateTensor<float>(memory_info, results_.data(), results_.size(), output_shape_.data(), output_shape_.size());

}


std::ptrdiff_t Run() {

const char* input_names[] = {"Input3"};

const char* output_names[] = {"Plus214_Output_0"};


session_.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor_, 1, output_names, &output_tensor_, 1);


result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end()));

return result_;

}


static constexpr const int width_ = 28;

static constexpr const int height_ = 28;


std::array<float, width_ * height_> input_image_{};

std::array<float, 10> results_{};

int64_t result_{0};


private:

Ort::Env env;

Ort::Session session_{env, ORT_TSTR("model.onnx"), Ort::SessionOptions{nullptr}};


Ort::Value input_tensor_{nullptr};

std::array<int64_t, 4> input_shape_{1, 1, width_, height_};


Ort::Value output_tensor_{nullptr};

std::array<int64_t, 2> output_shape_{1, 10};

};


std::unique_ptr<MNIST> mnist_;


int main()

{

try {

mnist_ = std::make_unique<MNIST>();

} catch (const Ort::Exception& exception) {

std::cerr << exception.what() << std::endl;

return 1;

}


mnist_->input_image_ = {

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,

0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

};

mnist_->Run();

std::cout << mnist_->result_ << std::endl;


std::cout << std::endl;

std::cout <<"detail" <<std::endl;


std::array<float, 10>::iterator ite;

int ct = 0;

for (ite = mnist_->results_.begin(); ite != mnist_->results_.end(); ite++) {

std::cout << ct++<<"     "<<(*ite) << std::endl;

}

return 0;

}

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


全ソースはこちら。

https://drive.google.com/file/d/1-96nTJBsDRhdvFz09C4d17vS8r50YntS/view?usp=sharing



2021年6月7日月曜日

Blocklyをカスタマイズ

 おじさん、microbitを買ってみました。

プログラムをどうやってコンパイルするのかと思ったら、ブラウザでなんか適当にブロックをおいてダウンロードするだけ。

どうやってmicrobitにプログラムを書き込むのかと思ったら、USBメモリみたいに見えるフォルダにダウンロードしたファイルを置くだけでした。


このボードだけあれば、ほかに何にインストールしなくても動くようになってるのね。

さすがイギリス製、イタリア製のArduinoと違い本当によくできている。

LEDが25個あるから文字も表示できるのでデバッグも簡単だし。



ScratchとかMicrobitとかいろんな会社からいろんなロボットキットが発売されているので、こういうような教材をおじさんが個人でも作れるのか調べてみました。



UIはBlocklyというJSのライブラリで作れるのね。

https://developers.google.com/blockly



BlocklyのライブラリがJavaScriptやLUAのコードを生成してくれるらしい。

LUAのインタープリタはC言語でマイコンで実行できるので、それをマイコンに転送して実行すればよいのね。

なんだ意外と簡単じゃん。


でも、おじさん、C/C++じゃないと嫌なんだよね。

どうやったら、C/C++出力ができるのか考えてみました。



JavaScriptの出力で文字列とかのシングルコート「'」を「"」で出力すれば、C++言語とほぼ同じなので、あとvarをintに置換すればコンパイラでコンパイルしてラズパイとかでもBlocklyを動かせるんじゃない?


ということで、blocklyのJSの'\''の部分を'\"'に置換して、C++を出力するblocklyを作ってみました。

なんかソースコードに、「Googleのコーディング規約で文字列はシングルクォートじゃないとだめだからシングルクォート」と書いてある。

おじさん的にはダブルクォートにしてくれるほうがいいと思うんだけどなぁ。

まぁその部分を3か所修正するだけ。できた!


blocklyのカスタマイズって楽しいね。

こうやってblocklyを使ってロボット教材を作れるのね。



2021年5月29日土曜日

BERTをやってみた。

最近ブログを更新していなかった理由の一つに、「せっかくAIの論文を読んで実装したのに動かなかった。」というのがあります。

BERTが全然動かなかったのです。

BERTは「アマゾンはどこにありますか」のようにAIに言葉で質問をすると「南アメリカです。」のように答えてくれるプログラムらしいです。

論文

https://qiita.com/omiita/items/72998858efc19a368e50


すげーなー。自然言語処理の王様らしいです。

ほんとうにこんなことができるのか、おじさんさっそくビルドしてみました。


GoogleがTensorFlowでBERTのC++実装を公開していたので、3月にビルドしてみました。

でもなぜかC++バージョンは動かない。何日もかけてビルドしたのに。


昨日久しぶりにGitHubをみたら、なんかバグが治ったっぽいので、C++のBERTを今日一日かけてWindowsで移植してみました。


昨日はPyTorch、今日はTensorFlow。

毎日いろんな機械学習フレームワークをビルドしないといけないです。



まず、C++のBertを取ってきます。

https://www.tensorflow.org/lite/inference_with_metadata/task_library/bert_question_answerer



そして、ソースを一つ一つWindowsで動くように直していきます。

さらに g++ *.ccだけでビルドして動くように、すべてのライブラリをディレクトリ構造がないようにフラットにしてビルドします。

直すこと数時間。

世界初!やっとTensorFlowのC++のBertがWindowsで動くようになりました。


ソース一式はこちら

https://drive.google.com/file/d/1qFM-W0ZRqTV8pri4tB1s3Bsez8fXyQOG/view?usp=sharing



入力

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

bert_question_answerer_demo -- \

 --model_path=mobilebert.tflite \

 --question="Where is Amazon rainforest?" \

 --context="The Amazon rainforest, alternatively, the Amazon Jungle, also known in \

English as Amazonia, is a moist broadleaf tropical rainforest in the Amazon \

biome that covers most of the Amazon basin of South America. This basin \

encompasses 7,000,000 km2 (2,700,000 sq mi), of which \

5,500,000 km2 (2,100,000 sq mi) are covered by the rainforest. This region \

includes territory belonging to nine nations."

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


デフォルトの実装が英語なのですが、日本語で学習させれば、きちんと日本語で答えられるようになるらしいです。

こんな感じで文章と質問を与えると答えを表示してくれます。



答え

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

Time cost to answer the input question on CPU: 36807.1 ms

answer[0]: 'most of the Amazon basin of South America.'

    logit: '8.79093, start_index: 39, end_index: 46

answer[1]: 'Amazon basin of South America.'

    logit: '7.74646, start_index: 42, end_index: 46

answer[2]: 'the Amazon basin of South America.'

    logit: '7.58969, start_index: 41, end_index: 46

answer[3]: 'Amazon biome that covers most of the Amazon basin of South America.'

    logit: '7.56741, start_index: 34, end_index: 46

answer[4]: 'the Amazon biome that covers most of the Amazon basin of South America.'

    logit: '7.17913, start_index: 33, end_index: 46

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



日本語で言うと、こんな感じでしょうか。


コンテキスト:「アマゾン熱帯雨林、または英語でアマゾニアとしても知られているアマゾンジャングルは、南アメリカのアマゾン盆地のほとんどをカバーするアマゾンバイオームの湿った広葉樹熱帯雨林です。この盆地は7,000,000 km2(2,700,000平方マイル)、そのうち5,500,000 km2(2,100,000 sq mi)が熱帯雨林に覆われています。この地域には、9か国に属する地域が含まれています。」


質問:「アマゾンの熱帯雨林はどこにありますか?」

答え:「アマゾンの大半は南アメリカにあります。」


BERTすごいねぇ。

文章をきちんと理解している。。。。

これ「やじまは悪い人ですか?」とか自分の嫌いな人の名前とかいろんな文章を入力して遊びたくなりますね。



2021年5月28日金曜日

StyleGANをやってみた

 おじさん、絵とか写真をコンピュータのAIが勝手に作ってくれる、StyleGANを勉強してみました。

https://qiita.com/woodyZootopia/items/10e7e7cf96bfe1249f66


まえから自分で動かしてみたかったんだよね。


このStyleGANを使うと、今話題の新垣結衣の画像も生成できちゃうらしい。

http://cedro3.com/ai/search-for-yui/

なんという知識とGPUの無駄遣い。

StyleGANのベクトル空間に我々人類は住んでるんだね。


このStyleGANですが、昔はNVIDIAのGPUでしか動かなかったんですが、最近はCPUだけでも動くみたい。

さらに、Pytorchの学習済みのデータをC++で簡単に利用できるように変換してくれる、tensor4なるものがあって、それを使うとC++でもStyleGANを動かせます。

https://github.com/podgorskiy/tensor4


ここに、本当に動くのかあやしいStyleGANのC++実装があるのですが、これを修正して、きちんとおじさんのGPUがないWindowsで動くようにしてみました。

https://github.com/podgorskiy/StyleGANCpp


このtensor4を作った人すごいね。

他になんのライブラリも必要なく、StyleGANを数個のファイルだけでC++で動かすことができます。10分くらいでStyleGANをつくれちゃいます。


これはかなり面白い。

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

#include "tensor4.h"

#include "StyleGAN.h"

#include "numpy-like-randn.h"


#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "stb_image_write.h"



int main()

{

int layers = 9;


// if loading compressed

//auto model = StyleGANLoad("StyleGAN.ct4", layers);


// if loading original

auto model = StyleGANLoad("StyleGAN.t4", layers, false);


{

auto rs = numpy_like::RandomState(4);

auto z = GenZ(rs);

auto w = GenW(model, z);

auto w_truncated = (w - t4::Unsqueeze<0>(model.dlatent_avg)) * 0.7f + t4::Unsqueeze<0>(model.dlatent_avg);

t4::tensor4f x;

t4::tensor3f img;

t4::tensor3f img2;

for (int i = 0; i < layers; ++i)

{

printf("layers=%d\n",i);

t4::tensor2f current_w = w;

if (i < 4)

{

current_w = w_truncated;

}

auto result = GenImage(model, x, current_w, i);

x = result.first;

img = result.second;

}


img2 = img * 0.5f + 0.5f;

float* p = img2.ptr();


for (int i = 0; i < 1024 * 1024*3; i++) {

float f;

f = p[i + 1024 * 1024 * 0];

if (f < 0)f = 0;

if (f >1)f = 1;

p[i + 1024 * 1024 * 0] = f;

}

unsigned char* a = new unsigned char[1024 * 1024 * 3];

for (int i = 0; i < 1024 * 1024 ; i++) {

a[i * 3 + 0] = (int)(p[i + 1024 * 1024 * 0] * 196.0);

a[i * 3 + 1] = (int)(p[i + 1024 * 1024 * 1] * 196.0);

a[i * 3 + 2] = (int)(p[i + 1024 * 1024 * 2] * 196.0);

}

stbi_write_png("aaa.png", 1024, 1024, 3, a, 1024 * 3);

delete[] a;

printf("saved\n");

}


return 0;

}

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


ということで写真をAIで生成してみました。


これ、本当にAIが生成したものなのでしょうか?
あまりに本物過ぎて合成なのか本物なのかわからないじゃないか!











2021年3月12日金曜日

顔ランドマーク検出をやってみた。

 先日このブログで、FaceSwapのプログラムを作りました。

だけど、時間がなくて、顔の輪郭部分の点を手動で設定していました。

このため、任意の写真で顔を入れ替えようとすると、点を設定する作業がとてもめんどくさい。

おじさん、任意の写真の顔をもっと簡単に入れ変えたいのです。


そこで、今回はどんな顔でも入れ替えできるように、プログラムを改良してみたと思います。


どうやって顔の輪郭を抜き出すんだろ。

調べてみると、顔の輪郭部分の検出のことを「顔ランドマーク検出」というらしい。

https://qiita.com/RanWensheng/items/d8768395166d041a753a


shape_predictor_68_face_landmarks.datというdlibの学習済みデータを使うのが一般的なようです。

学習済みデータ100Mバイトもあるじゃないか!

しかもdlibというライブラリを使うのね。

こんなことばっかりやっているから、おじさんのハードディスクがどんどんなくなっていく。


こんなかんじでdlibを使うのね。

https://iatom.hatenablog.com/entry/2020/11/01/152334



こんなかんじでdlibとOpevCVでFaceSwapをすれば、動画をどんどん変換できるのか。

https://github.com/MishaPrigara/FaceSwap



というわけで顔ランドマーク検出対応のFaceSwapを作ってみました。

ソースはこちら

https://drive.google.com/file/d/1_F9lTnfBVbme5pmI9WrSfaayQr5sQut_/view?usp=sharing

その辺に落ちている写真で実験。

入れ替え後の写真

元の写真


できた!

これ確かに任意の写真で顔を入れ替えられるようになると面白いね。


2021年3月8日月曜日

プラズマスピーカー

 おじさん、中学生の時に電子工作でラジオ作ったんだよね。

何気なく最近の中学生に、最近はどんな電子工作をするのか聞いてみたところ、「テスラコイル」と言われました。

おじさん、最近の中学生についていけない・・・・・。

テスラコイルって何満ボルトもあるやつでしょ?

電子工作で作れるの?

調べてみると、テスラコイルのキットがアマゾンで600円くらいで売られているじゃないか!

しかも、テスラコイルから発せられるプラズマで音を鳴らす、プラズマスピーカーなるものも作れるらしい。

もう何人も作った人がいるのね。

https://www.youtube.com/watch?v=sOr_7gOXjgk


というわけで、おじさんも数年遅れでアマゾンに注文して、プラズマスピーカーを作ってみました。

すげー。プラズマで本当に動く。

あんまり音はよくないけれど、この分解能というか解像度感はすごいね。

振動版がなくても音ってならせるんですね。

ということは原理的には雷のゴロゴロ音の代わりにも音楽ならせるはず。


昔はこういう面白いキットは日本でしか売ってなかったんですが、最近はすごい面白いキットが中国でたくさん作られているんですね。

日本が技術大国であったのはもう過去なのね。


2021年3月3日水曜日

FaceSwapをやってみた。

最近ブログを更新していませんでした。

画像処理やAI系で個人で数時間でできそうな面白いネタがあまりないんだよねぇ。


暇なので、相棒のテレビをみていたら、動画の顔を別の人の顔に変えるDeepfakeというソフトがあるらしい。

なんかいろいろ悪いことに使われそう。

FaceSwapという技術で動画から静止画を切り出して一枚一枚変換しているのね。

ソースを見てみたら、おじさんの大嫌いなPythonで書かれているじゃないか!


C++でできないかと思っていたところC++のFaceSwapのソースを見つけました。

https://learnopencv.com/face-swap-using-opencv-c-python/

これならば数時間で改造して遊ぶことができる。

改造して、以前作った最小のOpenCVで動くようにしてみました。



みんな、トランプ大統領の顔を変えて実験するのね。

おーなんかできた。

ソースコードも意外と短い。

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

#include "opencv2_core.hpp"

#include "opencv2_imgproc.hpp"

#include "opencv2_imgproc_imgproc_c.h"

#include "opencv2_imgcodecs.hpp"

#include "opencv2_photo.hpp"


#include <iostream>

#include <fstream>

#include <string> 

using namespace cv;

using namespace std;


//Read points from text file

vector<Point2f> readPoints(string pointsFileName){

vector<Point2f> points;

ifstream ifs (pointsFileName.c_str());

    float x, y;

int count = 0;

    while(ifs >> x >> y)

    {

        points.push_back(Point2f(x,y));


    }


return points;

}


// Apply affine transform calculated using srcTri and dstTri to src

void applyAffineTransform(Mat &warpImage, Mat &src, vector<Point2f> &srcTri, vector<Point2f> &dstTri)

{

    // Given a pair of triangles, find the affine transform.

    Mat warpMat = getAffineTransform( srcTri, dstTri );

    

    // Apply the Affine Transform just found to the src image

    warpAffine( src, warpImage, warpMat, warpImage.size(), INTER_LINEAR, BORDER_REFLECT_101);

}



// Calculate Delaunay triangles for set of points

// Returns the vector of indices of 3 points for each triangle

static void calculateDelaunayTriangles(Rect rect, vector<Point2f> &points, vector< vector<int> > &delaunayTri){


// Create an instance of Subdiv2D

    Subdiv2D subdiv(rect);


// Insert points into subdiv

    for( vector<Point2f>::iterator it = points.begin(); it != points.end(); it++)

        subdiv.insert(*it);         


vector<Vec6f> triangleList;

subdiv.getTriangleList(triangleList);

vector<Point2f> pt(3);

vector<int> ind(3);


for( size_t i = 0; i < triangleList.size(); i++ )

{

Vec6f t = triangleList[i];

pt[0] = Point2f(t[0], t[1]);

pt[1] = Point2f(t[2], t[3]);

pt[2] = Point2f(t[4], t[5 ]);


if ( rect.contains(pt[0]) && rect.contains(pt[1]) && rect.contains(pt[2])){

for(int j = 0; j < 3; j++)

for(size_t k = 0; k < points.size(); k++)

if(abs(pt[j].x - points[k].x) < 1.0 && abs(pt[j].y - points[k].y) < 1)

ind[j] = k;


delaunayTri.push_back(ind);

}

}

}



// Warps and alpha blends triangular regions from img1 and img2 to img

void warpTriangle(Mat &img1, Mat &img2, vector<Point2f> &t1, vector<Point2f> &t2)

{

    

    Rect r1 = boundingRect(t1);

    Rect r2 = boundingRect(t2);

    

    // Offset points by left top corner of the respective rectangles

    vector<Point2f> t1Rect, t2Rect;

    vector<Point> t2RectInt;

    for(int i = 0; i < 3; i++)

    {


        t1Rect.push_back( Point2f( t1[i].x - r1.x, t1[i].y -  r1.y) );

        t2Rect.push_back( Point2f( t2[i].x - r2.x, t2[i].y - r2.y) );

        t2RectInt.push_back( Point(t2[i].x - r2.x, t2[i].y - r2.y) ); // for fillConvexPoly


    }

    

    // Get mask by filling triangle

    Mat mask = Mat::zeros(r2.height, r2.width, CV_32FC3);

    fillConvexPoly(mask, t2RectInt, Scalar(1.0, 1.0, 1.0), 16, 0);

    

    // Apply warpImage to small rectangular patches

    Mat img1Rect;

    img1(r1).copyTo(img1Rect);

    

    Mat img2Rect = Mat::zeros(r2.height, r2.width, img1Rect.type());

    

    applyAffineTransform(img2Rect, img1Rect, t1Rect, t2Rect);

    

    multiply(img2Rect,mask, img2Rect);

    multiply(img2(r2), Scalar(1.0,1.0,1.0) - mask, img2(r2));

    img2(r2) = img2(r2) + img2Rect;

    

    

}



int main( int argc, char** argv)

{

//Read input images

    string filename1 = "ted_cruz.jpg";

    string filename2 = "donald_trump.jpg";

    

    Mat img1 = imread(filename1);

    Mat img2 = imread(filename2);

    Mat img1Warped = img2.clone();

    //Read points

vector<Point2f> points1, points2;

points1 = readPoints(filename1 + ".txt");

points2 = readPoints(filename2 + ".txt");

    

    //convert Mat to float data type

    img1.convertTo(img1, CV_32F);

    img1Warped.convertTo(img1Warped, CV_32F);

    

    

    // Find convex hull

    vector<Point2f> hull1;

    vector<Point2f> hull2;

    vector<int> hullIndex;

    

    convexHull(points2, hullIndex, false, false);

    

    for(int i = 0; i < hullIndex.size(); i++)

    {

        hull1.push_back(points1[hullIndex[i]]);

        hull2.push_back(points2[hullIndex[i]]);

    }


    

    // Find delaunay triangulation for points on the convex hull

    vector< vector<int> > dt;

Rect rect(0, 0, img1Warped.cols, img1Warped.rows);

calculateDelaunayTriangles(rect, hull2, dt);

// Apply affine transformation to Delaunay triangles

for(size_t i = 0; i < dt.size(); i++)

    {

        vector<Point2f> t1, t2;

        // Get points for img1, img2 corresponding to the triangles

for(size_t j = 0; j < 3; j++)

        {

t1.push_back(hull1[dt[i][j]]);

t2.push_back(hull2[dt[i][j]]);

}

        

        warpTriangle(img1, img1Warped, t1, t2);


}

    

    // Calculate mask

    vector<Point> hull8U;

    for(int i = 0; i < hull2.size(); i++)

    {

        Point pt(hull2[i].x, hull2[i].y);

        hull8U.push_back(pt);

    }


    Mat mask = Mat::zeros(img2.rows, img2.cols, img2.depth());

    fillConvexPoly(mask,&hull8U[0], hull8U.size(), Scalar(255,255,255));


    // Clone seamlessly.

    Rect r = boundingRect(hull2);

    Point center = (r.tl() + r.br()) / 2;

    

    Mat output;

    img1Warped.convertTo(img1Warped, CV_8UC3);

seamlessClone(img1Warped,img2, mask, center, output, NORMAL_CLONE);


imwrite("output.jpg", output);

    

    //imshow("Face Swapped", output);

    //waitKey(0);

    //destroyAllWindows();

    


return 1;

}

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