AI学習者・実務家の「疑問」を解消したい

外資系IT企業で、主に火力発電所のAI最適化に従事 →2019年6月: 米国SAS社が主催する ”SaS Analytics Hackathon”で優勝

信用スコアの作り方 - pythonで実装

今流行りの信用スコアについて、世界観とその作り方について、
解説します。

なぜ、信用スコアに興味を持ったかというと、
去年10月にSAS Japanさんから、"SAS Analytics Experience @ Milano"というカンファレンスに招待して頂き、Credit Scoring(信用スコア)についての講演を聞かせて頂いたことがきっかけです。

f:id:akenkenken:20200101172337j:plain
SAS Analytics Experienceに行った時の写真

その公演では、大きく下記の3つのトピックについて、話してくれました。

  1. 信用スコアとはそもそも何なのか?
  2. 現在、主流で、最先端のアルゴリズムは?
  3. 信用スコアは「ローン貸付の与信の判断」”以外”だと、どんな場面で活用できそうか?

本記事では、アルゴリズムの解説を主にしたいと思います。が、、

ただ、confidentialityの関係で、すべて公開するのは難しいようなので、

簡易版というか、Dr. Terisaに勧めてもらった本に書いてある手法
(Credit Risk Scorecards: Developing and Implementing Intelligent Credit Scoring CREDIT RISK SCORECARDS [ Naeem Siddiqi ])
を代わりに、解説したいと思います。

その本に書いてある手法は、
Dr. Terisaさんが、公演で解説してくれたもの(最先端の手法)と大きくは違わないし、欧米の多くの会社/スタートアップも、この本に書いてある手法を参考にしてるみたいなので、信用に値する手法だと言えるでしょう。

では、下記に、その作成方法を共有します!


使用するデータセット

Kaggleに落ちていた、Give Me Some Creditというデータセットを使用します。2011年のコンペで使用されていたものだそうです。
www.kaggle.com

特徴量の解説は、下記の図でまとまってるので、ご覧ください。

f:id:akenkenken:20200103181222p:plain
特徴量の解説

信用スコアを、AIの学習によって作り出す、ってことなので、
当然必要なデータセットは、返済できたかどうか?/返済を遅延かどうか?
の情報が含まれているデータセットが必要です。

信用スコアとは?

まず、信用スコアとは何か?について、確認しましょう。

信用スコアとは、ある人がお金を借りたい!って思った時に、
その人が信用できるか?(返済の能力があるか?)を数値化したものです。
まぁ社会的信用ってことですね。

f:id:akenkenken:20200103195956p:plain
信用スコアのイメージ

上記の図にある、ScoreCard Pointっていうのが、貴方の信用スコアを形成する数字になってます。

このScoreCard Pointを作成するために、Pythonで色々カキカキしていくことになります。

一見、難しそうなんだけど、論文読んでみると、やり方は非常にシンプルでした。実は、下記の3ステップだけで、信用スコア作成できます。

  1. 信用スコア作成に使用するコラムの階層化(ビニング)
  2. 各コラムの各階層における、WoEの計算
  3. 各コラムの各階層における、ScoreCard Pointの計算

では、各ステップの中身を詳しく観ていきましょう。

ステップ1 & 2の解説と実装

ステップ1と2は、信用スコアを作る上での、前処理みたいな位置づけです。

f:id:akenkenken:20200104165051p:plain
Step 1 & 2の説明

まず、Step1から観ていきましょうか。

ここの作業は、結構大変(泥臭い)です。

信用スコア作成に使用する、特徴量(例えば、月収)を、
階層化しないといけないので。
10-20万、20-30万みたいに。

なぜ階層化しなければいけないのか?と言うと、
上記の「信用スコアのイメージ」で解説したように、
信用スコアって、月収が10-20万の人は50点!、20-30万の人は55点!みたいに計算されますよね。

それって、データを準備する側は、「特徴量を階層化する」っていう前処理をしてるってことなんです。

「じゃあ、どう階層化すればいいの?」っていう疑問が出てくると思います。

Step2で後述しますが、
アカデミックな世界だと、階層化の基準は「DOGとDOBの差が最大化するように設定する」って言われてます(Credit Risk Scorecards: Developing and Implementing Intelligent Credit Scoring CREDIT RISK SCORECARDS [ Naeem Siddiqi ])

けど、実務の世界だと、完全に「Try and Error」でやるしかないそうです。
Dr. Terisaさんがそう言ってました。

「Try and Error」で判断すると言うのは、
一旦、階層を自分で指定してみて、信用スコアを出し、ドメインと常識に照らし合わせて、その信用スコアがMake Senseかどうか?を判断しろって言うことです。

「まじか、、めんどくさい」と思ったけど、実務で信用スコア作ってる方が、そう仰ってるので、我々学習者もサボらずに、頑張りましょう。

次は、Step2です。

Step2を理解するためには、

  1. WoEとは何か?
  2. DOG/DOBとは何か?

を理解する必要があります。
それぞれ、解説します。

1. WoE(Weight of Evidence)とは何か?
実際、いろんな定義があります。あえて、スタンスをとって定義すると、

「Good CustomerとBad Customer、どっちを予測するのに役立つの?」
知らせてくれる指標、と定義できます。

この定義で出てくる、
Good Customerとは、「過去に、借入の返済の遅延、債務不履行になったことが”ない”人」を指し、

Bad Customerとは、「過去に、借入の返済を遅延した人、債務不履行になったことが”ある”人」を指します。

プラスの値は、Good Customerの予測に役立ち、
マイナスの値は、Bad Customerの予測に役立ちます。

下記に、実際に計算したWoEのイメージをご覧いただくと、より理解できます。

f:id:akenkenken:20200104184005p:plain
WoEのイメージ(DebtRatioを例に)

DebtRatio(負債比率)は、あなたが持っている資産に対して、どのくらい負債・借金があるのか?を示す指標です。(DP = 負債/資産)

上記の図をご覧いただくと、
DeptRatioが高くなればなるほど、WoEの値が小さくなってますよね。

つまり、「資産に対して、負債が大きなっている人ほど、債務不履行になっている」というメッセージが読み取れます。

まぁ、直感的ですよね。

2. DOG/DOBとは何か?
上記のStep1&2の説明のスライドをもう一回見て見ましょう。
Step2のDOGとDOBの計算式に注目してください。

f:id:akenkenken:20200104165051p:plain
Step2に注目!

ここの式にある、「各カテゴリーの与信数/非与信数」というのは、例えば、
「月収20-30万円のカテゴリーで何人がGood/Bad Customerなのか?」
を示します。

「トータルの与信数/非与信数」は、
「データセット全体で、何人がGood/Bad Customerなのか?」を示します。

今回使用しているデータセットのコラムでいうと、
"SeriousDlqin2yrs" = 0/1であるときの数です。

言葉だけでイメージするの難しいと思うので、下記の図もご覧ください。

f:id:akenkenken:20200104201120p:plain
Distribution of Good と Distribution of Badの計算例

どうです? 理解できました?
もし、わかりずらい箇所があったら、遠慮なく質問してくださいね:)

最後に、Step1-2までの実装方法(python)を下記に共有します。

# binningを定義する
def binning(col, list_bins_func):
    binned_df_list = []
    for each in list_bins_func:
        binned_df_list.append(df[(df[col]>=each[0])&(df[col]<each[1])])
    return binned_df_list

# BinningとWoEの計算を実行する関数を定義する
def calc_woe_runner(col, list_bins):
    #実際にbinningを実行する
    list_binned_df  = binning(col, list_bins)
    each_num         = np.zeros(len(list_binned_df))
    dist_good         = np.zeros(len(list_binned_df))
    dist_bad           = np.zeros(len(list_binned_df))
    good_number  = np.zeros(len(list_binned_df))
    bad_number    = np.zeros(len(list_binned_df))
    
    # DOGとDOBを計算する
    for i, each in enumerate(list_binned_df):
        each_num[i]        = len(each)
        good_number[i]  = len(each[each["SeriousDlqin2yrs"] == 0])
        bad_number[i]    = len(each[each["SeriousDlqin2yrs"] == 1])
    
    dist_good   = good_number/good_number.sum()
    dist_bad    = bad_number/bad_number.sum()
    dist_total  =  (good_number + bad_number)/len(df)
    
    # WOE(Weight of Evidence)を計算する
    woe = np.log(dist_good/dist_bad)*100
    
    return col,woe,dist_total, good_number.sum(), good_number, bad_number.sum(),bad_number, dist_good, dist_bad

# 上記を実行する
# binning関数で使われる変数の定義 
col_list = ["age", "DebtRatio", 'MonthlyIncome']

age_bin_list = [[0,30], [30,40], [40,50],
                [50,60], [60,70], [70,80], 
                [80,90], [90,130]]

deptRatio_bin_list = [[0,0.2], [0.2,0.4], [0.4,0.6],
                      [0.6,0.8], [0.8,1.0], [1.0,1.2], 
                      [1.2,1.4], [1.4,1.6]]

monthlyIncome_bin_list = [[0,2000], [2000,4000], [4000,6000],
                          [6000,8000], [8000,10000], [10000,12000], 
                          [12000,14000], [14000,160000]]

list_combined = [age_bin_list, deptRatio_bin_list, monthlyIncome_bin_list]

# Actually calculate woe
col_list_for_df = []
woe_list_for_df = []
iv_list_for_df  = []
df_woe_list     = []
good_list_sum   = []
good_list_each  = []
bad_list_sum    = []
bad_list_each   = []
dist_good_list  = []
dist_bad_list   = []
total_dist_list = []
df_woe_concat   = pd.DataFrame()
i = 0
for col, each_bin_for_col in zip(col_list,list_combined):
    col_list_for_df, woe_list_for_df, total_dist_list, good_list_sum, good_list_each, bad_list_sum, bad_list_each, dist_good_list, dist_bad_list = calc_woe_runner(col, each_bin_for_col)
    col_df = pd.DataFrame(data=[col_list_for_df]*len(list_combined[0]), columns=["col"])
    
    woe_list_for_df = pd.DataFrame(data=woe_list_for_df, columns=["WoE"])
    good_list_df    = pd.DataFrame(data=good_list_each, columns=["Num_good"], dtype=int)
    bad_list_df     = pd.DataFrame(data=bad_list_each, columns=["Num_bad"], dtype=int)
    dist_good_df    = pd.DataFrame(data=dist_good_list, columns=["Distr_good"])
    dist_bad_df     = pd.DataFrame(data=dist_bad_list, columns=["Distr_bad"])
    total_dist_df   = pd.DataFrame(data=total_dist_list, columns=["Distr_total"])
    l = []
    for e in np.array(list_combined[i]):
        l.append(str(e[0]) + "-" + str(e[1]))
    bin_value_df = pd.DataFrame(data=l, columns=["Attribute"])
    
    df_woe_concat = pd.concat([col_df, bin_value_df,good_list_df, 
                               bad_list_df,dist_good_df, dist_bad_df, 
                               woe_list_for_df, total_dist_df], axis=1)
    df_woe_list.append(df_woe_concat)
    i += 1
df_woe = pd.concat(df_woe_list, axis=0)


上記のコードを実行すると、下記のようなOutputが出てくるはずです。

f:id:akenkenken:20200104202640p:plain
Step1-2を完了したときのOutput

ステップ3の解説と実装

Step3では、実際に信用スコア(Scorecard Point)の計算をします。
計算式は、下記の通り。

      Score = (β×WoE+ α/n)×Factor + Offset/n

ここで出てくる、下記の項(Terms)は、すでに計算不要です。

  1. WoEは、Step2で計算済み。
  2. Factorは、Scaling Factorなので、定数。
  3. Offsetは、Scaling Factorなので、定数。
  4. nは、SeriousDlqin2yrsの予測に使う特徴量の数なので、定数。

なので、計算するべき項は、βとαだけになります。
このβとαは、ロジスティック回帰でモデリングした後、算出されます。

つまり、今回使用しているデータセットを例にすると、

  • "SeriousDlqin2yrs" をターゲット変数として、ロジスティック回帰でモデリング
  • モデリング後に、Scorecard Pointで使用する変数(今回は、age, DebtRatio, Monthly Income)の係数と切片項をそれぞれ取得
  • その係数が、βになる
  • その切片項が、αになる

つまり、ステップ3は、ロジスティック回帰でモデリングをする、という作業のみになり、モデリング後に係数と切片をそれぞれ取得すれば良いということになります。

最後にStep3の実装方法を、下記に共有します。

# ロジスティック回帰モデルを学習させる
lr = LogisticRegression()
lr.fit(X_train, y_train)
print("AUC:{}".format(roc_auc_score(lr.predict(X_test), y_test)))

# 実際に信用スコアを算出する: Score = (β×WoE+ α/n)×Factor + Offset/n
df_woe_with_score = df_woe.reset_index().copy()
df_woe_with_score.iloc[:, 3:-1] = df_woe_with_score.iloc[:, 3:-1].astype(float)

# Scaling factorを定義する
n = len(default_features_list)

alpha = lr.intercept_[0]
beta_age    = lr.coef_[0][0]    # Ageコラムの係数
beta_dept   = lr.coef_[0][1]    # DebtRationの係数
beta_income = lr.coef_[0][2] # MonthlyIncomeの係数

# Scorecard Pointの合計の最大値を600にするためのスケーリング
factor      = 20/np.log(2)
offset      = 600-factor*np.log(20)

print("factor:{0}, offset:{1}".format(factor, offset))

# Scorecard Pointの計算
df_woe_with_score["score"] = None
score_list = []
for i in range(len(df_woe)):
    woe = df_woe_with_score["WoE"].iloc[i]
    if df_woe_with_score.iloc[i]["col"] == "age":
        score = (beta_age*woe+(alpha/n))*factor + (offset/n)
        df_woe_with_score["score"].iloc[i] = round(score, 1)
    elif df_woe_with_score.iloc[i]["col"] == "DebtRatio":
        coef = beta_dept.copy()
        score = (beta_dept*woe+(alpha/n))*factor + (offset/n)
        df_woe_with_score["score"].iloc[i] = round(score, 1)
    elif df_woe_with_score.iloc[i]["col"] == "MonthlyIncome":
        coef = beta_income.copy()
        score = (beta_income*woe+(alpha/n))*factor + (offset/n)
        df_woe_with_score["score"].iloc[i] = round(score,1)

そして、無事、信用スコア作成できました!

f:id:akenkenken:20200104211528p:plain
信用スコア完成!

以上になります。読んでいただき、ありがとうございました!
質問あれば、遠慮なく、教えてください!

複数の出力(Multiple Output)ができるモデルの作り方

イントロ

1つのアーキテクチャで、2つの予測値を出すモデルの作り方、共有します。

学習データの説明

「ボストン不動産価格」のデータセットを使用します。

説明変数:

  1. LSTAT: 給与の低い職業に従事する人口の割合 (%)
  2. RM: 住居の平均部屋数

ターゲット:

  1. MEDV: 家賃価格
  2. NOX: NOx の濃度

本編

下記のコードの中で、大事なのは、2つです。

  1. ターゲット変数二つをどう、準備すればいいのか?
  2. モデルの出力層をどう表現すればいいのか?

この2つに注目してください。

 # 前処理
df = pd.DataFrame(data=load_boston()["data"], columns=load_boston()["feature_names"])
df["Price"] = load_boston()["target"]

scaler = StandardScaler()
X = scaler.fit_transform(df[["LSTAT", "RM"]].values)
y = scaler.fit_transform(df[["Price", "NOX"]])

X_train, X_test, y_train, y_test = train_test_split(X,y, random_state=0, test_size=0.05)

# ネットワークのアーキテクチャの定義
def DNN(num_layer, mid_units, initial, activation_hidden, input_dim):
    """
    二つのoutputを出力するニューラルネットワーク
    
    num_layer: 隠れ層の数 (int)
    mid_units: ニューロンの数 (list, inside of which is int)
    initial:重みの初期化(string)
    activation_hidden: 活性化関数
   input_dim:入力層の次元
   
    """
    inputs = Input(shape=(input_dim,))
    x = Dense(mid_units[0], activation=activation_hidden)(inputs) 
    
    # Define the hidden layers
    for i in range(num_layer):
        x = Dense(mid_units[i], activation=activation_hidden, kernel_initializer=initial)(x)
       
    predictions = Dense(2, activation="linear")(x)

    model = Model(inputs=inputs, outputs=predictions)

    return model

# モデルの学習
model = DNN(2, [3, 3], "normal", "relu", 2)
model.compile(optimizer="adam", loss="mean_squared_error", metrics=["mae"])
model.fit(X_train, y_train, epochs = 100, batch_size = 512, validation_split = 0.5, verbose = 0)

# モデルの評価
print("モデルのストラクチャー")
print("出力層の数が2になっていることに注目!!!")
print(model.summary())
print("----------------------------------------------------------------------------------------------------------------------")
print("モデルの評価(不動産価格の予想の精度とNOXの予想の精度の平均): {}".format(mean_absolute_error(model.predict(X_test), y_test)))

複数出力ができるモデルの世界観と実装の解説は、近日追記します!

Numpyで"線形回帰with勾配降下法"を実装し、オープンソースと性能を比べてみた

イントロ

Numpyで線形回帰 with 勾配降下法を実装しました。
一応、オープンソースに対抗できるだけの性能は出たので、共有します。

丁寧に書いたので、参考になると思います。

Numpyの線形回帰 with 勾配降下法

# ライブラリーのインポート
import numpy as  np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split

def updater(b_current, m_current, X_vector, y_vector, learning_rate):
     
    # 回帰式
    y_pred = np.dot(X_vector, m_current) + np.array(b_current)
    
    # コストの計算(MSE)
    residual = (y_pred-y_vector)
    mse = np.sum(residual**2) / (2*len(X_vector))

    # 勾配(Gradient)の計算
    gradient_b  = np.sum(residual)/ len(X_vector)
    gradient_m = np.dot(X_vector.T, residual)/len(X_vector)
    
    # mとbのアップデート(係数と切片のアップデート)
    b_current  -= learning_rate*gradient_b
    m_current -= learning_rate*gradient_m
    
    return b_current, m_current, mse

def gradient_decent_runner(X_vector, y_vector, learning_rate, num_iterations, starting_m, starting_b):
    b  =  starting_b
    m = starting_m
    # bとmの更新(ここが学習の中身!!)
    for i in range(num_iterations):
        b, m, mse = updater(b, m, X_vector, y_vector, learning_rate)
    print("After {0} iterations b = {1}, m = {2}, error = {3}".format(num_iterations, b, m, mse))
    return b, m

def main():
    # データの準備(ボストンの不動産価格)
    df = pd.DataFrame(load_boston().data, columns=load_boston().feature_names)[["RM", "LSTAT"]]
    df["target"] = load_boston().target
    m_dimention = df.drop("target", axis=1).shape[1] # 係数の次元の定義
    
    # 学習のためのデータ準備
    X = df.drop("target", axis=1).values
    y = df["target"].ravel()
    
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=100)
    
    # 最急降下法のハイパーパラメーター
    learning_rate    = 0.0001
    num_iterations = 100000
    
    # 回帰式の準備 (y=mx+b)
    initial_m = np.zeros(m_dimention)
    initial_b  = 0
    
    # 最急降下法の実行
    b, m = gradient_decent_runner(X_train, y_train, learning_rate, num_iterations, initial_m, initial_b)
    
    return b, m, X_train, X_test, y_train, y_test

if __name__ == '__main__':
    b, m, X_train, X_test, y_train, y_test = main()

# 性能評価
y_prediction_from_scratch = np.dot(X_test, m) + np.array(b)
print("Numpyで実装した線形回帰 with 勾配降下法")
print("-----------------------------------------------------")
print("R2 Score: {}".format(round(r2_score(y_test, y_prediction_from_scratch), 4)))
print("RMSE: {}".format(round(np.sqrt(mean_squared_error(y_test, y_prediction_from_scratch)), 4)))
print("-----------------------------------------------------")

オープンソースの線形回帰 with 最小二乗法

from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(X_train, y_train)

print("オープンソースの線形回帰")
print("-----------------------------------------------------")
print("R2 Score: {}".format(round(r2_score(y_test, lr.predict(X_test)), 4)))
print("RMSE: {}".format(round(np.sqrt(mean_squared_error(y_test, lr.predict(X_test))), 4)))
print("-----------------------------------------------------")

結果

オープンソースとほぼ同じ精度が出せた。

オープンソース

  • R2 Score: 0.6631
  • RMSE: 5.7044

Numpy

  • R2 Score: 0.6625
  • RMSE: 5.7095

LSTMをよりわかりやすく&より詳細に!(前処理・python実践編)

モチベーション

LSTMを使う際、実際に必要な前処理のコーディングを解説します。

本記事は、実践編なので、

LSTMのデータ準備の世界観の説明は省略し、
pythonでの実践的なコーディングのみを説明します。

LSTMのデータ準備の世界観が知りたい!!という方は、
是非この記事を参考にしてください:)
https://akenkenken.hatenablog.com/entry/2019/09/29/205040akenkenken.hatenablog.com

データセットについて

データは、前回の記事(https://akenkenken.hatenablog.com/entry/2019/09/29/205040)で説明した通り、
中国の大気汚染(PM2.5)のデータセットを使用します。

f:id:akenkenken:20190929183559p:plain
出典:UCI Machine Learning Repository

データの保存は、こちらからできます:)
https://raw.githubusercontent.com/jbrownlee/Datasets/master/pollution.csv

本編:Pythonでどう表現するのか?

まず、基本的な前処理を一気にやってしまいます。

### 前処理 ###
"""
前処理で考慮したポイント
1. 時間の情報が、'year', 'month', 'day', 'hour'でバラバラになっているので、繋げる(for文は禁止!!!)
2. 最初の24時間、連続して、PM2.5の値が欠損しているので、そこはDrop(その他は、0で欠損補完)
3. その他、今記事で取り扱う上で、不要なコラム(”NO”)の削除
"""
# ライブラリのインポート
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd


#データの読み込み
df            = pd.read_csv("pollution.csv", parse_dates = [['year', 'month', 'day', 'hour']], 
                            index_col=0).drop("No", axis=1)
df.index.name = "timestamp"
df.columns    = ['PM2.5', 'dew_point', 'temp', 'pressure', 'wind_direction', 'wind_speed', 'snow', 'rain']


#時間情報を適切な形に変換する
"""
データの前処理で、for文を使うのは、なるべく避けてください。処理が遅くなっちゃうので。
今回は、apply関数を使用しています。
"""
df = df.reset_index()
def datetime_format_changer(x):
    return datetime.strptime(x, '%Y %m %d %H')
df["timestamp"] = df["timestamp"].apply(datetime_format_changer)
df = df.set_index("timestamp")


# 欠損値の処理
"""
欠損値処理は、いろんな論点があり、本来はもっとまじめにやるべきですが、
本記事では割愛させていただきます<(_ _)>
"""
# print("最初の24時間、PM2.5が欠損しているのを確認!!")
# print("-----------------------------------------")
# print(df[:24])

df = df[24:]
df.fillna(0, inplace=True)
df.head()


上記のコードを実行したら、こんなoutputが出てくるはず。
f:id:akenkenken:20191006174923p:plain


さて、基本的な前処理が終了したので、いよいよLSTMのために、
データを準備します。

今回は、2つのパターンを例とします。

パターン1: 1変数での予測

  • ターゲット:PM2.5 
  • 説明変数 :PM2.5(2分前), PM2.5(1分前)

パターン2:2変数での予測

  • ターゲット:PM2.5 
  • 説明変数 :PM2.5(2分前), PM2.5(1分前), temp(2分前), temp(1分前)
# 関数の定義
def create_dataset(dataset, look_back):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        xset = []
        for j in range(dataset.shape[1]):
            a = dataset.iloc[i:i+look_back, j]
            xset.append(a)
        dataY.append(dataset.iloc[i+look_back, 0])      
        dataX.append(xset)
    return np.array(dataX), np.array(dataY)

# パターン1
a = df.loc[:, ['PM2.5']].head(100) 
x_pattern1, y_pattern1 = create_dataset(dataset=a, look_back=2)

# パターン2
a = df.loc[:, ['PM2.5',"temp"]].head(100) 
x_pattern2, y_pattern2 = create_dataset(dataset=a, look_back=2)

パターン1の方は、1変数で予測してるので、
下記の図のようなイメージ。

f:id:akenkenken:20191007145624p:plain
パターン1のイメージ

パターン2の方は、2変数で予測してるので、
下記の図のようなイメージ。

f:id:akenkenken:20191007144427p:plain
パターン2のイメージ

LSTMをよりわかりやすく&より詳細に!(前処理・世界観の説明編)

モチベーション(この記事は、どんな【疑問】の解消を目指すのか?)

LSTMのモデル作りたいんだけど、
どうやってデータ準備すればいいの?

って混乱してませんか?

 

その混乱の原因って、ずばり、これに尽きると思う。

 

・準備するべきデータの形 / 世界観 を理解してない

 

もうホント、これに尽きる。
逆に言うと、”世界観”さえ掴めば!理解できる。

 

なので、その混乱を解消するぞぉ!!という思いで、この記事書きます。

かなり、気合入れて作ったので、是非参考にしてください:) 

 

この記事は、ディープラーニングの実務で、ご活躍されている、巣籠先生の本を参考にしています。

本編

結論から言うと、理解するべきステップ(世界観)は、大きく3つです

  1. いくつ前(何秒/分/時間)までを、予測に使いたいのか?を決める。
    (何か、コードを書く必要はない。ただ決めるだけで良い。)

  2. 同じ変数同士で、ベクトルをつくる。
    (インプットが2変数なら、下記のようなイメージ。)
    ( [ [x1(t), x1(t+1), x1(t+2)], [x2(t), x2(t+1), x2(t+2)] ] 
    (一般的な、教師あり学習のデータ準備とは、違うから、気を付けて!!)

  3. 出来上がったら、Numpyの3次元配列に変換する
    (Kerasのお作法)

 

でもさぁ、言葉だけで言われても、わからないよね。。。

なので、図解で美しく(しつこく)、説明します!

 

データ準備のイメージ(図解で、右脳で、理解!)

下記、”回帰データ準備” vs ”LSTMのデータ準備"で、対比させて、説明します。

 

使うデータは、こちら。

 

今回使用するデータセット

出典:UCI Machine Learning Repository https://raw.githubusercontent.com/jbrownlee/Datasets/master/pollution.csv

 

"回帰のデータ準備"

回帰のデータ準備

同じ時点の変数を、一つのベクトルにまとめるイメージ

これは、皆さん、馴染みがあるんじゃないかな?

だって、X_input_dataframe.valuesでやっちゃえば済むような処理だし、直感的にわかりやすいですよね。

 

*実は普通の回帰モデルでも、t-1の変数を説明変数とするモデル
(Psudo-time seriesモデル/Naxモデル)を作ることできます。
が、その特集は、次回!

"LSTMのデータ準備"

まずは、時系列モデルの考え方の、おさらいから!

時系列モデルの予測のイメージ

時系列モデル:過去から未来を予測する→時系列情報が重要

いいですか?

ちゃんと理解してますか?

これ理解出来たら、LSTMのデータ準備の仕方も、直感的にイメージできるようになるので、しっかりおさらいを!

 

では、LSTMってどうやってデータ準備するの?

にお答えします。

今回は、「過去3時点前までの情報から予測する」LSTMを例にします。

LSTMのデータ準備、過去3時点前までの情報から予測するver

 
「は? 何言ってんの? よくわかんないぞ(プンプン)」ってなってしまった方、

もう一度、冷静に考えてみて。

回帰の場合は、基本的に、過去の値とか関係ないよね?

 

アイスクリームの売り上げ予測・回帰version だったら、

 

例えば、”2019/09/29(日)における”

X(平均気温、日照時間)を説明変数として、

”2019/09/29(日)における売り上げ” を予測するでしょ?

だから、、、回帰の場合は、、、同じ時点の変数でまとめないといけない!!!

 

でも、LSTMの場合は、時系列モデルだから、こうなるよね?

 

”2019/09/29(日)における売り上げを予測するために、” 

 【[ [平均気温(3日前), 平均気温(2日前), 平均気温(1日前)],

     [日照時間(3日前), 日照時間(2日前), 日照時間(1日前)] ]】を説明変数とする。

 

でしょ?

つまり、時系列情報が重要!であり、それを一緒にまとめてあげることが重要!なんですよね。

  

もし、まだわからない箇所がありましたら、遠慮なく、お申し付けください!

 

世界観がわかったら、下記の記事を見て、pythonで実践してみよう!

akenkenken.hatenablog.com