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