ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • yaw_rate_model - ai를 이용한 모델 생성과 활용
    design 2024. 11. 7. 12:52

    시작하기 전에

     

    "바퀴 속도에서 요-레이트를 계산하기"라는 소재로 TSMaster의 미니프로그램 사용법을 설명하고 있다. 설명했던 것들을 나열하면 아래와 같다.

    yaw_rate_ws_filt는 yaw_rate_ws 보다 매끄러우나 차에 장착되어 있는 요-레이트 센서가 측정한 신호인 YAW_RATE에 비해 거칠다. YAW_RATE 만큼 매끄러우면 좋겠다. 필터의 파라미터를 조절하여 더 매끄럽게 만들 수 있을 것으로 기대한다. 하지만 신호 변화로 뒤처짐(lag)이 생길 것 같다. ai가 작성한 코드를 자주 쓰다보니 이런 생각이 들었다.

     

    CAN에 있는 바퀴 속도, 조향각, 횡가속도, 종가속도를 입력 신호들로 하고 YAW_RATE를 목표 신호로 하여 모델을 만들고, 미니프로그램에서 모델을 실행하여 요-레이트(yaw_rate_model)를 계산하면, yaw_rate_filt 보다 더 매끈한 요-레이트를 얻을 수 있지 않을까?

     

    나 스스로는 모델을 만들 능력이 전혀 없다. 하지만 ai를 이용하면 해낼 수 있을 것 같았다. 해보았다. 결과부터 이야기하자면, ai 덕택에 쉽게 모델을 만들 수 있었고, ai 덕택에 쉽게 미니프로그램에서 모델을 이용하여 yaw_rate_model을 계산할 수 있었다. 하지만 yaw_rate_model이 yaw_rate_filt에 비해서 더 매끄럽지는 않다. 이는 모델 생성이나 이용의 오류가 아니라 CAN에 올라와 있는 바퀴 속도 해상도가 낮다는 데 기인한 한계라고 생각한다.

     

    이번 블로그에서는 위 과정을 설명한다.

     

     

    개요

    • 데이터 준비
    • 모델 생성
    • 모델 실행: yaw_rate_model 계산
    • yaw_rate_ws_filt와 yaw_rate_model 비교

    모델을 생성하고 실행하기까지의 과정

     

     

    데이터 준비

     

     

    모델 생성

    • 아래와 같이 대화하여 ai가 모델을 생성하는 코드를 작성하도록 했다. 

     

    • 코드는 아래와 같다.
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_squared_error, r2_score
    from sklearn.ensemble import RandomForestRegressor
    
    class YawRateModel:
        def __init__(self):
            self.model = None
        
        def preprocess_data(self, data):
            """
            데이터 전처리 및 특성 엔지니어링
            """
            # 뒷 바퀴 속도 차이로부터 초기 yaw_rate_ws 계산
            data['yaw_rate_ws'] = self._calculate_yaw_rate_ws(data)
            
            # 특성 선택
            features = ['steering_angle', 'rear_wheel_speed_left', 
                        'rear_wheel_speed_right', 'lateral_accel', 
                        'longitudinal_accel', 'yaw_rate_ws']
            
            X = data[features]
            y = data['YAW_RATE']
            
            return X, y
        
        def _calculate_yaw_rate_ws(self, data):
            """
            뒷 바퀴 속도 차이로부터 요레이트 계산
            """
            wheel_base = 2.7  # 예시 값, 실제 차량 제원에 맞게 조정 필요
            
            yaw_rate_ws = (data['rear_wheel_speed_right'] - data['rear_wheel_speed_left']) / wheel_base
            return yaw_rate_ws
        
        def train_model(self, X, y):
            """
            랜덤 포레스트 회귀 모델 학습
            """
            X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
            
            self.model = RandomForestRegressor(n_estimators=100, random_state=42)
            self.model.fit(X_train, y_train)
            
            # 모델 성능 평가
            y_pred = self.model.predict(X_test)
            mse = mean_squared_error(y_test, y_pred)
            r2 = r2_score(y_test, y_pred)
            
            print(f"Mean Squared Error: {mse}")
            print(f"R² Score: {r2}")
            
            return X_test, y_test, y_pred
        
        def plot_results(self, y_test, y_pred):
            """
            예측값과 실제값 비교 플롯
            """
            plt.figure(figsize=(10, 6))
            plt.scatter(y_test, y_pred, alpha=0.5)
            plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
            plt.xlabel('Actual Yaw Rate')
            plt.ylabel('Predicted Yaw Rate')
            plt.title('Yaw Rate: Actual vs Predicted')
            plt.show()
        
        def feature_importance(self):
            """
            모델의 특성 중요도 분석
            """
            feature_imp = pd.DataFrame({
                'feature': self.model.feature_names_in_,
                'importance': self.model.feature_importances_
            }).sort_values('importance', ascending=False)
            
            plt.figure(figsize=(10, 6))
            feature_imp.plot(x='feature', y='importance', kind='bar')
            plt.title('Feature Importance in Yaw Rate Prediction')
            plt.tight_layout()
            plt.show()
    
    # 사용 예시 (실제 데이터로 대체 필요)
    def main():
        # 샘플 데이터 생성 (실제 데이터로 대체 필요)
        np.random.seed(42)
        data = pd.DataFrame({
            'steering_angle': np.random.uniform(0, 10, 1000),
            'rear_wheel_speed_left': np.random.uniform(0, 100, 1000),
            'rear_wheel_speed_right': np.random.uniform(0, 100, 1000),
            'lateral_accel': np.random.uniform(-5, 5, 1000),
            'longitudinal_accel': np.random.uniform(-5, 5, 1000),
            'YAW_RATE': np.random.uniform(-2, 2, 1000)
        })
        
        yaw_rate_model = YawRateModel()
        X, y = yaw_rate_model.preprocess_data(data)
        X_test, y_test, y_pred = yaw_rate_model.train_model(X, y)
        
        yaw_rate_model.plot_results(y_test, y_pred)
        yaw_rate_model.feature_importance()
    
    if __name__ == "__main__":
        main()
    • 코드의 main() 부분에 입력 데이터를 랜덤하게 생성한다. 이 부분을 파일에서 데이터를 읽어오도록 변경하였다.  
        # hsl: 샘플 데이터 생성 부분은 주석 처리하고 feather 파일을 로딩하도록 수정함
        # 샘플 데이터 생성 (실제 데이터로 대체 필요)
        # np.random.seed(42)
        # data = pd.DataFrame({
        #     'sas': np.random.uniform(0, 10, 1000),
        #     'ws_rl': np.random.uniform(0, 100, 1000),
        #     'ws_rr': np.random.uniform(0, 100, 1000),
        #     'lat_accel': np.random.uniform(-5, 5, 1000),
        #     'long_accel': np.random.uniform(-5, 5, 1000),
        #     'yaw_rate': np.random.uniform(-2, 2, 1000)
        # })
        
        # hsl: 데이터 로딩
        data = pd.read_feather(r"C:\data\tosun\projects\20240725_venue\Logging\Bus\20240725_venue2024_07_26_09_17_29.feather")

     

    • 코드를 보면 데이터의 일부로 학습하여 모델을 만들고 나머지로 모델의 예측 정확도를 검사한다.
      • 나는 그 과정이 어떻게 수행되는 지에 대해 전혀 모른다.
      • 나는 사실 Random Forester라는 방식이 있다는 정도는 알지만, 이 방식은 무엇이며, 이 경우에 적당한 방식인지도 모른다.
      • 내 목적은 ai와 TSMaster를 접목해서 사용하는 방식을 설명하는 것이다.
    • 실행 결과는 수치와 그래프로 표시된다.
      • 에러는 작은 것 같고, 정확도는 높다. 
        • Mean Squared Error: 0.08810816943008343
        • R² Score: 0.9988125799483951

    모델의 예측한 yaw_rate_model과 센서가 측정한 YAW_RATE의 관계 그래프이다.

     

    입력 값들이 yaw_rate_model에 끼치는 영향을 표시한 그래프이다. 뒷바퀴 속도 차이로 계산한 요-레이트인 yaw_rate_ws가 주로 영향을 끼친다. 조향각인 sas의 영향도 무시할 수 없다.

     

     

    모델 실행

    • 위와 같이 생성한 모델을 미니프로그램에서 실시간으로 yaw_rate_model을 계산하는데 사용하고자 한다. 순서는 아래와 같다.
      • 모델을 파일로 저장한다.
      • 미니프로그램에서 파일을 로딩한다.
      • WHL_SPD11 메시지를 수신할 때마다 yaw_rate_model을 계산한다.
    • 모델을 파일로 저장하고, 미니프로그램에서 로딩하는 방법을 ai에게 문의하였다.

    생성된 모델을 파일로 저장하고, 파일로 저장된 모델을 로드하여 사용하는 방법을 ai에게 문의하였다.

    • 코드는 아래와 같다.
    import joblib
    import numpy as np
    import pandas as pd
    
    class YawRateModelIO:
        @staticmethod
        def save_model(model, filename='yaw_rate_model.joblib'):
            """
            학습된 모델을 파일로 저장
            
            Parameters:
            -----------
            model : sklearn 모델 
                저장할 학습된 모델
            filename : str, optional (default='yaw_rate_model.joblib')
                저장할 파일 이름
            """
            try:
                joblib.dump(model, filename)
                print(f"모델이 성공적으로 {filename}에 저장되었습니다.")
            except Exception as e:
                print(f"모델 저장 중 오류 발생: {e}")
        
        @staticmethod
        def load_model(filename='yaw_rate_model.joblib'):
            """
            저장된 모델 로드
            
            Parameters:
            -----------
            filename : str, optional (default='yaw_rate_model.joblib')
                불러올 모델 파일 이름
            
            Returns:
            --------
            model : sklearn 모델
                로드된 모델
            """
            try:
                model = joblib.load(filename)
                print(f"{filename}에서 모델을 성공적으로 로드했습니다.")
                return model
            except Exception as e:
                print(f"모델 로드 중 오류 발생: {e}")
                return None
        
        @staticmethod
        def predict_yaw_rate(model, input_data):
            """
            로드된 모델로 요레이트 예측
            
            Parameters:
            -----------
            model : sklearn 모델
                로드된 머신러닝 모델
            input_data : pandas DataFrame or numpy array
                예측에 사용할 입력 데이터
            
            Returns:
            --------
            predictions : numpy array
                예측된 요레이트 값들
            """
            try:
                # 입력 데이터 형식 확인 및 변환
                if isinstance(input_data, pd.DataFrame):
                    input_array = input_data.values
                elif isinstance(input_data, np.ndarray):
                    input_array = input_data
                else:
                    raise ValueError("입력 데이터는 pandas DataFrame 또는 numpy array여야 합니다.")
                
                # 모델 예측
                predictions = model.predict(input_array)
                return predictions
            except Exception as e:
                print(f"예측 중 오류 발생: {e}")
                return None
    
    # 사용 예시
    def main():
        # 모델 저장 예시
        from sklearn.ensemble import RandomForestRegressor
        
        # 랜덤 샘플 데이터 생성 (실제 데이터로 대체)
        X = np.random.rand(100, 6)  # 6개의 특성
        y = np.random.rand(100)
        
        # 모델 학습
        model = RandomForestRegressor(n_estimators=100, random_state=42)
        model.fit(X, y)
        
        # 모델 저장
        YawRateModelIO.save_model(model)
        
        # 모델 로드
        loaded_model = YawRateModelIO.load_model()
        
        # 새로운 데이터로 예측
        new_data = np.random.rand(10, 6)
        predictions = YawRateModelIO.predict_yaw_rate(loaded_model, new_data)
        print("예측된 요레이트:", predictions)
    
    if __name__ == "__main__":
        main()
    • ai가 작성한 YawRateModelIO라는 클래스 코드를 이용하면,
      • YawRateModelIO.save_model(model) 이라는 한 줄의 코드로 모델을 파일로 저장할 수 있다.
      • loaded_model = YawRateModelIO.load_model() 이라는 한 줄의 코드로 모델을 파일에서 로드할 수 있다.
      • predictions = YawRateModelIO.predict_yaw_rate(loaded_model, 입력 데이터) 라는 한 줄의 코드로 '입력 데이터'를 이용하여 yaw_rate_model 값을 계산할 수 있다.
    • 위 방법을 확인하였으니 미니프로그램에 적용한다. yaw_rate_ws_filt 대비 변경한 부분은 아래와 같다.
    • 모델을 로드하기 위해 필요한 joblib 모듈을 임포트한다. 
    • YawRateModelIO 클래스를 추가한다.
    • 조향각 신호는 SAS11_1 메시지에 있다. 이 신호를 이용하기 위한 글로벌 변수 SAS11_1을 만든다.
      • SAS11_1 = dbs.TSAS11_1()
      • SAS11 메시지를 수신할 때마다 SAS11_1을 업데이트 하도록 한다.

    • 횡가속도, 종가속도 신호는 ESP12_1 메시지에 있다. 이 신호를 이용하기 위한 글로벌 변수 ESP12_1을 만든다.
      • ESP12_1 = dbs.TESP12_1()
      • ESP12 메시지를 수신할 때마다 ESP12_1을 업데이트 하도록 한다.

    • WHL_SPD11 메시지를 수신할 때마다, yaw_rate_ws, yaw_rate_ws_filt, yaw_rate_model을 계산하도록 한다.

    WHL_SPD11 메시지를 수신할 때마다 yaw_rate_model을 계산하도록 한다. calc_yaw_rate_ws_filt_model()을 실행하도록 하였다.

    def calc_yaw_rate_ws_filt_model(ACAN: RawCAN) -> None:
        '''
        WHL_SPD11 메시지의 신호들을 이용하여 yaw_rate_ws를 계산함
        yaw_rate_ws를 칼만 필터로 필터링함
        yaw_rate_model을 계산함
        '''
    
        global kalman_filter
        global model_yaw_rate
    
        ## yaw_rate_ws 계산
        # 메시지의 바이트 데이터들을 신호들로 분리하기 위함
        WHL_SPD11_1.FRawCAN = ACAN
    
        # yaw_rate_ws를 계산한다.
        whl_spd_rr_minus_rl = WHL_SPD11_1.WHL_SPD_RR - WHL_SPD11_1.WHL_SPD_RL
    
        # 베뉴의 rear track은1.555m이다. 
        # wheel_spd_rr_minus_rl를 rear track으로 나눠서 yaw rate를 구한다.
        # wheel speed는 kph이다. mps로 변환하기위해 1000 / 36000을 곱한다.
        # radian을 deg로 변환하기 위해 180 / pi를 곱한다.
        # 위 연산을 하는 것은 whl_spd_rr_minus_rl에 10.27을 곱하는 것과 같다.      
        yaw_rate_ws = whl_spd_rr_minus_rl * 10.27  
    
    
    
        ## yaw_rate_ws를 필터링하여 yaw_rate_ws_filt를 계산
    
        # 필터 파라미터 업데이트  
        # 사용자가 필터 파라미터들을 변경했을 수 있다.
        # 파라미터 변경 여부를 확인하고, 변경된 경우만 업데이트하려고 했는데
        # 무조건 업데이트 하는 것이 더 빠를 것 같다. 
    
        # 판넬의 위젯에 연결된 시스템 변수를 읽음
        process_noise = app.get_system_var_value("calc.process_noise")[1]    
        measurement_noise = app.get_system_var_value("calc.measurement_noise")[1]   
    
        # 필터 파리미터들을 업데이트함
        kalman_filter.update_filter_parameters(process_noise, measurement_noise)
        
        # yaw_rate_ws를 필터하여yaw_rate_ws_filt를 계산 
        yaw_rate_ws_filt = kalman_filter.update(yaw_rate_ws)
    
    
        ## yaw_rate_model 계산
        # 모델 입력을 데이터프레임으로 만듬
        df_input = pd.DataFrame(
            {
                'sas': [SAS11_1.SAS_Angle], 
                'ws_rl': [WHL_SPD11_1.WHL_SPD_RL], 
                'ws_rr': [WHL_SPD11_1.WHL_SPD_RR],
                'lat_accel': [ESP12_1.LAT_ACCEL], 
                'long_accel': [ESP12_1.LONG_ACCEL], 
                'yaw_rate_ws': [yaw_rate_ws],
            }
        )
             
        # yaw_rate_model을 계산함      
        yaw_rate_model = model_yaw_rate.predict(df_input)
        
    
        ## 그래프와 판넬 출력을 위한 시슽템 변수 업데이트
        # 시스템 변수 calc.yaw_rate_ws에위에서 계산한 yaw_rate_ws를 넣는다.
        app.set_system_var_double("calc.yaw_rate_ws", yaw_rate_ws)
    
        # 시스템 변수 calc.yaw_rate_ws_filt에 위에서 계산한 yaw_rate_ws_filt를 넣는다.
        app.set_system_var_double("calc.yaw_rate_ws_filt", yaw_rate_ws_filt)
    
        # 시스템 변수 calc.yaw_rate_model에 위에서 계산한 yaw_rate_model를 넣는다.
        app.set_system_var_double("calc.yaw_rate_model", yaw_rate_ws_filt)
        
        return # calc_yaw_rate_ws_filt()
    • 전체 코드는 첨부와 같다.

    yaw_rate_model.py
    0.01MB

     

     

    yaw_rate_ws_filt와 yaw_rate_model의 비교

     

    뒷바퀴 속도에서 구한 yaw_rate_ws 신호, 그 신호에 칼만 필터를 적용한 yaw_rate_ws_filt, 뒷바퀴 속도, 조향각, 횡가속도, 종가속도를 입력으로 Random Forest 방식으로 생성한 모델로 계산한 yaw_rate_model을비교한다.

     

    • yaw_rate_model이 yaw_rate_ws_filt보다 매끄러울 것을 기대했는데 그렇지 않다. 두 신호 사이의 관계를 보기위해 판넬에 x축을 yaw_rate_ws_filt로 y축을 yaw_rate_model로 하는 관계도를 만들었다.
    • https://youtu.be/qC91CY7BQ3w

     

    • 판넬에 3개의 관계도가 있다.
    • 가운데는 YAW_RATE vs. yaw_rate_ws 관계도이다. yaw_rate_ws가 거칠다. 결과적으로 중심선을 기준으로 산포가 넓다.
    • 왼쪽은 YAW_RATE vs. yaw_rate_ws_filt 관계도이다. 필터의 효과로 yaw_rate_ws_filt는 yaw_rate_ws 보다 덜 거칠다. 결과적으로 중심선을 기준으로 산포가 좁아졌다.
    • 오른쪽은 yaw_rate_ws_filt와 yaw_rate_model 관계도이다. 거의 직선으로 보인다. yaw_rate_ws_filt가 거친 부분에서 yaw_rate_model도 거칠다.

     

    결론

    • 나는 Random Forest라는 이름만 들어본 정도다. 랜덤 포리스트가 데이터에서 모델을 만드는 방법이라는 것 이상으로 아는 것이 전혀 없다. 사실 이 방법이 위에 설명한 경우에 적합한지 아닌지 조차 모른다.
    • 이런 수준의 사람도 ai의 도움으로 데이터에서 모델을 생성하고, 모델을 이용하여 CAN 신호를 대상으로 실시간 계산을 하는 것이 별로 어렵지 않았다.
    • ai의 활용은 더 증가할 것으로 예상한다. 개인적으로는 크게 증가할 것 같다.
    • TSMaster를 개발할 때 누군가 파이썬을 지원하게 결정했을 것이다. 이 결정은 큰 행운이라고 생각한다.