2018年11月7日水曜日

python responder & flask / node.js express

はじめに

HTTPサーバーを作るPythonのresponderというパッケージが新しくKenneth Reitzさんによって作られたということをHacker Newsで読んだので、 使ってみようかと思い、ついでに他のサーバーパッケージも 入れたすごく簡単な動作比較をします。
少ないですが、次のフレームワークを比較します。

 

 

環境構築

pythonとnodejsの環境構築です。


python
$ pipenv --python 3.6
$ pipenv install responder flask
TypeError: 'module' object is not callable エラーが出るとき:
Pipenvのバグで、pip18.1を使っているとエラーがでます。(10/17/2018現在) pip18.0にダウングレードすると直ります。
$ pipenv run pip install pip==18.0

  

node.js (v11.0.0)
$ npm init -y
$ npm i -S express

 

 

REST API(Hello World)

localhost:3000/hello へ GET すると Hello World!が返る基本的なREST APIです。



responder

# server_responder.py

import responder

api = responder.API()

@api.route("/hello")
async def hello(req, resp):
    resp.text = "Hello World!"

if __name__ == "__main__":
    api.run(port=3000)


サーバー開始
$ pipenv run python server_responder.py
INFO: Started server process [91078]
INFO: Uvicorn running on http://127.0.0.1:3000 (Press CTRL+C to quit)


curlでチェック
$ curl localhost:3000/hello
Hello World!$


返ってきました。サーバー側のログも出ています。
INFO: ('127.0.0.1', 53672) - "GET /hello HTTP/1.1" 200

 

 

flask

# server_flask.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"


サーバー開始
$ FLASK_APP=server_flask.py pipenv run flask run --port 3000
 * Serving Flask app "server_flask.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)


Flaskでは if __name__ == "__main__": ... でもできますが、コマンドで起動することを推奨しています。
ポートもコマンドで指定します。

curlでチェック
$ curl localhost:3000/hello
Hello World!$


サーバーログ
127.0.0.1 - - [07/Nov/2018 22:17:04] "GET /hello HTTP/1.1" 200 -

 

 

express

// server_express.js

const express = require("express");
const app = express();

app.get("/hello", (req, res) => res.send("Hello World!"))

app.listen(3000, () => console.log("server on"))


サーバー開始
$ node server_express.js
server on


Expressはログを自動で返しません。サーバーが起動したことを示すためにコールバック関数でserver onとコンソールに出力しました。

curlでチェック
$ curl localhost:3000/hello
Hello World!$


ログは一切出ません。

ログを出すには、別のモジュールである morgan をインストールして使います。
$ npm i -S morgan
// server_express.js

const express = require("express");
const app = express();

// morganをミドルウェアとして使う
const logger = require("morgan");
app.use(logger("dev"))

app.get("/hello", (req, res) => res.send("Hello World!"))

app.listen(3000, () => console.log("server on"))


起動してから、curlテストした際のログ
GET /hello 200 7.132 ms - 12

 

 

サーバーサイドレンダリングされたHTMLページ

HTMLを返すエンドポイントを作ります。
サーバーサイドでそのままHTMLを書いても使えますが、変数を渡したりするにはテンプレートエンジンというものを使います。
pythonのパッケージのresponderとflaskではデフォルトでjinja2が使われていて、node.jsでは色々選べますがここではexpressで公式に紹介されているpugを使います。
それぞれ、 Hello, {名前}!と名前の部分を変数としたHTMLを作成します。

 

 

responder

実は上でサーバーを起動した際に、自動でstatic/templates/の二つのファイルがルートに作成されています。ここに返すHTMLを作ります。
自動で作られたtemplates/内にpage.htmlを作り、Jinja templateを使ってテンプレーティングします。

<!-- templates/page.html -->

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>html page</title>
    </head>
    <body>
        <h1>Hello, {{ name }}!</h1>
    </body>
</html>


エンドポイント
# server_responder.py

import responder

api = responder.API()

@api.route("/hello/{name}")
async def hello(req, resp, *, name):
    resp.content = api.template("page.html", name=name)

if __name__ == "__main__":
    api.run(port=3000)


ブラウザでlocalhost:3000/hello/kazuyaにアクセスすると、Hello, kazuya!と表示され、タイトルはhtml pageとなっています。

curlでもテスト
$ curl localhost:3000/hello/kazuya
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>html page</title>
    </head>
    <body>
        <h1>Hello, kazuya!</h1>
    </body>
</html>
このコードを実行した時、エラーが出て、イシューをあげたら直してもらえました。 https://github.com/kennethreitz/responder/issues/76

 



flask

同じJinja2を使ってレンダリングしているので、HTMLは同じで大丈夫です。
気をつけるのは、ここではresponderが自動でtemplatesフォルダを作ってるのでそれを使いますが、flaskだけでやる場合は自分でフォルダを作らないといけないことです。

# server_flask.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/hello/<name>")
def hello(name=None):
    return render_template("page.html", name=name)


結果はresponderと一緒です。

 



express

pugをインストールします。
npm i -S pug


pug は書き方が独特で、yamlみたいな書き方をします。

html
    body
        h1 Hello, #{ name }!


expressにテンプレートエンジンと、HTMLファイルのある場所の設定をします。

const express = require("express");
const path = require("path");

const app = express();

// viewsフォルダを見るように設定
app.set("views", path.join(__dirname, "views"));
// テンプレートエンジンをpugに設定
app.set("view engine", "pug")

app.get("/hello/:name", (req, res) => {
  res.render("page", { name: req.params.name })
})

app.listen(3000, () => console.log("server on"))



結果はpythonのパッケージと同じになります。

 

 

考察

pythonはデコレータを使って、node.jsはコールバック関数を使ってHTTPリクエストに対する挙動をプログラムしています。
responderとexpressは両方エンドポイントで用いる関数にrequest, response的なオブジェクトが渡されることになっていたり、そのうちresponse側のオブジェクトで返答を定義するところが似ています。flaskはそういうのが無いですね。
あとrequestsとexpressではasync/awaitがサポートされているけど、flaskには無い。でもサーバー側で非同期処理ってどのくらいメリットがあるのか自分は知らないです。
pythonのパッケージはログとかテンプレートエンジンとか諸々最初からサポートされているけど、expressは全部自分で入れていかないといけない。Unix的にはexpressの方が良いパッケージなんだろうが、ちょっとめんどくさい。今回までのセットアップでも若干pythonパッケージの方が便利でした。
その代わりパフォーマンスは、間違いなくexpressの方が良いはずです。

2018年10月12日金曜日

久しぶりの投稿(一年の振り返りと今後)

はじめに


ブログをまたやろうと思います。
理由は、転職から1年経ち、自分のキャリアのこととか人生のことなどを振り返って、今後に向けて考えをまとめたくなったからです。
考えをまとめるときは経験上、文章化するのが良いとわかってます。
ただ、幾度とブログやSNS上の個人発信はした期間があったものの、個人的にあまりいい思い出になっていなくて、意図せず人を傷つけるような書き方をしたり、後から見返して穴に入りたくなるようなことを書いたり、そういうことがあってしばらく離れていました。
今回のブログの目的は、自分の考えをまとめることなので、その目的から逸した事とかを勢いで書いたり、思いつくままにつらつらと書き連ねて出版するとかして後から後悔しないように、気をつけようと思います。
今回は、この一年の振り返りをこのタイミングでします。


2018年3月11日日曜日

頭をひねって考え出したDefault Boxesの実装がChainerの実装と構造が一緒で嬉しい

screenを使ってjupyter notebookとか使ってると

セッションが切れても裏で動き続けているという裏技

2018年3月2日金曜日

pytorchでcudaのavailabilityを確かめる

TensorFlowがimportできなくなった。
全然DeepLearningが捗らないのでPyTorchを入れて動かしたらすっごくわかりやすかったから
今後PyTorchやろうかな。



>>> torch.cuda.is_available()
True


nvidia driver と CUDA と cudnn のインストールはここがわかりやすかったしPyTorchでちゃんと動くのでよかった
http://www.python36.com/install-tensorflow-using-official-pip-pacakage/
そもそものTensorFlowは動かないけど





>>> import tensorflow as tf
Illegal instruction (core dumped)
(dl) khatta@ubuntu:~$


同じ事象がCPUサーバーでも起きてたから、何かインストールするときに間違ってるんだと思う
どうしよう。。


PyTorchでDeepLearningできるからいいかな。。

2018年3月1日木曜日

jupyter notebook でmp4動画を流すやつ


すごい遅いです

import cv2
import matplotlib.pyplot as plt
from IPython import display
%matplotlib inline

cap = cv2.VideoCapture('video.mp4')

while(cap.isOpened()):
    try:
        ret, frame = cap.read()
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        plt.imshow(frame)

        display.clear_output(wait=True)
        display.display(plt.gcf())

    except KeyboardInterrupt:
        cap.release()

誰からも返事もらえない
https://ja.stackoverflow.com/questions/42040/python-opencv-jupyter-で-mp4-動画をリアルタイムに再生したい

2018年2月23日金曜日

tf.scatter_ndでゼロ埋め式テンソルを一発で作る

 (10, 4) の形のテンソルを作る際に、10個のうち4個だけ該当するインデックスに対して値を埋めて、あとはゼロにしたいみたいなことがある。
numpy を使ってまっすぐやると


tensor = np.zeros((10, 4))
indices = [2, 5, 6, 9]
tensor[indices] = [1, 1, 1, 1]

# array([[ 0.,  0.,  0.,  0.],
#        [ 0.,  0.,  0.,  0.],
#        [ 1.,  1.,  1.,  1.],
#        [ 0.,  0.,  0.,  0.],
#        [ 0.,  0.,  0.,  0.],
#        [ 1.,  1.,  1.,  1.],
#        [ 1.,  1.,  1.,  1.],
#        [ 0.,  0.,  0.,  0.],
#        [ 0.,  0.,  0.,  0.],
#        [ 1.,  1.,  1.,  1.]])


tf.scatter_ndを使うと

indices = [[2], [5], [6], [9]]
updates = [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
shape = [10, 4]
tensor = tf.scatter_nd(indices, updates, shape)

with tf.Session() as sess:
    print(sess.run(tensor))

# [[0 0 0 0]
# [0 0 0 0]
# [1 1 1 1]
# [0 0 0 0]
# [0 0 0 0]
# [1 1 1 1]
# [1 1 1 1]
# [0 0 0 0]
# [0 0 0 0]
# [1 1 1 1]]


一発でとか言ったんですが大概でした

2018年2月22日木曜日

np.eyeで簡単にone_hot_categoryベクトルを作る

https://docs.scipy.org/doc/numpy/reference/generated/numpy.eye.html



categories = ['background', 'Car', 'Truck', 'Pedestrian']
one_hot_category = np.eye(len(categories))

one_hot_category
# array([[ 1.,  0.,  0.,  0.],
#        [ 0.,  1.,  0.,  0.],
#        [ 0.,  0.,  1.,  0.],
#        [ 0.,  0.,  0.,  1.]])

2018年2月21日水曜日

MySQLで縦持ちテーブルを横持ちテーブルにする(サブクエリを使わない)

pythonの記事がどうしても今日かけなかったのでSQLのハックで好きなものを書きます(2017年3月の私の別の日記の記事)


日付 売上
2017-03-10 10,500
2017-03-12 5,470
2017-03-18 20,100


このようなorder_tableというテーブルがあったとして、これを


日付 2017-03-10 2017-03-12 2017-03-18
売上 10,500 5,470 20,100


こうする方法を考えます。
SQLでは縦並びの情報が日付と数字と違うとエラーなので、売上額だけ横に並べてみます。
カラム名もダイナミックに変える方法があれば教えてください。

SQLでは縦並びのデータを横並びにする方法はいくつかあるけど、

SELECT
  SUBSTRING_INDEX(GROUP_CONCAT(売上 ORDER BY 日付), ',', 1),
  SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(売上 ORDER BY 日付), ',', 2), ',', -1),
  SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(売上 ORDER BY 日付), ',', 3), ',', -1)
 FROM
  order_table


各カラム内でデータを縦持ちテーブル内の任意のデータに沿って並べ、その中のn番目のデータ(nは自由に指定できる)を表示させることが出来ます。

イメージ的にカラムがスロットルになったような感じです。
これでSQLが2次元から3次元になります。

python を始めてからインデックスの考え方に慣れたけどこれを書いた一年前は初めてで新鮮でした

2018年2月20日火曜日

学習済みのモデルの重みから一部だけ取り出す

例えばkerasを使えば、VGG16のアーキテクチャで、imagenetをベースに訓練された1000種類の物体を判別できるモデルを即使える。
https://keras.io/ja/applications/#vgg16

同じように、物体検知でも、SSDなどのアーキテクチャで、MSCOCOとかPASCAL VOCとかの物体を検知できるモデルが発表されている。
https://github.com/pierluigiferrari/ssd_keras#download-the-original-trained-model-weights
このレポジトリの方はcaffeのモデルをkerasで使えるようh5ファイルに変換して提供しているので、これをベースにお話しする。

このh5ファイルに入っているのは、ちょっと特殊なアクセス方法を取らなければいけない多次元配列であり、辞書のようにデータに名前がついていて、アクセスするには名前で呼び出さなければいけない。


import h5py

WEIGHT_PATH = 'weights.h5'
weights_file = h5py.File(WEIGHT_PATH, 'r')

weight = weights_file['conv6_3']['conv6_3']['kernel:0'].value


このようにh5pyでファイルを読みだして、それに対して名前を鍵に中身を出力していく。
このh5ファイルは、SSDのアーキテクチャで kernel:0 という鍵を最後にネットワークの重みを格納していて、バイアスに関しては同じ位置から bias:0 で呼び出せる。
この出力は普通に多次元配列で、かつこのモデルが作られた際の設定を確認すると、背景を含め81種類のMSCOCOの画像を認識検知できるモデルということがわかり、さらにMSCOCOの画像の種類順にcx, cy, w, hの四値の予測を格納していることがわかるので、
それを切り出して一種類の重みのみとして扱うことができる。
MSCOCOの画像の種類に対応するIDは、例えば止まれ標識は12番であり、
http://cocodataset.org/#explore
こちらで並んでいる順である。
なので12番を指定して重みを切り出すことができる。
また、全部で81種類のため、四値それぞれ切り出すには81個間を開けながら切りださなければいけない。


classes_of_interest = [0, 12] # background, stop sign
n_classes_source = 81
subsampling_indices = []
out_channels = weight.shape[3]
for i in range(int(out_channels/n_classes_source)):
    indices = np.array(classes_of_interest) + i * n_classes_source
    subsampling_indices.append(indices)
subsampling_indices = list(np.concatenate(subsampling_indices))

new_weight = weight[:,:,:,subsampling_indices]


そしてh5ファイルはデータに対して消去処理や作成処理、さらにflush()など特殊な処理をしないといけない。


DESTINATION_WEIGHT_PATH = 'subsampled.h5'
weights_destination_file = h5py.File(DESTINATION_WEIGHT_PATH)

del weights_destination_file['conv6_3']['conv6_3']['kernel:0']

weights_destination_file['conv6_3']['conv6_3'].create_dataset(name='kernel:0', data=new_weight)

weights_destination_file.flush()



これでこのSSDのconv6_3の層のカテゴリー判別ようの重みに関しては、背景と止まれ標識しか認知しなくなった。

2018年2月17日土曜日

多次元配列で最大値とそのインデックスをとる


li = np.random.rand(1,20).reshape(4,5)

# array([[ 0.08902982, 0.07044699, 0.80305672, 0.16506277, 0.48436258],
#        [ 0.34410367, 0.52830111, 0.29357735, 0.52468119, 0.55911187],
#        [ 0.98363013, 0.63727013, 0.3395793 , 0.94851833, 0.42974549],
#        [ 0.48017984, 0.50335503, 0.03280679, 0.82860064, 0.77796785]])


mx = li.argmax(axis=1)
l = []
for i in range(len(li)):
    l.append([[position, value] for position, value in enumerate(li[i])][mx[i]])

# l ->
# [[2, 0.80305672332582601],
# [4, 0.55911186642729027],
# [0, 0.98363012662030236],
# [3, 0.82860063949987028]]

処理のしやすさから l = np.array(l) としてここから使っていく。
物体検知のカテゴリ推論で使いました

https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html

2018年2月16日金曜日

Jupyter notebook で virtualenv を使う


workon my-virtualenv-name
pip install ipykernel
python -m ipykernel install --user --name=my-virtualenv-name

jupyter notebook を再起動したらメニューのKernel → change kernels で仮想環境の名前が出るのでスイッチ

https://help.pythonanywhere.com/pages/IPythonNotebookVirtualenvs/

2018年2月15日木曜日

pandas.DataFrameの中の値をfloatからintにする


In [1]: import pandas as pd

In [2]: csv = pd.read_csv('sample.csv',index_col=0)

In [3]: csv
Out[3]:
         class_id         image_name   xmax   xmin    ymax   ymin
0             Cat       IMG_0001.jpg  850.0  752.0   531.0  459.0
1             Cat       IMG_0002.jpg  508.0  193.0   481.0  260.0
2             Cat       IMG_0003.jpg  542.0  358.0   381.0  266.0
...           ...                ...    ...    ...     ...    ...
145597        Dog       IMG_9998.jpg  569.0  237.0   754.0  423.0
145598        Dog       IMG_9999.jpg  582.0  267.0   831.0  495.0

[145740 rows x 6 columns]

In [4]: csv[['xmin','ymin','xmax','ymax']] = csv[['xmin','ymin','xmax','ymax']].values.astype(int)

In [5]: csv
Out[5]:
         class_id         image_name  xmax  xmin  ymax  ymin
0             Cat       IMG_0001.jpg   850   752   531   459
1             Cat       IMG_0002.jpg   508   193   481   260
2             Cat       IMG_0003.jpg   542   358   381   266
...           ...                ...   ...   ...   ...   ...
145597        Dog       IMG_9998.jpg   569   237   754   423
145598        Dog       IMG_9999.jpg   582   267   831   495

[145740 rows x 6 columns]



・Loopするときとかfloatだと回せなかったりする

2018年2月13日火曜日

JPG画像のexif形式対応

画像を処理する前に、iPhoneなどで撮影した画像はExifというデータを元に上下左右のオリエンテーションを直しておかないと、PILとかで処理するときに勝手にひっくり返ったりしてしまう。
学習中に間違ったデータを与えかねないので、一括で直しておく。

from PIL import Image
import os

img_path = 'img/'

def rota(i):
    image = Image.open(i)
    if hasattr(image, '_getexif'):
        orientation = 0x0112
        exif = image._getexif()
        if exif is not None:
            orientation = exif[orientation]
            rotations = {
                3: Image.ROTATE_180,
                6: Image.ROTATE_270,
                8: Image.ROTATE_90
            }
            if orientation in rotations:
                image = image.transpose(rotations[orientation])
    image.save(i)

for i in os.listdir(img_path):
    rota(img_path+i)

4万枚画像があるので結構時間がかかる