[파이썬 실습] 정규화 모델 실습(1)
목표 : 정규화 모델 구축
- 1. 데이터 전처리
- Ridge, Lasso, Elasticnet regression 구축 (hyperparameter 탐색)
- 예측 결과 평가 및 변수 중요도 해석
1. 모듈 불러오기
from IPython.display import display, HTML
import warnings
warnings.filterwarnings('ignore')
# 데이터 전처리
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Lasso, Ridge, ElasticNet, LassoCV, RidgeCV, ElasticNetCV
from sklearn.model_selection import KFold, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.feature_extraction import DictVectorizer
from sklearn import metrics
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# 시각화
import seaborn as sns
import matplotlib.pyplot as plt
# 한글 폰트 설정
plt.rc('font', family = 'Malgun Gothic')
2. 데이터 불러오기 : HousePrices
데이터 구조
- 데이터 : 집 가격 예측 데이터
- 관측치 개수 : 1460개
- 변수 개수 : 80개 (집의 특성, 집 가격)
data = pd.read_csv('train1.csv', index_col = 'Id')
data.head()
데이터를 확인하면 80개의 변수로 구성이 되어있고 정수형, 범주형, 실수형, NaN 등 다양한 값들이 존재한다.
3. 탐색적 데이터 분석 및 데이터 전처리
데이터 shape 확인
print(f'관측치 수 : {data.shape[0]} \n변수 수 : {data.shape[1]}')
결측치, 데이터 타입 확인
data.info()
data.describe()
데이터 타입 변환 (int -> str(object))
수치형으로 데이터가 구성이 되어있지만 일부는 범주형이어야 되는 경우가 있다. 의미를 잘 파악해서 타입을 변환한다.
- 아래 3가지 변수는 등급을 의미하는 변수이기 때문에 실제로 표기되는 값은 int형이지만 의미적으론 숫자 간의 관계가 독립적이기 때문에 범주형으로 바꾸는 것이 적합함
for categ in ['MSSubClass', 'OverallQual', 'OverallCond']:
data[categ] = data[categ].astype(str)
target y 값 분포 확인
Train / Test 데이터 분리
X = data.drop('SalePrice', axis = 1)
y = data['SalePrice']
display(X.head(3))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 2021)
이제 전처리를 시작한다.
Null 확인 및 대체
- Null, NaN, NA, '정해지지 않은 값' 개수 확인
NaNinfo = X_train.isnull().sum()
NaNinfo = NaNinfo.sort_values(ascending = False)
NaNinfo = NaNinfo[NaNinfo > 0]
NaNinfo
이 각각의 변수들이 어떤 값으로 구성되어 있는지 확인을 한다.
for col in NaNinfo.index:
print(f'{col} : {data[col].unique().tolist()[:10]}')
위의 값을 보면 PoolQC는 nan, Ex, Fa, Gd로 구성이 되어있고, 각각 변수마다 결측치들이 존재하는 것을 알 수 있다.
y값에 결측치가 있다면 검증이 불가능하기 때문에 제거를 해야 한다.
- Target : SalePrice에 결측값이 있다면 해당 관측치는 제거
- Numerical : 평균값으로 대체(다양한 방법이 있을 수 있으나)
- Categorical : 'NaN'으로 대체
for feature in ['LotFrontage', 'LotArea', 'MasVnrArea',
'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF',
'1stFlrSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea', 'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath',
'BedroomAbvGr', 'KitchenAbvGr', 'TotRmsAbvGrd', 'Fireplaces', 'GarageYrBlt',
'GarageCars', 'GarageArea', 'WoodDeckSF', 'OpenPorchSF', 'EnclosedPorch', '3SsnPorch',
'ScreenPorch', 'PoolArea', 'MiscVal']:
X_train[feature] = X_train[feature].fillna(X_train[feature].mean())
X_test[feature] = X_test[feature].fillna(X_train[feature].mean())
for feature in ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu',
'GarageCond', 'GarageType', 'GarageFinish', 'GarageQual',
'BsmtExposure', 'BsmtFinType2', 'BsmtQual', 'BsmtCond', 'BsmtFinType1',
'Electrical', 'MSZoning', 'Functional', 'Utilities', 'KitchenQual', 'SaleType',
'Exterior1st', 'Exterior2nd', 'MasVnrType']:
X_train[feature] = X_train[feature].fillna('NaN')
X_test[feature] = X_test[feature].fillna('NaN')
category = list(X_train.select_dtypes(include = [object]))
category[:10]
위의 코드를 보면 범주형을 처리하기 위해 범주형을 모아서 카테고리로 리스트를 만들었다.
카테고리 변수는 숫자형이 아니기 때문에 인코딩 작업이 필요하다. 수치형 변수는 최대 최소 스케일링, 정규화 스케일링이 필요하다.
Label encoding vs One-hot encoding
카테고리 변수 인코딩 방법에는 Label encoding, One-hot encoding이 있다.
Label encoding은 정렬된 기준으로 숫자를 할당하는 방법이다.
One-hot encoding은 범주 종류에 대해 이진 값으로 할당하는 방법이다.
보통은 원-핫 인코딩을 많이 사용한다.
여기서도 원-핫 인코딩을 사용할 것이다.
vec = DictVectorizer()
vec.fit(X_train[category].to_dict('records'))
scaler = StandardScaler()
scaler.fit(X_train.drop(category, axis = 1))
위의 코드는 트레인셋에 대해 스케일러를 만들고 피팅하는 코드이다.
X_category = vec.transform(X_train[category].to_dict('records'))
X_train_category = pd.DataFrame(X_category.toarray(), columns = vec.feature_names_)
display(X_train_category.head())
X_train_without_category = X_train.drop(category, axis = 1)
X_scale = scaler.transform(X_train_without_category)
X_train_scale = pd.DataFrame(X_scale, columns = X_train_without_category.columns)
display(X_train_scale.head())
적용을 할 때엔 트레인셋과 테스트셋에 트랜스폼만 한다고 생각하면 된다.
X_train_final = pd.concat([X_train_scale, X_train_category], axis = 1)
X_train_final.tail()
X_category = vec.transform(X_test[category].to_dict('records'))
X_test_category = pd.DataFrame(X_category.toarray(), columns = vec.feature_names_)
display(X_test_category.head())
X_test_without_category = X_test.drop(category, axis = 1)
X_scale = scaler.transform(X_test_without_category)
X_test_scale = pd.DataFrame(X_scale, columns = X_test_without_category.columns)
display(X_test_scale.head())
X_test_final = pd.concat([X_test_scale, X_test_category], axis = 1)
X_test_final.tail()
X_train, y_train = X_train_final.values, y_train.values
X_test, y_test = X_test_final.values, y_test.values
여기까지가 데이터 전처리 부분이다
모델링
model_LR = LinearRegression(n_jobs = -1)
model_LR.fit(X_train, y_train)
display(pd.DataFrame(model_LR.coef_,
index = X_test_final.columns, columns = ['Linear regression 계수']))
일단 Linear Regression에 대해서 피팅을 실시했다.
아래 결과값은 coef값이다.
다음으로 라쏘, 릿지 모델을 만들 것이다.
람다값인 알파값을 임의로 설정해서 돌려봤다.
model_Lasso1 = Lasso(alpha = 0.0001, random_state = 1)
model_Lasso2 = Lasso(alpha = 100.0, random_state = 1)
model_Ridge1 = Ridge(alpha = 0.0001, random_state = 1)
model_Ridge2 = Ridge(alpha = 100.0, random_state = 1)
model_Lasso1.fit(X_train, y_train)
model_Lasso2.fit(X_train, y_train)
model_Ridge1.fit(X_train, y_train)
model_Ridge2.fit(X_train, y_train)
plt.figure(figsize = (10, 5))
plt.plot(sorted(np.abs(model_LR.coef_))[::-1], label = 'LR', c = 'gray')
plt.plot(sorted(np.abs(model_Lasso1.coef_))[::-1], label = 'Lasso alpha = 0.0001')
plt.plot(sorted(np.abs(model_Lasso2.coef_))[::-1], label = 'Lasso alpha = 100')
plt.plot(sorted(np.abs(model_Ridge1.coef_))[::-1], label = 'Ridge alpha = 0.0001')
plt.plot(sorted(np.abs(model_Ridge2.coef_))[::-1], label = 'Ridge alpha = 100')
plt.legend()
plt.ylim((-0.1, 1000))
plt.show()
그래프를 보면 X축은 변수의 종류, y축은 coef값이다.
라쏘에 대한 비교를 하자면 주황색이 작았던 파란색 대비 많은 변수의 값들이 계수가 0으로 도출이 되었다.
릿지를 보면 람다값이 큰 경우가 초록색 대비 계수 값들이 작은 값을 도출하는 것을 확인할 수 있었다.