スマートロックの セサミ mini を便利に使っています。(前回の記事) 物理的に鍵を使う必要がなく完全にワイヤレスで済みますし、施錠操作もオートロックのおかげで不要となりました。

非常に気に入っているのですが、セサミ mini のオートロックはドアの開閉と連動しているわけではなく時限式となっています。もたもたしているとドアを閉じる前に鍵が締まってしまうこともあります。時間を長めに設定すると、今度はロックが掛かるまで待つ場合に待ち時間が長くなります。

そこで、セサミの API を使ってドアの開け閉めと連動してロックが掛かる仕組みを作ってみました。本来なら、マグネット式のセンサーを使うのが最も簡単で確実だと思います。今回は手持ちの機材だけで何とかしようと思い、折角なので Web Camera で識別させてみました。


● Sesame API

CANDY HOUSE Developer Reference

さまざまなサイトに解説がありますので参考にさせていただきました。

Sesame API Version 3 のチュートリアル
APIキー取得方法とセサミIDの確認方法

API Key を取得したら Web 呼び出しで操作することができます。さらに鍵自体の Lock / Unlock 操作には、SmartLock 本体を識別するための Device ID が必要です。Device ID は API を通じて一覧から取得することもできますし、上の解説記事を見ると Web のダッシュボードで確認することもできるようです。

以下 Python 3.x を使っています。Device 一覧の取得です。

import requests
import json

API_URL= 'https://api.candyhouse.co/public/'
API_KEY= '<API_KEY>'

result= requests.get( API_URL+'sesames/', headers={ 'Authorization': API_KEY } )
device_list= json.loads( result.text )

for device in device_list:
    print( device['device_id'] )

毎回 Device ID を取得すると時間がかかるので file にキャッシュしておきます。これでサーバーに問い合わせるのは最初の一度だけになります。

import os
import requests
import json

API_KEY= '<API_KEY>'

class SesameAPI:
    CACHE_FILE= 'cache_file.txt'
    API_URL= 'https://api.candyhouse.co/public/'

    def __init__( self ):
        self.load_cache()

    def load_cache( self ):
        if os.path.exists( self.CACHE_FILE ):
            with open( self.CACHE_FILE, 'r' ) as fi:
                self.dev_list= json.loads( fi.read() )
        else:
            result= requests.get( self.API_URL+'sesames/', headers={ 'Authorization': API_KEY } )
            self.dev_list= json.loads( result.text )
            with open( self.CACHE_FILE, 'w' ) as fo:
                fo.write( json.dumps( self.dev_list ) )

    def get_device_id( self ):
        return  self.dev_list[0][ 'device_id' ]


print( SesameAPI().get_device_id() )

Device ID が判明したので、あとは状態の確認や Lock/Unlock コマンドの送信ができます。

class SesameAPI:
    ~

    def get_status( self ):
        result= requests.get( self.API_URL+'sesame/'+self.get_device_id(), headers={ 'Authorization': API_KEY } )
        status= json.loads( result.text )
        return  status['locked']

    def send_command( self, command ):
        requests.post( self.API_URL+'sesame/'+self.get_device_id(),
            headers={ 'Authorization': API_KEY, 'Content-type': 'application/json' },
            data=json.dumps( { 'command': command } ) )


sesame= SesameAPI()

# 状態の取得
print( sesame.get_status() )

# 施錠、解錠
sesame.send_command( 'lock' )
# sesame.send_command( 'unlock' )

Lock/Unlock コマンドの実行は時間がかかるので、結果を確認したい場合はあらためて問い合わせる必要があります。問い合わせには Command 実行時に得られる Task ID を使います。

↓成功か失敗を確実に返す (待つ) 場合。

    def get_task_result( self, task_id ):
        while True:
            time.sleep( 5.0 )
            result= requests.get( self.API_URL+'action-result?task_id='+task_id, headers={ 'Authorization': API_KEY } )
            status= json.loads( result.text )
            if status['status'] == 'terminated':
                if 'successful' in status:
                    return  status['successful']
                return  False

    def send_command( self, command ):
        result= requests.post( self.API_URL+'sesame/'+self.get_device_id(),
            headers={ 'Authorization': API_KEY, 'Content-type': 'application/json' },
            data=json.dumps( { 'command': command } ) )
        task= json.loads( result.text )
        task_id= task[ 'task_id' ]
        return  self.get_task_result( task_id )


sesame= SesameAPI()
print( sesame.send_command( 'lock' ) )


●画像による判定

機械学習 (Deep Learning) を使っています。モデルは非常に簡単な CNN で PyTorch を使いました。

USB の Web Camera を使ってドアの画像を撮影します。かなり少ないですが 640x480 で 1500枚ほど。様々な時間帯で人が写った出入り時の画像も含めます。先に完全に閉じている状態とそれ以外で分類しておきます。

Web Camera の撮影画像は少々暗く、夕方以降は見えないくらい真っ暗な写真が撮れることがあります。ドアを開けた方が明るいので、暗すぎる場合は閉まっているとみなしています。性能が良い最近のスマホの方が明るく写るので、スマホを Web Camera の代わりに使うともっと精度が上がるかもしれません。

学習は 128x128 dot にしましたがもっと小さくても良いと思います。Windows の GeForce GTX1070 でおよそ 15分くらいです。(BatchSize 32, Epoch 1000 の場合)

class Model_Sesame( nn.Module ):

    def __init__( self ):
        super().__init__()
        self.c0= nn.Conv2d( 3, 32, 5 )
        self.c1= nn.Conv2d( 32, 32, 5 )
        self.fc0= nn.Linear( 32*6*6, 128 )
        self.fc1= nn.Linear( 128, 64 )
        self.fc2= nn.Linear( 64, 2 )
        self.drop0= nn.Dropout( 0.25 )
        self.drop1= nn.Dropout( 0.25 )
        self.drop2= nn.Dropout( 0.5 )
        self.drop3= nn.Dropout( 0.5 )

    def forward( self, x ):
        x= F.relu( self.c0( x ) )
        x= F.max_pool2d( x, (4, 4) )
        x= self.drop0( x )
        x= F.relu( self.c1( x ) )
        x= F.max_pool2d( x, (4, 4) )
        x= self.drop1( x )
        x= x.view( x.size(0), -1 )
        x= F.relu( self.fc0( x ) )
        x= self.drop2( x )
        x= F.relu( self.fc1( x ) )
        x= self.drop3( x )
        x= self.fc2( x )
        return  x

開け閉めの判定部分では、OpenCV でキャプチャした画像をおよそ 0.5秒に一度推論に通しています。結果は完全に閉まっているかどうかの二択です。

    def main_loop( self ):
        cap= cv2.VideoCapture( 0 )
        PREDICT_TIME= 0.5
        SLEEP_TIME= 0.2
        STEP_PREDICT= int(PREDICT_TIME / SLEEP_TIME)
        counter= 0
        while( True ):
            ret,image= cap.read()
            cv2.imshow( 'preview', image )
            counter+= 1
            if counter > STEP_PREDICT:
                self.predict( image ) # 判定
                counter= 0
            time.sleep( SLEEP_TIME )
            key= cv2.waitKey(1) & 0xff
            if key == ord('q') or key == ord('\x1b'):
                break
        cap.release()
        cv2.destroyAllWindows()

データが少なく変化も乏しいので過学習している可能性はありますが、開閉のタイミングはそれっぽく取れているようです。あとは状態が変化したタイミングを捉えて、完全に閉まったときだけ lock command を送ります。

長時間走らせていると稀に誤判定が混ざることがあります。施錠されている状態をさらに施錠し直すだけなので、誤判定が紛れ込んでも特に問題はないです。ただそのたびに API が作動したという通知が来てしまうので少々気になります。

そこで確率の差が小さいあやふやな判定はある程度取り除くことにしました。さらに閉じたときは 4回以上連続、開けたときは 2回以上連続で同じ判定が続いたときだけ状態を反映させるようにしています。

    def predict( self, image ):
        size= IMAGE_SIZE # 64
        y,x,ch= image.shape
        ns= min(x,y)
        bx= (x - ns)//2
        by= (y - ns)//2
        crop_image= image[bx:bx+ns,by:by+ns]
        resize_image= cv2.resize( crop_image, (size,size) )
        ndata= np.zeros( (ch,size,size), dtype=np.float32 )
        for iy in (range(size)):
            for ix in (range(size)):
                for ic in range(ch):
                    ndata[ic,iy,ix]= resize_image[iy,ix,ic]
        fdata= ndata * (1.0/255.0)
        fdata= fdata.reshape( (1,ch,size,size) )
        x_data= torch.tensor( fdata, dtype=torch.float32, device=self.device )
        outputs_c= self.model( x_data ).to( 'cpu' ).detach().numpy() # 推論
        oy= outputs_c[0]
        self.sum= (self.sum + oy) * 0.5
        result= np.argmax(self.sum) # 0=Open, 1=Close

        open_state= self.open_state
        if result == self.prev_result:
            self.lock_count+= 1
            if result == 0:
                if self.lock_count >= 4:
                    self.lock_count= 0
                    open_state= 0
            elif result == 1:
                if self.lock_count >= 2:
                    self.lock_count= 0
                    open_state= 1
        else:
            self.lock_count= 0
            self.capture( image ) # 状態変化時の画像の保存
        self.prev_result= result

        if open_state != self.open_state:
            if open_state == 0:
                self.send_command( 'lock' ) # コマンドの送信
        self.open_state= open_state

Web Camera を設置して main_loop() を回します。これでドアを閉じたときに連動してセサミで施錠出来るようになりました。誤判定もなくなり明るい時間帯は安定して動いています。

まだまだデータの蓄積や改良が必要なので、判定結果が変化した時の画像を次の学習のために保存しています。最適化したら Raspberry Pi や Jetson Nano あたりで動かしたいと思っています。


●まとめ

API を利用して セサミ mini でもドアの動きに連動したオートロックを実現することができました。API が使えるのは非常に大きなメリットです。無い機能も簡単に作れますし、他にも様々な応用ができそうです。

最初にも書いたのですが、実用性を考えるなら普通にマグネットスイッチなどのセンサーを使った方が良いと思います。Web Camera を使う利点としては防犯カメラを兼用出来ることくらいでしょうか。もともと学習用データ収集のつもりだったのですが、ドアの開閉に連動して出入りした人の写真がきれいに残るようになりました。


関連エントリ
セサミmini、スマートロックを使って1年
Android UserLAnd で PyTorch を使う。C++ API
RADEON (ROCm) で PyTorch を使う。C++ API
Jetson Nano で TensorFlow の C 言語 API を使う


通勤で定期券の代わりになったり財布の代わりになったりスマートフォンの役割が増えています。全く同じように、スマートロックを使うとスマートフォンを家の鍵として使えるようになります。ドアの前でいちいち鍵(物理鍵)を取り出す必要がなくなり、スマートフォン一つで事足ります。

暗闇で鍵穴を探さなくて良くなりますし、オートロックがあるのでいちいち鍵を締める必要もありません。開閉は記録が残るし通知も来ます。どこでも状態を確認できるので、施錠し忘れを気にすることもなくなります。

スマートロックの セサミmini を使い始めてからちょうど 1年になりました。いくつかトラブルにも遭遇しましたが、自分にとってはなくてはならないものとなっています。

CANDY HOUSE: SESAME mini

スマートロックにも様々なものがありますが、自分で後付できるタイプが一番お手軽です。サムターンと呼ばれる一般的な鍵タイプであれば、ドアのつまみに重なるように本体を取り付けるだけで使えるようになります。設置も両面テープを使うので、工事も特殊な工具も要りません。

スマートロックを取り付けてもこれまで通りの使い方もできます。中からは手で鍵を開けられますし、外から普通に物理鍵も使えるので操作方法は好みで組み合わせられます。


●スマートロック セサミmini でできること一覧

・スマートフォンを鍵にする
・AppleWatch を鍵にする
・オートロック
・合鍵を簡単に作ることが出来る
・解錠&施錠ログの記録と確認
・手ぶら解錠、ノック解錠(iPhone のみ)

さらに別売りの WiFi アクセスポイントを使えば

・どこにいても鍵の操作ができる
・施錠状態をどこにいてもモニターできる
・解錠&施錠が行われたときのリアルタイム通知
・音声を使った鍵の開け締め
・スマートスピーカーとか他のデバイスとの連携

など


●スマートフォンを鍵として使う

初期設定が終われば Sesame アプリを入れた iPhone や Android 端末を鍵として使うことができます。ドアの前でアプリを開き、状態が表示されたらタップするだけです。すぐドアのつまみ(サムターン)が回り始めます。

Sesame
↑この画面をタッチするだけで施錠&解錠ができる。

仕組みは Bluetooth による直結です。ドアのそばでアプリを開くと Bluetooth でセサミ本体を検索し繋がります。接続も予想よりも早く、iPhone X の場合 2~3秒ですぐ操作できる状態になります。動作も安定しており iPhone X を使う上では今の所トラブルには遭遇したことがありません。

ただ上に書いたとおり操作にはアプリを使います。アプリを開くにはスマートフォンのロックも解除しなければならないので、若干の操作と時間がかかります。オフィスビルにあるようなタッチだけで即座に反応するカードキーを想像してしまうと少々ものたりないかもしれません。鍵を開けるまでの時間はスマートロックに置き換えても短くならないので注意です。

まだ試したことがないのですが、セサミ mini には「手ぶら解錠」や「ノック解錠」という機能があるので、工夫すればもっと早く開けられる方法もあるかもしれません。

スマートロックで便利になったところはいろいろあります。例えばアプリを入れるだけで他のスマートフォンからも操作できるようになりますし、ユーザーを追加して複数人での鍵のシェアもできます。自分で好きなだけ合鍵を作ることができるわけです。

個人的に一番魅力を感じているのは持ち物を減らせることです。鍵をすぐ取り出せるところに携帯しなくて良くなりますし、スマートフォンか Apple Watch さえ忘れなければ外出してもとりあえずなんとかなります。(ただし保険として物理鍵の携帯も忘れずに)

またオートロックも非常に便利な機能です。


●かなり便利なオートロック

セサミ mini にはオートロック機能があります。鍵を開けてから指定時間経過すると自動的に鍵を締めてくれます。例えば 10秒に設定しておけば、解錠してから 10秒後に自動的に鍵がかかります。必要な操作は「鍵を開けること」だけになります。

◎外に出る場合
・手でサムターンを回して解錠
・オートロックで施錠

◎中に入る場合
・スマートフォンや Apple Watch で解錠
・オートロックで施錠

なお、セサミ mini にはドアの開閉センサーがついていないため、ドアが本当に閉まっているどうかに関わらずロックしようとします。例えば来客対応時など、ドアが開いたままでも一定時間経てばサムターンが回り始めます。

ちなみに同様のスマートロック製品である Qrio にはドアの開閉センサーがついています。こちらは開けっ放しでオートロックが発動することもなく、実際にドアを閉めたタイミングでロックがかかります。その代わり Qrio の実売価格は セサミ mini の倍くらいするので、このあたりの機能差も価格に反映されているのかもしれません。

Qrio Lock


●Apple Watch を鍵として使う

スマートウオッチである Apple Watch からも操作できます。母艦のスマートフォンを経由しているわけではなく、Bluetooh でセサミ本体と直結しているようです。そのため iPhone を持ち歩かなくても良く、Apple Watch 単体でも鍵として機能します。iPhone のバッテリーが切れた場合の予備にもなります。

使い方は本体右のサイドボタンを押して、ドックの中から Sesame を選ぶだけ。

↓右ボタンを押してこの画面がでる
Sesame

↓Sesame を選んで、この画面になったら接続できたらタップするだけ。
null

ドックに登録すると常駐しているらしくアプリの起動で待たされることはありません。すぐ Bluetooth 接続に移行します。接続そのものは iPhone より若干時間がかかりますが、繋がったあとの反応は速いです。使っている Apple Watch がかなり古い Series 2 なので、新しいモデルだと接続も速くなっているかもしれません。

また文字盤のボタンとして登録しておくと、サイドボタンの Dock から選ばなくてもワンタッチで呼び出せます。(下記画像の左下)

Sesame

スマートウォッチだと身につけていられるので忘れることが少なく、かばんやポケットからスマートフォンを取り出す必要もなくなります。その気になれば電車も支払いも家の鍵も Apple Watch だけで済ませることが可能です。

ただスマートウォッチは片手がふさがっていると操作しづらいので、荷物を持っている場合はスマートフォンを取り出した方が早いです。


●Apple Watch Series 2 を使った場合のトラブルと解決方法

実際に Apple Watch をメインの鍵として、昨年 2019年3月から長いこと使用していました。機種は Apple Watch Series 2 で、3年以上前に発売されたモデルです。

iPhone X より若干遅いものの十分期待通りに動いており操作性も悪くありません。ただごく稀に、Bluetooth 接続に失敗して接続しなくなることががありました。もちろんスマートフォンでも開けられるので、締め出されて入れなくなることはありません。この症状は Apple Watch の再起動で直ります。頻度は 1~2ヶ月に一度程度なのであまり問題ではありませんでした。

ところがその後 2019年11月頃に Series 2 用の WatchOS 6 がリリースされ、OS を更新したタイミングで困ったことが起こるようになります。なぜか Sesame の設定が消えてしまい、接続できなくなる場合があります。これが割と頻繁に発生していました。

↓この画面になり設定が消えている。いつの間にか復活している。
Sesame

いろいろ試したところ、ドックに入れているアプリが多いことが原因らしいことがわかりました。

iPhone の Watch アプリを開き、「Dock」->「よく使う項目」に入っているアプリの数をできるだけ減らしておきます。「Sesame」込で 3個程度まで減らした現在は問題も解消し、安定してつながるようになりました。

おそらくドックに登録したアプリはある程度メモリに常駐しているものと思われます。Apple Watch Series 2 は RAM 容量が少ないので、メモリ消費量が多いと動作が不安定になっていたのかもしれません。Watch OS 6 への更新によって OS の消費メモリ量が増加したか、またはデフォルトでドックに入るアプリが増えていた可能性があります。 文字盤にセサミのアイコンを登録していたことが原因だった可能性があります。

下記のように Series 3 で RAM 容量が 1.5倍、Series 4 以降は 2倍に増えているので、新しい世代の Apple Watch では特に問題がないかもしれません。

発売年 名称 CPU RAM
2015年 Apple Watch 無印 armv7a 32bit x1 512MB
2016年 Apple Watch Series 1 armv7a 32bit x2 512MB
2016年 Apple Watch Series 2 armv7a 32bit x2 512MB
2017年 Apple Watch Series 3 arm64 64bit x2 768MB
2018年 Apple Watch Series 4 arm64 64bit x2 1GB
2019年 Apple Watch Series 5 arm64 64bit x2 1GB

Wikipedia: Apple Watch


●Wi-Fi アクセスポイントと API

セサミ mini には別売りのオプションとして「Wi-Fi アクセスポイント」があります。これを設置しておくとセサミ mini でできることが一気に増えます。

「Wi-Fi アクセスポイント」は電源用 USB コネクタ直付けの非常に小さいアダプタです。セサミ mini 本体には Bluetooth しか搭載されていませんが、このアクセスポイントを通して Wi-Fi 接続できるようになります。ルーター経由でインターネットにつながるわけです。

Bluetooth の届かない範囲でも鍵の操作が可能になりますし、通知もリアルタイムで受け取れます。どこにいても鍵の状態がわかるので、戸締まりしたかどうか心配することもなくなります。

またインターネットに繋がると API を通した連携ができるようになります。スマートスピーカーから制御したり、スマートフォン単体でも Siri を使った音声操作が可能となります。iPhone のショートカット機能を使ったスクリプトの作成もできます。

Sesame
↑ショートカットへの登録

API を活用すれば、Qrio のようなドアの開閉に連動したオートロックも自作できそうです。
(2020/03/31 追記: ドアの開閉に連動したオートロックを実際に作ってみました)


●締め出しと物理キーの重要性

一度だけ鍵が開かなくなるトラブルに遭遇しました。朝は普通にオートロックで施錠していたので、その後何らかの原因で故障したのではないかと思われます。セサミ本体のバッテリー残量表示も 100% だし Bluetooth の接続も問題ないのですが、いくら操作してもモーターが回りませんでした。

締め出しをくらったのですが、保険として物理キーも携帯していたためなんとかなりました。初期の頃から Apple Watch の接続に不安定なところがあったので、念の為予備の鍵を持ち歩いていたことが功を奏しました。スマートロックを設置した場合も、予備として物理キーを持っておくことを強くおすすめします。

その後本体は保証期間内だったこともあり交換対応していただきました。サポートの対応は非常に良く、その日のうち(10分経たず)に返答があったことには驚きました。数日使えないだけでも不便を感じていたので大変助かりました。


●まとめ

かなり便利に活用しています。外出時に必要な機能をスマートフォンやスマートウォッチに集約できるのが良いです。

ただし外出時は物理鍵の携帯を強く推奨します。あくまで予備なので、いざというときのために鞄の奥に忍ばせておいたり小銭入れに入れておく程度でも十分だと思います。



AI
oga at 16:00
Android スマートフォンの上に PyTorch の開発環境を作ってみました。かなり遅いですが、電車の中で書いたコードのビルドテストをしたり、簡単な実験をするくらいには使えそうです。

以下作業メモです。

使用した端末は Essential Phone PH-1 (Snapdragon 835, RAM 4GB, Android 10)。もちろん C++ だけでなく Python からも使えます。


●UserLAnd の準備

UserLAnd を使うと Android 上に Linux 環境を作ることができます。root 不要でただの Android アプリとして動きます。今回は最小構成にするため Ubuntu + SSH Terminal を選択しました。


●PyTorch のビルド

UserLAnd の Ubuntu を起動し Terminal で作業します。または PC 等から ssh で接続することもできます。

先に更新しておきます。

$ sudo apt update
$ sudo apt upgrade -y

必要なソフトのインストール

$ sudo apt install  git cmake g++
$ sudo apt install  libopenblas-dev

Python3 の準備

$ sudo apt install  python3-dev python3-setuptools
$ sudo apt install  python3-numpy python3-cffi
$ sudo apt install  python3-yaml python3-wheel

pytorch の準備。展開フォルダは仮に ~/pytorch とします。

$ cd
$ git clone --recursive https://github.com/pytorch/pytorch
$ cd pytorch
$ git submodule sync
$ git submodule update --init --recursive

ビルドします。複数スレッド使うとメモリ不足で落ちるので MAX_JOBS=1 を指定します。

$ cd ~/pytorch
$ USE_CUDA=0 USE_CUDNN=0 BUILD_TEST=0 USE_MKLDNN=0 USE_DISTRIBUTED=0 MAX_JOBS=1 python3 setup.py build_clib

Python 向けにビルドする場合は最後の "build_clib" を "install" にしてください。

かなり時間がかかるので電源に接続したまましばらく待ちます。

もし途中で↓のようなエラーが出た場合はコードを一部修正します。

... 8x8-dq-aarch64-neon.S:662: Error: operand mismatch -- `mov V8.4s,V9.4s'


"aten/src/ATen/native/quantized/cpu/qnnpack/src/q8gemm/8x8-dq-aarch64-neon.S" の 662行~669行あたりにある

    MOV V8.4s, V9.4s
    MOV v10.4s, v11.4s
    MOV v12.4s, V13.4s
    MOV V14.4s, V15.4s
    MOV V16.4s, V17.4s
    MOV V18.4s, V19.4s
    MOV V20.4s, V21.4s
    MOV V22.4s, V23.4s

これらの行を下のように変更します。

    MOV V8.16b, V9.16b
    MOV v10.16b, v11.16b
    MOV v12.16b, V13.16b
    MOV V14.16b, V15.16b
    MOV V16.16b, V17.16b
    MOV V18.16b, V19.16b
    MOV V20.16b, V21.16b
    MOV V22.16b, V23.16b

AArch64 の NEON にはもともとレジスタ同士の move 命令がありません。mov vdest, vsrc はアセンブラ内部で「 or vdest, vsrc, vsrc 」に置き換えられます。浮動小数点数で論理演算はできないので、アセンブラによっては 4s (単精度 32bit x4 = 128bit) の型指定がエラーになってしまうのだと考えられます。これは 16b (byte 8bit x16 = 128bit) に変更すれば OK です。


● C++ から利用する

前回も書いたように ~/pytorch/torch 以下が libtorch 相当になります。

include path に

~/pytorch/torch/include
~/pytorch/torch/include/torch/csrc/api/include

link path に

~/pytorch/torch/lib

を指定してコンパイルします。必要なライブラリは libc10 と libtorch_cpu です。コンパイルオプションの指定例は下記の通り。

$ clang -L~/pytorch/torch/lib -lc10 -ltorch_cpu  torchtest.cpp -o torchtest

実行時は LD_LIBRARY_PATH が必要です。

$ export LD_LIBRARY_PATH=~/pytorch/torch/lib

一度ビルドしておけば他の AArch64 Android の UserLAnd 上でも使えます。Raspberry Pi 4 の Ubuntu Server (AArch64) でも動きました。


● Python で使う

Python の場合は "build_clib" の代わりに "install" を使います。途中でエラーが出ますが問題ありません。("bdist_wheel" は途中で止まるので保留中)

$ USE_CUDA=0 USE_CUDNN=0 BUILD_TEST=0 USE_MKLDNN=0 USE_DISTRIBUTED=0 MAX_JOBS=1 python3 setup.py install

これで import torch できるようになります。torchvision が必要な場合も同じようにビルドしておきます。

$ sudo apt install zlib1g-dev libjpeg-dev
$ git clone https://github.com/pytorch/vision.git
$ cd vision
$ MAX_JOBS=1 python3 setup.py install



関連ページ
HYPERでんち: Deep Learning

関連エントリ
RADEON (ROCm) で PyTorch を使う。C++ API
Jetson Nano で TensorFlow の C 言語 API を使う
Android UserLAnd の更新と VNC 画面設定
UserLAnd : Android 9.0 で Ctrl + SPACE を使えるようにする
Android Termux で日本語入力を行う / UserLAnd との併用
Android 9.0 と Bluetooth Keyboard による日本語入力
Android で動く Linux 環境 UserLAnd が XServer XSDL に対応
Oculus Go を文章書き&開発マシンにする
UserLAnd とブラウザ
Android 上の開発環境と UserLAnd
OS の中の Linux (WSL/Chrome OS/Android UserLAnd)
ARM CPU 上の開発環境とコンパイル時間の比較 (2) Pixel 3/UserLAnd


AI
oga at 18:37
Deep Learning フレームワークのほとんどが Python を使っています。C++ など他の言語も使えますが、Python と同じ使い方ができるものはあまり多くありません。

例えば CNTK の場合 Keras の backend として使うとこんな感じです。

# Keras
model= models.Sequential([
    layers.Dense( 64, input_shape=(784,), activation='relu' ),
    ~
  ])

CNTK の Python API を使った場合↓

# CNTK
xinput= cntk.input_variable( 784, np.float32 )
model= layers.Sequential([
    layers.Dense( 64, activation=cntk.relu ),
    ~
  ])

CNTK の C++ の場合 Parameter を確保して複数の Function に分解する必要があるのでこうなります。↓

// CNTK + C++
auto device= CNTK::DeviceDescriptor::UseDefaultDevice();
auto xinput= CNTK::InputVariable( { 784 }, CNTK::DataType::Float, L"xinput" );
auto weight= CNTK::Parameter( { 64, 784 }, CNTK::DataType::Float, CNTK::HeNormaliInitializer(), device );
auto bias= CNTK::Parameter( { 64 }, CNTK::DataType::Float, 0.0f, device );
auto x= CNTK::ReLU( CNTK::Plus( bias, CNTK::Times( weight, xinput ) ) );

PyTorch の場合は C++ でも Python と同じ書き方で同じ API なので覚えることが少なくて済みます。

// PyTorch C++
class ModelImpl : public torch::nn::Module {
    torch::nn::Linear  fc0;
public:
    ModelImpl()
    {
        fc0= register_module( "fc0", torch::nn::Linear( 784, 64 ) );
        ~
    }
    torch::Tensor  forward( const torch::Tensor& x )
    {
        ~
        return  torch::relu( fc0( x ) );
    }
};
TORCH_MODULE(Model);


AMD RADEON を使用する場合の選択肢はいくつかあります。Python の場合は Keras + PlaidML が最も簡単で Windows/macOS でも使用できます。Linux の場合は ROCm が使えるため PlaidML 以外の選択肢が増えます。今回は C++ API を使いたいので Linux 上で ROCm を使用しました。

以下 RADEON で PyTorch (C++ API) を使うための作業メモです。


● ROCm の install

使用した環境は RADEON RX Vega 64 (gfx900) + Ubuntu 18.04 LTS です。

ROCm Documentation: Installation Guide

上のページを見ると Supported OS に 18.04.3 (Kernel 5.0) と書かれているため最初に Kernel を更新します。下記ページを参考にさせていただきました。

virtualiment: Ubuntu18.04にカーネル 5.0 をインストールする手順

カーネルの更新

$ sudo apt install --install-recommends linux-generic-hwe-18.04 xserver-xorg-hwe-18.04
$ sudo reboot

次に手順通りに ROCm を入れます。こちらは下記のページを参考にさせていただきました。

ニートが始めるUE4開発日誌: RX470で機械学習ことはじめ その3 ~ROCmとTensorFlowのインストール~

install 前の更新とライブラリインストール

$ sudo apt update
$ sudo apt dist-upgrade
$ sudo apt install libnuma-dev
$ sudo reboot

リポジトリ追加と ROCm のインストール

$ wget http://repo.radeon.com/rocm/apt/debian/rocm.gpg.key
$ sudo apt-key add rocm.gpg.key
$ echo 'deb [arch=amd64] http://repo.radeon.com/rocm/apt/debian/ xenial main' | \
   sudo tee /etc/apt/sources.list.d/rocm.list
$ sudo apt install rocm-dkms
$ sudo usermod -a -G video $LOGNAME
$ sudo reboot



● PyTorch のビルド

PyTorch を ROCm でビルドします。ビルド手順は下記ページを参考にさせていただきました。

Lernapparat: Building PyTorch on ROCm

事前の準備

$ sudo apt install git cmake
$ sudo apt install python3-pip
$ pip3 install setuptools numpy

ビルドに必要な ROCm のパッケージを入れます。

$ sudo apt install rocm-libs miopen-hip rccl roctracer-dev

cmake のパッチのため下記のスクリプトを使います。

#!/bin/bash
for fn in $(find /opt/rocm/ -name \*.cmake ); do
  sudo sed --in-place='~' 's/find_dependency(hip)/find_dependency(HIP)/' $fn
done

上記の内容をファイル(replace_script.sh)に保存してから下記のように実行します。

$ bash ./replace_script.sh

PyTorch を checkout します。仮に展開場所を ~/pytorch とします。

$ git clone --recursive http://github.com/pytorch/pytorch

pytorch フォルダで下記のコマンドを実行します。

$ cd ~/pytorch
$ python3 tools/amd_build/build_amd.py

ビルドします。

$ cd ~/pytorch
$ RCCL_DIR=/opt/rocm/rccl/lib/cmake/rccl/ PYTORCH_ROCM_ARCH=gfx900 hip_DIR=/opt/rocm/hip/cmake/ USE_NVCC=OFF BUILD_CAFFE2_OPS=0 PATH=/usr/lib/ccache/:$PATH USE_CUDA=OFF python3 setup.py bdist_wheel


しばらくしたら Python 向けのパッケージが ~/pytorch/dist/*.whl にできます。C++ 向けの libtorch 相当が ~/pytorch/torch 以下になります。

include path は下記の通り。

~/pytorch/torch/include
~/pytorch/torch/include/torch/csrc/api/include

library は下記の場所に入るので、ビルド時の libpath と実行時の LD_LIBRARY_PATH に追加します。

~/pytorch/torch/lib

Link 時のライブラリ指定はこんな感じ。

-lc10 -ltorch -lc10_hip -ltorch_hip -ltorch_cpu -lc10


これで CUDA 向けのコードがそのまま RADEON で動くようになりました。ROCm (hip) でも PyTorch の API は cuda のままです。C++ API でも to( torch::kCUDA ) で OK。

import torch
for di in range(torch.cuda.device_count()):
    print( di, torch.cuda.get_device_name(di), torch.cuda.get_device_capability(di) )

例えば Python で↑のコードを実行すると次のように表示されます。

0 Vega 10 XT [Radeon RX Vega 64] (3, 0)

起動時に少々待たされますが起動してしまえば十分速いです。BatchSize は 64 以上がおすすめです。


関連ページ
HYPERでんち: Deep Learning

関連エントリ
Jetson Nano で TensorFlow の C 言語 API を使う


UE4
oga at 00:17
Ryzen 9 3950X は 16 core 32 thread の CPU です。少し前まで 4 core 8 thread の CPU をメインに使っていたことを考えると、これ 1つで従来の PC の 4台分に相当します。

実際に速度を調べてみました。UE4 のコンパイル速度の比較は下記の通り。本当に 4台分、コンパイル時間が 1/4になっています。

● UE4 4.24.0 (GitHub版) の Engine コンパイル時間

CPU Core Thread Build 時間 (A) Build 時間 (B)
Ryzen 9 3950X Zen2 16C/32T 966 (16:06) 833 (13:53)
Ryzen 7 1800X Zen 8C/16T 2481 (41:21) 2240 (37:20)
Core i7-6700K Skylake 4C/8T 3787 (63:07) 3564 (59:24)

・Build 時間の単位は秒、値が小さい方が高速

Skylake Core i7-6700K で 1時間かかるビルドが Ryzen 9 3950X だと 16分で終わっています。4倍高速です。1800X と比べても 2.5倍速くなっています。これくらい速ければ分散ビルドなしで十分使えそうです。

ちなみに条件を統一するため 3台とも SATA 接続の SSD の上でビルドしています。NVMe ならもっと時間を短縮できると思われます。


・(A) は Total のビルド時間で UnrealHeaderTool と ShaderCompileWorker が含まれています。また UHT のヘッダ生成は並列化されないためシングルスレッドの時間が含まれます。

・(B) は Engine の ParallelExecutor だけの時間で、最後の Link 以外は並列化されています。シングルスレッドの時間が短いため (A) よりも Thread 数の差が出やすくなります。

・一旦 clean してから Engine\Build\BatchFiles\Build.bat を使い DevelopmentEditor Win64 でビルドしています。

Ryzen9

Ryzen9
↑16 core でも小さい Socket AM4。箱の中身は殆どが黒いスポンジ。


● ARM デバイスの比較に使っているコンパイルベンチの速度

CPU Core Clock Thread Build 時間 WSL
Ryzen 9 3950X Zen2 3.5 GHz 16C/32T 10 (00:10)
Ryzen 7 1800X Zen 3.6 GHz 8C/16T 26 (00:26)
Core i7-6700K Skylake 4.0 GHz 4C/8T 40 (00:40)
Core i7-4790K Haswell 4.0 GHz 4C/8T 41 (00:41)

・Build 時間の単位は秒、値が小さい方が高速

これも UE4 とほぼ同じで 4倍速くなっています。詳しくはこちら


● vfpbench の結果

CPU Thread Single-Thread Multi-Thread
Ryzen 9 3950X 16C/32T 123.3 GFLOPS 1898.0 GFLOPS
Ryzen 7 1800X 8C/16T 61.4 GFLOPS 476.2 GFLOPS
Core i7-6700K 4C/8T 135.6 GFLOPS 542.3 GFLOPS

・GFLOPS の値が大きい方が高速。単精度のみ。

1.9 TFLOPS (1898 GFLOPS) という見たことがない数値が出ました。あくまでメモリアクセスを無視した理想的な条件での値です。だいたい PS4 の GPU に相当します。実際のアプリケーションでこの速度が出るわけではないので参考程度にお願いします。

下記は計測結果のログです。Single Thread 単精度のみ。倍精度はまだ調査中です。

Group 0:  Thread=32  Clock=3.493000 GHz  (mask:ffffffff)
  SingleThread HP max: -
  SingleThread SP max:  122.851 GFLOPS
  SingleThread DP max:   52.722 GFLOPS
  MultiThread  HP max: -
  MultiThread  SP max: 1894.097 GFLOPS
  MultiThread  DP max:  954.959 GFLOPS

* Group 0:  Thread=1  Clock=3.493000 GHz
                                  TIME(s)   MFLOPS      MOPS    FOP IPC
SSE mulss (32bit x1) n8       :    0.244     8605.0     8605.0 (  1 2.5)
SSE addss (32bit x1) n8       :    0.244     8591.2     8591.2 (  1 2.5)
FMA vfmaddss (32bit x1) n8    :    0.317    13239.6     6619.8 (  2 1.9)
FMA vfmaddss (32bit x1) n12   :    0.365    17246.0     8623.0 (  2 2.5)
SSE mulps (32bit x4) n8       :    0.245    34194.1     8548.5 (  4 2.4)
SSE addps (32bit x4) n8       :    0.243    34520.1     8630.0 (  4 2.5)
SSE mul+addps (32bit x4) n8   :    0.183    45837.4    11459.3 (  4 3.3)
FMA vfmaddps (32bit x4) n8    :    0.303    55334.8     6916.9 (  8 2.0)
FMA vfmaddps (32bit x4) n12   :    0.364    69104.3     8638.0 (  8 2.5)
SSE ml+ad+adps (32bit x4) n9  :    0.218    43313.8    10828.4 (  4 3.1)
SSE mulss (32bit x1) ns4      :    0.368     5699.9     5699.9 (  1 1.6)
SSE addss (32bit x1) ns4      :    0.368     5698.4     5698.4 (  1 1.6)
SSE mulps (32bit x4) ns4      :    0.367    22812.0     5703.0 (  4 1.6)
SSE addps (32bit x4) ns4      :    0.366    22916.4     5729.1 (  4 1.6)
AVX vmulps (32bit x8) n8      :    0.258    65052.1     8131.5 (  8 2.3)
AVX vaddps (32bit x8) n8      :    0.260    64581.5     8072.7 (  8 2.3)
AVX vmul+addps (32bit x8) n8  :    0.157   106474.3    13309.3 (  8 3.8)
FMA vfmaddps (32bit x8) n8    :    0.318   105391.7     6587.0 ( 16 1.9)
FMA vfmaddps (32bit x8) n12   :    0.408   123302.0     7706.4 ( 16 2.2)
FMA vfma+mlps (32bit x8) n12  :    0.408    92570.0     7714.2 ( 12 2.2)
FMA vfma+adps (32bit x8) n12  :    0.345   109457.1     9121.4 ( 12 2.6)
AVX vml+ad+adps (32bit x8) n9 :    0.345    54644.5     6830.6 (  8 2.0)
FMA vfma+ml+adps (32bit x8) n9:    0.307    81930.4     8193.0 ( 10 2.3)


関連ページ
Compile Benchmark
VFP Benchmark Log 計測結果まとめ

関連エントリ
UE4 UnrealBuildTool の設定 BuildConfiguration.xml
UE4 UnrealBuildTool VisualStudio の選択を行う
UE4 UnrealBuildTool *.Build.cs のコードを共有する
AMD CPU Ryzen とコンパイル時間の比較 (2)
AMD CPU Ryzen とコンパイル時間の比較


| 次のページ(日付が古い方向)>>