WordPressから機械学習モデルの予測APIを呼び出してみた

# はじめに

先日、[機械学習モデルの予測値を返すAPIをDockerで作ってみた](https://qiita.com/dyamaguc/items/8d371a979cc70de80c7f)という記事を書きましたが、せっかくAPIで予測値を取得できるようになったのでWebページから予測を行いたいと思い、Dockerで構築してみました。今回構築するイメージは下図の通りです。

![D7DEB7E3-D41C-4632-9159-1681A1831A28.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/6fe3b93c-baa1-2e6a-2472-9720b719c3bb.jpeg)

Docker上に3つのコンテナを立てて、ローカルのブラウザからWordPressにアクセスして、予測結果をWordPressで確認するところまで行いたいと思います。

# 環境
使用する環境は次の通りです。

```
$sw_vers
ProductName:	Mac OS X
ProductVersion:	10.13.6
BuildVersion:	17G9016

$docker version
Client: Docker Engine - Community
 Version:           19.03.4
 API version:       1.40
（省略）
Server: Docker Engine - Community
 Engine:
  Version:          19.03.4
  API version:      1.40 (minimum version 1.12)
（省略）
```


# 構築

様々な手順があると思いますが、今回はWordPressを構築した後、予測用のコンテナを構築します。それらの接続確認をした後、WordPressの記事作成を行って動作確認をするというような流れで構築していきます。

## WordPressの構築
### イメージ取得

WordPressと機械学習モデルの予測に用いるコンテナイメージを取得します。
こちら[^1]の記事を参考にして、次の3つのコマンドを実行してイメージを取得します。

```
$ docker pull mysql:5.7.25
$ docker pull wordpress:4.9.1
```

``docker images``コマンドで取得したイメージを確認します。

```
$docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               5.7.25              98455b9624a9        7 months ago        372MB
wordpress           4.9.1               28084cde273b        22 months ago       408MB
```

### コンテナ起動

WordPressとMySQLのコンテナを起動します。
こちら[^2]を参考に、起動の前に``docker network``コマンドでネットワークを構築しておきます。

```
$docker network create test-network
```

次のコマンドでネットワークが作成されたことを確認します。

```
$docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
edbb6037a089        bridge              bridge              local
5f7c06763a14        host                host                local
1b7e419414a4        none                null                local
687f0232408b        test-network        bridge              local
```

作ったネットワーク上にコンテナを起動します。

```
$docker run --name mysql --network test-network -e MYSQL_ROOT_PASSWORD=test-pw -d mysql:5.7.25

$docker run --name wordpress --network test-network -e WORDPRESS_DB_PASSWORD=test-pw -v $(pwd)/vol:/mnt -p 8080:80 -d wordpress:4.9.1
```

WordPressについては``-v``オプションでローカルのvolという名前のディレクトリをコンテナ中の/mntにマウントしています。これはあとで予測用APIにPOST通信する時に使用するphpスクリプトをローカルで作成してコンテナ内に格納するためです。
また、``-p``オプションでポートフォワードを設定して、ローカルの8080番ポートをコンテナ内の80番ポートに転送しています。

ローカルのブラウザで、``http://localhost:8080``にアクセスするとWordPressの設定画面が現れるので、好みの言語を設定します。

![wordpress01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/76782d95-f23e-446a-7ad4-3d05634d32f4.png)

管理者ユーザの名前やパスワードを設定します。メールアドレスも入力する必要がありますが、適当に入れておけば大丈夫です。

![wordpress02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/bd637a13-e79b-cb31-091c-2f99aae1ac45.png)

「WordPressをインストール」ボタンをクリックすると、管理者用の画面が現れます。

![wordpress03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/a7904ae0-4b9e-19e0-3bc0-162ad2ecebbe.png)

この時点でブラウザで再度``http://localhost:8080/``にアクセスするとおしゃれなページが出てきます。

![wordpress04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/dff41609-3dff-a836-a194-b6bc3a64ef0b.png)

ここまででWordPressの構築は終了です。
予測用APIを叩く部分はあとで設定します。

## 予測用APIサーバの構築

構築は[機械学習モデルの予測値を返すAPIをDockerで作ってみた](https://qiita.com/dyamaguc/items/8d371a979cc70de80c7f)で記載した通りなので、省略しますが、コンテナを起動する際には作成したネットワークを指定するようにします。

```
$docker run -it --name test-api --network test-network --rm -p 5000:5000 -v $(pwd)/vol:/home myflask/mlapi:1.0 /bin/bash
root@b9e8ac7cdb71:/# cd home/
root@b9e8ac7cdb71:/home# python3 api.py 
Server is running ...
 * Serving Flask app "api" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 
```

これで予測用APIサーバーを立てられました。

## WordPressから予測用APIを叩く

### 接続確認

ここまででdockerのコンテナが3つ起動していることを確認します。

```
$docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
b9e8ac7cdb71        myflask/mlapi:1.0   "/bin/bash"              8 seconds ago       Up 6 seconds        0.0.0.0:5000->5000/tcp   test-api
c6b2f713a6a5        wordpress:4.9.1     "docker-entrypoint.s…"   17 minutes ago      Up 17 minutes       0.0.0.0:8080->80/tcp     wordpress
290950f0f643        mysql:5.7.25        "docker-entrypoint.s…"   18 minutes ago      Up 18 minutes       3306/tcp, 33060/tcp      mysql
```

一度WordPressコンテナからcurlコマンドで予測用APIサーバーとの接続を確認します。
``docker exec``コマンドでWordPressコンテナに入ってcurlコマンドで予測用のデータを送信します。

```
$docker exec -i -t c6b2f713a6a5 /bin/bash
root@c6b2f713a6a5:/var/www/html# curl http://test-api:5000/predict -X POST -H 'Content-Type:application/json' -d '{"feature":{"season":[1], "year":[0], "month":[1], "hour":[0], "holiday":[0], "weekday":[6], "workingday":[0], "weather":[1], "temp":[0.24], "feelslike":[0.3], "humidity":[0.8], "windspeed":[0.0]}}'
{"Content-Type":"application/json","prediction":[34.67747315059312],"success":true}
```
予測値が34.67...と返ってきているので、接続は問題なさそうです。

### WordPressのページ作成

それではWordPressの設定に移ります。
新しく固定ページを作成して、以下の内容を記入します。
なお、各項目の数値の選択肢はテキトーに設定しています。

```
<form action="/ml-submit.php" method="post">
season: <select name="season">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
year: <select name="year">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
month: <select name="month">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
hour: <select name="hour">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
holiday: <select name="holiday">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
weekday: <select name="weekday">
<option value=0>0</option>
<option value=1>1</option>
</select>
workingday: <select name="workingday">
<option value=0>0</option>
<option value=1>1</option>
</select>
weather: <select name="weather">
<option value=0>0</option>
<option value=1>1</option>
<option value=2>2</option>
</select>
temp(0-1): <input type="text" name="temp">
feelslike(0-1): <input type="text" name="feelslike">
humidity(0-1): <input type="text" name="humidity">
windspeed(0-1): <input type="text" name="windspeed">
<input class="submit" type="submit" value="phpを実行" /></form>
```

![wordpress05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/a734f5c4-a66c-d73a-cd90-c6e01b17c31d.png)

これで「公開」をクリックすると、作成したページを見られるようになります。

### phpスクリプト作成

作成したページの「予測」ボタンをクリックすると、データが予測用APIに送信されて予測結果が返り、ページに表示されるようにします。ローカルでphpファイルを作成します。


```ml-submit.php
<?php

$data = array(
	'feature'=> array(
			  'season'=> array((int)$_POST['season']),
			  'year' => array((int)$_POST['year']),
			  'month' => array((int)$_POST['month']),
			  'hour' => array((int)$_POST['hour']),
			  'holiday' => array((int)$_POST['holiday']),
			  'weekday' => array((int)$_POST['weekday']),
			  'workingday' => array((int)$_POST['workingday']),
			  'weather' => array((int)$_POST['weather']),
			'temp' => array((float)$_POST['temp']),
			'feelslike' => array((float)$_POST['feelslike']),
			'humidity' => array((float)$_POST['humidity']),
			'windspeed' => array((float)$_POST['windspeed'])
	)
);
$data_json = json_encode($data);

$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, 'http://test-api:5000/predict');
curl_setopt($ch, CURLOPT_PORT, 5000);
$result=curl_exec($ch);
$res_json = json_decode($result , true );
echo 'PREDICTION:'.$res_json['prediction'][0];
curl_close($ch);

echo "<br>更新：".date("Y/m/d H:i:s");

?>
```

ウェブページで入力したデータをarray形式に格納して、json形式に変換してから予測用APIサーバに送信しています。返り値の予測値を表示するようにしています。なお、phpの作成についてはこちら[^3]を参考にしています。ローカルで作成したファイルをWordPressコンテナに格納します。volディレクトリにphpを格納しておきます。

```
$ls vol/ml-submit.php 
vol/ml-submit.php
```

volディレクトリはコンテナ上の/mntにマウントしているので、phpが格納されているディレクトリにファイルをコピーします。

```
root@c6b2f713a6a5:/var/www/html# cp /mnt/ml-submit.php .
root@c6b2f713a6a5:/var/www/html# ls
index.php      readme.html	wp-blog-header.php    wp-config.php  wp-includes	wp-login.php	 wp-signup.php
license.txt    wp-activate.php	wp-comments-post.php  wp-content     wp-links-opml.php	wp-mail.php	 wp-trackback.php
ml-submit.php  wp-admin		wp-config-sample.php  wp-cron.php    wp-load.php	wp-settings.php  xmlrpc.php
```

ここまでで構築は完了です。

# 動作確認

では、WordPressで作成したページで値を適当に入力して、「予測」ボタンを押してみましょう。

![wordpress07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/d0d47ee5-2f8e-46cf-516d-4581ecd2438e.png)

「予測」ボタンを押すと、予測値が表示されました。やったー。

![wordpress08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/0c8aa6eb-e637-a7df-f279-e789d63cadb9.png)

真っ白なページに予測値などが表示されていて見た目は悪いですが、Webページ上で予測用APIを叩いて表示することができました。

# さいごに

Docker上でWordPressを使って作成したWebページ上で機械学習モデルの予測を行うことができました。まだまだ勉強が足りないところがあったり、見た目が悪かったりしていますが、今後の課題です。今後はAWSなどのクラウド上で動かしてみたいと思っています。

[^1]: https://qiita.com/maimax/items/5090fd37f9832fe3c5fd
[^2]: https://knowledge.sakura.ad.jp/16082/
[^3]: https://rdk.me/php-js-post/