Monitoring Telemetry Streams for Unusual Activity
Listen to the summary
Uses a voice available on your device
Audio options
On this page 5 sections
Related concepts 1 concepts
Key Takeaways
- TRACE-C provides an auditable, rank-calibrated method for detecting anomalies in multi-stream telemetry data.
- The detector successfully ranked Storm Atiyah as the top anomaly among 2,208 windows in a 2019 test set.
- The method uses a multi-channel approach where specific anomaly signals are attributed to either local telemetry changes or copula-form relational patterns.
- In a 2020 frozen hold-out of 4,392 windows, the system generated zero alerts, demonstrating behavior consistent with record-rule saturation.
Summary & Methodology Analysis
TRACE-C functions as a strictly-prior rank-calibrated detector designed for aligned multi-stream telemetry, such as the public electricity-system data provided by the National Energy System Operator. The architecture operates by calculating three distinct channels: a local channel, a relational channel, and a temporal channel. The relational channel employs an algebraic form resembling a Gaussian copula, which is a statistical method used to model dependencies between variables. Notably, this component is not a literal copula density as it omits probability-integral or normal-score transforms. These channels are then aggregated using Fisher aggregation before the system calculates an outer rank against historical aggregate scores.
Interactive System Flowchart
Illustrative Implementation
A short sketch of the paper's core idea, not the authors' own code.
# Illustrative sketch (not from the paper)
import torch, math
def partition(timestamps, data, window_hours=2):
# split into non‑overlapping windows of length window_hours
step = int(window_hours * 3600) # assume seconds
windows = []
for i in range(0, len(timestamps), step):
windows.append(data[i:i+step])
return windows
def robust_z(window, regime_idx, recent=40):
# compute residuals using the 40 most recent same‑regime values
# placeholder: median‑centered, scaled by MAD
recent_vals = window[-recent:]
median = recent_vals.median(dim=0).values
mad = (recent_vals - median).abs().median(dim=0).values
return (window - median) / (mad + 1e-6)
def local_channel(window):
# maximum normalized window sum
return window.sum(dim=0).max() / (window.numel() ** 0.5)
def relational_channel(residuals):
# Gaussian‑copula‑form log‑density contrast (illustrative)
cov = torch.cov(residuals.T)
inv_cov = torch.inverse(cov + torch.eye(cov.size(0))*1e-6)
quad = (residuals @ inv_cov * residuals).sum(dim=1)
return -0.5 * quad.mean()
def temporal_channel(window):
# worst standardized AR(1) innovation
phi = 0.9 # placeholder AR coefficient
innovations = window[1:] - phi * window[:-1]
std = innovations.std(dim=0)
worst = (innovations / (std + 1e-6)).abs().max()
return worst
def fisher_aggregate(ranks):
# combine channel ranks via Fisher's method
chi2 = -2 * torch.log(ranks.float() / ranks.size(0))
return chi2.sum()
def outer_rank(score, history):
# rank score against strictly‑prior aggregate scores
all_scores = torch.tensor(history + [score])
return (all_scores < score).sum().item() + 1
def select_windows(outer_ranks, budget):
# BH attempt; fallback to record rule if none selected
m = len(outer_ranks)
q = 0.05
thresholds = torch.arange(1, m+1) * q / m
selected = [i for i, (r, t) in enumerate(zip(torch.tensor(outer_ranks).float(), thresholds)) if r <= t]
if not selected:
# record rule: keep top‑budget ranks
selected = sorted(range(m), key=lambda i: outer_ranks[i])[:budget]
return selected
# Example workflow (placeholder data)
timestamps = torch.arange(0, 10000)
telemetry = torch.randn(10000, 5)
windows = partition(timestamps, telemetry)
history = []
outer_scores = []
for w in windows:
res = robust_z(w, regime_idx=0)
ch_local = local_channel(w)
ch_rel = relational_channel(res)
ch_temp = temporal_channel(w)
# rank channels (smaller rank = more extreme)
channel_vals = torch.tensor([ch_local, ch_rel, ch_temp])
ranks = channel_vals.argsort().float() + 1
agg = fisher_aggregate(ranks)
outer = outer_rank(agg, history)
history.append(agg)
outer_scores.append(outer)
selected = select_windows(outer_scores, budget=10)
// Illustrative sketch (not from the paper)
const torch = require('torch-js'); // placeholder for tensor ops
function partition(timestamps, data, windowHours = 2) {
const step = Math.round(windowHours * 3600); // seconds per window
const windows = [];
for (let i = 0; i < timestamps.length; i += step) {
windows.push(data.slice(i, i + step));
}
return windows;
}
function robustZ(window, recent = 40) {
// median‑centered, scaled by MAD using recent same‑regime values
const recentVals = window.slice(-recent);
const median = recentVals.reduce((a, b) => a.map((v, i) => v + b[i] / recent), Array(recentVals[0].length).fill(0));
const mad = recentVals.map(row => row.map((v, i) => Math.abs(v - median[i])))
.reduce((a, b) => a.map((v, i) => Math.max(v, b[i])), Array(recentVals[0].length).fill(0));
return window.map(row => row.map((v, i) => (v - median[i]) / (mad[i] + 1e-6)));
}
function localChannel(window) {
// max normalized window sum
const sums = window[0].map((_, i) => window.reduce((s, row) => s + row[i], 0));
const maxSum = Math.max(...sums);
return maxSum / Math.sqrt(window.length * window[0].length);
}
function relationalChannel(residuals) {
// Gaussian‑copula‑form log‑density contrast (illustrative)
// compute covariance matrix
const cov = torch.cov(torch.tensor(residuals));
const invCov = torch.inverse(cov.add(torch.eye(cov.shape[0]).mul(1e-6)));
const quad = residuals.map(r => torch.dot(torch.tensor(r), torch.mv(invCov, torch.tensor(r))));
return -0.5 * (quad.reduce((a, b) => a + b, 0) / residuals.length);
}
function temporalChannel(window) {
// worst standardized AR(1) innovation
const phi = 0.9; // placeholder
const innovations = [];
for (let t = 1; t < window.length; t++) {
innovations.push(window[t].map((v, i) => v - phi * window[t - 1][i]));
}
const std = innovations[0].map((_, i) => {
const col = innovations.map(row => row[i]);
const mean = col.reduce((a, b) => a + b, 0) / col.length;
return Math.sqrt(col.reduce((s, x) => s + (x - mean) ** 2, 0) / col.length);
});
const worst = innovations.flatMap(row => row.map((v, i) => Math.abs(v) / (std[i] + 1e-6))).reduce((a, b) => Math.max(a, b), 0);
return worst;
}
function fisherAggregate(ranks) {
// Fisher's method on channel ranks
const chi2 = ranks.map(r => -2 * Math.log(r / ranks.length));
return chi2.reduce((a, b) => a + b, 0);
}
function outerRank(score, history) {
// rank against strictly‑prior scores
const all = history.concat([score]);
return all.filter(s => s < score).length + 1;
}
function selectWindows(outerRanks, budget) {
const m = outerRanks.length;
const q = 0.05;
const thresholds = outerRanks.map((_, i) => ((i + 1) * q) / m);
let selected = outerRanks.map((r, i) => (r <= thresholds[i] ? i : -1)).filter(i => i >= 0);
if (selected.length === 0) {
// fallback record rule: keep top‑budget ranks
selected = outerRanks
.map((r, i) => ({ r, i }))
.sort((a, b) => a.r - b.r)
.slice(0, budget)
.map(obj => obj.i);
}
return selected;
}
// Example workflow (placeholder data)
const timestamps = Array.from({ length: 10000 }, (_, i) => i);
const telemetry = Array.from({ length: 10000 }, () => Array.from({ length: 5 }, () => Math.random()));
const windows = partition(timestamps, telemetry);
const history = [];
const outerScores = [];
for (const w of windows) {
const res = robustZ(w);
const chLocal = localChannel(w);
const chRel = relationalChannel(res);
const chTemp = temporalChannel(w);
const channelVals = [chLocal, chRel, chTemp];
const ranks = channelVals
.map((v, i) => ({ v, i }))
.sort((a, b) => a.v - b.v)
.map((obj, rank) => rank + 1);
const agg = fisherAggregate(ranks);
const outer = outerRank(agg, history);
history.push(agg);
outerScores.push(outer);
}
const selected = selectWindows(outerScores, 10);
Cross-Examination & FAQs
A deeper dive clarifying mechanics, constraints, and baseline evaluations.
Q1. What is the primary goal of TRACE-C?
TRACE-C is designed as an auditable, strictly-prior rank-calibrated detector for identifying anomalies within multi-stream operational telemetry.
Q2. What kind of data was used to test this detector?
The authors used public electricity-system telemetry from the National Energy System Operator.
Q3. Did the system flag many anomalies during the 2020 testing period?
No, in the 4,392 windows of the 2020 frozen hold-out, the system selected zero alerts.
Q4. How does the relational channel differ from a standard Gaussian copula?
The relational channel uses an algebraic form that resembles a Gaussian copula but does not function as a literal copula density, as it does not apply probability-integral or normal-score transforms.
Q5. Was the high rank for Storm Atiyah driven by the relational channel?
No, an ablation study revealed that the copula-form channel only ranked Storm Atiyah 59th, while the local channel was responsible for the top ranking.
Q6. What are the core limitations regarding the statistical validity of the ranks?
The rank-based p-values rely on the assumption of exchangeability, which is not established for the telemetry data, and the empirical rank counts serve as diagnostics rather than formal proofs of coverage or false-discovery control.
Q7. How were the 2019 and 2020 datasets sized?
The 2019 development segment contained 2,208 windows, and the 2020 frozen hold-out segment contained 4,392 windows.
Q8. Does the paper provide proof of false-discovery control?
No, the paper explicitly states that empirical rank counts are diagnostics and not proofs of coverage or false-discovery control.
Q9. How is the system's performance interpreted during years with few anomalies?
The zero alerts in 2020 are consistent with record-rule saturation rather than an uneventful year, and the highest-ranked window in that set was later interpreted as Storm Ellen.