Dự đoán doanh số bán của các cửa hàng walmart

- Phạm Duy Tùng
Dữ liệu bạn có là lịch sử bán hàng của 45 cửa hàng Walmart nằm ở các vị trí các nhau. Nhiệm vụ của chúng ta là sẽ dự đoán số bán của mỗi sản phẩm trong 45 cửa hàng trên.

Nghiên cứu dữ liệu

Trong thực tế, Walmart đã chạy các chương trình khuyến mãi trong các ngày lễ lớn trong năm. Có 4 ngày lễ lớn đó là Siêu cúp bóng bầu dục Mỹ (Super Bowl - tổ chức vào chủ nhật đầu tiên của tháng Hai. Đây là một sự kiện thể thao lớn và ngày tổ chức Super Bowl được người Mỹ coi là ngày lễ quốc gia của Hoa Kỳ (theo wiki https://vi.wikipedia.org/wiki/Super_Bowl)), ngày lễ lao động (Labor Day - ngày một tháng 5), lễ tạ ơn (Thanksgiving, ngày lễ tạ ơn ở Mỹ được tổ chức vào ngày thứ Năm lần thứ tư của tháng 11, còn ở Canada ngày lễ tạ ơn được tổ chức vào ngày thứ hai lần thứ hai của tháng 10, theo wiki https://en.wikipedia.org/wiki/Thanksgiving), lễ giáng sinh (Christmas ngày 24 và 25 tháng 12 theo wiki https://en.wikipedia.org/wiki/Christmas ). Những tuần có chứa những ngày lễ lớn này được đánh trọng số gấp 5 lần những tuần khác. Chúng ta phải xây dựng mô hình để mô hình hoá các tác động của việc giảm giá trong các tuần lễ này khi không có dữ liệu lịch sử đầy đủ.

Tập dữ liệu được cung cấp bao gồm:

Tập train: chứa dữ liệu số bán từ 05-02-2010 đến 01-11-2012. Các trường dữ liệu là: store number - mã cửa hàng, Dept number - mã sản phẩm, Date - Tuần, Weekly_Sales - số bán, IsHoliday - Nếu tuần đó có chứa các holidate thì đánh 1 ngược lại đánh 0.

Tập test: Chứa dữ liệu test, có các cột thuộc tính như tập train

Tập features: Chứa thông tin thêm về của hàng, bao gồm store - mã cửa hàng, Date - ngày, Temperature - Nhiệt độ, Fuel_Price - giá dầu (ở mỹ, mỗi khu vực khác nhau sẽ có giá nhiên liệu khác nhau), MarkDown1, MarkDown2,… , MarkDown5 - một chỉ số gì đó mà tác giả không cung cấp định nghĩa cho chúng ta, CPI - chỉ số giá tiêu dùng, Unemployment - tình trạng thất nghiệp, IsHoliday - Tuần có chứa ngày nghỉ.

Phân tích dữ liệu

Mình sẽ import một số thư viện cần thiết

import pandas as pd 
import numpy as np

#Do some statistics
from scipy.misc import imread
from scipy import sparse
import scipy.stats as ss
import math

#Nice graphing tools
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

Đọc các file data lên, merge các file lại với nhau



train = pd.read_csv('data/train.csv')
test = pd.read_csv('data/test.csv')
feature = pd.read_csv('data/features.csv')

train = train.merge(feature, how='left', on=['Store','Date'])
test = test.merge(feature, how='left', on=['Store','Date'])


# Merge in store info
stores = pd.read_csv("data/stores.csv")
train = train.merge(stores, how='left', on='Store')
test = test.merge(stores, how='left', on='Store')
print(train.head())

Kết quả

   Store  Dept        Date  Weekly_Sales  IsHoliday_x  Temperature  Fuel_Price  MarkDown1  MarkDown2  MarkDown3  MarkDown4  MarkDown5         CPI  Unemployment  IsHoliday_y Type    Size  Split
0      1     1  2010-02-05      24924.50        False        42.31       2.572        NaN        NaN        NaN        NaN        NaN  211.096358         8.106        False    A  151315  Train
1      1     1  2010-02-12      46039.49         True        38.51       2.548        NaN        NaN        NaN        NaN        NaN  211.242170         8.106         True    A  151315  Train
2      1     1  2010-02-19      41595.55        False        39.93       2.514        NaN        NaN        NaN        NaN        NaN  211.289143         8.106        False    A  151315  Train
3      1     1  2010-02-26      19403.54        False        46.63       2.561        NaN        NaN        NaN        NaN        NaN  211.319643         8.106        False    A  151315  Train
4      1     1  2010-03-05      21827.90        False        46.50       2.625        NaN        NaN        NaN        NaN        NaN  211.350143         8.106        False    A  151315  Train

Mới có 5 dòng đầu tiên mà thấy các chỉ số markdown Nan rồi.

Chúng ta tiến hành một số phân tích dữ liệu. À, Mình sẽ merge dữ liệu train và test lại rồi phân tích thống kê

df = pd.concat([train,test],axis=0) # Join train and test

print(df.describe())

Kết quả

                 CPI           Dept     Fuel_Price      MarkDown1      MarkDown2      MarkDown3      MarkDown4      MarkDown5          Size          Store    Temperature   Unemployment   Weekly_Sales
count  498472.000000  536634.000000  536634.000000  265596.000000  197685.000000  242326.000000  237143.000000  266496.000000  536634.00000  536634.000000  536634.000000  498472.000000  421570.000000
mean      172.090481      44.277301       3.408310    7438.004144    3509.274827    1857.913525    3371.556866    4324.021158  136678.55096      22.208621      58.771762       7.791888   15981.258123
std        39.542149      30.527358       0.430861    9411.341379    8992.047197   11616.143274    6872.281734   13549.262124   61007.71180      12.790580      18.678716       1.865076   22711.183519
min       126.064000       1.000000       2.472000   -2781.450000    -265.760000    -179.260000       0.220000    -185.170000   34875.00000       1.000000      -7.290000       3.684000   -4988.940000
25%       132.521867      18.000000       3.041000    2114.640000      72.500000       7.220000     336.240000    1570.112500   93638.00000      11.000000      45.250000       6.623000    2079.650000
50%       182.442420      37.000000       3.523000    5126.540000     385.310000      40.760000    1239.040000    2870.910000  140167.00000      22.000000      60.060000       7.795000    7612.030000
75%       213.748126      74.000000       3.744000    9303.850000    2392.390000     174.260000    3397.080000    5012.220000  202505.00000      33.000000      73.230000       8.549000   20205.852500
max       228.976456      99.000000       4.468000  103184.980000  104519.540000  149483.310000   67474.850000  771448.100000  219622.00000      45.000000     101.950000      14.313000  693099.360000

Phân tích một chút:

Bỏ qua cột Dept và Store vì nó là mã sản phẩm và mã của hàng, người ta thích đặt số bao nhiêu thì đặt.

Các chỉ số MarkDown có độ lệch chuẩn khá cao.

Nhiệt độ min là -7.29, max là 101.95, trung bình là 58, nên không thể là độ C được, có thể là độ F

Xem thử hệ số tương quan giữa các column như thế nào

sns.set(style="white")

# Compute the correlation matrix
corr = df.corr()

# Generate a mask for the upper triangle
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

# Set up the matplotlib figure
f, ax = plt.subplots(figsize=(11, 9))

# Generate a custom diverging colormap
cmap = sns.diverging_palette(220, 10, as_cmap=True)

# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0,
            square=True, linewidths=.5, cbar_kws={"shrink": .5})

plt.show()

Hình ảnh Hệ số tương quan giữa các cột trong dữ liệu

Phân tích một chút, chúng ta thấy rằng MarkDown5 hầu như không có liên quan gì đến các column còn lại. Hệ số trải từ -0.3 đến 0.3 chứng tỏ mổi quan hệ giữa các cột là khá lỏng lẻo. Chỉ số giá tiêu dùng tương quan tỷ lệ nghịch với tình trạng thất nghiệp (hợp lý không nhỉ). Kích thước cửa hàng càng bự thì bán càng nhiều (ok hiển nhiên), sản phẩm có mã càng lớn thì bán càng nhiều (? có lẽ là sản phẩm mới, người mỹ thích mua sản phẩm mới chăng). Và một vấn đề quan trọng là giá nhiên liệu, isHoliday, nhiệt độ không có mối tương quan với weekly sales. Chỉ số CPI và tình trạng thất nghiệp cũng ảnh hưởng không lớn với weekly sales.

Thử plot lên hình ảnh về số lượng bán và kích thước cửa hàng xem sao

plt.scatter( df['Size'],df['Weekly_Sales'])
plt.show()

Hình ảnh Tương quan giữa số bán và kích thước cửa hàng

Nhìn vào hình trên, chúng ta thấy rằng cửa hàng có kích thước nhỏ số bán cũng không tăng đột biến khi gặp ngày lễ, cửa hàng kích thước siêu bự có tỷ lệ đột biến thấp, cửa hàng trung trung có đột biến, ở khúc size 125000 và số bán là 700000. Chúng ta hãy xem những ngày có số bán lớn rơi vào ngày nào. Dựa vào bảng desription ở phía trên đã phân tích, trung bình của số bán là 15981 và lệch chuẩn là 22711, cộng lại là 15981 + 22711 = 38692, nhìn trên đô thị thì phần đột biến khá lớn. Max là 700000, min là 0 (cái này nhìn hình, không phải số thực tế ở bảng mô tả), mình sẽ lấy ra những ngày có số bán lớn hơn 350000 (vượt qua ngưỡng trung bình + độ lệch chuẩn rất nhều -> ngoại lệ là đây) xem những ngày đó là ngày gì


print(df.loc[df['Weekly_Sales'] >350000].head(10))

In ra top 10 thằng đầu tiên


               CPI        Date  Dept  Fuel_Price  IsHoliday_x  IsHoliday_y  MarkDown1  MarkDown2  MarkDown3  MarkDown4  MarkDown5    Size  Split  Store  Temperature Type  Unemployment  Weekly_Sales
37201   126.669267  2010-11-26    72       2.752         True         True        NaN        NaN        NaN        NaN        NaN  205863  Train      4        48.08    A         7.127     381072.11
37253   129.836400  2011-11-25    72       3.225         True         True     561.45     137.88   83340.33      44.04    9239.23  205863  Train      4        47.96    A         5.143     385051.04
88428   126.983581  2010-12-24     7       3.236        False        False        NaN        NaN        NaN        NaN        NaN  126512  Train     10        57.06    B         9.003     406988.63
95373   126.669267  2010-11-26    72       3.162         True         True        NaN        NaN        NaN        NaN        NaN  126512  Train     10        55.33    B         9.003     693099.36
95377   126.983581  2010-12-24    72       3.236        False        False        NaN        NaN        NaN        NaN        NaN  126512  Train     10        57.06    B         9.003     404245.03
95425   129.836400  2011-11-25    72       3.760         True         True     174.72     329.00  141630.61      79.00    1009.98  126512  Train     10        60.68    B         7.874     630999.19
115222  126.669267  2010-11-26    72       3.162         True         True        NaN        NaN        NaN        NaN        NaN  112238  Train     12        47.66    B        14.313     359995.60
115274  129.836400  2011-11-25    72       3.622         True         True    5391.83       8.00   63143.29      49.27    2115.67  112238  Train     12        53.25    B        12.890     360140.66
128984  182.544590  2010-12-24     7       3.141        False        False        NaN        NaN        NaN        NaN        NaN  200898  Train     14        30.59    A         8.724     356867.25
135665  182.783277  2010-11-26    72       3.039         True         True        NaN        NaN        NaN        NaN        NaN  200898  Train     14        46.15    A         8.724     474330.10

Nhìn vào bảng trên, chúng ta thấy rằng 10 ngày đầu tiên tập trung chủ yếu ở tháng 11 và tháng 12, tháng 12 là 24-25 tháng 12 -> ngày noel, còn tháng 11 là 25-26 tháng 11 (ngày gì vậy ta, trong mô tả không thấy) Tra lịch thì ngày 25 tháng 11 năm 2011 trúng thứ sáu, tra trên mạng một thông tin khá quan trong là “Black Friday sẽ rơi vào khoảng ngày 23-29 tháng 11” -> không nghi ngờ gì nữa có thể là ngày này đây. Thử tra tiếp ngày 26 tháng 11 năm 2010, cũng là thứ sáu luôn -> ngày black friday và ngày noel có sức mua điên cuồng quá.

Mình dùng một kỹ thuật nhỏ là giảm dần số bán, để ra số bán tối thiểu mà ngày black friday và ngày nodel vẫn còn giữ vị trí thống trị. Kỹ thuật khá đơn giản thôi, từ giá trị 350000, mỗi lần mình sẽ giảm đi 10000, và đếm số lần xuất hiện của các ngày, nếu có ngày nào đó nằm ngoài tuần chứa black friday và nodel thì mình dừng. Sau một hồi tìm kiếm và số bán đã xuất hiện, đó là 290000

print(df.loc[df['Weekly_Sales'] >290000,"Date"].value_counts())
2010-11-26    16
2011-11-25    14
2010-12-24     8
2011-12-23     3
2010-02-05     1

Làm sạch dữ liệu

Xử lý missing values

Một vấn đề khá quan trọng là trong tập dữ liệu này missing value khá nhiều, thử đếm số lượng null trong data cho ta biết được rằng

CPI              38162
Date                 0
Dept                 0
Fuel_Price           0
IsHoliday_x          0
IsHoliday_y          0
MarkDown1       271038
MarkDown2       338949
MarkDown3       294308
MarkDown4       299491
MarkDown5       270138
Size                 0
Split                0
Store                0
Temperature          0
Type                 0
Unemployment     38162
Weekly_Sales    115064

Các giá trị MarkDown bị null khá nhiều, cách đơn giản nhất là set 0 cho tất cả các giá trị null ( Mình lưu log lại những index null của các markdown).

df = df.assign(md1_present = df['MarkDown1']notnull())
df = df.assign(md2_present = df['MarkDown2']notnull())
df = df.assign(md3_present = df['MarkDown3']notnull())
df = df.assign(md4_present = df['MarkDown4']notnull())
df = df.assign(md5_present = df['MarkDown5'].notnull())

df.fillna(0, inplace=True)

Tạo đặc trưng

Đặc trưng holiday

df['IsHoliday'] = 'IsHoliday_' + df['IsHoliday_x'].map(str)
holiday_dummies = pd.get_dummies(df['IsHoliday'])

Đặc trưng ngày tháng

Rút trích tháng

df['DateType'] = [datetime.strptime(date, '%Y-%m-%d').date() for date in df['Date'].astype(str).values.tolist()]
df['Month'] = [date.month for date in df['DateType']]
df['Month'] = 'Month_' + df['Month'].map(str)
Month_dummies = pd.get_dummies(df['Month'] )

Rút trích ngày trước giáng sinh và black friday

df['Black_Friday'] = np.where((df['DateType']==datetime(2010, 11, 26).date()) | (df['DateType']==datetime(2011, 11, 25).date()), 'yes', 'no')
df['Pre_christmas'] = np.where((df['DateType']==datetime(2010, 12, 23).date()) | (df['DateType']==datetime(2010, 12, 24).date()) | (df['DateType']==datetime(2011, 12, 23).date()) | (df['DateType']==datetime(2011, 12, 24).date()), 'yes', 'no')
df['Black_Friday'] = 'Black_Friday_' + df['Black_Friday'].map(str)
df['Pre_christmas'] = 'Pre_christmas_' + df['Pre_christmas'].map(str)
Black_Friday_dummies = pd.get_dummies(df['Black_Friday'] )
Pre_christmas_dummies = pd.get_dummies(df['Pre_christmas'] )

Thêm các đặc trưng vào trong dữ liệu


df = pd.concat([df,holiday_dummies,Pre_christmas_dummies,Black_Friday_dummies],axis=1)

Thêm đặc trưng trung vị của từng loại cửa hàng vào từng tháng, do một số của hàng sẽ bị NA ở cột số bán ở một thời điểm nào đó, nên chúng ta replace số bán là 0 có vẻ không hợp lý lắm. Mình chọn cách là thay thế bằng trung bình của số bán trong tháng của cửa hàng cùng loại. Nhưng trước tiên thì tính trung bình số bán của từng loại cửa hàng cái đã.


medians = pd.DataFrame({'Median Sales' :df.loc[df['Split']=='Train'].groupby(by=['Type','Dept','Store','Month','IsHoliday'])['Weekly_Sales'].median()}).reset_index()
print(medians.head())

Kết quả

     Type    Dept    Store     Month        IsHoliday  Median Sales
0  Type_A  Dept_1  Store_1   Month_1  IsHoliday_False     17350.585
1  Type_A  Dept_1  Store_1  Month_10  IsHoliday_False     23388.030
2  Type_A  Dept_1  Store_1  Month_11  IsHoliday_False     19551.115
3  Type_A  Dept_1  Store_1  Month_11   IsHoliday_True     19865.770
4  Type_A  Dept_1  Store_1  Month_12  IsHoliday_False     39109.390

thêm dữ liệu vào trong data chính, loại bỏ NA và tạo key cho mỗi dòng để dễ dàng truy xuất

df = df.merge(medians, how = 'outer', on = ['Type','Dept','Store','Month','IsHoliday'])

# Fill NA
df['Median Sales'].fillna(df['Median Sales'].loc[df['Split']=='Train'].median(), inplace=True) 

# Create a key for easy access

df['Key'] = df['Type'].map(str)+df['Dept'].map(str)+df['Store'].map(str)+df['Date'].map(str)+df['IsHoliday'].map(str)

Chúng ta sẽ dự đoán số bán của tuần kế tiếp dựa vào kết quả số bán của tuần hiện tại, nên trong dữ liệu sẽ lưu trên ngày của tuần trước đó để dễ truy xuất. Vì 1 tuần có 7 ngày, chúng ta sẽ lưu giá trị là ngày ở cột hiện tại - 7

df['DateLagged'] = df['DateType']- timedelta(days=7)

Và giờ đây, chúng ta sẽ lặp qua toàn bộ các dòng trên tập dữ liệu, kiểm tra xem có dòng nào số bán nan hông, nếu có thì sẽ thay bằng trung bình đã tính ở trên. Ở đây mình tạo một sorted dataset để truy xuất cho nhanh


#Make a sorted dataframe. This will allow us to find lagged variables much faster!
sorted_df = df.sort_values(['Store', 'Dept','DateType'], ascending=[1, 1,1])
sorted_df = sorted_df.reset_index(drop=True) # Reinitialize the row indices for the loop to work

sorted_df['LaggedSales'] = np.nan # Initialize column
sorted_df['LaggedAvailable'] = np.nan # Initialize column
last=df.loc[0] # intialize last row for first iteration. Doesn't really matter what it is
row_len = sorted_df.shape[0]
for index, row in sorted_df.iterrows():
    lag_date = row["DateLagged"]
    # Check if it matches by comparing last weeks value to the compared date 
    # And if weekly sales aren't 0
    if((last['DateType']== lag_date) & (last['Weekly_Sales']>0)): 
        sorted_df.set_value(index, 'LaggedSales',last['Weekly_Sales'])
        sorted_df.set_value(index, 'LaggedAvailable',1)
    else:
        sorted_df.set_value(index, 'LaggedSales',row['Median Sales']) # Fill with median
        sorted_df.set_value(index, 'LaggedAvailable',0)

    last = row #Remember last row for speed
    if(index%int(row_len/10)==0): #See progress by printing every 10% interval
        print(str(int(index*100/row_len))+'% loaded')

print(sorted_df[['Dept', 'Store','DateType','LaggedSales','Weekly_Sales','Median Sales']].head())
9% loaded
19% loaded
29% loaded
39% loaded
49% loaded
59% loaded
69% loaded
79% loaded
89% loaded
99% loaded
     Dept    Store    DateType  LaggedSales  Weekly_Sales  Median Sales
0  Dept_1  Store_1  2010-02-05     23510.49      24924.50      23510.49
1  Dept_1  Store_1  2010-02-12     24924.50      46039.49      37887.17
2  Dept_1  Store_1  2010-02-19     46039.49      41595.55      23510.49
3  Dept_1  Store_1  2010-02-26     41595.55      19403.54      23510.49
4  Dept_1  Store_1  2010-03-05     19403.54      21827.90      21280.40

Công việc đơn giản tiếp theo là merge dữ liệu vào data chính và tính độ lệch giữa 2 tuần bán

# Merge by store and department
df = df.merge(sorted_df[['Dept', 'Store','DateType','LaggedSales','LaggedAvailable']], how = 'inner', on = ['Dept', 'Store','DateType'])
df['Sales_dif'] = df['Median Sales'] - df['LaggedSales']

Và bây giờ , thay vì ta ước lượng weekly sales, chúng ta sẽ ước lượng độ lệch giữa week sales và median sales (đây là một cách trong những cách để tính điểm dừng của dữ liệu time series)

df['Difference'] = df['Median Sales'] - df['Weekly_Sales']

Huấn luyện mô hình

Lựa chọn các đặc trưng huấn luyện

selector = [
    #'Month',
    'CPI',
    'Fuel_Price',
    'MarkDown1',
    'MarkDown2',
    'MarkDown3',
    'MarkDown4',
    'MarkDown5',
    'Size',
    'Temperature',
    'Unemployment',
    
    
    
    'md1_present',
    'md2_present',
    'md3_present',
    'md4_present',
    'md5_present',

    'IsHoliday_False',
    'IsHoliday_True',
    'Pre_christmas_no',
    'Pre_christmas_yes',
    'Black_Friday_no',
    'Black_Friday_yes',    
    'LaggedSales',
    'Sales_dif',
    'LaggedAvailable'
    ]

Tách dữ liệu train và test riêng ra


train = df.loc[df['Split']=='Train']
test = df.loc[df['Split']=='Test']

Lấy ngẫu nhiên 20% dữ liệu ở tập train để validation

# Set seed for reproducability 
np.random.seed(42)
X_train, X_val, y_train, y_val = train_test_split(train[selector], train['Difference'], test_size=0.2, random_state=42)

Huấn luyện bằng neural network sử dụng lstm


adam_regularized = Sequential()

    # First hidden layer now regularized
    model.add(Dense(32,activation='relu',
                    input_dim=X_train.shape[1],
                    kernel_regularizer = regularizers.l2(0.01)))

    # Second hidden layer now regularized
    adam_regularized.add(Dense(16,activation='relu',
                       kernel_regularizer = regularizers.l2(0.01)))

    # Output layer stayed sigmoid
    adam_regularized.add(Dense(1,activation='linear'))

    # Setup adam optimizer
    adam_optimizer=keras.optimizers.Adam(lr=0.01,
                    beta_1=0.9, 
                    beta_2=0.999, 
                    epsilon=1e-08)

    # Compile the model
    adam_regularized.compile(optimizer=adam_optimizer,
                  loss='mean_absolute_error',
                  metrics=['acc'])

    # Train
    history=adam_regularized.fit(X_train, y_train, # Train on training set
                                 epochs=10, # We will train over 1,000 epochs
                                 batch_size=2048, # Batch size 
                                 verbose=0) # Suppress Keras output
    print('eval',model.evaluate(x=X_val,y=y_val))

    # Plot network
    plt.plot(history.history['loss'], label='Adam Regularized')
    plt.xlabel('Epochs')
    plt.ylabel('loss')
    plt.legend()
    plt.show()

eval:  [1457.0501796214685, 0.002312783168124545]

Hình ảnh Độ lỗi trên tập train

Độ lỗi trên tập train giảm xuống đến gần 1450 thì đừng hẳn, không thể giảm được nữa

Giá trị độ lệch trên tập evaluation là 1457.0501796214685

Thử huấn luyện bằng random forest

regr = RandomForestRegressor(n_estimators=20, criterion='mse', max_depth=None, 
                        min_samples_split=2, min_samples_leaf=1, 
                        min_weight_fraction_leaf=0.0, max_features='auto', 
                        max_leaf_nodes=None, min_impurity_decrease=0.0, 
                        min_impurity_split=None, bootstrap=True, 
                        oob_score=False, n_jobs=1, random_state=None, 
                        verbose=2, warm_start=False)

    #Train on data
    regr.fit(X_train, y_train.ravel())
    y_pred_random = regr.predict(X_val)

    y_val = y_val.to_frame()

    # Transform forest predictions to observe direction of change
    direction_true1= y_val.values
    direction_predict = y_pred_random

    y_val['Predicted'] = y_pred_random
    df_out = pd.merge(train,y_val[['Predicted']],how = 'left',left_index = True, right_index = True,suffixes=['_True','_Pred'])
    df_out = df_out[~pd.isnull(df_out['Predicted'])]

    df_out['prediction'] = df_out['Median Sales']-df_out['Predicted']

    print("Medians: "+str(sum(abs(df_out['Difference']))/df_out.shape[0]))
    print("Random Forest: "+str(sum(abs(df_out['Weekly_Sales']-df_out['prediction']))/df_out.shape[0])) 

Kết quả


9% loaded
19% loaded
29% loaded
39% loaded
49% loaded
59% loaded
69% loaded
79% loaded
89% loaded
99% loaded
     Dept    Store    DateType  LaggedSales  Weekly_Sales  Median Sales
0  Dept_1  Store_1  2010-02-05     23510.49      24924.50      23510.49
1  Dept_1  Store_1  2010-02-12     24924.50      46039.49      37887.17
2  Dept_1  Store_1  2010-02-19     46039.49      41595.55      23510.49
3  Dept_1  Store_1  2010-02-26     41595.55      19403.54      23510.49
4  Dept_1  Store_1  2010-03-05     19403.54      21827.90      21280.40
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
building tree 1 of 20
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    6.5s remaining:    0.0s
building tree 2 of 20
building tree 3 of 20
building tree 4 of 20
building tree 5 of 20
building tree 6 of 20
building tree 7 of 20
building tree 8 of 20
building tree 9 of 20
building tree 10 of 20
building tree 11 of 20
building tree 12 of 20
building tree 13 of 20
building tree 14 of 20
building tree 15 of 20
building tree 16 of 20
building tree 17 of 20
building tree 18 of 20
building tree 19 of 20
building tree 20 of 20
[Parallel(n_jobs=1)]: Done  20 out of  20 | elapsed:  2.2min finished
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=1)]: Done  20 out of  20 | elapsed:    1.1s finished
Medians: 1545.7406070759525
Random Forest: 1356.4670052620745

Trung bình lệch của random forest là 1356, giá trị này nhỏ hơn so với giá trị output của lstm trả về.

Thử huấn luyện bằng XGBoost


param_dist = { 'max_depth':5}

    model = XGBRegressor(**param_dist)

    #Train on data
    model.fit(X_train, y_train.ravel())
    y_pred_random = model.predict(X_val)

    y_val = y_val.to_frame()

    # Transform forest predictions to observe direction of change
    direction_true1= y_val.values
    direction_predict = y_pred_random

    y_val['Predicted'] = y_pred_random
    df_out = pd.merge(train,y_val[['Predicted']],how = 'left',left_index = True, right_index = True,suffixes=['_True','_Pred'])
    df_out = df_out[~pd.isnull(df_out['Predicted'])]

    df_out['prediction'] = df_out['Median Sales']-df_out['Predicted']

    print("Medians: "+str(sum(abs(df_out['Difference']))/df_out.shape[0]))
    print("XGB Regressor: "+str(sum(abs(df_out['Weekly_Sales']-df_out['prediction']))/df_out.shape[0])) 

Kết quả


Medians: 1545.7406070759525
XGB Regressor: 1354.1976755192593

Kết quả cũng gần như bằng Random forest :).

Giờ mình sẽ dùng random forest để tạo file submission



rf_model = RandomForestRegressor(n_estimators=80, criterion='mse', max_depth=None, 
                      min_samples_split=2, min_samples_leaf=1, 
                      min_weight_fraction_leaf=0.0, max_features='auto', 
                      max_leaf_nodes=None, min_impurity_decrease=0.0, 
                      min_impurity_split=None, bootstrap=True, 
                      oob_score=False, n_jobs=1, random_state=None, 
                      verbose=0, warm_start=False)

#Train on data
rf_model.fit(train[selector], train['Difference'])
final_y_prediction = rf_model.predict(test[selector])

testfile = pd.concat([test.reset_index(drop=True), pd.DataFrame(final_y_prediction)], axis=1)
testfile['prediction'] = testfile['Median Sales']-testfile[0]

submission = pd.DataFrame({'id':pd.Series([''.join(list(filter(str.isdigit, x))) for x in testfile['Store']]).map(str) + '_' +
                           pd.Series([''.join(list(filter(str.isdigit, x))) for x in testfile['Dept']]).map(str)  + '_' +
                           testfile['Date'].map(str),
                          'Weekly_Sales':testfile['prediction']})

submission.to_csv('submission.csv',index=False)

Sau khi submit mô hình, mình đạt được kết quả là 4455.96312 trên private board, và 4419.17292 trên publish board. Đây là một kết quả khá tệ (đứng hạng khoảng top 300). Sau khi mình nhìn lại mô hình thì phát hiện một số vấn đề.

Các đặc trưng trong file features.csv nó không có mối tương quan gì hết với số bán như phân tích ở trên -> mình mạnh dạng bỏ luôn file features.csv, không quan tâm đến nó nữa, tập trung vào file chính.

Bỏ mấy cái lag luôn, thử forecast chính vào cái số bán luôn xem sao

Với cửa hàng nào thì xây dựng mô hình cho cửa hàng và sản phẩm đó, không xây dựng một mô hình tổng quát áp dụng cho toàn cửa hàng. với những cửa hàng không có trong tập train hoặc những sản phẩm mà cửa hàng đó chưa bán trước đây (nói chung là không có trong tập train) thì mới áp dụng mô hình của toàn cửa hàng cho nó.

Kết quả là mình đạt được 2736 trên private board và 2657.40087 trên publish board (top 30), kết quả trên vẫn làm cho mình chưa hài lòng lắm.

Cảm ơn các bạn đã theo dõi. Hẹn gặp bạn ở các bài viết tiếp theo.


Bài viết khác
comments powered by Disqus