Count rejected samples and poll outcomes; pin rollback contract
Status showed accepted sample counts but not why learning was slow. Each room's model now counts samples it rejected (outliers, implausible inputs), persisted as an optional rejected_count key and shown as models.<room>.rejected_samples. Status also counts every poll outcome since start (sample_results_since_start). Add tests that a model in the exact shape 0.2.1 wrote restores with its coefficients, and that today's saved model still has the keys and types 0.2.1's restore code reads, so rolling back keeps the learned model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ace6d0061b
commit
43915ba810
4 files changed
+69
-14
No files matched your search
@@ -90,6 +90,8 @@ class Learner:
|
||||
error_sum: float = 0.0
|
||||
on_count: int = 0
|
||||
off_count: int = 0
|
||||
# Samples offered but not fitted (outliers, implausible inputs).
|
||||
rejected_count: int = 0
|
||||
|
||||
def predict_delta(self, temp: float, outside: float, heat: float) -> float:
|
||||
a, b, c = self.theta
|
||||
@@ -97,15 +99,13 @@ class Learner:
|
||||
|
||||
def learn(self, temp_before: float, temp_after: float, outside: float, effective_heat: float,
|
||||
duration_seconds: float = 300, command: bool | None = None) -> bool:
|
||||
if not all(isfinite(x) for x in (temp_before, temp_after, outside, effective_heat)):
|
||||
return False
|
||||
if not 0 <= effective_heat <= 1:
|
||||
return False
|
||||
if not isfinite(duration_seconds) or not 270 <= duration_seconds <= 330:
|
||||
return False
|
||||
if (not all(isfinite(x) for x in (temp_before, temp_after, outside, effective_heat))
|
||||
or not 0 <= effective_heat <= 1
|
||||
or not isfinite(duration_seconds) or not 270 <= duration_seconds <= 330):
|
||||
return self._reject()
|
||||
y = (temp_after - temp_before) * 300 / duration_seconds
|
||||
if abs(y) > 0.8: # sensor glitch/door opened/restarted device
|
||||
return False
|
||||
return self._reject()
|
||||
x = [outside - temp_before, effective_heat, 1.0]
|
||||
predicted = sum(t * v for t, v in zip(self.theta, x))
|
||||
residual = y - predicted
|
||||
@@ -113,7 +113,7 @@ class Learner:
|
||||
forgetting = 0.999
|
||||
denominator = forgetting + sum(x[i] * px[i] for i in range(3))
|
||||
if not isfinite(denominator) or denominator <= 0:
|
||||
return False
|
||||
return self._reject()
|
||||
k = [value / denominator for value in px]
|
||||
self.theta = [t + k[i] * residual for i, t in enumerate(self.theta)]
|
||||
for i in range(3):
|
||||
@@ -135,13 +135,18 @@ class Learner:
|
||||
self.off_count += 1
|
||||
return True
|
||||
|
||||
def _reject(self) -> bool:
|
||||
self.rejected_count += 1
|
||||
return False
|
||||
|
||||
@property
|
||||
def mae(self) -> float | None:
|
||||
return self.error_sum / self.count if self.count else None
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {"theta": self.theta, "p": self.p, "count": self.count, "error_sum": self.error_sum,
|
||||
"on_count": self.on_count, "off_count": self.off_count}
|
||||
"on_count": self.on_count, "off_count": self.off_count,
|
||||
"rejected_count": self.rejected_count}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, source: dict[str, Any]) -> "Learner":
|
||||
@@ -175,14 +180,15 @@ class Learner:
|
||||
lower[i][j] = value ** .5
|
||||
else:
|
||||
lower[i][j] = value / lower[j][j]
|
||||
counts = [source.get(key, 0) for key in ("count", "on_count", "off_count")]
|
||||
counts = [source.get(key, 0)
|
||||
for key in ("count", "on_count", "off_count", "rejected_count")]
|
||||
if any(type(v) is not int or v < 0 for v in counts):
|
||||
return obj
|
||||
count, on_count, off_count = counts
|
||||
count, on_count, off_count, rejected_count = counts
|
||||
error_sum = float(source.get("error_sum", 0))
|
||||
if not isfinite(error_sum) or error_sum < 0 or on_count + off_count > count:
|
||||
return obj
|
||||
return cls(theta, p, count, error_sum, on_count, off_count)
|
||||
return cls(theta, p, count, error_sum, on_count, off_count, rejected_count)
|
||||
except (KeyError, TypeError, ValueError, OverflowError):
|
||||
return obj
|
||||
|
||||
|
||||
Reference in new issue
Block a user