caffe输入层怎么编写?

caffe的输入层总结

前面已经总结了下caffe已内置的输入层,但是除了python layer外,其余的局限性都比较大,比如大家常用的BoundingBox数据就很不统一,py-faster-rcnn里面使用python layer作输入层直接读取从PASCAL VOC格式数据(其实是从它构建的roidb并缓存了),而SSD自己构建了AnnotatedDataLayer来处理保存在lmdb中的数据,caffe-yolo也自己构建了BoxDataLayer供自己使用,大家都很不统一,caffe官方也迟迟不统一一个BBoxDataLayer出来,局面很分裂。本文就算是总结下阅读这些代码的经验吧。

那输入层应该怎么写?先来搞清除这几个概念。

一些基础概念

Datum

Datum是LMDB/LevelDB每个条目的值。但是Datum的内容是在caffe.proto里规定好了的,原版只有bytesdatafloatfloat_data两个字段可供保存数据。如果想保存多种数据,没办法,只能先组装到一块再放到datum中。

DataReader

yolo和ssd的DataReader完全一样啊。本对象还实例化了一个继承自InternalThread的类负责启动一个线程单独的读取磁盘数据。

如果输入层直接继承InternalThread类,则可以不实现DataReader类,相当于自己就是读入类,线程入口是InternalThreadEntry

Caffe原装的DataLayer就已继承InternalThread,只需要实现load_batch即可。

Batch

Bath就是每次让DataReader读到的数据,这个数据在输入层一般直接就直接想下一层传递了。这个caffe默认的只有data_label_两个Blob成员,如果我们有多种类型数据,caffe默认的恐怕也不够用。

Prefetch队列

  virtual void load_batch(Batch<Dtype>* batch);
  vector<shared_ptr<Batch<Dtype> > > prefetch_;
  BlockingQueue<Batch<Dtype>*> prefetch_free_;
  BlockingQueue<Batch<Dtype>*> prefetch_full_;

load_batch函数的用途:batch_size次从数据库中读取出共计batch_sizedatum数据,将这些datum转成Batch对象。

InternalThreadEntry每次从prefetch_free_pop一个Batch对象出来,然后调用load_batch装满这个Batch对象,再把这个Batch对象pushprefetch_full_中去。

XXXDataLayer::Forward_cpu的作用就是将当前的prefetch_current_指向的Batch对象pushprefetch_free_中去,然后再用从prefetch_full_pop取出一个对象到prefetch_current_上来。

总结下方法

简单标签

如果你的数据只是一些图,每张图对应一个int型可以表示的label,那你直接把图文件名和label组成文本文件用ImageDataLayer读就可以了,想用LMDB的就把数据存到LMDB中也可以。

bbox标签

因为原版datum字段有限,特别是label只有一个,大家只好各显神通。

caffe-yolo的做法是,datumdata存放图片,float_data存放bbox六元组(标签,难易标识,四个坐标)。ssd的做法则是对datum做了更大的更改以支持bbox数据。我为了方便,直接给原版datum定义了扩展字段,允许自行扩展若干自定义字段。

也可以将datum视为最简单的容器,我们在其内的data字段内另建容器,把数据结构先序列化存到datum的data字段,使用时再反序列化出来。

protobuf extension的使用

在proto中扩展Datum

// message Datum 需要先声明扩展字段区间,比如 {
//   extensions 11 to 20;
// }
// 然后想扩展datum时,只需要另外如此声明即可
extend Datum {
  optional string imgfilename = 15;
}

python代码中对消息扩展字段的使用

datum.Extensions[caffe.proto.caffe_pb2.imgfilename] = "/path/to/imgfile"

CPP对对消息扩展字段的使用

// get
datum.GetExtension(imgfilename)
// set
datum.SetExtension(imgfilename, "/path/to/imgfile");

 

方法1:直接继承BasePrefetchingDataLayer

需要实现load_batch函数,这个非常简单,队列和线程方法都已定义,仅需自己实现load_bactchLayerSetup相关的函数即可。

有些继承自此方法的层还定义了DataReader另外起个线程来读数据,其实没什么必要,因为load_batch就已经是在单独的线程里的。

方法2:直接继承Layer和InternalThread来实现

需要实现InternalThread里的InternalThreadEntry方法,并定义数据缓冲队列供使用。这个线程里的方法来调用一些方法实现文件输入并放在队列里供Layer的Forward方法取用。这个方法等于自己实现了一遍BasePrefetchingDataLayerLayerSetup函数最后启动这个线程即可。

附:各输入层的继承关系



python argparse 要点

写个需要参数的python脚本时,这个argparse模块就是相当顺手的,已默认支持常用的参数书写形式。

比如:

parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo')
parser.add_argument('-b','--bar')
# 那么下面的结果都是一样
# PROG --foo 2 --bar 3
parser.parse_args(['--foo','2', '--bar', '3'])
# PROG --fo 2 -b 3
parser.parse_args(['--fo','2', '-b', '3']) # 支持长参数只书写前面能区分出的部分即可,ArgumentParser的参数allow_abbrev=False可以关闭这个特性
# PROG --fo=2 -b=3
parser.parse_args(['--fo=2', '-b=3']) # 长短参数都可以使用等于号形式赋值
# PROG --foo=2 -b3
parser.parse_args(['--foo=2', '-b3']) # 短参数还支持连写形式

继续阅读python argparse 要点

给googletest输出的xml报告文件穿上花衣

给xml文件穿花衣就是XSLT的事情,那我们就来给googletest输出的xml报告穿上花衣服吧。

这是好多年前我创建的一套xslt文件,一直能用。

给googletest输出的xml报告文件穿上花衣,我这件衣服是高仿CUnit的。

我的这个googletest分支上已携带了xml-stylesheet声明,直接使用即可。

# cmake 构建googletest
cd googletest
mkdir build
cd build
cmake -Dgtest_build_samples=ON ..

# 编译googletest及样例
make

# 运行样例让它直接将结果输出到我们的xslt文件目录。
./sample1_unittest --gtest_output=xml:/home/renwei/gtest/gtxslt/sample1_ut_detail.xml

# firefox打开你就能看到穿花衣的gtest xml结果。
firefox /home/renwei/gtest/gtxslt/sample1_ut_detail.xml

这个有什么用?

用途大大的啊,自动化单元测试结果直接发布就不是孤零零的xml文件啊。那自动化单元测试有啥用?嗯,这个,少年,我该怎么回答你呢,一大篇文章。

python logging 要点

print大法固然随手就能方便写出来,但是某些情况还是用的不爽,需要更为专业的日志记录,那就看下python内置的logging模块吧。另外还有google出品的glog等第三方的日志模块,本问不涉及了。

一些基本概念

logger 记录器,记录器可以指定一个或多个handler处理器,以便同时记录到多个位置或者别的什么的区别记录。
handler 实际的处理器,logging模块里的日志最终处理者。
filter 过滤器,logger和handler上都可以指定filter。
formatter 格式化器,这个概念简单,就是决定最终输出格式的,关联与handler上。
loggers树 所有的loggers被整合成一棵以root为根的树,以定义时的名字为信息构建这个树,定义时以.符号分级。另外,某logger生成的record记录完后会自动向其父级logger传播,一直传播到root节点或遇到某级配置propagate = False的logger。
root根logger 默认会被创建的记录器。直接使用logging.warn()模块级日志函数使用的记录器就是它, basicConfig() 的设置也是针对它的, logging.getLogger() 不制定名字获得的logger也是它。

一些使用建议

配置方法

logging以子模块logging.config支持下面三种配置方式:

fileConfig 文件形式配置,文件格式就是configparser所支持的格式,基本就是ini格式。
dictConfig 直接用一个dict字典描述的配置信息,除了方便代码中直接写dict配置外,也方便导入以其他诸如json、yaml等配置文件描述的配置。
listen 启动一个socket server监听配置信息。
*basicConfig 这个basicConfig并不属于logging.config,但它可以配置最顶级的root节点。
*代码配置 对啊,我们当让可以直接在代码中创建logging中的各种对象添加到loggers树中,也可以用代码再对树中的各项予以再配置。

一些使用tips

getLogger(__name__)

使用模块方法logging.getLogger方法来获取或创建logger,多次以同一名称调用该方法获得的logger是同一个对象,不用担心重复或重新创建,安心使用。一般直接用模块名称__name__创建模块内使用的logger。

msg延迟格式化

尽量使用logging.info("info: %s",param)的方式而不是已格式化的字符串logging.info("info: {}".format(param)),因为msg可能被配置过滤掉无需输出了啊。

已内置的Handlers

FileHandler 基本的文件日志处理器。
WatchedFileHandler 它会监视文件,如果日志文件被其他程序改变,会自动关闭并重新打开文件。
RotatingFileHandler,
TimedRotatingFileHandler
以一组序列的文件名来循环记录日志,以文件大小或者时间间隔为限。
SocketHandler,
DatagramHandler
分别用TCP和UDP方式发布日志。
SMTPHandler,HTTPHandler 以SMTP和HTTP方法发布日志。
MemoryHandler 先缓存日志在内容中,后再flush到目标handler上。
NullHandler no-op。
SysLogHandler,
NTEventLogHandler
发布日志到unix syslog和NT event上。
StreamHandler 支持stdout stderr,以及其他支持write()flush()方法的所有文件或类文件对象。

python的单元测试和文档测试模块

unittest模块

用法炒鸡简单,基本用法如下

#!/usr/bin/env python  
#encoding: utf-8  
import unittest

def sum(x, y):
    return x+y

class TestFunc(unittest.TestCase):
    def setUp(self):
        pass
    def tearDown(self):  
        pass

    def test_sum_3_4(self):
        self.assertEqual(sum(3,4), 7)

    def test_sum_3_4_NE6(self):
        self.assertNotEqual(sum(3,4), 6)

    @unittest.expectedFailure
    def test_sum_3_4_EF(self):
        self.assertEqual(sum(3,4), 6)


if __name__ == '__main__':
    unittest.main()
    1. 首先引入 unittest 模块
    2. 定义 TestCase 类,类的 setUp 和 tearDown 函数按需使用,比如测试的一个类的话,在 setUp 中可实例化出一个被测类供测试函数使用,然后再写一堆 test_xxx 函数即可。
    3. 最后,main 模块中使用 unittest.main() 启动整个测试即可。
    4. 自动寻找文件夹中所有的单元测试文件并执行它python -m unittest discover -s ut_dir

doctest模块

doctest模块主要用于对文档字符串写的用法示例进行测试。

怎么写可以进行doctest的示例代码呢?直接实例吧

#!/usr/bin/env python  
#encoding: utf-8  

def sum( x, y):
    """sum function.
    
    Example:
    >>> sum(3,4)
    7
    """
    return x+y

if __name__ == '__main__':
    import doctest
    doctest.testmod()

直接运行这个py文件,嗯,啥结果也没有看见,那就对了,说明doctest通过了。doctest默认是不显示任何结果的,除非使用-v参数:python myfile.py -v 就可以看到doctest执行过程和结果了。

另外,doctest还提供了unittest API以方便unittest直接把doctest一块执行了。
只需要再测试模块中定义个`load_tests()`函数即可

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite())
    return tests

这般以后,unittest.main()会把doctest也一块给做了。

python正则表达式总结

match/search会缓存最近的对象

re.compile返回的是一个正则表达式对象。
下面两种方式等效,在使用正则较少的情况下不用太担心直接使用re.match的性能问题,因为match/search会缓存最近的对象。

pat = re.compile(pattern)
result = pat.match(string)
# is equivalent to
result = re.match(pattern, string)

re对象的方法

Method/Attribute Purpose
match() Determine if the RE matches at the beginning of the string.
search() Scan through a string, looking for any location where this RE matches.
findall() Find all substrings where the RE matches, and returns them as a list.
finditer() Find all substrings where the RE matches, and returns them as an iterator.

matchsearch在没有找到匹配时返回None,找到时返回match object。

match object的方法

Method/Attribute Purpose
group() Return the string matched by the RE
start() Return the starting position of the match
end() Return the ending position of the match
span() Return a tuple containing the (start, end) positions of the match

match还是search?还是find*?

match只检查字符串从0位置开始是否符合,而search则会搜索整个字符串。
search只搜索自一次匹配,而findall则会搜索完整个字符串提供所有匹配。

贪婪和非贪婪方式

*,+,?等都是贪婪方式。但是后面加个?号改为匹配尽可能少的字符,即*?,+?,??,{m,n}?

标识

Flag Meaning
DOTALL, S Make . match any character, including newlines
IGNORECASE, I Do case-insensitive matches
LOCALE, L Do a locale-aware match
MULTILINE, M Multi-line matching, affecting ^ and $
VERBOSE, X Enable verbose REs, which can be organized more cleanly and understandably.
UNICODE, U Makes several escapes like \w, \b, \s and \d dependent on the Unicode character database.

一些定位元字符

Metacharacter Meaning
| or
^ 行首
$ 行位
\A 字符串首
\Z 字符串尾
\b 单词边界
\B 非单词边界

group分组

group()group(0)是匹配串,group(n)[n>0]是第n个子分组, group(L,m,n)可以接受多个index返回元祖,groups()返回所有匹配子组。

非捕获组(?:…)和命名组(?P…)

非捕获组不能后向引用了,比如只需要单词或的匹配但是又不想捕获它时。
(...)\1使用序号后向引用,(?P<name>...)(?P=name)后向引用命名组。

Lookahead Assertions

(?=...)确定性断言,(?!...)否定性断言。

简单分支判断

(?(id/name)yes-pattern|no-pattern)提供一个简单的分支判断,如果id/name组存在匹配,就检索匹配yes-pattern,否则检索匹配no-pattern,否定项可选可省略。

git中的各种后悔药

FAQ 常用后悔药


工作区的代码想撤销: git checkout <file name>
add到暂存区的代码想撤销: git reset HEAD [<file name>]
提交到本地仓库的代码想撤销: git reset --hard <版本号>
把刚刚的提交撤回到暂存区: git reset --soft HEAD~1
revert某修改: git revert <id>
回滚后反悔怎么办?: git reflog 记录了我们的每一次命令( commit、merge 等信息), 根据这命令来查出我们的历史 commit id,然后 git reset即可
另外,git rebase -i也可以撤销之前的提交。 edit修改,drop丢弃,squash合并压缩

修改历史提交中的用户和邮箱

修改已提交的某一次的信息, 这个使用git rebasegit commit --amend 配合一步一步完成修改。
修改git全部已提交的用户名和邮箱 , 此方案直接全部过滤修改的方式,方便快捷。

删除分支、远程跟踪分支和远程分支

https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-both-locally-and-remotely?rq=1

# Deleting a remote branch,删除远程分支
git push origin --delete <branch>  # Git version 1.7.0 or newer
git push origin :<branch>          # Git versions older than 1.7.0
 
# Deleting a local branch,删除本地分支
git branch --delete <branch>
git branch -d <branch> # Shorter version
git branch -D <branch> # Force delete un-merged branches
 
# Deleting a local remote-tracking branch,删除本地的远程跟踪分支
git branch --delete --remotes <remote>/<branch>
git branch -dr <remote>/<branch> # Shorter
# only deleted his local remote-tracking branch origin/bugfix, and not the actual remote branch bugfix on origin
# 注:只会删除本地分支对应的远程跟踪分支,不会删除远程服务器上的分支

git fetch <remote> --prune # Delete multiple obsolete tracking branches
git fetch <remote> -p      # Shorter

# delete local tag
git tag -d <tagname>
# delete remote tag
git push origin :tagname #1
git push --delete origin tagname #2
git push origin :refs/tags/<tagname> #3 

建立空的新分支

这里以github的操作为例,创建一个名为gh-pages的空分支

# 创建一个独立的orphan的分支
git checkout --orphan gh-pages

# 删除原来的所有文件
git rm -rf .

注意这个时候你用git branch命令是看不见当前分支的名字的,除非你进行了一次commit。

caffe的输入层总结

来自图片:ImageDataLayer

最简单的输入方式,适用于分类任务,输入就是一个文件文件,而文本文件每行就是一个图片保存路径名称和它的数值标签即可。如果图片都已以文件夹或文件名保存了,可以非常容易地使用lsawk sed等命令建立其要求的文件文件。

来自LMDB或LevelDB数据库:DataLayper

每个条目记录值就是一个Datum,具体代码中就是使用datum.ParseFromString(cursor_->value())获取的。

但是原始的Datum只有一个data项和一个label项和可供使用的float_data项(直接保存的浮点数数据替代bytes表示的data)。如果您的输入数据除了图片还有别的,想自己新写输入层,除了使用这个float_data项外,您可以使用我pCaffe,对Datum定义了扩展条目,可以通过设置扩展字段的方式增加别的data条目。

来自HDF5数据文件:HDF5DataLayer

HDF5数据层参数中不能包含transform参数,不支持数据的图像预处理,直接拷贝数据使用。如果数据确实需要预处理,可以在其后增加reshape crop等层。

来自内存:MemoryDataLayer

每个batch开始前,数据必须使用MemoryDataLayer::Reset方法将内存地址传给网络,caffe会免拷贝地使用它。

自动切图:WindowData

主要是根据窗口定义文件自动地切图贴标签,与基准窗口overlap大于阈值的设为前景窗,否则设为背景窗。 窗口文件里可根据每张图手工控制这些指标,比如可以通过增大小样本类的窗口数以平衡样本不均衡现象。

DummyDataLayer

DummyData有Filler参数,设置来可以自动根据需要填充数据,在构建测试代码时相当好用。

InputLayer

这层参数只有shape,一般仅用于deploy网络中。替代旧式的位于网络参数中的input_shape参数。

PythonLayer

python层用来书写自定义数据的输入层是相当方便的。比如py-faster-rcnn就使用python层来搞定 GroundTruth BoundingBox 的输入。

支持动态并行和源码级plugins的Caffe——pCaffe

支持动态并行和源码级plugins的Caffe——pCaffe

https://github.com/wadefelix/caffe

特性:

  1. 开启了 CUDA Dynamic Parallelism 特性,在 Makefile.config 定义 CUDA_DYNAMIC_PARALLEL := 1 即可。
  2. 支持源码级别的代码 plugins 以支持开发新层等代码,每个新插入的层放置在 src/caffe/plugins 目录下单独的子目录中,方便交流新代码。
  3. plugins 目录已放置了 fast_rcnnSSD 的代码可供使用和参考。
    该 fast_rcnn 所有层均是caffe移植,无需python,且每个minibatch支持多张图,并且实践应用了 CUDA Dynamic Parallelism 特性。

继续阅读支持动态并行和源码级plugins的Caffe——pCaffe

linux上的任务

jobs, bg, fg 命令

jobs列出本会话所有任务
bg命令用于将作业放到后台运行,使前台可以执行其他任务。该命令的运行效果与在指令后面添加符号&的效果是相同的,都是将其放到系统后台执行。 Ctrl+Z后任务会暂停,bg命令可以让任务在后台继续运行。
fg命令用于将后台作业(在后台运行的或者在后台挂起的作业)放到前台终端运行。与bg命令一样,若后台任务中只有一个,则使用该命令时,可以省略任务号。

让进程在后台可靠运行的几种方法

技巧:让进程在后台可靠运行的几种方法

nohup ping www.ibm.com &
setsid ping www.ibm.com
(ping www.ibm.com &)
# disown 命令
# screen 命令
[/bash]

如何将正在运行的任务放入后台

使用& nohup启动的命令直接被放入后台,但是如果忘记输入&,或者你还要注销登录这次会话时,怎么办?
:第一步,Ctrl+Z把任务暂停,然后 bg %jobnumber 把任务放入后台运行。
若想在切回前台,使用fg %jobnumber 即可。

第二步,disown可以让任务忽略本任务的hangup信号。只放入后台的任务而不是使用nohup启动的,它也是要接受本会话的hangup的。
disown "%./experiments/scripts/faster_rcnn_end2end.sh"

其他知识

disown是bash内部命令,nohup是外部命令
disownbg,fg一样是针对job进行操作,nohup是针对命令操作
disown -hnohup一样,在退出bash后,把进程的控制权都交给init

其他用于管理用户会话的命令:
tty 列出本会话会话号
w 列出当前所有登录会话
skillpkill 可以给任务发信号, 如 skill -KILL -v pts/4 把会话pts/4的所有任务发kill信号。

更强大的是使用screen管理会话,但是ubuntu默认没有安装该程序,此命令网上知识也很多。