기계학습 수업의 기말과제는 흥미로운 주제를 선택해서 기계학습 모델링을 해보는 것이다.
내가 실습하기에 흥미로운 주제를 찾다가 2014년 월마트에서 Kaggle Competition으로 제공한 월마트 데이터셋을 바탕으로 주간 매출액을 예측하는 회귀(Regression)분석을 하기로 결정했다.
기본 데이터셋에 제공된 매장 크기, 실업률, 기온을 바탕으로 실제 매장을 찾아낸 후 주(State)별 평균 주급, State GDP Feature를 추가해 분석에 사용하였다.
(1) Walmart Recruiting - Store Sales Forecasting
(https://www.kaggle.com/competitions/walmart-recruiting-store-sales-forecasting/overview/)
(2) State Economic Monitor
(https://apps.urban.org/features/state-economic-monitor/)
# 구글 코랩으로 마운트 from google.colab import drive drive.mount('/content/drive')
# required libraries import numpy as np np.random.seed(42) import os import urllib.request import pandas as pd import matplotlib.pyplot as plt from matplotlib import pyplot import seaborn as sns import joblib from google.colab import files # machine learning libraries from sklearn.linear_model import SGDRegressor from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import cross_val_score from sklearn.model_selection import RandomizedSearchCV from sklearn.linear_model import Ridge, Lasso from sklearn.metrics import r2_score from scipy.stats import randint # preprocessing from sklearn.model_selection import train_test_split from sklearn.model_selection import StratifiedShuffleSplit # date from datetime import date import holidays import datetime
# 데이터 불러오기 data_path = '/content/drive/MyDrive/Walmart_Store_sales.csv' # 날짜는 날짜형식으로 지정해준다. all_data = pd.read_csv(data_path, parse_dates=["Date"]) all_data_size = len(all_data) # 날짜별 분리 all_data['Year'] = all_data['Date'].dt.year all_data['Month'] = all_data['Date'].dt.month all_data['Week'] = all_data['Date'].dt.week
# 이상치 제거 def drop_outliers(df, col_name): print('Dropping outliers in ', col_name ,'...') to_keep = (df[col_name].isnull()) | ((df[col_name] < df[col_name].mean() + 3 * df[col_name].std()) & (df[col_name] > df[col_name].mean() - 3 * df[col_name].std())) df = df.loc[to_keep,:] print('Done. Number of lines remaining : ', df.shape[0]) return df for c in ['Temperature', 'Fuel_Price', 'CPI', 'Unemployment', 'WeekIncome', 'StateGDP']: all_data = drop_outliers(all_data, c) all_data.head()
City, State, Date, Type, Year, Month, Week 변수는 Feature로서의 의미가 없다고 판단되거나 파생 feature를 만다는데 사용한 후 삭제된 컬럼임.
데이터셋을 확정하고 numerical feature에 대한 histogram을 만들어 시각적으로 다시 한번 확인함.
타겟값인 weekly_sales와 각 변수들을 산점도로 확인해 봤는데,
산점도로 봤을 때는 가운데 size(매장크기), month에서만 우상향하는 모습을 확인했고 나머지 attribute들은 방향을 확인하기는 어려웠다.
Fuel_price(유가)를 제외한 나머지 컬럼이 모두 5% 미만으로 통계적으로 유의하다는 결론을 내릴 수 있었다.
매출액과 다양한 변수들의 관계를 차트로 확인해봤는데,
# 날짜별 매출 확인 plt.figure(figsize =(10, 6)) sns.lineplot(x=all_data['Date'], y= all_data['Weekly_Sales'], palette='Paired') plt.title('Average Sales per Date', fontsize=15) plt.show()
# 사이즈 별 매출 확인 plt.figure(figsize =(10, 6)) sns.lineplot(x=all_data['Size'], y= all_data['Weekly_Sales'], palette='Paired') plt.title('Average Sales per Size', fontsize=15) plt.show()
# 스토어 별 매출 확인 store_sales = all_data['Weekly_Sales'].groupby(all_data['Store']).mean() size_sales = all_data['Weekly_Sales'].groupby(all_data['Size']).mean() plt.figure(figsize=(10, 6)) sns.barplot(store_sales.index, store_sales.values, palette="Paired") plt.grid() plt.title('Average Sales per Store', fontsize=15) plt.show()
각 속성들의 상관관계를 Correlation Matrix로도 확인해 봤는데
타겟값인 weekly_sales와는 Size과 정비례, 나머지는 수치가 그리 높진 않지만 주급 & 실업률 & StateGDP와 반비례하는 것을 볼 수 있다.
# 상관관계 확인 s = pd.DataFrame(corr) s = s['Weekly_Sales'].sort_values(ascending=False) s.columns = ['feature', 'corr']
# Correlation Matrix 만들기 plt.figure(figsize=(15, 10)) plt.title('Correlation Matrix', fontsize = 15) mask = np.zeros_like(corr, dtype=np.bool) mask[np.triu_indices_from(mask)] = True sns.heatmap(corr, cmap = sns.diverging_palette(220, 10, as_cmap=True), annot=True, mask=mask) plt.show()
X = dataset[[c for c in dataset.columns if c not in ['Weekly_Sales']]] y = dataset['Weekly_Sales']
np.set_printoptions(formatter={'float_kind': lambda x: "{0:0.2f}".format(x)}) X = X.values y = y.to_numpy() print(X[0,:]) print() print(y[0:5])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 42) print("X_train: ", X_train.shape), print("y_train: ", y_train.shape) print("X_test: ", X_test.shape), print("y_test: ", y_test.shape)
scaler = StandardScaler() scaler.fit(X_train) X_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test) print(X_scaled[:1]) print("----------------") print(X_test_scaled[:1])
예측한 값과 실제 환경에서 관찰되는 값의 차이를 나타낸다.
MSE보다 큰 오류값 차이에 대해 크게 패널티를 주는 이점이 있다.
lin_reg = LinearRegression() lin_reg.fit(X_scaled, y_train) some_data = X_scaled[:5] some_labels = y_train[:5] print("Predictions:", lin_reg.predict(some_data)) print("Labels:", list(some_labels))
나는 7개의 모델로 학습하였고
학습결과를 결정계수인 R2값으로 성능을 평가하였다.
결론을 미리 말하면 Random Forest에서 가장 높은 성능을 보임을 확인하였다.
Decision Tree는 Overfitting이라고 판단되어 선정에서 제외했다.
# 1. Decision Tree tree_reg = DecisionTreeRegressor(random_state=42) tree_reg.fit(X_scaled, y_train) # tree_predictions = tree_reg.predict(X_scaled) tree_mse = mean_squared_error(y_train, tree_predictions) tree_rmse = np.sqrt(tree_mse) # from sklearn.model_selection import cross_val_score scores = cross_val_score(tree_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) tree_rmse_scores = np.sqrt(-scores) # def display_scores(scores): print("Scores:", scores) print("Mean:", scores.mean()) print("Standard deviation:", scores.std()) # print("<<<Decision Tree>>>") display_scores(tree_rmse_scores) print("tree_rmse:", tree_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, tree_predictions))
# 2. Linear Regression lin_reg = LinearRegression() lin_reg.fit(X_scaled, y_train) # #training lin_predictions = lin_reg.predict(X_scaled) lin_mse = mean_squared_error(y_train, lin_predictions) lin_rmse = np.sqrt(lin_mse) # #test lin_test_predictions = lin_reg.predict(X_test_scaled) lin_test_mse = mean_squared_error(y_test, lin_test_predictions) lin_test_rmse = np.sqrt(lin_test_mse) # scores = cross_val_score(lin_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) lin_rmse_scores = np.sqrt(-scores) # print("<<<Linear Regression>>>") display_scores(lin_rmse_scores) print("lin_rmse:", lin_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, lin_predictions)) print("R2 score on test set : ", r2_score(y_test, lin_test_predictions))
# 3. Random Forest forest_reg = RandomForestRegressor(n_estimators=100, random_state=42) forest_reg.fit(X_scaled, y_train) # forest_predictions = forest_reg.predict(X_scaled) forest_mse = mean_squared_error(y_train, forest_predictions) forest_rmse = np.sqrt(forest_mse) # #test forest_test_predictions = forest_reg.predict(X_test_scaled) forest_test_mse = mean_squared_error(y_test, forest_test_predictions) forest_test_rmse = np.sqrt(forest_test_mse) # forest_scores = cross_val_score(forest_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) forest_rmse_scores = np.sqrt(-forest_scores) # print("<<<Random Forest>>>") display_scores(forest_rmse_scores) print("forest_rmse:", forest_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, forest_predictions)) print("R2 score on test set : ", r2_score(y_test, forest_test_predictions))
# 4. Ridge ridge_reg = Ridge(alpha=0.1, solver="cholesky") ridge_reg.fit(X_scaled, y_train) # ridge_predictions = ridge_reg.predict(X_scaled) ridge_mse = mean_squared_error(y_train, ridge_predictions) ridge_rmse = np.sqrt(ridge_mse) # #test ridge_test_predictions = ridge_reg.predict(X_test_scaled) ridge_test_mse = mean_squared_error(y_test, ridge_test_predictions) ridge_test_rmse = np.sqrt(ridge_test_mse) # ridge_scores = cross_val_score(ridge_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) ridge_rmse_scores = np.sqrt(-ridge_scores) # print("<<<Ridge>>>") display_scores(ridge_rmse_scores) print("ridge_rmse:", ridge_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, ridge_predictions)) print("R2 score on test set : ", r2_score(y_test, ridge_test_predictions))
# 5.Lasso lasso_reg = Lasso(alpha=0.1) lasso_reg.fit(X_scaled, y_train) # lasso_predictions = lasso_reg.predict(X_scaled) lasso_mse = mean_squared_error(y_train, lasso_predictions) lasso_rmse = np.sqrt(lasso_mse) # #test lasso_test_predictions = lasso_reg.predict(X_test_scaled) lasso_test_mse = mean_squared_error(y_test, lasso_test_predictions) lasso_test_rmse = np.sqrt(lasso_test_mse) # lasso_scores = cross_val_score(lasso_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) lasso_rmse_scores = np.sqrt(-lasso_scores) # print("<<<Lasso>>>") display_scores(lasso_rmse_scores) print("lasso_rmse:", lasso_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, lasso_predictions)) print("R2 score on test set : ", r2_score(y_test, lasso_test_predictions))
# 6.SGDRegressor sgd_reg = SGDRegressor(penalty="l2") #sgd_reg = SGDRegressor(max_iter=1000, tol=0.001, penalty="l2", eta0=0.1) sgd_reg.fit(X_scaled, y_train) # sgd_predictions = sgd_reg.predict(X_scaled) sgd_mse = mean_squared_error(y_train, sgd_predictions) sgd_rmse = np.sqrt(sgd_mse) # #test sgd_test_predictions = sgd_reg.predict(X_test_scaled) sgd_test_mse = mean_squared_error(y_test, sgd_test_predictions) sgd_test_rmse = np.sqrt(sgd_test_mse) # sgd_scores = cross_val_score(sgd_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) sgd_rmse_scores = np.sqrt(-sgd_scores) # print("<<<SGDRegressor>>>") display_scores(sgd_rmse_scores) print("sgd_rmse:", sgd_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, sgd_predictions)) print("R2 score on test set : ", r2_score(y_test, sgd_test_predictions))
# 7. Elastic Net from sklearn.linear_model import ElasticNet # elastic_reg = ElasticNet(alpha=0.1, l1_ratio=0.5) elastic_reg.fit(X_scaled, y_train) # elastic_predictions = elastic_reg.predict(X_scaled) elastic_mse = mean_squared_error(y_train, elastic_predictions) elastic_rmse = np.sqrt(elastic_mse) # #test elastic_test_predictions = elastic_reg.predict(X_test_scaled) elastic_test_mse = mean_squared_error(y_test, elastic_test_predictions) elastic_test_rmse = np.sqrt(elastic_test_mse) # elastic_scores = cross_val_score(elastic_reg, X_scaled, y_train, scoring="neg_mean_squared_error", cv=10) elastic_rmse_scores = np.sqrt(-elastic_scores) # print("<<<Elastic Net>>>") display_scores(elastic_rmse_scores) print("elastic_rmse:", elastic_rmse) print("-----------------------------------------------") print("R2 score on training set : ", r2_score(y_train, elastic_predictions)) print("R2 score on test set : ", r2_score(y_test, elastic_test_predictions))
학습했던 7개의 모델 중 Random Forest를 최종 선정한 후 해당 모델을 피클파일에 저장해 테스트 데이터에 예측해 보았다.
reg = RandomForestRegressor(n_estimators=100, random_state=42).fit(X_scaled, y_train) # # 피클 파일로 저장 ```python joblib.dump(reg, 'reg.pkl', compress=1)
# Evaluation loaded_model = joblib.load('./reg.pkl') # score = loaded_model.score(X_test_scaled, y_test) print('test score: {score:.3f}'.format(score=score))
test score: 0.972
테스트 데이터로 평가한 결과 0.972로 역시나 높은 성능을 보였다!
스코어가 지나치게 높게 나왔다는 의심(?)을 지울 수 없지만 train, test에서 둘 다 비슷하게 높은 점수를 받아 따로 튜닝을 하지는 않았다.
위에서 만든 Random Forest 모델을 사용하여 test set에 있던 1,215개의 매장 주간 매출액을 예측해 보았다.
정상적으로 예측됨을 확인!
df = pd.DataFrame(X_test, columns = ['Store', 'Holiday_Flag', 'Temperature', 'Fuel_Price', 'CPI', 'Unemployment', 'Size', 'WeekIncome', 'StateGDP', 'Month', 'Week']) # predictions = reg.predict(X_test_scaled) df['Weekly_Sales'] = predictions # # 소수점 2자리로 매출액 표시 pd.options.display.float_format = '{:.2f}'.format df
Regression 분석을 처음부터 끝까지 해본건 처음이라
생각보다 시간이 많이 소요되었다. (퇴근하고 평일 5일 저녁 내내 붙잡고 있었던 듯 하다.)
따로 튜닝을 할 필요없이 한번에 너무 좋은 결과가 나와서 이래도 되나 싶긴한데...
Kaggle에 기본 Dataset으로 회귀분석한 다른 사례를 보니 최고 점수(R2)가 92%인걸 보면
(https://www.kaggle.com/code/pierrelouisdanieau/walmart-sales-forecasting)
내가 고생해서 찾아 넣은 StateGDP와 WeekIncome이 학습에 큰 영향을 준 듯 하다.
A-Z까지 전체 다 해봤으니 다음 분석은 좀 수월하지 않을까?
실제 매장 위치를 어떻게 찾아내셨는지 구체적으로 알 수 있을까요 ??