[캐글 필사] Housing Price - Random Forest
진행일 : 2022.10.27 - 2022.10.30
데이터셋 정보
https://www.kaggle.com/code/swathianil/starter-housing-price-random-forest
Starter: Housing Price | Random Forest
Explore and run machine learning code with Kaggle Notebooks | Using data from Housing Prices Competition for Kaggle Learn Users
www.kaggle.com
| Housing Prices Competition: Ames Housing dataset
주택 가격 예측을 위한 데이터셋
- 79개의 설명변수는 Ames, Iowa 주거 지역의 특징을 보여준다.
목차
- 필요한 모듈 불러오기
- 데이터 불러오기
- 데이터 전처리
- 결측치 제거
- 상관성이 높은 특징(feature) 제거
- Feature engineering
- 데이터셋 분할
- 원핫인코딩 (One hot encode)
- 스케일링 (Feature Scaling)
- 랜덤 포레스트 회귀
- 성능 평가
- 전체 데이터로 모델 학습
- 예측을 위한 로우 데이터 불러오기
- 테스트 데이터 전처리
- 학습 데이터셋과 Feature 맞추기
- Feature engineering
- 결측치 대체
- 원핫인코딩 (One hot encode)
- 스케일링 (Feature Scaling)
- 테스트 데이터로 예측
- 제출본 만들기
1. 필요한 모듈 불러오기
import pandas as pd # 데이터 조작
import numpy as np
from sklearn.ensemble import RandomForestRegressor # 랜덤포레스트 '회귀' 모델을 통해 값을 '예측'
from sklearn.metrics import mean_absolute_error # MAE(절대평균오차)로 모델 성능 평가
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
2. 데이터 불러오기
iowa_file_path = 'input/train.csv'
df = pd.read_csv(iowa_file_path)
pd.options.display.max_rows = 20
df.head()
3. 데이터 전처리
예측치인 'SalePrice'이 없는 데이터셋(X)과
이를 따로 떼어내 예측치만 있는 데이터셋(y)으로 구분해준다.
# 데이터셋 분할
# X : 독립(independent) 데이터셋
# y : 종속(dependent) 데이터셋
X = df.drop('SalePrice', axis=1)
y = df.SalePrice
X.shape
"""
(1460, 61)
"""
예측치가 없는 데이터셋의 컬럼은 61개, 관측치는 1460개.
3.1 결측치 제거
# 결측치 확인
X.isna().sum()
"""
Id 0
MSSubClass 0
MSZoning 0
LotFrontage 259
LotArea 0
...
MiscVal 0
MoSold 0
YrSold 0
SaleType 0
SaleCondition 0
Length: 80, dtype: int64
"""
# 결측치 제거
def nulldrop(df):
df.dropna(axis=1, inplace=True)
return df
X = nulldrop(X)
X.isna().sum()
"""
Id 0
MSSubClass 0
MSZoning 0
LotArea 0
Street 0
..
MiscVal 0
MoSold 0
YrSold 0
SaleType 0
SaleCondition 0
Length: 61, dtype: int64
"""
결측치 갯수가 제거 전후로 80 -> 61개로 19개의 열이 줄어들었다.
3.2 상관성이 높은 특징(feature) 제거
상관성이 높은 특징은 종속변수 간 다중공선성(multicollinearity)을 초래할 수 있다.
다중공선성은 모델 성능에 영향을 미치므로 이를 제거하는 것이 바람직하다.
| 다중공선성
p개의 입력변수(x1, x2, ..., xp)들 사이에 상관관계가 존재하는 상태를 의미한다.
# 상관성 제거 함수
def corrdrop(df, maxcorr):
corr_matrix = df.corr().abs() # 상관 매트릭스 절댓값으로
mask = np.triu(np.ones_like(corr_matrix, dtype = bool))
# np.ones_like()를 통해 상관 매트릭스와 같은 배열의 array가 전부 1인 매트릭스를 생성
## 데이터타입을 bool로 선언한다는 것은 0이 아닌 모든 숫자를 True로 판단한다는 것이므로 True값만을 가짐
# np.triu()를 통해 첫번째 행부터 k개(생략되었으므로 기본=0) 생긴다.
## 데이터타입이 bool이므로 0으로 표시되지 않고 False로 바뀜.
### 첫번째 행부터 0이 k=0개, 두번째 행부터 0이 k+1=1개, ... 1개씩 증가하며 생긴다.
tri_df = corr_matrix.mask(mask) # .mask(조건) : 조건에 맞지 않는 데이터들을 선택하여 출력
to_drop = [x for x in tri_df.columns if any(tri_df[x] > maxcorr)] # 상관성이 높은 값은
df = df.drop(to_drop, axis = 1) # 제거
return df
X = corrdrop(X, 0.95) # 상관성 95%를 넘으면 제거하기 위해 값을 지정함.
print(f"The reduced dataframe has {X.shape[1]} columns.")
"""
The reduced dataframe has 61 columns.
"""
# 위에서 X.shape = (1460, 61) 이었으므로, 상관성 높은 변수를 제거하기 전과 후가 차이가 없음
# 즉, 상관성이 95% 이상인 값은 없다!
| 상관 분석(Correlation Analysis)
높은 상관성을 가진 feature를 골라내는 과정은, 이전에 'EDA - Data Scientist Salary' 필사에서 했던 과정과 유사했다. (더보기)
'EDA - Data Scientist Salary' 의 상관 분석 과정
우선 상관 매트릭스를 만든다. df.corr().abs()
이 매트릭스와 동일한 크기의 매트릭스를 만들고, (1로 가득찬 매트릭스 : np.ones_like())
첫번째 행부터 0이 지정한 k만큼 증가하도록 하여 (np.triu())
매트릭스에서 같은 값이 존재하지 않는 형태로 만들고 (matrix[0,1] == matrix[1,0] 이므로)
처음 만들었던 상관 매트릭스에서 조건 함수(where나 mask를 사용해 조건(data type='bool')에 맞는 값만 추출하면,
# BEFORE
# 0 1 2 3
# 0 1.0000 0.1175 0.8717 0.8179
# 1 0.1175 1.0000 0.4284 0.3661
# 2 0.8717 0.4284 1.0000 0.9628
# 3 0.8179 0.3661 0.9628 1.0000
# AFTER
# 0 1 2 3
# 0 NaN 0.1175 0.8717 0.8179
# 1 NaN NaN 0.4284 0.3661
# 2 NaN NaN NaN 0.9628
# 3 NaN NaN NaN NaN
이런 식으로 값이 나오게 된다. (여기서는 k=1 이어서 첫번째 행부터 0이 1개 존재하였다.)
| 코드 심층 리뷰
위 코드에서는 mask 함수를 이용해 조건에 맞지 않은, 즉 False에 해당하는 자리의 값들만 보여지도록 했다.
corr_matrix = df.corr().abs()
mask = np.triu(np.ones_like(corr_matrix, dtype = bool))
tri_df = corr_matrix.mask(mask)
to_drop = [x for x in tri_df.columns if any(tri_df[x] > maxcorr)]
df = df.drop(to_drop, axis = 1)
이전 필사에서는 where 함수를 이용해 조건에 맞는, 즉 데이터 타입이 bool(0이 아닌) 값만 출력하도록 했다는 점에서 달랐다.
df[numeric_features].corr().abs()
corr_matrix = df[numeric_features].corr().abs()
corr_upper_triangle_matrix = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype('bool'))
drop_list = [column for column in corr_upper_triangle_matrix.columns
if any(corr_upper_triangle_matrix[column] > 0.90)]
df = df.drop(drop_list, axis=1, inplace=True)
다시 본론으로 돌아와서, 상관성 높은 변수를 제거한 X.columns은 추후 필요하므로 저장해 놓는다. (6.1 테스트 데이터 전처리)
# 나중을 위해 컬럼명 저장해놓기
corr_cols = X.columns
3.3 Feature engineering
기존 변수를 조합하여 새로운 변수를 만드는 과정으로,
`TotBath`와 `Total_Home_Quality`, 'HighQualSF` 총 세 가지 변수를 만들었다.
def create_extra_features(df):
df['TotBath'] = df['FullBath'] + (0.5*df['HalfBath']) + df['BsmtFullBath'] + (0.5*df['BsmtHalfBath'])
df['Total_Home_Quality'] = df['OverallQual'] + df['OverallCond']
df['HighQualSF'] = df['1stFlrSF'] + df['2ndFlrSF']
return df
X = create_extra_features(X)
X.shape # 61->64 세개의 컬럼이 추가되었음.
참고 : 컬럼 정보를 확인하며 왜 이런 컬럼을 조합하였는지 생각해보기!
df['HalfBath']
# Half baths(세면대와 변기만 있는) above grade(지상)
df['BsmtFullBath']
# Basement(지하) full bathrooms(세면대, 변기, 욕조 등 모두 갖춰진)
df['OverallQual']
# OverallQual : Overall material and finish quality
# OverallCond : Overall condition rating
df['1stFlrSF']
# 1stF1rSF : First Floor squre feet
# Square Feet, SF : 평방피트 (면적을 세는 단위)
3.4 데이터셋 분할
학습 데이터셋, 테스트 데이터셋으로 분할하기
주피터 노트북이 실행될 때마다 매번 같은 비율로 나누기 위해서, random_state를 설정한다.
# 평가(validation data) 데이터, 학습(training data) 데이터로 나눈다.
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)
print(X_train.shape)
print(X_test.shape)
"""
(1095, 64)
(365, 64)
"""
3.5 원핫인코딩 (One hot encode)
머신러닝 알고리즘을 데이터에 적용하기 위해, 모든 값들을 (기계가 읽을 수 있는) 숫자 형식으로 입력하는 것이 중요하다.
먼저 데이터셋의 데이터 타입을 살펴보고, pandas 라이브러리인 'get_dummies'를 이용해 원핫인코딩을 실행했다.
| get_dummies
단순히 수치형 변수로만 변환하게 되면 서로 간에 관계성이 생길 수 있다.
예를 들어, '월요일=1, 화요일=2, 수요일=3'으로 변환할 경우, '1+2=3'이기 때문에 '월+화=수'가 성립하는 것은 아니므로
사실이 아닌 '관계성'으로 인해 잘못된 학습이 일어날 수 있어 서로 무관한 수, 즉 더미로 만든 가변수로 변환함으로써
발생할 수 있는 문제를 막아주는 것이다. 가변수는 0과 1로 이루어져 있다.
def onehotencode(df):
encoded = pd.get_dummies(df)
return encoded
X_train_encoded = onehotencode(X_train)
print(X_train_encoded.shape)
"""
(1095, 215)
"""
X_test_encoded = onehotencode(X_test)
print(X_test_encoded.shape)
"""
(365, 195)
"""
인코딩을 마친 학습 데이터셋과 테스트 데이터셋은 열을 기준(axis=1)으로 정렬(align)시킨다.
# align() : 두 객체를 동시에 정렬하는 가장 빠른 방법
# join='inner' : 두 객체 둘 다 있는 인덱스를 교차시킨다.
X_train_encoded_final, X_test_encoded_final = X_train_encoded.align(X_test_encoded, join='inner', axis=1)
# axis=1 : 열방향으로
# align(X_test_encoded) 함수 안에 있는 데이터셋 기준으로 조인 적용.
3.6 Feature scaling
각 변수의 독립성은 유지하면서 비교 가능한 범위로 설정해야 한다. 이때, 'fit_transform' 메서드를 사용한다.
| fit_transform
- 제로 평균 (zero mean)
- 데이터가 네트워크의 입력값으로 들어가기 전 데이터의 평균값을 빼준다.
- 데이터 벡터 각 원소의 평균값을 뺀 평균 제거 벡터를 사용.
- 단위 분산 (unit variance)
- 여러 입력값들 중 유독 수치의 크기가 큰 수의 값에 인해 신경망이 지나치게 영향받는 것을 방지하기 위하여 단위 분산을 1로 둔다.
- 분산은 값들의 평균을 구하고 그 값들의 제곱을 다 더해 다시 평균을 낸 값으로,
이 값을 1로 둔다는 것은 분산을 1로 맞춘 비율에 맞추어서 모든 값들을 조정하게 되면 원래 큰 값이어서 다른 영향력을 가질 수 있었던 가능성이 없어지게 된다.
scaler = StandardScaler()
X_train_encoded_scaled = scaler.fit_transform(X_train_encoded_final)
X_test_encoded_scaled = scaler.fit_transform(X_test_encoded_final)
X_test_encoded_scaled.shape
"""
(365, 191)
"""
4. 랜덤 포레스트 회귀
| 머신러닝 알고리즘 모델 선택하기
현재 사용하는 데이터셋은 라벨링이 되어있는 데이터셋이다. (주택가격 'SalePrice')
따라서 지도학습 접근법을 사용한다. 지도학습은 '분류'와 '회귀'로 구분된다.
우리는 변수의 값을 분류하는 것이 아닌 '예측'이 목표이므로 (주택가격 예측) '회귀' 문제를 다루고 있는 것이다.
따라서 랜덤 포레스트 회귀 모델을 사용한다.
| Random Forest Regression 랜덤 포레스트 회귀
지도학습 알고리즘으로, 앙상블(Ensemble) 학습 방법을 사용한다.
앙상블 학습 방법은 여러개의 머신러닝 알고리즘의 예측값들을 결합시키는 기법으로, 하나의 모델일 때보다 더 정확한 예측을 가능하게 한다.
모델 학습동안 여러개의 의사결정 트리를 만들어 트리의 결과값들을 사용한다.
랜덤 포레스트 모델에서는 오버피팅(과적합)이 발생하기 쉽다.
최적의 트리 단계(depth)를 알아내기 위해, 단계별 값들을 테스트하기 위한 평균절대오차(MAE, Mean Absolute Error)는 효과적인 매트릭스이다.
알고리즘이 예측한 것과 실제 값의 차이를 의미하며 값이 작을수록 성능이 좋다.
# 모델 정의
rf_model = RandomForestRegressor(random_state=0)
rf_model.fit(X_train_encoded_scaled, y_train)
4.1 성능 평가 (MAE)
rf_val_predictions = rf_model.predict(X_test_encoded_scaled)
rf_val_mae = mean_absolute_error(rf_val_predictions, y_test)
print("Validation MAE for Random Forest Model: {:,.0f}".format(rf_val_mae))
"""
Validation MAE for Random Forest Model: 15,867
"""
rf_val_predictions.shape
y_test.shape
"""
(365,)
"""
4.2 전체 데이터로 모델 학습하기
정확성을 향상시키기 위해, 모든 학습 데이터 X에 대해 학습을 진행하는 새로운 랜덤 포레스트 모델을 만든다.
#4 새로운 랜덤 포레스트 회귀 모델 정의
rf_model_on_full_data = RandomForestRegressor(random_state=0)
X_encoded = onehotencode(X) #3.5 전체 데이터(X) 원핫인코딩
scaler_2 = StandardScaler() #3.6 Feature scaling을 위한 Scaler
X_encoded_scaled = scaler.fit_transform(X_encoded) # 원핫인코딩을 마친 데이터 X에 feature scaling
rf_model_on_full_data.fit(X_encoded_scaled, y) #4 랜덤 포레스트 회귀 모델 학습
5. 예측을 위한 로우 데이터 불러오기
# 예측을 위해 사용할 파일의 경로
test_data_path = 'input/test.csv'
# 판다스로 테스트 데이터 파일 읽기
test_data = pd.read_csv(test_data_path)
test_data.shape
"""
(1459, 80)
"""
6. 테스트 데이터 전처리
6.1 학습 데이터와 동일하게 feature 맞추기
test_data = test_data[corr_cols] #3.2 에서 상관성 높은 feature를 삭제한 후 저장했던 컬럼을 불러온다.
test_data.shape
"""
(1459, 61)
"""
6.2 Feature engineering
test_data = create_extra_features(test_data) #3.3 학습데이터 feature engineering 과정에서 생성한 함수 적용
test_data.shape
"""
(1459, 64)
"""
기존에 있는 컬럼을 조합하여 새로운 3개의 변수를 만들어 61개에서 64개로 늘었다.
6.3 결측치 대체
test_data = test_data.fillna(0)
test_data.isna().sum()
6.4 원핫인코딩
test_data = pd.get_dummies(test_data)
test_data.shape
"""
(1459, 212)
"""
인코딩을 마친 학습 데이터셋과 테스트 데이터셋은 열을 기준(axis=1)으로 정렬(align)시킨다.
final_train, final_test = X_encoded.align(test_data, join='left', axis=1, fill_value=0)
print(final_train.shape)
"""
(1460, 219)
"""
print(final_test.shape)
"""
(1459, 219)
"""
6.5 Feature Scaling
final_test_scaled = scaler.transform(final_test)
final_test_scaled.shape
"""
(1459, 219)
"""
7. 테스트 데이터로 예측
# 제출할 예측치 만들기
test_preds = rf_model_on_full_data.predict(final_test_scaled)
test_preds.shape
"""
(1459,)
"""
8. 제출본 만들기
output = pd.DataFrame({'ID': test_data.Id, # 주택 ID 별
'SalePrice': test_preds }) # 판매가 예측치
output.to_csv('submission.csv', index=False) # csv파일로 저장