読み書きプログラミング

日常のプログラミングで気づいたことを綴っています

NodObjCを使ってNode.jsでPasteboardの画像データを取得する

Node.jsでスクリーンショットを扱いたいことがあって、外部コマンドscreencaptureを起動してファイルを生成してそれを読み込むという処理をしていました。
ファイルの生成場所にはRAMディスクを使うなど高速化を図っていたのですが、やはりオーバーヘッドがあるようで、Pasteboardから直接コピーしてみたいと思いました。(screencaptureにはファイルではなくPasteboardにコピーするというオプションがあります)

早速ですが、以下がその関数実装です。

const $ = require('nodobjc');
const ref = require('ref');

$.framework('Foundation');
$.framework('AppKit');

const PASTEBOARD = $.NSPasteboard('generalPasteboard');
const CLASS_ARRAY = $.NSArray('arrayWithObject', $.NSImage('class')); //  $.NSImage('class')は何度も呼び出すとSegmentation faultを起こすので注意。なぜかはよくわからない
const EMPTY_OPTIONS = $.NSDictionary('dictionary');

function getNSFileType(format) {
    switch (format) {
        case 'tiff':
        return $.NSTIFFFileType;
        case 'bmp':
        return $.NSBMPFileType;
        case 'gif':
        return $.NSGIFFileType;
        case 'jpg':
        return $.NSJPEGFileType;
        case 'png':
        return $.NSPNGFileType;
        case 'jp2':
        return $.NSJPEG2000FileType;
        default:
        throw new Error('unsupported format');
    }
}

function getImageFromPasteboard(format) {
    const pool = $.NSAutoreleasePool('new');
    let result = null;

    if (PASTEBOARD('canReadObjectForClasses', CLASS_ARRAY, 'options', EMPTY_OPTIONS)) {
        const nsimage = PASTEBOARD('readObjectsForClasses', CLASS_ARRAY, 'options', EMPTY_OPTIONS)('objectAtIndex', 0);
        const rep = $.NSBitmapImageRep('imageRepWithData', nsimage('TIFFRepresentation'));
        const data = rep('representationUsingType', getNSFileType(format), 'properties', null);
        result = new Buffer(ref.reinterpret(data('bytes'), data('length'))); // poolをdrainするにはコピーが必要
    }
    pool('drain');
    return result;
}

exports.getImageFromPasteboard = getImageFromPasteboard;

結果ですが、screencaptureでPNGフォーマットで保存していた場合と比べて、PasteboardでBMPを取り出すと倍ぐらい速くなりました。TIFFフォーマットで取り出すのが一番速そうですが、Node.js側でTIFFフォーマットを扱ういいライブラリがなくBMPにしました。

今、screencaptureのマニュアルを読むと、保存フォーマットにBMPも選べるらしく、BMPで保存していたらどうだったか気になります。RAMディスクのファイル読み書きよりPNGエンコード、デコードが重かったのかもしれません。

AlphaGo Zeroの研究開発に使われたリソースを見積もってみる

表題の中身

追記
AlphaGoの開発者のお一人Aja Huangさんのプレゼンテーションによると、2000TPUを使って自己対局を行ったようです。
sports.sina.com.cn
記事にはTPUバージョンの情報はありませんが、2000TPUで済んだということはTPU v2だったのかもしれません。

AlphaGo Zeroの研究開発にどのくらいのリソースが必要になるのか気になったので見積もってみました。

AlphaGo Zero本体のリソースは論文に「Google Cloudのシングルマシンと4TPUs」と明記されています。
TPUは現在、v1とv2があり、かなり性能が違います。v1は8ビット整数で92TOPS、v2は180TFLOPS(singleかhalfか不明)です。論文にはどちらのTPUを使ったかは記述がありませんでした。

同論文でAlphaGo Leeのハードウェアは48TPUを使った分散マシンとという記述があるので、このTPUはv1を示していると思われます。
The Registerの記事"How DeepMind's AlphaGo Zero learned all by itself to trash world champ AI AlphaGo • The Register"ではTPU1だと明記されていました。真偽の程はわかりませんが、辻褄が合うのでTPUはv1という仮定で考察を進めます。(脚注1)

まず、20ブロックのバージョンについて、論文に1手0.4秒(正確には1,600シミュレーション)の自己対戦を3日間で490万局行ったとあります。
3日間で490万局なので1局53msです。仮に1局の手数を300手と仮定すると、1手180usとなります。(300手は作り碁としては一般的ですが、ランダムな碁の場合、かなり控えめな数字です)
1手0.4秒を1手180usで実現するには、約2300並列が必要です。
1手0.4秒は4TPUでの数字なので、1手180usのためには約9000TPUが必要となります。

ここまでの結論として、

20ブロックAlphaGo Zeroの自己対戦には、少なくとも、約2300のGoogle Cloudシングルマシンと約9000のTPU v1のリソースが3日間必要だった。


続いて40ブロックのバージョンについて。
論文に40日間で2900万局行ったとあります。1局約120msです。20ブロックの場合のほぼ倍なので、同じリソースで1,600シミュレーションをそのまま使った(1手0.8秒相当)と想像します。

したがって、

40ブロックAlphaGo Zeroの自己対戦には、少なくとも、約2300のGoogle Cloudシングルマシンと約9000のTPU v1のリソースが40日間必要だった。

上記の考察は自己対戦のみで学習に必要なリソースは含まれていません。こちらは論文にある64GPUのハードで行ったと想像しています。

以上です。

(脚注1)
英語版WikipediaAlphaGo ZeroのハードがTPU v2という情報が明記されていますが、リファレンスを見てもどこにもそのような情報はなく編集者の先入観と思いました。
また、TPU v1ではAlphaGo ZeroのCNNを評価できないというご意見も伺っていますが、素人の私にはAlphaGo LeeのCNNが評価できてAlphaGo ZeroのCNNが評価できない理由がわかりませんでした。CNNのサイズがかなり大きくなっていることは理解していますが。
こういう情報って、DeepMindに親しい専門家が一言確認してくれたらわかる話なんですけどねぇ。

AlphaGo Zeroの民生化の可能性について

TPU v1は8ビット整数で92TOPSで、これはCNNの評価の場合、70FLOPS相当になります。(以下のprediction比較を参考にしました)

cloud.google.com


4 TPUだと368TOPS、280FLOPS相当。民生最強のGPUは現在GTX-1080Ti(TITAN Xpは民生とは言えない…)で、単精度11.3TFLOPS、int8が45.2TOPS なので、int8を採用したとしても、GTX-1080Ti 8枚で少し届かないぐらいです。民生PCとなる日はまだまだ先か、それとももうすぐでしょうか。

Maxima日本語マニュアル(5.41.0)更新しました。

第一種不完全ガンマ函数 gamma_greekが gamma_incomplelte_lowerに名前が変わったぐらいですね。後はマニュアルの見た目の修正のみでした。
maxima.osdn.jp

rust-cudnnのcuDNN v6対応

2年ほど前にAutmunAIというヨーロッパのベンチャがRustで書かれたDeep Learning Framework "Leaf"を開発し始めて注目していました。
しかし、残念なことに1年ほど前に、TensorFlowのエコシステムの成長を見て「負け」を宣言して開発を中止したようです。
Rustによるフレームワークは速度の面で優位性はあったのですが、マイナーな言語というところが弱点だったということでしょうか。

学習フレームワークとしてはともかく、Rustはこれからシステムだけでなくバックエンドやアプリも記述する言語に成長していくと予想しています。その時、推論を使う機会は多々あるかと思うので、Leafの一部、rust-cudnnはRustプログラマに撮って貴重なものになると思います。
で、アップデートをしました。

https://github.com/y-ich/rust-cudnn/tree/bindgen


モジュール名が本家とぶつかるのでcrates.ioには公開していません。依存性はgitプロパティで指定してください。

bindingを手書きではなく、bindgenで生成するようにしたので、今後cuDNNのバージョンアップに対するメンテは比較的楽になるかと思います。

(macOSでcargo testを通そうと思うと、AutmunAI社の別のモジュールcollenchymaのlink attributeを修正する必要があります。)

Maxima日本語マニュアル(5.40.0)更新しました。

仔細な修正以外の主なところはencode_timeとdecode_timeの追加、Maximaの式整理のイントロダクションの追加でした。

Herokuで無料でたくさんTwitter Streamingボットを実装する方法

Herokuでは、ここ2年ほどで無料のdynoに関して変遷がありましたが、今はアカウント毎に毎月所定の時間のdyno hourを割り当てるということに落ち着いたようです。

Twitterでリプライやフォローバックをリアルタイムで自動でしようと思うと、Streaming APIを利用するのが効率的ですが、Streaming APIを毎日24時間モニタするには24時間*31日のdyno hourをworkerで消費します。

無料分でやろうと思うと1つが限度ですね。
でも、もしもボットがCPUもメモリもそれほど食わないなら、フリーのdynoには512MBのメモリと4core-8threadのCPUが味方ですから、複数のボットを1つのdynoで動かせるはずです。

その方法をご紹介します。

使用言語はPythonを仮定しました。

1. PythonのForemanクローン、honchoを使う。
Herokuアプリの基本になるProcfileは以下のようになります。

worker: honcho -f ProcfileHoncho start

(*ProfileHochoは任意のファイル名です)

2. 続いてProcfileHonchoに動かしたいボットアプリを記述します。

mimiaka1846: python3 mimiaka/stream.py
retweet_ramen: python3 retweet_ramen/retweeter.py

(*注
mimiaka1846は私が運営している耳赤サイト用アカウントmimiaka1846の自動フォローバックアプリです。
retweet_ramenは私が運営しているラーメンなう。(@retweet_ramen)の自動リツイートアプリです。)

Python3を使いましたのでruntime.txtは以下の通りです。

python-3.6.0

後は、それぞれのアプリをコーディングして、使用したパッケージ情報まとめたrequirements.txtを用意すれば完了です。

CPUパワーとメモリが許す限り、ProcfileHonchoに何個アプリを追加しても、消費する無料dyno hourは変わりません。

RustによるWeb Assembly事始め

Rustが安定版1.14.0でWeb Assemblyを実験的にサポートしました。

blog.rust-lang.org

asm.js関連技術を使うにはC/C++に戻らないといけないのかとげんなりしていたので、これほどありがたいことはありません^^

EmscriptenによるWeb Assembly

早速、Web Assemblyから試してみました。macOSでのメモです。

1. Emscriptenをインストール

EmscriptenC/C++JavaScriptコンパイルするコンパイラです。RustもWeb Assemblyを出力する際にEmscriptenを利用します。

Emscripten SDKをダウンロードしてドキュメントのインストラクションに従うのが一番問題がないです。HomeBrewとか考え出すとLLVMのインストール時のコンパイルオプションとか色々ハマります。
Download and install — Emscripten 1.36.14 documentation

2. Firefox Developer Editionをインストールする

2017年1月5日現在、Web Assemblyをネイティブサポートしているのは、Developer Editionだけのようです。(製品版は有効化するフラグはあったのですが、ネイティブサポートされていませんでした)

www.mozilla.org

アドレスバーでabout:configを開き、javascript.options.wasmをtrueに設定します。
これでWeb Assemblyが有効になります。

3. hello.cをコンパイルする
#include <stdio.h>

int main(void)
{
  printf("Hello, World!\n");
  return 0;
}

という内容のhello.cを用意して、

emcc hello.c -o hello.html -s BINARYEN=1
chmod a+r hello.js

を実行します。
(初回は、Binaryenのインストールが実行されます(と思います))
生成されたhello.jsのパーミッションがユーザーオンリーになってるのでchmodしています。

4. 実行する

コンパイルして生成されたファイルをウェブサーバーがアクセスできるようにしてください。
それが終わったら、hello.htmlをFirefox Developer Editionでブラウズします。

以上です。
上記手順から外れると意外と苦労するかもしれませんよ(体験談)

RustによるWeb Assembly 実行編

続いてRustで同じことをします。

Rustはrustupを使って最新版(1.14.0以降)がインストールされているとします。

1. hello.rsをコンパイルする
fn main() { println!("Hello, World!"); }

という内容のhello.rsを用意して、

rustc --target=wasm32-unknown-emscripten hello.rs -o hello.html
chmod a+r hello.js

を実行します。

2. 実行する

Emscriptenの時と一緒です。hello.jsのパーミッションに注意。

RustによるWeb Assembly 関数呼び出し編

DOM関連の操作はJavaScriptでするのがよいかと思うので、RustのライブラリをJavaScriptから呼び出す方法が必要になるかと思います。やってみましょう。

1. hello.htmlを変更する
「RustによるWeb Assembly 実行編」で生成したhello.htmlに関数呼び出しを組み込みます。
具体的には

  script.src = "hello.js";
  document.body.appendChild(script);

の2行の間に

  script.onload = function() {
    var hello_world = Module.cwrap('hello_world', 'number', ['number']);
    console.log(hello_world(41));
  };

を追加します。これが、Rustで書かれた関数hello_worldをJavaScriptから呼び出す方法です。

2. hello.rsを変更する

#![feature(link_args)]
#[link_args = "-s EXPORTED_FUNCTIONS=['_hello_world']"]

extern {}

#[no_mangle]
pub extern fn hello_world(n: i32) -> i32 {
    n + 1
}

fn main() { } // ダミー。関数をエクスポートすると実行されない模様

link_argsでエクスポートする関数を指定します。
extern {}や#[no_mangle]はおまじないと思ってください。

3. rustをnightlyに変更
featureを使う場合、rustをnightlyバージョンにする必要があります。

rustup override set nightly

を実行してください。

4. コンパイルする

rustc --target=wasm32-unknown-emscripten hello.rs
chmod a+r hello.js

上記例と違い、オプション -o hello.htmlを省略することに注意してください。

5. 実行する
hello.htmlをFirefox Developer Editionでブラウズします。
ウェブコンソールを開くと、計算結果42が表示されていることを確認できるはずです。
(ウェブコンソールを開いたまま、ページを再読込するとページがクラッシュするようです。)