-
yaw_rate_model - ai를 이용한 모델 생성과 활용design 2024. 11. 7. 12:52
시작하기 전에
"바퀴 속도에서 요-레이트를 계산하기"라는 소재로 TSMaster의 미니프로그램 사용법을 설명하고 있다. 설명했던 것들을 나열하면 아래와 같다.
- 차에서 CAN 데이터 측정하는 방법
- 측정한 데이터를 그래프로 보는 방법
- 바퀴 속도에서 실시간으로 요-레이트를 계산하는 방법
- CAN 신호들로 실시간 연산하기 - 미니프로그램으로 yaw_rate_ws 계산 :: hsl's tsmaster 사용기
- 이렇게 계산된 요-레이트를 yaw_rate_ws라고 불렀다.
- yaw_rate_ws가 거칠어서 칼만 필터를 적용해보았다
- CAN 신호를 실시간으로 필터링 하기 - 미니프로그램과 ai :: hsl's tsmaster 사용기
- 칼만 필터 코드는 ai가 작성한 코드를 사용하였다.
- 필터된 요-레이트를 yaw_rate_ws_filt라고 불렀다.
- yaw_raw_ws를 FFT (Fast Fourier Transformation)도 해보았다.
- ai가 작성한 FFT 계산 코드를 사용하였다.
- 일반적으로 FFT 결과는 주파수를 수평축으로 진폭을 수직축으로 하는 그래프에 표시한다. TSMaster는 그렇게 표시할 수 있는 기능이 없다. ai에게 요청하여 TSMaster에서 FFT 결과를 받아서 그래프로 표시하는 외부 프로그램을 만들었다. 실시간으로 FFT 하기 - 미니프로그램과 ai :: hsl's tsmaster 사용기.
- FFT 결과를 표시하는 외부 프로그램을 미(美)적 측면이나 실행 속도 측면에서 개선해보았다. 역시 ai가 작성한 코드이다. FFT 비주얼라이저 개선 - 미니프로그램과 ai :: hsl's 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을 계산하는데 사용하고자 한다. 순서는 아래와 같다.
- 모델을 파일로 저장한다.
- 미니프로그램에서 파일을 로딩한다.
- WHL_SPD11 메시지를 수신할 때마다 yaw_rate_model을 계산한다.
- 모델을 파일로 저장하고, 미니프로그램에서 로딩하는 방법을 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을 계산하도록 한다.
- calc_yaw_rate_ws_filt_model()의 코드는 아래와 같다.
- 코드 중간에 판넬에서 필터 파라미터들을 입력받는 부분이 있다. 이 부분의 상세는 판넬로 사용자 입력 받는 법 :: hsl's tsmaster 사용기에 설명하였다.
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_ws_filt와 yaw_rate_model의 비교
- 미니프로그램 실행 결과는 아래 비디오에 보이는 것과 같다. yaw_rate_ws_filt와 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를 개발할 때 누군가 파이썬을 지원하게 결정했을 것이다. 이 결정은 큰 행운이라고 생각한다.
'design' 카테고리의 다른 글
그래픽 프로그램(graphic program)으로 yaw_rate_ws 계산하기 (0) 2024.12.11 실시간으로 FFT 하기 - 미니프로그램과 ai (1) 2024.10.25