はじめに
タイトルの通り、doc2vec・scikit-learn・Django・Vue.js・Herokuなどいろいろ駆使し、機械学習Webアプリケーションをフルスクラッチ実装した。
機械学習のモデリング手法、またはSPA構築のHowToはそれら単体では紹介されているが、それらをフルスクラッチで構成するノウハウは未だあまり出回っていないように思える。
まずは自分の備忘のため、そして特に作った機械学習モデルをWebアプリ化して公開してみたい人のため、アプリの構成や躓いたポイントなどを記事に残す。
この記事のスコープ
含む内容
- Webアプリを実装するのに使用したライブラリ
- Webアプリのディレクトリ構成
- 機械学習モデルとDjangoの連携
- Vue.js、Django REST Frameworkを使用したSPA(Single Page Application)実装
- Herokuデプロイの設定Tips
含まない内容
- ライブラリ単体の(doc2vec, scikit-learn, ...)説明や、Web開発の基礎(フロントエンドやREST Framework)の説明。各種チュートリアルを参考にされたい。
- 自然言語処理・機械学習の理論の説明。筆者は未だ理解が浅く、理論に関する言及はなるべく控える。
- ソースコード。いつかGithubで公開するかも。
- アプリの精度に対する議論。そもそも文章から性格を判断すること自体ナンセンスだし、学習のためのジョークアプリという位置づけにしたい。
作ったWebアプリの紹介
URL
http://personalityestimator.herokuapp.com/
サービスの説明
- 英文をInputとし、MBTIメソッドに基づいた16タイプの性格診断を行う。
- 16タイプの指標は、2値をもつ4つの指標の乗算(2^4 = 16)で表される。文章を入力することで、それぞれの指標にどれだけ寄っているかをパーセンテージで算出することができる。
- 試しにトランプ大統領のスピーチをInputすると**ENTP - The Debater(討論者)**と評価される。なかなかそれっぽい。
参考:MBTI 4指標(MBTI協会サイトより引用)
指標 | 基準 |
---|---|
興味関心の方向 | 外向(Extroverts) vs 内向(Introverts) |
ものの見方 | 感覚(Sensors) vs 直観(i Ntuitives) |
判断のしかた | 思考(Thinkers) vs 感情(Feelers) |
外界への接し方 | 判断的態度(Perceivers)・知覚的態度(Judgers) |
システムの説明
- 教師データとしてKaggle公開のデータセット「(MBTI) Myers-Briggs Personality Type Dataset」を使用している。レコードの単位は被験者単位で、カラムは性格診断結果(Type)と投稿した文章(Posts)の二つを持つ。
- 学習は、投稿した文章(Posts)をdoc2vecで100次元ベクトル化し、それらに対して2値分類を行うモデル(ロジスティック回帰)を4つ実装している。
備忘録
ランタイム・仮想環境
Pythonバージョンはpython-3.7.3
を使用。
仮想環境はanyenv + pyenv(インタプリタ切り替え) + virtualenv(パッケージ切り替え) + virtualenvwrapperを使用している。
尚、Anacondaは使用していない。当初入れていたがアプリ実装時はすべて削除している。(condaとpip:混ぜるな危険)
ブループリント
当Webアプリのブループリント(構成図)を説明する
No. | コンポーネント | 説明 |
---|---|---|
① | FE |
Vue.jsを採用。axiomライブラリを使用してAPIを非同期でコールし、返却値をレンダリングする。 CDN版を採用し、状態管理(vuex)やルーティング(vue-rooter)は実装していない。 |
② | API | Django REST Frameworkを採用。フォームで入力した文章をリクエストし、モデルの予測結果をレスポンスする。 |
③ | DB | SQ Liteを採用。プロジェクト開始時に自動で付属するだけで、今回特にデータの参照・登録は行っていない。 |
④ | Model | **Gensim (doc2vec)**を単語のベクトル化、Scikit-Learnをモデリングに採用。モデルは各指標ごとに2値分類を行うモデルを4つ実装。 |
⑤ | Server | Herokuを採用。①〜④を丸ごと1つのAppとしてデプロイしている。 |
requirements.txt
仮想環境にインストールした各種ライブラリを説明する(google-api-coreとか、明らかに不要そうなものも入っている気がする・・・)。基本すべてpipでインストール可能。
boto3==1.12.36
botocore==1.15.36
cachetools==4.0.0
certifi==2019.11.28
chardet==3.0.4
dj-database-url==0.5.0
Django==2.2.5 # ①
django-heroku==0.3.1 # ②
djangorestframework==3.10.3 # ①
docutils==0.15.2
gensim==3.8.1 # ③
google-api-core==1.16.0
google-auth==1.13.1
google-cloud-core==1.3.0
google-cloud-storage==1.27.0
google-resumable-media==0.5.0
googleapis-common-protos==1.51.0
gunicorn==20.0.4 # ②
idna==2.9
jmespath==0.9.5
joblib==0.14.1
numpy==1.18.1
pandas==1.0.1
protobuf==3.11.3
psycopg2==2.8.5
pyasn1==0.4.8
pyasn1-modules==0.2.8
python-dateutil==2.8.1
pytz==2019.3
requests==2.23.0
rsa==4.0
s3transfer==0.3.3
scikit-learn==0.22.2.post1 # ③
scipy==1.4.1
six==1.14.0
smart-open==1.10.0
sqlparse==0.3.1
urllib3==1.25.8
whitenoise==4.1.3
①:Django関係のライブラリ
django
はWebフレームワーク、djangorestframework
はその中でもREST API実装特化。両方ともpipでインストールする。
②:Heroku関係のライブラリ
djangoアプリをherokuにデプロイする際に必要となる。django-heroku
をインストールすれば依存パッケージのwhitenoise
やdj-database-url
も一緒にインストールされる。
③:機械学習関係のライブラリ
gensim
, scikit-learn
を導入する。モデルの学習はjupyterで別で学習しているが、モデルの予測処理には必要。他、numpy
, pandas
等も一緒に導入する。
ディレクトリ構成
当Webアプリのディレクトリ構成を説明する。
personalityestimator
├── Procfile # ②
├── apiv1 # ①
│ ├── __init__.py
│ ├── __pycache__
│ ├── admin.py
│ ├── apps.py
│ ├── clfs ③
│ │ ├── logreg_ie.pickle
│ │ ├── logreg_pj.pickle
│ │ ├── logreg_sn.pickle
│ │ ├── logreg_tf.pickle
│ │ └── model.pickle
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── config # ①
│ ├── __init__.py
│ ├── __pycache__
│ ├── local_settings.py # ④
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── get_random_secret_key.py # ④
├── manage.py
├── mbti # ①
│ ├── __init__.py
│ ├── __pycache__
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_.....
│ │ ├── __init__.py
│ │ └── __pycache__
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── requirements.txt # ②
├── runtime.txt # ②
├── static
│ └── style.css
└── templates
└── index.html
①:django プロジェクト・アプリケーション
指定ファイルは以下のコマンドで生成している。
(venv) $ django-admin startproject config .
(venv) $ python manage.py startapp mbti
(venv) $ python manage.py startapp apiv1
1プロジェクト、2アプリケーション(mbti, apiv1)で構成する。
プロジェクト開始時、config .
と指定することで、ベースディレクトリと設定ディレクトリ(config)が同じ名前になることを回避している。アプリケーションはモデルのみを定義するmbti
, REST API機能を有するapiv1
の2つを作成している。
上記コマンド以外では、以下を手動で実行している。
-
apiv1/serializers.py
を追加。REST Framework上でのデータの保持を行う役割を持つ。バリデーション等もこのファイルで定義する。 -
static
、templates
ディレクトリを追加し、静的ファイルやindex.htmlを格納する。settings.py
も同ディレクトリを検索するように一部書き換える。
②:Herokuデプロイ設定
Herokuデプロイ時には、Procfile
、requirements.txt
、runtime.txt
の3つを作成する。Procfile
はアプリを動かす指示書の役割、requirements.txt
は使用するライブラリをherokuに知らせる役割、runtime.txt
は使用するPythonのバージョンを知らせる役割を持つ。
③:モデルの格納
学習済のgensim, doc2vecのモデルをpickle化し、clfs
ディレクトリに格納している。
model.pickle
は単語のベクトル化、logreg_XX.pickle
はそれぞれの指標に対してロジスティック回帰で2値分類する。
④:秘密情報の設定
次節のはまったポイント③を参照。
はまったポイント
①Procfileの設定によるHerokuデプロイエラー
問題
Herokuでデプロイには成功するが、WebアプリにアクセスするとApplication Errorが発生する。
解決策
Procfileのプロジェクト名の記載欄に、間違えてルートディレクトリ名を記載していたことが原因だった。
上記コマンドstartproject config .
の通り、このWebアプリの正しいプロジェクト名はconfig
とである。一般的で紹介されているディレクトリ構成と異なっているためなかなか気づけなかった。
(誤)web: gunicorn personalityestimator(ルートディレクトリ名).wsgi --log-file -
(正)web: gunicorn config(設定ファイル名).wsgi --log-file -
②index.html上のデリミタ「{{}}」がDjango, Vue.jsで重複する
問題
今回Vue.jsはCDN版を採用しているため、index.html
に<script>タグで直接Vue.jsを書き込んでいる。
Djangoテンプレートの変数表示構文とVue.jsのMustache構文が両方ともデリミタ「{{}}」が使用されているため、そのままの設定ではバッティングする。
解決策
Vue.jsのデリミタを変更する。Vue.options.delimiters
でデリミタを「#{}」に変更して衝突を回避する。
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
...
<title>PersonalityEstimator</title>
<!-- CSS -->
<link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<link href="{% static 'style.css' %}" rel="stylesheet">
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
...
</div>
</body>
<script>
Vue.options.delimiters = ['#{', '}'] // ポイント
var app = new Vue({
el: '#app',
data: {
...
},
methods: {
...
})
</script>
③DjangoのSECRET_KEYをプロダクション環境に公開しないようにする
問題
settings.py
に初期値コメント「SECURITY WARNING: keep the secret key used in production secret!」の通り、SECRET_KEY
はプロダクション環境からは見えないようにしたい。
解決策
settings.py
から新たにlocal_settings.py
を複製し、SECRET_KEY
はlocal_settings.py
のみに記載する。settings.py
のSECRET_KEY
はlocal_settings.py
からインポートする。
import os
import django_heroku
from os import environ
from socket import gethostname
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
HOSTNAME = gethostname()
if 'local' in HOSTNAME:
from . import local_settings
SECRET_KEY = local_settings.SECRET_KEY
else:
SECRET_KEY = environ['SECRET_KEY']
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1', '.herokuapp.com']
...
local_settings.py
は.gitignore
に記載しgit管理されないようにする。
DEBUG = False
に書き換えるのも忘れないように。
尚、上記ディレクトリ構成にあったget_random_secret_key.py
は新しくSECRET_KEY
を生成する際に使用した。一度うっかり秘密鍵をgit管理に上げてしまったので、鍵の再生成を行った。
from django.core.management.utils import get_random_secret_key
secret_key = get_random_secret_key()
text = 'SECRET_KEY = \'{0}\''.format(secret_key)
print(text)
④その他
-
django-heroku
のインストール時に同梱されるwhitenoise
は最新バージョンで5系になっている。最新の情報が載っている参考サイトでもほぼ4系を採用していたため、アンインストールした後再度4系をインストールした。 - CSS。拡大・縮小による表示崩れやレスポンシブ対応の工夫をしている。親構造に
max-width
を指定するwrapperクラスをあちこちで指定している。
まだ嵌ってるポイント・Todo
本アプリのWebアプリの残課題を記載する。解決策をご存知の方がいらっしゃればコメントをいただきたい。
- **index.htmlに記載されている<script>をjsファイルに分離し、staticに格納したい。**main.jsファイルを読み込む手法も検証したが、どうやっても正常に読み込まれず断念。
- **clsf(モデル)の格納場所を変えたい。**何となくapiv1の中ではなくstaticの中に置くべきな気がしているが、あるべきが何かを知りたい。
- **apiv1/view.pyをリファクタリングしたい。**Typeの設定("INTJ": "The Architect"など)は同ファイルにベタ書きで持っているので、モデルに移植してマスタテーブル化したい。が、2つのModelを参照してレスポンスする方法がよくわからず断念。
- Modelの精度は上げたい。次は深層学習にチャレンジするつもり。
参考にしたサイト・書籍
①:(Web)doc2vec and logistic regression
URL
https://www.kaggle.com/wpncrh/doc2vec-and-logistic-regression
説明
当アプリで使用するデータセットを使用したKaggleのKernel。
Doc2Vec
による単語のベクトル化や、前処理の方法はこのKernelを参考にしている。本アプリではこのKernelに実装されている前処理に加えて、URLや記号を正規表現で取り除いている。
②:(動画)Build a Machine Learning API with Django
URL
https://youtu.be/tDnAcbYROSI
説明
Django REST FrameworkとMachine Learningをかけ合わせたアプリ実装を全6回で説明している。範囲はモデル実装からDjangoアプリ作成、Herokuにデプロイするところまですべて。
モデルをPickle化してDjangoアプリ内に入れるアイデアはこの動画から着想を得た。一方、Django REST Frameworkの記法は若干怪しいように見受けられるので、Djangoの学習はチュートリアルに沿うことをおすすめする。後半のHerokuデプロイは観ていない。
全編インド人による英語の講義。
③:(書籍)現場で使えるDjangoの教科書
URL
現場で使える Django の教科書《基礎編》
https://booth.pm/ja/items/1308742
現場で使える Django REST Framework の教科書
https://booth.pm/ja/items/1559869
説明
Django及びDjango REST Frameworkを体系的に学ぶには最もおすすめ。
Webアプリのディレクトリ構成や、DjangoとVue.jsの連携、デリミタの重複回避はこの書籍を参考にした。特にDjangoとVue.jsの連携はサンプルプロジェクトがそのまま載っているので大変参考になる。
④(Web)『完全版』Djangoアプリをherokuにデプロイ!
URL
http://digital-tree.xyz/blogs/1169
説明
DjangoアプリケーションをHerokuにデプロイする手順が記載されている。
2019年9月の記事のため(2020年5月現在は)、比較的最新。
⑤(Web)Djangoでの"秘密にしたい値"の取り扱い - ティッシュ残り一枚
URL
https://tsukachu.hatenablog.com/entry/handling_secret_keys
説明
Heroku、Github等で秘密鍵を公開しないようにするための実装方法の記載がある。
嵌ったポイント③「DjangoのSECRET_KEYをプロダクション環境に公開しないようにする」は本記事を参考にしている。
⑥(書籍)1冊ですべて身につくHTML & CSSとWebデザイン入門講座
URL
https://www.amazon.co.jp/dp/4797398892/ref=cm_sw_em_r_mt_dp_U_Of2VEbSY2RKCE
説明
HTMLとCSSの説明。
アプリ構想時点でCSSが全く分からなかったのでこの書籍で1から学習した。スマホ・PC両対応したレスポンシブデザインを作るのにも参考にしている。
⑦その他
Python自体はPythonデータサイエンスハンドブックや、みんなのPython 第4版で学習した。
終わりに
筆者はエンジニアではなく、趣味で一人コードを書いている。このアプリも空き時間を使って、構想から約半年かけて実装した。何でも調べれば出てくる時代にはなったが、その情報源を蓄積・公開している先人たちには感謝したい。この記事も、機械学習Webアプリを作りたい誰かのヒントになればよいなと思う。