読み書きプログラミング

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

Meteorを使ったリアルタイムアプリのためのデータ構造

Meteor歴5年で今更感があるのですが。

Meteorといえばリアルタイムアプリがターゲットのウェブプラットフォームです。
クライアントとサーバーのDBの同期やリアクティブなレンダリングはとても重宝しています。

さて、MeteorのDBのクライアント-サーバ同期の単位は、コレクションのドキュメントのトップのフィールドです。
(これを確認するには、クライアント側でDDPの通信をモニタしてみるといいです)

例えばチャットアプリを考えてみましょう。
大きく分けて3つのデータ構造が考えられると思います。

  1. チャット1つずつをそれぞれドキュメントにする
  2. スレッドをドキュメントにして、チャットID(番号)をトップのフィールドにしてチャットをスレッドのドキュメントに追加していく
  3. ルームをドキュメントにして、chatsフィールドを配列にしてチャットを追加していく


先に説明した通り、同期の単位はコレクションのドキュメントのトップのフィールドなので、1と2はチャット毎にDDPで同期が取られますが、3はチャットが追加されるたびにchatsフィールドの内容すべてをDDPで送ることになります。

パフォーマンスに大きな影響があるかと思いますので、ご注意ください。

macOS(High Sierra)でTensorFlowをコンパイルする

古いiMac(2012)を使っているのでGPUが馬力がないので、CPUでもIntel MKLを使って高速化をすべく、macOS(High Sierra)でTensorFlowをコンパイルしようとしたら嵌りました。

TensorFlowはコンパイル時にデフォルトのApple clangを使うのですが、このclangはOpenMPをサポートしていないので、omp.hが見つからないというエラーです。

で、コンパイラを変える方針で対処し始めたら相当失敗しました。

結論ですが、HomeBrewでlibompをインストールして、これをtensorflowのthird_partyフォルダーで登録する方法でうまくいきました。以下、必要な手順です。

1. Installing TensorFlow from Sources  |  TensorFlowでbazel buildを起動する手前まで準備する。

2. HomeBrewでlibompをインストールする

brew install libomp

3. libompをtensorflow/third_partyフォルダに登録する

cd tensorflow/third_pary
mkdir libomp
cd libomp
ln -s /usr/local/opt/libomp/include .
ln -s /usr/local/opt/libomp/lib
touch BUILD

以下はBUILDの内容です。

licenses(["notice"])

cc_library(
    name = "libomp",
    hdrs = glob(["include/*.h"]),
    visibility = ["//visibility:public"],
)

4. tensorflow/tensorflow/core/BUILDを編集する
name="core_cpu_impl"のライブラリで、

copts = tf_copts(),

copts = tf_copts() + ["-Ithird_party/libomp/include"],

に変更します。

copts = tf_copts() + ["-Xpreprocessor -fopenmp", "-Ithird_party/libomp/include"],

のほうが筋がいいかもしれませんが、試してません。ヘッダーだけが必要だったようで上記変更でコンパイルはできました。
また、name="core_cpu_impl"のライブラリのdepsに

"//third_party/libomp",

を追加します。

以上で準備完了です。コンパイルしてインストールしましょう。

bazel build --config=mkl //tensorflow/tools/pip_package:build_pip_package && bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg && pip install /tmp/tensorflow_pkg/tensorflow-1.8.0-cp36-cp36m-macosx_10_13_x86_64.whl

動作確認するには、tensorflowフォルダから出てください。(出ないとimport tensorflowが失敗します)

Web WorkerのためのRemote Method Invocation(RMI)

表題のライブラリを作成しました。

github.com

山口さんが開発されたPyaq(https://github.com/ymgaq/Pyaq)という9路盤の囲碁AIをJavaScriptの移植(A9.js)したのですが、そのときに、

という問題がありました。

なので、メインスレッドとワーカースレッドの小洒落た通信の枠組みが必要になったのでこのライブラリを作りました。

特長は、メインスレッド、ワーカースレッドどちらをクライアントにもできる対称な構成です。
実際、メインスレッドからワーカーに探索をリクエストする部分とワーカーがメインスレッドにニューラルネットワークの評価をリクエストする部分を同じライブラリで実現しています。
(当たり前だと言われたらそれまでですが^^;)

シングルスレッドで設計したクラスの変更がほぼ必要ないところも特長の一つです。
(詳細になりますが、実際には、ニューラルネットワークの評価結果(Float32Array)をTransferListとして渡すとデタッチができないらしく、サーバー側(メインスレッド側)でコピーが必要でした)


必要の際にはご活用ください。

本当はメタプログラミングでクライアント側クラスを自動生成したかったのですが、JavaScriptは未実装メソッドがコールされたときにフックする方法がなさそうなので諦めました。いい方法があれば教えてください。

ChromeでTwitterサイトをアプリっぽくする

Twitter for Macの提供終了が発表されました。

Mac版公式Twitterアプリ、突然の提供終了 - ITmedia NEWS

iMacTwitter for Macを立ち上げて、画面の左側に幅を最小にしていつもTLを楽しんでいる私としてはとても困りました。
なのでChromeTwitterサイトをアプリ風にすることにしました。

1. Twitterサイトを小窓でオープンする
このためには、ブックマークレットを使います。具体的には以下のURLのブックマークを作ってください。

javascript:(function(){const win=window.open('https://twitter.com/','twitter','left=0,top=0');win.resizeTo(446,screen.availHeight);}())

446というのはTwitterサイトのレイアウトに合わせたマジックナンバーです。

これだけでは表示がずれてイケていないので、今度はTwitterサイトのレイアウトを変えます。このためにはChrome拡張を使います。

2. Twitterサイトのレイアウトを変える
フォルダを作って、以下のファイルを用意してください。

manifest.json

{
  "name": "Twitter",
  "version": "1",
  "manifest_version": 2,
  "description": "App-like Twitter",
  "content_scripts": [{
    "matches": ["https://twitter.com/*"],
    "css": ["mystyle.css"]
  }]
}

このマニフェストで、Twitterサイトにアクセスした時に、Chromeがmystyle.cssを流し込んでくれます。

mystyle.css

@media screen and (max-width: 446px) {
    /* トップ */
    .dashboard {
        display: none !important;
    }
    .global-nav .container {
        min-width: 100% !important;
    }
    .Icon--bird {
        display: none !important;
    }
    .wrapper,
    .content-main,
    .MomentsPage.MomentsGuidePage .MomentsGuidePage-content,
    .MomentCapsuleSummary--hero .MomentCapsuleSummary-details,
    .MomentsPage.MomentsGuidePage .MomentsGuidePage-capsules,
    .MomentCapsuleSummary {
        width: 100% !important;
        padding: 0px !important;
    }
    .modal {
        width: 100% !important;
    }
    .Gallery {
        font-size: 0px !important; /* .Gallery-contentがinline-blockで、タグ前のスペースが見えてしまってずれたように見えるのでサイズを0にする */
    }
    .Gallery-content {
        min-width: 0px !important;
        width: 446px !important;
    }

    /* プロフィール */
    .AppContainer {
        max-width: 446px !important;
        margin: 0px !important;
    }
    .u-lg-size1of4 {
        display: none !important;
    }
    .Grid-cell.u-size2of3 {
        width: 100% !important;
    }
    .PermalinkOverlay-modal {
        left: 0px !important;
        margin-left: 0px !important;
        width: 100% !important;
    }
    .permalink-container {
        width: 100% !important;
    }
}

2つのファイルを作ったら、Chrome拡張に追加してください。(追加の仕方は、ググると紹介ブログが色々出てきます)

3. 「新しいツイートを表示」を何とかする。
TwitterのトップページはTwitter for Macのように自動でTLを流してくれないので、ぼーっと眺めるにはこれをなんとかしたいです。がんばらなくてもそのためのChrome拡張がありました。

Chrome ウェブストアでrefresh twitterを検索

好みのChrome拡張を追加してください。
以上です。

Enjoy your Twitter life!

機械翻訳のオープンソース情報を集めます

追記
ここにたくさん集めてありました。https://github.com/jonsafari/nmt-list

  • Sockeye

URL: https://github.com/awslabs/sockeye
コメント: Sequence-to-sequence framework
プラットフォーム: MXNet

  • Fairseq-py

URL: https://github.com/facebookresearch/fairseq-py
コメント: Sequence-to-Sequence Toolkit
プラットフォーム: PyTorch

  • tensor2tensor

URL: https://github.com/tensorflow/tensor2tensor
コメント: generalized sequence to sequence models
プラットフォーム: TensorFlow

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の研究開発に使われたリソースを見積もってみる

表題の中身

2018/01/30 追記その3
私の「論文のTPUはTPU1を示す」説に対してDeepZenGo代表の加藤英樹さんから「Google の misleading にマンマと騙されている。TPUはTPU2である」というご指摘があったので、なぜこんなことになってしまったのかと思いながら、ケリをつけるために、思い切ってDeepMind社のDavid Silverさんにメールで問い合わせてみました。経過すること4時間、「self-playにはTPU1を使った」という回答をいただきました。
下記のAja Huangさんのプレゼンテーションでの2000TPUと合わせて考えると、1600シミュレーションは0.4秒相当であるという論文での記述はTPU1を1つ使った場合、0.4秒だと考えるとすべての辻褄がほぼ合います。

2017/12/06 追記その2
AlphaZeroの論文が出ました。そこには「5000個の第1世代TPU」と世代が明記されていました。第1世代TPUでCNNが評価できることがはっきりしましたし、AlphaGo Zeroでも第1世代だったと推測するのが自然かと思います。
ただ、その場合、下記追記の2000TPUでは済まない気がするのでちょっと不思議です。

2017/11/10 追記
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となる日はまだまだ先か、それとももうすぐでしょうか。