/images/avatar.jpg

我的个人博客

group convolution.

参考 第一张图代表标准卷积操作。若输入特征图尺寸为 ,卷积核尺寸为 ,输出特征图尺寸为 ,标准卷积层的参数量为: 。(一个滤波器在输入特征图 大小的区域内操作,输出结果为1个数值,所以需要 个滤波器。) 第二张图代表分组卷积操作。将输入特征图按照通道数分成 组,则每组输入特征图的尺寸为 ,对应的卷积核尺寸为 ,每组输出特征图尺寸为 。将 组结果拼接(concat),得到最终尺寸为 的输出特征图。分组卷积层的参数量为 。 深入思考一下,常规卷积输出的特征图上,每一个点是由输入特征图 个点计算得到的;而分组卷积输出的特征图上,每一个点是由输入特征图 个点计算得到的。自然,分组卷积的参数量是标准卷积的 。 将输入特征图沿着通道方向进行划分,分割成不同组,每一组分别采用一个卷积和进行卷及操作。 输入特征图的大小、输出特征图的大小和标准的卷积一样,指示卷积核的参数整体减小了。 深度可分离卷积(Depthwise separable convolution) 这张图怎么少的了呢: 图(a)代表标准卷积。假设输入特征图尺寸为 ,卷积核尺寸为 ,输出特征图尺寸为 ,标准卷积层的参数量为: 。 图(b)代表深度卷积,图(c)代表逐点卷积,两者合起来就是深度可分离卷积。深度卷积负责滤波,尺寸为(DK,DK,1),共M个,作用在输入的每个通道上;逐点卷积负责转换通道,尺寸为(1,1,M),共N个,作用在深度卷积的输出特征映射上。 深度卷积参数量为 ,逐点卷积参数量为 ,所以深度可分离卷积参数量是标准卷积的 。 为了便于理解、便于和分组卷积类比,假设 。深度卷积其实就是 的分组卷积,只不过没有直接将 组结果拼接,所以深度卷积参数量是标准卷积的 。逐点卷积其实就是把组结果用 conv 拼接起来,所以逐点卷积参数量是标准卷积的 。(*只考虑逐点卷积,之前输出的特征图上每一个点是由输入特征图 区域内的点计算得到的;而逐点卷积输出上每一个点是由 区域内的点计算得到的)。*自然,深度可分离卷积参数量是标准卷积的 。

ArrayList扩容机制

说明:基于JDK15进行分析 JDK15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //默认容量大小为10 private static final int DEFAULT_CAPACITY = 10; //空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; //空数组实例的大小 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //将要被存储的ArrayList元素的数据缓冲 //The capacity of the ArrayList is the length of this array buffer,即ArrayList的容量就是这个缓冲的长度 //Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.

并发总结

并发和并行的区别 参考 并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机 并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。 线程和进程的区别 简单的理解: 进程是系统中的一个应用,进程不会相互影响。进程是程序运行的基本单位,系统运行一个程序就是一个进程从创建到灭亡的过程 一个进程中可以包含多个线程,一个线程开辟一个栈空间,假设有10个线程,就会开辟10个栈空间。但是不同线程之间是共享方法区和堆的。 栈是线程私用的,生命周期和线程相同,栈描述的是Java方法执行的线程内存模型:每个方法执行的时候都会同步创建一个栈帧用于存储局部变量表/操作数栈,动态连接。方法出口等信息。 堆是所有线程共享的区域。这在JVM启动时创建,此内存唯一的目的就是存放对象实例 方法区是所有线程共享的区域,用于存储已被JVM加载的类型信息,常量,静态常量,即时编译后的代码缓存片段。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 这里要引入一个概念:除了CPU以外所有的执行环境,主要是寄存器的一些内容,就构成了进程的上下文环境。进程的上下文是进程执行的环境。当这个程序执行完了,或者分配给他的CPU时间片用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去做的主要工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存 --- 进程的颗粒度太大,每次的执行都要进行进程上下文的切换。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成: 程序A得到CPU -> CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。 这里a,b,c的执行是共享了A进程的上下文,CPU在执行的时候仅仅切换线程的上下文,而没有进行进程上下文切换的。进程的上下文切换的时间开销是远远大于线程上下文时间的开销。这样就让CPU的有效使用率得到提高。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境的更为细小的CPU时间段。线程主要共享的是进程的地址空间。 作者:zhonyong 链接:https://www.zhihu.com/question/25532384/answer/81152571 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 小结:进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。参考 进程的实现: 为了实现进程模型,操作系统维护这一张表格,即进程表(PCB)。每个进程占用一个进程表项(或称为进程控制块)。该表项中包括了进程的状态的重要信息,包括程序计数器,堆栈指针,内存分配状况,所打开文件的状态,账号和调度信息,以及其他在进程由运行态转换到就绪态或阻塞态必须保存的信息,从而保证该进程随后能再次启动,就像从未被中断一样 注意:进程表是在操作系统中的(在内核态中) 再一次明确:进程切换的效率是比较低的 在切换进程时,首先用户态必须切换到内核态;然后保存当前进程的状态 ,包括在进程表中存储寄存器值以便以后重新加载。在许多系统中,内存映像也必须保存;接着,通过运行调度算法选定一个新进程;之后,应该将新进程的内存映像重新载入MMU(内存管理单元)中;最后,新进程开始运行。除此之外,进程切换还要使得整个内存高速缓存失效,强迫缓存从内存中动态重新载入两次(进入内核一次,出内核一次)。 进程是由内核管理和调度的,所以进程的切换只能发生在内核态。 所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。 通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行,如下图所示: 为什么需要线程: 主要原因:在一个进程中可能会同时发生多个活动。其中某一些活动随着时间的推移会被阻塞。通过将进程分解为多个线程,程序设计模式会变得简单 线程比进程更加的轻量化,所以线程比进程更容易创建和销毁。 进程和线程的区别:

操作系统基础

什么是操作系统 操作系统所处的位置: 底层是硬件,包括芯片,电路板,硬盘,键盘等 硬件的上层是软件,软件又分为用户态和内核态 其中操作系统运行在内核态中,处于软件的最基础部分,操作系统具有对所有硬件的完全访问权,可以执行机器能够运行的任何指令。 用户态的最低层次称为用户接口程序(用户与之交互的程序,基于文本的称为shell,基于图形界面的称为GUI),用户接口程序允许用户运行其他程序,例如web应用,电子邮件,音乐播放器。 文本模式登录后所取得的程序被称为壳(shell),这是因为这只程序负责最外面跟使用者(我们)打交道,所以被戏称为“壳” 在Linux中的shell为bash 操作系统本质上是一个运行在计算机的软件,用于管理计算机硬件和软件资源(操作系统是一个运行在内核态的软件) 操作系统屏蔽了硬件的复杂性 操作系统运行与裸机之上,为其他软件提供基础的运行环境 操作系统具有两种功能: 向应用程序提供抽象;例如将硬盘抽象为文件,使用该抽象,程序可以创建,读写文件,而不用直接和硬件打交道。 管理计算机资源;在相互竞争的程序间有序的控制对处理器、存储器以及其他I/O接口的分配。 什么是系统调用 如果一个进程在用户态需要使用内核态的功能,就进行系统调用从而陷入内核,由操作系统代为完成。 线程中发生函数调用时, 就会在线程栈中分配函数调用栈, 而虚拟内存分配, 文件操作, 网络读写等很多功能都是由操作系统来实现的, 再向用户程序暴露接口, 所以线程免不了要调用操作系统提供的系统服务, 即系统调用. Linux 的系统调用主要有以下这些: Task Commands 进程控制 fork(); exit(); wait(); 进程通信 pipe(); shmget(); mmap(); 文件操作 open(); read(); write(); 设备操作 ioctl(); read(); write(); 信息维护 getpid(); alarm(); sleep(); 安全 chmod(); umask(); chown(); 例:

ArrayList与数组之间的相互转换

将ArrayList转换为数组 方法1:利用循环语句将ArrayList中的元素添加到数组中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ArrayListTest { public static void main(String[] args) { List<Integer> list =new ArrayList(); list.add(1); list.add(2); list.add(3); list.add(4); int[] b=new int[list.size()]; for (int i=0;i<list.size();i++){ b[i]= list.get(i); } for (int s:b){ System.out.println(s); } } } 方法2:使用ArrayList中的toArray方法,将Arraylist转换为数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ArrayListTest { public static void main(String[] args) { List<Integer> list =new ArrayList(); list.

异常

异常分类 所有的异常都派生于Throwable,在下一层又分解为Error和Exception。 Error类层次描述Java运行时系统的内部错误和资源耗尽错误,发生这类错误时,除了通知错误并尽力妥善终止程序外,你几乎无能为力,这一类错误很少发生。 Exception类又分为Runtime Exception和其他Exception。 Java将异常进行分类: 非检查型异常(unchecked):派生于Error类和Runtime Exception类的所有异常 检查型异常(checked):其他所有异常 需要为所有检查型异常提供异常处理器。 声明检查型异常 一个方法必须声明所有可能抛出的检查型异常,如果没有声明,编译器就会发出一个错误警告 声明的方法:在方法首部通过throws 声明这个方法可能抛出的检查型异常,例如 1 2 3 4 5 6 7 class Test{ public Image LoadImage(String s) throws IOException{ ... } } 这里需要注意的是: 如果子类覆写父类的方法,那么子类的这个方法声明的异常不能比父类更加通用。子类中的方法可以声明更加特殊的异常或不声明异常 如果父类中的方法没有声明任何异常,那么子类的方法也不能声明异常 如何抛出异常 例: 1 2 3 4 5 6 7 8 9 10 11 String readData(Scanner in) throws EOFException{ while(){ if(!in.hasNext()){ if(n<len){ throw new EOFException;//抛出异常 } } } return s; } 抛出异常的方法: