HerokuでPyCall(NumPy)とSinatraを動かす

heroku-examples/python-miniconda を参考に、PyCallとSinatraをHerokuで動かしてみました。

ソースはこちらです。
https://github.com/tnantoka/pycall-heroku

Docker関連

# 最初とpush前の確認時にやる
$ docker build -t tnantoka/pycall-heroku .

# アプリ開発中
$ docker run -it --rm -v `pwd`/app:/opt/app -p 5000:5000 -e PORT=5000 tnantoka/pycall-heroku

# 確認
$ docker run -it --rm -p 5000:5000 -e PORT=5000 tnantoka/pycall-heroku
  • VOLUMEはサポートされないのでADDで。
  • 最後はbuildしてpushするが、開発中は-vで上書き。
  • Herokuでは$PORTが使われるので-eで渡す。

他のプロジェクトでも使いたかったので、heroku-minicondaにRubyをインストールしたものを公開した。
https://hub.docker.com/r/tnantoka/miniconda-ruby/
DockerHubデビュー!

Herokuにデプロイ

$ heroku plugins:install heroku-container-registry
$ heroku container:login
$ heroku create pycall-heroku
$ heroku container:push web
$ heroku open

この状態だと何故かApplication Errorになってしまったのですが、ブラウザでHerokuにログインしてDynoを手動でONにすればいけました。謎です。
(heroku createの後、pushする前に1度アクセスしておいた方がいい?)

https://pycall-heroku.herokuapp.com/
こちらで無事動いております。

その他

pyimportas指定だとエラーになった。
僕のSinatra力が足りなそう。

参考

RubyでMNISTのデータを読む

PyCallの作者さんのGemがあった。

https://github.com/mrkn/ruby-mnist

images = Mnist.load_images('train-images-idx3-ubyte.gz')[2].size # => 60000
labels = Mnist.load_labels('train-labels-idx1-ubyte.gz').size # => 60000

pixels = images[0].unpack('C*')
pixels.size # => 784
labels[0] # => 5

pixels.map { |p| p > 100 ? '*' : ' ' }.each_slice(28).map { |a| a.join('') }.join("\n")





                *** ****    
           ************     
        **********          
        **********          
         *****   *          
           **               
           ***              
            **              
             ****           
              ****          
               ****         
                 ***        
                 ***        
               *****        
             *******        
           *******          
          ******            
       *******              
     ********               
    *******                 



という感じで使える。

unpack、苦手意識ある。

unpack (String) - Rubyリファレンス

文字列のバイト列を引数formatの書式に従って分解し、配列を返します。文字列をバイナリデータとして扱うときに使います。

わかりやすい。

PycallとNumPyで3層ニューラルネットワーク

O'Reilly Japan - ゼロから作るDeep Learning P.58ページからを参考に、フォワード処理を実装。

utils.rb

require 'bundler'
Bundler.require

require 'pycall/import'
include PyCall::Import
pyimport 'numpy', as: :np

def identity_function(x)
  x 
end

def sigmoid(x)
  1 / (1 + np.exp.(-1 * x))
end

-xが以下のエラーで動かなかったので-1 * xにした。

~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/pycall-0.1.0.alpha.20170317/lib/pycall/pyobject_wrapper.rb:174:in `method_missing': undefined method `-@' for array([ 0.3,  0.7,  1.1]):PyCall::PyObject (NoMethodError)
Did you mean?  -

neuralnet.rb

require 'bundler'
Bundler.require

require 'pycall/import'
include PyCall::Import
pyimport 'numpy', as: :np

require './utils'

def init_network
  network = {}
  network[:W1] = np.array.([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
  network[:b1] = np.array.([0.1, 0.2, 0.3])
  network[:W2] = np.array.([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
  network[:b2] = np.array.([0.1, 0.2])
  network[:W3] = np.array.([[0.1, 0.3], [0.2, 0.4]])
  network[:b3] = np.array.([0.1, 0.2])
  network
end

def forward(network, x)
  w1, w2, w3 = network[:W1], network[:W2], network[:W3]
  b1, b2, b3 = network[:b1], network[:b2], network[:b3]

  a1 = np.dot.(x, w1) + b1
  z1 = sigmoid(a1)
  a2 = np.dot.(z1, w2) + b2
  z2 = sigmoid(a2)
  a3 = np.dot.(z2, w3) + b3
  y = identity_function(a3)

  y
end

network = init_network
x = np.array.([1.0, 0.5])
y = forward(network, x)
p y # array([ 0.31682708,  0.69627909])

普通に動いて楽しい。

ソース

https://github.com/tnantoka/hello-pycall

PyCallでMatplotlibを使う

O'Reilly Japan - ゼロから作るDeep LearningのP.17ページにある単純なグラフ。

plot.rb

require 'bundler'
Bundler.require

require 'pycall/import'
include PyCall::Import
pyimport 'numpy', as: :np
pyimport 'matplotlib', as: :mp

pyimport 'matplotlib.pyplot', as: :plt

x = np.arange.(0, 6, 0.1)
y = np.sin.(x)

plt.plot.(x, y)
plt.show.()
$ bundle exec ruby plot.rb 

動いた!

参考

ソース

https://github.com/tnantoka/hello-pycall

PyCallでNumPyを使う

O'Reilly Japan - ゼロから作るDeep LearningのP.12辺りのサンプルが動くか試してみる。

PyCallのインストール

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

$ bundle init

# Gemfile
gem 'pycall'

$ bundle

numpy.rb

require 'bundler'
Bundler.require

require 'pycall/import'
include PyCall::Import
pyimport 'numpy', as: :np

x = np.array.([1.0, 2.0, 3.0])
y = np.array.([2.0, 4.0, 6.0])
p x + y # array([ 3.,  6.,  9.])

A = np.array.([[1, 2], [3, 4]])
p A # array([[1, 2],
    #        [3, 4]])
p A.shape # (2, 2)
p A.dtype # dtype('int64')

B = np.array.([[3, 0], [0, 6]])
p A + B # array([[ 4,  2],
        #        j[ 3, 10]])
$ bundle exec ruby numpy.rb 

普通に動いた!

参考

ソース

https://github.com/tnantoka/hello-pycall