Deep Learning - Darknet to Keras

darknet模型转换为Keras的.h5模型

Posted by vhpg on June 3, 2019

本篇文章观点仅限于目前的理解,后续若有新的理解,还会继续更新。

1. Darknet (.cfg & .weights)

darknet中,网络配置存储在.cfg文件中,权重等参数存储在.weights文件中,在.weights文件中,所有参数以二进制形式存储。下面根据darknet源码来分析参数的存储顺序。

网络训练的入口为./examples/detector.c/train_detector(),该函数调用了save_weights()函数来保存权重,save_weights()函数位于./src/parser.c中,通过逐层查找可发现,最终用于保存权重的是save_convolutional_weights()save_connected_weights()等函数。

save_weights()中,先保存了四项头文件,代码如下,前三个参数暂时不清楚表示什么含义,最后一个参数net->seen相当于epoch

int major = 0;
int minor = 2;
int revision = 0;
fwrite(&major, sizeof(int), 1, fp);
fwrite(&minor, sizeof(int), 1, fp);
fwrite(&revision, sizeof(int), 1, fp);
fwrite(net->seen, sizeof(size_t), 1, fp);

save_convolutional_weights()的部分代码如下,其中l.newights为当前卷积层所有卷积权重的个数,l.n为当前卷积层卷积核的个数,fwrite()函数第一个参数指向src,最后一个参数指向dst。 可以看出,在卷积层,参数保存的顺序为bias BN filters,对于filters,它的shape[out_dim, in_dim, height, width]out_dim即为l.nin_dim为输入的channel个数。

void save_convolutional_weights(layer l, FILE *fp)
{
    int num = l.nweights;
    fwrite(l.biases, sizeof(float), l.n, fp);
    if (l.batch_normalize){
        fwrite(l.scales, sizeof(float), l.n, fp);
        fwrite(l.rolling_mean, sizeof(float), l.n, fp);
        fwrite(l.rolling_variance, sizeof(float), l.n, fp);
    }
    fwrite(l.weights, sizeof(float), num, fp);
}

全连接层的权重存储与此类似,存储顺序为bias weights BN

根据以上分析,结合.cfg中网络的具体结构,通过依次读取相应的字节数,可分别取出各层的参数。

2. Keras (.h5)

Keras的模型使用hdf5格式进行存储,HDF是一种为存储为存储和处理大容量科学数据而设计的文件格式和相应的库文件,当前较为流行的版本是HDF5。python中可使用h5py模块来操作HDF5数据。

Keras中提供了get_weights()函数查看数据:

for layer in model.layers:
  weights = layer.get_weights()  # list of numpy array

也可以通过h5py模块读取数据,具体操作代码如下:

import h5py

def print_structure(weight_file_path):
    """
    Prints out the structure of HDF5 file.

    Args:
      weight_file_path (str) : Path to the file to analyze
    """
    f = h5py.File(weight_file_path)
    try:
        if len(f.attrs.items()):
            print("{} contains: ".format(weight_file_path))
            print("Root attributes:")
        for key, value in f.attrs.items():
            print("  {}: {}".format(key, value))

        if len(f.items())==0:
            return

        for layer, g in f.items():
            print("  {}".format(layer))
            print("    Attributes:")         # 权重名称及一些参数配置属性
            for key, value in g.attrs.items():
                print("      {}: {}".format(key, value))

            print("    Dataset:")
            for p_name in g.keys():
                param = g[p_name]
                for k_name in param.keys():
                    print("      {}/{}: {}".format(p_name, k_name, param.get(k_name)[:]))
    finally:
        f.close()

输入大致如下:

layer_0
  Attributes:
    nb_params: 2
    subsample: [1 1]
    init: glorot_uniform
    nb_filter: 32
    name: Convolution2D
    activation: linear
    border_mode: full
    nb_col: 3
    stack_size: 3
    nb_row: 3
  Dataset:
    param_0: (32, 3, 3, 3)
    param_1: (32,)
layer_1
  Attributes:
    nb_params: 0
    activation: relu
    name: Activation
  Dataset:
layer_2
  Attributes:
    nb_params: 2
    subsample: [1 1]
    init: glorot_uniform
    nb_filter: 32
    name: Convolution2D
    activation: linear
    border_mode: valid
    nb_col: 3
    stack_size: 32
    nb_row: 3
  Dataset:
    param_0: (32, 32, 3, 3)
    param_1: (32,)
layer_3
  Attributes:
    nb_params: 0
    activation: relu
    name: Activation
  Dataset:
layer_4
  Attributes:
    nb_params: 0
    name: MaxPooling2D
    ignore_border: True
    poolsize: [2 2]
  Dataset:

可以看出,对keras来说,hdf5文件存储了包括网络配置和参数的所有信息:第一部分为文件的整体信息,第二部分为各层的配置Attributes和参数Dataset

3. Darknet to Keras

根据以上的分析,将darknet的模型转换为keras模型就比较简单,思路为,首先根据darknet的.cfg文件来逐层读取.weights权重,然后根据这些信息新建keras的层,使用set_weights()或直接在建层时将权重赋上去,最后将建立好的model保存为.h5模型。

具体的转换代码可参考这里,基本思路与上面的描述类似。 此处留有一个疑问,即在给BN层赋权重时,将卷积层的偏置参数也加进来了,此处应该是有问题的:

bn_weight_list = [
            bn_weights[0],  # scale gamma
            conv_bias,  # shift beta
            bn_weights[1],  # running mean
            bn_weights[2]  # running var
        ]

Reference

.h5 code