摘要:

  在深度学习中,为了提升数据传输带宽和计算性能,经常会使用NCHW、NHWC和CHWN数据格式,它们代表Image或Feature Map等的逻辑数据格式(可以简单理解为数据在内存中的存放顺序)。本文以百度的AI端上推理设备EdgeBoard为原型,介绍EdgeBoard选择NHWC数据格式的技术考量。

  EdgeBoard简介

  EdgeBoard是百度基于FPGA芯片研发的嵌入式AI解决方案,高性能的加速引擎可提供3.6Tops的强大算力,完整的嵌入式参考设计使硬件集成轻松便捷。目前EdgeBoard提供了FPGA软核和计算卡模块两种形态供硬件集成,面向项目部署也提供了抓拍机和计算盒两种基础硬件产品。EdgeBoard深度兼容百度大脑模型资源与工具平台(EasyDL/AI Studio),极大降低了开发验证、产品集成、科研教学、项目部署门槛,适用于安防监控、工业质检、医疗诊断、农作物生长监控、无人驾驶、无人零售等场景

  数据格式的逻辑表示与物理表示

  深度学习中经常会使用NCHW、NHWC和CHWN数据格式来表示数据,其中N、H、W、C定义如下:

  N:一个batch内图片的数量,一次处理的图片数量

  H:垂直高度方向的像素个数,图片的高

  W:水平宽度方向的像素个数,图片的宽

  C:通道数。例如灰度图像为1,彩色RGB图像为3

  下图表示N=2,C=16,H=5,W=4的数据排列,其中左图是逻辑表示,右图是物理表示。

  以NCHW为例,其逻辑表示如左上图,n=0时,三个坐标分别标识了C、H、W的方向。第一个元素是000,第二个元素沿着w方向,即001,随后是002, 003;然后沿H方向,即004, 005, 006, 007...如此反复到019后;再沿C方向,020,021, 022 .....一直到319;再沿N方向,也就是n=1,然后重复W方向,H方向和C方向。

  根据以上NCHW的划分,物理地址表示定义如下(如右上图):

  [a:0] 表示W方向,在一行内从左到右

  [a:1] 表示从H方向,一行一行的从上到下

  [a:2] 表示在C方向,从一个通道到另外一个通道

  [a:3] 表示从N方向,从n=0 到n=1

  最终NCHW数据格式的物理分布(在内存中的一维表示)表示为000 001 002 003 004 ... 018 019 020 ... 318 319 320 ... ... 637 638 639。可以理解成把一个通道的所有像素一行一行地排列起来,然后排下一个通道,即n=0排列完后再排n=1。

  同理NHWC表示是先沿C方向,再沿W方向,再沿H方向,最后沿N方向。所以在内存的存放顺序是,第一个元素是000,第二个沿C方向,即020,040, 060 ...一直到300,之后切换到W方向,001 021 041 061...301..到了303后再切换到H方向,即004, 024 ... 304,最后到了319,再切换到N方向,320,340 ...一直到639。

  [b:0] 表示C方向,第一个像素从一个通道到另外一个通道

  [b:1] 表示从W方向,最后一个通道第一个像素回到第一个通道的第二个像素

  [b:2] 表示在H方向,最后一个通道第一行最后一个像素回到第一个通道的第二行的第一个像素

  [b:3] 表示从N方向,从n=0 到n=1

  NHWC其物理表示为000 020 ... 300 001 021 ... 283 303 004 ... 319 320 340 ... ... 339 359 ... 639。可以理解成把一个batch的一个像素的所有通道先排列起来,然后排下一个像素。n=0排列完成后,再排n=1。

  同理CHWN其逻辑表示,先沿N方向,再沿W方向,再沿H方向,最后是沿C方向。

  [c:0] 表示从N方向,从n=0的第一个像素到n=1的第一个像素

  [c:1] 表示从N方向,从n=1的第一个像素回到n=0的第二个像素

  [c:2] 表示在H方向,从n=1的第一行最后一个像素回到n=0的第二行第一个像素

  [c:3] 表示从N方向,从n=1的第一个通道最后一个像素回到n=0的第二个通道第一个像素

  CHWN其物理表示为 000 032 001 321 ... 003 323 004 324 ... ... 019 339 020 ...。可以理解成先把一个batch中N幅图像的第一个通道第一个像素排列起来,然后排第二个像素;再排第二个通道,第三个通道...

  数据在内存中的偏移地址

  深度学习中涉及大量的数据计算,计算需要从内存中取出数据,因此需要计算出数据的偏移地址以便进行取数。有了上面的逻辑表示和物理表示,可以推导出4维逻辑表示(n,c,h,w)映射到一维内存中偏移地址的公式。

  定义位置(n,c,h,w)表示第n个batch的第c通道的第h行的第w列,那么该位置在不同数据格式下,在内存中的偏移地址计算公式如下: NCHW: offset_nchw(n, c, h, w) = n * CHW + c * HW + h * W + w NHWC: offset_nhwc(n, c, h, w) = n * HWC + h * WC + w * C + c CHWN: offset_chwn(n, c, h, w) = c * HWN + h * WN + w * N + n 其中N、C、H、W为常量,n、c、h、w为变量

  在NCHW中,CHW=C*H*W,表示一个Batch,可以理解成一个BGR 3通道的图片,表达的是一个立方体。HW=H*W,表示一个平面,可以理解成是BGR 3通道图片的一个通道(灰度图就是一个通道图片)。W是一行,可以理解成一个通道上的一行。

  以上图为例,如果想计算出绿色圈,即341的位置(n=1,c=1, h=0, w=1)。我们需要先跳过n=0的数据(CHW),图中箭头1指向的蓝色框区域;再跳过n=1的第一个通道(HW),图中箭头2指向蓝色框区域;这时进入到了n=1的第二个通道,跳过h=0行(0*W);最后跳过w个数到达偏移位置。

  EdgeBoard为何使用NHWC

  下面来分析EdgeBoard选择NHWC数据格式的原因。

  上图表示卷积的计算过程。根据卷积的运算特点,相同位置窗口所有通道数与卷积的参数相乘后累加,可以有下面两种计算方式:

  先通道后像素:先把一个像素点的所有通道数与卷积的参数相乘后累加,再进行下一个像素,直到卷积核窗口乘累加完成。比如第一次滑窗的计算公式 (w0,0,0)*(x0,0,0) + (w1,0,0)*(x1,0,0) + (w2,0,0)*(x2,0,0) + (w0,0,1)*(x0,0,1) + (w1,0,1)*(x1,0,1) + (w2,0,1)*(x2,0,1) + (w0,0,2)*(x0,0,2) + (w1,0,1)*(x1,0,2) + (w2,0,2)*(x2,0,2) + (w0,1,0)*(x0,1,0) + (w1,1,0)*(x1,1,0) + (w2,1,0)*(x2,1,0) + (w0,1,1)*(x0,1,1) + (w1,1,1)*(x1,1,1) + (w2,1,1)*(x2,1,1) + (w0,1,2)*(x0,1,2) + (w1,1,1)*(x1,1,2) + (w2,1,2)*(x2,1,2) + (w0,2,0)*(x0,2,0) + (w1,2,0)*(x1,2,0) + (w2,2,0)*(x2,2,0) + (w0,2,1)*(x0,2,1) + (w1,2,1)*(x1,2,1) + (w2,2,1)*(x2,2,1) + (w0,2,2)*(x0,2,2) + (w1,2,1)*(x1,2,2) + (w2,2,2)*(x2,2,2) = 0*-1 + 0*-1 + 0*0 + 0*1 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*1 + 1*0 + 2*1 + 1*0 + 0*0 +1*0 + 0*0 + 0*0 + 0*1 + 2*1 + 0*-1 + 1*-1 + 2*1 + 0*0 + 0*-1 + = 5

  先像素后通道:先把一个通道滑动窗口与卷积参数相乘后累加,再进行下一个通道,直到所有通道乘累加完成。比如第一次滑窗计算公式

  (w0,0,0)*(x0,0,0) + (w0,0,1)*(x0,0,1) + (w0,0,2)*(x0,0,2) + (w0,1,0)*(x0,1,0) + (w0,1,1)*(x0,1,1) + (w0,0,2)*(x0,1,2) + (w0,2,0)*(x0,2,0) + (w0,0,1)*(x0,2,1) + (w0,0,2)*(x0,2,2) + (w1,0,0)*(x1,0,0) + (w1,0,1)*(x1,0,1) + (w1,0,2)*(x1,0,2) + (w1,1,0)*(x1,1,0) + (w1,1,1)*(x1,1,1) + (w1,0,2)*(x1,1,2) + (w1,2,0)*(x1,2,0) + (w1,0,1)*(x1,2,1) + (w1,0,2)*(x1,2,2) + (w2,0,0)*(x2,0,0) + (w2,0,1)*(x2,0,1) + (w2,0,2)*(x2,0,2) + (w2,1,0)*(x2,1,0) + (w2,1,1)*(x2,1,1) + (w2,0,2)*(x2,1,2) + (w2,2,0)*(x2,2,0) + (w2,0,1)*(x2,2,1) + (w2,0,2)*(x2,2,2) = 0*-1 + 0*1 + 0*0 + 0*0 +0*1 + 1*0 + 0*0 + 2*1 + 2*1 + 0*-1 + 0*-1 + 0*0 + 0*0 + 1*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 2*1 + 1*0 + 0*1 + 1*-1 + 0*-1 = 5

  可以看出两种方式计算的结果是一样。

  而对于NHWC格式,即先通道后像素,是把一个像素的所有通道的数据放在一起。这样对应上图第一个像素的3个通道值,第二个像素的3个通道值,第三个像素的3个通道值,它们在内存中的地址都是连续的,也就是说一次就可以把kernel第一行需要计算的数取出,3x3的kernel需要3次取数。

  而对于NCHW格式,即先像素后通道,是把一个通道的所有像素按顺序排列,这样对于一个3*3的卷积核,需要每取3个数就需要跳跃n个数后,再取3个数。一个通道需要取3次,3个通道需要取9次。

  在实际网络中,通常通道数会远大于卷积kernel数(不会像上图只有3个通道,通常是几十、几百个通道)。这样对于NHWC格式来说说,取数的次数会比NCHW少很多。对EdgeBoard来说,为了增加其所支持网络的广泛性,减少对大输入尺寸和高存储量权重网络的限制,采用NHWC的格式可以实现分批次地把Feature Map和Weight数据读取到FPGA的片上缓存,例如对于3x3的Kernel,我们可以只读取三行(3WC)Feature Map的数据到FPGA内进行计算,即可得到一行输出数据,并传输到片外大容量缓存DDR,而不需依赖下一个3WC的Feature Map输入数据就可完成每一批次的输入输出数据传输。

  再例如,我们也可将Weight数据根据FPGA片上缓存的不同大小分割成N份,一份一份发送到FPGA分别做卷积运算后,再传输回DDR做相应拼接,这等同于做一次大的卷积运算,好处在于可以根据不同容量的FPGA器件做不同匹配,大大提高了代码的硬件适配性。此外,由于C维度之间数据相关性较弱,采用NHWC格式更能发挥FPGA高并行度的计算特点,充分利用FPGA的算力。

  下表为EdgeBoard使用NHWC数据格式的网络性能:

  参考链接:

  [1]Intel MKL github

  https://intel.github.io/mkl-dnn/understanding_memory_formats.html

  [2] 零基础入门深度学习

  https://www.zybuluo.com/hanbingtao/note/485480

  福利

  据可靠消息:EdgeBoard正在打折中,历史最低价,直降1000元,有兴趣可以看看:https://aim.baidu.com/product/5b8d8817-9141-4cfc-ae58-640e2815dfd4

EdgeBoard简介

EdgeBoard是百度基于FPGA芯片研发的嵌入式AI解决方案,高性能的加速引擎可提供3.6Tops的强大算力,完整的嵌入式参考设计使硬件集成轻松便捷。目前EdgeBoard提供了FPGA软核和计算卡模块两种形态供硬件集成,面向项目部署也提供了抓拍机和计算盒两种基础硬件产品。  EdgeBoard深度兼容百度大脑模型资源与工具平台(EasyDL/AI  Studio),极大降低了开发验证、产品集成、科研教学、项目部署门槛,适用于安防监控、工业质检、医疗诊断、农作物生长监控、无人驾驶、无人零售等场景。

数据格式的逻辑表示与物理表示

深度学习中经常会使用NCHW、NHWC和CHWN数据格式来表示数据,其中N、H、W、C定义如下:

l N:一个batch内图片的数量,一次处理的图片数量。

l H:垂直高度方向的像素个数,图片的高。

l W:水平宽度方向的像素个数,图片的宽。

l C:通道数。例如灰度图像为1, 彩色RGB图像为3。

下图表示N=2,C=16,H=5,W=4的数据排列,其中左图是逻辑表示,右图是物理表示。

以NCHW为例,其逻辑表示如左上图,n=0时,三个坐标分别标识了C、H、W的方向。第一个元素是000,第二个元素沿着w方向,即001,随后是002, 003;然后沿H方向,即004, 005, 006, 007...如此反复到019后;再沿C方向,020,021, 022 .....一直到319;再沿N方向,也就是n=1,然后重复W方向,H方向和C方向。

根据以上NCHW的划分,物理地址表示定义如下(如右上图):

l [a:0] 表示W方向,在一行内从左到右。

l [a:1] 表示从H方向,一行一行的从上到下。

l [a:2] 表示在C方向,从一个通道到另外一个通道。

l [a:3] 表示从N方向,从n=0 到n=1。

最终NCHW数据格式的物理分布(在内存中的一维表示)表示为000 001 002 003 004 ... 018 019 020 ... 318 319 320 ... ... 637 638 639。可以理解成把一个通道的所有像素一行一行地排列起来,然后排下一个通道,即n=0排列完后再排n=1。

同理NHWC表示是先沿C方向,再沿W方向,再沿H方向,最后沿N方向。所以在内存的存放顺序是,第一个元素是000,第二个沿C方向,即020,040, 060 ...一直到300,之后切换到W方向,001 021 041 061...301..到了303后再切换到H方向,即004, 024 ... 304,最后到了319,再切换到N方向,320,340 ...一直到639。

l [b:0] 表示C方向,第一个像素从一个通道到另外一个通道。

l [b:1] 表示从W方向,最后一个通道第一个像素回到第一个通道的第二个像素。

l [b:2] 表示在H方向,最后一个通道第一行最后一个像素回到第一个通道的第二行的第一个像素。

l [b:3] 表示从N方向,从n=0 到n=1。

NHWC其物理表示为000 020 ... 300 001 021 ... 283 303 004 ... 319 320 340 ... ... 339 359 ... 639。可以理解成把一个batch的一个像素的所有通道先排列起来,然后排下一个像素。n=0排列完成后,再排n=1。

同理CHWN其逻辑表示,先沿N方向,再沿W方向,再沿H方向,最后是沿C方向。

l [c:0] 表示从N方向,从n=0的第一个像素到n=1的第一个像素。

l [c:1] 表示从N方向,从n=1的第一个像素回到n=0的第二个像素。

l [c:2] 表示在H方向,从n=1的第一行最后一个像素回到n=0的第二行第一个像素。

l [c:3] 表示从N方向,从n=1的第一个通道最后一个像素回到n=0的第二个通道第一个像素。

CHWN其物理表示为 000 032 001 321 ... 003 323 004 324 ... ... 019 339 020 ...。可以理解成先把一个batch中N幅图像的第一个通道第一个像素排列起来,然后排第二个像素;再排第二个通道,第三个通道...

数据在内存中的偏移地址

深度学习中涉及大量的数据计算,计算需要从内存中取出数据,因此需要计算出数据的偏移地址以便进行取数。有了上面的逻辑表示和物理表示,可以推导出4维逻辑表示(n,c,h,w)映射到一维内存中偏移地址的公式。

定义位置(n,c,h,w)表示第n个batch的第c通道的第h行的第w列,那么该位置在不同数据格式下,在内存中的偏移地址计算公式如下: NCHW: offset_nchw(n, c, h, w) = n * CHW + c * HW + h * W + w NHWC: offset_nhwc(n, c, h, w) = n * HWC + h * WC + w * C + c CHWN: offset_chwn(n, c, h, w) = c * HWN + h * WN + w * N + n 其中N、C、H、W为常量,n、c、h、w为变量。

在NCHW中,CHW=C*H*W,表示一个Batch,可以理解成一个BGR 3通道的图片,表达的是一个立方体。HW=H*W,表示一个平面,可以理解成是BGR 3通道图片的一个通道(灰度图就是一个通道图片)。W是一行,可以理解成一个通道上的一行。

上图为例,如果想计算出绿色圈,即341的位置(n=1,c=1, h=0, w=1)。我们需要先跳过n=0的数据(CHW),图中箭头1指向的蓝色框区域;再跳过n=1的第一个通道(HW),图中箭头2指向蓝色框区域;这时进入到了n=1的第二个通道,跳过h=0行(0*W);最后跳过w个数到达偏移位置。

EdgeBoard为何使用NHWC

下面来分析EdgeBoard选择NHWC数据格式的原因。

上图表示卷积的计算过程。根据卷积的运算特点,相同位置窗口所有通道数与卷积的参数相乘后累加,可以有下面两种计算方式:

·   先通道后像素:先把一个像素点的所有通道数与卷积的参数相乘后累加,再进行下一个像素,直到卷积核窗口乘累加完成。比如第一次滑窗的计算公式: (w0,0,0)*(x0,0,0) + (w1,0,0)*(x1,0,0) + (w2,0,0)*(x2,0,0) + (w0,0,1)*(x0,0,1) + (w1,0,1)*(x1,0,1) + (w2,0,1)*(x2,0,1) + (w0,0,2)*(x0,0,2) + (w1,0,1)*(x1,0,2) + (w2,0,2)*(x2,0,2) + (w0,1,0)*(x0,1,0) + (w1,1,0)*(x1,1,0) + (w2,1,0)*(x2,1,0) + (w0,1,1)*(x0,1,1) + (w1,1,1)*(x1,1,1) + (w2,1,1)*(x2,1,1) + (w0,1,2)*(x0,1,2) + (w1,1,1)*(x1,1,2) + (w2,1,2)*(x2,1,2) + (w0,2,0)*(x0,2,0) + (w1,2,0)*(x1,2,0) + (w2,2,0)*(x2,2,0) + (w0,2,1)*(x0,2,1) + (w1,2,1)*(x1,2,1) + (w2,2,1)*(x2,2,1) + (w0,2,2)*(x0,2,2) + (w1,2,1)*(x1,2,2) + (w2,2,2)*(x2,2,2) = 0*-1 + 0*-1 + 0*0 + 0*1 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*1 + 1*0 + 2*1 + 1*0 + 0*0 +1*0 + 0*0 + 0*0 + 0*1 + 2*1 + 0*-1 + 1*-1 + 2*1 + 0*0 + 0*-1 + = 5

·   先像素后通道:先把一个通道滑动窗口与卷积参数相乘后累加,再进行下一个通道,直到所有通道乘累加完成。比如第一次滑窗计算公式:

(w0,0,0)*(x0,0,0) + (w0,0,1)*(x0,0,1) + (w0,0,2)*(x0,0,2) + (w0,1,0)*(x0,1,0) + (w0,1,1)*(x0,1,1) + (w0,0,2)*(x0,1,2) + (w0,2,0)*(x0,2,0) + (w0,0,1)*(x0,2,1) + (w0,0,2)*(x0,2,2) + (w1,0,0)*(x1,0,0) + (w1,0,1)*(x1,0,1) + (w1,0,2)*(x1,0,2) + (w1,1,0)*(x1,1,0) + (w1,1,1)*(x1,1,1) + (w1,0,2)*(x1,1,2) + (w1,2,0)*(x1,2,0) + (w1,0,1)*(x1,2,1) + (w1,0,2)*(x1,2,2) + (w2,0,0)*(x2,0,0) + (w2,0,1)*(x2,0,1) + (w2,0,2)*(x2,0,2) + (w2,1,0)*(x2,1,0) + (w2,1,1)*(x2,1,1) + (w2,0,2)*(x2,1,2) + (w2,2,0)*(x2,2,0) + (w2,0,1)*(x2,2,1) + (w2,0,2)*(x2,2,2) = 0*-1 + 0*1 + 0*0 + 0*0 +0*1 + 1*0 + 0*0 + 2*1 + 2*1 + 0*-1 + 0*-1 + 0*0 + 0*0 + 1*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 0*0 + 0*0 + 0*-1 + 0*0 + 2*1 + 1*0 + 0*1 + 1*-1 + 0*-1 = 5

可以看出两种方式计算的结果是一样。

而对于NHWC格式,即先通道后像素,是把一个像素的所有通道的数据放在一起。这样对应上图第一个像素的3个通道值,第二个像素的3个通道值,第三个像素的3个通道值,它们在内存中的地址都是连续的,也就是说一次就可以把kernel第一行需要计算的数取出,3x3的kernel需要3次取数。

而对于NCHW格式,即先像素后通道,是把一个通道的所有像素按顺序排列,这样对于一个3*3的卷积核,需要每取3个数就需要跳跃n个数后,再取3个数。一个通道需要取3次,3个通道需要取9次。

在实际网络中,通常通道数会远大于卷积kernel数(不会像上图只有3个通道,通常是几十、几百个通道)。这样对于NHWC格式来说说,取数的次数会比NCHW少很多。对EdgeBoard来说,为了增加其所支持网络的广泛性,减少对大输入尺寸和高存储量权重网络的限制,采用NHWC的格式可以实现分批次地把Feature Map和Weight数据读取到FPGA的片上缓存,例如对于3x3的Kernel,我们可以只读取三行(3WC)Feature Map的数据到FPGA内进行计算,即可得到一行输出数据,并传输到片外大容量缓存DDR,而不需依赖下一个3WC的Feature Map输入数据就可完成每一批次的输入输出数据传输。

再例如,我们也可将Weight数据根据FPGA片上缓存的不同大小分割成N份,一份一份发送到FPGA分别做卷积运算后,再传输回DDR做相应拼接,这等同于做一次大的卷积运算,好处在于可以根据不同容量的FPGA器件做不同匹配,大大提高了代码的硬件适配性。此外,由于C维度之间数据相关性较弱,采用NHWC格式更能发挥FPGA高并行度的计算特点,充分利用FPGA的算力。

下表为EdgeBoard使用NHWC数据格式的网络性能:

网络

输入尺寸

单帧耗时

resnet50

224 x 224

19ms

resnet101

224 x 224

33ms

mobilenet-v1

224 x 224

5ms

vgg-ssd

300 x 300

89ms

inception-v2

299 x 299

19ms

inception-v3

299 x 299

31ms

inception-v4

299 x 299

61ms

mobilenet-ssd

224 x 224

21ms

mobilenet-ssd-640

640 x 640

59ms

yolo-v3

416 x 416

98ms

福利

据可靠消息:EdgeBoard正在打折中,历史最低价,直降1000元,有兴趣可以看看:https://aim.baidu.com/product/5b8d8817-9141-4cfc-ae58-640e2815dfd4

参考链接:

[1] Intel MKL github

https://intel.github.io/mkl-dnn/understanding_memory_formats.html

[2] 零基础入门深度学习

https://www.zybuluo.com/hanbingtao/note/485480

深入浅出理解EdgeBoard中NHWC数据格式的更多相关文章

  1. 深入浅出理解c++虚函数

    深入浅出理解c++虚函数   记得几个月前看过C++虚函数的问题,当时其实就看懂了,最近笔试中遇到了虚函数竟然不太确定,所以还是理解的不深刻,所以想通过这篇文章来巩固下. 装逼一刻: 最近,本人思想发 ...

  2. 简单理解jQuery中$.getJSON、$.get、$.post、$.ajax用法

    在WEB开发中异步请求方式普遍使用,ajax技术减少程序员的工作量,也提升用户交互体验.AJAX的四种异步请求方式都能实现基本需求,闲话不多说,直接切入正题. 1.$.getJSON $.getJSO ...

  3. [转]深入理解MFC中程序框架

    最近抽空复习了一下MFC的内容,觉得一篇博文写的不错. 原文内容太多直接给出链接吧:深入理解MFC中程序框架 链接2:深入浅出话VC++(2)——MFC的本质 链接3:MFC单文档/视图结构穷追猛打

  4. 深入浅出spring IOC中三种依赖注入方式

    深入浅出spring IOC中三种依赖注入方式 spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和 ...

  5. 深入浅出C/C++中的正则表达式库

    写在前面: 本文是面向有正则表达式基础的读者朋友的,如果你还不知道正则表达式是什么,请先到这里学习一下  :http://en.wikipedia.org/wiki/Regular_expressio ...

  6. 转:深入浅出spring IOC中四种依赖注入方式

    转:https://blog.csdn.net/u010800201/article/details/72674420 深入浅出spring IOC中四种依赖注入方式 PS:前三种是我转载的,第四种是 ...

  7. 【转】在Python的struct模块中进行数据格式转换的方法

    这篇文章主要介绍了在Python的struct模块中进行数据格式转换的方法,文中还给出了C语言和Python语言的数据类型比较,需要的朋友可以参考下 Python是一门非常简洁的语言,对于数据类型的表 ...

  8. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  9. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

随机推荐

  1. 个人使用的lilypond第一个模板

    手残非要用lilypond打谱真是…… 可是lilypond又能满足各种细节标记和谱文混排,这是musescore达不到的 所以还是开这个坑,希望能逐渐自己有能力编写自己的音乐教材 个人用Fresco ...

  2. Go中的文件读写

    在 Go 语言中,文件使用指向 os.File 类型的指针来表示的,也叫做文件句柄 .我们来看一下os包的使用方式. 1.读取文件 os包提供了两种打开文件的方法: Open(name string) ...

  3. 消息中间件-activemq入门(二)

    上一节我们了解了JMS规范并且知道了JMS规范的良好实现者-activemq.今天我们就去了解一下activemq的使用.另外我们应该抱着目的去学习,别忘了我们为什么要使用消息中间件:解耦系统之间的联 ...

  4. alluxio源码解析-netty部分(2)

    netty简介 Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端.   netty作为alluxio中重要的通讯组件 在常见的客户端上传,下载中,都会有n ...

  5. red hat enterprise Linux 64 bit 配置IP

    在win7 64位操作系统的台式机器上,安装了VMware® Workstation,9.0.1 build-894247.新建一个虚拟机安装linux.具体过程请搜索相关文档.安装的时候选择的网络连 ...

  6. JavaWeb——JSP表达式语言(EL)

    1.JSP表达式语言(EL)用于在jsp从访问存储在JavaBean中的数据,例如 User ID: ${user.userId}<br /> 这里的${user.userId}就是JSP ...

  7. Java 复制PDF文档的2种方法

    本文将介绍通过Java程序来复制PDF页面,包括: 跨文档复制,即从文档1复制到文档2 在同一文档内复制,即从页面A复制到页面B 使用工具:Free Spire.PDF for Java (免费版) ...

  8. SonarQube系列三、Jenkins集成SonarQube(dotnetcore篇)

    [前言] 本系列主要讲述sonarqube的安装部署以及如何集成jenkins自动化分析.netcore项目.目录如下: SonarQube系列一.Linux安装与部署 SonarQube系列二.分析 ...

  9. 三步理解--门控循环单元(GRU),TensorFlow实现

    1. 什么是GRU 在循环神经⽹络中的梯度计算⽅法中,我们发现,当时间步数较⼤或者时间步较小时,循环神经⽹络的梯度较容易出现衰减或爆炸.虽然裁剪梯度可以应对梯度爆炸,但⽆法解决梯度衰减的问题.通常由于 ...

  10. Ubuntu 17 安装Chrome浏览器

    1.进入下载文件存放目录 cd Downloads 2.下载chrome文件 2.1 32位使用如下命令 wget https://dl.google.com/linux/direct/google- ...