[PYTHON - 머신러닝_랜덤 포레스트]★str.split(expand= True)★K-폴드 교차검증★하이퍼파라미터 튜닝
1. 랜덤 포레스트
==> 결정 트리의 단점인 오버피팅 문제를 완화시켜주는 발전된 형태의 트리 모델이다.
https://knowallworld.tistory.com/375
오버피팅 : 예측 모델이 훈련 셋을 지나치게 잘 예측한다면 새로운 데이터를 예측 할 때 큰 오차를 유발 할 수 있다. ==> 훈련 셋과 테스트 셋의 예측 정확도를 줄여야 한다.
==> 랜덤으로 생성된 무수히 많은 트리를 이용하여 예측
==> 여러 모델을 활용하여 하나의 모델을 이루는 기법을 앙상블이라 부른다.
==> 앙상블 기법 : 여러 모델을 만들고 각 예측값을 투표/평균 등으로 통합하여 더 정확한 예측 도모
장점 :
㉠ 결정 트리와 마찬가지로, 아웃라이어에 거의 영향을 받지 않는다.
㉡ 선형/비선형 데이터에 상관없이 잘 작동한다.
단점 :
㉠ 학습 속도가 상대적으로 느리다.
㉡ 수많은 트리를 동원하기 때문에 모델에 대한 해석이 어렵다.
2. 변수 전처리
data['engine']
data['engine'].str.split(expand = True) # 컬럼 하나씩만 인덱싱 해준다.
# 공백 기준으로 문자를 분할하여 별도의 변수로 출력
==> expand = True ==> 2개의 컬럼으로 나눈다.
data[['engine' , 'engine_unit']] = data['engine'].str.split(expand = True)
# 분할된 문자들을 새로운 변수들로 저장
data['engine'].head()
==> 'engine' 열 , 'engine_unit' 열 생성
def isFloat(value): # 함수 정의
try : # 시도
num = float(value) # 값을 숫자로 변환
return num # 변환된 값 리턴
except ValueError: # try에서 ValueError가 난 경우
return np.NaN # np.NaN 리턴
==> 에러가 발생했던 'bhp' 는 숫자로 변환할 수 없으므로 except 블록으로 넘어간다.
==> except Valuerror인 경우 NaN 값으로 대체한다.
def mile(x) :
if x['fuel'] == 'Petrol':
return x['mileage'] / 80.43
elif x['fuel'] == 'Diesel':
return x['mileage'] / 73.56
elif x['fuel'] == 'LG':
return x['mileage'] / 40.85
elif x['fuel'] == 'CNG':
return x['mileage'] / 44.23
data['mileage'] = data.apply(mile , axis=1) # mile 함수로 마일리지 수정
data[['fuel' ,'mileage']]
def torque_unit(x):
if 'NM' in str(x):
return 'NM'
elif 'KGM' in str(x):
return 'kgm'
data['torque_unit'] = data['torque'].apply(torque_unit)
data[['torque' , 'torque_unit']]
data['torque_unit'].isna()
data[data['torque_unit'].isna()]
data[data['torque_unit'].isna()]['torque'].unique() #
==> torque_unit이 NULL 값인 행의 'torque' 열의 고윳값 출력
def split_num(x):
x = str(x)
for i,j in enumerate(x):
if j not in '0123456789.' : # 만약 j가 0123456789.에 포함되지 않으면
cut= i
break
return x[:cut]
==> x = str(x) ==> string 화
data['torque'] = data['torque'].apply(split_num)
data['torque']
data['torque'] = data['torque'].replace('' , np.NaN)
==> 빈칸 값을 ==> NaN 값으로 대체하기
data['torque'] = data['torque'].astype('float64')
==> 'float64'로 변환하기
def torque_trans(x) :
if x['torque_unit'] == 'kgm':
return x['torque'] * 9.8066
else:
return x['torque']
data['torque'] = data.apply(torque_trans , axis =1 ) #함수 적용
data['torque']
==> 단위에 따른 차이를 맞춰주는 변환
3. 결측치 처리와 더미변수화
data.dropna(inplace = True) #결측치 행 제거
==> 결측치 행 제거
data = pd.get_dummies(data , columns = ['name' , 'fuel' , 'seller_type' , 'transmission' , 'owner'], drop_first= True)
data
==> 더미 변수화
4. 모델링 및 평가하기
from sklearn.model_selection import train_test_split
X_train , X_test, y_train , y_test = train_test_split(data.drop('selling_price' , axis =1 ) , data['selling_price'] , test_size= 0.2 ,random_state=100)
# 훈련 셋/ 시험 셋 분리
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state= 100)
model.fit(X_train , y_train)
train_pred = model.predict(X_train)
test_pred = model.predict(X_test)
==> 랜덤 포레스트 모델은 사이킷런의 ensemble 패키지 안에 속해 있다.
==> 랜덤 포레스트는 매번 다른 방식으로 나무들을 생성하므로, random_state를 지정한다.
==> 훈련 셋으로 학습한 훈련셋과 시험셋에 대한 예측값 2가지를 모두 구한다.
from sklearn.metrics import mean_squared_error #RMSE 이용
print('train_rmse : ' , mean_squared_error(y_train , train_pred)**0.5 , 'test_rmse : ' , mean_squared_error(y_test , test_pred) **0.5)
5. K-폴드 교차검증
==> 교차검증의 목적은 모델의 예측력을 더 안정적으로 평가하기 위함이다.
==> 새로운 데이터를 얼마나 잘 예측하는지 확인하고자 훈련셋과 시험셋을 나누어서 평가하였다.
==> 데이터 분할은 랜덤 샘플링으로 이루어졌기 때문에 안정적이다.
==> BUT. 우연한 오차들이 예측력을 평가하는 데 작은 노이즈로 존재한다.
==> random_state 매개변수에 다양한 숫자를 넣어보면 매번 훈련셋의 평가값(RMSE, accuracy score)가 계속 변한다.
==> 작은 오차들 고려하여 평가하는 방법이 교차검증
==> 더 낮은 RMSE를 얻기 위함이 아니라, 우연의 요소가 배제된 더 신뢰할 만한 결과를 얻기 위함이다.
1> K-폴드 교차검증 :
데이터를 특정 개수(K개) 로 쪼개어서 그중 하나씩을 선택하여 시험셋으로 사용하되, 이 과정을 K번만큼 반복
이터레이션 1
훈련 셋 | 훈련 셋 | 훈련 셋 | 훈련 셋 | 시험 셋 |
이터레이션 2
훈련 셋 | 훈련 셋 | 훈련 셋 | 시험 셋 | 훈련 셋 |
이터레이션 3
훈련 셋 | 훈련 셋 | 시험 셋 | 훈련 셋 | 훈련 셋 |
이터레이션 4
훈련 셋 | 시험 셋 | 훈련 셋 | 훈련 셋 | 훈련 셋 |
이터레이션 5
시험 셋 | 훈련 셋 | 훈련 셋 | 훈련 셋 | 훈련 셋 |
==> 이터레이션(반복)은 모델링을 수행하는 단위 이다.
==> 이터레이션의 평가값(오차)의 평균값을 내어 RMSE를 도출한다.
2> 코드
from sklearn.model_selection import KFold
data.reset_index(drop=True , inplace =True) # drop 매개변수를 사용하지 않으면 기존 인덱스가 새로운 컬럼 형태로 추가된다.
==> data 결측치 행 제거하였으므로 , index값 초기화 시켜준다. drop = True로 index 열 생성 방지
kf = KFold(n_splits=5) #KFold 객체 생성 , 5개로 분할
X = data.drop('selling_price' , axis =1 ) # 종속변수 제거하여 X에 저장
y = data['selling_price'] # 종속변수를 y에 저장
for i, j in kf.split(X) : # 순회
print(i , j )
[1575 1576 1577 ... 7868 7869 7870] [ 0 1 2 ... 1572 1573 1574]
[ 0 1 2 ... 7868 7869 7870] [1575 1576 1577 ... 3146 3147 3148]
[ 0 1 2 ... 7868 7869 7870] [3149 3150 3151 ... 4720 4721 4722]
[ 0 1 2 ... 7868 7869 7870] [4723 4724 4725 ... 6294 6295 6296]
[ 0 1 2 ... 6294 6295 6296] [6297 6298 6299 ... 7868 7869 7870]
==> 훈련 셋과 시험셋으로 사용할 인덱스 출력 ==> 5개의 다른 공간 보여준다.
from sklearn.ensemble import RandomForestRegressor
train_rmse_total = []
test_rmse_total = []
for train_index , test_index in kf.split(X):
X_train , X_test = X.loc[train_index] , X.loc[test_index]
y_train , y_test = y[train_index] , y[test_index]
model = RandomForestRegressor(random_state= 100) # 모델 객체 생성
model.fit(X_train , y_train) # 학습
train_pred = model.predict(X_train) # 훈련셋 예측
test_pred = model.predict(X_test) # 시험셋 예측
train_rmse = mean_squared_error(y_train , train_pred) **0.5 # 훈련 셋 rmse 계산
test_rmse = mean_squared_error(y_test , test_pred) **0.5 # 시험 셋 rmse 계산
train_rmse_total.append(train_rmse)
test_rmse_total.append(test_rmse)
==> X_train , X_test , y_train , y_test 정의 하여 mean_squared_error 활용하여 RMSE 값 도출한다.
print('train_rmse : ' , sum(train_rmse_total)/5 , 'test_rmse :' , sum(test_rmse_total)/5)
==> 최종 RMSE는 오차값들의 평균이다. ==> 5를 나누어 준다.
6. 랜덤 포레스트
==> 랜덤 포레스트는 결정 트리의 집합체
==> 결정트리만 사용하면 오버피팅의 문제가 생긴다. ==> 랜덤 포레스트가 여러개의 트리를 만들 때는 데이터 전체가 아닌 매번 다른 일부의 데이터를 사용하여 다른 트리를 만들어낸다.
==> 전체 데이터를 사용한 결과보다 예측력이 떨어질 수 있으나, 예측력이 떨어지는 수많은 트리들과 함께 중위값을 찾아내면 오버피팅을 막는 데 효율적이다.
일부 데이터를 취하는 기준
1> 데이터의 행(row) 기준으로 일부씩만 추출 ==> 1만 행의 데이터가 있다고 할때 , 1 만행 전체를 사용하는 게 아닌 약 2/3에 해당하는 데이터만을 사용 ==> 다른 결과의 트리 생성
2> 데이터의 열(column) 변수 기준 ==> A라는 변수가 예측하는데 결정적인 역할을 한다고 가정, 트리 1000개를 만들더라도 최상위 노드에서의 분류 기준은 항상 A 변수가 된다.
==> 다른 변수들에게 가중치 부여하여 오버피팅을 피하도록 한다.
==> 랜덤포레스트의 최종 예측값은 각 트리의 예측값들을 기반으로 만들어진다. ==> 회귀 문제는 연속형 변수를 예측하기 때문에, 각 트리에서 만들어낸 예측값들의 평균값을 랜덤 포레스트의 최종 예측값으로 사용한다.
==> 분류 문제는 각 트리에서 예측한 값들 중 최다 투푯값으로 랜덤 포레스트의 예측값이 결정된다.
==> EX) 0과 1을 분류하는 문제에서 총 1000개 트리 중 700개 트리가 1로 예측하고 300개 트리가 0으로 예측했다면, 가장 많은 투표를 받은 1이 랜덤 포레스트의 최종 예측값이 된다.
7. 하이퍼파라미터 튜닝
==> 랜덤 포레스트는 수많은 하이퍼파라미터를 갖고 있다.
n_estimators : 랜덤 포레스트를 구성하는 결정 트리의 개수
max_depth : 결정 트리와 동일하게, 각 트리의 최대 깊이를 제한
min_samples_split : 해당 노드를 나눌 것인지 말 것인지를 노드 데이터 수를 기준으로 판단한다. ==> 이 매개변수에 지정된 숫자보다 적은 수의 데이터가 노드에 있으면 더는 분류하지 않는다.
min_samples_leaf : 분리된 노드의 데이터에 최소 몇 개의 데이터가 있어야 할지를 결정하는 매개변수이다. ==> 이 매개변수에 지정된 숫자보다 적은 수의 데이터가 분류된다면, 해당 분리는 이루어지지 않는다.
n_jobs : 병렬 처리에 사용되는 CPU 코어 수. 많은 코어를 사용할수록 속도는 빨라지며, -1을 입력하면 지원하는 모든 코어를 사용한다.
from sklearn.ensemble import RandomForestRegressor
train_rmse_total = []
test_rmse_total = []
for train_index , test_index in kf.split(X):
X_train , X_test = X.loc[train_index] , X.loc[test_index]
y_train , y_test = y[train_index] , y[test_index]
model = RandomForestRegressor(n_estimators= 300 , max_depth= 50 , min_samples_split=5 , min_samples_leaf= 1 , n_jobs= -1 , random_state= 100) # 모델 객체 생성
model.fit(X_train , y_train) # 학습
train_pred = model.predict(X_train) # 훈련셋 예측
test_pred = model.predict(X_test) # 시험셋 예측
train_rmse = mean_squared_error(y_train , train_pred) **0.5 # 훈련 셋 rmse 계산
test_rmse = mean_squared_error(y_test , test_pred) **0.5 # 시험 셋 rmse 계산
train_rmse_total.append(train_rmse)
test_rmse_total.append(test_rmse)
==> 오버피팅이 줄어들었다.
8. 정리
1> 중고차의 가격을 예측하는 모델 만들기
2> pandas, numpy , matplotlib , seaborn 라이브러를 임포트하여 데이터 처리하기
3> 텍스트 데이터, 결측치 처리와 더미 변수를 변환한다.
4> RandomForest를 사용하여 예측 모델을 만들었다. 연속형 변수이기 때문에 RMSE를 좋다/나쁘다로 평가할 수는 없다.
5> RandomForest에서 쓸수 있는 몇가지 하이퍼파라미터를 변경하여 test_rmse(평균 제곱근 편차)를 조금 낮출 수 있었다.
출처 : 데싸노트의 실전에서 통하는 머신러닝
(Golden Rabbit , 저자 : 권시현)
※혼자 공부용