본문 바로가기
AI/머신러닝

06. 의사결정 나무(Decision Tree) | 자전거

by 사라리24 2024. 6. 11.
SMALL

1. 자전거 데이터셋

  

 

  • 작업파일

bike.csv
2.42MB

 

 

  • import
  
 
        import numpy as np
        import pandas as pd
        import seaborn as sns
        import matplotlib.pyplot as plt
 
  

 

 

  • 데이터 가져오기
 
 
        bike_df = pd.read_csv('/content/drive/MyDrive/1. KDT/6. 머신러닝 딥러닝/데이터/bike.csv')
        bike_df
 
 

 

 

  • 정보보기
  
 
        bike_df.info()
 
  

 

 

  • 평균치 보기
 
 
        bike_df.describe()
 
  



  • count: 데이터의 개수 (결측값을 제외한 값의 개수)
  • mean: 평균값
  • std: 표준 편차
  • min: 최솟값
  • 25%: 1사분위수 (데이터의 25% 지점의 값)
  • 50%: 중간값 (데이터의 50% 지점의 값, 중앙값)
  • 75%: 3사분위수 (데이터의 75% 지점의 값)
  • max: 최댓값

 

 

  • [ count ] 막대그래프로 보기
  
 
      sns.displot(bike_df['count'])
 
  

 

 

  • [ count ] 이상치 데이터 확인
 
 
        sns.boxplot(bike_df['count'])
 
  




 

  • 대여수가 체감온도에 영향을 받는 것을 확인
  
 
 
        sns.scatterplot(x='feels_like', y='count', data=bike_df, alpha= 0.3)
 
  
 

 

 

  • 대여수가 기압차로 영향을 받는 것을 확인
 
 
          sns.scatterplot(x='pressure', y='count', data=bike_df, alpha=0.3)
 
  

 

 

  • 대여수가 풍속에 따라 향을 받는 것을 확인
  
     
        sns.scatterplot(x='wind_speed', y='count', data=bike_df, alpha=0.3)
 
 
 

 

  • 대여수가 풍향에 따라 영향을 받는 것을 확인
  
  
      sns.scatterplot(x='wind_deg', y='count', data=bike_df, alpha=0.3)
 
  

 

  • null 값 확인
 
      
        bike_df.isna().sum()
 
  

 

 

  • null 값 0으로 채우기
  
 
          bike_df = bike_df.fillna(0)
          bike_df.isna().sum()
 
 

 

 

  • 날짜가 문자형인데, 날짜타입으로 바꿔줘야 함
 
 
        bike_df.info()
 
  
 

 

 

  • 날짜타입으로 변경하고 확인
  
 
        bike_df['datetime'] = pd.to_datetime(bike_df['datetime'])
  
        bike_df.info()
 
 

 

 

  • year, month, hour 파생변수 만들기
 
 
        bike_df['year'] = bike_df['datetime'].dt.year
        bike_df['month'] = bike_df['datetime'].dt.month
        bike_df['hour'] = bike_df['datetime'].dt.hour
 
       bike_df.head()
 
 

 

  • 'datetime' 열에서 날짜 부분만 추출해서 'date' 새로운 열에 넣기
  
 
         bike_df['date'] = bike_df['datetime'].dt.date
         bike_df.head()
 
 

 

 

  • 날짜별 대여수 그래프로 보기
 
 
          plt.figure(figsize=(14, 4))
          sns.lineplot(x='date', y='count', data=bike_df)
          plt.xticks(rotation=45)
          plt.show()
 
  

 

 

  • 2019년 월별 평균 자전거 대여 갯수를 출력
  
 
        bike_df[bike_df['year'] == 2019].groupby('month')['count'].mean()
 
 

 

 

  • 2020년 월별 평균 자전거 대여 갯수를 출력
  
 
        bike_df[bike_df['year'] == 2020].groupby('month')['count'].mean()
        # 2020년 4월 데이터가 없음을 알 수 있다
 
  

 

 

  • 코로나 기점으로 날짜 파생변수 만들기
 
 
        # covid
        #                         ~ 2020-04-01: precovid
        # 2020-04-01 ~ 2021-04-01: covid
        # 2021-04-01 ~                        : postcovid

        def covid(date):
            if str(date) < '2020-04-01':
                return 'precovid'
            elif str(date) < '2021-04-01':
                return 'covid'
            else:
                return 'postcovid'
 
      bike_df['date'].apply(covid)
 
 

 

 

  • 'covid'라는 새로운 열
    날짜를 기준으로 'precovid', 'covid', 'postcovid'로 구분
  
 
 
        bike_df['covid'] = bike_df['date'].apply(lambda date: 'precovid' if str(date) < '2020-04-01' else 'covid' if str(date) < '2021-04-01' else 'postcovid')
        bike_df.head()
   
 



 

 

  •  'season' 파생변수
    각 달을 기준으로 'spring', 'summer', 'fall', 'winter'로 구분 
 
 
        # season
 
        # 3~5월: spring
        # 6~8월: summer
        # 9~11월: fall
        # 12~2월: winter

        bike_df['season'] = bike_df['month'].apply(lambda x: 'winter' if x == 12
                                                                                                                       else 'fall' if x >= 9
                                                                                                                       else 'summer' if x >= 6
                                                                                                                       else 'spring' if x >= 3
                                                                                                                       else 'winter')
 
       bike_df[['month', 'season']]
 
 

 

  •  'day_night' 파생변수
    각 시간대(hour)를 기준으로
    'early morning', 'late morning', 'early afternoon',
    'late afternoon', 'early evening', 'late evening',  'night'로 구분
  
 
          # day_night
 
          # 21시 이후: night
          # 19시 이후: late evening
          # 17시 이후: early evening
          # 15시 이후: late afternoon
          # 13시 이후: early afternoon
          # 11시 이후: late morning
          # 6시 이후: early morning
 

          bike_df['day_night'] = bike_df['hour'].apply(lambda x: 'night' if x >= 21
                                                                           else 'late evening'  if x >= 19
                                                                           else 'early evening' if x >= 17
                                                                           else 'late afternoon' if x >= 16
                                                                           else 'early afternoon' if x >= 13
                                                                           else 'late morning' if x >= 11
                                                                           else 'early morning' if x >= 5
                                                                           else 'night')
 
 
         bike_df.head()

 

 

  • 데이터 전처리: 필요없는 데이터 삭제
 
 
        # 필요 없는 데이터 날리기
        bike_df.drop(['datetime', 'month', 'date', 'hour'], axis = 1, inplace=True)
 
        bike_df.head()
 
 

 

  • 문자형 데이터 확인
  
 
        bike_df.info()
 
 

 

 

  • 문자형 필드: 고유값의 개수
 
 
      for i in ['weather_main', 'covid', 'season', 'day_night']:
          print(i, bike_df[i].nunique())
 
 

 

 

  • 'weather_main' 열에 있는 모든 고유한 값
  
 
        bike_df['weather_main'].unique()
  
 
array(['Clouds', 'Clear', 'Snow', 'Mist', 'Rain', 'Fog', 'Drizzle',  'Haze', 'Thunderstorm', 'Smoke', 'Squall'], dtype=object)
  • 'Clouds': 구름
  • 'Clear': 맑음
  • 'Snow': 눈
  • 'Mist': 안개
  • 'Rain': 비
  • 'Fog': 안개
  • 'Drizzle': 가랑비
  • 'Haze': 연무
  • 'Thunderstorm': 천둥번개
  • 'Smoke': 연기
  • 'Squall': 돌풍

 

 

  • 'weather_main', 'covid', 'season', 'day_night'  열 원핫인코딩
  
 
        bike_df = pd.get_dummies(bike_df, columns =['weather_main', 'covid', 'season', 'day_night'])
        bike_df.head()
 
  
 

 

 

  • 열 45개 보기
 
 
          pd.set_option('display.max_columns', 45)
 
          bike_df.head()
 
 








 

 

  • 데이터 분리
    학습용(train)과 테스트용(test) 
  
   
        from sklearn.model_selection import train_test_split
 
        X_train, X_test, y_train, y_test = train_test_split(bike_df.drop('count', axis=1), bike_df['count'], test_size=0.2, random_state=2024)
 
        X_train.shape, y_train.shape
 
        X_test.shape, y_test.shape
 
   

X 학습 : 26703개의 샘플(행)과 39개의 특성(열)

X 테스트 : 6676개의 샘플(행)과 39개의 특성(열)

y 학습 : 26703개의 샘플(행)

y 테스트 : 6676개의 샘플(행)

 

2. 의사결정나무(Decision Tree)


  - 데이터를 분석하여 패턴을 파악하여 결정 규칙을 나무구조로 나타낸 기계학습 알고리즘

 

의사 결정 나무(Decision Tree)


데이터를 분석하여 패턴을 파악하고 결정 규칙을 나무 구조로 나타내는 기계학습 알고리즘입니다.
이 알고리즘은 분류(Classification)와 회귀(Regression) 문제를 모두 해결할 수 있습니다.

데이터를 분할하여 최적의 분할 지점을 찾아가는 과정에서
지니계수(Gini Index)나 엔트로피(Entropy)를 사용하여 불순도를 측정합니다.
이 지표들은 데이터의 불순도를 나타내며, 값이 작을수록 데이터의 순도가 높습니다.

하지만 의사 결정 나무는 훈련 데이터에 과도하게 적합하여 오버피팅(과적합)되기 쉽습니다.
이는 훈련 데이터에서는 정확도가 높지만 테스트 데이터에서는 성능이 나빠지는 현상을 말합니다.
오버피팅을 방지하기 위해 사전 가지치기 사후 가지치기라는 두 가지 방법이 사용됩니다.

  • 사전 가지치기(Pre-pruning):
    나무가 다 자라기 전에 알고리즘을 멈추는 방법입니다.
    이 방법은 나무가 너무 깊어지거나 가지가 너무 많아지는 것을 방지하여 오버피팅을 줄입니다.
  • 사후 가지치기(Post-pruning):
    의사결정 나무를 완전히 생성한 후, 밑에서부터 가지를 쳐 나가는 방법입니다.
    이 방법은 초기에는 나무를 최대한 성장시킨 후, 불필요한 가지를 제거하여 오버피팅을 줄입니다.
의사 결정 나무는 오버피팅을 피하고 모델의 일반화 성능을 향상시키기 위해 이러한 가지치기 기법을 사용합니다.
이를 통해 데이터를 효과적으로 분류하고 예측할 수 있는 모델을 만들 수 있습니다.


 

  • scikit-learn 라이브러리에서 DecisionTreeRegressor 클래스를 가져오기
 
 
        from sklearn.tree import DecisionTreeRegressor
 
 
DecisionTreeRegressor를 사용하면
연속적인 타겟 변수를 예측하는 모델을 만들 수 있습니다.


데이터를 분할하여 최적의 분할 지점을 찾는 방식으로 예측을 수행합니다.

 

 

  • 결정 트리 회귀 모델(dt)을 생성
  
 
        dt = DecisionTreeRegressor(random_state=2024)
 
  
random_state라는 속성 : 매개변수는 모델의 난수 발생 시드(seed)를 설정

 

 

  • 학습시키기
 
 
        dt.fit(X_train, y_train)
 
 

 

  • X_test에 대한 예측 값을 계산, pred1 변수에 저장
  
 
      pred1= dt.predict(X_test)
      pred1
 
 
array([ 78., 426.,  74., ..., 265., 219., 333.])

 

 

  • 그래프로 표현하기
 
 
        sns.scatterplot(x=y_test, y=pred1)
 
 

 

 

  • 다른 모델과 비교하기
    - mean_squared_error 함수 :
    Scikit-Learn 라이브러리에서 제공하는 성능 평가 함수 중 하나
    - mean_squared_error(y_test, pred1, squared=False) : MSE를 계산
    - squared=False 매개변수
    :  MSE의 제곱근 -  RMSE(Root Mean Squared Error)를 계산
  
 
         from sklearn.metrics import mean_squared_error
 
        mean_squared_error(y_test, pred1, squared=False)
 
  

226.94416226486078

 

 

 

3. 선형 회귀 vs 의사결정나무

  

 

  • LinearRegression 클래스
    선형 회귀 모델을 생성, 모델을 학습
  
 
      from sklearn.linear_model import LinearRegression
 
      lr = LinearRegression()
  
      lr.fit(X_train, y_train)
 
 

 

 

  • 예측 값을 계산하고, 실제 값과 예측 값을 산점도로 시각화
 
 
      pred2 = lr.predict(X_test)
 
 
       sns.scatterplot(x=y_test, y=pred2)
 
 

 

 

  • 테스트 데이터의 실제 값(y_test)과 모델의 예측 값(pred2) 사이의 평균 제곱 오차(MSE)를 계산
    squared=False 옵션을 사용하여 루트 평균 제곱 오차(RMSE)를 반환
  
 
        mean_squared_error(y_test, pred2, squared=False)
 
 
229.00179574454936

 

 

  • 모델 성능비교
    : RMSE 값이 작을수록 모델의 예측이 실제 값에 가깝다는 것을 의미
    : 의사결정나무 모델선형 회귀 모델보다 예측 성능이 더 좋다
 
 
        # 의사결정나무 : 210.74186203651976
        # 선형 회귀 : 221.1987722244733
        210.74186203651976 - 221.1987722244733
 
-10.456910187953525

 

 

  •  하이퍼 파라미터 
  
      # 하이퍼 파라미터 적용
      dfr = DecisionTreeRegressor(random_state=2024 , max_depth=50, min_samples_leaf=30)
 
오버피팅 일어나지 않게 깊이를 50까지 설정
레벨도 30개 미만으로 되면 디테일하게 이해하지 말고 멈춤

 

 

  • 다시 학습시키기
 
 
      dtr.fit(X_train, y_train)
 
  

 

  • 실제 값(y_test) 의사 결정 트리 모델예측한 값(pred3) 사이의 루트 평균 제곱 오차(RMSE)를 계산
  
 
      pred3 = dt.predict(X_test)
 
      mean_squared_error(y_test, pred3, squared=False)
  
 
181.29247853838177 

 

 

  • 성능비교
    : 파라미터 튜닝이 가장 성능이 좋음


        # 의사 결정 나무 RMSE: 210.74186203651976
        # 선형 회귀 RMSE: 221.1987722244733
        # 의사 결정 나무 파라미터 튜닝 RMSE: 181.29247853838177
        181.29247853838177 - 210.74186203651976
 
 
 
-29.44938349813799

 

 

  • 의사결정나무 시각화하기(1)
    : 의사 결정 트리 모델(dt)의 구조를 시각화
  
 
      from sklearn.tree import plot_tree
 
      plt.figure(figsize=(24, 12))
      plot_tree(dt, max_depth=5, fontsize=12)
      plt.show()
 
 

 

  • 의사결정나무 시각화하기(2)
    : 의사 결정 트리 모델(dtr)의 구조를 시각화
  
 
        plt.figure(figsize=(24, 12))
        plot_tree(dtr, max_depth=5, fontsize=10, feature_names=X_train.columns)
        plt.show()