使用给定的指标将一组元素匹配到另一组

问题描述

首先,我想说的是我不是在寻找代码,而是在寻找一种算法

动机:

我正在编写一个复杂的实时软件系统的顶级测试。它运行所有软件组件(约20个进程,约100个线程),设置伪造的数据源(rtsp视频源),并将准备好的数据(视频文件)馈送到系统,记录系统响应(事件),然后最终停止系统准备好的测试数据已发送。

由于测试数据始终相同,我希望被测系统能够在正确的时间(从测试开始)提供正确的响应(事件)。

然后我将生成的响应(事件)与预期的事件(手动准备)进行比较,我希望这些事件都存在,可能会有一些小的时间差异,我会限制某些给定的time-tolerance,比如说5秒

比方说,经过测试的系统应该可以在1500秒长的视频中检测到动物,然后我观看了下来并写下了5种动物以及它们出现在视频中的时间:

at   10s - a sparrow
at   20s - a cat
at   50s - a rabbit
at  100s - an owl
at 1000s - a bear

基于此,我然后写expected_events集:

expected_events = [
    Event(10,'sparrow'),Event(20,'cat'),Event(50,'rabbit'),Event(100,'owl')
    Event(1000,'bear')
]

我想知道实际检测到的事件(受处理器负载,磁盘使用率,网络使用率atd影响,因为这是真实计算机上的多进程系统)的匹配程度 这些expected_eevents

假设测试过的系统返回了

detected_events = [
    Event(10.1,Event(19.5,Event(50.2,Event(99.3,'owl')
    Event(1000.2,'bear')
]

我认为与期望的事件100%匹配是正确的,所有事件都存在并且时差低于time-tolerance

matches = [
    {'name': 'sparrow','detected': 10.1,'expected': 10,'time-diff': 0.1},{'name': 'cat','detected': 19.5,'expected': 20,'time-diff': 0.5},{'name': 'rabbit','detected': 50.2,'expected': 50,'time-diff': 0.2},{'name': 'owl','detected': 99.3,'expected': 100,'time-diff': 0.7},{'name': 'bear','detected': 1000.2,'expected': 1000,]

如果测试的系统返回:

detected_events = [
    Event(10.1,'owl')
    Event(1010.5,'bear')
]
我期望的

可能会导致这样的匹配:

raw_matches = [
    {'name': 'sparrow','detected': None,'time-diff': None},'detected': 1010.5,'time-diff': 10.52},]

pruned_matches = [
    {'name': 'sparrow',]

我认为这是失败的,因为:

  • 没有发现猫
  • 检测到熊迟了10.5秒
  • 因此5个中只有3个真正匹配,结果应该是60%匹配

因此,我需要的是一种针对detected_events来评估expected_events方法,以便能够评估被测系统的运行状况。

简化

因为匹配事件类型对于该问题至关重要,并且可以作为每种事件类型的单独匹配来完成,所以我将做以下简单说明:

  • 所有事件都是相同的-即仅事件的时间很重要,因此事件仅由时间戳来表示
  • 时间戳记将为int,以便于阅读

我认为什么是“好”比赛:

正如许多人在评论中指出的那样,除了消除时差> time-tolerance的比赛之外,我实际上没有评估最终比赛的指标。 这使难度增加了一些,但我认为这很直观-我知道应该在什么时候发生,并且将其与实际事件进行比较,并且我将尽可能地匹配它们以确保:

  • 尽可能多地匹配预期事件
  • 每个detected_event匹配的expected_event必须在给定的时间公差下同时发生。

因此,我认为这是“正确的”匹配项(具有5秒的时间公差):

                                  matches = [
expected_events = [10,20]  =>        {'expected': 10,'detected': 10},detected_events = [10,20]            {'expected': 20,'detected': 20},]

                                  matches = [
expected_events = [10,'detected': 15},detected_events = [15,25]            {'expected': 20,'detected': 25},'detected':  5},detected_events = [ 5,15]            {'expected': 20,11]  =>        {'expected': 10,'detected': 11},detected_events = [11,12]            {'expected': 11,'detected': 12},]


                                  matches = [
expected_events = [10,26]        ]

expected_events = [10,20]  =>    matches = []
detected_events = [ 4,26]

                                  matches = [
expected_events = [10,20,30] =>     {'expected': 20,'detected': 17},detected_events = [17,24]        ]

我认为这是“不良”匹配项(也就是说,这不是不是我希望其工作的方式):

                                  matches = [
expected_events = [10,20]  =>        {'expected': 20,25]        ]
# matched only 1 events even though it's possible to match 2

                                  matches = [
expected_events = [10,11]  =>        {'expected': 11,12]        ]
# matched only 1 events even though it's possible to match 2


                                  matches = [
expected_events = [10,'detected': 4},detected_events = [ 4,26]            {'expected': 20,'detected': 26},]
# should not match anything,time differences > 5sec

代码/我尝试过的操作:

代码应如下所示:

expected_events = [10,50,100,1000] # times in second
detected_events = [11,18,51,1001]

def metric(ev1,ev2):
    return abs(ev1 - ev2)

matches = match_events(expected_events,detected_events,metric,tolerance=5)
  1. 简单,幼稚的方法-从最佳匹配开始

    我尝试了一种非常简单的方法

    • (expected_events,detected_events)的乘积
    • 计算时差
    • 过滤器匹配的时间差大于给定的容差
    • 按时差对比赛进行排序
    • 从第一个匹配开始匹配,并丢弃“冲突”(即,后一个匹配使用相同的元素)

    这适用于简单的情况,但是当事件“转移”时我遇到了问题:

     expected_events = [10,11]
     detected_events = [11,12] 
    

    现在我得到了:

     [
         {'expected': 11,]
    

    我想拥有:

     [
         {'expected': 10,{'expected': 11,]
    
  2. 蛮力-排列

    然后我尝试了蛮力:

    • (expected_events,detected_events)的乘积
    • 计算时差
    • 过滤器匹配的时间差大于给定的容差
    • 创建给定匹配项的所有可能排列
    • 对于每个排列,从第一个匹配开始匹配,并丢弃“冲突”(即,后一个匹配使用相同的元素)
    • 计算所有匹配项的长度
    • 只保留最大长度的东西
    • 选择与min匹配(按所有时间差之和排序)

    这有效,但是正如您可能期望的那样,对于任何长度短于几个元素的东西来说太慢了。 (我希望使用惰性评估使其有效,但无法使其正常工作。)

问题:

对于这种匹配,正确的算法是什么? 在我看来,这可能已经是解决的问题-但我不知道要搜索什么。


这是解决的问题-分配问题-https://en.wikipedia.org/wiki/Assignment_problem-感谢Yossi Levi!

解决方法

我将按照您的要求,提供一种非编程方法,但据我所知仅是逻辑(半数学)方法。

算法步骤:

  • 将阈值定义为T
  • 在较小的列表上填充无关紧要的值(例如“无”)-只是要确保尺寸一致
  • 通过将一个列表的每个元素减少到另一列表中的元素的绝对值来创建相似性矩阵,让我们将矩阵定义为S。

说明:S [i,j]是指列表A中第i个元素与列表B中第j个元素之间的区别

  • 创建一个二进制矩阵B,其中满足阈值critirea的每个元素均为1,否则为0(MATLAB-> B = S

例如:

       0 0 0
    
B =    0 1 1
    
       1 0 0

假设X维代表列表A,Y代表列表B,则:

B[2] === A[2]
B[2] === A[3]
B[3] === A[1] (=== means that two elements satisfy the criteria of similarity)

现在-问题变得更加数学化,为了找到最佳匹配,您可以使用以下建议之一:

蛮力(我认为不太优雅):

  • 选择1(在未标记的行和列中)的元素
  • 将他的整个列和行标记为已标记
  • 继续选择另外1个,直到没有合法的地方并将其加到score
  • 对所有选项进行迭代处理,然后选择score最高的选项

更优雅的方法:

  • 对列的所有排列进行迭代(对于B矩阵)
  • 如果迹线或对角线等于len(A),则结束(找到所有元素的匹配项)
  • 选择TRACE最高(或对角线相反)的排列 当然,最坏情况下的排列数为N! (其中N是B的尺寸)

最佳方法:

在网上搜索如何在只有列交换允许的情况下找到矩阵的最大迹线我遇到了this(请注意 Example Matrix解释部分)。 此外,我在PyPI package中找到了它的实现。

如文档所述-算法正在尝试查找矩阵的所有可能排列的 minimum 迹线-因此,只需使用-B作为参数进行调用即可。

总体示例(最后一步是更优雅的方法):

A = [1,5,7]
B = [6,6,2]

T = 2

S = 
5 5 1 
1 1 3
1 1 5

B = 
0 0 1
1 1 0
1 1 0

permutations of columns:

1- 
0 0 1
1 1 0
1 1 0

Trace = 1
other-diagonal = 3

Done -- > no need for more permutations because len(other-diagonal) == 3
A[1] =1 === B[3] =2
A[2] =5 === B[2] =6
A[3] =7 === B[1] =6

随时询问或提供您可能有的见识