首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >明月机器学习系列031:特殊二分图的最优匹配算法(二)

明月机器学习系列031:特殊二分图的最优匹配算法(二)

作者头像
明月AI
发布于 2021-10-28 14:35:58
发布于 2021-10-28 14:35:58
8060
举报
文章被收录于专栏:野生AI架构师野生AI架构师

但是这个算法最近又遇到了效率问题。

还是时间效率问题


最近文档比对测试的时候,同事传了两个一百多页的文档进行比对,然后反馈说总是比对失败,系统的同事反馈说是引擎出问题了。

那究竟是引擎哪里出问题了呢?于是又打开测试模型,观察日志输出,发现在页面配对的时候,就一直停在那里,没有继续往下输出。观察cpu和内存占用情况,有一个cpu100%,内存占用没有明显增加,可以大致判断是不是死循环了,或者有一个计算量非常大的东西正在跑。开始时,并没有意识到是配对算法本身的效率问题,毕竟这个算法前不久还专门优化过,空耗了大半天的时间。后来实在没辙了,就调戏页面匹配算法,结果还真就是这个算法的问题。

即使我们在算法的第二个进行了相当部分的剪枝,但是只要匹配的元素比较多,计算量还是非常大的,看上去就像是假死了一样。

算法优化版本V3


既然知道了问题,那就想办法解决。

在我们的场景中,两边元素其实基本上是可以匹配上的,不匹配的元素只是少数,那我们是否可以用分治的思路来解决呢?虽然总的元素比较多,但是如果可以划分成小块,那计算量将大幅下降。

结合到我们的场景,如果我们能预先将一些比较确定的元素先匹配上,那不确定的部分就很可能会被分成几个小区间,如下图:

左右两边相同颜色的部分就是两个混乱的区域(待匹配的区域),其他的区域就是已经匹配好的区域。混乱的区域是对应的,直接输入到我们的V2版算法中就行,那现在问题的关键就是找到对应的混乱区域。

V3版算法步骤与实现


找到混乱区域的算法步骤:

  1. 计算左边每个元素和右边相邻元素的匹配度(在我们场景中就是字符串的相似得分,基于编辑距离);
  2. 选择匹配度最高的作为左边每个元素的临时匹配,这样就会得到一个匹配列表,如(1,3,5),这表示左边的第1个元素和右边第1个元素匹配,第2个元素和右边第3个元素匹配,第3个元素和右边第5个元素匹配;
  3. 如果匹配列表刚好是按顺序递增的且没有相同的元素,则满足完全配对,直接返回结果。
  4. 否则,不满足条件的区域就是我们要找的区域。如(1,3,2,5),表示右边第2和3这两个元素是混乱的,但是头尾两个元素是正确的,这时我们只要对混乱区间进行重排即可,这个区间应该是左边的第2和第3个元素,对应右边的第2,3,4这三个元素,因为1和5是对应的,中间这部分是混乱的。

梳理清楚步骤之后,实现其实就不太难了:

代码语言:javascript
复制
    def match(self, min_score=0.2, sort_min_score=0.01):
        """快速配对算法(类似贪心算法)
        :return items: numpy.ndarray: [(idx1, idx2)]
        """
        scores = self.scores
        len1, len2 = scores.shape
        if len1 < 1 or len2 < 1:
            return np.array([])
        if len1 == 1 and len2 == 1:
            return np.array([[0, 0]])
        is_T = False    # 是否转置
        if len1 > len2:
            is_T = True
            scores = scores.T  # 转置之后,保证行数是比较少的

        # 每行最大值的索引
        max_index = np.argmax(scores, axis=1)
        eq_index = max_index == np.sort(max_index)
        len_index = len(max_index)
        if all(eq_index) and len_index == len(set(max_index)):
            # 顺序一致且每个值都不同
            data = [[i, v] for i, v in enumerate(max_index) if scores[i, v] >= sort_min_score]
            if is_T:
                data = [[v, i] for i, v in data]
            return np.array(data)

        # 顺序不一致或者存在相同的值
        eq_index = np.zeros((len_index,), dtype=bool)
        min_arr, max_arr = self.get_min_max(max_index)
        for i in range(len_index):
            # 比前面的值都大,比后面的值都小
            if i > 0:
                if max_index[i] <= max_arr[i-1]:
                    eq_index[i] = True
                    continue
            if i < len_index-1:
                if max_index[i] >= min_arr[i+1]:
                    eq_index[i] = True

        data = []
        is_true = False
        # self.min_score = min_score
        for (i, v), flag in zip(enumerate(max_index), eq_index):
            if not flag:
                is_true = False
                if scores[i, v] >= min_score:
                    data.append([i, v])
                if v == scores.shape[1]:     # 已经是最大的值
                    break
                continue
            if is_true:
                continue
            # 找到连续的True块
            is_true = True
            true_len = 0
            for j in range(i, len_index):
                if eq_index[j]:
                    true_len += 1
                else:
                    break

            # 在小区域[min_i:max_i, min_j:max_j]内进行匹配
            min_j = 0 if len(data) == 0 else data[-1][1]+1
            max_j = scores.shape[1] if i+true_len >= len_index else max_index[i+true_len]
            min_i, max_i = i, i+true_len
            self.scores = scores[min_i:max_i, min_j:max_j].copy()
            tmp_data = self.match_old(min_score=min_score)
            if debug:
                print("tmp data: ", tmp_data)

            for tmp_i, tmp_v in tmp_data:
                data.append([tmp_i+min_i, tmp_v+min_j])

        if is_T:
            data = [[v, i] for i, v in data]
        return np.array(data)

后续


其实,如果仔细想,这个算法还是有漏洞的,例如临时匹配是否应该先将一些得分低的过滤掉呢?这个需要更多的测试。

总体上,对这整个算法的设计还是挺满意的,效果也很好,原来算法的时间复杂度应该是指数级的,现在更加接近线性级。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档