ABEJA Tech Blog

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

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

Firebase on Vue

On the top of ABEJA’s smart AI brain, Abeja also comes with a ‘pretty face’, and we, the SaaS development division team, is the one who’s responsible for it. We love embracing new and exciting technologies, and our technology stack proves it:

f:id:abeja:20170726184558p:plain

In the last couple of weeks, we’ve been working on a new internal project, this time around, instead of AWS Lambda + Vue.js combo, something we’ve been using for a long time, we switched to Firebase and Cloud Functions for Firebase as our backend solution. We had so much fun working with it, and we’d like to spread the love, share with you our experiences in this post, here’s what we gonna go about it.

  • Why Firebase
  • Firebase as the database
    • Real-time
    • Authentication
    • Deploy and hosting
    • Vue.js and vuefire
  • Firebase with Cloud Functions
    • The good
    • The not so good
  • Lesson learned
    • Best practice of structuring your data
    • Aggregations
  • Conclusion

You could probably find tons of articles online talking about the good or bad of either Firebase or Vue.js, as our fellow programmers always say, talk is cheap, show me the code, so, we’d like to take a different approach, not only we’ll be sharing the code, but also, to present you an actual working application that you can play around with. Now, we present you, the Abeja totalizer, a real-time voting application built with Firebase, Cloud Function for Firebase, and Vue.js. With this simple web application, you will be able to create a questionnaire, share it with your friends, and track the results in real-time, it’s a simple web application, and it perfectly demonstrate some of the awesome features this technology stack offers, which we will be getting into next.

Why Firebase

Well, the reason is simple, we’re a front-end focus team, and we don’t want to maintain a dedicated backend stack, we were looking for a BaaS solution for our small to medium size projects, looking at the most advanced BaaS solution out there, it comes down to Firebase and Graphcool, and since we were thinking about more Google Cloud Platform and less Amazon AWS, then Firebase became the most obvious choice.

Firebase as the database

Real-time

Real-time. As a front-end focus team, building a real-time application sounds a bit intimidating, it’s not easy to pull it off without a sophisticated backend solution. Firebase comes with real-time by default, you will not even notice the difference while developing your app, it just works.

Authentication

Authentication. again, this is one of those things that every non-trivial app will need, but will take some time to do it right. Firebase provides a solid solution that you can easily integrate with your front-end stack, email and password based authentication, federated identity provider (Google, Facebook, Twitter, Github) integration, phone number authentication, or even anonymous authentication, you name it, it has it.

Deploy and hosting

Hosting, your shining new web app is ready to roll, where to host it? CDN? SSL? They’re as important as the features your app offer. And they’re all taken good care by Firebase’s production-grade hosting solution. As for deployment, here’s our deployment script, it’s pretty straight forward, after JavaScript gets transpiled, firebase deploy that’s all you need to do, then your app is ready to be shared with millions of users :)

// 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"
}

If you’re not a front-end savvy developer, you may wondering what the hell with prepackage, package and postpackage, well, long story short, we’re basically using the babel to transpile our ES6 JavaScript code down to ES5 cause currently Google Cloud Functions supports only Node.js version 6.11, we know the versioning is a bit confusing, for more information on this, as well as how to write Cloud Functions with ES6, here’s a good article to get started. Link

Vue.js and vuefire

So now we have a solid database, how about connecting it to our front-end stack? Which is Vue.js in our case, we don’t plan to get too much into Vue.js as its popularity speaks for itself. All we’ll say is that Vue.js allow us to build componentized web applications blazing fast! Back to connect Firebase to Vue.js, vuefire is the library we rely upon to make it happen, and on the top of vuefire, we also wrote a plugin, which makes getting data from Firebase to your Vue component even easier.

Here’s our plugin implementation:

// 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
}

You can use this as any other Vue.js plugins:

// abeja-totalizer/src/main.js
import FirebaseDBPlugin from './libs/FirebaseDBPlugin'
Vue.use(FirebaseDBPlugin)

Then in your Vue.js component, you can simply drop whichever firebase data object you’d like:

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

Firebase with Cloud Functions

Cloud Functions for Firebase, the good

Cloud Functions for Firebase. This is THE most exciting feature Firebase added not long ago. Firebase was never considered a one-stop BaaS solution cause having a real-time database is not enough. Everything changed with the arrival of Cloud Functions for Firebase, now you’ll be able to write your business logic, which could be triggered by either HTTP request or real-time Firebase change event (and much more), how cool is that! And thanks for the serverless architecture, you write your function, deploy it, then you don’t ever need to worry about it ever again, no maintenance, no scaling concerns, no worry be happy :D

Cloud Functions for Firebase, the not so good

Well, nothing is perfect, at the time of writing this post, Cloud Functions for Firebase has one downside as far as we can tell, you can’t really debug your non-HTTP triggered function in local development environment, Firebase command line tool has this experimental feature which allows you to debug HTTP triggered functions, and also Google does provide a tool called Cloud Functions Local Emulator for debugging, however, it only works with Cloud Functions, not Cloud Functions for Firebase, Google Cloud Functions and Cloud Functions for Firebase are kind of the same thing but with some slight differences. So, is there any alternatives besides deploy, run, stare at the logs? (by the way, Google Cloud Platform logging feature is very nice). The answer is unit tests, take a look at this (https://firebase.google.com/docs/functions/unit-testing) article for more information.

Lesson learned

Besides the tips on debugging, there are some other lesson-learned best practices when working with Firebase we’d like to share.

Best practice of structuring your data

Keep your database structure as flat as possible. It’s very attempting to nest one property within another one, nested properties have two major problems, almost impossible to query for one, and another one is too large of payload for a single request. And the solution would be flattening your data as always, here’s an example of how we structured the data for one of our internal projects, everything is flattened, and we use firebase generated key to form the relationship between data objects.

// 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: '

Aggregations

We do miss the powerful aggregation functions provided by the traditional relational databases. Turns out you’re not exactly out of luck, one potential solution could be taking advantage of Cloud Functions for Firebase, create your own aggregation functions, which get triggered upon Firebase data changes, yet another use case for Cloud Function for Firebase 😃. Here’s a naive example of doing this just for reference:

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)
    }
  })

The conclusion

After working with it for a couple of weeks, we all agree that Firebase on Vue will be our de-facto technology stack for the front-end team to build future projects. And we highly recommend you to do so if you want to build real-time, robust web applications in less time. Hopefully this blog post, the sample application as well as our code examples have done their job to win you over. 😃

That’s it, you can find rest of the sample codes in this repository https://github.com/abeja-inc/abeja-totalizer , until next time, happy coding.

ABEJA SaaS Dev

We are Hiring!

We are also continuously looking for motivated engineers on various positions on frontend, backend and AI platform. If you want to be a part of fast paced startup based in Tokyo please contact us via email ( recruit@abeja.asia ) or Linkedin page. To see what we are working on, have a look at other posts by our Engineers and Researchers.

To know more about ABEJA and its business: www.wantedly.com

AWS Greengrassの気になる所を調べてみた!

エンジニアの河崎です。

昨日、AWS GreengrassがGAになりました

AWS GreengrassはLambda等のAWSで利用可能なサービスの一部をローカルデバイス上で実行できるようにする事で、エッジ側でのアプリケーション実行を簡単にできるサービスです。 ABEJAではエッジコンピューティングを行っていますので、さっそく調査をしてみました。

以降の検証は Raspberry Pi 3 Model B で行っています。

※2017年6月9日時点の情報です。また、できるだけ正確な情報を書くようにしましたが、間違っている可能性もありますのでご留意ください。

調べてみた

AWS Greengrassとは?

公式ドキュメント

What Is AWS Greengrass? - AWS Greengrass

Developers.IOのブログエントリ

【新サービス】AWS GreengrassがGA(一般利用開始)になりました! | Developers.IO

どうやって動かすの?

GreengrassCoreのインストールされたデバイス上で常時起動型のLambda functionを実行するサンプル

Deploying a simple Lambda function to AWS Greengrass - AWS Greengrass

GreengrassCoreのインストールされたデバイスをハブにして、IoTデバイス間の通信をするサンプル

AWS Greengrass Example Scenario - AWS Greengrass

Developers.IOのブログエントリ

AWS Greengrass CoreをEC2でセットアップする | Developers.IO

Greengrass Coreの動作要件は?

AWS Greengrass FAQs - Amazon Web Servicesに記載があります。

  • Operating Systems: Ubuntu 14.04 LTS, Jessie Kernel 4.¼.4, and other Linux distributions with Kernel 4.4 or greater
  • CPU Architectures: x86_64, Armv7, Aarch64 (ArmV8)

また、以下のカーネルコンフィグの項目が有効化されている必要があります。世の中にはcgroupsやoverlayfsが有効化されていないカーネルが乗ったデバイスが有るので気をつけたいですね。

自分のカーネルでカーネルコンフィグの項目が有効化されているかはdockerのチェックスクリプトを使うと良いかもしれません。

2.Kernel configuration

·Mqueue: CONFIG_POSIX_MQUEUE

·Overlay: CONFIG_OF_OVERLAY

·Overlay FS: CONFIG_OVERLAY_FS

·Seccomp Arch Filter: CONFIG_HAVE_ARCH_SECCOMP_FILTER

·Seccomp Filter: CONFIG_SECCOMP_FILTER

·Seccomp: CONFIG_SECCOMP

·Devpts: CONFIG_DEVPTS_MULTIPLE_INSTANCES

 

3.Kernel configuration for Namespace - Kernels must be built with these configurations enabled:

·IPC isolation: CONFIG_IPC_NS

·Network isolation: CONFIG_NET_NS

·UTS isolation: CONFIG_UTS_NS

·User isolation: CONFIG_USER_NS

·PID isolation: CONFIG_PID_NS

 

4.Kernel configuration for Cgroup - Kernels must be built with these configurations enabled:

·Enable cgroups: CONFIG_CGROUPS

·Enable Memory cgroup: CONFIG_MEMCG

·Enable freezer cgroup: CONFIG_CGROUP_FREEZER

·Enable devices cgroup: CONFIG_CGROUP_DEVICE

·Enable pids cgroup: CONFIG_CGROUP_PIDS

デバイス上でLambdaが動くってどういうこと?

GreengrassCoreがインストールされたデバイス上でpythonのスクリプトを実行する事ができます。Lambda functionの実行はMQTTでメッセージを受けた時に実行、定期的に実行、常時起動などができるようです。 ちなみに、GreengrassCoreで実行されるLambda functionには実行時間の制限はありません。

Lambda functionの実行ログは?GreengrassCoreのログは?

以下の2種類のログを取得可能。それぞれ、CloudWatchとLocalファイルシステムに保存する事ができます。Localファイルシステムの場合はどれくらいのサイズまで保存するかを指定できます。

  • User lambda logs
  • Greengrass system logs

検証してみた

GreengrassからAWSの他のサービスへアクセスする権限はどうやって指定するの?

IAM Roleを使う

GrenngrassからCloudWatch Logsへログを送る場合は、Greengrass GroupにIAM Roleを付与します。また、LambdaからCloudWatch Logsや他のサービスにアクセスする時も同様にLambda functionにIAM Roleを付与します。

Greengrassからどういう感じでAWSサービス扱うのかな?

基本的にはMQTTでクラウドに送った後にRules Engineを使うのがよさそうです。Lambda functionに適切なIAM Roleを付与すればSDKからKinesisやDynamoDBにアクセスする事は可能だと思うので、ユーザ側の要件によりそう。

CloudWatch Eventsからデバイス側のLambdaを呼び出せる?

今のところできなさそう?

Greengrassコアがオフラインの状態でもLambda functionの実行はできる?

多分できる。未検証

このサンプルを動かしてる状態で、ネットワークの状態をいじると検証できます。

Lambda functionからpythonライブラリや設定ファイル等にアクセスできる?

可能

クラウドでの実行時と同様にDeployment Packageを作る事ができます。この際、AWS Greengrass Core SDKをダウンロードしてパッケージに含める必要があります。また、設定ファイルもパッケージにいれれば読み込みできそうです。 しかし、デプロイパッケージの容量制限は50MBなので、特定用途では使えない事がありそうです。 また、パッケージする時にコンパイルが走るライブラリは、GreengrassCore上のランタイムでは動かないと思われるので一工夫必要です。多くの場合、パッケージしたいマシンはx86でGreengrassCoreが動くデバイスはARMですよね。また、コンテナ内には必要依存ライブラリも存在しません。

こういうプロジェクトを参考にがんばるとよさそうですね。 GitHub - vitolimandibhrata/aws-lambda-numpy: NumPy Python Library for AWS Lambda

Lambda functionからデバイス側のファイルシステムにアクセスできる?

読み込みのみ可能

Lambda function起動時に、デバイスのファイルシステムを起点として、コンテナのファイルシステムをoverlayfsで構築しているようです。Lambda functionで書き込んだファイルは、デバイス側のファイルシステムに反映されませんし、Lambda functionを再起動した時はLambdaから見えているファイルシステムはリセットされています。 /tmpはコンテナ起動時にリセットされるので、デバイス側の/tmpとコンテナ側の/tmpは完全に独立しています。

以上の特性を利用すれば、Lambdaからデバイス側のファイルを参照だけする事ができますが、推奨されない気がします。

Lambda functionからデバイス側のカメラやGPIO等にアクセスできる?

今のところできない

Lamda functionの中から/devを見てみましたが、カメラデバイスやgpioデバイスは見えていませんでした。

ローカルネットワークでのデバイス間通信ってどんな感じでやるの?

APIを使う事でGreengrass Coreのローカルネットワーク内でのIPをIoTデバイス側から取得できます。 AWS IoT device SDKを使う事で簡単に、IoTデバイスから同一ネットワーク内のGreengrass Coreに接続する事ができます。Greengrass CoreにはMQTT brokerと同一GreengrassCore内のDeviceShadowを保持するDBが有るので、IoTデバイスとGreengrass CoreがAWSに繋がらない状態でも、デバイス同士のメッセージの交換や、DeviceShadowを使った状態の同期が可能です。このサンプルを動かしてみると良いと思います。

Local Rules Engineって何?

Subscriptionsの事だと思います。メッセージのソース、ターゲット、メッセージを流すトピックフィルターを指定できます。ソースとターゲットは、IoT Cloud(クラウド側のMQTT broker)、Local Device Shadow、同一Grenngrass Group内のデバイス、Lambda functionを指定できます。このサンプルをいじれば色々検証できそうです。 f:id:toshitanian:20170609101719p:plain

おわりに

現時点ではGreengrassCore上でのLambda functionは、センサー等を使ったヘビーな処理を実行するというよりは、ゲートウェイとして必要になるメッセージの処理と、デバイスの操作を簡易的に行うために使う事が想定されているのかなという感想を持ちました。 まだドキュメントが少ないので、増えていくと検証もすすみそうですね。当面は、APIドキュメントを読んでいこうと思います。

AWSを使う事でクラウドだけでなく、エッジ側でもシステムを簡単に構築・運用できるようになっていってほしいですね。

宣伝

ABEJAではエッジ・クラウドを問わずディープラーニングを実行できるプラットフォームを提供しています。 テクノロジーの最先端に挑戦したいイケてるしヤバイエンジニアの方のご参画をお待ちしております!

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

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

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

学生向けの勉強会もしています!ガンガン応募してください!

https://abeja-innovation-meetup.connpass.com/event/57686/

タカハシ春の GAN 祭り!〜 一日一GAN(๑•̀ㅂ•́)و✧ 〜

f:id:taka_t0m0:20170530141554j:plain

ABEJAでリサーチャーをしています高橋です。

昨今 deep learning 界隈では Generative Adversarial Net(GAN) が流行っていて、世はまさにガンガン行こうぜ時代ですね。

GAN を用いると綺麗な絵が作成できたり二つの絵の中間のような絵を生成できたりします。例えばこの論文のような感じです。このように GAN は有用なモデルである一方、最近の GAN では急によくわからない式が出てきたりするので、勉強も兼ねて「一日一GAN」をやってみました。今回読んだ論文のリストは以下です。

それぞれの論文の概要と自分なりの考察を報告できればと思います! 論文を読む前や読んでいる途中に参考になったなら幸いです。

一応実装も行い こちらで公開していますが、パラメータの調節などはしておらずそのままでうまくいかなさそうである旨ご了承ください。今後各 GAN の比較実験を行なった際には、その結果や使い勝手なども報告できればと考えています。一日一GANでパラメータ調節や比較実験までやるのは厳しかったです。

本筋に入る前に


GAN の 概要

本筋に入る前に GAN について説明します。

GAN のたとえ話

数式で説明する前に、GAN の学習過程についてよくあるたとえ話を述べたいと思います。

GAN で行なっていることは、贋作師と真贋鑑定士の対決、つまりガンの飛ばし合いです。贋作師は世に偽物を売るために有名な絵とそっくりな絵を描く能力が重要で、真贋鑑定士は偽物が世の中に出回らないように偽物と本物を区別する能力が重要となります。真贋鑑定士が偽物と本物を見分ける能力が向上したとします。すると贋作師は自分の偽物の絵が世に出回らなくなり困ったことになるので、更に本物に近い絵を描けるように能力の向上を目指します。能力が向上し偽物が出回ると、今度は真贋鑑定士が困ったことになるので真贋鑑定士は本物と偽物を見分ける能力の向上を目指します。そうすると、また贋作師が困ったことになって。といった具合に、二人が対決することで二人の能力がどんどん上がっていくわけです。特に贋作師の方に注目すれば、最終的には本物と区別がつかないような絵を作れるようになる、という話です。

GAN の objective

上記のような話を考えるのは簡単ですが、具体的にどういう学習をするの、とか、うまくいく理論保証はあるの、と言った部分が気になります。ということでこの部分についても触れたいと思います。以下、絵など模倣したい対象 data set があり、そのうちの一つを $x$ と書くことにします。また、$x$ は 確率分布$p_{data}(x)$に従っているとします。

真贋鑑定士 Discriminator $D$は、data 内にある $x$ を本物と判定できる様に、つまり、$x\sim p_{data}(x)$に対して$D(x) = 1$ とできる様に学習を行います。贋作師 Generator $G$ はどの様に偽物を作るのかと言えば、何らかの事前に決めた分布 $p_z$ に従う $z$ を用意し、$G$ は $z$ を入力として贋作を出力します。つまり、Generator は $D(G(z)) = 1$ とする様に頑張り、Discriminator は $D(G(z)) = 0$ となる様に学習を行います。

以上から、例えば以下の様な objective を設定して学習を行えば良さそう、と考えられます。 \begin{align} \min_{G}\max_D \left[\mathbb{E}_{x\sim p_{data}}\left[\log D(x)\right]+\mathbb{E}_{z\sim p_{z}}\left[\log(1 - D(G(z)))\right]\right]\ . \end{align} 以上で良さそうだと思えるのは、$G$ を固定すれば$D$ は $D(x)$ を大きくし、$D(G(x))$ を小さくする様に学習され、$D$ を固定すれば $G$ は $D(G(z))$ を大きくする様に学習されるためです。

理論解析

この学習で良い偽物を作れる理由を述べたいと思います。 以下 $p_z$ と $G(z)$から誘導される確率分布$p_g(x)$ と書くことにすると、 良い偽物が作れるようになる理由は、この最適化問題の global optimum が $p_g(x)$と $p_{data}(x)$が一致する時だからです。以下でそれを確認します。

まず、$G$ を固定した際、最良の $D_G$ は以下のように計算できます。 objective $\mathcal{L}$ が \begin{align} \mathcal{L}(G, D) &= \int p_{data}(x) \log(D(x)) dx+ \int p_z(z)\log(1 - D(G(z)))dz \\ &=\int p_{data}(x) \log(D(x)) + p_g(x) \log(1 - D(x)) dx \end{align}

と書き直せますが、$f(x) = a\log(x) + b\log(1 - x)$ の最大値は $x = a/(a + b)$で取るから \begin{align} D_G = \frac{p_{data}}{p_{data} + p_g} \end{align} となります。これを用いてちょっと計算すると \begin{align} \max_{D}\mathcal{L}(G, D) &= \mathbb{E}_{x\sim p_{data}}\left[\log\left(\frac{p_{data}}{p_{data} + p_g}\right)\right] +\mathbb{E}_{x\sim p_g} \left[\log\left(\frac{p_g}{p_{data} + p_g}\right)\right] \\ &= -\log(4) + 2 JS(p_{data}||p_g)\ . \end{align} ここで JS は Jensen-Shannon divergence。JS は $p_{data} = p_g$ となる際に最小値を取るので、上式はこの時に最小となります。

以上をまとめれば、objective $\mathcal{L}$ を min max 最適化をしていけば、 確率分布が一致するという意味で $G$ は対象の data を良く模倣できるようになる、というのが理論的な根拠となります。

長くなりましたが、ようやく準備終了。以下では、一日一GAN を報告していきます!

Day 1 EBGAN


論文は https://arxiv.org/abs/1609.03126 です。

EBGAN の概要

Original GAN とやることは同じで、 objective だけを変更した論文で、違う objective でも二つの確率分布が一致することを示した論文です。 論文同様ポンと objective を紹介すると、EBGAN では以下を objective としています。

\begin{align} \mathcal{L} = \sum_{x\in p_{data}}D(x) + \sum_{z\in p_z}\max(0, m - D(G(z)))\ . \end{align}

この論文では、この objective でも original GAN の様に min max 最適化をしていけば、 $p_{data}$ と$p_g$ が一致するようになることを示しています。

理論解析

二つの確率分布$p_{data}$と$p_g$が一致するようになる証明は通常の GAN と似ているが少しだけ違う感じです。 GAN と同じように、任意の $G$ に対して最良の $D$を探し、$\mathcal{L}$ に代入しちょっと計算すると

\begin{align} \max_{D}\mathcal{L}(G, D) = m + m\int \mathbb{1}_{p_{data}(x) < p_g(x)}(p_{data}(x) - p_g(x)) dx \end{align}

となります。最良の $G$ を考えるとこれの第二項が $0$ にならなければならず、それは二つの確率が一致する時、という証明です。第二項が$0$になることの詳細は論文を。GAN と似ていますが、JS divergence に帰着させないのが違いです。

ちょっとした考察

論文を読んでいただければ分かるかと思いますが、論文の一番最初の式が上の objective なんですよね。いやぁ急にこの objective をポンと与えられても、というのが率直な感想ではないかと思います。

EBGAN は energy base ということなので、なんらかの分布と繋がってればと考えたのですが、ここ ではそれは無理と主張されています。それにもめげずにちょっと考えてみたところ、完璧な対応関係とはほど遠いですが、ある極限では GAN の objective と EBGAN の objective は Fermi分布を通して繋がっていそうと思えてきたので述べてみます。予め断っておきますが、本当に完璧な対応関係からはほど遠いです。。。

GAN の discriminator は[0, 1]の確率を返すので、ここでは Fermi 分布だと思いましょう。

\begin{align} D_{GAN}(x) = \frac{1}{1 + \exp( (E(x) -\mu)/k_BT )}\ . \end{align}

ここで $E(x) \geq 0$は energy, $\mu$ は chemical potential に対応する positive な constant、$T$ は温度に対応する定数、$k_B$は Boltzman constant。特に今回は低温($k_BT << \mu$)な Fermi 分布だとします。 この場合、$E(x) \sim 0$において

\begin{align} D_{GAN}(x) \sim 1 - \exp( (E(x) -\mu)/k_BT )\ \end{align}

$E(x) >> \mu$ に対して

\begin{align} D_{GAN}(x) \sim \exp(-E(x)/k_BT) \end{align}

と近似できます。この近似を利用して、GAN の discriminator にとって非理想的な極限(本物を偽物と思い、偽物を本物と思う)を考えると、

\begin{align} L_{GAN}& = -\sum_{x\in p_{data}}\log\left(D_{GAN}(x)\right) - \sum_{z\in p_z}\log\left(1-D_{GAN}(G_{GAN}(z))\right)\\ &\sim \frac{1}{k_BT}\left[\sum_{x\in p_{data}}E(x) + \sum_{z\in p_z}(\mu - E(G_{GAN}(z)))\right] \end{align}

となりEBGANの objective に近しいものが得られます。$max(0, *)$ がない(理論解析ではこの max が本質)などズレも大きいですが、ただポンと objective が与えられるよりかは energy という気分が伝わるかと思います!

Day 2 WGAN


論文は https://arxiv.org/abs/1701.07875 です。

WGAN 概要

WGAN も original GAN から objective を変更した論文です。

基本的に、GAN は $p_{data}$ と $p_g$ を一致させることで Generator が実際のデータを良く模倣できるという話なので、確率分布間の距離を定義して、その距離を近くすれば良いというのも自然な話です。最初に見たように、original GAN では間接的には JS divergence という距離を最小化していることに対応しています。WGAN では、Earth Mover’s distance(EMD) で距離を測り、直接これの最小化をすることで二つの分布を一致させることを狙います。EMD は扱いが面倒なので、それと等価である Wasserstein distance

\begin{align} D(p_{data},p_g) = \sup_{f \in 1-Lipschitz}\left[\mathbb{E}_{x\sim p_{data}}\left(f(x)\right)-\mathbb{E}_{x\sim p_{g}}\left(f(x)\right)\right] \end{align}

を用いて二つの確率分布を近づけるように学習を行う、というのがこの論文の話です。

理論解析

上記したように、確率分布間の距離最小化なので、original GAN のような理論解析は不要かと思います。

ちょっとした考察

この論文では、EMD の定義だとか Wasserstein distance が EMD の双対だとかポンと出てくる印象です。ここでは定義とか双対性に付いて少し考えてみようと思います。「ちょっとした」と書きましたが、結構長くなるのでご注意ください。

discrete 版 EMD

EMD の定義の気分を知ったり双対と言った部分をはっきりさせるために、以下、discrete に切って考えてみます。discrete に切るとは、$x\in R^n$をそのまま考えるのではなく、格子状に切って${x_i}$という集合を作って考えることを意味します。

まずは EMD から。論文には訳のわからない定義が書いてありますが、discrete に切ると、以下のような線形の最適化問題に帰着します。

\begin{align} &\min_{x_{ij}} \sum_{ij} d_{ij}x_{ij}\\ &s.t. x_{ij} \geq 0, \sum_{i}x_{ij} = p_{data}(x_j), \sum_{j}x_{ij} = p_g(x_{i}) \end{align}

ここに $d_{ij}$ が二点間の距離で、$x_{ij}$ が求めたい変数。このように書き下してみると、この問題は「最も効率よく$p_g$という山を$p_{data}$という山に移すためにはどうすれば良いか」という問題に対応することが分かります。というのも、$x_{ij}$が$i\rightarrow j$と輸送する量(対角成分は $i$ 地点に残す量)だと思えば、各制約は

  • 一つ目の制約$i\rightarrow j$ に輸送する量は$0$以上と言う自明な制約
  • 二つ目は $j$ 地点に輸送して欲しい量の制約
  • 三つ目は $i$ 地点から運び出すことができる量の制約

をそれぞれを表しているためです。「効率よく」というのは総移動距離$\times$輸送量の最小化を意味しています。

この線形最適化問題について、例えば、$p_{data} = p_g$ であれば、全部残せば良いので自明に最良解は $0$ となり、確率分布に少しでも差が出れば、輸送が必要になるので最良の目的関数値は $0$ より大きくなります。そのため、上記線形計画問題が二つの確率分布の一致具合を測る指標となるわけです。

EMD と Wasserstein distanceの双対性

EMD の気分が分かったので、 Wasserstein distance との双対性を確認してみます。まず、上記最適化問題は線形の最適化問題なので、最良の目的関数値を知る上ではLagrange 双対問題を考えても何ら問題ありません。Lagrange 双対の詳細はここ をご参照ください。上記最適化問題の Lagrange双対問題は

\begin{align} &\max_{\lambda,\nu} \sum_{i}\lambda_i p_g(x_i) + \sum_{j}\nu_j p_{data}(x_j)\\ &s.t.\ \ \lambda_i + \nu_j \leq d_{ij} \end{align} となります。ここに$\lambda, \nu$は双対変数を表します。以下ではこの問題を Original Problem と呼び、この問題の最適解を $z^{*}$ と書くことにします。

このOriginal Problemですが、実は以下のような$\lambda_i + \nu_i = 0$という制約を追加したような問題(Modified Problem)を考えても最良の目的関数値は同じになります。以下ではそのことを示します。

\begin{align} &\max \sum_{i}\lambda_i p_g(x_i) - \sum_{j}\lambda_j p_{data}(x_j)\\ &s.t.\ \ \lambda_i - \lambda_j \leq d_{ij} \end{align}

この Modified Problem の最適な目的関数値を $z_{mod}^*$と書くことにすれば、$z_{mod}^*\leq z^*$ と $z^*\leq z_{mod}^*$の両方が示せれば良く以下ではそれを確認します。

まずは前者ですが、これは自明。上述したように、Modified Problem は Original Problemに制約を追加して作っているため、最良の目的関数値は悪くなるため。 ということで、$z^*\leq z_{mod}^*$を頑張って示せば良い事になります。

OriginalProblem の最適解を $\lambda_i^*, \nu_j^*$と書くことにします。これらを使って、新しく次のような量を定義します。

\begin{align} \mu_i := \min_j\left(d_{ij} - \nu_j^* \right) \end{align} この$\mu$には、以下のような二つの性質があります。

  1. $\lambda_i^*\leq \mu_i\leq -\nu_i^*$
  2. $|\mu_i - \mu_j|\leq d_{ij}$

以下ではこの二つの性質を示しますが、この二つが示せた場合のご利益を先に述べます。Original Problemにおいて$p_{data}$, $p_g$が positiveであることから、一つ目の性質から、単なる値比較をすると

\begin{align} z^*\leq \sum_{i}\mu_i p_g(x_i) - \sum_{j}\mu_j p_{data}(x_j) \end{align} という不等式が得られます。よく見ると、右辺は Modified Problemの目的関数と同じ形をしており、さらに $\mu$ の二つ目の性質から、$\mu_i$は Modified Problemの制約を満たしています。このこととModified Problemが最大化問題であることを考慮すれば、 \begin{align} z^*\leq \sum_{i}\mu_i p_g(x_i) - \sum_{j}\mu_j p_{data}(x_j)\leq z_{mod}^* \end{align} という評価が得られ証明終了とななります。ということで頑張って$\mu_i$の性質をcheck できればOK。

まずは一つ目。$\lambda_i^*$がOriginalProblem の制約を満たしているので、$\lambda_i^* \leq d_{ij} - \nu_j^*$が成立。これがすべての$j$について成り立ち、 $\mu_i$の定義を思い出せば、$\lambda_i^{*}\leq \mu_i$が成立します。また$\mu_i$の定義から$\mu_i\leq d_{ii} - \nu_{i} = -\nu_i$となるので一つ目の性質は成り立ちます。 続いて二つ目。各$\mu_i = \min_k\left(d_{ik} - \nu_k^* \right)$について、右辺を最小とする$k$を$k_i$と書くことにすれば、任意の$j$に対して、$\mu_i = \min_k\left(d_{ik} - \nu_k^* \right)= d_{ik_i} - \nu_{k_i}^*\leq d_{ik_j} - \nu_{k_j}^*$が成立します。$\mu_i > \mu_j$のとき、この性質と三角不等式を使えば、

\begin{align} |\mu_i - \mu_j| \leq |d_{ik_j} - \nu_{k_j}^* - (d_{jk_j} - \nu_{k_j}^*)|\leq |d_{ik_j} - d_{jk_j}| \leq d_{ij} \end{align} となります。$\mu_i < \mu_j$の際もほぼ同様の式変形で同じ評価が得られるので、二つ目の性質が成り立ちます。 よって、Modified Probelm と Original Problem は同じ最良値を持つことが示せました。

Modified Probelm の目的関数から、これの連続版は Wasserstein distance の目的関数となることが想像できると思いますし、Modified Problem の制約から 1-Lipschitz の制約が出てくるのも見えるかと思います!

ふぅ、疲れた。。。

Day 3 LSGAN


論文は https://arxiv.org/abs/1611.04076 です。

LSGAN 概要

Original GAN から objective を変えた論文です。

そもそも Discriminator の学習では、本物に対しては $1$ を返し偽物に対しては $0$ を返せるようになれば良く、 Generator の学習では Discriminator の出力を $1$ にできるようにすれば良いです。とすると、素直に以下のような objective を minimize して行けば良いのでは、と思えてきます。

\begin{align} \mathcal{L}_D &= \mathbb{E}_{x\sim p_{data}}\left(D(x) - 1\right)^2 + \mathbb{E}_{z\sim p_{z}}\left(D(G(z))\right)^2\\ \mathcal{L}_G &= \mathbb{E}_{z\sim p_{z}}\left(D(G(z)) - 1\right)^2 \label{lsgan_naiive} \end{align} このように変更すると、sigmoid cross entropy よりも勾配損失が起こりにくくなり安定して計算がしやすくなりそう、というのがご利益です。

LSGAN論文ではもっと一般的に、 \begin{align} \mathcal{L}_D = \mathbb{E}_{x\sim p_{data}}\left(D(x) - b\right)^2 + \mathbb{E}_{z\sim p_{z}}\left(D(G(z)) - a\right)^2\\ \mathcal{L}_G = \mathbb{E}_{x\sim p_{data}}\left(D(x) - c\right)^2 + \mathbb{E}_{z\sim p_{z}}\left(D(G(z)) - c\right)^2 \end{align} とした場合に、どのような$(a, b, c)$だとうまく二つの確率分布が一致するのか、を調べています。なお、$\mathcal{L}_G$ の第一項は、最適化計算では関係ありませんが、テクニカルな理由で付けてあります。

理論解析

以下では二つの分布が一致する条件を調べますが、証明は完全に original GAN で紹介したものと同じ道筋を辿ります。original GAN では、任意の $G$ に対して最良の $D$ を計算し、それを元の objective に代入してちょっと計算すると JS divergence になって、という証明でした。LSGAN も全く同様の計算をしていけば大丈夫です。 $\mathcal{L}_D$ から $G$ を固定した場合の最良の $D$ は \begin{align} D_G = \frac{bp_{data} + ap_g}{p_{data} + p_g} \end{align} となります。これを $\mathcal{L}_G$ に代入してちょっと計算すると \begin{align} \mathcal{L}_G = \int \frac{( (b - c)(p_{data} + p_g) - (b - a)p_g)^2}{p_{data} + p_g} \end{align} という評価が得られます。この評価にたどり着く際に、先に付け加えた項が効いてきます。この評価から、例えば \begin{align} b - c = 1, b - a = 2 \end{align} の場合には \begin{align} \mathcal{L} = \chi^2_{Pearson}(p_{data} + p_g||2p_g) \end{align} と Pearson $\chi$ squared divergence の形になり、やっぱり $p_{data} = p_g$ の際に最小となることが示せます。

ちょっとした考察

LSGAN の最初に述べた objective は $b = c = 1, a = 0 $なので、条件を満たしていないように感じます。しかし、少し置き換えをして計算してやればやっぱりPearson $\chi$ squared の形になることを示せます。 論文では $(a, b, c)$として条件に合うものに注目していますが、論文推奨のパラメータ以外でももっと良いものがあるかもです!

Day 4 f-GAN


論文は https://arxiv.org/abs/1606.00709 です。

f-GAN の概要

f-GAN も original GAN から objective を変更した論文です。

GAN は二つの確率分布を同じにするのが基本です。なので、二つの確率分布間の距離を定義し、$p_{data}$ と $p_g$の距離を $0$ にするように学習するのは素直な話です。Wasserstein GAN もそうでしたね。Wasserstein GAN は EMD あるいは Wasserstein distance で考えていましたが、f-GAN では f-divergence class \begin{align} D(p_{data}||p_g) = \int p_gf\left(\frac{p_{data}}{p_{g}}\right) dx\ . \end{align} をベースとして考えています。ここに $f$ は $f(1) = 0$を満たす関数で、例えば、$f(x) = x\log(x)$ とすれば KL divergence になり、$f(x) = x^2-1$とすれば Pearson $\chi$ sqruaredとなります。この f-divergene ですが、凸解析の定理や Jensen不等式などを使用すると下から評価でき、f-GAN ではその下からの bound を良い指標と思って学習を行います。

理論解析

他の項目の理論解析とは少々異なりますが、ここでは f-divergence の下からの評価を確認したいと思います。

定理と定義

論文では凸解析の定理を使っているので、ここでは、定理の適用範囲を確認ということで定義とか定理を述べるだけ述べておこうと思います。詳しくは凸解析の参考書などをご覧ください。

論文で使用している定理は「閉真凸関数であれば、$f^{**} = f$」です。ということで、以下では閉真凸と$f^{*}$ の定義だけ述べておきます。

以下では $f: R^N \rightarrow [-\infty, \infty]$ とします。

  • 凸で、全ての $x$ について$f(x) > -\infty$ かつ $f(x) < \infty$ という $x$ が存在する関数が真凸関数。
  • 真凸かつ下半連続(任意の$x_0$ について $\liminf_{x \rightarrow x_0} f(x)\geq f(x_0)$)である関数が閉真凸関数。

$f^{*}$ の定義は以下です。

  • 真凸関数に対して $f^{*}(\xi) = \sup_x\left(\xi^Tx - f(x)\right)$ が Fenchel 双対。

今回利用している定理は最初にも述べたように「閉真凸関数であれば、$f^{**} = f$」なので、使う際には$f$ が上記した閉真凸であることの確認が必要になるのでご注意を。

f-GAN の objective

先に紹介したように、$f$ が閉真凸関数であった場合、$f = f^{**}$が成立するので \begin{align} f(x) = f^{**}(x) = \sup_{\xi}\left(x^T\xi - f^{*}(\xi)\right) \end{align} が成り立ちます。これを f-divergence の定義 \begin{align} D(p_{data}||p_g) = \int p_gf\left(\frac{p_{data}}{p_{g}}\right) dx\ . \end{align} に代入してJensen 不等式などを利用すると、 \begin{align} D(p_{data}||p_g) \geq \sup_{D}\left[ \mathbb{E}_{x \in p_{data}}\left(D(x)\right) - \mathbb{E}_{x\in p_g}\left(f^{*}(D(x))\right)\right] \end{align} という評価を得られます。右辺を f-divergence の良い指標と思い GAN のような min max 学習を行っていくのが f-GAN です。

個人的な印象ですが、motivation があり導出が一直線な気持ち良い論文です!

ちょっとした考察

実は、真凸などでも $f\geq f^{**}$ は成立するので、上記不等式自体はもう少し広いクラスでも成立はします。まぁ、真凸と閉真凸の差は$dom_f$の boundery くらいなので真凸ではあまり面白いことはできないかと思いますが。。。

Day 5 DualGAN


論文は https://arxiv.org/abs/1704.02510 です。

DualGAN の概要

GAN を利用したアプリケーション系の論文です。

やりたいことは domain 変換です。例えば、イラスト $\leftrightarrow$ 写真といった変換で、イラストを与えた時にそれっぽい写真を作成したり、その逆をしたりできるように学習を行います。 この論文の手法の特徴としては、pix2pix とは異なり、データが対にはなっていないこと。つまり、学習データとして「イラストとそれに対応する写真」が与えられるのではなく、「イラストの集合と写真の集合」がデータとして与えられている状況で上記の学習を行います。

DualGAN の学習方法

他の項目では理論解析と称していましたが、 アプリケーションよりの論文ということで学習方法という項とさせて頂きます。

学習の基本戦略は以下の図の通りです。図は論文より引用。 f:id:taka_t0m0:20170522161949j:plain 図のように、Generator と Discriminator を二組み用意します。Generator で domain 変換、Discriminator は違う domain からの絵を偽物と判定、といった感じで学習を行いますが、詳細な手順は以下の通りです。

domain U の絵に対しては、

  1. domain U から絵を draw。この絵については Discriminator B で True と判定。
  2. その絵を Generator A で domain V に変換
  3. 変換された絵を Discriminator A で Flase と判定
  4. 変換された絵を Generator B で domain U に戻し、reconstruction error を加える。

とします。domain V に対しても $A \leftrightarrow B$, $U \leftrightarrow V$ とした手続きをすれば OKです。 このように学習を行い、二つの Generator で domain が行き来できるようになることを狙います。

ちょっとした考察

具体的にどうやれば良いのか見えていませんが、auto encode する際に、$p(x^{\prime}|x) = \sum_y p(x^{\prime}|y)p(y|x)$ と思って sum を考慮したらどうなるかが気になります。DualGAN の論文でも指摘されているように、今回の変換は機械翻訳の話から inspire されています。そして機械翻訳の場合にはこれを考慮していることがあります。例えば、https://arxiv.org/abs/1606.04596 です。これを考慮できるのか否かや入れて良くなるかどうかは全く見えていませんが、今後も考察をと考えています!

Day 6 infoGAN


論文は https://arxiv.org/abs/1606.03657 です。

infoGAN の概要

GAN の学習ができれば、latent の各次元にも意味が出てくることが期待されます。例えば、MNIST であれば、latent の変数をうまく動かせば違う数字を出力できるようになる、といった具合です。ただ、どの変数をどうすればどう変わるのか、というのは予めは分からないので、そこをなんとかできないか、と考えたのが infoGAN です。つまり、latent の中である次元に指定の意味を持つように学習を行います。この論文では、それを目指すために相互情報量を正則化項として加えています。

理論解析

理論解析ではありませんが、正則化に用いる項や objective を紹介できればと思います。

上記した通り、latent の一部に明確な意味を持たせるために、相互情報量を正則化項として加えます。明確な意味を持たせるには、generator に食わせる latent を $z_{full} = (c, z)$ と分けた時、なんらかの事後確率 $P(c|G(c, z))$ が高くなりなさい、という制約のもとで学習を行えば良いことが想像されます。例えば、MNIST であれば、c を数字の種類だと思うと、$c$ と $z$ を与えて絵を generate し、その絵は $c$ である確率が高くありなさいという条件を課せば、latent の中でも$c$は数字の種類の情報を持つことが期待できます。論文では $P(c|G(c, z))$ ではなく相互情報量を正則化項として加えています。ここに相互情報量の定義は以下の通りです。 \begin{align} I(c; G(c, z)) = H( c) - H(c|G(c, z)) = -\log(p( c)) + \log(p(c|G(c, z)))\ . \end{align} 本当の $P(c|G(c, z))$ を求めるのは難しいので、DNN で近似した $Q(c|G(c, z))$ を用いると、上記相互情報量を以下のように下から抑えることができます。(詳細は論文を。) \begin{align} I(c; G(c, z)) \geq H( c) + \mathbb{E}_{c\in P( c), x \in G(c, z)}\left[Q(c| G(c, z))\right] := \mathcal{L_{m}}\ . \end{align}

infoGAN ではこの下限を用いて、 \begin{align} \mathcal{L} = \mathcal{L}_{GAN} - \lambda\mathcal{L_{m}} \end{align} という objective を利用して学習を行います。$\mathcal{L}_m$を見ればわかるように、$P( c)$から $c$ を選び、その $c$ について、$Q(c|G(c, z))$ が高くなるという制約のもと $G$ の学習が進めるので、これで目的が達成できる訳です。

ちょっとした考察

完全に的外れ感もあり、また、具体的な話ではありませんが、やはり相互情報量というとnegative sampling を考えてみたくなります。もちろんラベルが少ないうちはこんなものを考える必要はありませんが、多くなった場合などのためにうまく組み合わせられないかな、と考えています!

Day 7 CVAEGAN


論文は https://arxiv.org/abs/1703.10155 です。

CVAEGAN の概要

CVAEGAN は、GAN を利用したアプリケーションよりの論文です。

やりたいこととしては、人の写真から「表情は同じで違う人」の絵を作ることです。もう少し言えば、写真と人の名前を input として一つのベクトルを作り、そのベクトルと別な名前から対象の人で同じ表情の写真を作ることを目指します。同じ表情で違う人、だけであればCVAEでもできそうですが、VAE 系の場合、loss として pixel 毎の二乗誤差 を使うためか絵がボヤけることが多い印象があります。そこで、mean squared error で測るのではなく、Discriminator で測りましょうというのがこの論文です。

CVAEGAN の学習方法

学習方法ですが、以下の絵を見ればそのまんまです。絵は論文から引用。 f:id:taka_t0m0:20170522162007j:plain

VAE 部分については以下のような手続き。

  1. 絵とそのラベルを draw
  2. 絵とラベルから Encoder で hidden vector zを作成。
  3. z とラベルから絵を Generator が作成。作成した絵と元の絵で pixel wise mean squared error。

GAN 部分については以下のような手続き。

  1. 絵とそのラベルを draw。z も draw。
  2. z とラベルから絵を Generator が作成。その絵を Discriminator が False と判定。
  3. draw してきた絵を Discriminator が True と判定。

残りのロスは feature map loss です。

このように学習すれば、ある $(x, c)$から hidden $z$ を作り、その $z$ と $c^{\prime}$ を使って Generator にかけてやれば目的が達成できるかと思います。

ちょっとした考察

論文では supervised、つまり全ての絵にクラスが付いている状況でやっているようですが、おそらくこのモデルは semi-supervised に拡張可能かと思います。上記CVAE論文のごとくやれば行けると思います!

終わりに


以上、一日一GAN を報告しました。一応論文で急に出てきた式を考察したり、論文を読んでこういうこともできるかもを意識して書いてみましたが、いかがだったでしょうか? 考察中と述べた部分もございましたし、この辺り一緒に議論して頂ける人がいらっしゃいますと幸いです。最初にも述べましたが、それぞれ implementation はしたけれどもパラメータ調節などはしていない、という状況です。機会があればこれらの比較実験もして報告できれば、と思います! またその時まで!長々とありがとうございました。

宣伝


ABEJA では、イケててヤバいエンジニアを募集しています。GAN はしばらく流行りそうですし、今後 GAN でできることもガンガン広がっていくと思います。新しいGAN を含め最先端技術を使ってガンガン行きたい方是非お待ちしております!

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

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

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

学生向けの勉強会もしています!ガンガン応募してください!

https://abeja-innovation-meetup.connpass.com/event/57686/

参考文献

[1] Goodfellow, Ian, et al. “Generative adversarial nets.” Advances in neural information processing systems. 2014.

[2]Radford, Alec, Luke Metz, and Soumith Chintala. “Unsupervised representation learning with deep convolutional generative adversarial networks.” arXiv preprint arXiv:1511.06434 (2015).

[3] Zhao, Junbo, Michael Mathieu, and Yann LeCun. “Energy-based generative adversarial network.” arXiv preprint arXiv:1609.03126 (2016).

[4] Arjovsky, Martin, Soumith Chintala, and Léon Bottou. “Wasserstein gan.” arXiv preprint arXiv:1701.07875 (2017).

[5] Mao, Xudong, et al. “Least squares generative adversarial networks.” arXiv preprint arXiv:1611.04076 (2016).

[6] Nowozin, Sebastian, Botond Cseke, and Ryota Tomioka. “f-GAN: Training generative neural samplers using variational divergence minimization.” Advances in Neural Information Processing Systems. 2016.

[7] Yi, Zili, Hao Zhang, and Ping Tan Gong. “DualGAN: Unsupervised Dual Learning for Image-to-Image Translation.” arXiv preprint arXiv:1704.02510 (2017).

[8] Xi Chen, Yan Duan, Rein Houthooft, John Schulman, Ilya Sutskever, Pieter Abbeel. “InfoGAN: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets.” arXiv preprint arXiv:1606.03657(2016).

[9] Bao, Jianmin, et al. “CVAE-GAN: Fine-Grained Image Generation through Asymmetric Training.” arXiv preprint arXiv:1703.10155 (2017).

[10] http://www.inference.vc/are-energy-based-gans-actually-energy-based

[11] http://www.msi.co.jp/nuopt/glossary/term_c93514f7967b5ea6f4c77d569addef8d655072df.html

[12] Cheng, Yong, et al. “Semi-supervised learning for neural machine translation.” arXiv preprint arXiv:1606.04596 (2016).

[13]Kingma, Diederik P., et al. “Semi-supervised learning with deep generative models.” Advances in Neural Information Processing Systems. 2014.

機は熟した!グラフ構造に対するDeep Learning、Graph Convolutionのご紹介

はじめまして。ABEJAでResearcherをやらせていただいている白川です。

先日、化合物の物性推定をDeep Learningをつかって従来手法より300,000倍高速に処理するという論文がでました([1], [2])。この論文の手法は、Graph Convolutionというグラフ上に定義されたConvolution演算がベースとなっています。物性推定に限らず、グラフ解析全般を Deep Learning で上手にこなせるようになれば、Deep Learningのアプリケーションの幅がぐっと拡がり、さらなるイノベーションが起きそうな予感がします。 ICMLやNIPSなどの機械学習系の主要国際会議でも数年前からGraph Convolutionについての論文がちらほら出現しはじめており、とくに最近その勢いが増してきている印象があります。個人的にも最近(前から?)にわかにグラフづいていたので、この機会にグラフ上のDeep Learning、とくにGraph Convolutionについて整理して、このEmerging Technologyを盛り上げていければと思います!

グラフとして表現されるデータ

ここでいうグラフとは棒グラフとか円グラフとかのグラフではなくて、何かと何かがつながっている様を表すネットワーク構造のことを表します。 ネットワークと言うとニューラルネットワークのネットワークや通信ネットワークなどと混同しがちなので、ここではグラフ/Graphと呼ぶことにします(もっとも、ニューラルネットワーク、通信ネットワークもグラフ/ネットワークであることは間違いないですね)。 グラフはノードがエッジでむすばれることにより構成されます。エッジに向きがついているとき有向グラフといい、そうでないとき無向グラフといいます。同一ノードを結ぶエッジ(ループ)を考えたり、同じノード間に複数のエッジ(多重エッジ)が張られた状況を考える場合もあります。

f:id:daynap1204:20170425111114p:plain

グラフの典型的な例としては、ソーシャルネットワークがあります。

f:id:daynap1204:20170424171825p:plain

皆さんの身近にもグラフの例は無数にあります。それこそ「つながり」が存在するものすべてはグラフといえるかもしれません。 グラフやグラフ上でのアクティビティなどすぐに思いつくだけでも

  • Internet
  • Web
  • 電力供給網
  • 交通網
  • 購買データ(誰が何を買ったか)
  • 動画や音楽の視聴履歴
  • Webアクセス履歴
  • 論文の共著関係
  • メールのやりとり
  • サプライチェーン
  • 化合物の分子構造
  • タンパク質
  • センサーネットワーク
  • 感染症の伝播経路
  • 進化系統樹
  • 神経系
  • 脳内の各部位の関係性ネットワーク
  • 3Dオブジェクトのポリゴン表現
  • プログラムのクラス図

などなど。 こちらのWebサイトでは様々なデータをグラフとして描画していて眼に楽しいです。

www.visualcomplexity.com

この他にも画像やテキストもグラフとして捉えることができます。

f:id:daynap1204:20170426004520p:plain

画像の場合、グラフのノード=ピクセルにはRGBの色情報が載っています。テキストの場合は単語を出現順に結んだグラフ(上の遷移)となります。 テキストについては、係り受け関係でグラフをつくってもよいかもしれません。 このように画像やテキストなどの従来からDeep Learningで取り扱われてきた対象も、その上部構造としてグラフ構造を想定することができます。

後々のため、グラフに対する要件を整理しておきましょう。

  • エッジには向きがついている場合(有向)とそうでない場合(無向)がある
  • ふたつのノードの間に複数種のエッジが張られている場合がある
  • ループ(同一ノードを結ぶエッジ)がありえる
  • ノードには属性が紐付いていることがある(画像のRGBなど)
  • エッジには属性が紐付いていることがある
  • エッジには重みが紐付いていることがある、等々

Graph Convolution

グラフ上でDeep Learningを展開する技術、なかでもGraph Convolutionと呼ばれる技術が最近のトレンドになってきていると感じています。 以下では、Graph Convolutionの実現方法について、いくつかの論文を引き合いに出しながらご説明したいと思います。

どこがむずかしいのか

実は最近までGraph上のDeep Learningは一部の研究者を除くと、それほど注目されていなかったように感じます。個人的にはそれは技術的な理由によるところが大きいと考えています。 というのも、最近のDeep Learningのキーとなる構成要素はConvolutionという操作なのですが、これをGraph上に適切に定義するのが一筋縄ではいかないのです (発展途上の技術のため、Deep Learningのフレームワークのサポートが十全ではないのも理由かもしれません)。

まずは画像の場合のConvolutionを考えてみます。 Convolutionは画像の各ピクセルに対してその周辺のピクセルの値を重み付けして足し合わせる操作のことを指します。 この際、「周辺のピクセル」としては3x3とか5x5といった、注目しているピクセル中心の正方形領域(フィルターと言います)を考えることが多いです。 またConvolutionの重みは、いずれのピクセルを中心に考えるときにも同じ重みを使います。たとえば下図([3])の場合、青色の7x7の画像上を3x3のフィルターが走査することで5x5の出力が得られますが、このフィルターの3x3の各マス目には固有の重みが紐付いていて、各出力はその重みで配下の画像のピクセル値を重み付け和することで計算されます。

f:id:daynap1204:20170417175330g:plain

Graphについても画像と同様にConvolutionを定義したいのですが、画像の場合と異なり決定的な難点が立ち現れます。それはグラフの注目ノードの周りの周辺ノードの接続関係が注目ノードごとに不定形であるかもれしれないという点です。

f:id:daynap1204:20170426133624p:plain

この難点をヒューリスティックもしくは理論的なアプローチにより解決したのが今回ご紹介するGraph Convolutionになります。

Graph Convolution の構成

Graph Convolutionの構成方法を思い切って分類すると、下記の2タイプになります。

  1. Graph Fourier変換を利用した構成
  2. より直接的な構成

それぞれご説明します。

Graph Fourier 変換を利用した Graph Convolution の構成

といいつつ以下の議論は少し技術的になってしまうので、読み飛ばせるように先に結論だけまとめておきます。

  • ループや多重エッジをもたない重み付き無向グラフに対しては、理論由来のわりと正統的な方法で Graph Convolution が定義できる

もうちょっと詳細(技術的)には、

  1. ループや多重エッジを持たない重み付き無向グラフに対しては Graph Fourier 変換という、通常の意味での Fourier 変換のアナロジーを定義することができる
  2. Graph Fourier変換に対してConvolution Theoremを適用することでGraph Convolutionを定義することができる
  3. うまくパラメータ付けすることで、ノードの隣接関係を考慮したGraph Convolutionも定義できる([6])

という具合になります。Graph Fourier変換とは、グラフ上の信号(ノードに対して付与されたベクター値)に対して定義される、Fourier変換に似た操作です。 Fourier変換は波形信号を周波数成分ごとに成分分解する変換ですが、Graph Fourier変換はグラフ上で定義された信号を「ゆるやかな信号」や「急峻な信号」へ成分分解する変換となります。

もし仮にGraph Fourier変換が定義できたとすると(構成方法はあとで説明します)、ConvolutionはFourier Domainにおける要素積に相当するという定理(Convolution Theorem)

$$\displaystyle \widehat{f * g} = \widehat{f} \odot \widehat{g} \ \ \ \ ( \widehat{f}はfのFourier変換、*, \odotはそれぞれConvolution, 要素積を表す) $$

を適用することで

  1. グラフ上の信号(= 各ノードに割り振られた特徴ベクター)に対してGraph Fourier変換を施す
  2. 変換された信号に対して、何かと要素積をとる
  3. 要素積の結果に対して逆Graph Fourier変換を施す

というようにして間接的にグラフに対してConvolutionを定義することができます。 ここで2の「何か」はFourier領域において表現されたConvolutionです。

Graph Fourier 変換

以下の議論はだいぶ技術的になります。詳細については[5]を参照してください。 ループや多重エッジをもたない、重み付き無向グラフ $ \mathcal{G}=(\mathcal{V}, \mathcal{E}, \mathcal{W}) $を考えます。$ \mathcal{V}, \mathcal{E} $はそれぞれグラフ$ \mathcal{G}$のノード、エッジの集合です。$ \mathcal{W}$は $(i,j)$ 成分 $W_{i,j} \geq 0$ がエッジ $(i,j)$ の重みを表す対称行列です。$N=|\mathcal{V}|$をグラフ$\mathcal{G}$のノード数とします。

Graph Fourier変換はグラフ $\mathcal{G}$ 上の信号に対するある種の信号変換として定義されます。ここでグラフ上の信号とは、グラフの各ノードに対する $d$ 次元ベクターの割り当てのことを指します。以下の図([5]より引用)は、$d=1$ のケースの信号です。$d>1$ の場合、こういった1次元信号が $d$ 個あると思ってください。

f:id:daynap1204:20170425114703p:plain

Graph Fourier変換のしたいことは、与えられた信号を「なだらかな信号」や「急峻な信号」など、信号の緩急に応じた成分へ分解することです。 $\mathbf{x} \in \mathbb{R}^{N}$ を1次元信号とするとき、信号のなだらかさや急峻さを下記で定義します。

$$ \displaystyle \sum_{i, j} {W_{i, j} ( x_i - x_j )^2} \tag{1}. $$

上式を最小化する(つまりもっともなだらかな)信号は定数信号です。そのうちから二乗ノルムが 1 のものをとって、これを $\mathbf{u}_0$ と表すことにします。 $i=0,1,2,…,N-2$に対して帰納的に

$$ \displaystyle \mathbf{u}_{i+1} = \mathop{\arg \min}\limits_{\mathbf{x} \in \mathbb{R}^{N}, | \mathbf{x} | = 1, \mathbf{x} \perp { \mathbf{u}_0, … , \mathbf{u}_i } } \sum_{i, j} W_{i, j}(x_i - x_j) ^ 2 $$

と定めると、${ \mathbf{u}_0, …, \mathbf{u}_{N-1} }$ は $\mathbb{R}^N$ の正規直行系になります。

なお、(1)式はGraph Laplacian $L$を用いて簡易に表現することができます。 Graph Laplacianは、Graphの次数行列(Degree Matrix)、隣接行列(Adjacency Matrix)を用いて下図([4])のように定義される行列です。 次数行列は、対角成分に各ノードの次数=接続エッジ数を並べたもの、隣接行列は $(i,j)$ 成分に $i$ 番目のノードと $j$ 番目のノードにエッジが張られている場合に1を、そうでない場合に0を並べた行列です。

f:id:daynap1204:20170425120416p:plain

このGraph Laplacianを用いると(1)式は $$\displaystyle \sum_{i, j} {W_{i, j} ( x_i - x_j )^2} = 2 x^T L x $$ と表現されることが知られています。さらに${ \mathbf{u}_0, …, \mathbf{u}_{N-1} }$は$L$の固有ベクターとなることもわかります(これらは簡単な計算で確かめられます)。${ \mathbf{u}_0, …, \mathbf{u}_{N-1} }$はたとえば下記のような具合になります([5])。 f:id:daynap1204:20170427003642p:plain

さて本題のGraph Fourier変換ですが、これは $\mathbf{x}$ の基底 ${ \mathbf{u}_0, …, \mathbf{u}_{N-1} }$ に関する直行変換

$$ \displaystyle \mathbf{x} = \sum_k \alpha_k \mathbf{u}_k, \ \ \ \ \ $$

における係数ベクターへの対応 $\mathbf{x} \rightarrow \widehat{\mathbf{x}} = \mathbf{\alpha} = (\alpha_0, \alpha_1, …, \alpha_{N-1})$ として定義されます(ここで $\alpha_k = \langle \mathbf{x}, \mathbf{u}_k \rangle$ は$\mathbf{x}$と$\mathbf{u}_k$の内積です)。$U=(\mathbf{u}_0, …, \mathbf{u}_{N-1} )$ととれば、Graph Fourier変換は$\widehat{\mathbf{x}} = U^T \mathbf{x}$と簡潔に表現できます。逆Graph Fourier変換を、$\widehat{\mathbf{x}} \rightarrow \widehat{\widehat{\mathbf{x}}} = U \widehat{\mathbf{x}}$として定義します。

Graph Fourier変換、逆Graph Fourier変換が定義できたので、Graph Convolutionを定義することができます。Convolution TheoremによるとConvolutionはFourier変換後には要素積として表現されるので、 乗じる係数ベクターを$\mathbf{\theta}=(\theta_0, …, \theta_{N-1})$とすると、Graph Convolutionは

  1. グラフ上の信号(=各ノードの特徴ベクター)に対してGraph Fourier変換を施す: $\mathbf{x} \rightarrow \widehat{\mathbf{x}} = U^T \mathbf{x}$
  2. 変換された信号に対して、$\mathbf{\theta}$と要素積をとる: $\widehat{\mathbf{x}} \rightarrow \mathbf{\theta} \odot \widehat{\mathbf{x}}$
  3. 要素積の結果に対して逆Graph Fourier変換を施す: $ \mathbf{\theta} \odot \widehat{\mathbf{x}} \rightarrow U (\mathbf{\theta} \odot \widehat{\mathbf{x})}$

となります。合わせるとGraph Fourier変換により定義されるGraph Convolutionは $$ \displaystyle \mathbf{x} \rightarrow U (\mathbf{\theta} \odot (U^T {\mathbf{x}))} \tag{2} $$ という対応になります。いまは1次元信号を考えましたが、$d$次元信号($d \ge 1$)を考える場合は、各次元ごとにconvolutionをした結果を総和します。

なお、上記は比較的一般的な構成でしたが、$L$の$(\mathbf{u}_0, \mathbf{u}_1, …, \mathbf{u}_{N-1})$に対応する固有値を$\mathbf{\lambda} = (\lambda_0, \lambda_1, …, \lambda_{N-1})$として、それらを対角にならべた行列を$\Lambda = diag(\lambda_0, \lambda_1, …, \lambda_{N-1})$とすると、実対称行列の固有分解により$L=U \Lambda U^T$ですので、$\mathbf{\theta}=\mathbf{\lambda}$ととったとき(2)式は $$ \displaystyle \mathbf{x} \rightarrow U (\mathbf{\theta} \odot (U^T {\mathbf{x}))} = U \Lambda U^T \mathbf{x} = L\mathbf{x} $$ と簡単に表されます。この特殊な場合のGraph Convolutionは各ノードごとにそのノードの隣接ノードのみに依存します。同様に$L^2 \mathbf{x}, L^3 \mathbf{x}, …$は各ノードから2エッジ、3エッジ、…離れたノードにのみ依存します(局所性)。論文[6]では$L$のかわりに$L$の$K$次多項式 $g(L) = \sum_{k = 0}^K \alpha_k L^k$ ($\alpha_k$ はパラメータ)を作用させることで局所性を考慮したGraph Convolutionを提案しています。 また、論文[7]では$g(L)$の次数を一次($K=1$)までに限定したGraph Convolutionをノードの半教師あり分類問題に適用する手法を提案しています。 これらの手法については、下記の記事も参考になります。

www.inference.vc

より直接的なGraph Convolutionの構成

Graph Fourier変換由来の定義の場合、技術的な理由から、ループや多重エッジをもたない、重み付き無向グラフに限定する必要があります。 ですが、そもそもGraph Fourier変換を介してGraph Convolutionを定義する必然性はあるのでしょうか? グラフの接続関係は既知なので、これだけを使って定義する事はできないのでしょうか?(そもそもGraph Laplacian自体、接続関係のみから定義されるような代物でした)

そのような観点にたつと、より直接的なGraph Convolutionの定義をしたくなります。例によってこちらも技術的になるので先に結論を述べておきます。

  • ノードやエッジの接続関係のみから、より直接的にGraph Convolutionを定義することができる(理論由来ではない)
  • こうして定義されたGraph Convolutionには有向グラフや多重エッジ、ループなどのより複雑な構造も自然に導入できる

なお、冒頭に上げたGoogleによる化合物の物性推定へGraph Convolutionを適用した際のGraph Convolutionはこちらの直接的な定義にもとづいています。 ほかにもKnowledge Baseの解析にも適応されたりしています[8]。

どう定義するか

考え方の基本は簡単です。多種データの取り扱いに困ったときは、ひとまず線形和にしてNeural Networkに入力。これだけです。非常にDeep Learning的な考え方です。

具体的なケースで考えやすいように、[8]にしたがってKnowledge Baseを例にとって考えてみます。ここではKnowledge Baseを「AはBである(A, is-a, B)」とか「AはBを持っている(A, has-a, B)]のような3つ組が羅列された集合とします。このときKnowledge Baseは下図([8]より引用)のように、A, Bをノードとしてis-aやhas-aなどの関係性をAからBへの有向エッジとする多重有向グラフとして表現されます

f:id:daynap1204:20170426104936p:plain

このグラフの場合、下記を考慮する必要があります。

  • エッジは有向
  • ノードに属性がある

目標は、各ノード$v$にそのノードの何らかの属性を表す信号$\mathbf{x}_v \in \mathbb{R}^d$が乗っているものとして、他のノードとの接続関係をもとに Graph Convolution を定義して、それを適用することで信号を更新していくことです。最終的にそうして更新されていった信号からノードの属性予測や未知の関係性の予測を行います。先にあげたKnowledge Baseの図で言えば、赤いノード、エッジが予測により補完されるべきノードの属性、関係性になります。

[8]ではこれらを下記のように構築します。

f:id:daynap1204:20170426105040p:plain

この図の意味するところを説明します。まず、各ノード$v$ごとにその隣接ノードを接続のされ方に応じて分類します。具体的には、

  • ノード$v$に入力されたエッジか出力されたエッジか
  • そのエッジの表す関係性は何か

の2種の情報の組み合わせとして接続のされ方を区別します。これらの接続のされ方ごとにノード$v$の近傍が変わるものと考えます。 ついで、それぞれの接続のされ方に応じた近傍ごとに、近傍ノードの信号の総和(もしくは平均)をとった後、接続のされ方に応じた線形変換を施し足しこんでいきます。これにより、エッジの向きやエッジの関係性を加味した足し込みがなされますが、この足し込まれた表現をこのケースでのGraph Convolution操作として定義します。[8]で提示されている式を示しておきます(ノード $i$ の信号の更新式になっています。Graph Convolution後に非線形変換$\sigma$を施しています)。

$$ \displaystyle h^{(l+1)}_i = \sigma \left( \sum_{r \in \mathcal{R}} \sum_{j \in \mathcal{N}^r_i} \frac{1}{c_{i, r}} W^{(l)}_r h^{(l)}_j + W^{(l)}_0 h^{(l)}_i \right). $$

コンセプトが非常にわかりやすい論文なので、[8]はオススメです。考え方が素直なので、グラフがより複雑になっても素直に拡張できそうですね。

冒頭でご紹介したGoogleの化合物物性予測の論文([1],[2])は、既存のGraph Convolutionの実現法を

  1. message (近傍からどんな情報を取得するか)
  2. update (messageからどのように信号を更新するか)
  3. readout (グラフの信号から「グラフ自体」の特徴をどう読み取るか)

に機能整理した上でフレームワーク化しています(MPNN, Message Passing Neural Networks framework)。 既存研究の整理としては非常にわかりやすい論文です。 具体的なモデルや実装の詳細があまり明らかになっていないのですが、物性予測のための手法としては、今後、こういったGraph Convolution由来の 手法がベンチマークとなっていくのではないでしょうか。

おわりに

お付き合い頂きありがとうございます。 Graph Convolutionはまだまだ発展途上の技術ですが、最近になってようやく機が熟してきた感があります。 静的なグラフの解析だけでなく、RNN/LSTMと組み合わせることで動的なグラフの解析を可能にする手法([9],[10])や、テキストの係り受け関係を捉えるのにGraph Convolutionを適用した論文([11])など、Graph Convolutionはすでに要素技術として確立してきています。 本記事執筆中に開催されている、Deep Learning周辺の先端的な話題を取り扱う国際的なworkshop会議ICLR2017にも、Graph Convolution関連の論文が数本採択されていたりと、これから間違いなく来る技術と信じていますので、これからも継続的に研究していきたいと思います。

宣伝

ABEJAでは最先端技術にピピっとくる、イケててヤバいエンジニアを募集しています。 一緒にGraph Convolutionを盛り上げていきたい方、その他Emerging Technologyを開拓していきたい方等々、是非お待ちしております!

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

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

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

参考文献

[1] J. Gilmer et al., “Neural Message Passing for Quantum Chemistry”, arXiv preprint arXiv:1704.01212, 2017

[2] https://research.googleblog.com/2017/04/predicting-properties-of-molecules-with.html

[3] https://github.com/vdumoulin/conv_arithmetic

[4] https://en.wikipedia.org/wiki/Laplacian_matrix

[5] D. I. Shuman et al., “The Emerging Field of Signal Processing on Graphs: Extending High-Dimensional Data Analysis to Networks and Other Irregular Domains”, IEEE Signal Processing Magazine, 30(3):83–98, 2013

[6] B. Defferrard et al., “Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering”, NIPS2016

[7] T. N. Kipf et al., “Semi-Supervised Classification with Graph Convolutional Networks”, ICLR2017

[8] M. Schlichtkrull et al., “Modeling Relational Data with Graph Convolutional Networks”, arXiv preprint arXiv:1703.06103, 2017

[9] Y. Seo et al., “Structured Sequence Modeling with Graph Convolutional Recurrent Networks”, ICLR2017

[10] F. Manessi et al., “Dynamic Graph Convolutional Networks”, arXiv preprint arXiv:1704.06199, 2017

[11] D. Marcheggiani et al., “Encoding Sentences with Graph Convolutional Networks for Semantic Role Labeling”, arXiv preprint arXiv:1703.04826, 2017

【IoT】SORACOM AirとRaspberryPiで作るインフルエンザ注意報

f:id:hiroyuki_abeja:20170309201833j:plain

はじめに

初めまして。新卒2年目エンジニア、大田黒(オオタグロ)です。主に、ABEJA Platformの開発を担当しています。この記事では、会社で使っている技術について紹介しつつ、簡単なIoTデバイスとアプリケーションのメイキングについて書きます。

モチベーション

デバイスを作り始めた当初(2月)、周囲ではインフルエンザが流行していました。 ちょうどその時、趣味で購入したRaspberryPiとSORACOM AirのSimカード(+モデム)が眠っていたので 何か作ってみようと思い立ちました。

厚生労働省 平成28年度インフルエンザQ&Aによると空気が乾燥すると気道粘膜の防御機能が低下し、インフルエンザにかかりやすくなるとあります。

空気の乾燥を通知することができれば「加湿器を動かす」「マスクを付ける」等のアクションのきっかけを作る事ができ、インフルエンザ対策になります。今回は空気の乾燥を検知し、Slackに自動通知してくれるIoTデバイスを作ろうと思います。

作成物

全体像

f:id:hiroyuki_abeja:20170309091434p:plain Fig.1 「インフルエンザ注意報」のシステム構成

デバイス

役割:特定のデータ(今回の場合は湿度)を取得&加工し、アプリケーション側(後述)にデータ・イベント情報をおくる

今回は、下記の理由から「Raspberry Pi」というARMプロセッサ搭載の小型軽量のボードコンピューターを利用します。

  • 本体の入手性が良い
  • 利用例が多く、資料の入手性が良い
  • 価格が比較的安い
  • センサと融合しやすい
  • LinuxベースのOSが動く
  • USBポート/LANポートが存在

通信ネットワーク

役割:デバイスとアプリケーション間でのデータの橋渡しをする

「どこでも持ち運びができるシステムにしたい!」というコンセプトの元、3G/LTE回線を利用します。 今回は下記の理由からIoT向けのデータ通信サービス(MVNO)として「SORACOM Air」、モデムとして「AKA-020」を利用します。

  • SIMの入手性が良い(最近はAmazonでも手軽に購入できる)
  • SIMの有効化・各種設定が全てWebコンソール上でできる
    • クレジットカードがあればすぐに有効化できる
    • 利用状況をWebから確認できる
  • 細かな設定をAPIから制御できる
  • 閉域網の構築が容易 (今回は使いません)

アプリケーション

役割:データを受け取り、解析・保存・アクションを行う

今回は、アプリケーション構築のためにFunction-as-a-Service(FaaS)の一種である「AWS Lambda」というサービスを利用します。 FaaSを活用すると「サーバ」という管理単位を意識しなくても、アプリケーションを構築することができます。

最近はFaaSを積極的に活用した「サーバレスアーキテクチャ」による開発を行っています。 今回は、業務で使っている技術紹介も兼ねて、アプリケーション部分をAWS Lambdaを使って実装を行います。

  • インフラ環境の整備の必要がなく、開発に集中できる
    • 実行に必要なインフラの準備を行わなくて良い
    • スケーラビリティや可用性の確保を自動で行ってくれる
  • サーバを常に稼働させるより安い
    • イベントの実行回数・実行時間で課金される
  • Python / Node.js / Java等でコーディングできる

参考:サーバレスアーキテクチャという技術分野についての簡単な調査

必要なもの

部品リスト

f:id:hiroyuki_abeja:20170309200006j:plain

TABLE 1 部品表

部品名 型番・性能 入手先
RaspberryPi 3 ModelB 秋月電子 通販コード(M-10414)
スイッチングACアダプタ5V2.5A AD-B50P250 秋月電子 通販コード(M-10507)
ブレッドボード EIC-801 秋月電子 通販コード(P-00315)
3G USBドングル AK-020 SORACOM スターターキット Amazon
湿度センサ TDK CHS-GSS 千石電商 管理コード6A4A-HRFF
A/D変換器 MCP3204 秋月電子 通販コード(I-00239)
高精度温度計 LM35DZ 秋月電子 通販コード(I-00116)
ブレッドボード・ジャンパーワイヤ 秋月電子 通販コード(P-00288)
ブレッドボード・ジャンパーワイヤ(オス-メス) 秋月電子 通販コード(P-03472)

各パーツの役割について

パーツを「ブラックボックス」のまま使いたくない方のために、代表的なパーツの役割・諸元について簡単に説明します。

A/D 変換器(MCP3208)

一般的にセンサとデバイスを接続する時は、センサで計測される温度・湿度といった「物理量」を電圧といった「電気信号」に変換して扱う事が多いです。一方で、電気信号(アナログ)は、デジタル論理で動くコンピューターでは直接扱う事ができません。従って、センサから得た電圧(連続値)をコンピューターで扱えるデジタル値に変換するための「変換器」が必要となります。この役割を持つのがA/D変換器(Analog to Digital Converter)と呼ばれるものです。

f:id:hiroyuki_abeja:20170328005932p:plain:w200

Fig.2 MCP3208のピン配置 (転載元:データシート)

※後述の作成過程において必要になります。印刷しておくと便利です。

今回、A/D変換器としてMCP3208を利用します。MCP3208には以下のような特徴があります。

  • 入力:8本
    • 図中のPin 0〜7を使う事で8本の電圧信号をA/D変換できる
  • 分解能:12bit (4096段階)
    • 基準電圧を用いて4096段階で入力された電圧を表現できる
  • 変換速度:100ksps (電源5V時)
    • 1秒間に100000回のA/D変換ができる (1sps = 1 sample per sec)
  • シリアル・ペリフェラル・インタフェース(SPI)を利用
    • 4本の通信用信号線(図中のPin10,11,12,13)を使い、デジタル信号でデータのやり取りができる
    • 一般的な通信手法であるため、幅広いデバイスと接続できる

温度センサ(LM35DZ)

f:id:hiroyuki_abeja:20170403042803p:plain:w300

Fig.3 LM35DZのピン配置 (転載元:データシート)

今回は温度センサとして、TI社LM35DZを利用します。電源を供給するだけで、温度に比例した電圧が中央のピンから出力される仕組みになっています。従って、前述のA/D変換器に直接接続し、デバイス側で読み込む事ができます。

データシートによると出力電圧と温度の関係は、0 [mv] +10[mV/℃]とあるため、10℃時に100[mV] 、20℃時に200[mV]の電圧が出力されます。 この関係を利用することで、デバイス内部で読み込んだ電圧から温度を計算可能です。

湿度センサ(CHS-GSS)

f:id:hiroyuki_abeja:20170403043626p:plain:w200

Fig.4 CHS-GSSのピン配置 (転載元:データシート)

今回は湿度センサとしてTDK社のCHS-GSSを利用します。このセンサも前述の温度センサと同様で、電源を供給するだけで湿度に比例した電圧が出力される仕組みになっています。湿度センサも同様、A/D変換器に直接接続できます。データシートによると、100%(RH)時に1.0[V]が出力されます。この関係を利用することで、デバイス内部で読み込んだ電圧から湿度を計算可能です。

事前準備

今回作成を進めるにあたり、下記のものが必要になります。

  • SORACOM ユーザーコンソール用のアカウント
  • AWS アカウント
  • HDMIケーブル
  • LANケーブル
  • USBキーボード
  • AWS コマンドラインインターフェイス(aws-cli)
  • Slack API トークン
  • SD <=> MicroSD変換機 (任意)
  • SSHクライアント(任意)

作り方

f:id:hiroyuki_abeja:20170310015011p:plain

Fig.5 作成回路の全体感

Raspberry Piのセットアップ

MicroSDカード作成

f:id:hiroyuki_abeja:20170403045229p:plain:w300

Fig.6 Raspbianのダウンロード画面

Download Raspbian for Raspberry Piより、RaspberryPi用のイメージをダウンロードし、OSイメージをMicroSDカードに書き込んでください。

※環境によってSDカードの作成手順が異なります。Macの方は下記ページを参考にしてください。

参考:Mac OS X で Raspberry PiのOSイメージを焼く

動作チェック

SDカード作成後、RaspberryPiにHDMIケーブル・USBキーボード・LANケーブルを接続し、電源アダプタを接続してください。 電源アダプタを接続すると、RaspberryPi上の緑色LEDが点灯します。

f:id:hiroyuki_abeja:20170328000947j:plain

Fig.7 RaspberryPi 動作時の様子

下記のような画面が出れば成功です。

f:id:hiroyuki_abeja:20170403030421j:plain

Fig.8 RaspberryPi OSブート時の画面

SPI通信の有効化

RaspberryPiをAD変換器に接続するにあたって、SPI通信用のカーネルモジュールを有効化します。 下記のコマンドから、raspi-config(RaspberryPiの設定ツール)を開き、「7 Advanced Options」→「A6 SPI」へと進み、SPI通信を有効化してください。

$ sudo raspi-config

f:id:hiroyuki_abeja:20170403045327p:plain:w300

Fig.9 raspi-config起動の様子

回路作成

この章では、実際に回路構築を行います。配線ミスはデバイスやパーツの故障に繋がるため、慎重に作業を行ってください。

RaspberryPiとA/D変換器の接続

接続対応表
RaspberryPi側端子名 RaspberryPi側ピン番号 MCP3208側端子名 MCP3208側ピン番号 説明
DC Power 5V #02 Vdd #16 電源供給用
DC Power 5V #02 Vref #15 基準電圧供給用
Ground #06 DGND #09 電源供給用
Ground #06 ANGD #14 電源供給用
SPI_MOSI #19 Din #11 SPI通信データ転送用
SPI_MISO #21 Dout #12 SPI通信データ転送用
SPI_SCLK #23 CLK #13 SPI通信クロック供給用
SPI_CE0_N #24 CS/SHDN #10 SPI通信スレーブ選択用

表中のピン番号と物理的なピン配置の関係は下記の通りです。

f:id:hiroyuki_abeja:20170403030837p:plain:w400

Fig.10 Raspberry Pi のピン配置

上記の図は、公式サイトより転載しました。 MCP3208のピン配置はFig.2をご確認ください。

実際の配線方法

実際の配線には、部品表のブレッドボードとジャンパーワイヤを使って接続を行います。 接続対応表・下記の実体配線図を接続を行ってください。

f:id:hiroyuki_abeja:20170403024229p:plain

Fig.11 実体配線図

下記の写真は、配線完了時の様子です。

f:id:hiroyuki_abeja:20170328001105j:plain

Fig.12 実際の接続後の様子

A/D変換器とセンサの接続

今回はMCP3208のCH0(チャンネル0)に温度センサ、CH1(チャンネル1)に湿度センサを接続します。

接続対応表
温度センサ

TABLE MCP3208 <=> LM35DZ 接続対応表

MCP3208側端子名 MCP3208側ピン番号 LM35DZ側端子名 LM35DZ側ピン番号 説明
Vdd #16 +Vs #01 電源供給用
CH0 #01 Vout #02 センサー出力取り込み用
DGND #09 GND #03 電源供給用
湿度センサ

TABLE MCP3208 <=> CHS-GSS 接続対応表

MCP3208側端子名 MCP3208側ピン番号 CHS-GSS側端子名 CHS-GSS側ピン番号 説明
CH1 #02 Vout #01 センサー出力取り込み用
DGND #09 GND #02 電源供給用
Vdd #16 +Vs #03 電源供給用
実際の配線方法

RaspberryPiとMCP3208の時と同様、ジャンパーワイヤを使って接続を行ってください。 CHS-GSSは部品表面にピン配置が書いてありますが、LM35DZは書いてありません。 接続ミスしないように注意してください。

f:id:hiroyuki_abeja:20170403035328p:plain

Fig.13 実体配線図

f:id:hiroyuki_abeja:20170403041541j:plain

Fig.14 実際の接続後の様子

SORACOM Airの準備

大まかな流れは下記のとおりです。

  1. SIMのアクティベーション
  2. SIMのモデムへのマウント
  3. RaspberryPi側の設定

SIMのアクティベーション

f:id:hiroyuki_abeja:20170328023103p:plain Fig.15 SIM登録画面

「SORACOM ユーザーコンソール」にログイン後、「➕SIM登録」を押してください。 上の様な画面が出てくるので、そこに必要な情報を記載してください。 (SIMカードのマウントされているプラスチックの裏側に、IMSI等が記述されています)

SIMカードのマウント

f:id:hiroyuki_abeja:20170328021818j:plain

Fig.16 SIMカードマウント後の様子

アクティベーションが完了したので、SIMカードを利用することができます。このSIMカードをモデムに挿すことで利用ができるのですが、サイズが異なるため、直接挿すことができません。 スターターキットにはサイズ変換用アダプタが同封されています。このアダプタを使い、写真のようにSIMカードをマウントしてください。

PPP接続準備

SORACOM AirのSIMを使いモデムからPPP接続を行うために、wvdialを導入する必要があります。 wvdialはPPP(Point-to-Point Protocol)のダイアラであり、これを使うことで楽にRaspberryPiからPPP発信が可能です。 下記のコマンドからwvdialをインストール可能です。(ejectは後述のスクリプトで利用します)

$ sudo apt-get update
$ sudo apt-get install -y eject wvdial

wvdialのための設定ファイルを生成します。

接続用シェルスクリプトの用意

下記のスクリプトを任意の場所に配置してください。 自動起動スクリプト(/etc/rc.local)から呼び出すため、実行権限を付ける必要性があります。

スクリプトパス例:/home/pi/connect_with_soracom.sh

接続テスト

f:id:hiroyuki_abeja:20170328025547p:plain Fig.17 PPPリンクが確立している様子 (SSH経由で確認)

上記のシェルスクリプトを実行し、ifconfigを実行するとppp0という項目が確認できると思います。 ppp0がしばらく待っても生成されない場合、何かしらの問題が発生している可能性が高いです。

プログラミング(アプリケーションサイド)

この章では、AWS Lambda上で動かすコード(Lambdaファンクション)の作成方法・API経由で呼び出す方法について説明します。 ここからの作業はAWSのWebコンソールからでも可能ですが、最近業務で使っていて便利だったAWS コマンドラインインターフェイス(aws-cli)を使って説明を行います。

CLIの初期設定

$ aws configure

上記のコマンドを実行すると以下4項目が順番に聞かれるので、適切に入力してください。 AccessKeyId・SecretAccessKeyは、AWS Identity and Access Management (IAM)から取得する事ができます。

AWS Access Key ID [None]: xxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxx
Default region name [None]: us-east-1 (ご自身の環境にあわせてください)
Default output format [None]: json

Lambdaを使ったアプリケーションの構築

これから実際にAWS コマンドラインインターフェイスを使ったアプリケーション構築の説明にうつります。 作業手順に登場するREGION,ACCOUNT_IDは、各自の環境に置き換える必要性があります。

Lambdaファンクションの準備

下記のスクリプトは、今回のアプリケーションの中核を担うAWS Lambda上で動くファンクションです。Lambda上のランタイム Python 2.7で動くことを想定して書いています。

スクリプトパス例:<WORK_DIR>/lambda_function.py

※ スクリプト中のSlack用APIトークンですが、事前に取得したAPIトークンに書き換える必要性があります。

本スクリプトでは、SlackClientというライブラリを利用しています。 しかしLambda上ではpipインストールが利用できないので、サードパーティ製ライブラリを直接導入する事ができません。今回は、Lambdaファンクションを必要なライブラリと一緒にZipで圧縮し、Lambdaに登録します。

Lambdaファンクション登録

ここまででLambdaファンクションの準備ができました。次にLambdaファンクションの登録を行います。 大まかな流れは下記のとおりです。

  1. Lambda実行用ロール作成
  2. Lambdaファンクション登録

API Gatewayの設定

前述のLambdaファンクションをAPI経由で呼び出すために、API Gatewayの設定を行います。 今回は下記の前提条件のもと、設定を行います。

  • デバイス側から送信したデータは、POSTメソッドを使いJSON形式でデータを受け取る
  • 今回は認証はもうけない
  • ステージ名はdevとする

大まかな流れは下記のとおりです。

  1. API作成&定義
  2. APIとLambdaの連携・権限付与
  3. APIのレスポンス設定
  4. APIのデプロイ

ここまででAPI Gatewayの設定は完了です。 API Gatewayで作成したエンドポイントは、https://<REST_API_ID>.execute-api.<REGION>.amazonaws.com/<STAGE>/<ENDPOINT>の形でアクセスが可能になっています。

今回の例では、https://ou89mo95s6.execute-api.us-east-1.amazonaws.com/dev/fluReportとなります。

プログラミング(デバイスサイド)

ここまででアプリケーションサイドの構築が終わりました。 ここからは、アプリケーションにデータを送るデバイスサイドのプログラミングを行います。

PiPiperの導入

今回、RaspberryPiからA/D変換器にSPI通信でアクセスする為にPiPiperというライブラリを利用します。 基本的には下記のコマンドで準備が可能です。

$ sudo apt-get install ruby ruby1.9.1-dev libssl-dev
$ sudo gem install pi_piper

計測・データ送信用スクリプト

下記のスクリプトは、RaspberryPi上で動く計測(A/D変換)・データ送信用のスクリプトです。 大まかな処理の流れは以下のとおりです。

  1. SPI通信でA/D変換器と通信し、電圧データを取得する
  2. 電圧データから温度[℃]・湿度[%]を計算する
  3. API Gatewayに送る用のオブジェクトを生成する
  4. API Gatewayにjson形式でデータ送信
  5. 一定時間待つ
  6. 1へ戻る

このスクリプトを実行する事で、定期的に温度・湿度データがアプリケーション側へと送信されます。 スクリプト内の送信先エンドポイントは、前のステップで作成したエンドポイントを指定する必要性があります。

スクリプトパス例:/home/pi/sender.rb

※ このスクリプトはSPI通信を行うため、管理者権限が必要です。sudoを付けて実行する必要性があります。

自動起動化

ここまでで最低限のアプリケーションの用意が終わりました。 最後にPPP接続自動化と計測・データ送信用スクリプトを自動起動する設定を行います。

/etc/rc.localに下記の行を追加してください。

次から電源投入をする事で、自動でAPI Gatewayに計測データが転送されるようになります。

動作風景

f:id:hiroyuki_abeja:20170309201833j:plain

Fig.18 オフィス内にデバイスを設置したときの様子

作成したデバイスを早速で社内でランニングさせました。 上記の図は、デバイス設置時の様子です。

f:id:hiroyuki_abeja:20170309202234p:plain:w200

Fig.19 Slackでの通知の様子

この日は加湿器が電源OFFの状態で暖房が動いていたため極端に湿度が低く、電源を付けたら早速アラートが飛んでいきました。 社内の様子を見ていると、アラートを見た社員が加湿器の電源を入れる・予防マスクを付けるといった狙い通りアクションを実施していることが確認できました。今回のIoTデバイスの運用によって社内のインフルエンザに対する危機意識向上に貢献できたと思います。

記録したデータを可視化すると、以下のような感じになりました。 手元の温度計・湿度計と比較したところほぼ一致していました。計測の妥当性は問題なさそうです。

f:id:hiroyuki_abeja:20170309203905p:plain

Fig.20 温度変化プロット

f:id:hiroyuki_abeja:20170309203916p:plain

Fig.21 湿度変化プロット

感想

業務で得た知識を活かせている

今回使っている「SORACOM Airを用いたデータ通信」や「サーバレスアーキテクチャ」ですが、入社後に業務を通して得た知識であり、業務ではもちろん、趣味の開発でも積極的に利用しています。今まで、Webアプリケーションを構築する時は、

  1. 最初に仮想(物理)サーバを用意
  2. プロビジョニングを実施
  3. ファイアウォールを設定
  4. Nginx等のミドルウェアをインストール
  5. アプリケーションをフレームワークを使って記述
  6. デプロイ
  7. 必要に応じてロードバランサーを配置

といった作業を行っていました。今では、

  1. 必要なインターフェースを定義(API Gateway)・認証設定
  2. 必要なロジックを記述(AWS Lambda)
  3. デプロイ

上記のプロセスで済むので、機能の高速な実装・仮説検証のイテレーションが回るようになっています。

技術的な成長を感じた

実は私は2015年にABEJAに入社したのですが、入社当時はアプリケーション開発に関する知識・経験がなく、開発に必要な知識のインプットから始まりました。(バッググラウンドが生体医工学・電気電子工学だったので….) 1年目は、デバイスからプラットフォームまで開発・運用の実践経験をひたすら積みました。ブログ執筆を通して入社時当時の自分を思い出し、成長できたなと感じる事ができました。

自社プロダクトのバリューの再認識

ABEJA Platformは、Deep Learning を活用し、様々な大量データの取得・蓄積・学習・解析・出力・フィードバックを行うことができる先進的なプラットフォームです。

今回は、社内技術の中身を記事にする都合上、AWS Lambda等を利用したアプリケーション構築を行いましたが デバイスとABEJA Platformを連携させれば、もっと楽にアプリケーション構築が可能です。今回の例でいうと、デバイスと通信ネットワークを用意するだけで、データの取得・蓄積・データの解析・フィードバック(Slackへの通知)をABEJA Platformをおまかせできます。

本記事ではデバイス管理・認証の仕組み・データ管理の仕組みを一切実装していませんが、実際に大量のIoTデバイスの繋がるアプリケーション部分を作るためにはこれらの仕組みを実装する必要があり大変です。これらの仕組みもABEJA Platformにおまかせできるため、エンジニアは本質的なバリューに集中できます。

今回は、デバイスからアプリケーションまでの一通りの実装を通して自社で開発しているプラットフォームのバリューについて改めて理解できました。

最後に

IoT・ビッグデータ・AIをキーワードにバリバリ活躍したい方!! ご興味のある方は以下のリンクより、Wantedlyページをご確認ください。

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

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

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