CTPN 算法详解_面试版

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTPN 算法详解_面试版相关的知识,希望对你有一定的参考价值。

参考技术A 参考链接:https://blog.csdn.net/bestrivern/article/details/100889632

1、CTPN 是什么

CTPN 全称是 connection text proposal network,是连接文本区域网络,用于进行横向文本检测。它通过将一系列小的文本框判断是不是标注的文本框的一部分,然后将是同一个标注文本框的小文本框连接成一个文本框区域,称为候选区域。重复上述操作,直到每个标注文本框的候选区域都生成完毕,最后对每个候选区域的大小进行微调。

2、CTPN 的网络结构

3、简述 CTPN 的步骤

① 使用 VGG16 的卷积阶段的网络层作为骨干网络,然后将图片输入 VGG16 网络中进行特征提取,生成特征图

② 在 ① 中输出的特征图中滑动进行 3*3 卷积,然后进行 im2col 操作,然后每次滑动都得到一个 3*3*通道数的特征向量,最后生成一个新的特征图,然后输入 BiLSTM 中进行序列特征提取,再传入全连接层中进一步提取特征

③ 在 ② 的全连接层后接 3 个全连接层分支,分别预测垂直坐标回归、分类得分、水平平移量回归

④ 将 ③ 中的预测垂直坐标回归和分类得分结果输入 RPN 中,而 RPN 的具体步骤如下:

a、

4、CTPN 的优点

① 将骨干网络生成的特征图输入后续网络,而不是将原图输入后续网络,减少了后续网络的计算量

② 引入了 BiLSTM 网络层,使得在进行文本检测时,网络能够利用到文本的语义信息

③ 增加了新的边界得分的预测分支,使得在预测文本框的边界时的效果更好,提高了网络的检测效果

④ 将 RPN 层后置到最后,可以使得 RPN 能够获得更丰富、更高级的特征输入,从而能够产生更好的结果

5、CTPN 的缺点

① 引入了 BiLSTM 网络层,相比于 CNN 可以并行化,BiLSTM 无法进行并行化,因此降低了网络的运行效率,增加了网络的运行时间

② 引入了 BiLSTM 网络层,可能会导致网络发生梯度消失或者梯度爆炸

③ 增加了新的边界得分预测分支,增加了标注成本

④ 只能用于水平方向的文本检测,垂直方向上会出现字与字的断开。如果存在倾斜,需要修改 anchor 的连接方式

⑤ anchor 的合并和断开是一个问题。程序使用的是水平 50 个像素内合并,垂直 IoU>0.7 合并。或许由于 BiLSTM 的引入,导致断开这个环节变差。所以对于双栏,三栏的这种文本,ctpn 会都当做一个框处理,有时也会分开处理,总之不像 EAST 效果好。

6、CTPN 可以改进的点

7、CTPN 的损失函数

分类得分损失: ,具体损失函数为交叉熵损失

边框回归损失: ,具体损失函数为 Smooth_L1

边界框得分损失: ,具体损失函数为 Smooth_L1

假设所有的 anchor 的集合为 A,则 i 表示集合 A 中的第 i 个 anchor,而 则表示第 i 个 anchor 的分类得分,分别为 0 和 1,0 表示是非文本框,而 1 表示是文本框。而 表示归一化因子,是 A 中的 anchor 个数之和。

假设 A 中的 anchor 跟 GroundTruth 的 IoU 大于 0.5 的 anchor 集合为 B,则 j 表示 B 中的第 j 个 anchor, 表示的是边框回归值。而 使用的是 Smooth_L1 进行计算损失。 是归一化因子,是 B 中的 anchor 个数之和。 是多任务平衡系数,一般取值为 1.

假设边界 anchor 的集合为 C,则 o 表示 C 中的第 o 个 anchor ,则 表示 C 中的第 k 个 anchor 的得分,而 使用 Smooth_L1 进行计算损失。 表示归一化因子,而 是多任务平衡系数,一般取值为 2.

8、Smooth_L1 损失函数

假设输入为 x ,则

当 |x| < 1 时,输出

其他情况,输出 |x| - 0.5

优点:

当 |x| < 1 时, Smooth_L1 的梯度为 x,所以即使 x 的值很小,也能有梯度;

而当 |x| ≥ 1 时,Smooth_L1 的梯度为 ±1,此时不易发生梯度爆炸。

且 Smooth_L1 对离群点和异常值不敏感

9、边框偏移计算公式

假设 anchor 的中心坐标为 y_a 和高度 h_a,而预测的中心坐标为 y_p 和高度 h_p,真实值的中心坐标为 y_t 和高度 h_t,则计算公式为:

s_p = (y_p - y_a) / h_a

s_t = (y_t - y_a) / h_a

h_pa = log(h_p / h_a)

h_ta = log(h_t / h_a)

注:y_p 和 h_p 

动画详解十大经典排序算法Java版实现(上)

经典排序算法可以说是面试必考题,然而大学毕业之后基本都忘光了,正好趁这个机会拿出来好好总结复习下,也算不辜负当年老师的一片苦心了。

十大经典排序算法包括:冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序,计数排序,桶排序,基数排序

本来准备一口气把十种都介绍一遍,然而整理完前五种后大脑就宕机了(亲测熬夜会使人变傻······),那干脆就分为上下两部分好了,正好也方便记忆。

接下来我们用动画的方式,先来分析前五种算法:冒泡排序,选择排序,插入排序,希尔排序,归并排序


1. 冒泡排序

算法介绍

冒泡排序是一种简单的排序算法,它会重复遍历要排序的数列,一次比较两个元素,如果两个元素的顺序错误,就交换过来。

遍历数列需要重复进行,直到没有再需要交换的元素,才代表数列已经排序完成。

因为小的元素会经由交换慢慢“浮”到数列的顶端,就像水泡冒出来一样,所以才称之为“冒泡排序”。

动画演示

步骤描述

  • 比较相邻的元素:如果第一个元素比第二个大,就交换它们;
  • 比较每一对相邻元素并交换,从开始第一对到最后一对,这样最后的元素就是最大的数;
  • 针对所有的元素重复以上步骤,除了最后一个;
  • 重复前三个步骤,直到排序完成;

代码实现

    public static void main(String[] args) {
        int arr[] = {8, 5, 3, 2, 4};
        //冒泡
        for (int i = 0; i < arr.length; i++) {
            //外层循环,遍历次数
            for (int j = 0; j < arr.length - i - 1; j++) {
                //内层循环,升序(如果后一个值比前一个值小,则交换)
                //内层循环一次,获取一个最大值
                if (arr[j + 1] < arr[j]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

2. 选择排序

算法介绍

选择排序是一种简单直观的排序算法。

它首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素放到已排序序列的末尾。

以此类推,直到所有元素全部排序完毕。

动画演示

步骤描述

  • 将第一个值看成最小值;
  • 和后续的值进行比较,找出最小值和下标;
  • 交换本次遍历的起始值和最小值;
  • 重复以上步骤直到列表排序结束;

代码实现

    public static void main(String[] args) {
        int arr[] = {6, 5, 3, 2, 4};
        // 选择
        for (int i = 0; i < arr.length; i++) {
            // 默认第一个是最小的
            int min = arr[i];
            // 记录最小的下标
            int index = i;
            // 通过与后面的数据进行比较得出,最小值和下标
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {
                    min = arr[j];
                    index = j;
                }
            }
            //然后将最小值与本次循环的a[i]值进行交换
            arr[index] = arr[i];
            arr[i] = min;
        }
    }

3. 插入排序

算法介绍

对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

动画演示

步骤描述

  • 第一个元素默认已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素大于新元素,将该元素移到下一位置;
  • 重复上一步骤,直到找到已排序的元素<=新元素的位置;
  • 将新元素插入到该位置;
  • 重复步骤 2 ~ 5;

代码实现

    public static void main(String[] args) {
        int arr[] = {7, 5, 3, 2, 4};
        //插入排序
        for (int i = 1; i < arr.length; i++) {
            //外层循环,从第二个开始比较
            for (int j = i; j > 0; j--) {
                //内层循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
                if (arr[j] < arr[j - 1]) {
                    int temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                } else {
                    //如果不小于,说明插入完毕,退出内层循环
                    break;
                }
            }
        }
    }

4. 希尔排序

算法介绍

希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。它与插入排序的不同之处在于,它会优先比较距离较远的元素。

希尔排序会把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

动画演示

动画演示效果看起来不太好理解,我们可以通过以下图片进一步加深理解: 

步骤描述

  • 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  • 按增量序列个数 k,对序列进行 k 趟排序;

  • 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序;

  • 仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度;

代码实现

    public static void main(String[] args) {
        int arr[] = {7, 5, 3, 2, 4};
        //希尔排序(插入排序变种版)
        for (int i = arr.length / 2; i > 0; i /= 2) {
            //i层循环控制步长
            for (int j = i; j < arr.length; j++) {
                //j控制无序端的起始位置
                for (int k = j; k > 0  && k - i >= 0; k -= i) {
                    if (arr[k] < arr[k - i]) {
                        int temp = arr[k - i];
                        arr[k - i] = arr[k];
                        arr[k] = temp;
                    } else {
                        break;
                    }
                }
            }
            //j,k为插入排序,不过步长为i
        }
    }

5. 归并排序

算法介绍

归并排序是建立在归并操作上的一种有效的排序算法。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,是一种稳定的排序方法。

排序规则是将已经有序的子序列进行合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 

动画演示

步骤描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列;

代码实现

    public static void main(String[] args) {
        int arr[] = {7, 5, 3, 2, 4, 1,6};
        //归并排序
        int start = 0;
        int end = arr.length - 1;
        mergeSort(arr, start, end);
    }

    public static void mergeSort(int[] arr, int start, int end) {
        //判断拆分的不为最小单位
        if (end - start > 0) {
            //再一次拆分,直到拆成单个数据
            mergeSort(arr, start, (start + end) / 2);
            mergeSort(arr, (start + end) / 2 + 1, end);
            //记录开始/结束位置
            int left = start;
            int right = (start + end) / 2 + 1;
            //记录每个小单位的排序结果
            int index = 0;
            int[] result = new int[end - start + 1];
            //如果拆分后的两块数据都还存在
            while (left <= (start + end) / 2 && right <= end) {
                //比较两块数据的大小,然后赋值,并且移动下标
                if (arr[left] <= arr[right]) {
                    result[index] = arr[left];
                    left++;
                } else {
                    result[index] = arr[right];
                    right++;
                }
                //移动单位记录的下标
                index++;
            }
            //当某一块数据不存在
            while (left <= (start + end) / 2 || right <= end) {
                //直接赋值到记录下标
                if (left <= (start + end) / 2) {
                    result[index] = arr[left];
                    left++;
                } else {
                    result[index] = arr[right];
                    right++;
                }
                index++;
            }
            //最后将新的数据赋值给原来的列表,并且是对应分块后的下标
            for (int i = start; i <= end; i++) {
                arr[i] = result[i - start];
            }
        }
    }

看到归并排序脑子就不行了,本篇博客就到这里,我们下期再见。


参考了博客如下,在这里表示特别感谢:

Java中的十大经典排序算法最强总结!!!_良月柒-CSDN博客

Java的几种常见排序算法 - 小不点丶 - 博客园

以上是关于CTPN 算法详解_面试版的主要内容,如果未能解决你的问题,请参考以下文章

动画详解十大经典排序算法Java版实现(上)

动画详解十大经典排序算法Java版实现(上)

最短路-Dijkstra算法:朴素版&堆优化版(Java详解)

面试宝典 | 冒泡排序详解

滴滴 算法实习生 面试详解 nlp方向

动画详解十大经典排序算法Java版实现(下)