#1 はじめに
keyword: Django Apache wsgi Ubuntu Android keras tensorflow MNIST VGG16
1.1 経緯
僕は常頃からAndroidメインでやっている人なんですが、サーバーとかに関するもの(いわゆるオープン系)は触ったことないのでやってみようと思ったのが最初です。そこからhttpとかポートとかPOSTとGETとかいろいろ覚えて、何かいいものないかなと言うことで「アプリ側はAndroid、サーバー側はPython」とかいうのを思いついてしまった次第です。**結構頑張って書いた記事なので既読感覚でいいねをしてもらっていいですか!!**よろしくお願いしまーす。
1.2 初のオープン系
POSTっちゅうのがあるみたいですね。GETは前の記事のQueryか。なるほど、、、。あっそう。
って感じで流しました。実装ではあまり考えませんでしたから支障はないでしょう。結果としてオープン系は異言語をつなげることができるんだなーと思ったわけですし。
1.3 スペック、環境(注意:pythonは3.6以下のものを使ってください!そうしないと死にます。私が経験済みです!!!!!)
2019/1/1 現在
OS : Windows10(64bit) #使いませんでした
: Ubuntu 18.04.1 LTS(64bit) #こっちのみ
プロセッサ(参考): Intel® Core™ i7-2700K CPU @ 3.50GHz × 8
Python version : 3.6.7 #ココ重要
Apache2 version: 2.4.29
Django version : 2.1.4
Pip version : pip 18.1 from /usr/local/lib/python3.6/dist-packages/pip (python 3.6)
Pythonライブラリのバージョン↓
Package Version
----------------------- ---------
absl-py 0.6.1
apturl 0.5.2
asn1crypto 0.24.0
astor 0.7.1
Brlapi 0.6.6
certifi 2018.1.18
chardet 3.0.4
command-not-found 0.3
cryptography 2.1.4
cupshelpers 1.0
defer 1.0.6
distro-info 0.18
Django 2.1.4
gast 0.2.0
grpcio 1.17.1
h5py 2.9.0
httplib2 0.9.2
idna 2.6
Keras 2.2.4
Keras-Applications 1.0.6
Keras-Preprocessing 1.0.5
keyring 10.6.0
keyrings.alt 3.0
language-selector 0.1
launchpadlib 1.10.6
lazr.restfulclient 0.13.5
lazr.uri 1.0.3
louis 3.5.0
macaroonbakery 1.1.3
Mako 1.0.7
Markdown 3.0.1
MarkupSafe 1.0
mod-wsgi 4.6.5
netifaces 0.10.4
numpy 1.13.3
oauth 1.0.1
olefile 0.45.1
opencv-contrib-python 3.4.5.20
opencv-python 3.4.5.20
pexpect 4.2.1
Pillow 5.3.0
pip 18.1
protobuf 3.6.1
pycairo 1.16.2
pycrypto 2.6.1
pycups 1.9.73
pygobject 3.26.1
pymacaroons 0.13.0
PyNaCl 1.1.2
pyRFC3339 1.0
python-apt 1.6.3
python-debian 0.1.32
pytz 2018.3
pyxdg 0.25
PyYAML 3.12
reportlab 3.4.0
requests 2.18.4
requests-unixsocket 0.1.5
scipy 1.2.0
screen-resolution-extra 0.0.0
SecretStorage 2.3.1
setuptools 39.0.1
simplejson 3.13.2
six 1.11.0
ssh-import-id 5.7
system-service 0.3
systemd-python 234
tensorboard 1.12.1
tensorflow 1.12.0
termcolor 1.1.0
ubuntu-drivers-common 0.0.0
ufw 0.35
unattended-upgrades 0.1
urllib3 1.22
usb-creator 0.3.3
wadllib 1.3.2
Werkzeug 0.14.1
wheel 0.30.0
xkit 0.0.0
zope.interface 4.3.2
もしなぜかエラーが出てくる場合にはバージョンを確認してみてください。
2 サーバー側の実装
##2.1 Python・Django・Apache2・wsgiのインストールと新しいプロジェクトの立ち上げ方
これが手本です↓ありがとうございます。
https://qiita.com/itisyuu/items/dafa535adc8197208af1
とても参考になるのでいろいろ助けてもらいました。
###Python
$ sudo apt install python3-pip python3-dev
$ sudo apt install python-pip python-dev
$ python -V
Python 3.6.7
必要ならばpython3と打てばpython3.6が呼び出されるようにしてください。
###Django
$ pip3 install django
$ apt install python-django-common
$ django-admin --version
###Apache
$ sudo apt-get update
$ sudo apt-get install apache2
$ apt-get install apache2-dev
$ systemctl restart apache2
ここにアクセスしてください↓
http://localhost/
ここはあなたが使用しているパソコンのサーバーです。ここでIt Works!
と出力されるとインストールできているということです。
こちらでもかまいません。
http://127.0.0.1/
127.0.0.1はIPアドレスといい、ネットワーク上の住所です。127.0.0.1は自分
を表します。
以下のコマンドで自分のIPアドレスを知ることができます。
$ ifconfig
enp13s0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether xxxxxxxxxx txqueuelen 1000 (イーサネット)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 17
enx3476c5229d10: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.xxx.x.x netmask 255.255.255.0 broadcast 192.xxx.x.255
inet6 xxxxxxxxxxxxxxxxxxxx prefixlen 64 scopeid 0x20<link>
ether xxxxxxxxxx txqueuelen 1000 (イーサネット)
RX packets 55892 bytes 24881116 (24.8 MB)
RX errors 0 dropped 722 overruns 0 frame 0
TX packets 33614 bytes 11814780 (11.8 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (ローカルループバック)
RX packets 5036 bytes 1293963 (1.2 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5036 bytes 1293963 (1.2 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
この場合だと10行目の192.xxx.x.x
がIPアドレスです。
http://192.xxx.x.x/
にアクセスすれば同じものが見れるはずです。
###mod_wsgi
ApacheとDjangoを連携するモジュール(mod)です。
$ pip3 install mod_wsgi
$ sudo mod_wsgi-express install-module
ここでwsgiのインストールに使ったpip3がpython3.6配下のものであるか確認してください
###DjangoとApacheをwsgiでくっつける
$ mod_wsgi-express install-module
これで出てきたものをコピーしましょう。ちなみに私はこれでした。↓(python3.6の人は下と同じものが出てくるかもです。)
LoadModule wsgi_module "/usr/lib/apache2/modules/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so"
WSGIPythonHome "/usr"
そして
$ vi /etc/apache2/apache2.conf
これでvimが開きます。なれない人は
$ gedit /etc/apache2/apache2.conf &
その最終行にさっきコピーしたものをそのまま入れます。そして閉じましょう!保存を忘れずに。
ここまでがインストール等です。新しいプロジェクトを生成しましょう。
###プロジェクトの立ち上げ方
以下のコマンドで新しいdjangoのプロジェクトを作りましょう。
今回はサイト名(プロジェクトの名前)をDjangoApp001
とします。
~$ mkdir DjangoApp001
~$ cd DjangoApp001
~$ django-admin startproject DjangoApp001
以下のコマンドで新しいApacheのサイトを作りましょう。/etc/apache2/sites-available/
にはサイトの設定ファイルがいくつもあります。ここで新しいサイトを作るのです。
$ vi /etc/apache2/sites-available/django.conf
or
$ gedit /etc/apache2/sites-available/django.conf &
開いた無地ファイルの中に以下のことを入力します。
(username)
はしっかり変えてください。
<VirtualHost *:80>
WSGIDaemonProcess DjangoApp001 python-home=/usr python-path=/home/(username)/DjangoApp001
WSGIScriptAlias / /home/(username)/DjangoApp001/DjangoApp001/wsgi.py process-group=DjangoApp001
<Directory /home/(username)/DjangoApp001/DjangoApp001>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
</VirtualHost>
ファイルの構成はこうなります。(django-admin
をした時点でこのような構成にはなっています。)
ここまでのファイル構成
/home
/(username)
/DjangoApp001
/DjangoApp001
wsgi.py
さて、これからサーバー側に変更を加えるごとにサーバーの再起動をしなければなりません。その時は以下のようにすればOKです。
ストップ
$ sudo systemctl stop apache2
スタート
$ sudo systemctl start apache2
再起動(ストップ&スタート)
$ sudo systemctl restart apache2
わかっていると思いますがここで構築したサーバーは同じルーター内でのみアクセスでき、パソコンが動いている状態でないと使用できません。同じルーター内でパソコンとサーバーが起動していればスマホでもノートパソコンでもPSVitaでもアクセスできます。
##2.2 実装
まずはGUI操作で画像ファイルをアップロードして保存するものを作りましょう。
ほとんどパクリですっ...すみません...↓ありがとうございます。
https://qiita.com/narupo/items/e3dbdd5d030952d10661
ここから先何も書いてなければhome/(username)/DjangoApp001/DjangoApp001/
内のもの
settings.py
このプロジェクトのDjango内の定義を行います。
......
ALLOWED_HOSTS = ['192.xxx.x.x'] #もともと書いてあるので192.xxx.x.x(自分のIPアドレス)(127.0.0.1はNG)を追加してください
......
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
static
とは静止ファイルつまり実行できない画像や動画のことです。(動画は動くけどなぁ〜って思ったそこの君ぃ!動画は実行できないだろぅ〜?)
mediaと指定すれば
home/(username)/DjangoApp001/media
が作られそこにstatic
が保存されます。
urls.py
url関連などの設定を行います。
urlpatterns = [
url(r'^/?$', views.index, name='index'),# views.pyで最初に呼ぶ関数
]
......
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
デバッグを追加することでなんのエラーが出たかをきれいに表示してくれます。
###models.py
ここからが正念場です。
models.py
ではこのサーバーで扱うデータを定義します。今回は画像だけですので一つです。
データベースに保存するデータの箱を定義すると言っていいでしょう。
from django.db import models
class Photo(models.Model):
image = models.ImageField(upload_to='DjangoApp001') #変更可: プロジェクトの名前をどうぞ
###forms.py
forms.py
ではmodels.py
の箱と対比して外部からデータを入れるためのパイプの種類を定義します。今回は画像のみです。文字列を取りたければCharFieldです。ここで設定したものはのちのindex.html
に影響します。
from django import forms
class PhotoForm(forms.Form):
image = forms.ImageField()
###home/(username)/DjangoApp001/DjangoApp001/templates/DjangoApp001/index.html
サイトのレイアウトです。PythonとHtmlを混ぜた構文です。
<html>
<title>マルチパートのテストページ for django and Java</title>
<body>
<hr>
<form action="{% url 'index' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<input type="submit" value="投稿"/>
</form>
<hr>
</body>
</html>
{{ form }}
の部分でforms.pyの内容を反映してます。ImageField
ならファイルピッカーが自動で備え付けられCharField
なら文字列を入力するところが生成されます。すごいですよね。
###views.py(珍しくオリジナル)
ここでPythonします。(以上)
っとその前に..重みデータとかをしっかり保存しときましょうでもいいですよ。
ないのであれば僕が作った重みファイルとモデルデータをダウンロードしてください。(近日公開します、すみません)
生成したい人はぜひこれを参考に...https://qiita.com/Cyber_Hacnosuke/items/9b6f561632a56598bb56
重みデータ等はhome/(username)/DjangoApp001/DjangoApp001/data/にでも入れときましょう。
結構悩んだりしたのでこのソースコード気に入ってるんですよ(w)
import os
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.shortcuts import render, redirect
from .forms import PhotoForm
from .models import Photo
from keras.models import load_model
from keras import backend as K
from PIL import Image
import numpy as np
import cv2
# 親ディレクトリを取得する関数です。 https://qiita.com/elu697/items/56d7f5e74ee25bb8d234
def parentPath(path1=__file__, f=0):
return str('/'.join(os.path.abspath(path1).split('/')[0:-1-f]))
@csrf_exempt
def index(request):
if request.method == 'GET': #GETでアクセス(一回目)
return render(request, 'MnistScanner/index.html', {
'form': PhotoForm(),
'photos': Photo.objects.all()
}) # index.htmlをもとにサイトを生成(レンダリング)して表示
elif request.method == 'POST':# POSTでアクセス(画像とともにくる。2回目以降)
form = PhotoForm(request.POST, request.FILES)# POSTデータとFILE(画像)の準備
if not form.is_valid():# データが不足していた場合
raise ValueError('invalid form')
photo = Photo()
photo.image = form.cleaned_data['image'] #'image'はindex.htmlでのデータの"name"、画像を取る
photo.save() #mediaへ保存
filename = photo.image.name #画像のファイル名
returnAnswer = myChallenge(filename) #判定する
return HttpResponse(str(returnAnswer))
def myChallenge(filename):
#データの形を変数に
image_rows = 28
image_cols = 28
image_color = 1 #グレースケールのこと
input_shape = (image_rows, image_cols, image_color)
#親の親のパス
parent = parentPath(__file__, 1)
img = Image.open(parent + '/media/' + str(filename)).convert('L') #画像のロード
#画像を28x28に変換
img.thumbnail((image_rows, image_cols))
#フロート型の行列に変換
img = np.array(img, dtype=np.float32)
img = img.reshape(-1, image_rows, image_cols, image_color)
#黒0~255白の画像データをMNISTのデータと同じ白0~1黒に変える
img = 1 - np.array(img / 255)
parentDir = parentPath(__file__, 0)
model = load_model(parentDir + '/data/MNIST-model.h5')
model.load_weights(parentDir + '/data/MNIST-weights.h5')
answer = model.predict_classes(np.array(img))
#★ポイント
K.clear_session()
return(str("The computer guesses that this figure is \"" + str(answer[0]) + "\"!"))
ポイントの部分で実に3日かかりました。その頃にはAndroid側もできて手書きのデータを送れるようになっていたのに、一回目はしっかり判定できてよっしゃ!ってなりますが、二回目やると500エラー。つまりサーバー側のエラーが起こって、LOGみてもなんじゃこれ状態でもう...(泣)自分は英語に自信がないので日本語にこだわって調べましたがむだでした。日本語にこだわり続けて3日。間違えて押してしまった英語のGithubの質問サイトで目にしたのは解決されたものでした。
どうやらDjangoでtensorflowやkerasをやっている人は皆こうなるそうです。セッションとやらをクリーンしなきゃいけないってそのままやん!こうすると何回も判定できます。
たった一行でこんな悩むのか!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
(ちなみにPython3.7を使ってたのでさらに3日ほど3.7でどうにかtensorflowを使おうと躍起になっておりました。)
重みデータとかのパスはご自由に。
#まとめと注意
今回は第一弾です!DjangoはとてもいいFrameWorkなので機械学習サイトにはもってこいです!
注意ですがPOSTはPOSTでも普通のではなくファイルも遅れるマルチパート送信ですのであしからず。
次回はAndroid側の実装です!なお、Androidいらねぇって方でも今のままで利用できます。Androidでマルチパート送信できちゃいます!
ではまた今度!!!
Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
Github: https://github.com/CyberHacnoshuke
#【追記:投稿直後】
サーバーによってはハッキングや不正な連続アクセスのアタック(DOS攻撃)から守るためにCSRF対策が有効になってる場合があります。一般的にはHTMLがしっかり認証してくれるそうですが、Androidなどからアクセスするともちろん跳ね返されます。
セキュリティ面では危ないですがCSRF対策をオフにしましょう。フレームワークごとに違いますが、Djangoではメソッド前に@csrf_exempt
を付けることで解除できそうです。(上にしれっと書いてある)なお、メソッドごとにしか効かないのでプロジェクトごと無効にしたいのであれば別の方法でできます。(すみません、調べてません)
CSRFについて知りたい方はどうぞ↓
https://qiita.com/maruloop/items/e14d02299bd136f4b1fc