修改DarkNet的.weights文件以编辑模型版本号
0. 使用场景
我们使用深度学习网络来进行图像识别或分类时,有时会需要进行多版本的管理. 当使用开源的darknet网络进行深度模型训练时,可以在.weights文件中记录模型的版本信息.
1. 高阶做法
通过DarkNet的源代码设置版本号,即在模型训练中直接生成版本号.
控制.weights文件写入时,版本信息的代码段位于DarkNet源码中,parser.c文件下save_weights_upto函数
void save_weights_upto(network *net, char *filename, int cutoff)
{
#ifdef GPU
if(net->gpu_index >= 0){
cuda_set_device(net->gpu_index);
}
#endif
fprintf(stderr, "Saving weights to %s\n", filename);
FILE *fp = fopen(filename, "wb");
if(!fp) file_error(filename);
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);
int i;
for(i = 0; i < net->n && i < cutoff; ++i){
layer l = net->layers[i];
if (l.dontsave) continue;
if(l.type == CONVOLUTIONAL || l.type == DECONVOLUTIONAL){
save_convolutional_weights(l, fp);
} if(l.type == CONNECTED){
save_connected_weights(l, fp);
} if(l.type == BATCHNORM){
save_batchnorm_weights(l, fp);
} if(l.type == RNN){
save_connected_weights(*(l.input_layer), fp);
save_connected_weights(*(l.self_layer), fp);
save_connected_weights(*(l.output_layer), fp);
} if (l.type == LSTM) {
save_connected_weights(*(l.wi), fp);
save_connected_weights(*(l.wf), fp);
save_connected_weights(*(l.wo), fp);
save_connected_weights(*(l.wg), fp);
save_connected_weights(*(l.ui), fp);
save_connected_weights(*(l.uf), fp);
save_connected_weights(*(l.uo), fp);
save_connected_weights(*(l.ug), fp);
} if (l.type == GRU) {
if(1){
save_connected_weights(*(l.wz), fp);
save_connected_weights(*(l.wr), fp);
save_connected_weights(*(l.wh), fp);
save_connected_weights(*(l.uz), fp);
save_connected_weights(*(l.ur), fp);
save_connected_weights(*(l.uh), fp);
}else{
save_connected_weights(*(l.reset_layer), fp);
save_connected_weights(*(l.update_layer), fp);
save_connected_weights(*(l.state_layer), fp);
}
} if(l.type == CRNN){
save_convolutional_weights(*(l.input_layer), fp);
save_convolutional_weights(*(l.self_layer), fp);
save_convolutional_weights(*(l.output_layer), fp);
} if(l.type == LOCAL){
#ifdef GPU
if(gpu_index >= 0){
pull_local_layer(l);
}
#endif
int locations = l.out_w*l.out_h;
int size = l.size*l.size*l.c*l.n*locations;
fwrite(l.biases, sizeof(float), l.outputs, fp);
fwrite(l.weights, sizeof(float), size, fp);
}
}
fclose(fp);
}
与版本相关的代码段为
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);
可以通过修改major
minor
revision
这三个变量的值来更改模型的版本号.
但让我难以理解的是,为什么在源码中版本号是写死的——限于我的水平,暂时没有找到通过接口进行修改的方法.
如此这般,虽然可以通过改写源码中的版本号,然后再编译、重新训练的方式控制新生成的模型的版本,但如果还是沿用写死版本号的方式,未免太麻烦了.
本文仅指出版本号可以修改的位置,至于如何开放接口,或者如何使用系统时间自动生成版本号的方法不在本文中进行讨论.如读者有兴趣可以在源码基础上进行修改.
2. 低阶做法
利用二进制文本编辑给训练得到的模型文件赋版本号.
DarkNet的.weights文件中,头一行160位(如果你是64位机器)是用来记录版本信息和图片训练张数的.
从源码中可以获知,分别是
- int32位的主版本号、
- int32位的次版本号、
- int32位的修订版本号
- 及long long unsigned int 64位的图片训练张数(注意最后一个值是低32位在前,高32位在后).
方法思路如下:
- 正常训练得到.weights模型文件
- 用二进制文本编辑器打开.weights模型文件
- 对.weights模型文件中的信息行进行修改
- 保存修改
这条方法在没有能力通过高阶方法(源码)对模型文件赋版本号时可以采用,虽然low了一些,但可以得到带有版本信息的模型文件.
这里的核心是需要用一个二进制文本编辑器打开并修改.weights模型文件,可以采纳的工具包括Notepad++(添加插件模式)、sublime、ultraedit等.
接下来描述使用vscode插件的方式进行修改:
-
在vscode的插件扩展中查找hexdump插件;
-
安装并启用插件后,用vscode打开.weights文件
-
右键,通过hexdump方式打开文件
- 打开文件后,注意观察 .weights文件的相关信息区域,在第一区域的第一行,为版本相关信息(第二区域看上去是一堆乱码,可能是对第一区域内容的注释,但具体是什么,并不清楚,暂时不去管它)
-
比如说需要修改主版本号,主版本号要修改的位置上右键,选择"Edit Value Under Cursor"
-
修改好之后,被修改的这个位置上的十六进制数字会显示为红色
- 之后再次在空白区域右键,选择"Export to Binary File"
至此,即完成了对模型文件的版本号记录. 通常我们可以通过对主、次、修订版本号的排列组合来管理模型的版本.