【darknet源码】:导入训练数据

darknet源码中的权重读取由函数load_network()中的load_weight函数搞定。
导入的数据的结构体信息见:【darknet源码】:网络核心结构体
整体调用流程:
detector.c–>data.c/load_data()–>data.c/load_threads()–>data.c/load_data_in_thread()–>data.c/load_thread()
分支路线流程:
data.c/load_threads()中还会对数据进行concat和free操作。
concat操作:data.c/load_threads()–>data.c/concat_datas()–>data.c/concat_data()–>data.c/concat_matrix()
free操作:data.c/free_data()–>matrix.c/free_matrix()
分支源代码见:【darknet源码】:导入训练数据(分支)

1.detector.c
pthread_t load_thread = load_data(args);
2.data.c/load_data()
/*
** 开辟线程,读入一次迭代所需的所有图片数据:读入图片的张数为args.n = net.batch * net.subdivisions * ngpus,读入数据将存入args.d中(虽然args是按值传递的,但是args.d是指针变量,函数内改变args.d所指的内容在函数外也是有效的)
** 输入: args    包含要读入图片数据的信息(读入多少张,开几个线程读入,读入图片最终的宽高,图片路径等等)
** 返回: 创建的读取数据的线程id,以便于外界控制线程的进行
** 说明: darknet作者在实现读入图片数据的时候,感觉有点绕来绕去的(也许此中有深意,只是我还未明白~),
**       总的流程是:load_data(load_args args)->load_threads(load_args* ptr)->load_data_in_thread(load_args args)->load_thread(load_args* ptr),
**       load_thread()函数中会选择具体的读入函数,比如load_data_detection(),要强调的是,所有这些函数,都会输入load_args类型参数,或者是其指针,
**       而且很多函数开头就会新建一个args,并且深拷贝传入的args,而后返回新建的args,同时可能会随意改动甚至释放传入的args,这一整串流程只要记住一点:不管args在之后的函数中怎么变,
**       其中的指针变量所指的内存块其实都是一样的(所以不管在哪个函数中,不要随意深层释放args.d的数据,除非是真正的用完了,而这里还处于数据读入阶段,所以决不能深层释放args.d);
**       而非指针变量,每个函数都不尽相同,但是没有关系,因为load_data()函数中传入的args是按值传递(不是指针)的,不管之后的函数怎么改动args的非指针变量,都不会影响load_data()函数外的值。
** 碎碎念(TODO):又是按值传递的哎,虽然上面说道因为按值传递使得更改args非指针变量不会影响外界的args,但是传入指针也可以做到啊,反正一进load_data()就深拷贝了args给ptr,而load_data()也没改动args的非指针变量。
*/
pthread_t load_data(load_args args)
{
    // 定义一个线程id
    pthread_t thread;
    // 深拷贝args到ptr(虽说是深拷贝,但要注意args还有很多的指针变量,所有这些指针变量,ptr与args都指向同一内存块)
    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args;

    // 创建相应线程,并绑定load_threads()函数到该线程上,第二参数是线程的属性,这里设置为0(即NULL),第四个参数ptr就是load_threads()的输入参数
    if(pthread_create(&thread, 0, load_threads, ptr)) error("Thread creation failed");
    // 返回创建的线程id
    return thread;
}
3.data.c/load_threads()
/*
** 开辟多个线程读入图片数据,读入数据存储至ptr.d中(主要调用load_in_thread()函数完成)
** 输入: ptr    包含所有线程要读入图片数据的信息(读入多少张,开几个线程读入,读入图片最终的宽高,图片路径等等)
** 返回: void*  万能指针(实际上不需要返回什么)
** 说明: 1) load_threads()是一个指针函数,只是一个返回变量为void*的普通函数,不是函数指针
**       2) 输入ptr是一个void*指针(万能指针),使用时需要强转为具体类型的指针
**       3) 函数中涉及四个用来存储读入数据的变量:ptr, args, out, buffers,除args外都是data*类型,所有这些变量的
**          指针变量其实都指向同一块内存(当然函数中间有些动态变化),因此读入的数据都是互通的。
** 流程: 本函数首先会获取要读入图片的张数、要开启线程的个数,而后计算每个线程应该读入的图片张数(尽可能的均匀分配),
**       并创建所有的线程,并行读入数据,最后合并每个线程读入的数据至一个大data中,这个data的指针变量与ptr的指针变量
**       指向的是统一块内存,因此也就最终将数据读入到ptr.d中(所以其实没有返回值)
*/
void *load_threads(void *ptr)
{
    int i;
    // 先使用(load_args*)强转void*指针,而后取ptr所指内容赋值给args
    // 虽然args不是指针,args是深拷贝了ptr中的内容,但是要知道prt(也就是load_args数据类型),有很多的
    // 指针变量,args深拷贝将拷贝这些指针变量到args中(这些指针变量本身对ptr来说就是内容,
    // 而args所指的值是args的内容,不是ptr的,不要混为一谈),因此,args与ptr将会共享所有指针变量所指的内容
    load_args args = *(load_args *)ptr;
    if (args.threads == 0) args.threads = 1;
    // 另指针变量out=args.d,使得out与args.d指向统一块内存,之后,args.d所指的内存块会变(反正也没什么用了,变就变吧),
    // 但out不会变,这样可以保证out与最原始的ptr指向同一块存储读入图片数据的内存块,因此最终将图片读到out中,
    // 实际就是读到了最原始的ptr中,比如train_detector()函数中定义的args.d中
    data *out = args.d;
    // 读入图片的总张数= batch * subdivision * ngpus,可参见train_detector()函数中的赋值
    int total = args.n;
    // 释放ptr:ptr是传入的指针变量,传入的指针变量本身也是按值传递的,即传入函数之后,指针变量得到复制,函数内的形参ptr
    // 获取外部实参的值之后,二者本身没有关系,但是由于是指针变量,二者之间又存在一丝关系,那就是函数内形参与函数外实参指向
    // 同一块内存。又由于函数外实参内存是动态分配的,因此函数内的形参可以使用free()函数进行内存释放,但一般不推荐这么做,因为函数内释放内存,
    // 会影响函数外实参的使用,可能使之成为野指针,那为什么这里可以用free()释放ptr呢,不会出现问题吗?
    // 其一,因为ptr是一个结构体,是一个包含众多的指针变量的结构体,如data* d等(当然还有其他非指针变量如int h等),
    // 直接free(ptr)将会导致函数外实参无法再访问非指针变量int h等(实际经过测试,在gcc编译器下,能访问但是值被重新初始化为0),
    // 因为函数内形参和函数外实参共享一块堆内存,而这些非指针变量都是存在这块堆内存上的,内存一释放,就无法访问了;
    // 但是对于指针变量,free(ptr)将无作为(这个结论也是经过测试的,也是用的gcc编译器),不会释放或者擦写掉ptr指针变量本身的值,
    // 当然也不会影响函数外实参,更不会牵扯到这些指针变量所指的内存块,总的来说,
    // free(ptr)将使得ptr不能再访问指针变量(如int h等,实际经过测试,在gcc编译器下,能访问但是值被重新初始化为0),
    // 但其指针变量本身没有受影响,依旧可以访问;对于函数外实参,同样不能访问非指针变量,而指针变量不受影响,依旧可以访问。
    // 其二,darknet数据读取的实现一层套一层(似乎有点罗嗦,总感觉代码可以不用这么写的:)),具体调用过程如下:
    // load_data(load_args args)->load_threads(load_args* ptr)->load_data_in_thread(load_args args)->load_thread(load_args* ptr),
    // 就在load_data()中,重新定义了ptr,并为之动态分配了内存,且深拷贝了传给load_data()函数的值args,也就是说在此之后load_data()函数中的args除了其中的指针变量指着同一块堆内存之外,
    // 二者的非指针变量再无瓜葛,不管之后经过多少个函数,对ptr的非指针变量做了什么改动,比如这里直接free(ptr),使得非指针变量值为0,都不会影响load_data()中的args的非指针变量,也就不会影响更为顶层函数中定义的args的非指针变量的值,
    // 比如train_detector()函数中的args,train_detector()对args非指针变量赋的值都不会受影响,保持不变。综其两点,此处直接free(ptr)是安全的。
    // 说明:free(ptr)函数,确定会做的事是使得内存块可以重新分配,且不会影响指针变量ptr本身的值,也就是ptr还是指向那块地址, 虽然可以使用,但很危险,因为这块内存实际是无效的,
    //      系统已经认为这块内存是可分配的,会毫不考虑的将这块内存分给其他变量,这样,其值随时都可能会被其他变量改变,这种情况下的ptr指针就是所谓的野指针(所以经常可以看到free之后,置原指针为NULL)。
    //      而至于free(ptr)还不会做其他事情,比如会不会重新初始化这块内存为0(擦写掉),以及怎么擦写,这些操作,是不确定的,可能跟具体的编译器有关(个人猜测),
    //      经过测试,对于gcc编译器,free(ptr)之后,ptr中的非指针变量的地址不变,但其值全部擦写为0;ptr中的指针变量,丝毫不受影响,指针变量本身没有被擦写,
    //      存储的地址还是指向先前分配的内存块,所以ptr能够正常访问其指针变量所指的值。测试代码为darknet_test_struct_memory_free.c。
    //      不知道这段测试代码在VS中执行会怎样,还没经过测试,也不知道换用其他编译器(darknet的Makefile文件中,指定了编译器为gcc),darknet的编译会不会有什么问题??
    //      关于free(),可以看看:http://blog.sina.com.cn/s/blog_615ec1630102uwle.html,文章最后有一个很有意思的比喻,但意思好像就和我这里说的有点不一样了(到底是不是编译器搞得鬼呢??)。
    free(ptr);

    // 每一个线程都会读入一个data,定义并分配args.thread个data的内存
    data *buffers = calloc(args.threads, sizeof(data));

    // 此处定义了多个线程,并为每个线程动态分配内存
    pthread_t *threads = calloc(args.threads, sizeof(pthread_t));
    for(i = 0; i < args.threads; ++i){
        // 此处就承应了上面的注释,args.d指针变量本身发生了改动,使得本函数的args.d与out不再指向同一块内存,
        // 改为指向buffers指向的某一段内存,因为下面的load_data_in_thread()函数统一了结口,需要输入一个load_args类型参数,
        // 实际是想把图片数据读入到buffers[i]中,只能令args.d与buffers[i]指向同一块内存
        args.d = buffers + i;
        // 下面这句很有意思,因为有多个线程,所有线程读入的总图片张数为total,需要将total均匀的分到各个线程上,
        // 但很可能会遇到total不能整除的args.threads的情况,比如total = 61, args.threads =8,显然不能做到
        // 完全均匀的分配,但又要保证读入图片的总张数一定等于total,用下面的语句刚好在尽量均匀的情况下,
        // 保证总和为total,比如61,那么8个线程各自读入的照片张数分别为:7, 8, 7, 8, 8, 7, 8, 8
        args.n = (i+1) * total/args.threads - i * total/args.threads;
        // 开启线程,读入数据到args.d中(也就读入到buffers[i]中)
        // load_data_in_thread()函数返回所开启的线程,并存储之前已经动态分配内存用来存储所有线程的threads中,
        // 方便下面使用pthread_join()函数控制相应线程
        threads[i] = load_data_in_thread(args);
    }
    for(i = 0; i < args.threads; ++i){
        // 以阻塞的方式等待线程threads[i]结束:阻塞是指阻塞启动该子线程的母线程(此处应为主线程),
        // 是母线程处于阻塞状态,一直等待所有子线程执行完(读完所有数据)才会继续执行下面的语句
        // 关于多线程的使用,进行过代码测试,测试代码对应:darknet_test_pthread_join.c
        pthread_join(threads[i], 0);
    }
    // 多个线程读入所有数据之后,分别存储到buffers[0],buffers[1]...中,接着使用concat_datas()函数将buffers中的数据全部合并成一个大数组得到out
    *out = concat_datas(buffers, args.threads);
    // 也就只有out的shallow敢置为0了,为什么呢?因为out是此次迭代读入的最终数据,该数据参与训练(用完)之后,当然可以深层释放了,而此前的都是中间变量,
    // 还处于读入数据阶段,万不可设置shallow=0
    out->shallow = 0;

    // 释放buffers,buffers也是个中间变量,切记shallow设置为1,如果设置为0,那就连out中的数据也没了
    for(i = 0; i < args.threads; ++i){
        buffers[i].shallow = 1;
        free_data(buffers[i]);
    }
    // 最终直接释放buffers,threads,注意buffers是一个存储data的一维数组,上面循环中的内存释放,实际是释放每一个data的部分内存
    // (这部分内存对data而言是非主要内存,不是存储读入数据的内存块,而是存储指向这些内存块的指针变量,可以释放的)
    free(buffers);
    free(threads);
    return 0;
}
4.data.c/load_data_in_thread()
/*
** 创建一个线程,读入相应图片数据(此时args.n不再是一次迭代读入的所有图片的张数,而是经过load_threads()均匀分配给每个线程的图片张数)
** 输入: ptr    包含该线程要读入图片数据的信息(读入多少张,读入图片最终的宽高,图片路径等等)
** 返回: phtread_t   线程id
** 说明: 本函数实际没有做什么,就是深拷贝了args给ptr,然后创建了一个调用load_thread()函数的线程并返回线程id
*/
pthread_t load_data_in_thread(load_args args)
{
    pthread_t thread;
    // 同样第一件事深拷贝了args给ptr(为什么每次都要做这一步呢?求指点啊~)
    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args;
    // 创建一个线程,读入相应数据,绑定load_thread()函数到该线程上,第四个参数是load_thread()的输入参数,第二个参数表示线程属性,设置为0(即NULL)
    if(pthread_create(&thread, 0, load_thread, ptr)) error("Thread creation failed");
    return thread;
}
5.data.c/load_thread()
void *load_thread(void *ptr)
{
    //printf("Loading data: %d\n", rand());
    load_args a = *(struct load_args*)ptr;
    if(a.exposure == 0) a.exposure = 1;
    if(a.saturation == 0) a.saturation = 1;
    if(a.aspect == 0) a.aspect = 1;

    if (a.type == OLD_CLASSIFICATION_DATA){
        *a.d = load_data_old(a.paths, a.n, a.m, a.labels, a.classes, a.w, a.h);
    } else if (a.type == REGRESSION_DATA){
        *a.d = load_data_regression(a.paths, a.n, a.m, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
    } else if (a.type == CLASSIFICATION_DATA){
        *a.d = load_data_augment(a.paths, a.n, a.m, a.labels, a.classes, a.hierarchy, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure, a.center);
    } else if (a.type == SUPER_DATA){
        *a.d = load_data_super(a.paths, a.n, a.m, a.w, a.h, a.scale);
    } else if (a.type == WRITING_DATA){
        *a.d = load_data_writing(a.paths, a.n, a.m, a.w, a.h, a.out_w, a.out_h);
    } else if (a.type == REGION_DATA){
        *a.d = load_data_region(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
    } else if (a.type == DETECTION_DATA){
        *a.d = load_data_detection(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
    } else if (a.type == SWAG_DATA){
        *a.d = load_data_swag(a.paths, a.n, a.classes, a.jitter);
    } else if (a.type == COMPARE_DATA){
        *a.d = load_data_compare(a.n, a.paths, a.m, a.classes, a.w, a.h);
    } else if (a.type == IMAGE_DATA){
        *(a.im) = load_image_color(a.path, 0, 0);
        *(a.resized) = resize_image(*(a.im), a.w, a.h);
    } else if (a.type == LETTERBOX_DATA){
        *(a.im) = load_image_color(a.path, 0, 0);
        *(a.resized) = letterbox_image(*(a.im), a.w, a.h);
    } else if (a.type == TAG_DATA){
        *a.d = load_data_tag(a.paths, a.n, a.m, a.classes, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
    }
    free(ptr);
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yuanCruise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值