2

20181105 打卡

画的并不好看。都有点不想放上去。主要是用PS画的,眼睛的部分不会 = =,头发也有点乱。这个脸型花了很久,但是还是觉得似乎有点不对?

PS画完的图,导入CSP之后,居然显示效果不一样,这个有点神奇。练习1h多一点。

0

嗯,此刻一个非技术板块诞生了

先贴上一张昨晚的作品,这样这篇博客会自动有个插图。:P

左边的男生是想象着随便画的,右边的女生其实是参考的一个插画画的,画的和原图一点都不像就是了。

之前一直在博客里面记录一下自己学习的技术技能,最近写博客的频率已经降到了一个很可怕的数值。一方面是工作的内容过于枯燥,没有什么新技术的使用,不知道要写些什么有意思的内容,另一方面就是工作确实太忙了,写一篇博客需要至少半天的时间,平时肯定是没有的,周末又要陪女票大人出去逛街。这真是现实和生活之间的巨大的矛盾啊,果然有句话是对的:成人的世界里,没有容易二字。实在伐开心。 read more

0

_rebuild_tensor_v2?pytorch版本间模型兼容性脱坑实践

最近使用Pytorch 0.4.0 进行模型训练,之后使用一个转模型的工具时,报了一个错,就是标题里面的_rebuild_tensor_v2相关的错误。最后发现是本地使用的pytorch的版本是0.3.0,和0.4.0模型上不兼容。各论坛上的解决方案都是说pytorch版本不向后兼容,建议升级pytorch。无奈我这里不方便升级pytorch版本。那么问题就来了,有没有什么不需要修改pytorch源码,或是不升级pytorch,又能让老版本的pytorch读取新版本模型的方案呢?

当然是有的,而且工作量很小。

一、Pytorch模型存储和读取的流程

首先,我们使用pytorch存储模型会使用 torch.save 这个函数,直接将模型的state_dict()保存下来。类似下面的代码:

读取参数的代码也十分简单:

而低版本的pytorch就是在load_state_dict这里报了错。

二、State Dict

我们首先要知道,model.state_dict()的返回值究竟是什么。

这里我直接给出结论:

model.state_dict()的返回值是一个collections.OrderedDict对象,它的键是一个字符串,它的值是Tensor的对象。所以造成兼容性问题的其实是Tensor对象的不兼容。

那么是不是可以将Tensor转化成一个新的非Pytorch内置的数据类型呢?这样就可以避免兼容性问题。

numpy.ndarray就是我们需要的中间态。

三、模型转换

首先,我们需要将state_dict的参数转换成numpy.ndarray保存下来。这里使用高版本的pytorch。

之后,用低版本的pytorch载入这个numpy的state_dict。

四、总结

对于这个问题,还有很多的解决方案,这里是比较简单的一种。

PS. 这是目前为止,写的最快的一篇博客了。。。

 

转载请注明出处,谢谢!

0

C++ Boost JSON解析库的使用

最近在写一个C++项目的时候,有大量的配置信息,于是将这些配置信息整合进一个文本文件中,选择了JSON这种数据格式。C++在处理JSON数据的库有很多,比如Jsoncpp,Boost等,这个项目中由于本身就已经用到了Boost这个库,因此,也就选用Boost来进行JSON的解析了。

Boost的JSON解析,使用的是property_tree这个数据类型,它可以方便的解析XML和JSON。

一、Boost JSON解析库的几个注意事项

在具体介绍之前,必须要强调一下,这个库默认不是线程安全的!不是线程安全的!不是线程安全的!不做任何处理的情况下,如果直接在多线程的程序中使用Boost解析JSON,可能会在奇怪的时候报段错误。

这是由于Boost的JSON解析是基于SPIRIT语法解析的,而SPIRIT本身就不是线程安全的,我们如果需要它支持线程安全,就必须加入一个宏#define BOOST_SPIRIT_THREADSAFE,把它放在引用boost的头文件的最开始就行。理论上,在编译的时候加入宏也是可以的。

另一个需要注意的是,一般网上找的教程中,property_tree都是不支持unicode编码的,如果想要支持unicode,需要一些额外的操作。这个从网上可以查到,我尝试了一下,最终还是放弃了。取而代之的一个方案就是把中文的各种路径啥的,用软链接替换成英文和数字。之后世界就美好了。

二、boost::property_tree::ptree 类型

对于JSON或者XML,boost将他们解析之后都会生成一个ptree的数据结构。类似于下面的结构。

可以看出,这是一个很标准的树的结构。对于树中的每一个节点,都有自己的数值和子节点,每个子节点都有一个唯一的名字。data_type和key_type通常是std::string或std::wstring。如果希望处理unicode的字符串的话,就需要用到std::wstring了。下面的例子中,使用的全部都是std::string。

三、JSON文件的解析

首先,我们用一个小栗子,来介绍一下Boost是如何读取JSON数据的。

这里首先我们需要定义一个boost::property_tree::ptree类型的对象,之后通过boost::property_tree::read_json函数进行数据的读取,之后就可以使用各种ptree的接口进行数据的操作了。
在boost/property_tree/json_parser.hpp文件中我们可以看到读写JSON的一些接口。

它支持读写JSON,对于读取操作,它支持直接根据文件名称来加载JSON或者通过输入流来加载。输出也是相同。所以我们上面的Demo中,需要将字符串s转换成字符串流对象ss,之后才能进行加载。写文件支持写入到文件或者输出流中,最后一个bool值表示是否格式化输出json。

四、JSON对象的读取

我们知道JSON对象主要有两种格式:键值对和数组。JSON灵活就在于键值对的值还可以键值对或者数组,数组的每个元素也是。

那么我们分别介绍键值对和数组的数据获取方式。

1)键值对的解析

ptree支持一个操作叫做get_child,可以根据键的名字,来获取子节点。而且这个名字还可以是累加的。什么叫可以累加呢?我们看一下下面的代码:

输出的结果为:

get_child这个函数,可以根据节点的名字,获取到子节点的ptree对象。这个节点的名字可以使用.连接各个层级的名称。get_value<Type>方法,可以获取节点的值,并且转换成期望的数据类型。如果我们就是想获取节点的值。不期望有任何转换,可以使用data这个函数。

get_child要求输入的名称路径必须是存在的,否则会抛异常。如果我们不知道某个名称路径是否存在的话,可以使用get_child_optional这个函数,如果路径不存在,该函数会返回boost::null。get_child_optional返回的类似于指针的结构,如果需要获取值,可以用这样的写法:pt.get_child_optional("some_key")->get_value<int>()。

我们可以向现在这样通过各种树的操作,选择到我们的需要的节点,再通过get_value<Type>函数获取到数据值。但这样的操作有时候会有点繁琐。boost支持更简化的一些操作。下面是同样功能的一个例子:

get这个函数相当于先get_child得到要找的节点,之后再调用get_value<Type>这个函数。get_value<Type>这个函数可以获取节点的值,同时把它转换成Type格式。即ptree.get<int>("a.b")等价于ptree.get_child("a.b").get_value<int>()。

通过get函数,我们可以很方便的获取某个节点的数据,而且还能顺便完成类型的转换,真的不能更方便了!

2)数组的解析

为什么数组的解析要单独来说呢?因为,数组格式中,没有键,所以我们不能根据名字来获取节点了,所以读取的方式有了些许的不同。

Boost针对数组,给我们提供了遍历子节点的迭代器接口。可以十分方便的遍历某节点的所有的子节点(当然在键值对的解析中也可以使用)。

打印的结果:

可以看出,Boost中将JSON数组也是按照键值对的方式去存储,只是键的内容是一个空的字符串。迭代器的first是键的结果,数组中就是空字符串。second就是我们的值。

3) 其他的实用接口

bool empty(): 返回该节点是否含有子节点。比如当一个节点已经是叶子节点的时候,可以用这个函数来判断。
assoc_iterator find(const key_type &key): 给定一个名字路径,返回指向该节点的迭代器或者boost::property_tree::ptree::not_found。
size_type count(const key_type &key): 返回指定名称路径的节点的子节点的数目。

五、JSON对象的编辑

Boost支持很多的对JSON对象的写的操作,但是我在项目中没有用到,所以在这里暂时就没有动力整理下去了~~ 这里附上Boost ptree的文档,方便大家查阅:

https://www.boost.org/doc/libs/1_65_1/boost/property_tree/ptree.hpp

六、疑难杂症

1.怎么判断某个键是否存在?

使用get_child_optional,再判断返回是否为boost::null,这个对象直接相当于false。

2.怎么方便的遍历数组?

这个功能,我还专门查过。其实懂了之前的迭代器的使用,就能方便的遍历了。下面是我用的一个代码。

使用的话就这样:

不过这个解决方案有个问题,就是如果根节点就是数组的话,似乎就不能很好的work了。

3.怎么解析中文

/(ㄒoㄒ)/~~

转载请注明出处,谢谢!

0

C++ 并发队列的原理简介与开源库concurrentqueue安利

由于最近在做一个项目,但是框架本身有个不合理的设计。其中的代码是单线程的,数据的读取和计算都在一个线程里面完成。也就是说,我们的程序有很大的一部分时间在读取文件数据,导致最终的运行速度很慢。这里就可以使用多线程来优化。

这里需要使用最基本的生产者消费者模式

使用若干个线程作为生产者,负责数据的读取和预处理,这部分任务是IO密集型的,也就是不太占CPU,但是比较占带宽,而且有延时。在处理完数据之后,将数据放到一个队列中。

同时,使用若干个线程充当消费者,从这个队列里面获取数据,然后进行计算。计算的部分是CPU密集型的(其实我这里计算是GPU做的,就只有一个消费者),计算完成之后输出结果。

那么贯穿这一整套方案的,就是我们的队列。

在并发任务中,通常都需要一个队列机制,将并行的任务转化成串行的任务,或者将串行的任务提供给并行工作的线程。这个队列会同时被多个线程读写,因此也必须是线程安全的。

一、线程安全的实现策略

对于线程安全的队列的实现,似乎经常成为企业的面试题,常见的实现方法就是互斥量和条件变量,本质上就是锁的机制。同一时间只有一个线程具有读写的权限。锁的机制在并发量不大情况下,十分的清晰有效。在并发量较大的时候,会因为对锁的竞争而越发不高效。同时,锁本身也需要维护一定的资源,也需要消耗性能。

这时候,大家肯定会想问,不使用锁机制,还可以处理这种并发的情况吗?

答案是肯定的,首先我们知道锁主要有两种,悲观锁和乐观锁。对于悲观锁,它永远会假定最糟糕的情况,就像我们上面说到的互斥机制,每次我们都假定会有其他的线程和我们竞争资源,因此必须要先拿到锁,之后才放心的进行我们的操作,这就使得争夺锁成为了我们每次操作的第一步。乐观锁则不同,乐观锁假定在很多情况下,资源都不需要竞争,因此可以直接进行读写,但是如果碰巧出现了多线程同时操控数据的情况,那么就多试几次,直到成功(也可以设置重试的次数)。

我们生活的时候,总会碰到很多的不顺心的事情,比如模型训练崩了,被某些库搞得头大,或者女票又生气了什么的,不妨学习一下乐观锁的精神,再训一次?再编译一次?大不了再哄一次。一次不行就两次。

回到乐观锁上,乐观锁中,每次读写都不考虑锁的存在,那么他是如何知道自己这次操作和其他线程是冲突的呢?这就是Lock-free队列的关键——原子操作。原子操作可以保证一次操作在执行的过程中不会被其他线程打断,因此在多线程程序中也不需要同步操作。在C++的STL中其实也提供了atomic这个库,可以保证多线程在操控同一个变量的时候,即使不加锁也能保证起最终结果的正确性。而我们乐观锁需要的一个原子操作就是CAS(Compare And Swap),绝大多数的CPU都支持这个操作。

CAS操作的定义如下(STL中的一个):

首先函数会将obj与expected的内容作比较:

  1. 如果相等,那么将交换obj和val的值,并返回true。
  2. 如果不相等,则什么也不做,之后返回false。

那么使用这个奇怪的操作,为什么就可以实现乐观锁了呢?这里我们看一个例子。这也是我学习的时候看的例子。

在我们向list中插入元素的时候,首先获取到当前的头指针的值head,然后我们在写数据的时候,首先和此刻的头指针值作对比,如果相同,那么就把新的节点插入。如果不相同,说明有线程先我们一步成功了,那么我们就多尝试一次,直到写入成功。

以上就是使用CAS操作实现的乐观锁。上面的这个append就是最简单的Lock-free且线程安全的操作。

二、concurrentqueue

最近在做这个项目的时候,就被安利了一个header only的C++并发队列库 concurrentqueue。本着不重复造轮子的原则,我在项目中用了这个库,由于它只是两个头文件,特别方便的就加入到了项目中。关于这个库的特点,项目的github上写了很多。这里直接照搬下来,不做解释。

  • Knock-your-socks-off blazing fast performance.
  • Single-header implementation. Just drop it in your project.
  • Fully thread-safe lock-free queue. Use concurrently from any number of threads.
  • C++11 implementation — elements are moved (instead of copied) where possible.
  • Templated, obviating the need to deal exclusively with pointers — memory is managed for you.
  • No artificial limitations on element types or maximum count.
  • Memory can be allocated once up-front, or dynamically as needed.
  • Fully portable (no assembly; all is done through standard C++11 primitives).
  • Supports super-fast bulk operations.
  • Includes a low-overhead blocking version (BlockingConcurrentQueue).
  • Exception safe.
  • read more

    2

    CNN的目标检测概述(三)

    本次介绍的是Fast R-CNN,与之前的RCNN和SPPNet不同,Fast R-CNN是一个清晰和快速的目标检测的框架。在训练和测试的速度上都远超过上述两种方法。同时,Fast R-CNN的训练是一次性的端到端的训练,同时训练的分类和回归两个任务。极大的简化的训练的流程。

    项目代码:https://github.com/rbgirshick/fast-rcnn

    三、Fast R-CNN

    1)R-CNN与SPPNet的不足

    R-CNN在目标检测中有很好的准确率,但是这个方法本身仍有很多的问题。

  • 训练过程是多级的。R-CNN的训练分成三个部分,首先是finetune一个网络(目标检测的类别和ImageNet不一样)。之后是使用SVM进行目标的分类的训练。最后是使用feature map来进行目标的bounding-box的回归训练。
  • 训练过程费时费空间。SVM和回归两个任务,需要存储目标的特征,需要很多空间。网络的训练过程很慢。
  • 测试速度太慢。需要对每个proposal进行前馈,耗时太长。速度只有47s / image。
  • read more

    0

    [转载] GDB十分钟教程

    GDB十分钟教程

    作者: liigo
    原文链接: 
    http://blog.csdn.net/liigo/archive/2006/01/17/582231.aspx
    日期: 2006年1月16日

    本文写给主要工作在Windows操作系统下而又需要开发一些跨平台软件的程序员朋友,以及程序爱好者。

    GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。

    GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。 read more

    0

    CNN的目标检测概述(二)

    这次介绍的是2015年的Kaiming He的一篇论文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition,以下简称SPP-net。

    SPP-net的主要贡献是提出了一种新的pooling的方式,spatial pyramid pooling,简称为SPP。使用这种pooling的方式,可以将任意大小的输入feature map给pooling到固定的大小。使用这种pooling的方式,最终在分类和检测任务上均有一定的效果。

    二、SPP-net

    1)问题描述

    在介绍SPP这个pooling方式之前,我们先说一下,为什么需要这种特殊的pooling。

    一般来看,CNN结构通常都由两个部分组成:卷积层和全连接层。比如7层的AlexNet,就是由5层的卷积层和2层的全连接层组成。对于卷积层,它可以处理任意尺度的输入(= = 请忽略极端情况)。而全连接层需要固定大小的输入。因此最终,我们的CNN结构的输入大小是由全连接层所固定。

    那么固定大小的输入会造成什么问题呢?一般图像的大小并不是固定的,但是CNN要求输入固定,这样我们通常会采取两种方式得到固定大小的图像:裁剪和变形(仿射变换,缩放等)。

    裁剪操作很难正好的包含需要的目标,而变形的方式会导致目标发生形变。两种方式都不能很好的处理图片的尺度问题。

    而SPP这种pooling方式的引入,就可以突破CNN固定输入的约束。SPP可以将任意大小的输入feature map给pooling到固定的大小。将SPP层加在最后一个卷积层的后面,这样就可以pooling出固定的大小,之后再接上全连接层。这样得到的CNN结构,就可以以任意尺度的图像作为输出了,而使用了SPP层的网络,就成为SPP-net。

    这张图就是一般的CNN结构(上)和SPP-net(下)的结构示意图。

    2)SPP层工作流程

    使用SPP层的CNN结构如下:

    这里重点介绍一下SPP层的工作方式,之前查阅其他的博客,发现都没有得到很好地解释,为此专门阅读了Caffe的SPPLayer实现代码,发现实现的方式很简单。

    上图中,最下方是CNN的卷积部分,黑色的部分是最后一个卷积层的输出,在这个图里面,卷积的最终输出的通道数为256。

    对于一个feature map,我们按照固定的方式对他进行划分。比如现在有一个feature map的宽和高分别是W和H,通道数不妨就取256。我们使用MAX-Pooling的方式做处理,金字塔的层数设置为3。

    首先,我们将这个feature map复制为3份。每一份都看成金字塔的一层。

  • 对于第一层,也就是顶层,即图中最右边的示意图。我们将整个feature map看做一个整体。这样使用一个(W, H)的kernel来进行pooling,这样就得到了1*1*256的输出。
  • 对于第二层,即图中的中间的示意图。我们将feature map看成2*2个独立的区域,每个区域单独的pooling。实现上即使用了kernel大小为(\lceil \frac{W}{2}\rceil ,\lceil \frac{H}{2}\rceil ),stride大小为(\lceil \frac{W}{2}\rceil ,\lceil \frac{H}{2}\rceil )的一个pooling层进行pooling。最终得到了2*2*256的输出。
  • 同理,第三层,即图中的左边的示意图。将feature map看成4*4个独立的区域,单独pooling。使用kernel大小为(\lceil \frac{W}{4}\rceil ,\lceil \frac{H}{4}\rceil ),stride大小为(\lceil \frac{W}{4}\rceil ,\lceil \frac{H}{4}\rceil )的pooling层进行pooling。最终得到4*4*256的输出。
  • 总结下来,就是对于第N层,我们将整个feature map划分成2^{N-1}*2^{N-1}的区域,分别pooling。实现上使用kernel为(\lceil \frac{W}{2^{N-1}}\rceil ,\lceil \frac{H}{2^{N-1}}\rceil ),stride为(\lceil \frac{W}{2^{N-1}}\rceil ,\lceil \frac{H}{2^{N-1}}\rceil )的pooling层进行pooling即可,最终得到2^{N-1}*2^{N-1}*dim的输出。
  • read more

    0

    CNN的目标检测概述(一)

    在2012年的ImageNet中,AlexNet拔得头筹。之后,CNN成为了图像识别中的一大利器。

    在目标检测中引入CNN,开山之作就是2013年的Rich feature hierarchies for accurate object detection and semantic segmentation,之后简称R-CNN。

    一、R-CNN

    1)算法原理

    R-CNN中,将目标检测分成两步来实现:

    1. 首先是生成Region proposals,也就是候选框。有许多的基于图像的低维特征生成候选框的算法,例如selective search等。
    2. 训练一个分类和回归的网络。这个网络可以根据候选框的区域的图片,判断这个图片的类别,以及它应该回归到的位置。

    2)检测过程

    这样,在具体的一张图片的目标检测的时候,我们可以通过下面的过程来得到结果:

    1. 对于给定一张图片,通过算法得到大量的候选框(2k个左右)。
    2. 将候选框的图片裁剪出,然后输入到CNN网络中。
    3. 网络的输出为分类的结果和回归的结果。然后我们就知道这个区域是不是目标,如果是,计算出它的回归的位置。
    4. 得到大量的有类别的框,通过NMS算法,得到最终的目标的框。

    3)网络训练

    对于目标检测这个任务,我们现有的标注数据其实并不多。所以通常都是使用ImageNet等大的公开的数据进行预训练,或者直接使用预训练好的模型在finetune。

    1. 预训练(Supervised pre-training)

    这里使用ILSVRC 2012(也就是ImageNet的训练数据集)进行分类任务的CNN网络的预训练。

    在我们自己复现论文的时候,可以选择现有的开源模型。在各种框架的Model Zoo中可以方便的下载各种网络的模型。常见的有AlexNet,VGGNet,GoogleNet,ResNet等的模型。据师弟的实验,VGGNet在目标检测中使用的频率最高,效果也最好。 read more

    0

    [转载] 应用Valgrind发现Linux程序的内存问题

    原文地址:https://www.ibm.com/developerworks/cn/linux/l-cn-valgrind/

    Valgrind 概述

    体系结构

    Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind的体系结构如下图所示:

    图 1 Valgrind 体系结构

    Valgrind 体系结构

    Valgrind包括如下一些工具:

    1. Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
    2. Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
    3. Cachegrind。它主要用来检查程序中缓存使用出现的问题。
    4. Helgrind。它主要用来检查多线程程序中出现的竞争问题。
    5. Massif。它主要用来检查程序中堆栈使用中出现的问题。
    6. Extension。可以利用core提供的功能,自己编写特定的内存调试工具。

    Linux 程序内存空间布局

    要发现Linux下的内存问题,首先一定要知道在Linux下,内存是如何被分配的?下图展示了一个典型的Linux C程序内存空间布局:

    图 2: 典型内存空间布局 read more