ABEJA Tech Blog

株式会社ABEJAのエンジニアたちがAI, IoT, Big Dataなど様々なテーマについて発信します

NVIDIA DeepStream SDK on Jetson の紹介

DeepStream SDKとは

NVIDIA社が提供している、DNNのためのメディア・ストリーム処理フレームワークで、物体認識等のDNN推論を低レイテンシ&ハイパフォーマンスでビデオストリームに適用できます。

f:id:toshitanian:20180116144826j:plain
引用: https://developer.nvidia.com/deepstream-sdk

また今回はJetsonを利用したエッジ・デバイス上での事例を紹介していますが、通常のNVIDIAのGPUを搭載したサーバでも利用でき、例えばライブ配信等にリアルタイムでDNN推論を適用できたりします。

Jetson とは

NVIDIAのSoCが搭載されている、ハードウェア・プラットフォームの事で、モバイルやロボティクス等のエッジ・デバイス上でGPUを使用した推論を走らせることができ、現在、Maxwell世代のアーキテクチャを搭載したTX1と、Pascal世代のアーキテクチャを採用したTX2が存在しています。

TX2 TX1
GPU NVIDIA Pascal™, 256 CUDA cores NVIDIA Maxwell ™, 256 CUDA cores
CPU HMP Dual Denver 2/2 MB L2 + Quad ARM® A57/2 MB L2 Quad ARM® A57/2 MB L2
Video 4K x 2K 60 Hz Encode (HEVC) 4K x 2K 60 Hz Decode (12-Bit Support) 4K x 2K 30 Hz Encode (HEVC) 4K x 2K 60 Hz Decode (10-Bit Support)
Memory 8 GB 128 bit LPDDR4 59.7 GB/s 4 GB 64 bit LPDDR4 25.6 GB/s
Display 2x DSI, 2x DP 1.2 / HDMI 2.0 / eDP 1.4 2x DSI, 1x eDP 1.4 / DP 1.2 / HDMI
CSI Up to 6 Cameras (2 Lane) CSI2 D-PHY 1.2 (2.5 Gbps/Lane) Up to 6 Cameras (2 Lane) CSI2 D-PHY 1.1 (1.5 Gbps/Lane)

DeepStream SDK on Jetson

概要

DeepStream SDK on Jetson はその名の通りDeepStream SDKをJetsonで使用できるようにした物で、利用する上で素のDeepStream SDKとの大きな違いは、組み込み分野でよく使用されるメディア・フレームワークであるGStreamer(https://gstreamer.freedesktop.org/) に対応している所です。

GStreamerアーキテクチャ図

f:id:toshitanian:20180116144925p:plain
引用: https://en.wikipedia.org/wiki/GStreamer

GStreamerは、パイプラインでメディア処理を行うフレームワークで、既に様々なプラグインが存在し、DeepStream SDK on JetsonでもDeepStream SDKの機能と組み合わせてそれらを利用できます。またDeepStreamの機能その物もGStreamerのelement(パイプラインを構成する要素)として提供され、通常のGStreamerアプリケーションを開発するのと同じインターフェースで開発できます。

利用方法

この記事を書いている2018年1月14日現在、DeepStream SDK on Jetsonはearly access programとして提供されており、まずこちら(https://developer.nvidia.com/deepstream-jetson) から必要情報を記入して申し込む必要があります。 DeepStream SDKには簡単に機能を試す&開発のベースに成るアプリケーション・コードが付属されており、それをベースに開発を進めることが出来ます。また、モデル部は現在Caffeがサポートされており、Caffeでトレーニング済みモデルを生成すれば、SDKに組み込まれている専用のGStreamer elementから簡単に利用できます。

ユースケース

DeepStream SDK on Jestonは例えば以下のようなユースケースで利用できます。 * ホームデバイス * ロボティクス分野 * 防犯カメラ等のリアルタイム監視

Jetsonは複数のカメラ(最大6つまで)を同時に扱う事ができるので、その特色を活かしたユースケースが考えられます。

f:id:toshitanian:20180116145310p:plain
引用: https://developer.nvidia.com/deepstream-jetson

Deep Stream SDK on Jetsonのアーキテクチャ

f:id:toshitanian:20180116192935j:plain
引用: NVIDIA DeepStream SDK on Jetson Development Guideより

Deep Stream SDKは前述の通りMedia FrameworkにGStreamer(以下gst)を採用しており、gstのAPIを使用して開発できます。また勿論独自のgst pluginも作成する事ができ、独自のメディア処理elementを追加できます。Deep Stream SDK固有のgst pluginとして主に以下の物が用意されています。

  • GST-nvcaffeGIE - caffeのモデルを走らせるためのelement
  • GST-nvtracker - object tracking API
  • GST-nvosd - camera等の入力にoverlayで画面を構成する(一般的にcameraのlive viewにoverlay表示する画面の事はOSDと言う)

gstで提供されている機能はgstを介さないlow layer APIとしても使用できます。

開発環境構築の流れ

Deep Stream SDK on Jetsonのインストール

TX2(Target)上で以下を実行します。

 $ tar xpvf DeepStream_SDK_on_Jetson_1.0_pre-release.tbz2
 $ sudo tar xpvf deepstream_sdk_on_jetson.tbz2 -C /
 $ sudo tar xpvf deepstream_sdk_on_jetson_models.tbz2 -C /
 $ sudo tar xpvf R28.1_patch_TX2.tbz2 -C /
 $ sudo ldconfig

動作確認

同じくTX2上で以下の実行を行います。

$ nvgstiva-app -c ${HOME}/configs/PGIE-FP16-CarType-CarMake-CarColor.txt

※注意 pipelineでエラーが出たら

sudo rm -rf ${HOME}/.cache/*

とcacheを削除してやる必要があります。

Deep Stream SDK exampleの実行

Example pluginのbuild & install

TX2上で必要なライブラリをinstallします。

 $ sudo apt-get install libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev \
     libegl1-mesa-dev
 $ sudo ln -sf /usr/lib/aarch64-linux-gnu/tegra-egl/libEGL.so.1 \
     /usr/lib/aarch64-linux-gnu/libEGL.so

build & install

$ cd gst-dsexample_sources
$ make && sudo make install

Example appのbuild

  $ sudo apt-get install libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev
  $ sudo ln -s /usr/lib/aarch64-linux-gnu/tegra/libnvid_mapper.so.1.0.0 \
             /usr/lib/aarch64-linux-gnu/libnvid_mapper.so
  $ cd nvgstiva-app_sources/nvgstiva-app
  $ make
  $ ./nvgstiva-app -c ${HOME}/configs/PGIE-FP16-CarType-CarMake-CarColor.txt

Example appのアーキテクチャ

f:id:toshitanian:20180116193735j:plain
引用: NVIDIA DeepStream SDK on Jetson Development Guideより

Example appの簡単な検証

Example appを付属の動画ファイル or CSIで接続されているcameraで動かした所、上限の30fpsでの推論ができました。

NvMMLiteOpen : Block : BlockType = 261
TVMR: NvMMLiteTVMRDecBlockOpen: 7907: NvMMLiteBlockOpen
NvMMLiteBlockCreate : Block : BlockType = 261
TVMR: cbBeginSequence: 1223: BeginSequence  1280x720, bVPR = 0
TVMR: LowCorner Frequency = 100000
TVMR: cbBeginSequence: 1622: DecodeBuffers = 5, pnvsi->eCodec = 4, codec = 0
TVMR: cbBeginSequence: 1693: Display Resolution : (1280x720)
TVMR: cbBeginSequence: 1694: Display Aspect Ratio : (1280x720)
TVMR: cbBeginSequence: 1762: ColorFormat : 5
TVMR: cbBeginSequence:1776 ColorSpace = NvColorSpace_YCbCr601
TVMR: cbBeginSequence: 1904: SurfaceLayout = 3
TVMR: cbBeginSequence: 2005: NumOfSurfaces = 12, InteraceStream = 0, InterlaceEnabled = 0, bSecure = 0, MVC = 0 Semiplanar = 1, bReinit = 1, BitDepthForSurface = 8 LumaBitDepth = 8, ChromaBitDepth = 8, ChromaFormat = 5
TVMR: cbBeginSequence: 2007: BeginSequence  ColorPrimaries = 2, TransferCharacteristics = 2, MatrixCoefficients = 2
Allocating new output: 1280x720 (x 12), ThumbnailMode = 0
OPENMAX: HandleNewStreamFormat: 3464: Send OMX_EventPortSettingsChanged : nFrameWidth = 1280, nFrameHeight = 720
** INFO: <bus_callback:115>: Pipeline running

TVMR: FrameRate = 30
TVMR: NVDEC LowCorner Freq = (100000 * 1024)
TVMR: FrameRate = 30.252152

**PERF: FPS 0 (Avg)
**PERF: 32.73 (32.73)
TVMR: FrameRate = 29.508274
**PERF: 30.03 (31.33)
TVMR: FrameRate = 30.000030
**PERF: 29.99 (30.87)
TVMR: FrameRate = 29.752076

学習済みModel

Caffe等、TensorRT2がサポートしているmodel formatであれば独自にpluginを書く必要もなく、DeepStream SDKのelementを介して推論を動かすことが出来ます。

まとめ

DeepStream SDK on Jetsonを使う事によりエッジサイドでのDeep Learningを使ったリアルタイムビデオ解析アプリケーション開発がより簡単にできそうです。今後TensorRT3への対応などにより、導入のハードルが下がる事が期待できそうです。

宣伝

以下の職種で積極採用中です!

www.wantedly.com

www.wantedly.com

また、1/22(月)に ABEJA Cloud AI Night 〜エッジコンピューティング編〜 を開催します。エッジコンピューティングに興味のある方は是非ご参加ください。

eventregist.com

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

Amazon Kinesis Video Streams × Deep Learning

エンジニアの河崎です。

AWS re:Invent2017で、新サービスのKinesis Video Streamのイントロダクションセッションでデモとユースケースの発表をさせていただきました。

www.youtube.com

この、Kinesis Video Streamsの紹介とre:Inventで発表したデモの構成をJAWS-UG AIで発表させていただきましたので、ご覧になってください。

speakerdeck.com

宣伝

来年2月にABEJA SIX 2018というイベントを開催します。映像関係の事例の発表もありますので是非ご参加ください。

six.abejainc.com

製造業やインフラ、流通小売業などで、人工知能をどのように活用しているか、実事例を多数ご紹介する1DAYカンファレンスです。 本カンファレンスでは、人工知能活用を推進して事業効率化、生産性向上に成功した各業界のリーダー達が集結。導入までのプロセス、活用ノウハウ、実際の成果まで、業務活用の最前線をご紹介します。

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

NIPS2017に参加してきました

ABEJAの白川です.

先日カリフォルニアのロングビーチで開かれたNIPS2017へ出席してきました. 論文レベルの技術的な話題については1月にNIPS論文読み会を開催する予定ですので詳細はそちらに譲ることにして,ここではごくごく大雑把なオーバービューを私見偏見交えてご紹介したいと思います.パラレルトラックのため聴講できなかった講演がだいぶありますので,かなり聴講バイアスがかかっていることをご容赦ください.また,新しめの流行にフォーカスしています.

Summary

  • 史上最大規模のNIPSだった
  • Bayesian Deep Learningが非常に流行っていた
  • Optimal Transportは実用的な道具
  • Meta Learningが流行している
  • 非ユークリッド的なDeep Learningに対する注目

史上最大規模のNIPS

今回のNIPSは参加者数7844人,投稿された論文数は3240本で,史上最大規模だったそうです.毎年ネタのごとく語らえれる参加者数の指数関数的増加は今年も継続して,2035年に全人口を超えるそうです(笑). f:id:tatsuya_shirakawa:20171213160015p:plain

arXivなどに事前投稿されている論文の方が採択率が高かったという分析結果も発表されていました(投稿されている論文の採択率は29%で投稿されていない論文の採択率は15%).

会場は下の写真の左側にみえる細長い建物でおこなわれましたが,とにかく中が広く,部屋の移動も大変でした. f:id:tatsuya_shirakawa:20171214120132p:plain

一番大きい Hall Aだと7844人収容してもこんな感じです. f:id:tatsuya_shirakawa:20171214121129p:plain

ちなみに再来年のCVPR,ICMLは同じ会場で開かれるそうですが,そちらは夏開催のはずなので,冬開催の今回よりもロングビーチを満喫できるかもしれません (冬といっても日中は結構暑くて半袖でも大丈夫なくらいですが,朝や夜中,会場内は冷房が聞いていて寒かったです).

Bayesian Deep Learning

f:id:tatsuya_shirakawa:20171214134047p:plain A. Kendall '17

Bayesian Deep Learningはデータやニューラルネットワーク自体に何らかの確率分布を仮定しつつDeep Learningを行う手法で,これにより

  • データ自体のゆらぎの評価
  • モデルの予測の確信度の出力
  • ノイジーなデータへのフィッティング

などが自然に表現できるようになります. たとえば意思決定においてリスクテイクができないケースではモデルの予測確信度の出力は必須と思いますし,データにノイズが多く乗っている場合は,それを平均的に丸め込んでしまうよりノイズ自体を可能な範囲でモデル化するほうが精緻なモデルが作れそうです. NIPSの最中,Bayesian Deep Learningはデータ数が少ない場合のモデル構築に有用という声をいろんなセッションで聞きました.個人的に2014か2015くらいのNIPSである有名な研究者がこれからのDeep Learningはモデル化できるところはモデル化していくのが正しいんだみたいなことをおっしゃっていたのが印象に残っていて,Bayesian Deep Learningもその流れだなぁと感じました.

Optimal Transport

f:id:tatsuya_shirakawa:20171214111120p:plain Solomon '15

Optimal Transport(最適輸送)はナイーブには,確率分布を最適に変形させる方法,あるいは確率的な割当問題を研究する分野です. 詳細には,確率変数$x,y$についての確率分布$P(x), P(y)$と$(x,y)$に対して定義されるコスト関数$c(x,y) \in \mathbb{R}$とが与えられたとき, 下記の意味でコスト最小な同時分布$P(x,y)$を求めるのが目標です.

$$ \begin{equation} \min_{P(x,y)} \int c(x,y) P(x,y) dx dy \ \ s.t. \ \ \int P(x,y) dy = P(x), \int P(x, y) dx = P(y) \end{equation} $$

$P(x,y) = P(y|x)P(x)$と考えると,確率分布$P(x)$から確率分布$P(y)$へ輸送している気持ちになれます. Optimal Transportはこのようにかなり数学的な定式化がなされるものなのですが,純粋な数学的なテクニックとしての他にも

  • 画像・3Dオブジェクトのモーフィング
  • shape reconstruction
  • ドキュメント間類似度(Word Mover Distance)
  • ドメイン適応
  • ...

など,応用もふんだんにあります.Wasserstein GANもOptimal TranportのテクニックをGANへ適用した手法ですが,他にもGANへ適用した 手法が多数公開され始めているので要注目です.

今回のNIPSでもOptimal TransportのチュートリアルやWorkshopが開かれて,聴講してきました.

個人的には,Optimal Transportは数学的には面白いし理論解析には使えそうだなと思いつつも,実際にどう計算するんだろうというのがわからず, 横目で見ていたくらいの感じだったのですが,Entropic Regularizationという,先のOptimal Tranportの目的関数に,$P(x,y)$の乱雑さを増すような 正則化をいれてやることにより,比較的容易に最適化計算ができるようになるのだそうです(恥ずかしながら知らなかった).

このあたりも含め,チュートリアルで使われた非常にわかりやすいスライドが公開されているので,ご興味あるかたは是非御覧ください.

Meta Learning

メタラーニングも流行っていました.メタラーニングをどう定義するかは難しいですが,モデルの効率的な学習方法自体を学習する方法や,タスク依存なモデルを生成するモデルの作り方を調べたりといった(すでに何を言っているのかわからないですね),一段メタな問題設定を指します.強化学習との親和性が強いためか,例年以上の盛り上がりを見せていたように感じます.

Google Deep MindのOriol VInyalsが既存のMeta Learningの手法を非常にわかりやすく分類していました. f:id:tatsuya_shirakawa:20171214123514p:plain

このスライドの右下に出てくるのがMAMLという手法なのですが,これは,複数のタスクがあるときにユニバーサルな種モデルを一つ学習しておいて,それをpretrained modelにして各タスクに定義されたデータセットを用いてgradient descentをすることで,個別タスクに適応したモデルを生成するメタラーニング手法で,今年のNIPSで発表されました.論文をみたときにこんな方法でうまくいくのかと訝しんでいたのですが,同著者のworkshoptでの講演によると,驚くべきことに,MAMLというフレームワークはある種のuniversalityを持つのだそうです(論文はこちら).

なお,同著者の講演で出てきた下記のスライドが私にはとても刺さりました.

f:id:tatsuya_shirakawa:20171214133321p:plain

最強のモデルを学習するには,強力なモデルを可能な限りたくさんのタスクに晒すのがよいと思っています.今年のICMLでマルチタスク学習が着実に実戦投入されているのを見て我が意を得たりという感じがしていましたが,今回のNIPSに参加して,マルチタスク学習ももちろん有用ですがメタラーニング的な考え方も非常に大切だと学びました.とくに先程紹介したBayeisan Deep Learningの考え方とも親和性があるような気がしますので,今後深掘りしていきたいと思います.

非ユークリッド的なDeep Learning

f:id:tatsuya_shirakawa:20171214121642p:plain M. M. Bronstein '16

非ユークリッド的というか,入力空間の幾何構造を活用したDeep Learningです.個人的にも興味を持っていて,過去にもブログ(Graph CNNPoincare Embedding)で紹介した事があるのですが,今回のNIPSでもチュートリアルが開催されるなど,標準技術として浸透してきた感があります. チュートリアルでは

  • Graph CNN
  • Geometric Deep Learning

が紹介されました.前者については以前のブログを参照していただきたいのですが,グラフ構造上でConvolution演算を定義することにより,CNNの適用先をGraphへも拡張するような研究です.後者は3Dオブジェクトの表面などの凹凸構造を適切に表現するような座標系を用いることでより効率的にDeep Learningを適用できるようにする技術です.詳細についてはこちらの論文に非常によくまとまっています.

さいごに

今回は大雑把に個人視点のオーバビュー的な形でご紹介しましたが,とにかく内容が盛りだくさんで細かいTipsから面白そうな新しい論文ネタまで,ここでご紹介しきれなかった話題は無数にあります.個人的にも興味を持っていた話題(Knowledge Graph Embedding, Entangled Representation, Variational Inferenceなどなど)についても最新の話題にキャッチアップすることができました.また,日頃あまり接触できない日本人の方とのコミュニケーションをとることができ,個人的にも有意義なNIPS参加でした.流行を肌で感じるにはうってつけの機会ですので,今後とも継続的に参加(できれば論文投稿)・発信していければと思います.

f:id:tatsuya_shirakawa:20171214122302p:plain おまけ: 最終日のMicheal I. Jordan(機械学習の超大御所)らによるバンド演奏

宣伝

来年2月にABEJA SIX 2018というイベントを開催します。私の所属するLabsチームからも発表をさせて頂く予定です.

six.abejainc.com

製造業やインフラ、流通小売業などで、人工知能をどのように活用しているか、実事例を多数ご紹介する1DAYカンファレンスです。 本カンファレンスでは、人工知能活用を推進して事業効率化、生産性向上に成功した各業界のリーダー達が集結。導入までのプロセス、活用ノウハウ、実際の成果まで、業務活用の最前線をご紹介します。

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

俺はSSHをしたくない ~ Fargate vs ECS+SpotFleet のPrice比較 ~

この記事は AWS Fargate Advent Calendar 2017 の9日目の記事です。

こんにちは。 service sshd stop 布教活動中の、ABEJAのサービス系インフラを管理しているインフラエンジニアの村主です。

今回AWSからFargateというコンテナマネージドサービスが発表されました。 Fargateの詳しい説明は AWS Fargate Advent Calendar 2017 の等の情報を参照いただければと思います。

タイトルでも書き、昔に こんな記事 を書きましたが、僕はSSHはしたくないし、極論言えばインフラも管理したくありません。

そこで今回のre:InventでFargateが発表されて、EC2やクラスタの面倒みない=SSHしなくてええやん!イェーイ!を夢見て、コスト面の試算を始めました。

CPUとメモリの組み合わせ

FargateはCPUとメモリをある程度柔軟に選択でき、約50通りの組み合わせができます。 CPUとメモリの組み合わせは以下の通り、CPUに合わせて利用できるメモリ量が決まってるようです。

CPU (vCPU) Memory Values (GB)
0.25 0.5, 1, 2
0.5 1, 2, 3, 4
1 Min. 2GB and Max. 8GB, in 1GB increments
2 Min. 4GB and Max. 16GB, in 1GB increments
4 Min. 8GB and Max. 30GB in 1GB increments

Fargateの料金

CPUとメモリ毎の利用料は以下になり、1ヶ月に換算すると概ね以下のような金額になります。

Resource Cost/Hour Cost/Month(744h)
CPU (1vCPU) $0.0506 $37.6464
Memory Values (1GB) $0.0127 $9.4488

オンデマンドとの比較(バージニア)

そしてオンデマンドとの比較をしてみます。(若干計算間違ってるかも。ご了承ください)

Type CPU MEM On-demand Cost(744h) Fargate Cost(744h)
t2.small 1vCPU 2GB $17.112 $56.544
m3.medium 1vCPU 3.75GB $49.848 $73.0794
m4.large 2vCPU 8GB $74.4 $150.8832

オンデマンドと比較すると高く見えますが、オンデマンドのCPU/MEMリソースを100%効率活用した場合の比較なので、 スケジューリングされたコンテナが上手くはまらなければ効率はもう少し落ちると思います。 また、AutoScalingとか入れてると70%や80%でAutoScale発動とか組んでると、さらに20-30%位の効率は落ちると思います。 それでも少し高いかもしれませんが、EC2やクラスタの面倒をみない分手間が減ります。

僕の立ち位置としては、社内のエンジニアにAWSやインフラのノウハウとトランスファーすることが多いので、 インフラの考慮する要素が減ると(例えば、EC2やクラスタの管理等が減ると)エンジニアが少ない労力で たくさんのインフラを管理することが出来るようになる(インフラすら意識しない感じかもしれないけど)ので、 学習コスト等を考えると非常に安いものかと思います。

この時点で積極的に使っていこうと思いました。そう、この時点では。

SpotFleetとの比較

弊社ではECSを使ってるサービスがあるので置き換えられるかな?と思いましたが、よく考えたらSpotFleetも利用しているため、 オンデマンドの価格より少し高いなら、SpotFleetと比較したらもっと高くなっちゃうのでは・・?夢への計画が・・。と思い恐る恐る計算してみました。

  • 注意点
    • 2017/12/7時点でのバージニアの価格です
    • 価格変動するため、以下の金額から増減することは大いにあります
    • あくまで超参考値としてください
    • t2.smallはSpotInstanceがないので省いています 最近tシリーズもSpotInstanceが利用出来るようになったようです!
Type CPU MEM On-demand Fargate SpotFleet
m3.medium 1vCPU 3.75GB $49.848 $73.0794 $5.8776
m4.large 2vCPU 8GB $74.4 $150.8832 $22.3944

愕然としました

参考値としてm3.mediumとm4.largeだけ算出してますが、概ね同じような価格差かと思います。 リソースの非効率分の割合の話や、SpotFleetは価格変動に伴う停止リスクがあるにしても、この差は無いだろう・・。何度も計算しましたが、結果は同じでした。

でも僕は諦めません。 SpotFleet使うまでは無いよね。とか、ここ落ちると流石に・・。という部分にはSpotFleetを使うのを躊躇するので、 そういう部分ではインフラの管理コストを削減するために積極的にFargateを採用して行きたいと思います。 僕は諦めません。

また、そもそもLambdaで無理やりやっているような処理をFargateに置き換える事の相性は良さそうなので、積極的に採用して行きたいと思います。

おすすめ

SpotFleetの金額は見ずに、何も考えずにFargate使う方が幸せになれます。

異空間への埋め込み!Poincare Embeddingsが拓く表現学習の新展開

ABEJAでResearcherしている白川です。

今回ご紹介するのは、Poincaré Embeddings [1]という手法です。その内容に驚愕し、個人的に調べたり実装したり勉強会でお話したりしていたところ、最近運良く自分の実装をredditで取り上げてもらえたので、これを機にその驚愕の内容を共有できればと思います。 正直、自分の中ではまだ煮詰まりきっていない技術なので、現況の共有はしますが、ところどころ私の憶測や展望、期待が入り混じっていることをご容赦ください。

www.reddit.com

Poincaré Embeddingsは大雑把に言えばword2vecを異空間で実現する技術で、双曲空間(Hyperbolic Space)という、おなじみのEuclide空間(2点$x,y$の間の距離を$\sqrt{(x_1 - y_1)^2 + (x_2 - y_2)^2 + … + (x_d - y_d)^2}$で測るいつもの空間)とは異なる空間でデータを表現します。それにより、従来手法(word2vecのようにEuclide空間へ埋め込む手法)では200次元くらいのベクター表現を要していたケースで、なんと5次元で遥かに高い精度を達成することに成功しています。

f:id:daynap1204:20170827142406p:plain Poincaré Embeddingsによる埋め込み精度の向上([1]から引用)

Poincaré空間に埋め込むことによるメリットを列挙しておきます。

  • Euclide空間への埋め込みに比べて、空間をexponentialに効率よく利用できる
  • それにより、十分精度の埋め込みを低次元で実現できる
  • データのもつ階層構造を自動的に抽出できる
  • 実装が簡単(ちょっとした補正をするだけです)
  • アイデアがわかりやすい
  • リーマン多様体上での機械学習の取っ掛かりとしてよい

この手法、アイデアは簡単ですがとても可能性を感じます(リーマン多様体上の機械学習とかは昔からちょいちょいあるので、やっと実用段階に来たかという感が強いですが)。 論文を読むと数学的なバックグラウンドが一見必要に見えますが、やってることは単純ですので、本記事でその雰囲気を噛み砕いてお伝えできればと思います。

なお、私はグラフ構造をDeep Learningで表現したり、グラフ構造を使って課題を解決することに興味を持っているのですが、本手法をそういったケースにうまく使えないか模索しています。とにかく、シンプルなアイデアなのでいろいろ想像が膨らんで楽しいです!

以下、勉強会で使ったslideもあるので、合わせてご参照ください。本稿で説明しきれていない内容もちょいちょい盛り込んであります。

www.slideshare.net

また、私の実装は下記にあります。本稿で紹介する実験の一部も再現できるので、ご興味あればお試しください。IssueやPRも大歓迎です。

github.com

まずはword2vecから復習していきましょう。

word2vecの復習

word2vecやskipgramをよくご存じの方は本節は飛ばしても大丈夫です。

word2vec[2]はテキスト中の単語の表現ベクターを求めるツールで、skipgramとcbow[3,4]という2つのモデルが実装されています。

もともと、Yoshua Bengioらにより、Deep Neural Networksを使って単語の表現ベクター(分散表現)を求めるという論文[5]が2001年(!)に出されていたのですが、この論文では、単語の表現ベクターをDeep Neural Networksを用いて求めるという仕組みになっていました。

Tomas Mikolovによるskipgram/cbowの発見の貢献は、Deep Neural Networksのような複雑でパワフルなモデルを使わなくとも、単語の「表現ベクター表」を直接最適化していけば良いことを実証したことにあります(冷静に考えれば表の最適化だけでよいことはわかります)。

さらにTomas Mikolovらは、Negative Samplingという仕組みを導入し、圧倒的な計算効率の向上も実現しています[6]。ここにおいて、NLP界隈でのword2vec祭が始まりました。よいモデルとよい実装がちゃんと公開されるというのは素晴らしいことですね!ちなみに私はシンプルで強力なword2vecが大好きです。

さて、word2vecですが、ここではNegative Samplingつきのskipgram(SGNS)の方だけ説明します(cbowもちょっとモデルが違いますがノリは一緒です)。 SGNSは、非常に単純なモデルで、単語の表現ベクター表を用意しておいて

  1. 文中に近接して出て来る単語対(positive samples)の表現ベクターは内積が大きい
  2. 乱択された単語対(negative samples)の表現ベクターは内積が小さい

となるように最適化を行います。具体的には、単語 $i$ の表現ベクターを $v_i$ とするとき $V={\{v_i\}}_{i \in I}$ が表現ベクター表ですね)、下記の目的関数を最大化します。

$\text{maximize}_{ \{v_i \}_i } \ \ \mathbb{E}_{(i,j) \sim P(i,j)} \Bigl[ \log \sigma ( \langle v_i, v_j \rangle ) \Bigr] + k \ \mathbb{E}_{(i,j) \sim P(i)P(j)} \Bigl[ \log 1-\sigma (\langle v_i, v_j \rangle) \Bigr]$

ここで、$\langle v_i, v_j \rangle$は単語$i,j$の表現ベクターの内積です。$P(i,j)$は単語対$(i,j)$が近接して現れる確率分布(下図の場合、注目している語onの左右に2単語以内に現れる単語を近接語としています)を表し、$P(i)$は単語$i$単独の頻度分布(unigram分布)です。また、$k$はpotive samplesとnegative samplesのバランスをコントロールする非負定数で(理論的には$k=1$が理想ですが、$k=5,10,20$などの値が実用上用いられます)、関数$\sigma(x)=1/(1+\exp(-x))$はシグモイド関数です($P(i)$を3/4乗したりという細かいチューニングが本当はあるのですが、本質的でないので無視しています)。

難しいことを抜きにしたら、positive samplesの出現確率を増加させ、negative samplesの出現確率を低下させるように単語の表現ベクターを求めていることになります。

f:id:daynap1204:20170827155338p:plain SkipGram with Negative Sampling (SGNS)

なお、SGNSはpointwise mutual information(PMI)を要素値にとる行列の分解になることが知られています(GANsのオリジナル論文のdiscriminatorの収束証明と同様の議論で容易に示せます)。

$$ \langle v_i, v_j \rangle = PMI(i,j) = log \frac{P(i,j)}{P(i)P(j)} $$

SGNSの素晴らしい点は、その計算効率です。テキストデータの上に窓(上図の左上の点線窓)を滑らせていきながら注目語(上図ならon)と周辺語(同cat, sat, the, mat)を取得していき(positive samples)、その一方で単語の頻度分布から単語をサンプリング(negative samples)してくれば、あとは確率的勾配降下法により容易に最適化できます。さらに、これにより得られる単語の表現ベクターは、よく知られているような正則性をもつことが実験的に知られています(linguistic regularity)。king-man = queen-womanというやつですね。

f:id:daynap1204:20170827161023p:plain

[6]より引用

SGNSですが、適用先はテキストデータだけにとどまりません。ここではグラフ構造への適用例として、LINE(Large-scale Information Network Embedding)[7]という手法を紹介します。なお、ここでグラフへの適用を紹介するのは、私の趣味もありますが、本題のpoincaré embeddingもグラフ構造(実際は木構造)に関係するからというのもあります。word2vecはグラフ構造へ何も考えずそのまま適用できるのになかなか日の目を見ないという状況を改善したいという気持ちもあります(もちろん常識的に適用している方もたくさんおられると思います)。

LINE - グラフへのSGNSの適用

LINEはグラフへSGNSを適用する手法です。具体的には、下記の対応関係にあります。

対象 LINE SGNS
埋め込まれるもの ノード 単語
P(i) ノードの次数に比例 単語の出現頻度に比例
P(i,j) グラフのエッジからの一様分布 窓から一様サンプリング

すなわち、下記のような学習になります。

  1. グラフからエッジを一様サンプリングして単語対を生成(positve samples)
  2. ノードを次数に比例する確率で2つサンプリングすることでノード対を生成(negative samples)

最適化するのもSGNSと同じ目的関数です。

$ \text{maximize}_{ \{v_i \}_i } \ \ \mathbb{E}_{(i,j) \sim P(i,j)} \Bigl[ \log \sigma (\langle v_i, v_j \rangle ) \Bigr] + k \ \mathbb{E}_{(i,j) \sim P(i)P(j)} \Bigl[ \log 1-\sigma (\langle v_i, v_j \rangle ) \Bigr]$

グラフにもそのまま適用できたのは、SGNSのシンプルさのおかげですね。

Poincaré Embeddings

ここからが本題です。SGNSはシンプルで素晴らしい。計算効率も非常に良い。これ以上言うことはないのではないかと思っていましたが、Poincaré Embeddingsはそこに待ったをかけました。

その埋め込み、最良ですか?

SGNSでは、二つの単語$i,j$の表現ベクター$v_i, v_j$をとり、その内積

$$ \langle v_i, v_j \rangle $$

でそれらの単語の共起の度合いを測りました(これが大きい方が共起しやすい)。

でもそれより良い測り方はないのでしょうか?

Poincaré Embeddings[1]では、Euclide空間よりも空間効率の良い双曲空間という歪んだ空間へデータ(ノードや単語)を埋め込み、その空間でそれらのデータ間の距離を測ることで、はるかに効率的な表現ベクターを獲得することに成功しました。

内積$\langle u,v \rangle$は $$ \langle u,v \rangle = ({\Vert u + v \Vert}^2 - {\Vert u \Vert}^2 + {\Vert v \Vert}^2)/2 $$ を満たすので、本質的にEulide空間における距離の測り方に依存しています。したがって、SGNSはEuclide空間への埋め込みモデルと考えることができます。 Deep Learningで扱われるたいていのモデルはEuclide空間で構成されていると考えて差し支えないと思います(もちろん例外はあります)。

Poincaré Embeddingsの場合、Euclide空間ではなく、双曲空間(Hyperbolic Space)で埋め込みを構成します。双曲空間ではEuclide空間と同様、各点はベクターとして表現されますが、Euclide空間とは異なり空間に歪みがあり、2点間の距離の測られ方が異なります。そのため、この歪みを利用することでEuclide空間よりも効率的で個性的な埋め込みが可能になります(要はEuclide空間はフラットすぎたんですね)。

双曲空間についてさらっておきます。

双曲空間(hyperbolic space)

双曲空間は数学的には非常に由緒正しい空間です。深掘りすると議論が尽きないので、ここでは美味しいところだけつまみ食いしましょう。 詳細については、wikipedia[8]や論文[9]を参照してください。書籍であれば、以下の本が非常にわかりやすく書かれています。

双曲幾何 (現代数学への入門)

双曲幾何 (現代数学への入門)

私は読んでいないですが、書店でちら見した感じでは、最近出た下記の本にも双曲空間が易しく解説されていました。

双曲空間にはいくつかの構成方法(モデル)がありますが、ここでは、オリジナル論文に従ってPoincaré Ball (Poincaré Disk) モデルを採用します。Poincaré Ballモデルでは、データは$d$次元の単位球$\mathbf{B}^d = \{x \in \mathbb{R}^d | \Vert x \Vert < 1 \}$ 内に表現されます。Poincaré Ballモデルの場合、この単位球$\mathbf{B}^d$の周縁部($\Vert x \Vert = 1$)に近づくに従って解像度が加速度的に高くなるようになっていて(外側に行くほど空間が爆発的に広がっているイメージです)、これが表現の効率性を生み出しています。

双曲空間については、エッシャーによる描画があります。

f:id:daynap1204:20170827165827j:plain

M.C. Escher, Circle Limit III, 1959

外側に行くほど空間が密に詰まっているのがわかると思います(逆に見れば、外側ほど空間が拡がっています)。

数学的にはこの空間の拡がり方の増加は、下記のように表されます。

$$ g_x = \Biggl(\frac{2}{1-{\Vert x \Vert }^2 } \Biggr)^2 g^E $$

この式の意味ですが、$g_x$がPoincaré Disc上での物差し(metric)、$g^E$がEuclide空間の物差しを表します。Poincaré Ball上の点$x$における空間の拡がり方は、同じ点のEuclide空間における拡がり方に比べて、$2/(1-{\Vert x \Vert}^2)$倍であることを表します。$\Vert x \Vert$が1に近づけば近づくほどこの倍数は大きくなっていくので、Poincaré Ballの周縁部へ行くほど空間が拡大されていくことになります。

では、Poincaré Ballの2点$x,y$の間の距離はどのようになるのでしょうか?数学的には、このような歪んだ空間における2点間の距離は、測地線(geodesic)という2点を結ぶ最短経路の長さで定義されます。Euclide空間における2点間の距離はその2点を結ぶ直線の長さで定義されましたが、測地線はEuclide空間における距離の概念を歪んだ空間(多様体)へ拡張したものです。

Poincaré Ballでは場所ごとに物差しの長さ異なるため、点$x$から点$y$へ至る道それぞれの途上で物差しは変化し続けることになります。そのため、それらの道のうちの最短のもの(測地線)がどんなものでその長さがいくらかを特定したり計算したりするのは難しそうな気がしますが、幸いなことにPoincaré Ballの場合、

  1. 2点$x,y$を結ぶ測地線は常にその2点を通り、単位球に直行する円弧になる
  2. 測地線の長さは明示的に計算可能で$d(x,y) = arccosh \Bigl(1 + 2 \frac{{\Vert x - y \Vert}^2}{(1-{\Vert x \Vert}^2)(1-{\Vert y \Vert}^2)} \Bigr)$

となることが知られています。ここで、$arccosh(\gamma)=\log \bigl(\gamma + \sqrt{\gamma-1}\sqrt{\gamma+1} \bigr)$は逆双曲線余弦関数と呼ばれる関数です。

f:id:daynap1204:20170827231134p:plain

[1]より引用

これだけでも嬉しいのですが、それだけでは終わりません。 双曲空間には木構造を自然な形で埋め込むことができるという特殊な性質が知られており、木構造を構成するノード間の距離(家系図で言うところの1親等、2親等というやつです)を保つように、適当な次元の双極空間へ埋め込むことができます[9]。雰囲気だけ理解することにすれば、この埋め込みは木のルートに近いノードをPoincaré Ballの中心近くに、リーフに近いノードをPoincaré Ballの周縁部の近くに配置します(上図の(b))。

この性質により、Poncaré Ballへ埋め込んだときに中心近くに配置されるデータはより抽象度が高く、周縁部に配置されるデータは具象度が高くなることが期待されます(あとで見るように、実際にそうなるところが驚愕すべき点です)。

勢いで書いてしまったので整理します。

  • 双曲空間は数学的に由緒正しい空間
  • 双曲空間はPoincaré Ballモデルを採用することで、単位球内に表現することができる

さらに、Poincaré Ballモデルは

  • 単位球の原点から周縁部に行くにつれ、空間が加速度的に拡がっていき、空間効率よくデータを埋め込むことができる
  • 2点間の距離は明示的に計算できる
  • 階層構造が末端ほど周縁部に配置されるように自然に埋め込まれることが知られている。

という素晴らしい性質を持ちます。

Poincaré Embeddingsの構成

Poincaré EmbeddingsはPoincaré Ballへデータを埋め込む手法です。skipgramではデータ対$(i,j)$の間の類似度を内積$\langle v_i, v_j \rangle$で表していましたが、Poincaré Embeddingsのオリジナル論文では、これを2点間の距離の符号を反転したもの$-d(v_i,v_j)$で表します。内積で表していたものを距離で表すことにしたので、厳密にはskipgramとPoincaré Embeddingは対応が崩れていますが、ここでは目を瞑っておきます(あとで触れるように、双曲空間で内積相当のものを定義して利用している論文も出ています[10])。

Poincaré Embeddingsをskipgramと同様に学習する場合、

  • 共起するデータ対(positive samples)に対しては距離を小さく
  • 乱択されたデータ対(negative samples)に対しては距離を大きく

という具合に最適化を行います。ただし、距離関数が非負値な関係上、距離$d(v_i,v_j)$をそのまま使わずに、負値もとれるように $$ \frac{d(v_i, v_j) - r}{t}$$ というような補正を入れます($r,t$はハイパーパラメータです)。ちょっと歯がゆい感じですが、致し方なし。より良い類似度/非類似度関数の定義はfuture worksを待ちましょう。

最適化はRiemannian SGDという歪んだ空間で実行可能なSGD(確率的勾配降下法)により行いますが、こちらの説明は下記のスライド1枚にかえさせていただきます。要点は簡単で、勾配降下する際に、物差しの長さ分の補正をするだけです。

f:id:daynap1204:20170828013331p:plain

オリジナル論文[1]における実験結果

お待ちかねの実験結果です。2種類の実験をしています。

  1. WordNet の名詞の階層構造の埋め込み
  2. グラフのリンク予測

それぞれ見ていきましょう。細かい詳細は割愛していますので、必要であればオリジナル論文[1]を参照してください。

WordNet の名詞の階層構造の埋め込み

こちらは最初に挙げた私の実装でも再現できるようにしてあります。実行方法の詳細はREADMEをご覧ください。

WordNet[10]は英語の概念辞書です。オリジナル論文では、ここから名詞のみを抽出し、その概念上の上位・下位関係を列挙したものをデータセットとしています(例えば犬は哺乳類なので、上位概念が哺乳類、下位概念が犬という具合です)。具体的には

下位語 上位語
apple_jelly.n.01 confiture.n.01
telephone_system.n.01 physical_entity.n.01
organizer.n.02 causal_agent.n.01
utmost.n.01 extent.n.02
tael.n.01 abstraction.n.06
financial_institution.n.01 organization.n.01
uruguayan.n.01 whole.n.02
holocentrus_ascensionis.n.01 vertebrate.n.01
bell_metal.n.01 substance.n.01
takeoff.n.02 happening.n.01

という感じのデータになると想像されます(論文に書いてなかったので私は実験結果の数値をもとに、想像しながら合わせ込みました…)。 このデータは名詞対の並びになっていて、左側に下位の語が、右側に上位の語が来るようにしてあります。".n"はその語が名詞であることを表し、".01"や".02"といった数字は、 同じ綴りでも意味が異なる語を区別するために付けられています。

この1行を1 positive sampleとし、negative samplesはこのデータ中の単語の出現頻度に比例した確率で2単語乱択することにより生成します。

上記データに対して、下記のような関数の最大化を行います。

$$ \mathcal{L}(\Theta) = \sum_{(i, j)\in \mathcal{D}} \log \frac{ \exp(-d(v_i, v_j))}{\sum_{j' \in \mathcal{N}(i)} \exp(-d(v_i, v_{j'}))}$$

ここで、$\Theta$はこのモデルのパラメータ全体を表し、$\mathcal{N}(i)$は、データ$i$に対して生成された$j$のnegative samplesで、基本的には出現頻度に比例した乱択で生成されますが、$(i, j')$がデータセット$\mathcal{D}$に存在している場合はそれを除外して再生成します。 上記関数の最大化は、positive sampleとnegative samplesが混ぜこぜに与えられたときに、どれがpositive sampleかを当てる確率を最大化することに相当します。

結果は最初にお見せしたとおり、Euclide空間での埋め込みとは段違いの精度を叩き出しています。

f:id:daynap1204:20170828015724p:plain

精度向上もすごいですが、個人的にとても興味深く感じるのは、その埋め込まれ方です。下記もオリジナル論文からの引用ですが、上位語ほど中心近くに配置されていることがわかると思います。正しく階層構造が抽出されていますね(もっともデータセットを作るところで作為的に(下位語、上位語)とならべているので、ちょっとズルいですが…)

f:id:daynap1204:20170828015924p:plain

自前のコードでも同様の埋め込みを再現できたので載せておきます(私のコードを実行していただければ再現可能です)。見やすさのため、データの距離関係を保つ変換(等長変換)を施して、哺乳類(mammal)を中心に配置しています。

f:id:daynap1204:20170828020209p:plain

グラフのリンク予測

これは、与えられた2ノードの間にエッジが存在するかを予測するタスクです。このタスクの場合、下記の確率モデルのクロスエントロピー最大化を行います。

$$ P(e(i,j)=1; \Theta) = \frac{1}{1 + \exp((d(i,j)-r)/t)} $$

ここで、$i,j$はグラフのノードで$e(i,j)$は、それらの間にエッジが存在しているとき$e(i,j)=1$、エッジが存在していないとき$e(i,j)=0$となる関数です。$\Theta$は同様にモデルのパラメータで、$d(i,j)-r)/t$は先に説明した負値をとれるように補正済みの距離関数です。 この確率モデルを最大化することにより、互いにエッジが貼られているノードほど近くに配置されるようになります。

この場合でもやはり、Euclide空間での埋め込みを上回る精度を達成しています。

f:id:daynap1204:20170828021136p:plain

余談: Poincaré Ballにおける"内積"

内積ではなくあえて"内積”としたのは、Poincaré Ballにおける理想的な内積の定義はとても難しいと感じているからです。

Poincaré Embeddingsの論文[1]が出て間もなく、Poincaré Ballにおいて"内積"を定義し、Poincaré Embeddingsで$-d(v_i, v_i)$と表されていた類似度関数をその"内積"で置き換えた論文[11]が登場しました(この頃、何か双曲空間での埋め込みを助長するようなトリガーがあったのでしょうか?)。先日開催されたKDD2017のworkshopで発表されたようです。

www.mlgworkshop.org

この論文ではPoincaré Ballにおける"内積"を以下のように定義しています。 2点$u,v$のPoincaré Ballにおける"内積"をEuclide空間の内積$\langle u, v \rangle$と区別するために$\langle \langle u, v \rangle \rangle$と表したとき

$$ \langle\langle u,v \rangle \rangle = d(o,u) \ d(o,v) \ cos \theta .$$

がその"内積"です。 ここで、$o$はPoincaré Ballの原点であり、$\theta$は2点u,vが原点oを中心に為す角の角度です。Poincaré Ballにおいて、原点ともう一点の間の測地線はその2点間を結ぶ直線になることが知られているので、$\theta$は$o$と$u$とを結ぶ測地線と$o$と$v$とを結ぶ測地線とがなす角と思うことができます。この観点から、この"内積"は、Euclide空間における内積の概念をPoincaré Ballへ拡張したものとみなすことができます。

実は私も$-d(v_i, v_j)$の代わりになる関数を探していて、同じような"内積"に思い至ったのですが、最近、以下の対応関係から、Poincaré Ballにおける"内積"の定義はPoincaré Ballの空間としての豊かさを有効活用できていないことに気づきました。

$$ \langle \langle u,v \rangle \rangle = <u', v'>, \ \ u' = \frac{d(o,u)}{\Vert u \Vert} u, \ \ v' = \frac{d(o,v)}{\Vert v \Vert} v $$

対応$u \rightarrow u',\ v \rightarrow v'$により、Poincaré BallとEuclide空間との間に1対1の対応関係ができてしまい、しかもこの対応関係は"内積"の値を保ちます。なので、せっかくPoincaré Ballで考えていたのに、Euclide空間で考えるのと同じ表現能力しか獲得できていません。

双曲空間に適切な類似度/非類似度を定義するのは今後の課題になりそうです。

おわりに

いかがだったでしょうか。Poincaré Embeddingsの可能性を少しでもお伝えできていれば幸いです。データをよりリッチな空間で表現するという方向性は従来からありましたが(kernel法もimplicitにそういうことをしていますね)、Poincaré Ballでの表現は実用上の最適解の一つかもしれません。

以前Graph Convlutionをご紹介したことがありますが[12]、Graph Convolutionがうまくいくのはグラフに局所性や再帰性、フラクタル性、階層構造があるからだと思います。これらの性質を考えるにつけ、Poincaré Embeddingsとの親和性を感じずにはいられません。双曲空間でDeep Learningが展開される将来に胸をときめかせつつ、本稿を終わりにしたいと思います。

宣伝

ABEJAでは先端技術に目ざといイケてるResearcherを募集しています!その他職種も大絶賛募集中です!!

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

ABEJAという会社に興味が湧いてきた方はWantedlyで会社、事業、人の情報を発信しているので、是非ともフォローを!! www.wantedly.com

ABEJAの中の人と話ししたい!オフィス見学してみたいも随時受け付けておりますので、気軽にポチッとどうぞ↓↓

参考文献

[1] M. Nickel and D. Kiela, “Poincaré Embeddings for Learning Hierarchical Representations”, arXiv:1705.08039

[2] https://code.google.com/archive/p/word2vec/

[3] T. Mikolov et al., “Linguistic Regularities in Continuous Space Word Representations”, NAACL 2013

[4] T. Mikolov et al., “Efficient Estimation of Word Representations in Vector Space”, ICLR 2013

[5] Y. Bengio et al., “A Neural Probabilistic Language Model”, NIPS'00

[6] T. Mikolov et al., “Distributed Representations of Words and Phrases and Their Compositionality”, NIPS 2013

[7] J. Tang et al., “LINE: Large-scale Information Network Embedding”, WWW2015

[8] https://en.wikipedia.org/wiki/Hyperbolic_space

[9] Krioukov, Dmitri et al., “Hyperbolic geometry of complex networks”, Physical Review 2010

[10] https://wordnet.princeton.edu/

[11] B. P. Chamberlain et al., “Neural Embeddings of Graphs in Hyperbolic Space”, arXiv:1705.10359

[12] http://tech-blog.abeja.asia/entry/2017/04/27/105613

USB型 Deep Learning アクセラレーター「Movidius Neural Compute Stick」を使ってみた

7月20日、Intel (Movidius) がUSB接続タイプのスティック型ディープニューラルネットワーク処理用アクセラレータ「Movidius Neural Compute Stick」を発表しました。

NCSは、Deep Learningに特化した専用チップ「Myriad 2」が搭載された、外付けの演算装置です。USBポートに挿すだけでDeep Learningの推論処理を実行させることができるため、ラズパイやノートPCのようなデバイスでも比較的高速にDeep Learningアプリケーションを実行することができるようになります。

CVPR2017で先行発売されたMovidius Neural Compute Stick(以下、NCS)を入手しましたので使ってみました。CVPRでも数百個しか販売されてないとの事なので貴重です。 f:id:toshitanian:20170801215853j:plain

検証環境

  • Ubuntu 16.04
  • Linux kernel: 4.4.0-87-generic
  • Intel Core i7 3632QM

Getting started

NCSのGetting Startedに従って動かしてみます。Getting Startedのページはよくわからないので、詳細なAPIドキュメントとGetting StartedのPDFをここから入手します。

環境構築

最新版(2017/07/26時点: 1.07.07)のMovidius Neural Compute (MvNC) SDKを以下のコマンドに従ってダウンロード・展開します。参考

$ sudo apt-get update 
$ sudo apt-get upgrade

$ mkdir ~/ncsdk
$ cd ~/ncsdk
$ wget https://ncs-forum-uploads.s3.amazonaws.com/ncsdk/MvNC_SDK_01_07_07/MvNC_SDK_1.07.07.tgz
$ tar -xvf MvNC_SDK_1.07.07.tgz
x MvNC_Toolkit-1.07.06.tgz
x MvNC_API-1.07.07.tgz

Movidius Neural Compute Toolkitをインストールします。

Movidius Neural Compute ToolkitはDeep Learningのチューニング・検証・プロファイリングをするためのツールを提供しています。コンパイルは現在時点ではcaffeのmodelをNCSでロードして実行できる形式にする機能を持っています。 インストールに少し時間がかかる(15分くらい)ので気長に待ちます。

$  tar -xvf MvNC_Toolkit-1.07.06.tgz
$ cd bin
$ ./setup.sh

サンプルで使うcaffeモデルをダウンロードします。少し時間がかかるので気長に待ちます。

$ cd data
$ ./dlnets.sh

Movidius NC APIをインストールします。

Movidius NC APIはNCS上での推論処理をプログラムから実行するためのAPIを提供します。少し時間が(ry

$ ~/ncsdk
$ tar -xvf MvNC_API-1.07.07.tgz
$ cd ncapi
$ ./setup.sh

ここまででMovidius Neural Compute ToolkitとMovidius NC APIのインストールが完了しました。

サンプルを動かしてみる

ここからは実際にNCSを使ってサンプルを動かして行きます。ホストマシンのUSBポートにNCSを挿してください。lsusbを打つと新しいデバイスが認識されている事がわかります。

Python APIを使ったサンプル

PythonからNCSをどう使うかをサンプルを使って見てみたいと思います。classification_example.pyというpythonのサンプルではAlexnetを使って指定された画像の推論処理をしています。実行すると以下のようなログを出しました。

$ cd ~/ncsdk/ncapi/py_examples
$ python3 classification_example.py 2
Device 0 Address: 3 - VID/PID 03e7:2150
Starting wait for connect with 2000ms timeout
Found Address: 3 - VID/PID 03e7:2150
Found EP 0x81 : max packet size is 512 bytes
Found EP 0x01 : max packet size is 512 bytes
Found and opened device
Performing bulk write of 825136 bytes...
Successfully sent 825136 bytes of data in 54.576918 ms (14.418385 MB/s)
Boot successful, device address 3
Found Address: 3 - VID/PID 040e:f63b
done
Booted 3 -> VSC

------- predictions --------
prediction 1 is n02123159 tiger cat
prediction 2 is n02123045 tabby, tabby cat
prediction 3 is n02119022 red fox, Vulpes vulpes
prediction 4 is n02085620 Chihuahua
prediction 5 is n02326432 hare

以下の流れで、pythonからNCS使う事ができます。

  • NCSのデバイスをプログラム上で取得する
  • NNのモデルをデバイスに転送する
  • 推論したい画像をデバイスに転送する
  • 推論結果をデバイスから取得する

classification_example.pyの中身は以下のようになっています。

from mvnc import mvncapi as mvnc
import numpy
import cv2
import time
import csv
import os
import sys

if len(sys.argv) != 2:
    print ("Usage: enter 1 for Googlenet, 2 for Alexnet, 3 for Squeezenet")
    sys.exit()
if sys.argv[1]=='1':
    network="googlenet"
elif sys.argv[1]=='2':
    network='alexnet'
elif sys.argv[1]=='3':
    network='squeezenet'
else:
    print ("Usage: enter 1 for Googlenet, 2 for Alexnet, 3 for Squeezenet")
    sys.exit()

# get labels
labels_file='../tools/synset_words.txt'
labels=numpy.loadtxt(labels_file,str,delimiter='\t')
# configuration NCS
mvnc.SetGlobalOption(mvnc.GlobalOption.LOGLEVEL, 2)
devices = mvnc.EnumerateDevices()
if len(devices) == 0:
    print('No devices found')
    quit()
device = mvnc.Device(devices[0])
device.OpenDevice()
opt = device.GetDeviceOption(mvnc.DeviceOption.OPTIMISATIONLIST)

if network == "squeezenet":
    network_blob='../networks/SqueezeNet/graph'
    dim=(227,227)
elif network=="googlenet":
    network_blob='../networks/GoogLeNet/graph'
    dim=(224,224)
elif network=='alexnet':
    network_blob='../networks/AlexNet/graph'
    dim=(227,227)
#Load blob
with open(network_blob, mode='rb') as f:
    blob = f.read()
graph = device.AllocateGraph(blob)
graph.SetGraphOption(mvnc.GraphOption.ITERATIONS, 1)
iterations = graph.GetGraphOption(mvnc.GraphOption.ITERATIONS)

ilsvrc_mean = numpy.load('../mean/ilsvrc12/ilsvrc_2012_mean.npy').mean(1).mean(1) #loading the mean file
img = cv2.imread('../images/cat.jpg')
img=cv2.resize(img,dim)
img = img.astype(numpy.float32)
img[:,:,0] = (img[:,:,0] - ilsvrc_mean[0])
img[:,:,1] = (img[:,:,1] - ilsvrc_mean[1])
img[:,:,2] = (img[:,:,2] - ilsvrc_mean[2])
graph.LoadTensor(img.astype(numpy.float16), 'user object')
output, userobj = graph.GetResult()
order = output.argsort()[::-1][:6]
print('\n------- predictions --------')
for i in range(1,6):
    print ('prediction ' + str(i) + ' is ' + labels[order[i]])
graph.DeallocateGraph()
device.CloseDevice()

ちなみに

NCSをNCSで推論させるとハーモニカになります。

f:id:toshitanian:20170810100019j:plain

$ ./ncs-fullcheck -c1 ../networks/AlexNet ~/Desktop/b03.jpg 
OpenDevice 1 succeeded
Graph allocated
harmonica, mouth organ, harp, mouth harp (12.98%) rubber eraser, rubber, pencil eraser (12.19%) lighter, light, igniter, ignitor (9.95%) whistle (8.45%) sunscreen, sunblock, sun blocker (5.20%) 
Inference time: 283.648071 ms, total time 288.491546 ms
Deallocate graph, rc=0
Device closed, rc=0

ラズベリーパイでNCSを使う

Raspberrypi3でMovidius Neural Compute Stickのサンプルまでを動かしてみた - Qiita

RaspberryPI3でMovidius NCSのサンプルを少し真面目に動かしてみた - Qiita

こちらにABEJAのリサーチャーがNCSをラズベリーパイで動かしてみたポストがあるのでご参照ください。

まとめ

今回はあまり綿密な検証はしませんでしたが、NCSを使うことでGPUを搭載していないマシンでも比較的簡単にDeep Learningを使えることがわかりました。処理性能の高くないデバイスでもNCSを挿すだけでエッジサイドDeep Learningを実行できるというのは夢がありますね。

We are hiring!

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

ABEJAという会社に興味が湧いてきた方はWantedlyで会社、事業、人の情報を発信しているので、是非ともフォローを!! www.wantedly.com

ABEJAの中の人と話ししたい!オフィス見学してみたいも随時受け付けておりますので、気軽にポチッとどうぞ↓↓

Vue.jsのバックエンドとしてのFirebase

このポストは英語版を日本語に直したものです。

Vue.jsのバックエンドとしてのFirebase

こんにちは、株式会社ABEJAのカンとです。AIの会社として知られているABEJAですが、フロントエンドをメインとするチームもあります。私が所属しているSaaS Development チームです。私達は(最近のフロントエンドチームは皆そうですが…)最新技術が大好きで、以下のような技術を使っています。

f:id:abeja:20170726184558p:plain

最近、社内ツールを作っています。元々使っていた「AWS Lambda+Vue(S3)」の代わりに「Firebase + Cloud functions for firebase」を利用していますが、とても便利で楽しいです。ぜひ皆さんにも使って頂きたく、このブログを書くことになりました。

以下のような流れで書きたいと思います。

  • なぜFirebaseなのか
  • Firebase as the database
    • Real-time
    • Authentication
    • Deploy and hosting
    • Vue.js and vuefire
  • Firebase with Cloud Functions
    • So good.
    • Not so good.
  • 学んだこと
    • Databaseの構造
    • 集計
  • さいごに

より理解を高めるためにABEJA totalizerというサンプルサービスを用意しました。このサービスは簡単に投票用課題を作り、リアルタイムで集計するサービスです。このサービスはVuejs, firebase, cloud function for firebaseを使って作りました。

なぜFirebaseなのか

理由は簡単です。我々フロントエンドチームはサーバーを運用したくないからです。小規模から中規模までカバーできるBaaSをどれにするか検討した結果、FirebaseとGraphcoolに辿り着きました。最近、GCPの勢いがいいこともありFirebaseに決めました。

Firebase as the database

Real-time

FirebaseはWeb用のSDKを入れるだけでRealTime Web systemが実現できます。WebSocketを入れる必要もEventをListenする必要もありません。DBの値が変わったら自動にブラウザ上の値が変更されます。さらに、DBを直接LoadしているのでAPIを書く必要もそれ用のサーバーを持つ必要もありません。

Authentication

認証。どのシステムにも必須で大事な仕組みであり、当然開発するのに時間もかかります。Firebaseは Emailはもちろん、Facebook、GoogleなどSocial Authenticationをサービスとして提供していて簡単に追加できます。これにはPassword変更やEmail Verificationなど開発・管理が面倒な機能も全て含まれています。

Deploy and hosting

ホスティング。新しいサービスができたらどうやってホスティングしますか?SSLは?Firebaseはfirebase deployコマンド一発でデプロイでき、すぐにあなたのサービスを提供することができます。

// abeja-totalizer/package.json
"scripts": {
  "prepackage-functions": "rimraf functions",
  "package-functions": "babel 'functionsES6' --out-dir 'functions' --presets=es2015 --copy-files --ignore 'node_modules'",
  "postpackage-functions": "cd functions && yarn",
  "deploy": "yarn run package-functions && firebase deploy"
}

実際私達が使ってるDeployコマンドです。イケてるフロントエンド開発者ならprepackagepostpackageでいったい何をやってるんだと思うかも知れません。簡単に説明しますと、Google Cloud FunctionsがES6に対応していないため、トランスパイルしています。Linkこのリンクを参考にしました。

Vue.js and vuefire

このイケてるDBとVue.jsをどうやって繋げるのか。Vue.jsの詳細については最近の人気からたくさん情報がありますのでふれないようにしますが、Vue.jsを使えば簡単かつ恐ろしいスピートでコンポネント化されたWebサービスを作ることができます! FirebaseとVue.jsを繋げるところに話を戻すと、使用するのはvuefireです。そして私達はそれをより簡単にするため、以下のようなPluginを書きました。

// abeja-totalizer/src/libs/FirebaseDBPlugin.js
import firebase from '../helper/firebaseDB'

const db = firebase.firebaseApp.database()
const DATABASE_NAME = process.env.DB_NAME
const fbRef = ref => {
  return db.ref(`${DATABASE_NAME}/${ref}`)
}
const questions = db.ref(`${DATABASE_NAME}/questions`)
const answers = db.ref(`${DATABASE_NAME}/answers`)
const FB_MAPPING = {
  'questions': function () {
    this.$bindAsArray('questions', questions)
  },
  'question': function (param) {
    if (!param.questionKey) return
    this.$bindAsObject('question', questions.child(param.questionKey))
  },
  'answers': function (param) {
    if (!param.questionKey) return
    this.$bindAsArray('answers', answers.child(param.questionKey))
  },
  'myAnswer': function (param) {
    if (!param.questionKey) return
    if (!param.myId) return
    this.$bindAsObject('myAnswer', answers.child(param.questionKey).child(param.myId))
  }
}

const fbBinding = function () {
  let fbBind = this.$options['fbBind']
  if (typeof fbBind === 'function') fbBind = fbBind.call(this)
  if (!fbBind) return
  Object.keys(fbBind).forEach(k => {
    const b = FB_MAPPING[k]
    b.call(this, fbBind[k])
  })
}
const init = function () {
  fbBinding.call(this)
}

const install = (Vue) => {
  Vue.prototype.$fbRef = fbRef
  Vue.prototype.$fbBinding = fbBinding
  Vue.mixin({
    created: init // 1.x and 2.x
  })
}

export default {
  install
}
    // abeja-totalizer/src/main.js
    import FirebaseDBPlugin from './libs/FirebaseDBPlugin'
    Vue.use(FirebaseDBPlugin)

このようにして使うことができます。

// abeja-totalizer/src/components/Main.vue
export default {
  fbBind: function () {
    return {
      'questions': {}
    }
  }
}

Firebase with Cloud Functions

So good

Cloud Functions for Firebase. これはFirebaseで開発を始めて以降一番興奮した機能です。FirebaseはRealtime DBを持つBaaSとしてだけでなく、様々なFunctionを持っています。このFunctionを使ってHTTP Request(普通のAPI)の処理はもちろん、DB、Storage、ユーザー登録変更イベントをキャッチし、ロジックを入れる(Event Sourcing)ことができます!Serverless Architectureに感謝です!functionを書いてDeployすれば、もうメンテナンスもスケーリングも気にする必要がありません。no worry be happy 😬

Not so good

完璧なシステムはないです。今のところHTTP triggered function以外のCloud Functions for FirebaseをLocal環境でDebugする術はありません。Firebase CLIを使えばHTTP triggered functionのみLocal環境でDebugすることができます。そしてGoogleは「cloud Functions Local Emulator」というツールをCloud Functions用に提供していますが、Cloud Functions for firebaseでは使うことができません!(なんと!)Cloud FunctionsとCloud Functions for firebaseはほぼ同じですが少し違います。では他にDeployしないでDebugする術はないでしょうか。答えはUnitTestを書くことです。このドキュメントを参考してください。

学んだこと

デバッグ以外にもFirebaseを使いながら学んだことがいくつかあります。

データの構造はFlatに!

可能な限りデータはフラットに持ちましょう。データをNestedして持つと2つ大きな問題に直面します。1つ目はレコードを1つ取得するQueryを書くことが難しいこと、2つ目はSingle RequestのPayloadが大きくなることです。それに対する対策は以下の例みたいにデータをフラットにすることです。

// customers has many shops, each shop has many devices, devices owned by customer
customers
  -KnrCVAqhTQ33fGkz50s
    shops
      -KnrCVBHeSztSd86_CVI: true
      ...
shops
  -KnrCVBHeSztSd86_CVI
    customerKey: -KnrCVAqhTQ33fGkz50s
    devices
      -KnXFP1dGQoLsu4dzran: true
      ...
devices
  -KnXFP1dGQoLsu4dzran
    customerKey: -KnrCVAqhTQ33fGkz50s
    shopKey: -KnrCVBHeSztSd86_CVI
    name: '

集計

Firebaseでは従来のRDBが提供するような集計機能を使うことができません。でもRealtimeDatabaseならではの方法でこれを実現することができます。それはCloud Functions for Firebaseを利用することです。集計ロジックをFunctionとして書いて、DBの値が変更された時集計することです。以下のサンプルを参考にしてください。

import 'babel-polyfill'
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp(functions.config().firebase)

const setFirebaseRef = async (ref, index, plusOrMinus) => {
  let countRef = ref.child(`${index}/count`)
  let countValue = (await countRef.once('value')).val()
  countRef.set(countValue + plusOrMinus)
}

export const countSelect = functions.database.ref('/totalizer/answers/{questionKey}/{userId}')
  .onWrite(async event => {
    console.log('on write')
    let selectionRef = admin.database().ref(`/totalizer/questions/${event.params.questionKey}/selections`)
    if (event.data.previous.exists()) {
      console.log('update')
      let prevIndex = event.data.previous.val()
      await setFirebaseRef(selectionRef, prevIndex, -1)
      let newIndex = event.data.val()
      await setFirebaseRef(selectionRef, newIndex, 1)
    } else {
      console.log('create')
      let index = event.data.val()
      await setFirebaseRef(selectionRef, index, 1)
    }
  })

※このFunctionにはBugがあります。答えの変更があった時元の答えを-1にし変更された答えを+1にしていますが、同時にたくさんの処理が走る場合正しく動作しません。Eventがあるタイミングで計算をし直す必要があると思います。

さいごに

数週間Firebaseを使って、FirebaseとVue.jsは一緒に使うことが非常に便利であることが分かりました。これからのProjectにもこの組み合わせでいくと思います。このPostに使ったコードを参考に皆さんもぜひ試してみてください😃

サンプルコードはこちらから利用できます。今度会う時まで、Happy coding!

ABEJA SaaS Dev

We are hiring!

ABEJAが発信する最新テクノロジーに興味がある方は、是非ともブログの読者に!

ABEJAという会社に興味が湧いてきた方はWantedlyで会社、事業、人の情報を発信しているので、是非ともフォローを!! www.wantedly.com

ABEJAの中の人と話ししたい!オフィス見学してみたいも随時受け付けておりますので、気軽にポチッとどうぞ↓↓