轻量级C++时序数据异常检测工具
采用分层解耦设计,体现了三个核心思想:
class TimeSeriesData {
std::vector<double> timestamps, values; // 连续内存,缓存友好
};
class AnomalyDetector {
static std::vector<bool> detect(...); // 无状态,线程安全
};
int main(){ /* 读取 -> 检测 -> 输出 */ }
核心思想:
一个数据点如果偏离近期历史数据太远,就是异常
数学公式:
均值:
标准差:
Z-score:
判定:
//(来自 AnomalyDetector.cpp)
for (size_t i = window_size; i < n; ++i) {
// 窗口范围: [i-window_size, i-1]
// 例如 i=50, window_size=20 → 分析第30-49个点,判断第50个点
double sum = 0.0, sum_sq = 0.0;
for (size_t j = i - window_size; j < i; ++j) {
sum += values[j]; // Σx
sum_sq += values[j] * values[j]; // Σx²
}
double mean = sum / window_size;
double variance = (sum_sq - sum * sum / window_size) / window_size;
double std_dev = sqrt(variance);
// 判断当前点 i 是否为异常
if (abs(values[i] - mean) > threshold * std_dev) {
anomalies[i] = true;
}
}
朴素实现:O(n × window_size) = 10万 × 50 = 500万次运算
优化潜力:使用增量更新可降至O(n),窗口滑动时只需加减一个值
前window_size个点:信息不足,默认标记为 false (正常) 标准差为零:所有值相同,无法判定,跳过
// 在 loadCSV 中
std::vector<double> timestamps, values;
timestamps.reserve(100000); // 预先分配,避免动态扩容
values.reserve(100000);
不reserve:每次push_back可能触发内存重分配,时间复杂度O(n²)
reserve:仅分配一次,push_back均摊O(1),总时间O(n)
// 优秀:顺序访问
for (auto& v : values) { sum += v; }
// 糟糕:随机访问(本项目未使用)
for (int idx : random_indices) { sum += values[idx]; }
原理:CPU缓存以64字节为单位加载,顺序访问几乎 **100%** 命中缓存
在CMakeLists.txt中添加:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native")
-O3 :最高级别优化 -march=native :针对当前CPU指令集优化(如AVX2)
bool TimeSeriesData::loadCSV(const std::string& filename) {
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
// 假设格式: timestamp,value
size_t comma_pos = line.find(',');
double ts = std::stod(line.substr(0, comma_pos));
double val = std::stod(line.substr(comma_pos + 1));
timestamps.push_back(ts);
values.push_back(val);
}
}
关键点:
std::getline() :逐行读取,内存占用稳定
std::stod() :字符串转double,比C的 atof() 更安全
假设:数据已清洗,无空行或格式错误
基于当前架构,可无缝升级:
① STL分解(Seasonal-Trend Decomposition)
// 伪代码
auto [trend, seasonal, residual] = STLdecompose(values, period);
// 检测residual的异常
② 孤立森林(Isolation Forest)
// 伪代码
IsolationForest forest(100); // 100棵树
forest.fit(values);
auto scores = forest.anomaly_score(values);
③ LSTM自编码器
// 伪代码
LSTMAutoencoder model(input_size=1, hidden_size=32);
model.train(values);
auto reconstructions = model.predict(values);
auto errors = |values - reconstructions|; // 重构误差
增量计算(将复杂度降至O(n)):
// 维护窗口统计量,避免重复计算
class IncrementalWindow {
double sum = 0, sum_sq = 0;
std::deque<double> window;
void push(double value) {
window.push_back(value);
sum += value;
sum_sq += value * value;
}
void pop() {
double old = window.front();
window.pop_front();
sum -= old;
sum_sq -= old * old;
}
};
static std::vector<bool> detect(...);
优势:
性能:
if (window_size > n) throw std::invalid_argument(...);
// 使用异常而非错误码,符合C++ RAII思想
| 模块 | 算法/技术 | 时间复杂度 | 空间复杂度 | 优化点 |
|---|---|---|---|---|
| CSV读取 | 字符串分割 | O(n) | O(n) | reserve() 预分配 |
| 异常检测 | 滑动窗口Z-score | O(n×w) | O(1) | 增量更新可优化至O(n) |
| 结果输出 | 文件I/O | O(n) | O(1) | 缓冲区写入 |