Pythonと機械学習を活用し、花の種類を判定するWebアプリケーションを作ろう!

はじめに

この章では、Scikit-learnとFlaskを使ってアイリスの花の入力から、3種類の花の中から1つを推測するWebアプリケーションを開発します。
Scikit-learnやFlaskに関して既知という前提で進めていきます。

環境は、Python3.6以上に、scikit-learn、flask、Werkzeug、WTFormsなどのモジュールを事前にインストールしてあることを前提に進めます。

それぞれ、次のコマンドでモジュールをインストールすることが可能です。
(Macですとターミナル、Windowsですとコマンドプロンプトを起動し、次のコマンドを入力し実行(Enter)してください)

pip install sklearn flask 

万が一、モジュールエラーが出た場合は、別途そのエラーのモジュールをpipコマンドでインストールしてください。
今回作成したソースコードはこちらからzipファイルをダウンロード可能です。

完成版

プログラムの全体像

アイリスの花のデータを学習し、ユーザーが、アイリスの花の萼片の長さや幅などの4つの数値・小数点のデータを入力することで、その長さから何の花かを出力(推測)することができるWebアプリケーションを作っていきます。

まず、フロントエンド側から萼(がく)の長さや、花びらの長さなどを入力し、判定(送信)ボタンを押します。その値がバックエンド(flask + scikit-learn)側に送られます。

バックエンド側で入力した値からそれがアイリスのどの花かをフロントエンドに返します。

ファイル構成と準備

今回は、app.pyと、nn.py、templatesフォルダとその中に、index.htmlとresult.htmlの合計4つのファイルを作成します。
フォルダは、それら4つをまとめたiris_predictフォルダとその中に、templatesフォルダの2つを作成します。

.
├── app.py
├── nn.pkl
├── nn.py
├── templates
    ├── index.html
    └── result.html

1 directories, 5 files

では、まずiris_predictフォルダを作成していきます。

右クリックを押すと次のようなポップアップが表示されます。
新規フォルダというのを選択します。

名称未設定フォルダというファイル名が作成されますので、
名前の部分をクリックするとフォルダ名を変更できますので、iris_predictという名前に変更しましょう。

これから作成するプログラムは全て、iris_predictフォルダの中に保存していきます。

モデルを作成する

では、まず機械学習プログラムの部分から作っていきましょう。
使うアルゴリズムはニューラルネットワークです。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.externals import joblib
from sklearn.metrics import classification_report, accuracy_score
# import pickle

# データ取得
iris = load_iris()
x, y = iris.data, iris.target

# 訓練データとテストデータに分割
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.5, random_state=0)

# solverには確率的勾配降下法(sgd)やadamなどが利用可能です。
model = MLPClassifier(solver="sgd", random_state=0, max_iter=3000)

# 学習
model.fit(x_train, y_train)
pred = model.predict(x_test)

# 学習済みモデルの保存
joblib.dump(model, "nn.pkl", compress=True)

# filename = 'nn.sav'
# pickle.dump(model, open(filename, 'wb'))

# 予測精度
print("result: ", model.score(x_test, y_test))
print(classification_report(y_test, pred))

このプログラムを今回はnn.pyとして保存します。
実行すると、nn.pklというファイルが作成されます。
このファイルが後に使用する学習済みモデルになります。

FlaskでWebアプリケーションを作る

Flaskを使って、Webサーバ側のプログラムを実装していきます。
保存するファイル名は、app.pyとします。

predict関数とgetName関数を作っていきます。
predict関数では、先ほどのnn.pyで学習して作成された学習済みモデルを読み込み、引数で受け取ったparametersを学習済みモデルに渡しています。
その結果[0]か[1]か[2]が変数predに代入されその値をreturnしています。

getName関数では、predict関数の返り値をgetName関数に渡すことで、ラベルから花の名前を返しています。

app.py

from flask import Flask, render_template, request, flash
from wtforms import Form, FloatField, SubmitField, validators, ValidationError
import numpy as np
from sklearn.externals import joblib

# 学習済みモデルを読み込み利用します
def predict(parameters):
    # ニューラルネットワークのモデルを読み込み
    model = joblib.load('./nn.pkl')
    params = parameters.reshape(1,-1)
    pred = model.predict(params)
    return pred

# ラベルからIrisの名前を取得します
def getName(label):
    print(label)
    if label == 0:
        return "Iris Setosa"
    elif label == 1: 
        return "Iris Versicolor"
    elif label == 2: 
        return "Iris Virginica"
    else: 
        return "Error"

app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = 'zJe09C5c3tMf5FnNL09C5d6SAzZoY'

# 公式サイト
# http://wtforms.simplecodes.com/docs/0.6/fields.html
# Flaskとwtformsを使い、index.html側で表示させるフォームを構築します。
class IrisForm(Form):
    SepalLength = FloatField("Sepal Length(cm)(蕚の長さ)",
                     [validators.InputRequired("この項目は入力必須です"),
                     validators.NumberRange(min=0, max=10)])

    SepalWidth  = FloatField("Sepal Width(cm)(蕚の幅)",
                     [validators.InputRequired("この項目は入力必須です"),
                     validators.NumberRange(min=0, max=10)])

    PetalLength = FloatField("Petal length(cm)(花弁の長さ)",
                     [validators.InputRequired("この項目は入力必須です"),
                     validators.NumberRange(min=0, max=10)])

    PetalWidth  = FloatField("petal Width(cm)(花弁の幅)",
                     [validators.InputRequired("この項目は入力必須です"),
                     validators.NumberRange(min=0, max=10)])

    # html側で表示するsubmitボタンの表示
    submit = SubmitField("判定")

@app.route('/', methods = ['GET', 'POST'])
def predicts():
    form = IrisForm(request.form)
    if request.method == 'POST':
        if form.validate() == False:
            flash("全て入力する必要があります。")
            return render_template('index.html', form=form)
        else:            
            SepalLength = float(request.form["SepalLength"])            
            SepalWidth  = float(request.form["SepalWidth"])            
            PetalLength = float(request.form["PetalLength"])            
            PetalWidth  = float(request.form["PetalWidth"])

            x = np.array([SepalLength, SepalWidth, PetalLength, PetalWidth])
            pred = predict(x)
            irisName = getName(pred)

            return render_template('result.html', irisName=irisName)
    elif request.method == 'GET':

        return render_template('index.html', form=form)

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

templatesフォルダを作成し、templatesフォルダ内に、index.htmlとresult.htmlを作成します。

次の内容を記述し、index.htmlというファイル名で保存し、templatesフォルダ内に保存してください。

index.html

<title>Iris Predict App</title>
<style>
#wrapper {
   text-align: center;
}
</style>

      <div id="wrapper">
         {% for message in form.SepalLength.errors %}
            <div>{{ message }}</div>
         {% endfor %}

         {% for message in form.SepalWidth.errors %}
            <div>{{ message }}</div>
         {% endfor %}

         {% for message in form.PetalLength.errors %}
            <div>{{ message }}</div>
         {% endfor %}

         {% for message in form.PetalWidth.errors %}
            <div>{{ message }}</div>
         {% endfor %}

         <form method="post">
            {{ form.SepalLength.label }}<br>
            {{ form.SepalLength }}
            <br>
            {{ form.SepalWidth.label }}<br>
            {{ form.SepalWidth }}
            <br>
            {{ form.PetalLength.label }}<br>
            {{ form.PetalLength }}
            <br>
            {{ form.PetalWidth.label }}<br>
            {{ form.PetalWidth }}
            <br>
            {{ form.submit }}
         </form>
      </div>

次の内容を記述し、result.htmlというファイル名で保存してください。

<!DOCTYPE html>
<title>Iris Predict App</title>
{% if irisName %}
  <h1>これは {{ irisName }} です</h1>
  <a href="./">トップへ戻る</a>
{% else %}
  <a href="./">トップへ戻る</a>
{% endif %}

動作確認

app.pyを次のようにして起動します。

python app.py

ターミナルやコマンドプロンプトから次のような出力されます。

 * Serving Flask app "app" (lazy loading)
 * 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:5000/ (Press CTRL+C to quit)

http://127.0.0.1:5000/
にアクセスすることで、次のようなWebページが表示されます。

あとは、それぞれ4つの入力フォームに数値を入れて判定ボタンを押すことで、花の種類が表示されます。

まとめ

この記事では、Pythonを使って、4種類の入力から花を推測するWebアプリケーションを開発しました。

是非とも、他の学習済みモデルも同様に、Webアプリケーションに組み込んでみて色々なアプリケーションを作ってみてください。