背景:对于FPGA加速模块的使用,除了知道如何设置一些宏变量和全局变量之外,对于卷积核权值的存储和输入数据的存储顺序是另外一个非常重要的问题。为了尽快将其源码移植到自己的项目中,需要构造这两个部分,原论文中是使用的python脚本将caffermode转化成相应的weight.bin。那么,如果对于非caffemodel,或者想对自己的CNN程序进行局部加速,怎么办?这里就需要搞清楚weights.bin和input.bin二者是如何排列的。
方法:搜索作者github->issue,发现蛛丝马迹
(一)首先对于input.bin的结构 https://github.com/dgschwend/zynqnet/issues/4
研读代码之后发现,
for y in range(H):
for x in range(W):
for c in range(CH):
pixel = data[c,x,y]
if pixel is None: pixel = 99999
pixels.append(pixel);
对于input的存储并不是,按照每个通道,一个通道一个通道来存储的。而是先存储所有通道的(0,0)坐标位置的元素,再存储(0,1)坐标的元素,再存储(0,2)的元素,直到(0,w),然后是(1,0),(1,1),……(1,w)直到(h,w)。这样则完成了所有通道的输入。
(二)对于weights.bin的结构 https://github.com/dgschwend/zynqnet/issues/12
# Functions to Reshape and Save given Weight/Bias Blob
def append_filters(weights, blob):
ch_out = blob.shape[0]
ch_in = blob.shape[1]
kernel = blob.shape[2]
for ci in range(ch_in):
for co in range(ch_out):
for ky in range(kernel):
for kx in range(kernel):### BEWARE: X, Y MIGHT BE SWITCHED!
weights.append(blob.data[co][ci][kx][ky])
def append_bias(weights, blob):
ch_out = blob.shape[0]
for co in range(ch_out):
weights.append(blob.data[co])
# Append Weights and Biases to
append_filters(weights, params[0])
append_bias(weights, params[1])
查看源码发现,是先存储卷积核系数,然后再存储偏倚;
假设卷积核是64*16*3*3,即输出64通道,卷积核个数是64,每个卷积核是16通道,然后3×3大小,卷积核系数的存储是先存储卷积核0的0通道的k*k个权值,再保存卷积核1的0通道的k*k个权值,直到保存所有的卷积核的0通道的k个权值,然后再保存卷积核0的1通道,卷积核1的1通道,卷积核2的1通道,直到所有卷积核的1通道,不断这样循环,直到所有卷积核的ci个通道保存完成。
对于darknet中,每个层的权值l.weights是如何保存的?
// 1. Convolution !!!
#ifndef GEMMCONV
int fil;
// filter index
#pragma omp parallel for // "omp parallel for" - automatic parallelization of loop by using OpenMP
for (fil = 0; fil < l.n; ++fil) {
int chan, y, x, f_y, f_x;
// channel index
for (chan = 0; chan < l.c; ++chan)
// input - y
for (y = 0; y < l.h; ++y)
// input - x
for (x = 0; x < l.w; ++x)
{
int const output_index = fil*l.w*l.h + y*l.w + x;
int const weights_pre_index = fil*l.c*l.size*l.size + chan*l.size*l.size;
int const input_pre_index = chan*l.w*l.h;
float sum = 0;
// filter - y
for (f_y = 0; f_y < l.size; ++f_y)
{
int input_y = y + f_y - l.pad;
// filter - x
for (f_x = 0; f_x < l.size; ++f_x)
{
int input_x = x + f_x - l.pad;
if (input_y < 0 || input_x < 0 || input_y >= l.h || input_x >= l.w) continue;
int input_index = input_pre_index + input_y*l.w + input_x;
int weights_index = weights_pre_index + f_y*l.size + f_x;
sum += state.input[input_index] * l.weights[weights_index];
}
}
// l.output[filters][width][height] +=
// state.input[channels][width][height] *
// l.weights[filters][channels][filter_width][filter_height];
l.output[output_index] += sum;
}
}
从以上计算可以看出l.weights的的保存顺序是按照不同卷积划分的,卷积核索引作为第一个维度,然后是通道索引,然后是行,然后是列。state.input是按照通道排列的,先存放第一个通道,然后是第二个通道。。。存放第一个通道的时候,按照行来存储。
下面的问题就是如何将darknet中的权值和输入按照zynqnet的权值和输入方式进行转换,构造zynqnet可以接受的权值和输入~