【初心者の方向け】RNNを使った小売業の売り上げ予測

こんにちは!EMです^^

 

今回は趣向を変えて、小売業の簡単な売上予測の流れを解説していこうと思います。

 

 

【概要】

 

このレシピでは、人工的で整っていないリアルなPOSデータを使用して、商品の売上予想モデルの実装を試すことができます。 ここでは、データの収集→データの加工→モデルの実装という流れで進めます。 コンペや研究用のデータセットは最初からきれいなデータが用意されている事が多いですが、 実際の現場では、空白のあるデータも多く、綺麗な状態に整えることから始まります。今回は特に小売業に関するデータに注目していきたいと思います。 また、回帰型ニューラルネットワークを中心に、どの種類が今回のケースで精度が良かったのか、また作ったものをより良くするにはどうすればいいのかを分析し、次へとつなげていくヒントをお伝えします。

 

 

【学べること】

このレシピを学ぶことで以下の手法を学ぶことができます。
・ECサイトではない、リアルな小売業で起こりえるPOSデータの扱い
・小売業に関するデータの理解
・scikit-learnやkerasを利用した売り上げ予想モデルの構築
・構築したルールベースモデルの分析、RNNの比較

 

【 始める前のスキルセット】
・Python 、pandas 、numpy の基礎を学んだことがある人
・学ぶ姿勢

 


【開発環境】
・Google Colaboratory


【ツール】
・Python (3.7)
・pandas
・numpy
・matplotlib
・scikit-learn

【データセット】
今回、ある仮想スポーツ店のPOSデータを対象に売上予測を行いたいと思います。


https://docs.google.com/spreadsheets/d/1Lbnx0LAl8eX8keS1rVN235lm3cMCwhkOrETMi7MY0nI/edit?usp=sharing



【データを収集しよう】

 

このレシピでは、2020年9月の売上データを元にして、未来の売上予想の分析に取り組んでいきます。 分析にはこれまでの売上を教師データにしたモデルを構築していくので、まずはデータを読み込んでみましょう。

google.colabをマウントします。

 

 

from google.colab import drive
drive.mount('/content/gdrive', force_remount=True)

 


このコードを実行すると、URLが表示されます。その中で取得したauthorization codeを四角の中にコピーすると実行が完了します。

次に、必要なライブラリをインポートしていきます。

 


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

#Pythonで実行時のwarningsが出てこないようにします
import warnings
warnings.filterwarnings("ignore")

 

Pythonライブラリのnumpy、pandas、matplotlibの読み込みを行っています。


また今回扱う「salesdata.csv」は購入明細の詳細となっています。いつ、どのくらい、具体的に何の商品を買ったのか、インボイスの番号ごとにデータが作られています。
まずはデータを読み込んで、先頭5行のみを表示し、データを見てみましょう。

様々な方法がありますが、ここではgoogle drive上にsalesdata.csvを読み込ませたという前提で進みます。


#一行目の()の内容は、各々のデータの格納先によって変わるのでご注意下さい。
#大文字小文字など、完璧に一致していなければエラーが出ます。

df = pd.read_csv("/content/gdrive/My Drive/recipe/salesdata.csv")

df.head()

 

 


実行すると、それぞれのデータの先頭5行のデータが確認できます。
データの先頭5行を表示させることで、どのような列が存在するのか、それぞれのデータ列の関連性など、全体の大枠を掴むことができます。
これまで、機械学習の入門書などのサンプル練習をこなしてきた方は、分析に適したデータが既に準備されているケースが多く、今回のように見にくい(醜い)データを扱った事がある方は少ないのではないでしょうか。
しかし、実際の現場では、データをかき集めるところから始まり、データの概要をとらえ、分析に適した形に加工する事から始める事が多いです。データが色んなファイルに分かれている事もあります。

それでは、今回のケースに関して、データの大枠を掴んでいきましょう。
このファイルの中には、商品名、商品単価、オーダー(インボイス)ごとにいつ、いくら買ったのかなどの情報が格納されています。

ではどのデータを使っていくのがよいのでしょうか。
分析業務の目的にも寄りますが、「売上をなんとかしたい」という抽象的なお題の場合でも、「今後の優良顧客を見つけたい」という具体的なお題の場合でも、まずはデータ全体像を把握する事が重要です。


出力はIndex/Int64Index型などがあります。列のタイプであるdtypeも出力します。


df.columns


<出力結果>


Index(['Invoice No', 'Part No', 'Description', 'Invoice Date', 'User Type', 'Order Qty', 'Ship Qty', 'B/O', 'Unit Price', 'Extended price', 'U/M', 'Tax 01', 'Tax 02', 'Unit Cost (Average)', 'Margin % (Average)',
'Unit Cost (Current)', 'Margin % (Current)', 'Discount', 'Territory',
'Salesperson', 'Salesperson Name', 'Currency', 'Product Code',
'Ship Date', 'Required Date', 'Warehouse'], dtype='object')


上記を見て、どのようなデータが格納されているか、大体想像できるでしょうか。
データを理解する一歩として、分析のテクニックだけでなく、多少のドメイン知識もデータを正しく理解する為に必要かもしれません。

今回は小売業でよく使われる内容が多いと思うので、ここで出てくる言葉を大まかに説明していきます。


Invoice No … インボイスナンバー(1回購入するごとに付けられる番号)
Part No … パートナンバー (商品コード/SKUとも呼ばれます)
Description … 商品概要
Invoice Date … インボイス発行日
Order Qty… 注文した数
Ship Qty…発送した数
B/O…バックオーダー
Unit Price…商品1つあたりの値段
Extended price…Unit Price×Order Qtyの合計
Unit Cost…商品の原価(平均)
Margin % …粗利益
Salesperson…商品を販売した場所/媒体の番号
Salesperson Name…商品を販売した場所/媒体
Currency…通貨
Product Code…商品のカテゴリー
Ship Date…発送した日
Required Date…注文した日
Warehouse…倉庫の番号

ここで取り扱っている小売業のデータの場合、「売上」のデータは全体を把握する為に切っても切り離せないので、売上に関連するデータを主軸に考えていきましょう。

 


df[df.duplicated()]





今回は重複しているデータがありませんでした。もし重複していれば、エラーなのか、必要なデータなのか現場の方と確認して扱って下さい。


df_nulls = df[df.isnull().apply(lambda x: max(x), axis=1)]
df_nulls.head()

 



ECサイトではなく、人の手を介してデータが作られている場合、複数人で管理している場合もあったりするので、おのずと「汚い」データになる事が多いです。欠陥値によっては現場にヒアリングを行い保管するか、スタッフに欠陥値を埋めてもらうなどの対応が必要になります。今回のケースでは必要な売上の金額については欠陥がないので、プログラムで欠陥値を補完していきます。



df['User Type'] = df['User Type'].fillna(0)
df['Description'] = df['Description'].fillna(0)
df['Margin % (Average)'] = df['Margin % (Average)'].fillna(0)
df['Margin % (Current)'] = df['Margin % (Current)'].fillna(0)
df['Currency'] = df['Currency'].fillna('USD')
df['Ship Date'] = df['Ship Date'].fillna(0)
df['Territory'] = df['Territory'].fillna(0)


df_nulls = df[df.isnull().apply(lambda x: max(x), axis=1)]
df_nulls.head()

 

 

 


空欄だった箇所に0を入れて、空白を埋める事が出来ました。


#df.to_csv('sales_invoice_clothing10.18.csv')
綺麗なデータをCSVとして保存したい場合は、上記のコードを実行すると、新しいデータが上書きされます。

 


【モデルを構築してみよう】


やっとデータの準備が終わりました、早速、モデルを構築してみましょう。今回は過去の実績から予想したルールベースの予測をしていきます。実装にはscikit-learnを利用することで簡単に実現できます。 まずは、データを学習とテストデータに分割しましょう。


import os

#pandasにデータを変換する
df['Invoice Date'] = pd.to_datetime(df['Invoice Date'])

#set_indexを使って'invoice data'の列を作る
df.set_index('Invoice Date',inplace=True)

 


#Unit Priceの合計(sum)を1日ごと(1D)にリサンプリングする
dfd = df["2020-09-01":].resample('1D').sum()"Unit Price"

#testとtrainデータを分ける
train = dfd["2020-09-01":"2020-09-20"]
test = dfd["2020-09-20":]



ax = train.plot()
test.plot(ax=ax)
plt.legend(['train', 'test'])

 


青色はtrain用のデータとして、オレンジはtest用のデータとして分ける事が出来ました。リサンプリングした通り、1日ごとにプロットされています。


#min-max scalingを適応させる
from sklearn.preprocessing import MinMaxScaler

sc = MinMaxScaler()

train_sc = sc.fit_transform(train)
test_sc = sc.transform(test)

X_train = train_sc[:-1]
y_train = train_sc[1:]

X_test = test_sc[:-1]
y_test = test_sc[1:]

 

分割にはtrain_test_splitが利用できます。 各パラメータですが、test_sizeではテストに利用する データの割合を指定します。 今回はデータ数がすくないこともあるので、9月20日分からテストの範囲にしておきます。


ここでは3種類のRNNを見ていきたいと思います。RNN(Recurrent Neural Networks)は系列データをモデル化するために使用されます。 これらのデータは、テキスト、オーディオ、さらには時系列データなどから作られます。 ネットワークのウェイトは指示されたサイクルから形成されるので、系列データをモデル化する事ができます。 フィードフォワードレイヤー(ニューロンのウェイトに基づいて値を転送するシンプルなNN)と同様に、RNNにも隠れ層がありますが、隠れ層にはそれ自体への接続があり、ある時点での隠れ層の状態を次の時点での隠れ層への入力として使用できます。 これにより、隠れている中間の状態で、入力系列と出力系列の間の時間的関係に関する情報を取得する事ができます。
この記事には図と一緒に解説してくれているので、詳しくNNについて知りたい方はぜひ参考にして見てください。バックプロパゲーションの内容ですが、NNの仕組みについても学べるかと思います。
https://towardsdatascience.com/understanding-backpropagation-algorithm-7bb3aa2f95fd

 


#モデルの作成(Kerasでは、Sequentialという箱を用意して、そこに深層学習(ディープラーニング)のネットワーク層を追加していきます。

from keras.models import Sequential
from keras.layers import Dense
import keras.backend as K
from keras.callbacks import EarlyStopping
from keras.layers import LSTM, GRU

K.clear_session()

model = Sequential()
model.add(Dense(10, input_dim=1, activation='relu'))
model.add(Dense(1))
#↑これが、1つのネットワーク層を表します。
#Denseというのは、入力と出力を全て接続するネットワークです。上記の場合、出力が1となります。
model.compile(loss='mean_squared_error', optimizer='adam')

early_stop = EarlyStopping(monitor='loss', patience=1, verbose=1)

#モデルの学習
model.fit(X_train, y_train,
epochs=10, # 訓練データを繰り返し学習させる数
batch_size=5, # 訓練データを10ずつのデータに分けて学習させる
verbose=1,
callbacks=[early_stop])

#予測値を出す
y_pred_fc = model.predict(X_test)

lstm_units = 6
#LSTM Network w/6 units(出力の数)
X_train_t = X_train[:, None]
X_test_t = X_test[:, None]

K.clear_session()
model = Sequential()

model.add(LSTM(6, input_shape=(1, 1)))
#input_shape/最初の層の時だけ指定。入力層の形を指定。

model.add(Dense(1))

model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(X_train_t, y_train,
epochs=8, batch_size=5, verbose=1,
callbacks=[early_stop])

y_pred_lstm = model.predict(X_test_t)

gru_units = 5
#GRU w/ 5 units(出力の数)
K.clear_session()
model = Sequential()

model.add(GRU(6, input_shape=(1, 1)))

model.add(Dense(1))

model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(X_train_t, y_train,
epochs=8, batch_size=5, verbose=1,
callbacks=[early_stop])

y_pred_gru = model.predict(X_test_t)


Denseというのは、入力と出力を全て接続するネットワークです。上記の場合、出力が10となります。
またKerasでは、指定されたオプティマイザー(optimizer)を使用して、モデルトレーニングの最適に使用されるアルゴリズムを定義します。 Kerasでは、下記のURLにリストされているさまざまなオプティマイザーを指定できます。
https://keras.io/api/optimizers/

今回は3種類のRNNを同時に比べていきましょう。

<出力結果>


Epoch 1/10
3/3 [==============================] - 0s 4ms/step - loss: 0.2409
Epoch 2/10
3/3 [==============================] - 0s 6ms/step - loss: 0.2765
Epoch 3/10
3/3 [==============================] - 0s 4ms/step - loss: 0.2107
Epoch 4/10
3/3 [==============================] - 0s 4ms/step - loss: 0.1873
Epoch 5/10
3/3 [==============================] - 0s 3ms/step - loss: 0.2003
Epoch 6/10
3/3 [==============================] - 0s 2ms/step - loss: 0.1863
Epoch 7/10
3/3 [==============================] - 0s 3ms/step - loss: 0.1869
Epoch 8/10
3/3 [==============================] - 0s 5ms/step - loss: 0.1949
Epoch 9/10
3/3 [==============================] - 0s 3ms/step - loss: 0.2216
Epoch 10/10
3/3 [==============================] - 0s 4ms/step - loss: 0.1554
Epoch 1/8
3/3 [==============================] - 2s 4ms/step - loss: 0.3179
Epoch 2/8
3/3 [==============================] - 0s 4ms/step - loss: 0.2536
Epoch 3/8
3/3 [==============================] - 0s 4ms/step - loss: 0.2876
Epoch 4/8
3/3 [==============================] - 0s 4ms/step - loss: 0.2580
Epoch 5/8
3/3 [==============================] - 0s 4ms/step - loss: 0.2308
Epoch 6/8
3/3 [==============================] - 0s 3ms/step - loss: 0.2730
Epoch 7/8
3/3 [==============================] - 0s 3ms/step - loss: 0.2979
Epoch 8/8
3/3 [==============================] - 0s 4ms/step - loss: 0.2200
Epoch 1/8
3/3 [==============================] - 2s 5ms/step - loss: 0.1775
Epoch 2/8
3/3 [==============================] - 0s 5ms/step - loss: 0.1972
Epoch 3/8
3/3 [==============================] - 0s 5ms/step - loss: 0.1762
Epoch 4/8
3/3 [==============================] - 0s 4ms/step - loss: 0.1611
Epoch 5/8
3/3 [==============================] - 0s 9ms/step - loss: 0.2007
Epoch 6/8
3/3 [==============================] - 0s 3ms/step - loss: 0.1572
Epoch 7/8
3/3 [==============================] - 0s 6ms/step - loss: 0.1850
Epoch 8/8
3/3 [==============================] - 0s 5ms/step - loss: 0.1397

loss は日本語で損失です。 損失関数は、モデルのパフォーマンスを評価するため、およびモデルのトレーニングプロセスで使用されます。 Kerasでは、MAE、MSE、エントロピーなどのさまざまな損失関数を指定できます。ここでの loss は、学習用データで学習を終えた際の「損失」の値で、 値の意味としては、小さい数字ほど正しい結果を出せるように学習できたことを表します。逆に値が大きい場合は正しい結果を出せていないという事になります。

今回はVanilla RNN, LSTM, GRUを使用した予想結果を、二次元のグラフとして表してみましょう。


pd.DataFrame(
{'Truth': sum(y_test.tolist(),),
'Vanilla RNN': sum(y_pred_fc.tolist(),
),
'LSTM': sum(y_pred_lstm.tolist(),),
'GRU':sum(y_pred_gru.tolist(),
)
},index = test[1:].index).plot()

 

f:id:tennisfashionista:20210602021307p:plain



 


今回はVanilla RNNが1番Truthに近い結果になりました。
Vanilla RNNとはLSTMよりもシンプルで、単一のレイヤーを通過する構造になっています。より詳しく知りたい方は、回帰型ニューラルネットワーク(RNN)についてまとまっているブログがあるので、そちらをご参照下さい。

RNNに関する参照ブログ
https://towardsdatascience.com/recurrent-neural-networks-d4642c9bc7ce

今回紹介した例は、入り口部分を少し覗いただけのもので、実際にはラグ特徴量をいくつか追加して予想モデルの精度を高めていく形になります。

 

【まとめ】

いかがでしたでしょうか?今回は以下の内容を見ていきました。


・ECサイトではない、リアルな小売業で起こりえるPOSデータの扱い
・小売業に関するデータの理解
・scikit-learnやkerasを利用した売り上げ予想モデルの構築
・構築したルールベースモデルの分析、RNNの比較


各ステップにはまだまだ改善できる所があります! 今回のレシピではここまでですが、ぜひ自分なりの工夫をしてみてくださいね。

 

 

またもっともっと勉強したい!という方は、ぜひ社会人でも
学べる環境があるので、ぜひ無料の資料や説明会等ものぞいてみてくださいね^^

 

 

最後まで読んで頂きありがとうございました!