看起来您的异常检测算法在处理不同输入测试用例时,由于数据分布的变化,导致无法使用统一的阈值进行有效判断。为了解决这个问题,可以考虑以下几种改进方法:

  1. 动态阈值:根据数据的统计特性动态调整阈值,而不是使用固定的阈值。
  2. 自适应方法:使用滑动窗口或自适应算法,根据最近的数据调整检测标准。
  3. 多指标融合:综合多个指标的统计特性,确定更为稳健的异常判定标准。

以下是针对您的 detectAnomaly 函数的具体修改建议和代码示例。

1. 动态阈值调整

将阈值根据数据的均值和标准差动态计算,而不是依赖于固定的阈值。这可以提高算法对不同数据分布的适应性。

func (d *TimeSeriesAnomalyDetector) detectAnomaly(newValue float64, recentValues []DataPoint, previousValue float64, windowSize int, impulseFactorParams ImpulseFactorParams, crestFactorParams CrestFactorParams, changeRateParams ChangeRateParams, rmsParams RMSParams, shapeFactorParams ShapeFactorParams) (bool, string, map[string]float64) {
	metrics := make(map[string]float64)
	reasons := make([]string, 0)
	window := getWindowValues(recentValues, windowSize)
	// 计算各项指标
	metrics["ImpulseFactor"] = d.calculateImpulseFactor(window)
	metrics["CrestFactor"] = d.calculateCrestFactor(window)
	metrics["ChangeRate"] = d.calculateChangeRate(newValue, previousValue)
	metrics["RMS"] = d.calculateRMS(window)
	metrics["ShapeFactor"] = d.calculateShapeFactor(window)
	// 动态计算阈值
	dynamicThresholds := d.calculateDynamicThresholds()
	// 判断各项指标是否超过动态阈值
	if metrics["ImpulseFactor"] > dynamicThresholds["ImpulseFactor"] {
		reasons = append(reasons, fmt.Sprintf("脉冲因子: %.3f > %.3f", metrics["ImpulseFactor"], dynamicThresholds["ImpulseFactor"]))
	}
	if metrics["CrestFactor"] > dynamicThresholds["CrestFactor"] {
		reasons = append(reasons, fmt.Sprintf("峰值因子: %.3f > %.3f", metrics["CrestFactor"], dynamicThresholds["CrestFactor"]))
	}
	if metrics["ChangeRate"] > dynamicThresholds["ChangeRate"] {
		reasons = append(reasons, fmt.Sprintf("变化率: %.3f > %.3f", metrics["ChangeRate"], dynamicThresholds["ChangeRate"]))
	}
	if metrics["RMS"] > dynamicThresholds["RMS"] {
		reasons = append(reasons, fmt.Sprintf("RMS: %.3f > %.3f", metrics["RMS"], dynamicThresholds["RMS"]))
	}
	if metrics["ShapeFactor"] > dynamicThresholds["ShapeFactor"] {
		reasons = append(reasons, fmt.Sprintf("波形因子: %.3f > %.3f", metrics["ShapeFactor"], dynamicThresholds["ShapeFactor"]))
	}
	// 综合判断
	isAnomaly := len(reasons) >= 3 // 至少三个指标异常
	return isAnomaly, strings.Join(reasons, "; "), metrics
}

// 动态计算阈值的方法
func (d *TimeSeriesAnomalyDetector) calculateDynamicThresholds() map[string]float64 {
	thresholds := make(map[string]float64)
	// 计算脉冲因子的动态阈值
	if len(d.ImpulseFactorHistory) > 0 {
		mean, std := calculateMeanAndStd(d.ImpulseFactorHistory)
		thresholds["ImpulseFactor"] = mean + 2*std // 可根据需要调整倍数
	}
	// 计算峰值因子的动态阈值
	if len(d.CrestFactorHistory) > 0 {
		mean, std := calculateMeanAndStd(d.CrestFactorHistory)
		thresholds["CrestFactor"] = mean + 2*std
	}
	// 计算变化率的动态阈值
	if len(d.ChangeRateHistory) > 0 {
		mean, std := calculateMeanAndStd(d.ChangeRateHistory)
		thresholds["ChangeRate"] = mean + 2*std
	}
	// 计算RMS的动态阈值
	if len(d.RMSHistory) > 0 {
		mean, std := calculateMeanAndStd(d.RMSHistory)
		thresholds["RMS"] = mean + 2*std
	}
	// 计算波形因子的动态阈值
	if len(d.ShapeFactorHistory) > 0 {
		mean, std := calculateMeanAndStd(d.ShapeFactorHistory)
		thresholds["ShapeFactor"] = mean + 2*std
	}
	return thresholds
}

2. 使用滑动窗口进行自适应检测

通过滑动窗口实时更新统计值,使得算法能够更快地适应数据变化。

func (d *TimeSeriesAnomalyDetector) Update(timestamp string, newValue float64) (bool, string, []DataPoint) {
	d.mu.Lock()
	defer d.mu.Unlock()
	// 更新最近值缓存
	newPoint := DataPoint{Timestamp: timestamp, Value: newValue}
	d.RecentValues = append(d.RecentValues, newPoint)
	if len(d.RecentValues) > d.WindowSize {
		d.RecentValues = d.RecentValues[1:]
	}
	// 更新基准窗口
	// ... 现有代码保持不变 ...
	// 调用 detectAnomaly
	isAnomaly, reason, metrics := d.detectAnomaly(newValue, d.RecentValues, d.PreviousValue, d.WindowSize, d.ImpulseFactorParams, d.CrestFactorParams, d.ChangeRateParams, d.RMSParams, d.ShapeFactorParams)
	// 更新连续异常计数和其他状态
	// ... 现有代码保持不变 ...
	return isAnomaly, reason, d.RecentValues
}

3. 引入异常评分机制

为每个指标分配不同的权重,计算综合异常评分,以提高检测的准确性。

func (d *TimeSeriesAnomalyDetector) detectAnomaly(newValue float64, recentValues []DataPoint, previousValue float64, windowSize int, impulseFactorParams ImpulseFactorParams, crestFactorParams CrestFactorParams, changeRateParams ChangeRateParams, rmsParams RMSParams, shapeFactorParams ShapeFactorParams) (bool, string, map[string]float64) {
	metrics := make(map[string]float64)
	reasons := make([]string, 0)
	window := getWindowValues(recentValues, windowSize)
	// 计算各项指标
	metrics["ImpulseFactor"] = d.calculateImpulseFactor(window)
	metrics["CrestFactor"] = d.calculateCrestFactor(window)
	metrics["ChangeRate"] = d.calculateChangeRate(newValue, previousValue)
	metrics["RMS"] = d.calculateRMS(window)
	metrics["ShapeFactor"] = d.calculateShapeFactor(window)
	// 动态计算阈值
	dynamicThresholds := d.calculateDynamicThresholds()
	// 计算异常评分
	anomalyScore := 0.0
	if metrics["ImpulseFactor"] > dynamicThresholds["ImpulseFactor"] {
		anomalyScore += 1.0 // 根据需要调整分数
		reasons = append(reasons, fmt.Sprintf("脉冲因子异常: %.3f > %.3f", metrics["ImpulseFactor"], dynamicThresholds["ImpulseFactor"]))
	}
	if metrics["CrestFactor"] > dynamicThresholds["CrestFactor"] {
		anomalyScore += 1.0
		reasons = append(reasons, fmt.Sprintf("峰值因子异常: %.3f > %.3f", metrics["CrestFactor"], dynamicThresholds["CrestFactor"]))
	}
	if metrics["ChangeRate"] > dynamicThresholds["ChangeRate"] {
		anomalyScore += 1.0
		reasons = append(reasons, fmt.Sprintf("变化率异常: %.3f > %.3f", metrics["ChangeRate"], dynamicThresholds["ChangeRate"]))
	}
	if metrics["RMS"] > dynamicThresholds["RMS"] {
		anomalyScore += 1.0
		reasons = append(reasons, fmt.Sprintf("RMS异常: %.3f > %.3f", metrics["RMS"], dynamicThresholds["RMS"]))
	}
	if metrics["ShapeFactor"] > dynamicThresholds["ShapeFactor"] {
		anomalyScore += 1.0
		reasons = append(reasons, fmt.Sprintf("波形因子异常: %.3f > %.3f", metrics["ShapeFactor"], dynamicThresholds["ShapeFactor"]))
	}
	// 设置异常阈值,比如评分>=3为异常
	isAnomaly := anomalyScore >= 3
	return isAnomaly, strings.Join(reasons, "; "), metrics
}

4. 记录异常时间和时间戳

确保在检测到异常时,记录具体的时间和时间戳,以便后续分析和追踪。

func (d *TimeSeriesAnomalyDetector) Update(timestamp string, newValue float64) (bool, string, []DataPoint) {
	d.mu.Lock()
	defer d.mu.Unlock()
	// 更新最近值缓存
	newPoint := DataPoint{Timestamp: timestamp, Value: newValue}
	d.RecentValues = append(d.RecentValues, newPoint)
	if len(d.RecentValues) > d.WindowSize {
		d.RecentValues = d.RecentValues[1:]
	}
	// 更新基准窗口
	// ... 现有代码保持不变 ...
	// 调用 detectAnomaly
	isAnomaly, reason, metrics := d.detectAnomaly(newValue, d.RecentValues, d.PreviousValue, d.WindowSize, d.ImpulseFactorParams, d.CrestFactorParams, d.ChangeRateParams, d.RMSParams, d.ShapeFactorParams)
	// 记录异常事件
	if isAnomaly {
		parsedTime, err := parseTimestamp(timestamp)
		if err != nil {
			log.Printf("无法解析时间戳: %s", timestamp)
			parsedTime = time.Now()
		}
		event := AnomalyEvent{
			Timestamp: parsedTime,
			Level:     Critical, // 根据实际情况设定级别
			Value:     newValue,
		}
		d.TimeWindowEvents = append(d.TimeWindowEvents, event)
		log.Printf("异常检测: %s at %s", reason, parsedTime.Format(time.RFC3339))
	}
	// 更新连续异常计数和其他状态
	// ... 现有代码保持不变 ...
	return isAnomaly, reason, d.RecentValues
}

总结

通过上述改进,您的异常检测算法将更加灵活和自适应,能够根据不同的输入数据动态调整检测标准,从而提高异常检测的准确性。同时,记录详细的异常时间和时间戳,有助于后续的分析和问题定位。

动态阈值和滑动窗口自适应是两种不同的技术,它们都可以用于提高异常检测算法的适应性和准确性,但它们的应用方式和关注点有所不同。下面详细解释两者的区别:

1. 动态阈值(Dynamic Thresholds)

定义
动态阈值是指根据数据的统计特性(如均值、标准差)动态计算检测阈值,而不是使用固定的阈值。这种方法可以根据数据的变化自动调整阈值,从而适应不同的数据分布。

应用方式
通常,动态阈值会基于历史数据的统计特性来计算。例如,可以计算过去一段时间内数据的均值和标准差,然后将阈值设置为均值加上若干倍的标准差。当新数据点到来时,将其与当前阈值进行比较,以判断是否为异常。

关注点
动态阈值主要关注于阈值本身的自适应调整,使其能够反映数据的整体分布情况。它假设数据的统计特性在一段时间内是相对稳定的,并基于这些统计特性来设定阈值。

优点

  • 能够适应不同数据集的统计特性。
  • 减少了对固定阈值的依赖,提高了算法的灵活性。

缺点

  • 对数据的统计特性变化较为敏感,如果数据分布发生较大变化,可能需要重新计算阈值。
  • 可能需要一定的历史数据来计算初始阈值。

2. 滑动窗口自适应(Adaptive Sliding Window)

定义
滑动窗口自适应是指使用一个固定大小的窗口来实时更新数据的统计特性,并基于窗口内的数据来检测异常。窗口会随着新数据的到来而向前滑动,始终包含最新的数据。

应用方式
滑动窗口自适应方法会维护一个包含最近 N 个数据点的窗口。当新数据点到来时,将其加入窗口,并移除窗口中最旧的数据点。然后,基于窗口内的数据计算统计特性(如均值、标准差),并使用这些统计特性来检测新数据点是否为异常。

关注点
滑动窗口自适应主要关注于数据的实时性和局部性。它假设数据的统计特性可能随时间变化,因此只关注最近一段时间内的数据。通过不断更新窗口,算法可以快速适应数据的变化。

优点

  • 能够快速响应数据的变化,实时性较好。
  • 对数据的局部变化更为敏感,能够及时检测到局部的异常。

缺点

  • 窗口大小的选择较为关键,过大或过小的窗口都可能影响检测效果。
  • 计算量相对较大,因为每次新数据到来都需要重新计算窗口内数据的统计特性。

总结

动态阈值滑动窗口自适应都是为了提高异常检测算法的适应性和准确性,但它们的方法和关注点不同:

  • 动态阈值关注于根据数据的统计特性动态调整阈值,适用于数据分布相对稳定的场景。
  • 滑动窗口自适应关注于实时更新数据的统计特性,并基于窗口内的数据进行检测,适用于数据分布可能随时间变化的场景。

在实际应用中,可以将两者结合使用。例如,可以使用滑动窗口来实时更新数据的统计特性,并基于这些统计特性动态计算阈值。这样可以兼顾数据的实时性和整体分布情况,提高异常检测的准确性和适应性。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐