洋食の日記

「だ・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

scikit-learnで学習した分類器をjoblib.dumpで保存するときはcompressをTrueにするとファイルが一つにまとまって便利

scikit-learnで学習した分類器を保存する場合、joblib.dumpを使用するが、これだと、大量のnpyファイルが作られる。この場合、joblib.dumpのcompressを使うとよい。まず、例えば以下のような、train.pyがあるとする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sklearn.datasets import load_svmlight_file
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import LinearSVC
from sklearn.externals import joblib

def main():
  # MNISTの訓練データセットを読み込む。
  tr_samples, tr_labels = load_svmlight_file('mnist.scale')

  # one-versus-restで線形SVM分類器を学習する。
  # n_jobsを-1にするとパラレルで実行してくれる。
  classifier = OneVsRestClassifier(LinearSVC(), n_jobs=-1)
  classifier.fit(tr_samples, tr_labels)

  # 学習した分類器を保存する。
  joblib.dump(classifier, 'svc.pkl')

if __name__ == '__main__':
  main()

これを実行すると、大量のnpyファイルが作られる。まあ美しくない。

$ ./train.py
$ ls -1
svc.pkl_01.npy
svc.pkl_02.npy
svc.pkl_03.npy
...
svc.pkl_51.npy
svc.pkl
train.py
mnist.scale
mnist.scale.t

学習した分類器を一つのファイルに保存したい場合は、joblib.dumpの部分でcompress引数にTrueを指定する。compressは0から9の整数をとり、値が大きくなるほど圧縮率が高まる。 Trueを指定するとcompress=3と同じになる。拡張子はなんでも良いけど、「圧縮しましたよ」ということで「.cmp」をつけてみた。

joblib.dump(classifier, 'svc.pkl.cmp', compress=True)

これを実行すると、学習した分類器が、たった一つのファイルにまとめられる。

$ ./train.py
$ ls -1
svc.pkl.cmp
train.py
mnist.scale
mnist.scale.t

読み込みは、圧縮しない場合と同様で、joblib.loadで良い。例えば以下のような、test.pyを用意する。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from sklearn.datasets import load_svmlight_file
from sklearn.externals import joblib
from sklearn.metrics import accuracy_score

def main():
  # MNISTのテストデータセットを読み込む。
  te_samples, te_labels = load_svmlight_file('mnist.scale.t')

  # 学習した分類器を読み込む。
  classifier = joblib.load('svc.pkl.cmp')

  # パラメータを表示してみる。
  print classifier

  # ラベルを推定する。
  pr_labels = classifier.predict(te_samples)

  # Accuracyを計算してみる。
  print accuracy_score(te_labels, pr_labels)

if __name__ == '__main__':
  main()

これを実行すると、one-versus-restな線形SVM分類器がちゃんと読み込まれているのがわかる。

$ ./test.py
OneVsRestClassifier(estimator=LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss=l2, multi_class=ovr, penalty=l2,
     random_state=None, tol=0.0001, verbose=0),
          estimator__C=1.0, estimator__class_weight=None,
          estimator__dual=True, estimator__fit_intercept=True,
          estimator__intercept_scaling=1, estimator__loss=l2,
          estimator__multi_class=ovr, estimator__penalty=l2,
          estimator__random_state=None, estimator__tol=0.0001,
          estimator__verbose=0, n_jobs=-1)
0.918

圧縮されているので、ファイルサイズもそれなりに小さい。 libsvmやliblinearをコマンドで使うと、重みとかパラメータが書かれたテキストファイルが作られる。 MNISTぐらいだと大したことないけど、特徴ベクトルの次元数が大きいと、ファイルサイズも大きくなってツラい。 そういう意味でも良い。

$ liblinear-train -q -s 2 mnist.scale mnist.scale.model
$ ls -l mnist.scale.model | awk '{print $4"\t"$8}'
147K    mnist.scale.model
$ ls -l svc.pkl.cmp | awk '{print $4"\t"$8}'
55K     svc.pkl.cmp

NginxでUserDir的なことするのは設定ファイルにlocationを書くだけで良い

Apache2からNginxへの移行を進めていて、UserDir的なことをしようと思ったら、mod_userdirを有効にする感じではなかった。Debian GNU/Linuxであれば、/etc/nginx/sites-available/defaultを編集するだけでよい。PHPも動くようにしたいので、ついでにそれも設定する。

$ sudo vim /etc/nginx/sites-available/default
...省略

  # 最初からコメントで「PHP使うならindex.phpを追加しなさいよ」と書いてある。
  # Add index.php to the list if you are using PHP
  index index.php index.nginx-debian.html index.html index.htm;

...省略

  # for UserDir
  location ~ ^/~([^/]+)/(.+\.php)$ {
    alias /home/$1/public_html/$2;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $request_filename;
  }
  location ~ ^/~(.+?)(/.*)?$ {
    alias /home/$1/public_html$2;
  }

...省略

基本的には、正規表現で拾ったものを、aliasに渡しているだけ。これで再起動して設定を反映すればおk。

$ sudo systemctl restart nginx

とっても簡単。本日も先人に感謝。

DebianでNginx上でPHPを動かすのはaptでPHP-FPMを入れて設定ファイルをちょっと編集するだけで良い

Nginxの設定ファイルの接しやすさに感動して、Apache2からNginxに完全移行することにした。 WebツールやデモシステムをPHPで作っていたので、とりあえず、PHPが動くようにする。 作業自体は大したことなくて、PHPFastCGI実装のPHP-FPM(FastCGI Process Manager)をインストールすれば良い。

$ sudo apt-get install php5-fpm

設定ファイルとかインストールした時点で良い感じになっている。/etc/php5/fpm/pool.d/www.confを見ると、

$ vim /etc/php5/fpm/pool.d/www.conf
...
listen = /var/run/php5-fpm.sock
...

socketとして/var/run/php5-fpm.sockがあるのがわかる。phpのリクエストがきたら、このsocketに渡せば良い。 これもnginxの設定ファイルにすでにコメントの形で書いてあって、その部分をコメントアウトすれば良い。

$ sudo vim /etc/nginx/sites-available/default
...
location ~ \.php$ {
  include snippets/fastcgi-php.conf;

  # With php-fpm (or other unix sockets):
  #fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  # With php-cgi (or other tcp sockets):
  #fastcgi_pass 127.0.0.1:9000;
}

再起動して設定を反映させる。※PHP-FPMの設定ファイルを変更してないけど勢いで再起動しちゃった。

$ sudo systemctl restart php5-fpm.service
$ sudo systemctl restart nginx

以下の内容で、/var/www/html/hoge.phpとか置いてアクセスしてみると、Server APIがFPM/FastCGIになってたりして上手く動いているのがわかる。

<?php
phpinfo();

超簡単でまったくつまづくことがなかった。先人にマジ感謝。

TheanoなKerasをデプロイするときはNginx+uWSGI+Flaskが良さそう

Kerasで作った画像認識プログラムを、Webサービスの形にしてみようと思い色々ためした。 画像認識処理をAPIの形で立ち上げ、フロントから叩くことにした。 複雑で大規模な構造のAPIにはならないので、フレームワークにはFlaskを選択した。 はじめ、Apache2+mod_wsgiを考えたが、設定ファイルがなんだか面倒なのと、 APIを叩くたびに「Using Theano backend.」とかimportから始まるのでツラい(これも設定ファイルでなんとかなるのかもだけど掘り下げてない)。 KerasなのにTensorFlowじゃないのは、Kerasが出たての頃から使ってて~/.keras/keras.jsonがそんな設定になってるからで、こだわりではない。 そんなわけで、まずは、TheanoなKerasのFlaskアプリを用意する。ちなみに、OSはDebian GNU/Linuxです。

# -*- coding: utf-8 -*-

# 学習するわけではないのでCPUモードで起動する。
# 環境変数をプログラム中で変えることでKerasとTheanoを設定する。
import os
os.environ['KERAS_BACKEND'] = 'theano'
os.environ['THEANO_FLAGS'] = 'mode=FAST_RUN,device=cpu,floatX=float32'
# Kerasがらみ
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
from keras.models import Model
from keras.models import model_from_json
# Flaskがらみ
from flask import Flask

# 省略...(学習したモデルを読み込んだりとか)

app = Flask(__name__)
@app.route('/')
def index():

# 省略...(認識した結果をJSONで返したりとか)

if __name__ == '__main__':
  app.run()

Nginxの設定ファイルを書く。sites-available以下に書いて、sites-enabledからシンボリックリンクを張るのが、DebianなNginxのマナー。 5000番ポートをlistenしてるのは、Flask単体でrunしたとき、localhost:5000でサーバーが動くので、その名残り。意味はない。location内のほうが大事で、適当なsocket(/hoge/uwsgi.sock)を用意して、これを介してuwsgiにつなぐ。設定の楽さに感動。

$ sudo vim /etc/nginx/sites-available/uwsgi
server {
  listen 5000;
  listen [::]:5000;

  location / {
    include uwsgi_params;
    uwsgi_pass unix:/hoge/uwsgi.sock;
  }
}
$ cd /etc/nginx/sites-enabled
$ sudo ln -sn /etc/nginx/sites-available/uwsgi

そして、uwsgiでFlaskアプリを動かす。uwsgiの設定ファイルを用意すると、オプションの指定が楽になるんだろうけど、とりあえず試したいだけなので、コマンドで実行する。

# Flaskアプリのファイルはhoge.pyだとして...
$ /usr/local/bin/uwsgi -s /hoge/uwsgi.sock --wsgi hoge:app --chmod-socket=666
*** Starting uWSGI 2.0.14 (64bit) on [Sun Mar 12 15:20:11 2017] ***

...

*** Operational MODE: single process ***
Using Theano backend.
WSGI app 0 (mountpoint='') ready in 7 seconds on interpreter 0x1a2e1e0 pid: 10892 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 10892, cores: 1)

この状態で、APIをいくら叩いても「Using Theano backend.」とかならなかったので、とても良い。 Kerasでは~/.keras以下に設定ファイルや学習済みCNNモデルなどを置くので、デプロイするためのユーザーを用意したほうが良いかもしれない。 ちなみに、Apache2+mod_wsgiAPIを立ち上げたときは、/var/www/.kerasや/var/www/.theanoが作られた。

ポート番号が被らなければNginxとApache2を共存できる

Debian GNU/LinuxにApache2を入れてWebサーバーを構築している。Nginxを試してみたいので、共存させることを試みた。 まずは、インストールする。ここで、先にApache2を止めておかないと、Nginxのインストールがコケる。

$ sudo systemctl stop apache2.service
$ sudo apt-get install nginx
$ sudo systemctl stop nginx.service
$ sudo systemctl start apache2.service

Apache2が立ち上がっている状態で、Nginxを立ち上げようとすると当然コケる。

$ sudo systemctl start nginx.service
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xe" for details.

Nginxの設定ファイルを変更し、ポート番号を80から5000(適当に空いているポート番号)にする。

$ vim /etc/nginx/sites-enabled/default
...
# Default server configuration
#
server {
  #listen 80 default_server;
  #listen [::]:80 default_server;
  listen 5000 default_server;
  listen [::]:5000 default_server;
...
  # Add index.php to the list if you are using PHP
  #index index.html index.htm index.nginx-debian.html;
  index index.nginx-debian.html index.html index.htm;
...

ここで、index.nginx-debina.htmlを手前にもってきたのは、/var/www/html以下にApache2のindex.htmlと、Nginxのindex.nginx-debian.htmlが配置されているため。これで、Nginxが問題なく立ち上がる。

$ sudo systemctl start nginx.service

5000番にアクセスするとNginxのWelcomeページが表示される。

$ links http://localhost:5000/
Welcome to nginx on Debian!
...

色々と試してみて、最終的にはNginxに完全移行したい。