之前已经解读了parse_args()子程序。
在这第二篇解读博客中,我们先解读三个子程序再回到我们的主函数中。
主要代码为三个子程序cfg_from_file(args.cfg_file),cfg_from_list(args.set_cfgs),cfg,这两个子程序以及变量路径为/py-faster-rcnn/lib/fast_rcnn/config.py
首先是
cfg
先解析前面几行代码
from easydict import EasyDict as edict
__C = edict()
cfg = __C
__C.TRAIN = edict()
__C.TRAIN.SCALES = (600,)
EasyDict可以使得变量以属性的方式去访问字典的值
__C = edict() 将 __C 变成一个字典。
cfg = __C 在这里将__C 的地址给了cfg,在下面更改__C 时也同时更改了cfg的值。
__C.TRAIN = edict() 将 __C.TRAIN也改变为一个字典,整体就是字典中嵌套一个字典。
__C.TRAIN.SCALES = (600,) 给字典 __C.TRAIN 中设置一个变量 SCALES 并赋值为 (600,) 。
下面的都是一样进行赋值,不做重复解释。
所以在主函数中“cfg.GPU_ID = args.gpu_id”是给cfg这个字典中的GPU_ID进行赋值,取值为之前设定的参数args.gpu_id,在默认中我们设置为0。
cfg_from_file(filename)
def cfg_from_file(filename):
"""Load a config file and merge it into the default options."""
import yaml
with open(filename, 'r') as f:
yaml_cfg = edict(yaml.load(f))
_merge_a_into_b(yaml_cfg, __C)
在这段子程序中通过以写的方式打开文件“filename”(with open(filename, ‘r’) as f),并使用edict()将文件的内容变更为一个字典的数据结构赋值给 yaml_cfg ( yaml_cfg = edict(yaml.load(f)) ),最后程序调用了_merge_a_into_b(a,b)将a与b合并。接下来来解读这个子程序的内容 (_merge_a_into_b(yaml_cfg, __C)) 。
def _merge_a_into_b(a, b):
"""Merge config dictionary a into config dictionary b, clobbering the
options in b whenever they are also specified in a.
"""
if type(a) is not edict:
return
for k, v in a.iteritems():
# a must specify keys that are in b
if not b.has_key(k):
raise KeyError('{} is not a valid config key'.format(k))
# the types must match, too
old_type = type(b[k])
if old_type is not type(v):
if isinstance(b[k], np.ndarray):
v = np.array(v, dtype=b[k].dtype)
else:
raise ValueError(('Type mismatch ({} vs. {}) '
'for config key: {}').format(type(b[k]),
type(v), k))
# recursively merge dicts
if type(v) is edict:
try:
_merge_a_into_b(a[k], b[k])
except:
print('Error under config key: {}'.format(k))
raise
else:
b[k] = v
从注释中我们可以看到这个子程序的功能是将字典A合并到B中。
首先需要判定这个程序是否为字典的形式,如果不是则直接返回。
if type(a) is not edict:
return
在之后的程序中就是进行不断地比较a与b之间的键名是否一致,以及数据的格式是否一致。
程序先将a中的键名与键值读取出来(for k, v in a.iteritems():)
然后开始判断a中的键名是否在b中,如果不是就输出错误提示。
if not b.has_key(k):
raise KeyError('{} is not a valid config key'.format(k))
进行判断数据格式是否一致:读取b中对应数据的格式(old_type = type(b[k])),判断v的格式是否不一致(if old_type is not type(v):),如果是,判断b[k]数据格式是否为np.ndarray(if isinstance(b[k], np.ndarray):),如果是,则更改v的数据格式为np.ndarray(v = np.array(v, dtype=b[k].dtype)),如果不是则输出错误提示。
old_type = type(b[k])
if old_type is not type(v):
if isinstance(b[k], np.ndarray):
v = np.array(v, dtype=b[k].dtype)
else:
raise ValueError(('Type mismatch ({} vs. {}) '
'for config key: {}').format(type(b[k]),
type(v), k))
此时b[k]与v的数据格式已经一致。
在之后的一段程序中程序调用了自身这个递归的用法。先是判断v的类型是否为edict,如果不是则直接将v赋值到b[k],如果是,则使用递归的方式将a,b中的内容再细化输入到 _merge_a_into_b(a, b) 自身这个函数中。如果a或b中没有这个键值k则输出错误。
if type(v) is edict:
try:
_merge_a_into_b(a[k], b[k])
except:
print('Error under config key: {}'.format(k))
raise
else:
b[k] = v
在这里需要明白的是在前一段程序中先判断v与b[k]类型不一致并且b[k]为np.ndarray格式的情况下将v变更为np.ndarray。若v与b[k]类型已知,则判断直接复制即之后这段程序的b[k] = v。
回到cfg_from_file(filename)程序的作用。将filename中数据与__C数据进行相同合并。也就是说我们通过这个子程序读取一个文件内的数据并合并到一个之前的字典 __C中。
接下来解读的是子程序:cfg_from_list(cfg_list)
cfg_from_list(cfg_list)
def cfg_from_list(cfg_list):
"""Set config keys via list (e.g., from command line)."""
from ast import literal_eval
assert len(cfg_list) % 2 == 0
for k, v in zip(cfg_list[0::2], cfg_list[1::2]):
key_list = k.split('.')
d = __C
for subkey in key_list[:-1]:
assert d.has_key(subkey)
d = d[subkey]
subkey = key_list[-1]
assert d.has_key(subkey)
try:
value = literal_eval(v)
except:
# handle the case when v is a string literal
value = v
assert type(value) == type(d[subkey]), \
'type {} does not match original type {}'.format(
type(value), type(d[subkey]))
d[subkey] = value
程序首先判断cfg_list的长度是否为2的倍数(assert len(cfg_list) % 2 == 0)这里是为了确保后续的程序运行顺利,先进行检查操作。
从cfg_list 中读取k和v(for k, v in zip(cfg_list[0::2], cfg_list[1::2]):)。用一个测试程序让大家明白这是一个怎么样的过程。
a=[0,1,2,3,4,5,6,7,8,9]
a[0::2]
Out[9]: [0, 2, 4, 6, 8]
a[1::2]
Out[10]: [1, 3, 5, 7, 9]
上面的程序应该很显然就可以理解k和v在cfg_list中的位置了,这也是为什么要保证cfg_list的长度的原因了。
将读取出来的键用“.”分割开来,变成一个list(key_list = k.split(‘.’))。在这里为了便于理解需要观察前面的 __C这个字典,字典中是嵌套着其他的字典,key_list中依次存储字典名
for subkey in key_list[:-1]:
assert d.has_key(subkey)
d = d[subkey]
将键的list中的键名一一读取出来并不断读取字典中的字典,知道最后一个字典d,最后一个键名先不做操作(key_list[:-1])。
最后一段程序:
在此处读取key_list中的最后一个键名(subkey = key_list[-1]),确保最后一层字典中有这个键名(assert d.has_key(subkey))。
如果v为字符类型者使用literal_eval()将v转换为数值型赋值给value,如果v是字符型并且不是一个数则直接将这个数值赋值给value。
try:
value = literal_eval(v)
except:
# handle the case when v is a string literal
value = v
确保value的类型与d[subkey]一致(assert type(value) == type(d[subkey])),否则输出错误报告。
当类型一致时将 value赋值给d[subkey]。
assert type(value) == type(d[subkey]), \
'type {} does not match original type {}'.format(
type(value), type(d[subkey]))
d[subkey] = value
所以此处cfg_from_list(cfg_list)的函数功能为将cfg_list中的数据赋值给对应的__C中的键名。
在此准备阶段的子程序解读完成。我们回到Faster RCNN的主函数中。
if __name__ == '__main__':
args = parse_args()
print('Called with args:')
print(args)
if args.cfg_file is not None:
cfg_from_file(args.cfg_file)
if args.set_cfgs is not None:
cfg_from_list(args.set_cfgs)
cfg.GPU_ID = args.gpu_id
主函数依次是得到参数,打开文件args.cfg_file并与初始__C数据合并。再将args.set_cfgs中的数据赋值到对应的键名中。