darknet中weights⽂件存储格式
以下内容根据个⼈理解整理⽽成,如有错误,欢迎指出,不胜感激。
0. 写在前⾯
本⽂对darkent保存的.weights⽂件进⾏分析,以便后续将权值进⾏导出。
复习所涉及的c语⾔知识:sprinf(), fwrite()&fread(), FILE类型
.weights中权值的存储格式
1. sprinf(), fwrite()&fread(), FILE类型
sprinf():
sprinf将⼀个格式化的字符串输出到⼀个⽬的字符串buff中:
// 在darknet中的使⽤
char buff[256];
sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);fopen 创建文件
fwrite()&fread():
fwrite以⼆进制⽅式向⽂件流中写⼊数据:
// buffer: 数据源地址
// size:  每个单元字节数
// count: 总计单元数
// stream: ⽂件流指针
size_t fwrite(void* buffer, size_t size, size_t count, FILE * stream);
// 在darknet中的使⽤
fwrite(l.weights, sizeof(float), num, fp);
fread与fwrite类似,只不过buffer变为⽬的地址
size_t fread(void* buffer, size_t size, size_t count, FILE * stream);
FILE:
使⽤fopen( )函数可以创建⼀个新的⽂件或者打开⼀个已有的⽂件,这个调⽤会初始化⼀个FILE类型的对象,FILE类型包含了所有⽤来控制流的必要的信息。
FILE *fp = fopen(filename, "wb");
int b = fclose( FILE *fp );
2. .weights中权值的存储格式
以下内容主要以conv层为例
在detector.c的train_detector()函数末尾,保存权值的相关代码如下:
// buff只是⼀个字符串,并没有实际创建⽂件
char buff[256];
sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);
save_weights(net, buff);
通过向上追溯,可以在parser.c中到函数save_weights(),并进⼀步追踪到save_weights_upto(),主要代码注释如下:
// *filename: 即为前⾯的buff字符串
// cutoff: ⽹络层数
void save_weights_upto(network net, char *filename, int cutoff)
{
// 初始化⼀个⽂件读写流
FILE *fp = fopen(filename, "wb");
if(!fp) file_error(filename);
// 以下三个变量在version.h中定义
// #define MAJOR_VERSION 0
// #define MINOR_VERSION 2
// #define PATCH_VERSION 5
int major = MAJOR_VERSION;
int minor = MINOR_VERSION;
int revision = PATCH_VERSION;
fwrite(&major, sizeof(int), 1, fp);
fwrite(&minor, sizeof(int), 1, fp);
fwrite(&revision, sizeof(int), 1, fp);
// net.seen⽤于记录训练时⼀共经历了多少张图⽚
// 可根据该参数及cfg中对batch的配置,得出当前迭代次数
fwrite(net.seen, sizeof(uint64_t), 1, fp);
/
/ 逐层保存权值
int i;
for(i = 0; i < net.n && i < cutoff; ++i){
layer l = net.layers[i];
pe == CONVOLUTIONAL && l.share_layer == NULL){
save_convolutional_weights(l, fp);
} pe == CONNECTED){
save_connected_weights(l, fp);
} pe == BATCHNORM){
save_batchnorm_weights(l, fp);
}
}
fclose(fp);
}
在具体分析save_convolutional_weights()函数之前,⾸先要分析convolutional_layer.c中的make_convolutional_layer()函数,该函数根据每个卷积层的配置,为当前层参数分配相应数量的内存,主要代码注释如下:
// 卷积核个数
l.n = n;
// 卷积核权重总个数:n*c*size*size  groups是分组卷积时的参数,默认为1
l.nweights = (c / groups) * n * size * size;
// 为卷积核权值、偏置、BN参数分配内存
l.weights = (float*)calloc(l.nweights, sizeof(float));
l.biases = (float*)calloc(n, sizeof(float));
l.scales = (float*)calloc(n, sizeof(float));
再来看parser.c中的save_convolutional_weights()函数就⽐较容易理解:
void save_convolutional_weights(layer l, FILE *fp)
{
int num = l.nweights;
// 有BN的卷积层应该是不需要bias的
fwrite(l.biases, sizeof(float), l.n, fp);
if (l.batch_normalize){
fwrite(l.scales, sizeof(float), l.n, fp);
lling_mean, sizeof(float), l.n, fp);
lling_variance, sizeof(float), l.n, fp);
}
fwrite(l.weights, sizeof(float), num, fp);
}
3. 总结
从以上分析可以看出,.weights⽂件实际上就是⼀个字节流,我们只需要根据其保存时的顺序,每次读取相应字节数量的内容即可将其解析出来。
要注意,对卷积权重,这⾥是⼀维形式进⾏存储,就相当于将⼀个N*C*H*W的tensor展开成⼀维向量。
Reference