シェアバイク利用者数予測

出力層に複数ユニットを持つ全結合ニューラルネットワークを用いたシェアバイク利用者数予測

本チュートリアルでは、全結合ネットワークを構築し、季節、天気などのデータから2種類のシェアバイク利用者数を予測します。データは以下のUCIのホームページからダウンロード可能です。( https://archive.ics.uci.edu/ml/datasets/Bike+Sharing+Dataset )

必要なライブラリ

  • matplotlib 2.0.2
  • numpy 1.12.1
  • scikit-learn 0.18.2
  • pandas 0.20.3
In [2]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

import renom as rm
from renom import Sequential
from renom import Dense, Relu
from renom import Adam

データの読み込みと前処理

まずはじめにデータを読み込みます。ダウンロードフォルダの中には2つのcsvファイルが入っていますが、本チュートリアルでは日毎のシェアバイク一時利用者数と予約利用者数を予測するので、day.csvファイルのみを使用します。

In [3]:
df = pd.read_csv("../day.csv")

不要なコラム(’instant’, ‘dteday’, ‘cnt’)をデータフレームから削除します。’dteday’に関しては、データセットに用意されているseason,yr,mnth,holiday,workingdayの特徴量で表現されると見なし、データ前処理を簡略化するために省略しました。ただし、dtedayを含めることで予測精度をさらに向上させられる可能性もあります。

In [4]:
df1=df.drop(['instant','dteday','cnt'],axis=1)
df1.head()
Out[4]:
season yr mnth holiday weekday workingday weathersit temp atemp hum windspeed casual registered
0 1 0 1 0 6 0 2 0.344167 0.363625 0.805833 0.160446 331 654
1 1 0 1 0 0 0 2 0.363478 0.353739 0.696087 0.248539 131 670
2 1 0 1 0 1 1 1 0.196364 0.189405 0.437273 0.248309 120 1229
3 1 0 1 0 2 1 1 0.200000 0.212122 0.590435 0.160296 108 1454
4 1 0 1 0 3 1 1 0.226957 0.229270 0.436957 0.186900 82 1518

各列データを標準化し、numpy配列に変換を行います

In [5]:
df_s = df1.copy()

col_std=[]
col_mean=[]
for col in df1.columns:
    v_std = df1[col].std()
    v_mean = df1[col].mean()
    col_std.append(v_std)
    col_mean.append(v_mean)
    df_s[col] = (df_s[col] - v_mean) / v_std

df_s.head()
Out[5]:
season yr mnth holiday weekday workingday weathersit temp atemp hum windspeed casual registered
0 -1.347291 -1.000684 -1.599066 -0.171863 1.497783 -1.470218 1.109667 -0.826097 -0.679481 1.249316 -0.387626 -0.753218 -1.924153
1 -1.347291 -1.000684 -1.599066 -0.171863 -1.495054 -1.470218 1.109667 -0.720601 -0.740146 0.478785 0.749089 -1.044499 -1.913899
2 -1.347291 -1.000684 -1.599066 -0.171863 -0.996248 0.679241 -0.725551 -1.633538 -1.748570 -1.338358 0.746121 -1.060519 -1.555624
3 -1.347291 -1.000684 -1.599066 -0.171863 -0.497441 0.679241 -0.725551 -1.613675 -1.609168 -0.263001 -0.389562 -1.077996 -1.411417
4 -1.347291 -1.000684 -1.599066 -0.171863 0.001365 0.679241 -0.725551 -1.466410 -1.503941 -1.340576 -0.046275 -1.115863 -1.370398

データの分割

訓練データとテストデータに分割します。

In [6]:
X, y = np.array(df_s.iloc[:, :11]), np.array(df_s.iloc[:, 11:13])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)

Sequentialモデルを用いたニューラルネットワークの定義

全てのパラメータが一時利用者数と予約利用者数に影響すると考えられるので全結合ニューラルネットワークを構築します。予測する対象が2つなので出力層のユニット数を2に設定します。他の層のユニット数はハイパーパラメータになります。ユニット数の決定にはハイパーパラメータのチュートリアルを参照してください。

In [7]:
sequential = Sequential([
    Dense(10),
    Relu(),
    Dense(8),
    Relu(),
    Dense(6),
    Relu(),
    Dense(2)
])

利用者数を回帰するための学習ループ

In [8]:
# parameters
BATCH = 10
EPOCH = 100
optimizer = Adam(lr=0.01)

# Learning curves
learning_curve = []
test_curve = []

# Training loop
for i in range(1, 1+EPOCH):
    N = X_train.shape[0] # Number of records in training data
    perm = np.random.permutation(N)
    train_loss = 0

    for j in range(N//BATCH):
        # Make mini-batch
        index = perm[j*BATCH:(j+1)*BATCH]
        train_batch_x = X_train[index]
        train_batch_y = y_train[index]

        # Forward propagation
        with sequential.train():
            z = sequential(train_batch_x)
            loss = rm.mean_squared_error(z, train_batch_y)

        # Backpropagation
        grad = loss.grad()

        # Update
        grad.update(optimizer)

        train_loss += loss.as_ndarray()

    # calculate mean squared error for training data
    train_loss = train_loss / (N // BATCH)
    learning_curve.append(train_loss)

    # calculate mean squared error for testidation data
    y_test_pred = sequential(X_test)
    test_loss = rm.mean_squared_error(y_test_pred, y_test).as_ndarray()
    test_curve.append(test_loss)

    # print training progress
    if i % 10 == 0:
        print("Epoch %d - loss: %f - test_loss: %f" % (i, train_loss, test_loss))

print('Finished!')
Epoch 10 - loss: 0.135388 - test_loss: 0.131142
Epoch 20 - loss: 0.119636 - test_loss: 0.117048
Epoch 30 - loss: 0.108519 - test_loss: 0.118423
Epoch 40 - loss: 0.105684 - test_loss: 0.140581
Epoch 50 - loss: 0.099742 - test_loss: 0.130865
Epoch 60 - loss: 0.099561 - test_loss: 0.127347
Epoch 70 - loss: 0.098927 - test_loss: 0.122917
Epoch 80 - loss: 0.095182 - test_loss: 0.140498
Epoch 90 - loss: 0.100924 - test_loss: 0.156761
Epoch 100 - loss: 0.093405 - test_loss: 0.150348
Finished!

モデルの評価

学習曲線のプロット

まず正しく学習が行われているかを確認するために学習曲線をプロットしましょう。

In [9]:
plt.figure(figsize=(10, 4))
plt.plot(learning_curve, label='train_loss')
plt.plot(test_curve, label='test_loss', alpha=0.6)
plt.title('Learning curve')
plt.xlabel("Epoch")
plt.ylabel("MSE")
plt.ylim(0, 1)
plt.legend()
plt.grid()
../../../_images/notebooks_regression_bikeshare_notebook_18_0.png

上図よりtest loss曲線がtrain loss曲線から徐々に離れつつあります。これはモデルが訓練データを過学習しつつあることを意味します。従って、train lossとtest lossが大きく離れる前に学習を打ち切っています。このような過学習はデータセットの数が少ない場合に起こりやすいです。

実際の利用者数と予測した利用者数の比較

次に実際のシェアバイク利用者数と予測した利用者数の比較を行います。

In [10]:
# predict test value
y_pred = sequential(X_test)

casual_true = y_test[:,:1].reshape(-1, 1) * col_std[11] + col_mean[11]
casual_pred = y_pred[:,:1] * col_std[11] + col_mean[11]
registered_true = y_test[:,1:2].reshape(-1, 1) * col_std[12] + col_mean[12]
registered_pred = y_pred[:,1:2] * col_std[12] + col_mean[12]

plt.figure(figsize=(8, 8))
plt.plot([5, 8000], [5, 8000], c='k', alpha=0.6, label = 'diagonal line') # diagonal line
plt.scatter(casual_true, casual_pred,label='casual')
plt.scatter(registered_true, registered_pred,label='registered')
plt.xlim(0, 8000)
plt.ylim(0, 8000)
plt.xlabel('acutual count of users', fontsize=16)
plt.ylabel('predicted count of users', fontsize=16)
plt.legend()
plt.grid()
../../../_images/notebooks_regression_bikeshare_notebook_21_0.png

グラフのx軸は実際の利用者数、y軸は予測した利用者数を示します。黒い直線は対角線(y=x)であり、プロットがこの直線に近ければ近いほど、予測が正しく行われていることになります。グラフより、約1000〜3000辺りの範囲で利用者数が正しく予測できていないことがわかります。正確に予測を実施するためには、正しく予測できたデータと正しく予想できなかったデータを分けるて原因を調査する必要があります。

二乗平均平方根

二乗平均平方根は元のデータと予測されたデータの間の平均的な誤差を表現します.今回の場合は作成したモデルは、平均的すると一時利用者数の場合は約208人、予約利用者数の場合は約377人程度の誤差があることを意味します.

In [11]:
print("Root mean squared error:{}".format(np.sqrt(rm.mse(casual_true, casual_pred))))
print("Root mean squared error:{}".format(np.sqrt(rm.mse(registered_true, registered_pred))))
Root mean squared error:208.06089782714844
Root mean squared error:377.4617004394531