Gluon Package
Gluon包有以下API:只选择高频api介绍。
1. Parameter-参数相关
1)class mxnet.gluon.
Parameter
(name, grad_req='write', shape=None, dtype=<type 'numpy.float32'="">, lr_mult=1.0, wd_mult=1.0, init=None, allow_deferred_init=False, differentiable=True, stype='default', grad_stype='default'
Parameter类包含了Blocks的参数。当指定了Parameter.initialize(...)之后,Parameter在每个context,也就是设备上都会有一份参数的copy。
ctx = mx.gpu(0) x = mx.nd.zeros((16, 100), ctx=ctx) w = mx.gluon.Parameter('fc_weight', shape=(64, 100), init=mx.init.Xavier()) b = mx.gluon.Parameter('fc_bias', shape=(64,), init=mx.init.Zero()) w.initialize(ctx=ctx) b.initialize(ctx=ctx) out = mx.nd.FullyConnected(x, w.data(ctx), b.data(ctx), num_hidden=64)
代码利用到了参数的initialize方法:
>>> weight = mx.gluon.Parameter('weight', shape=(2, 2)) >>> weight.initialize(ctx=mx.cpu(0)) >>> weight.data() [[-0.01068833 0.01729892] [ 0.02042518 -0.01618656]] >>> weight.grad() [[ 0. 0.] [ 0. 0.]] >>> weight.initialize(ctx=[mx.gpu(0), mx.gpu(1)]) >>> weight.data(mx.gpu(0)) [[-0.00873779 -0.02834515] [ 0.05484822 -0.06206018]] >>> weight.data(mx.gpu(1)) [[-0.00873779 -0.02834515] [ 0.05484822 -0.06206018]]
2)class mxnet.gluon.
Constant
(name, value)[source]
不可变tensor,常量。常量被autograd和Trainer忽略,因此它们的值在训练期间不会改变。但是仍然可以使用set_data方法手动更新它们的值。
常量s可以用以下任一项创建:
const = mx.gluon.Constant('const', [[1,2],[3,4]]) # or: class Block(gluon.Block): def __init__(self, **kwargs): super(Block, self).__init__(**kwargs) self.const = self.params.get_constant('const', [[1,2],[3,4]])
3)class mxnet.gluon.
ParameterDict
(prefix='', shared=None)
管理参数的字典。
2. Containers-容器
1. class mxnet.gluon.
Block
(prefix=None, params=None)
所有神经网络层和模型的基类。
from mxnet.gluon import Block, nn
from mxnet import ndarray as F
class Model(Block):
def __init__(self, **kwargs):
super(Model, self).__init__(**kwargs)
# use name_scope to give child Blocks appropriate names.
with self.name_scope():
self.dense0 = nn.Dense(20)
self.dense1 = nn.Dense(20)
def forward(self, x):
x = F.relu(self.dense0(x))
return F.relu(self.dense1(x))
model = Model()
model.initialize(ctx=mx.cpu(0))
model(F.zeros((10, 10), ctx=mx.cpu(0)))
Block类主要方法:
1) collect_params
(select=None)
返回一个ParameterDict类型,包含了Block和它孩子的参数。也可以选择一部分参数返回。例如选择指定的参数:[‘conv1_weight’, ‘conv1_bias’, ‘fc_weight’, ‘fc_bias’]:
model.collect_params('conv1_weight|conv1_bias|fc_weight|fc_bias')
或者搜集所有名字里有‘weight’或者‘bias’的参数,可以用正则匹配:
model.collect_params('.*weight|.*bias')
>>>model.collect_params()
Out[5]:
model1_ (
Parameter model1_dense0_weight (shape=(20, 10), dtype=float32)
Parameter model1_dense0_bias (shape=(20,), dtype=float32)
Parameter model1_dense1_weight (shape=(20, 20), dtype=float32)
Parameter model1_dense1_bias (shape=(20,), dtype=float32)
)
>>>model.collect_params('.*bias')
Out[6]:
model1_ (
Parameter model1_dense0_bias (shape=(20,), dtype=float32)
Parameter model1_dense1_bias (shape=(20,), dtype=float32)
)
2) initialize
(init=, ctx=None, verbose=False, force_reinit=False)
初始化Block及其孩子的参数,等效于:block.collect_params().initialize(...)
model.collect_params().initialize(ctx=mx.cpu(0))
# or:
model.initialize(ctx=mx.cpu(0))
3)load_parameters
(filename, ctx=None, allow_missing=False, ignore_extra=False, cast_dtype=False, dtype_source='current')
save_parameters
(filename)
保存和载入模型。具体操作见这里。
注意的是:利用save_parameters保存的参数只能由load_parameters载入,这个方法只是保存了参数,没有保存网络。所以你要载入就得先初始化模型。而利用HybridBlock.export()方法可以同时保存模型和参数。
2. class mxnet.gluon.
HybridBlock
(prefix=None, params=None)[source]
这个混合block同时支持Symbol和NDArray的前向。类似于上面的Block,有点不同:
import mxnet as mx from mxnet.gluon import HybridBlock, nn class Model(HybridBlock): # 区别1 def __init__(self, **kwargs): super(Model, self).__init__(**kwargs) # use name_scope to give child Blocks appropriate names. with self.name_scope(): self.dense0 = nn.Dense(20) self.dense1 = nn.Dense(20) def hybrid_forward(self, F, x): # 区别2 x = F.relu(self.dense0(x)) return F.relu(self.dense1(x)) model = Model() model.initialize(ctx=mx.cpu(0)) model.hybridize() # 区别3 model(mx.nd.zeros((10, 10), ctx=mx.cpu(0)))
如上有三处区别,利用hybrid就和symbol一起工作,变成了静态图,没法像NDArray那样索引。在使用hybridize()激活之前,HybridBlock的工作方式与普通Block类似。激活后,HybridBlock将创建一个表示正向计算的符号图并将其缓存。在随后的前向过程中,将使用缓存的图而不是hybrid_forward()。说白了就是hybrid是令gluon变成Module那样可以利用Symbol的办法。这里有个ref:Hybrid - Faster training and easy deployment
方法的话介绍一个吧:export
(path, epoch=0, remove_amp_cast=True)
主要是保存hybridblock的模型和参数,将分别保存成json和param后缀的文件。⚠️这个保存后的结果有两种载入方式:一种就是SymbolBlock.imports,,另一种是mxnet.mod.Module 或C++ interface。demo见这里。
3. class mxnet.gluon.
SymbolBlock
(outputs, inputs, params=None)[source]
根据symbol来构建block。这对于利用预训练的模型作为特征提取器时是有用的。例如,可以从alexnet的fc2层来得到输出。
参数中的outputs就是你想要得到的哪一层的输出,inputs就是输入变量,这两都是symbol类型或者symbol的列表类型。params是ParameterDict类型,就是关于参数argumetns和辅助参数auxililary的一个字典。例子好懂:
# To extract the feature from fc1 and fc2 layers of AlexNet: alexnet = gluon.model_zoo.vision.alexnet(pretrained=True, ctx=mx.cpu(), # 搞一个预训练的gluon模型,返回的是HybridBlock类型: inputs = mx.sym.var('data') # 输入变量,symbol类型 out = alexnet(inputs) # 上面提到了HybridBlock同时支持symbol和ndarray的前向 internals = out.get_internals() # 得到所有层信息 print(internals.list_outputs()) outputs = [internals['model_dense0_relu_fwd_output'],internals['model_dense1_relu_fwd_output']] # 想分别得到fc1和fc2的输出 # Create SymbolBlock that shares parameters with alexnet feat_model = gluon.SymbolBlock(outputs, inputs, params=alexnet.collect_params()) # 建立这个symbolblock,把预训练参数搞进来 x = mx.nd.random.normal(shape=(16, 3, 224, 224)) print(feat_model(x))
上面也提到了,这个SymbolBlock有个方法imports,可用来加载json类型的网络结构和参数params:
static imports
(symbol_file, input_names, param_file=None, ctx=None)[source]
>>> net1 = gluon.model_zoo.vision.resnet18_v1( ... prefix='resnet', pretrained=True) >>> net1.hybridize() >>> x = mx.nd.random.normal(shape=(1, 3, 32, 32)) >>> out1 = net1(x) >>> net1.export('net1', epoch=1) # 上面提到hybridblock保存模型的方法 >>> >>> net2 = gluon.SymbolBlock.imports( # 可用symbolblock来载入模型和参数 ... 'net1-symbol.json', ['data'], 'net1-0001.params') >>> out2 = net2(x)
4. class mxnet.gluon.nn.
Sequential
(prefix=None, params=None)[source]
序列化的堆叠Blocks:
net = nn.Sequential() # use net's name_scope to give child Blocks appropriate names. with net.name_scope(): net.add(nn.Dense(10, activation='relu')) net.add(nn.Dense(20))
5. class mxnet.gluon.nn.
HybridSequential
(prefix=None, params=None)[source]
序列化堆叠HybridBlocks:
net = nn.HybridSequential() # use net's name_scope to give child Blocks appropriate names. with net.name_scope(): net.add(nn.Dense(10, activation='relu')) net.add(nn.Dense(20)) net.hybridize()
3. Trainer-训练器
class mxnet.gluon.
Trainer
(params, optimizer, optimizer_params=None, kvstore='device', compression_params=None, update_on_kvstore=None)[source]
给参数施加优化器,Trainer应当与autograd一起使用。对于下面的情况,不可以把update_on_kvstore置为False:
- dist kvstore with sparse weights or sparse gradients
- dist async kvstore
- optimizer.lr_scheduler is not None
例子:
from mxnet import autograd as ag
train_data, val_data = get_data_iters(dataset, batch_size, opt) net.collect_params().reset_ctx(ctx) trainer = gluon.Trainer(net.collect_params(), 'sgd', # trainer用法 optimizer_params={'learning_rate': opt.lr, 'wd': opt.wd, 'momentum': opt.momentum, 'multi_precision': True}, kvstore=kv) loss = gluon.loss.SoftmaxCrossEntropyLoss() total_time = 0 num_epochs = 0 best_acc = [0] for epoch in range(opt.start_epoch, opt.epochs): trainer = update_learning_rate(opt.lr, trainer, epoch, opt.lr_factor, lr_steps) tic = time.time() train_data.reset() metric.reset() btic = time.time() for i, batch in enumerate(train_data): data = gluon.utils.split_and_load(batch.data[0].astype(opt.dtype), ctx_list=ctx, batch_axis=0) label = gluon.utils.split_and_load(batch.label[0].astype(opt.dtype), ctx_list=ctx, batch_axis=0) outputs = [] Ls = [] with ag.record(): # trainer与autograd一起用 for x, y in zip(data, label): z = net(x) L = loss(z, y) # store the loss and do backward after we have done forward # on all GPUs for better speed on multiple GPUs. Ls.append(L) outputs.append(z) ag.backward(Ls) trainer.step(batch.data[0].shape[0]) # step方法在backward后更新参数,主要是在record之外 metric.update(label, outputs) if opt.log_interval and not (i+1)%opt.log_interval: name, acc = metric.get() logger.info('Epoch[%d] Batch [%d]\tSpeed: %f samples/sec\t%s=%f, %s=%f'%( epoch, i, batch_size/(time.time()-btic), name[0], acc[0], name[1], acc[1]))
4. Utilities-数据划分包
1. mxnet.gluon.utils.
split_data
(data, num_slice, batch_axis=0, even_split=True)[source]
针对NDArray类型数据沿着batch的维度划分,通常用在数据并行,每个设备需要一部分数据。
num_slice:要划分的数据份数
返回:列表,NDArray类型
2. mxnet.gluon.utils.
split_and_load
(data, ctx_list, batch_axis=0, even_split=True)[source]
与上面唯一不同的地方是num_slice变成了ctx_list,也就是设备列表,划分数据的份数=列表长度,也就是设备数目。
for i, batch in enumerate(train_data): data = gluon.utils.split_and_load(batch.data[0].astype(opt.dtype), ctx_list=ctx, batch_axis=0) label = gluon.utils.split_and_load(batch.label[0].astype(opt.dtype), ctx_list=ctx, batch_axis=0)
3. mxnet.gluon.utils.
clip_global_norm
(arrays, max_norm, check_isfinite=True)[source]
缩放NDArray,使得所有元素的2范数之和小雨max_norm。