จาก ep ที่แล้วที่เราเรียนรู้เรื่อง Feature Engineering แบบ Basic กันไปแล้ว ใน ep นี้เราจะมาศึกษาข้อมูลที่เราพบบ่อย ๆ ในการทำงานอีกเช่นกัน คือ ข้อมูลแบบ Time Series เราจะสอนโมเดล Machine Learning ให้เรียนรู้จากข้อมูล Time Series ได้อย่างไร ให้ Forecast พยากรณ์ยอดขายร้านขายยา Rossmann ได้ความแม่นยำมากที่สุด

และจำเป็นต้องใช้ Deep Neural Network แบบ Recurrent Neural Network (RNN) หรือไม่

Time Series

Time Series คือ ข้อมูลที่เปลี่ยนแปลงไปตามเวลา การจะใช้ข้อมูลแบบนี้ เราจะแบ่งเวลาออกเป็นช่วงเท่า ๆ กัน เช่น รายชั่วโมง รายวัน รายเดือน แล้วรวบรวมข้อมูลตามช่วงนั้น มาวิเคราะห์อดีต เพื่อทำนายอนาคตต่อไป เช่น อุณหภูมิเฉลี่ยแต่ละวัน อุณหภูมิสูงสุดแต่ละเดือน ยอดขายรายวัน ยอดขายรายสัปดาห์ จำนวนคนเข้าร้านรายชั่วโมง จำนวนคนเข้าร้านรายเดือน etc.

Dataset

ในเคสนี้เราจะใช้ Dataset ชื่อ Rossmann Store Sales จาก Kaggle

Rossmann store, Bremen, Germany By Rami Tarawneh - Own work, CC BY 2.5, https://commons.wikimedia.org/w/index.php?curid=674596
Rossmann store, Bremen, Germany By Rami Tarawneh – Own work, CC BY 2.5, https://commons.wikimedia.org/w/index.php?curid=674596

Rossmann เป็นเชนร้านขายยา ที่มีมากกว่า 3000 สาขาใน 7 ประเทศแถบยุโรป ผู้จัดการสาขามีหน้าที่ประเมินยอดขายรายวัน ล่วงหน้า ตั้งแต่วันรุ่งขึ้น ไปจนถึง 6 สัปดาห์ข้างหน้า ยอดขายของแต่ละสาขาจะมากขึ้นหรือน้อยลง ขึ้นอยู่กับหลายปัจจัย เช่น โปรโมชั่น คู่แข่ง วันหยุดโรงเรียน วันหยุดราชการ เทศกาลต่าง ๆ

ใน Dataset จะมีข้อมูลประวัติยอดขาย ระหว่างวันที่ 1 Jan 2013 – 31 July 2015 ของ 1115 สาขา ในประเทศเยอรมัน หน้าที่ของเราก็คือ ทำนายยอดขายใน Column Sales ใน Test Set

Rossmann Store Sales ER Diagram
Rossmann Store Sales ER Diagram

มีไฟล์ดังนี้

  • train.csv – ข้อมูลสาขารายวัน และประวัติการขาย
  • test.csv – ข้อมูลสาขารายวัน ไม่รวมยอดขาย
  • sample_submission.csv – ตัวอย่างไฟล์สำหรับ Submit
  • store.csv – ข้อมูลสาขา Master

ข้อมูลเพิ่มเติม

  • state_names.csv
  • googletrend.csv
  • store_states.csv
  • weather.csv

หมายเหตุ มีบางสาขากำลังปิด เพื่อ Renovate

เรามาเริ่มกันเลยดีกว่า

Open In Colab

เคสนี้เราจะทำนายยอดขาย ของร้านขายยาในเครือ Rossmann ที่มีกว่า 3000 สาขา ใน 7 ประเทศแถบยุโรป โดยเราจะทำนายจากข้อมูล ร้านค้า โปรโมชั่น ข้อมูลคู่แข่ง และข้อมูลประกอบภายนอก ได้แก่ Google Trends, พยากรณ์อากาศ, etc.

0. Magic Commands

In [0]:
%reload_ext autoreload
%autoreload 2
%matplotlib inline

1. Import Library

In [0]:
from fastai import *
from fastai.tabular import *

2. ข้อมูล

เคสนี้เราจะใช้ Dataset ชื่อ Rossmann Store Sales จาก Kaggle

รัน cell ด้านล่างเพื่อดาวน์โหลด Dataset

In [0]:
# ! wget http://files.fast.ai/part2/lesson14/rossmann.tgz 
# ! mv ./rossmann.tgz ~/.fastai/data
# ! mkdir ~/.fastai/data/rossmann
# ! tar -xvzf ~/.fastai/data/rossmann.tgz -C ~/.fastai/data/rossmann
In [0]:
path=Config().data_path()/Path('rossmann/')

2.1 ดูรายการไฟล์

ls ดูว่ามีไฟล์อะไรบ้าง

In [0]:
path.ls()
Out[0]:
[PosixPath('/home/jupyter/.fastai/data/rossmann/state_names.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/joined'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/test.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/sample_submission.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/googletrend.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/weather.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/joined_test'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/store.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/train_clean'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/models'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/train.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/df'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/store_states.csv'),
 PosixPath('/home/jupyter/.fastai/data/rossmann/test_clean')]

ใช้ Pandas read_csv ไปใส่ DataFrame ตามชื่อไฟล์

In [0]:
table_names = ['train', 'store', 'store_states', 'state_names', 'googletrend', 'weather', 'test']
tables = [pd.read_csv(path/f'{fname}.csv', low_memory=False) for fname in table_names]
train, store, store_states, state_names, googletrend, weather, test = tables
len(train),len(test)
Out[0]:
(1017209, 41088)

2.2 ดูข้อมูลทุกตาราง

เป็นการดี ที่เราสำรวจข้อมูลก่อน เพื่อจะได้รู้จักหน้าตาข้อมูลในทุกตาราง

In [0]:
train.head()
Out[0]:
StoreDayOfWeekDateSalesCustomersOpenPromoStateHolidaySchoolHoliday
0152015-07-3152635551101
1252015-07-3160646251101
2352015-07-3183148211101
3452015-07-311399514981101
4552015-07-3148225591101
In [0]:
train.describe(include='all')
Out[0]:
StoreDayOfWeekDateSalesCustomersOpenPromoStateHolidaySchoolHoliday
count1.017209e+061.017209e+0610172091.017209e+061.017209e+061.017209e+061.017209e+0610172091.017209e+06
uniqueNaNNaN942NaNNaNNaNNaN4NaN
topNaNNaN2015-02-16NaNNaNNaNNaN0NaN
freqNaNNaN1115NaNNaNNaNNaN986159NaN
mean5.584297e+023.998341e+00NaN5.773819e+036.331459e+028.301067e-013.815145e-01NaN1.786467e-01
std3.219087e+021.997391e+00NaN3.849926e+034.644117e+023.755392e-014.857586e-01NaN3.830564e-01
min1.000000e+001.000000e+00NaN0.000000e+000.000000e+000.000000e+000.000000e+00NaN0.000000e+00
25%2.800000e+022.000000e+00NaN3.727000e+034.050000e+021.000000e+000.000000e+00NaN0.000000e+00
50%5.580000e+024.000000e+00NaN5.744000e+036.090000e+021.000000e+000.000000e+00NaN0.000000e+00
75%8.380000e+026.000000e+00NaN7.856000e+038.370000e+021.000000e+001.000000e+00NaN0.000000e+00
max1.115000e+037.000000e+00NaN4.155100e+047.388000e+031.000000e+001.000000e+00NaN1.000000e+00
In [0]:
store.head()
Out[0]:
StoreStoreTypeAssortmentCompetitionDistanceCompetitionOpenSinceMonthCompetitionOpenSinceYearPromo2Promo2SinceWeekPromo2SinceYearPromoInterval
01ca1270.09.02008.00NaNNaNNaN
12aa570.011.02007.0113.02010.0Jan,Apr,Jul,Oct
23aa14130.012.02006.0114.02011.0Jan,Apr,Jul,Oct
34cc620.09.02009.00NaNNaNNaN
45aa29910.04.02015.00NaNNaNNaN
In [0]:
store_states.head()
Out[0]:
StoreState
01HE
12TH
23NW
34BE
45SN
In [0]:
state_names.head()
Out[0]:
StateNameState
0BadenWuerttembergBW
1BayernBY
2BerlinBE
3BrandenburgBB
4BremenHB
In [0]:
googletrend.head()
Out[0]:
fileweektrend
0Rossmann_DE_SN2012-12-02 - 2012-12-0896
1Rossmann_DE_SN2012-12-09 - 2012-12-1595
2Rossmann_DE_SN2012-12-16 - 2012-12-2291
3Rossmann_DE_SN2012-12-23 - 2012-12-2948
4Rossmann_DE_SN2012-12-30 - 2013-01-0567
In [0]:
weather.head()
Out[0]:
fileDateMax_TemperatureCMean_TemperatureCMin_TemperatureCDew_PointCMeanDew_PointCMin_DewpointCMax_HumidityMean_Humidity...Max_VisibilityKmMean_VisibilityKmMin_VisibilitykMMax_Wind_SpeedKm_hMean_Wind_SpeedKm_hMax_Gust_SpeedKm_hPrecipitationmmCloudCoverEventsWindDirDegrees
0NordrheinWestfalen2013-01-018427519487...31.012.04.0392658.05.086.0Rain215
1NordrheinWestfalen2013-01-027415329385...31.014.010.02416NaN0.006.0Rain225
2NordrheinWestfalen2013-01-031186108410093...31.08.02.02621NaN1.027.0Rain240
3NordrheinWestfalen2013-01-0499899810094...11.05.02.02314NaN0.257.0Rain263
4NordrheinWestfalen2013-01-0588787610094...10.06.03.01610NaN0.007.0Rain268

5 rows × 24 columns

In [0]:
test.head()
Out[0]:
IdStoreDayOfWeekDateOpenPromoStateHolidaySchoolHoliday
01142015-09-171.0100
12342015-09-171.0100
23742015-09-171.0100
34842015-09-171.0100
45942015-09-171.0100

2.3 Feature Engineering

แปลงชนิดข้อมูล train.StateHoliday

ดูข้อมูลวันหยุดราชการ ว่ามีค่าอะไรบ้าง a = public holiday วันหยุดราชการ, b = Easter holiday วันอีสเตอร์, c = Christmas วันคริสต์มาส, 0 = None

In [0]:
train.StateHoliday.unique()
Out[0]:
array(['0', 'a', 'b', 'c'], dtype=object)

แต่ละค่ามีจำนวนเท่าไร

In [0]:
train.StateHoliday.value_counts()
Out[0]:
0    986159
a     20260
b      6690
c      4100
Name: StateHoliday, dtype: int64

จะเห็นว่าข้อมูล skew มาก เราจะแปลงให้เป็น boolean ว่าเป็นวันหยุด หรือไม่เป็นวันหยุด เท่านั้น

In [0]:
train.StateHoliday = train.StateHoliday!='0'
test.StateHoliday = test.StateHoliday!='0'

หลังแปลง

In [0]:
train.StateHoliday.value_counts()
Out[0]:
False    986159
True      31050
Name: StateHoliday, dtype: int64

แตก Field ใหม่

ในตาราง googletrend ข้อมูลจะมาเป็น ช่วงวัน 1 สัปดาห์ เราจะแตก Column เป็น Date ของวันที่เริ่ม และ แตก Column State ออกมาจากท้ายรหัสใน file

และแก้ HB,NI เป็น NI ให้เหมือน State อื่น ๆ

เรื่องการใช้ Pandas จะอธิบายต่อไป

In [0]:
googletrend['Date'] = googletrend.week.str.split(' - ', expand=True)[0]
googletrend['State'] = googletrend.file.str.split('_', expand=True)[2]
googletrend.loc[googletrend.State=='NI', "State"] = 'HB,NI'

Date Part

เราสามารถทำ Feature Engineering แตกความหมายแฝงใน Date ออกเป็น Column ย่อย ๆ (นอกเหนือจากวันเดือนปี) เพื่อให้โมเดล มอง Pattern ได้ง่ายขึ้น เช่น ปี เดือน วัน สัปดาห์ วันที่เท่าไรของสัปดาห์ วันที่เท่าไรของปี เป็นวันสิ้นเดือนหรือไม่ เป็นวันต้นเดือนหรือไม่ เป็นวันสิ้นใตรมาสหรือไม่ เป็นวันสิ้นปีหรือไม่ etc.

เพราะ วันสิ้นเดือน วันสุดสัปดาห์ วันสิ้นปี น่าจะมีผลต่อยอดขาย ต่างกับวันธรรมดา ถ้าเราให้แต่ Date กับโมเดล จะเป็นการยากมากที่โมเดลจะมองออกเอง

In [0]:
def add_datepart(df, fldname, drop=True, time=False):
    "Helper function that adds columns relevant to a date."
    fld = df[fldname]
    fld_dtype = fld.dtype
    if isinstance(fld_dtype, pd.core.dtypes.dtypes.DatetimeTZDtype):
        fld_dtype = np.datetime64

    if not np.issubdtype(fld_dtype, np.datetime64):
        df[fldname] = fld = pd.to_datetime(fld, infer_datetime_format=True)
    targ_pre = re.sub('[Dd]ate$', '', fldname)
    attr = ['Year', 'Month', 'Week', 'Day', 'Dayofweek', 'Dayofyear',
            'Is_month_end', 'Is_month_start', 'Is_quarter_end', 'Is_quarter_start', 'Is_year_end', 'Is_year_start']
    if time: attr = attr + ['Hour', 'Minute', 'Second']
    for n in attr: df[targ_pre + n] = getattr(fld.dt, n.lower())
    df[targ_pre + 'Elapsed'] = fld.astype(np.int64) // 10 ** 9
    if drop: df.drop(fldname, axis=1, inplace=True)

เราจะเรียกฟังก์ชัน add_datepart กับ Column Date ของทุกตาราง

In [0]:
add_datepart(weather, "Date", drop=False)
add_datepart(googletrend, "Date", drop=False)
add_datepart(train, "Date", drop=False)
add_datepart(test, "Date", drop=False)

ลองดู googletrend จะมี Column เพิ่มจาก 5 กลายเป็น 18

In [0]:
googletrend.head()
Out[0]:
fileweektrendDateStateYearMonthWeekDayDayofweekDayofyearIs_month_endIs_month_startIs_quarter_endIs_quarter_startIs_year_endIs_year_startElapsed
0Rossmann_DE_SN2012-12-02 - 2012-12-08962012-12-02SN2012124826337FalseFalseFalseFalseFalseFalse1354406400
1Rossmann_DE_SN2012-12-09 - 2012-12-15952012-12-09SN2012124996344FalseFalseFalseFalseFalseFalse1355011200
2Rossmann_DE_SN2012-12-16 - 2012-12-22912012-12-16SN20121250166351FalseFalseFalseFalseFalseFalse1355616000
3Rossmann_DE_SN2012-12-23 - 2012-12-29482012-12-23SN20121251236358FalseFalseFalseFalseFalseFalse1356220800
4Rossmann_DE_SN2012-12-30 - 2013-01-05672012-12-30SN20121252306365FalseFalseFalseFalseFalseFalse1356825600

Join ตารางเข้าด้วยกัน

Join ข้อมูลเข้าด้วยกัน เป็นตารางใหญ่ตารางเดียว จะได้เอาไปสร้าง Databunch สำหรับให้โมเดล

ประกาศฟังก์ชันสำหรับ Join 2 Dataframe เข้าด้วยกัน จาก Column ที่กำหนด แบบ Left Outer Join ด้วย merge แล้วจึงเช็คค่า null ดูว่ามีข้อมูลหลุดไหม จะดีกว่า Inner Join ที่เราจะไม่สามารถทราบได้ว่า มีข้อมูลหลุดหรือไม่

โดยถ้ามี Column ชื่อซ้ำกัน ให้เติมชื่อ Column ใน df ทางขวาด้วย '_y'

In [0]:
def join_df(left, right, left_on, right_on=None, suffix='_y'):
    if right_on is None: right_on = left_on
    return left.merge(right, how='left', left_on=left_on, right_on=right_on, 
                      suffixes=("", suffix))

Join weather กับ state_names

In [0]:
weather = join_df(weather, state_names, "file", "StateName")

Join store กับ store_states

In [0]:
store = join_df(store, store_states, "Store")
len(store[store.State.isnull()])
Out[0]:
0

Join train, test กับ store ตั้งชื่อว่า joined, joined_test แล้วเช็คว่า Record ไหนที่หลุดบ้าง จะมีค่าเป็น null

In [0]:
joined = join_df(train, store, "Store")
joined_test = join_df(test, store, "Store")
len(joined[joined.StoreType.isnull()]),len(joined_test[joined_test.StoreType.isnull()])
Out[0]:
(0, 0)

Join joined, joined_test กับ googletrend แล้วเช็คว่า Record ไหนที่หลุดบ้าง จะมีค่าเป็น null

In [0]:
joined = join_df(joined, googletrend, ["State","Year", "Week"])
joined_test = join_df(joined_test, googletrend, ["State","Year", "Week"])
len(joined[joined.trend.isnull()]),len(joined_test[joined_test.trend.isnull()])
Out[0]:
(0, 0)

เราจะแยก googletrend ของทั้งประเทศเยอรมันไว้ต่างหาก เป็นอีกตาราง

In [0]:
trend_de = googletrend[googletrend.file == 'Rossmann_DE']

Join joined, joined_test กับ trend_de แล้วเช็คว่า Record ไหนที่หลุดบ้าง จะมีค่าเป็น null

In [0]:
joined = joined.merge(trend_de, 'left', ["Year", "Week"], suffixes=('', '_DE'))
joined_test = joined_test.merge(trend_de, 'left', ["Year", "Week"], suffixes=('', '_DE'))
len(joined[joined.trend_DE.isnull()]),len(joined_test[joined_test.trend_DE.isnull()])
Out[0]:
(0, 0)

Join joined, joined_test กับ weather แล้วเช็คว่า Record ไหนที่หลุดบ้าง จะมีค่าเป็น null

In [0]:
joined = join_df(joined, weather, ["State","Date"])
joined_test = join_df(joined_test, weather, ["State","Date"])
len(joined[joined.Mean_TemperatureC.isnull()]),len(joined_test[joined_test.Mean_TemperatureC.isnull()])
Out[0]:
(0, 0)

วนลูปลบ Column ซ้ำที่เราให้ลงท้ายด้วย '_y'

In [0]:
for df in (joined, joined_test):
    for c in df.columns:
        if c.endswith('_y'):
            if c in df.columns: df.drop(c, inplace=True, axis=1)

เนื่องจากโมเดลอาจจะมีปัญหา กับข้อมูลขาด NA (Not Available) เราจะเติมข้อมูลที่ขาดไปด้วย fillna ด้วย ปี 1900, 1 เดือน และ 1 สัปดาห์

In [0]:
for df in (joined,joined_test):
    df['CompetitionOpenSinceYear'] = df.CompetitionOpenSinceYear.fillna(1900).astype(np.int32)
    df['CompetitionOpenSinceMonth'] = df.CompetitionOpenSinceMonth.fillna(1).astype(np.int32)
    df['Promo2SinceYear'] = df.Promo2SinceYear.fillna(1900).astype(np.int32)
    df['Promo2SinceWeek'] = df.Promo2SinceWeek.fillna(1).astype(np.int32)

สร้าง Field Date ขึ้นมาจาก 2 Field Number และใช้ Field ใหม่นั้น คำนวน จำนวนวันตั้งแต่คู่แข่งเปิดสาขา จาก CompetitionOpenSinceYear และ CompetitionOpenSinceMonth

In [0]:
for df in (joined,joined_test):
    df["CompetitionOpenSince"] = pd.to_datetime(dict(year=df.CompetitionOpenSinceYear, 
                                                     month=df.CompetitionOpenSinceMonth, day=15))
    df["CompetitionDaysOpen"] = df.Date.subtract(df.CompetitionOpenSince).dt.days

Clear ข้อมูลนอกลู่นอกทาง เช่น คู่แข่งยังไม่เปิดร้าน แต่จะเปิดในอนาคต หรือเปิดมาแล้วนานมาก ก่อน 1990 ให้เป็น 0

In [0]:
for df in (joined,joined_test):
    df.loc[df.CompetitionDaysOpen<0, "CompetitionDaysOpen"] = 0
    df.loc[df.CompetitionOpenSinceYear<1990, "CompetitionDaysOpen"] = 0

สร้าง CompetitionMonthsOpen จาก CompetitionDaysOpen หาร 30 วัน และถ้าค่าเกิน 24 เดือน ก็ให้เท่ากับ 24 เดือน

In [0]:
for df in (joined,joined_test):
    df["CompetitionMonthsOpen"] = df["CompetitionDaysOpen"]//30
    df.loc[df.CompetitionMonthsOpen>24, "CompetitionMonthsOpen"] = 24
joined.CompetitionMonthsOpen.unique()
Out[0]:
array([24,  3, 19,  9,  0, 16, 17,  7, 15, 22, 11, 13,  2, 23, 12,  4, 10,  1, 14, 20,  8, 18,  6, 21,  5])

Week

รัน cell ด้านล่างเพื่อ install isoweek

In [0]:
# ! pip install isoweek

แปลงข้อมูล Week จาก Promo2SinceYear, Promo2SinceWeek เป็น Date ชื่อ Promo2Since จะได้ใช้งานสะดวกขึ้น และสร้าง Column ใหม่ Promo2Days นับวันจนวันที่ใน Column Date

In [0]:
from isoweek import Week
for df in (joined,joined_test):
    df["Promo2Since"] = pd.to_datetime(df.apply(lambda x: Week(
        x.Promo2SinceYear, x.Promo2SinceWeek).monday(), axis=1))
    df["Promo2Days"] = df.Date.subtract(df["Promo2Since"]).dt.days

Clear ข้อมูล Promo2Days, Promo2SinceYear โปรโมชั่นในอนาคต หรือโปรโมชั่นนานมากแล้ว ก่อน 1990 ให้เป็น 0

สร้าง Column ใหม่ Promo2Weeks จำกัดข้อมูลให้อยู่ในช่วงที่ต้องการ ระหว่าง 0 ถึง 24

In [0]:
for df in (joined,joined_test):
    df.loc[df.Promo2Days<0, "Promo2Days"] = 0
    df.loc[df.Promo2SinceYear<1990, "Promo2Days"] = 0
    df["Promo2Weeks"] = df["Promo2Days"]//7
    df.loc[df.Promo2Weeks<0, "Promo2Weeks"] = 0
    df.loc[df.Promo2Weeks>25, "Promo2Weeks"] = 25
    df.Promo2Weeks.unique()

Save ข้อมูลที่เราแปลงเรียบร้อยแล้ว เอาไว้ก่อน เวลามีปัญหาจะได้ไม่ต้องทำใหม่

In [0]:
joined.to_pickle(path/'joined')
joined_test.to_pickle(path/'joined_test')

Durations

ก่อนหน้านี้เป็นการ Feature Engineering ดึงความหมายของข้อมูลระหว่าง Column ต่อไปเราจะ ดึงความหมายของข้อมูลระหว่าง Row ดูบ้าง เช่น

  • ค่าเฉลี่ยแบบเคลื่อนที่
  • ระยะเวลาก่อนจะถึงเหตุการณ์ครั้งต่อไป
  • ระยะเวลาหลังจากเกิดเหตุการณ์ครั้งที่แล้ว

ประกาศฟังก์ชัน คำนวนระยะเวลา วันที่ผ่านไป ของ Column ที่กำหนด นับวันที่มีการเปลี่ยนแปลงค่า ถ้าพบว่าค่าเปลี่ยนอีกครั้ง ก็จะ reset counter เป็น 0 แล้วเริ่มนับใหม่ และเริ่มนับใหม่เมื่อเปลี่ยน Store สาขา

In [0]:
def get_elapsed(fld, pre):
    day1 = np.timedelta64(1, 'D')
    last_date = np.datetime64()
    last_store = 0
    res = []

    for s,v,d in zip(df.Store.values,df[fld].values, df.Date.values):
        if s != last_store:
            last_date = np.datetime64()
            last_store = s
        if v: last_date = d
        res.append(((d-last_date).astype('timedelta64[D]') / day1))
    df[pre+fld] = res

เราจะเรียกฟังก์ชันกับทุก Column ด้านล่าง

In [0]:
columns = ["Date", "Store", "Promo", "StateHoliday", "SchoolHoliday"]

เราจะ append train, test dataframe เข้าด้วยกัน เพราะมันไม่ตรงกันอยู่แล้ว (สาขา, วันที่) จะได้ทำทีเดียว

In [0]:
#df = train[columns]
df = train[columns].append(test[columns])

เรียกฟังก์ชัน get_elapsed ด้วย SchoolHoliday ทั้ง 2 ทิศทาง ทั้ง Before และ After

In [0]:
fld = 'SchoolHoliday'
df = df.sort_values(['Store', 'Date'])
get_elapsed(fld, 'After')
df = df.sort_values(['Store', 'Date'], ascending=[True, False])
get_elapsed(fld, 'Before')

เรียกฟังก์ชัน get_elapsed ด้วย StateHoliday ทั้ง 2 ทิศทาง ทั้ง Before และ After

In [0]:
fld = 'StateHoliday'
df = df.sort_values(['Store', 'Date'])
get_elapsed(fld, 'After')
df = df.sort_values(['Store', 'Date'], ascending=[True, False])
get_elapsed(fld, 'Before')

เรียกฟังก์ชัน get_elapsed ด้วย Promo ทั้ง 2 ทิศทาง ทั้ง Before และ After

In [0]:
fld = 'Promo'
df = df.sort_values(['Store', 'Date'])
get_elapsed(fld, 'After')
df = df.sort_values(['Store', 'Date'], ascending=[True, False])
get_elapsed(fld, 'Before')

เราจะ fillna ข้อมูลที่ขาดไป จาก การเรียกฟังก์ชันด้านบน ด้วยเลข 0

In [0]:
df = df.set_index("Date")
In [0]:
columns = ['SchoolHoliday', 'StateHoliday', 'Promo']
In [0]:
for o in ['Before', 'After']:
    for p in columns:
        a = o+p
        df[a] = df[a].fillna(0).astype(int)

Rolling

ค่า Sum แบบเคลื่อนที่ window = 7 วัน

เปิดดู df ว่าหน้าตาเป็นอย่างไร

In [0]:
df.head()
Out[0]:
StorePromoStateHolidaySchoolHolidayAfterSchoolHolidayBeforeSchoolHolidayAfterStateHolidayBeforeStateHolidayAfterPromoBeforePromo
Date
2015-09-1711False0130105000
2015-09-1611False0120104000
2015-09-1511False0110103000
2015-09-1411False0100102000
2015-09-1310False09010109-1

สร้าง bwd, fwd จาก df ใน Column ที่กำหนด ('Store', 'SchoolHoliday', 'StateHoliday', 'Promo') โดย Group by Store แล้ว Sum ทุก Column ใน windows 7 วัน

In [0]:
bwd = df[['Store']+columns].sort_index().groupby("Store").rolling(7, min_periods=1).sum()
In [0]:
bwd.head()
Out[0]:
StoreSchoolHolidayStateHolidayPromo
StoreDate
12013-01-011.01.01.00.0
2013-01-022.02.01.00.0
2013-01-033.03.01.00.0
2013-01-044.04.01.00.0
2013-01-055.05.01.00.0

กลับทิศจากวันที่มากไปน้อย

In [0]:
fwd = df[['Store']+columns].sort_index(ascending=False
                                      ).groupby("Store").rolling(7, min_periods=1).sum()

ลบ Column Store ทิ้ง เพราะ Sum ของ Store ย้อนหลัง 7 วัน ไม่มีความหมาย แล้ว Reset index

In [0]:
bwd.drop('Store',1,inplace=True)
bwd.reset_index(inplace=True)
In [0]:
bwd.head()
Out[0]:
StoreDateSchoolHolidayStateHolidayPromo
012013-01-011.01.00.0
112013-01-022.01.00.0
212013-01-033.01.00.0
312013-01-044.01.00.0
412013-01-055.01.00.0
In [0]:
fwd.drop('Store',1,inplace=True)
fwd.reset_index(inplace=True)

Reset index ของ df เพื่อเตรียม merge กับ bwd, fwd

In [0]:
df.reset_index(inplace=True)
In [0]:
df.head()
Out[0]:
DateStorePromoStateHolidaySchoolHolidayAfterSchoolHolidayBeforeSchoolHolidayAfterStateHolidayBeforeStateHolidayAfterPromoBeforePromo
02015-09-1711False0130105000
12015-09-1611False0120104000
22015-09-1511False0110103000
32015-09-1411False0100102000
42015-09-1310False09010109-1

สั่ง merge df กับ bwd, fwd ถ้าเจอชื่อซ้ำ ให้ต่อท้ายด้วย _bw และ _fw ตามลำดับ

In [0]:
df = df.merge(bwd, 'left', ['Date', 'Store'], suffixes=['', '_bw'])
df = df.merge(fwd, 'left', ['Date', 'Store'], suffixes=['', '_fw'])

drop Column ปกติทิ้งไป ให้เหลือแต่ Column ที่เราสร้างใหม่จาก Duration, Rolling สังเกตชื่อ BeforeXXX, AfterXXX, XXX_bw, XXX_fw

In [0]:
df.drop(columns,1,inplace=True)

ลองดู df ที่มีแต่ Column ที่เราสร้างใหม่

In [0]:
df.head()
Out[0]:
DateStoreAfterSchoolHolidayBeforeSchoolHolidayAfterStateHolidayBeforeStateHolidayAfterPromoBeforePromoSchoolHoliday_bwStateHoliday_bwPromo_bwSchoolHoliday_fwStateHoliday_fwPromo_fw
02015-09-1711301050000.00.04.00.00.01.0
12015-09-1611201040000.00.03.00.00.02.0
22015-09-1511101030000.00.02.00.00.03.0
32015-09-1411001020000.00.01.00.00.04.0
42015-09-1319010109-10.00.00.00.00.04.0

Save ไว้ก่อน

In [0]:
df.to_pickle(path/'df')

แปลง Column Date จาก Type String เป็น Date

In [0]:
df["Date"] = pd.to_datetime(df.Date)

ลองดูว่ามี Column อะไรบ้าง

In [0]:
df.columns
Out[0]:
Index(['Date', 'Store', 'AfterSchoolHoliday', 'BeforeSchoolHoliday',
       'AfterStateHoliday', 'BeforeStateHoliday', 'AfterPromo', 'BeforePromo',
       'SchoolHoliday_bw', 'StateHoliday_bw', 'Promo_bw', 'SchoolHoliday_fw',
       'StateHoliday_fw', 'Promo_fw'],
      dtype='object')

Load joined และ joined_test ที่เก็บ dataframe ที่ Join ทุกตารางด้วยกันไว้ ขึ้นมา

In [0]:
joined = pd.read_pickle(path/'joined')
joined_test = pd.read_pickle(path/f'joined_test')

Join เข้ากับ df ที่เราสร้างใหม่

In [0]:
joined = join_df(joined, df, ['Store', 'Date'])
In [0]:
joined_test = join_df(joined_test, df, ['Store', 'Date'])

เนื่องจากใน df เรารวม Training Set และ Test Set เข้าด้วยกัน เมื่อ merge กับ joined เราจึงต้องกรอง joined ให้เหลือแต่ Record ที่มีข้อมูล ยอดขาย ไม่เท่ากับ 0 ให้เหลือแต่ Training Set เท่านั้น

In [0]:
joined = joined[joined.Sales!=0]

Reset index

In [0]:
joined.reset_index(inplace=True)
joined_test.reset_index(inplace=True)

Save joined สุดท้ายไว้ก่อน มีอะไร จะได้ไม่ต้องแปลงใหม่

In [0]:
joined.to_pickle(path/'train_clean')
joined_test.to_pickle(path/'test_clean')

3. เตรียมข้อมูล

Load ข้อมูล joined สุดท้ายขึ้นมาใส่ train_df, test_df

In [0]:
train_df = pd.read_pickle(path/'train_clean')
test_df = pd.read_pickle(path/'test_clean')

นับดูว่า Training Set, Test Set มีอย่างละกี่ Record

In [0]:
len(train_df),len(test_df)
Out[0]:
(844338, 41088)

เนื่องจาก Column เยอะมาก ถึง 93 Column เราจะ Tranpose แล้วค่อย Print

In [0]:
train_df.head().T
Out[0]:
01234
index01234
Store12345
DayOfWeek55555
Date2015-07-31 00:00:002015-07-31 00:00:002015-07-31 00:00:002015-07-31 00:00:002015-07-31 00:00:00
Sales526360648314139954822
Customers5556258211498559
Open11111
Promo11111
StateHolidayFalseFalseFalseFalseFalse
SchoolHoliday11111
Year20152015201520152015
Month77777
Week3131313131
Day3131313131
Dayofweek44444
Dayofyear212212212212212
Is_month_endTrueTrueTrueTrueTrue
Is_month_startFalseFalseFalseFalseFalse
Is_quarter_endFalseFalseFalseFalseFalse
Is_quarter_startFalseFalseFalseFalseFalse
Is_year_endFalseFalseFalseFalseFalse
Is_year_startFalseFalseFalseFalseFalse
Elapsed14383008001438300800143830080014383008001438300800
StoreTypecaaca
Assortmentaaaca
CompetitionDistance12705701413062029910
CompetitionOpenSinceMonth9111294
CompetitionOpenSinceYear20082007200620092015
Promo201100
Promo2SinceWeek1131411
..................
Min_Sea_Level_PressurehPa10151017101710141016
Max_VisibilityKm3110311010
Mean_VisibilityKm1510141010
Min_VisibilitykM1010101010
Max_Wind_SpeedKm_h2414142314
Mean_Wind_SpeedKm_h111151611
Max_Gust_SpeedKm_hNaNNaNNaNNaNNaN
Precipitationmm00000
CloudCover14264
EventsFogFogFogNaNNaN
WindDirDegrees13309354282290
StateNameHessenThueringenNordrheinWestfalenBerlinSachsen
CompetitionOpenSince2008-09-15 00:00:002007-11-15 00:00:002006-12-15 00:00:002009-09-15 00:00:002015-04-15 00:00:00
CompetitionDaysOpen2510281531502145107
CompetitionMonthsOpen242424243
Promo2Since1900-01-01 00:00:002010-03-29 00:00:002011-04-04 00:00:001900-01-01 00:00:001900-01-01 00:00:00
Promo2Days01950157900
Promo2Weeks0252500
AfterSchoolHoliday00000
BeforeSchoolHoliday00000
AfterStateHoliday5767576757
BeforeStateHoliday00000
AfterPromo00000
BeforePromo00000
SchoolHoliday_bw55555
StateHoliday_bw00000
Promo_bw55555
SchoolHoliday_fw71511
StateHoliday_fw00000
Promo_fw51511

93 rows × 5 columns

In [0]:
test_df.head().T
Out[0]:
01234
index01234
Id12345
Store13789
DayOfWeek44444
Date2015-09-17 00:00:002015-09-17 00:00:002015-09-17 00:00:002015-09-17 00:00:002015-09-17 00:00:00
Open11111
Promo11111
StateHolidayFalseFalseFalseFalseFalse
SchoolHoliday00000
Year20152015201520152015
Month99999
Week3838383838
Day1717171717
Dayofweek33333
Dayofyear260260260260260
Is_month_endFalseFalseFalseFalseFalse
Is_month_startFalseFalseFalseFalseFalse
Is_quarter_endFalseFalseFalseFalseFalse
Is_quarter_startFalseFalseFalseFalseFalse
Is_year_endFalseFalseFalseFalseFalse
Is_year_startFalseFalseFalseFalseFalse
Elapsed14424480001442448000144244800014424480001442448000
StoreTypecaaaa
Assortmentaacac
CompetitionDistance1270141302400075202030
CompetitionOpenSinceMonth9124108
CompetitionOpenSinceYear20082006201320142000
Promo201000
Promo2SinceWeek114111
Promo2SinceYear19002011190019001900
..................
Min_Sea_Level_PressurehPa999993994994993
Max_VisibilityKm3126262626
Mean_VisibilityKm1212101012
Min_VisibilitykM78228
Max_Wind_SpeedKm_h2937292937
Mean_Wind_SpeedKm_h1426141426
Max_Gust_SpeedKm_hNaN53454553
Precipitationmm7.113.050.760.763.05
CloudCover66556
EventsRainRainRainRainRain
WindDirDegrees191187209209187
StateNameHessenNordrheinWestfalenSchleswigHolsteinSchleswigHolsteinNordrheinWestfalen
CompetitionOpenSince2008-09-15 00:00:002006-12-15 00:00:002013-04-15 00:00:002014-10-15 00:00:002000-08-15 00:00:00
CompetitionDaysOpen255831988853375511
CompetitionMonthsOpen2424241124
Promo2Since1900-01-01 00:00:002011-04-04 00:00:001900-01-01 00:00:001900-01-01 00:00:001900-01-01 00:00:00
Promo2Days01627000
Promo2Weeks025000
AfterSchoolHoliday1337202037
BeforeSchoolHoliday00000
AfterStateHoliday105105115115105
BeforeStateHoliday00000
AfterPromo00000
BeforePromo00000
SchoolHoliday_bw00000
StateHoliday_bw00000
Promo_bw44444
SchoolHoliday_fw00000
StateHoliday_fw00000
Promo_fw11111

92 rows × 5 columns

กำหนด Preprocessing ให้ databunch

In [0]:
procs=[FillMissing, Categorify, Normalize]

กำหนดว่า Column ไหน เป็น Independent Variable แบบ Category หรือ Continuous

In [0]:
cat_vars = ['Store', 'DayOfWeek', 'Year', 'Month', 'Day', 'Dayofweek', 'Dayofyear', 'Is_month_end', 
    'Is_month_start', 'Is_quarter_end', 'Is_quarter_start', 'Is_year_end', 'Is_year_start', 
    'StateHoliday', 'SchoolHoliday', 'CompetitionMonthsOpen', 'Open', 
    'Promo2Weeks', 'Promo2Days', 'StoreType', 'Assortment', 'PromoInterval', 'CompetitionOpenSinceYear', 
    'Promo2SinceYear', 'State', 'Week', 'Events', 'Promo_fw', 'Promo_bw', 'StateHoliday_fw', 'StateHoliday_bw',
    'SchoolHoliday_fw', 'SchoolHoliday_bw', 'Promo', 'AfterPromo', 'BeforePromo']

cont_vars = ['CompetitionDistance', 'Max_TemperatureC', 'Mean_TemperatureC', 'Min_TemperatureC',
   'Max_Humidity', 'Mean_Humidity', 'Min_Humidity', 'Max_Wind_SpeedKm_h', 'Precipitationmm',
   'Mean_Wind_SpeedKm_h', 'CloudCover', 'trend', 'trend_DE', 'AfterSchoolHoliday', 'BeforeSchoolHoliday', 
   'AfterStateHoliday', 'BeforeStateHoliday']

กำหนด Dependent Variable

In [0]:
dep_var = 'Sales'

สร้าง df เตรียมใส่ databunch

In [0]:
df = train_df[cat_vars + cont_vars + [dep_var,'Date']].copy()

หาช่วงวันของ Test Set

In [0]:
test_df['Date'].min(), test_df['Date'].max()
Out[0]:
(Timestamp('2015-08-01 00:00:00'), Timestamp('2015-09-17 00:00:00'))

กำหนดว่าจะแบ่ง Training Set, Validation Set ยังไง

In [0]:
cut = train_df['Date'][(train_df['Date'] == train_df['Date'][len(test_df)])].index.max()
cut
Out[0]:
41395
In [0]:
valid_idx = range(cut)

ลองดู Dependent Variable

In [0]:
df[dep_var].head()
Out[0]:
0     5263
1     6064
2     8314
3    13995
4     4822
Name: Sales, dtype: int64

กำหนด Data Pipeline สร้าง Databunch จากการกำหนดค่าด้านบน

In [0]:
databunch = (TabularList.from_df(df, path=path, cat_names=cat_vars, cont_names=cont_vars, procs=procs,)
                .split_by_idx(valid_idx)
                .label_from_df(cols=dep_var, label_cls=FloatList, log=True)
                .add_test(TabularList.from_df(test_df, path=path, cat_names=cat_vars, cont_names=cont_vars))
                .databunch(bs=32))

สำรวจข้อมูล

เราสามารถรัน cell นี้หลายครั้ง เพื่อเรียกดู batch ต่อ ๆ ไป ได้เรื่อย ๆ เป็นการสำรวจข้อมูล

In [0]:
databunch.show_batch(rows=10)
StoreDayOfWeekYearMonthDayDayofweekDayofyearIs_month_endIs_month_startIs_quarter_endIs_quarter_startIs_year_endIs_year_startStateHolidaySchoolHolidayCompetitionMonthsOpenOpenPromo2WeeksPromo2DaysStoreTypeAssortmentPromoIntervalCompetitionOpenSinceYearPromo2SinceYearStateWeekEventsPromo_fwPromo_bwStateHoliday_fwStateHoliday_bwSchoolHoliday_fwSchoolHoliday_bwPromoAfterPromoBeforePromoCompetitionDistance_naCloudCover_naCompetitionDistanceMax_TemperatureCMean_TemperatureCMin_TemperatureCMax_HumidityMean_HumidityMin_HumidityMax_Wind_SpeedKm_hPrecipitationmmMean_Wind_SpeedKm_hCloudCovertrendtrend_DEAfterSchoolHolidayBeforeSchoolHolidayAfterStateHolidayBeforeStateHolidaytarget
5551201532061FalseFalseFalseFalseFalseFalseFalse013125728daMar,Jun,Sept,Dec20142013BY10Rain5.01.00.00.00.00.0100FalseFalse-0.4986-0.5883-0.5185-0.5592-0.05320.65540.98833.2620-0.30802.53810.2552-0.3185-0.1598-0.6042-0.14940.53080.19918.747034
8442201312311365TrueFalseTrueFalseTrueFalseFalse115125974aaFeb,May,Aug,Nov20122011NW1Rain1.00.01.02.05.05.0011-6FalseFalse-0.4384-0.7085-0.5185-0.2438-1.6279-0.8778-0.38671.5975-0.30801.5318-0.3604-0.1372-0.3759-1.06821.0552-1.04891.15408.122965
351120136100161FalseFalseFalseFalseFalseFalseFalse06119133aaFeb,May,Aug,Nov20122013NW24#na#0.04.00.00.00.00.003-7FalseFalse-0.02060.61440.60920.8602-1.4966-0.6479-0.5904-0.3999-0.30800.1902-0.97610.40660.1645-0.1402-0.7517-0.8593-2.35758.532673
4842013117317FalseFalseFalseFalseFalseFalseFalse08100aa#na#20121900HH3Snow3.01.00.00.00.00.006-4FalseFalse-0.5627-1.7909-1.9282-1.97860.86530.88540.9373-1.0657-0.3080-0.31290.2552-1.0435-1.0245-0.46500.4099-0.7013-1.00228.171034
296420139123255FalseFalseFalseFalseFalseFalseFalse024100aaFeb,May,Aug,Nov20072014HE37Rain2.04.00.00.00.00.0100FalseFalse0.52910.49410.75021.01790.86530.5788-0.33570.04390.5093-0.14520.2552-0.4091-1.56490.1847-0.32152.11040.53798.873888
316220141717FalseFalseFalseFalseFalseFalseFalse124100da#na#20011900NW2Rain4.02.00.01.01.05.0100FalseFalse0.4548-0.10720.18630.5448-0.8405-0.41790.07161.0426-0.30802.0349-0.3604-1.0435-1.1325-1.06821.0552-1.0173-1.92629.452188
916520145164136FalseFalseFalseFalseFalseFalseFalse018100aa#na#20121900NW20Fog4.00.00.00.00.00.007-3FalseFalse-0.68700.49410.1863-0.40150.4717-0.5712-1.0487-0.7328-0.3080-0.6483-1.59170.95040.7049-0.0937-0.0203-0.73290.78448.525757
12420156113162FalseFalseFalseFalseFalseFalseFalse001251900acJan,Apr,Jul,Oct19002010SH24#na#3.01.00.00.00.00.006-4FalseFalse-0.56140.97520.4683-0.24380.0780-1.2612-1.2524-0.7328-0.3080-0.9837-2.82290.40661.78580.1847-0.6226-0.66971.18488.657651
108942013523122FalseFalseFalseFalseFalseFalseFalse024100da#na#20091900NW18Rain2.05.00.01.00.00.0100FalseFalse-0.02960.73460.75020.7025-1.4966-1.1078-0.69220.1549-0.3080-0.1452-0.3604-0.2278-1.02450.18470.2378-1.17520.96929.532424
14642014873219FalseFalseFalseFalseFalseFalseFalse10125619dcJan,Apr,Jul,Oct19612012NW32Rain-Thunderstorm2.05.00.00.05.05.0100FalseFalse0.33301.21571.45511.64880.07800.0421-0.3867-0.7328-0.3080-0.3129-0.36040.76910.7049-1.06821.05520.3412-0.57098.980802

4. สร้างโมเดล

กำหนดค่าช่วง Output ให้เป็น ระหว่าง 0 ถึง เลยค่ามากที่สุดไป 20%

In [0]:
max_log_y = np.log(np.max(train_df['Sales'])*1.2)
y_range = torch.tensor([0, max_log_y], device=defaults.device)

เนื่องจากตารางเล็ก ๆ ไม่ซับซ้อน เราจะสร้างโมเดล ที่ประกอบด้วย Dense Layer ที่ Deep แค่ 2 Hidden Layer โดย ps คือ Dropout ของแต่ละ Layer, emb_drop คือ Dropout สำหรับ Embedding, metrics ที่ใช้คือ exponential root mean squared percentage error (exp_rmspe)

In [0]:
learner = tabular_learner(databunch, layers=[1024, 1024], 
                          ps=[0.02,0.02], emb_drop=0.05, 
                          y_range=y_range, metrics=exp_rmspe, 
                          callback_fns=[ShowGraph])

5. เริ่มต้นเทรนโมเดล

ใช้ lr_finder หา Learning Rate ที่มากที่สุดทีโมเดลรับได้

In [0]:
learner.lr_find(wd=0.2)
learner.recorder.plot(suggestion=True)
LR Finder is complete, type {learner_name}.recorder.plot() to see the graph.
Min numerical gradient: 1.10E-02
Min loss divided by 10: 2.75E-01

เทรน 3 Epoch

In [0]:
learner.fit_one_cycle(3, max_lr=1e-2, wd=0.2)
epochtrain_lossvalid_lossexp_rmspetime
00.0600250.0543020.26632104:54
10.0456810.0414920.22077204:52
20.0178710.0196920.12717904:52

Save โมเดลไว้ก่อน

In [0]:
learner.save("05b-tabular-rossmann-1")
In [0]:
learner.load("05b-tabular-rossmann-1");

เทรนต่อ อีก 4 Epoch โดยลด learning rate ลง

In [0]:
learner.lr_find(wd=0.2)
learner.recorder.plot(suggestion=True)
LR Finder is complete, type {learner_name}.recorder.plot() to see the graph.
Min numerical gradient: 2.29E-06
Min loss divided by 10: 1.10E-07
In [0]:
learner.fit_one_cycle(4, max_lr=3e-4, wd=0.2)
epochtrain_lossvalid_lossexp_rmspetime
00.0178910.0228110.13344804:50
10.0168850.0171130.12404204:51
20.0195790.0146460.11493604:51
30.0119910.0141430.10972204:50

Save ไว้ก่อน

In [0]:
learner.save("05b-tabular-rossmann-2")
In [0]:
learner.load("05b-tabular-rossmann-2");

เทรนต่อ อีก 5 Epoch โดยลด learning rate ลงอีก

In [0]:
learner.lr_find(wd=0.2)
learner.recorder.plot(suggestion=True)
LR Finder is complete, type {learner_name}.recorder.plot() to see the graph.
Min numerical gradient: 1.58E-06
Min loss divided by 10: 2.75E-07
In [0]:
learner.fit_one_cycle(5, max_lr=1e-4, wd=0.2)
epochtrain_lossvalid_lossexp_rmspetime
00.0213630.0135550.11378704:50
10.0144250.0135040.11328904:50
20.0145120.0135810.10846204:49
30.0139790.0167660.11596804:50
40.0126450.0131830.10686604:49

Save ไว้ก่อน

In [0]:
learner.save("05b-tabular-rossmann-3")
In [0]:
learner.load("05b-tabular-rossmann-3");

สำเร็จแล้ว

ใช้เวลาประมาณ 45 นาที เราเทรน Model ได้ exp_rmspe ประมาณ 0.106

6. ดูผลลัพธ์

ลองดูข้อมูล Record ที่ 850

In [0]:
row = df.iloc[850]
row
Out[0]:
Store                                       852
DayOfWeek                                     5
Year                                       2015
Month                                         7
Day                                          31
Dayofweek                                     4
Dayofyear                                   212
Is_month_end                               True
Is_month_start                            False
Is_quarter_end                            False
Is_quarter_start                          False
Is_year_end                               False
Is_year_start                             False
StateHoliday                              False
SchoolHoliday                                 1
CompetitionMonthsOpen                        24
Open                                          1
Promo2Weeks                                  25
Promo2Days                                 1579
StoreType                                     c
Assortment                                    a
PromoInterval                   Jan,Apr,Jul,Oct
CompetitionOpenSinceYear                   2004
Promo2SinceYear                            2011
State                                        HE
Week                                         31
Events                                      Fog
Promo_fw                                      5
Promo_bw                                      5
StateHoliday_fw                               0
StateHoliday_bw                               0
SchoolHoliday_fw                              7
SchoolHoliday_bw                              5
Promo                                         1
AfterPromo                                    0
BeforePromo                                   0
CompetitionDistance                         940
Max_TemperatureC                             23
Mean_TemperatureC                            16
Min_TemperatureC                              8
Max_Humidity                                 98
Mean_Humidity                                54
Min_Humidity                                 18
Max_Wind_SpeedKm_h                           24
Precipitationmm                               0
Mean_Wind_SpeedKm_h                          11
CloudCover                                    1
trend                                        85
trend_DE                                     83
AfterSchoolHoliday                            0
BeforeSchoolHoliday                           0
AfterStateHoliday                            57
BeforeStateHoliday                            0
Sales                                      5722
Date                        2015-07-31 00:00:00
Name: 850, dtype: object

ให้โมเดลทำนายกัน

In [0]:
np.exp(learner.predict(row)[0].data)[0], row.Sales
Out[0]:
(5748.4023, 5722)

โมเดลทำนายได้ใกล้เคียง

ให้ทำนาย Test Set

In [0]:
test_preds=learner.get_preds(DatasetType.Test)
test_df["Sales"]=np.exp(test_preds[0].data).numpy().T[0]
test_df[["Id","Sales"]]=test_df[["Id","Sales"]].astype("int")
test_df[["Id","Sales"]].to_csv("rossmann_submission.csv",index=False)

ส่งประกวดไปยัง Kaggle

In [0]:
# ! kaggle competitions submit rossmann-store-sales -f {'rossmann_submission.csv'} -m "My submission"
100%|████████████████████████████████████████| 434k/434k [00:10<00:00, 40.6kB/s]
Successfully submitted to Rossmann Store Sales

อันดับบน Leaderboard

แล้วเราไปเปิดเว็บ หน้า Private Leader Board เพราะการแข่งขันจบไปแล้ว ดูผลคะแนนของเรา เมื่อทดสอบกับ Test Set

NameSubmittedWait timeExecution timePrivate ScorePublic Score
rossmann_submission.csva few seconds ago0 seconds1 seconds0.112000.10879

เทียบกับคนอื่น จะเห็นว่าอยู่ประมาณ Top 2% อยู่ประมาณอันดับที่ 50 จาก 3302

#Team NameScore
49ssnnAnnss0.11195
50newebug0.11198
51Mr. Lucky0.11203
52Pepan0.11205
53minimalist0.11208

7. สรุป

  1. ข้อมูลแบบ Time Series ไม่จำเป็นต้องใช้โมเดล แบบ RNN เสมอไป เราสามารถทำ Feature Engineering กระจายข้อมูลวันเวลา ออกมาเป็นหลาย ๆ Column จะช่วยให้โมเดลทำงานได้ง่ายขึ้นกว่าวันเวลา Column เดียว
  2. เราสามารถใส่ Domain Knowledge ความสัมพันธ์ระหว่าง Column และ ระหว่าง Row เพิ่มเป็น Column ใหม่เข้าไปได้ โดยที่ต้องเขียนโปรแกรมเพิ่มนิดหน่อย
  3. การใช้ข้อมูลจากหลาย ๆ แหล่งมา join กัน ทำให้ได้ Domain Knowledge ที่หลากหลายมากขึ้น
In [0]:
 

แชร์ให้เพื่อน:

Keng Surapong on FacebookKeng Surapong on GithubKeng Surapong on Linkedin
Keng Surapong
Project Manager at Bua Labs
The ultimate test of your knowledge is your capacity to convey it to another.

Published by Keng Surapong

The ultimate test of your knowledge is your capacity to convey it to another.

Enable Notifications    OK No thanks