Jawaril99 commited on
Commit
318de84
·
1 Parent(s): 6c2e259

Improve RUL prediction with time-series features

Browse files
batteryswap_example/planners/best.pickle CHANGED
Binary files a/batteryswap_example/planners/best.pickle and b/batteryswap_example/planners/best.pickle differ
 
batteryswap_example/train.py CHANGED
@@ -8,7 +8,7 @@ import pathlib
8
  import os
9
 
10
 
11
- from sklearn.dummy import DummyRegressor
12
  import pandas as pd
13
  import numpy
14
  import numpy as np
@@ -80,18 +80,127 @@ class DummyRULModel(RULModel):
80
  self.use_total_elapsed_days = True
81
 
82
  def _compute_features(self, unit_df: pd.DataFrame) -> np.ndarray:
83
- unit_df = unit_df.sort_values(self.time_col)
 
 
84
 
85
  all_stats = {}
86
- # FIXME: actually compute some features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  feature_names = sorted(all_stats.keys())
89
- self._feature_names_ = feature_names # stored for inspection/debugging
90
- return np.array([all_stats[k] for k in feature_names], dtype=float)
 
 
 
 
91
 
92
  def _build_feature_matrix(self, timeseries: pd.DataFrame) -> tuple[np.ndarray, list]:
93
  rows, ids = [], []
94
- for unit_id, unit_df in timeseries.groupby(self.group_col):
 
 
 
95
  if len(unit_df) < 2:
96
  continue
97
  rows.append(self._compute_features(unit_df))
@@ -103,13 +212,21 @@ class DummyRULModel(RULModel):
103
  y = np.array([rul[unit_id] for unit_id in ids])
104
 
105
  # FIXME: actually use an estimator that learns
106
- self.model = DummyRegressor(strategy='median')
 
 
 
 
 
107
  self.model.fit(X, y)
108
  return self
109
 
110
  def predict(self, timeseries: pd.DataFrame) -> pd.DataFrame:
111
  rows, ids = [], []
112
- for unit_id, unit_df in timeseries.groupby(self.group_col):
 
 
 
113
  rows.append(self._compute_features(unit_df))
114
  ids.append(unit_id)
115
 
 
8
  import os
9
 
10
 
11
+ from sklearn.ensemble import ExtraTreesRegressor
12
  import pandas as pd
13
  import numpy
14
  import numpy as np
 
80
  self.use_total_elapsed_days = True
81
 
82
  def _compute_features(self, unit_df: pd.DataFrame) -> np.ndarray:
83
+ unit_df = unit_df.sort_index(level=self.time_col).copy()
84
+ if len(unit_df) == 0:
85
+ raise ValueError("Received empty battery time series")
86
 
87
  all_stats = {}
88
+
89
+ # Convert timestamps to elapsed days
90
+ times = pd.to_datetime(
91
+ unit_df.index.get_level_values(self.time_col)
92
+ )
93
+ elapsed_days = (
94
+ times - times[0]
95
+ ).total_seconds().to_numpy() / 86400.0
96
+
97
+
98
+ # General history features
99
+ all_stats["n_obs"] = float(len(unit_df))
100
+ all_stats["history_days"] = (
101
+ float(elapsed_days[-1]) if len(elapsed_days) > 1 else 0.0
102
+ )
103
+
104
+ # Features for voltage and temperature
105
+ for col in self.value_cols:
106
+ values = pd.to_numeric(
107
+ unit_df[col],
108
+ errors="coerce"
109
+ )
110
+
111
+ valid = values.notna()
112
+
113
+ if valid.sum() == 0:
114
+ all_stats[f"{col}_latest"] = 0.0
115
+ all_stats[f"{col}_mean"] = 0.0
116
+ all_stats[f"{col}_std"] = 0.0
117
+ all_stats[f"{col}_min"] = 0.0
118
+ all_stats[f"{col}_max"] = 0.0
119
+ all_stats[f"{col}_change"] = 0.0
120
+ all_stats[f"{col}_slope"] = 0.0
121
+ continue
122
+
123
+ x = values[valid].to_numpy(dtype=float)
124
+ t = elapsed_days[valid.to_numpy()]
125
+
126
+ all_stats[f"{col}_latest"] = float(x[-1])
127
+ all_stats[f"{col}_mean"] = float(np.mean(x))
128
+ all_stats[f"{col}_std"] = float(np.std(x))
129
+ all_stats[f"{col}_min"] = float(np.min(x))
130
+ all_stats[f"{col}_max"] = float(np.max(x))
131
+ all_stats[f"{col}_change"] = float(x[-1] - x[0])
132
+
133
+ # Recent-window features
134
+ for window_days in (7, 14, 30):
135
+ cutoff = t[-1] - window_days
136
+ recent_mask = t >= cutoff
137
+
138
+ recent_x = x[recent_mask]
139
+ recent_t = t[recent_mask]
140
+
141
+ prefix = f"{col}_{window_days}d"
142
+
143
+ if len(recent_x) > 0:
144
+ all_stats[f"{prefix}_mean"] = float(np.mean(recent_x))
145
+ all_stats[f"{prefix}_std"] = float(np.std(recent_x))
146
+ all_stats[f"{prefix}_min"] = float(np.min(recent_x))
147
+ all_stats[f"{prefix}_max"] = float(np.max(recent_x))
148
+ all_stats[f"{prefix}_change"] = float(
149
+ recent_x[-1] - recent_x[0]
150
+ )
151
+
152
+ if len(recent_x) >= 2 and np.ptp(recent_t) > 0:
153
+ recent_slope = np.polyfit(
154
+ recent_t,
155
+ recent_x,
156
+ 1
157
+ )[0]
158
+ else:
159
+ recent_slope = 0.0
160
+
161
+ all_stats[f"{prefix}_slope"] = float(recent_slope)
162
+ else:
163
+ all_stats[f"{prefix}_mean"] = 0.0
164
+ all_stats[f"{prefix}_std"] = 0.0
165
+ all_stats[f"{prefix}_min"] = 0.0
166
+ all_stats[f"{prefix}_max"] = 0.0
167
+ all_stats[f"{prefix}_change"] = 0.0
168
+ all_stats[f"{prefix}_slope"] = 0.0
169
+
170
+ # Recent level compared with overall level
171
+ if len(x) > 0:
172
+ recent_7_mask = t >= (t[-1] - 7)
173
+ recent_7 = x[recent_7_mask]
174
+
175
+ if len(recent_7) > 0:
176
+ all_stats[f"{col}_recent7_vs_mean"] = float(
177
+ np.mean(recent_7) - np.mean(x)
178
+ )
179
+ else:
180
+ all_stats[f"{col}_recent7_vs_mean"] = 0.0
181
+
182
+ # Trend per day
183
+ if len(x) >= 2 and np.ptp(t) > 0:
184
+ slope = np.polyfit(t, x, 1)[0]
185
+ else:
186
+ slope = 0.0
187
+
188
+ all_stats[f"{col}_slope"] = float(slope)
189
 
190
  feature_names = sorted(all_stats.keys())
191
+ self._feature_names_ = feature_names
192
+
193
+ return np.array(
194
+ [all_stats[k] for k in feature_names],
195
+ dtype=float,
196
+ )
197
 
198
  def _build_feature_matrix(self, timeseries: pd.DataFrame) -> tuple[np.ndarray, list]:
199
  rows, ids = [], []
200
+ for unit_id, unit_df in timeseries.groupby(
201
+ self.group_col,
202
+ observed=True
203
+ ):
204
  if len(unit_df) < 2:
205
  continue
206
  rows.append(self._compute_features(unit_df))
 
212
  y = np.array([rul[unit_id] for unit_id in ids])
213
 
214
  # FIXME: actually use an estimator that learns
215
+ self.model = ExtraTreesRegressor(
216
+ n_estimators=300,
217
+ min_samples_leaf=2,
218
+ random_state=42,
219
+ n_jobs=-1,
220
+ )
221
  self.model.fit(X, y)
222
  return self
223
 
224
  def predict(self, timeseries: pd.DataFrame) -> pd.DataFrame:
225
  rows, ids = [], []
226
+ for unit_id, unit_df in timeseries.groupby(
227
+ self.group_col,
228
+ observed=True
229
+ ):
230
  rows.append(self._compute_features(unit_df))
231
  ids.append(unit_id)
232