Flaskで人工知能アプリ:メジャーリーガー年俸予測(1992)

#Flaskを使って年俸を予測してみた。
[コードはこちらからダウンロード可能です。(GitHub)](https://github.com/MachinoTensei/baseball_app)
緑色の「Clone or Download」をクリックして、「Download ZIP」をクリックするとダウンロードできます。
**（このように、「そんなの言われなくてもわかってる」という内容まで書いてあるところもありますので、僕同様に初心者の方も少しはわかりやすくなっていると思います。）**

![スクリーンショット 2019-06-14 午後21.11.54 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427946/57e9c6ca-4557-d685-a057-07143ac4a7f0.png)
![スクリーンショット 2019-06-14 午後21.53.26 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427946/9d20f919-8803-f2d2-69b8-914f9474e9cf.png)


**[注意]**

- データ数が337しかなかったため、精度はあまり良くないどころの騒ぎではありません。
- 今回はデータ分析に重きは置いておらず、WEBアプリケーション制作に集中しています。
- プログラミング歴約１ヶ月の初心者が書いています。

##この記事の対象者
- Flaskと機械学習モデルを組み合わせたWEBアプリを作りたい方
- Flaskの[公式チュートリアル](https://a2c.bitbucket.io/flask/index.html)程度の内容を理解している方（僕は[paiza](https://paiza.jp/works/search_courses/2306)でのみ学習）
- 機械学習・深層学習を学習済みの方（機械学習のコードは載せてありますが、WEBアプリ作成が目的のため、解説はしません）
- python, frask（少しでいい）, 機械学習（自分でモデルを作れればいい）の知識があれば大丈夫かと思います。（HTMLなどを使いますが、勉強したことないので僕もよく知りません）

##この記事でできること
- Flaskで簡単なAIアプリを作成できる（最悪、コピペ or ダウンロード で行けると思います。）
- 機械学習モデルをFlaskに組み込む方法がわかる

##開発環境
OS:**mac**

- python3.7
- Flask 1.0.3
- Jupyter 1.0.0
- Keras 2.2.4
- Matplotlib 3.1.0
- numpy 1.16.4
- pandas 0.24.2
- requests 2.22.0
- scikit-learn 0.21.2
- tensorflow 1.13.1

##（１）機械学習モデルを作成する
**以下のコードは全てJupyter notebookで実行しています。**
###（１−１）使用モジュール
```python
import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.preprocessing import StandardScaler

from keras.models import Sequential
from keras.layers.core import Dense, Activation
```

###（１−２）データ読み込み
データは[ここからダウンロード](https://www4.stat.ncsu.edu/~boos/var.select/baseball.txt)できます。
テキストエディタにコピペするなりしてください。（僕は"baseball_data.csv"と名前をつけて保存しました。）

```python
data = pd.read_csv("baseball_data.csv")
data.columns = ("sarary", "batting avg", "OBR", "run", "hit", "double", "triple", "homerun", "RBI", 
                "fourball", "strikeout", "stolen", "error", "x13", "x14", "x15", "x16")
#OBP:出塁率
#RBI:打点
#run:得点
```

###（１−３）訓練データとテストデータに分割して正規化
```python
train_data = data.iloc[:300, 1:13].values.astype("float32")#訓練データ
test_data = data.iloc[300:, 1:13].values.astype("float32")#テストデータ

train_label = data["sarary"][:300].values.astype("float32")#訓練データのラベル
test_label = data["sarary"][300:].values.astype("float32")#テストデータのラベル


#正規化
stdc = StandardScaler()

#訓練データの平均と標準偏差
mean_train = np.mean(train_data, axis = 0)
std_train = np.std(train_data, axis = 0)
#ラベルの平均と標準偏差
mean_label = np.mean(train_label, axis = 0)
std_label = np.std(train_label, axis = 0)

#テストデータは訓練データの値で正規化
for column in range(test_data.shape[1]):
    test_data[:, column] = (test_data[:, column] - mean_train[column]) / std_train[column]
test_label = (test_label - mean_label) / std_label

#訓練データを正規化
train_data = stdc.fit_transform(train_data)
train_label = (train_label - mean_label) / std_label
```
###（１−４）k分割交差検証を使って学習モデルを作る
`model.save("baseball_model.h5")`を実行することでモデルを保存できます。
（"baseball_model.h5"の部分は自由にしてください。）

```python
k = 4#分割数
num_val_data = len(train_data) // k#検証データ数
num_epochs = 150
batch_size = 4

mae_all_scores = []
loss_all_scores = []
val_loss_all_scores = []

for num in range(k):
    print(num+1, "回目")
    
    #検証用データ
    val_data = train_data[num * num_val_data: (num + 1) * num_val_data]
    val_label = train_label[num * num_val_data: (num + 1) * num_val_data]
    
    #訓練用データ
    par_train_data = np.concatenate([train_data[: num * num_val_data], train_data[(num + 1) * num_val_data: ]], axis = 0)
    par_train_label = np.concatenate([train_label[: num * num_val_data], train_label[(num + 1) * num_val_data: ]], axis = 0)
    
    model = Sequential()
    model.add(Dense(4, activation="relu", input_shape=(train_data.shape[1], )))
    model.add(Dense(4, activation="relu"))
    model.add(Dense(1))
    
    model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
    
    history = model.fit(par_train_data, par_train_label, 
                        epochs=num_epochs, 
                        batch_size=batch_size, 
                        validation_data=(val_data, val_label), 
                        verbose = 0
                       )
    
    
    mae_history = history.history["val_mean_absolute_error"]
    loss_history = history.history["loss"]
    val_loss_history = history.history["val_loss"]
    
    mae_all_scores.append(mae_history)
    loss_all_scores.append(loss_history)
    val_loss_all_scores.append(val_loss_history)


print("終了")

print("モデルを保存しています・・・")
model.save("baseball_model.h5")
print("モデルを保存しました。")
```
###（１−５）モデルの精度を確認（今回は特に関係ない）
今回はWEBアプリを作ることが目的であるので、モデルの良し悪しは関係ないが、下記のコードを実行することで良し悪しを確認できる。
特に興味のない方は読み飛ばしていただいて大丈夫です。

```python
average_mae_history = [np.mean([x[i] for x in mae_all_scores]) for i in range(num_epochs)]
average_loss_history = [np.mean([x[i] for x in loss_all_scores]) for i in range(num_epochs)]
average_val_loss_history = [np.mean([x[i] for x in val_loss_all_scores]) for i in range(num_epochs)]

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.grid(True)
plt.show()

plt.plot(range(1, len(average_loss_history) + 1), average_loss_history, color = "red", label="loss")
plt.plot(range(1, len(average_val_loss_history) + 1), average_val_loss_history, color = "blue", label="val_loss")
plt.legend()
plt.grid(True)
plt.show()
```
[実行結果]
![スクリーンショット 2019-06-14 午後21.37.13 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427946/9f7f242c-7648-fc28-9014-c8aa569dd539.png)

##（２）Flask側
**以下のコードは[CotEditor](https://apps.apple.com/jp/app/coteditor/id1024640650?mt=12)で書いています。（ファイル名：baseball_sarary.py）**
コードがかければCotEditorである必要は全くないですが、jupyterで実行するわけではありません。

```python:baseball_sarary.py
from flask import Flask, request, render_template
import keras
import tensorflow as tf

app = Flask(__name__)

#---------------モデルを読み込む---------------
model = keras.models.load_model("baseball_model.h5")
model._make_predict_function()
graph = tf.get_default_graph()
#---------------------------------------------------------------------------


#---------------モデルを実行するための関数---------------
import numpy as np
def model_run(data):

	mean_train = np.array([0.25924, 0.32521668, 46.94, 93.69334, 16.78, 2.4466667, 9, 44.1, 35.193333, 57.013332, 8.576667, 6.76])
	std_train = np.array([3.7369091e-02, 4.3865968e-02, 2.8560400e+01, 5.1223877e+01, 1.0147821e+01, 2.6179805e+00, 9.0631123e+00, 2.9168533e+01, 2.4886461e+01, 3.3527500e+01, 1.1886300e+01, 5.9678917e+00])
	std_label = 1248.6285
	mean_label = 1274.0566
	
	for i in range(12):
		data[i] = (data[i] - mean_train[i]) / std_train[i]
	data = data.reshape(1, 12)
	
	global graph
	with graph.as_default():
		ans = model.predict(data)
	ans = model.predict(data)
	ans = (ans * std_label) + mean_label
	ans = int(ans[0][0]*1000)
	
	return ans
#---------------------------------------------------------------------------

@app.route("/")
def home():
	message = "起動したいアプリを選んでください。"
	
	return render_template("home.html", message = message)

@app.route("/baseball")
def baseball():
	message = "下の項目を入力すると、年俸が算出されます。(1992年)"
	
	return render_template("baseball.html", message = message)


@app.route("/baseball/result", methods = ["POST"])
def baseball_result():
	message = "あなたの年俸が予測されました！"
	
	batting_avg = float(request.form["batting_avg"])
	obp = float(request.form["obp"])
	run = float(request.form["run"])
	hit = float(request.form["hit"])
	double = float(request.form["double"])
	triple = float(request.form["triple"])
	homerun = float(request.form["homerun"])
	rbi = float(request.form["rbi"])
	fourball = float(request.form["fourball"])
	strikeout = float(request.form["strikeout"])
	stolen = float(request.form["stolen"])
	error = float(request.form["error"])
	
	data_list = [batting_avg, obp, run, hit, double, triple, homerun, rbi, fourball, strikeout, stolen, error]
	data_np = np.array(data_list)
	sarary = model_run(data_np)
	sarary_yen = sarary * 128
	return render_template("baseball_result.html", message = message, sarary = sarary, sarary_yen = sarary_yen)


```
重要な部分は、下記の部分かと思います。このコードで、先ほど作ったモデルを読み込んでいます（もう一つ下のコードに特に重要な部分だけを抜き出しました）。
モデルの読み込みはよくわからず、ネットで調べながらやりました。
最初は「pickle」でやろうとしましたが、エラーが出てしましい（jupyterではただしく読み込めた）、[この記事](https://github.com/keras-team/keras/issues/10431)を参考にしました。pickleでやった時は、参考記事の方と同じようなエラーが出てしましいました。

```python:モデル読み込み部分
import keras
import tensorflow as tf

model = keras.models.load_model("baseball_model.h5")
model._make_predict_function()
graph = tf.get_default_graph()

import numpy as np
def model_run(data):

    mean_train = np.array([0.25924, 0.32521668, 46.94, 93.69334, 16.78, 2.4466667, 9, 44.1, 35.193333, 57.013332, 8.576667, 6.76])
    std_train = np.array([3.7369091e-02, 4.3865968e-02, 2.8560400e+01, 5.1223877e+01, 1.0147821e+01, 2.6179805e+00, 9.0631123e+00, 2.9168533e+01, 2.4886461e+01, 3.3527500e+01, 1.1886300e+01, 5.9678917e+00])
    std_label = 1248.6285
    mean_label = 1274.0566

    for i in range(12):
        data[i] = (data[i] - mean_train[i]) / std_train[i]
    data = data.reshape(1, 12)

    global graph
    with graph.as_default():
        ans = model.predict(data)
    ans = model.predict(data)
    ans = (ans * std_label) + mean_label
    ans = int(ans[0][0]*1000)

    return ans
```

####モデルの読み込み方法(Keras:TensorFlowの場合)
(TensorFlowを使った)KerasのモデルをWEBアプリに組み込みたい場合は、下記のようにしていただければ、正常に動作すると思います。
このモデルはjupyterで`model.save("モデル名")`として保存しており、pickleは使っておりません（エラーが出てしまい、今のところ使い方がわかりません）。下記のコードのように覚えておけば、このアプリに限らず色々な場所で応用できると思います。

```python:モデル読み込みの最重要部分
#Keras,TensorFlowを読み込む
import keras
import tensorflow as tf

#下記の３行でモデルを読み込む
model = keras.models.load_model("モデル名")
model._make_predict_function()
graph = tf.get_default_graph()

#関数の中で"model.predict()"を使う。（関数化しなくても動作するかとは思います。）
def model_run(data):
    global graph
    with graph.as_default():
        ans = model.predict(data)
    return ans
```

##（３）HTML側
こちらは特に解説はありません。（paizaでFlaskの動画をみているときにおまけで得た知識でやっているため、解説できる知識がありません。）
HTMLのコードは[GitHub](https://github.com/MachinoTensei/baseball_app)からダウンロードしてください。「baseball_sarary.py」などのデータもあります。

##（４）アプリ実行
以下、ターミナルで実行してください。GitHubからダウンロードしたままのファイル構成を保っていれば、正常に起動するかと思います。
###（４−１）仮想環境を作成
ターミナルを開いて仮想環境を作ります。（既にある方はスキップ）

```python
$ python3.7 -m venv <仮想環境の名前>#"<>"は要りません
$ source <仮想環境名>/bin/activate#仮想環境起動
$ pip install Flask#Flaskなど、必要なものをインストールしてください
#開発環境に記載したものをインストールしておけば動作すると思います。
```
###（４−２）実行コード
```python
#先ほどの仮想環境を起動した状態で以下を実行
#GitHubからダウンロードした"MyApp"は仮想環境の場所に置いておいてください
#仮想環境作成時にディレクトリを特に指定しなかった場合は、デスクトップに置いておけばいいかと思います。

$ cd MyApp#MyAppの場所を指定
$ FLASK_APP=baseball_sarary.py FLASK_ENV=development flask run
#「FLASK_APP=<実行したいファイル名>」で起動したいファイルを指定
#「FLASK_ENV=development」としておくことで、HTMLなどを編集した際にブラウザの更新ボタンを押せば、即座に編集内容が反映されます。
#「flask run」で起動
```

###（４−３）[localhost:5000](http://localhost:5000)で開く
ブラウザで、「 http://localhost:5000 」を検索するとアプリが実行されます。
下のような画面になれば成功です。
最初にも言いましたが、年俸がマイナスになることは多々ありますが、決してエラーではなく、学習モデルが悪いだけです。
![スクリーンショット 2019-06-15 8.46.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427946/53a65f9d-f76e-a73a-c5e5-722a55e97cd6.png)


(終了)