《第八章》
8.1命令式和符号式混合编程
命令式:jupyter lab上一边输入一边输出。
符号式:将所有代码转化为字符prog,然后通过下述方法执行。
y = compile(prog, '', 'exec')
exec(y)
混合式编程:1.使用HybridSequential;2.使用HybridBlock,如下。
class HybridNet(nn.HybridBlock):
def __init__(self, **kwargs):
super(HybridNet, self).__init__(**kwargs)
self.hidden = nn.Dense(10)
self.output = nn.Dense(2)
# 是hybrid_forward而不是forward,且需要F来决定使用哪个类
# MXNet有基于命令式编程的NDArray类(默认)和基于符号式编程的Symbol类。
def hybrid_forward(self, F, x):
print('F: ', F)
print('x: ', x)
x = F.relu(self.hidden(x))
print('hidden: ', x)
return self.output(x)
net = HybridNet()
net.initialize()
x = nd.random.normal(shape=(1, 4))
net.hybridize() # 能提升性能。
net(x)
net(x)
# 在hybrid_forward函数里,相同输入和中间输出全部变成了Symbol类型,再次前向后不再打印输出。
# 对于原地操作a += b和a[:] = a + b(需改写为a = a + b)
练习
在本节
HybridNet
类的hybrid_forward
函数中第一行添加x.asnumpy()
,运行本节的全部代码,观察并分析报错的位置和错误类型。答:在转换成符号式编程net.hybridize()的时候提示
Function asnumpy is not implemented for Symbol and only available in NDArray.
因为对于少数像
asnumpy
这样的Symbol
所不支持的函数无法在hybrid_forward
函数中使用并在调用hybridize
函数后进行前向计算。如果在
hybrid_forward
函数中加入Python的if
和for
语句会怎么样?加入if的错误:
Function __bool__ (namely operator "bool") is not implemented for Symbol and only available in NDArray.
加入for,暂时没发现错误。
回顾前面几章中你感兴趣的模型,改用
HybridBlock
类或HybridSequential
类实现。
8.2异步计算
class Benchmark(): # 本类已保存在d2lzh包中方便以后使用
def __init__(self, prefix=None):
self.prefix = prefix + ' ' if prefix else ''
def __enter__(self):
self.start = time.time()
def __exit__(self, *args):
print('%stime: %.4f sec' % (self.prefix, time.time() - self.start))
# 通过这个基准类来测试时间。
with Benchmark('Workloads are queued.'):
x = nd.random.uniform(shape=(2000, 2000))
y = nd.dot(x, x).sum()
MXNet包括用户直接用来交互的前端和系统用来执行计算的后端。
MXNet能够通过异步计算提升计算性能。
在“使用异步计算提升计算性能”一节中,我们提到使用异步计算可以使执行1000次计算的总耗时降为t1+1000t2+t3。这里为什么要假设1000t2>999t1?
答:当执行真正计算的时间t2足够大的时候,才有必要通过异步降低(这题猜的)
8.3自动并行计算
MXNet能够通过自动并行计算提升计算性能。例如一边计算一边传入内存。
练习
本节中定义的
run
函数里做了10次运算。它们之间也没有依赖关系。设计实验,看看MXNet有没有自动并行执行它们。答:有依赖,没有并行执行。
设计包含更加复杂的数据依赖的计算任务,通过实验观察MXNet能否得到正确的结果并提升计算性能。
答:可以。
当运算符的计算量足够小时,仅在CPU或单块GPU上并行计算也可能提升计算性能。设计实验来验证这一点。
def run(x): with d2l.Benchmark('Run.'): list = [nd.dot(x, x) for _ in range(10)] return list x_cpu = nd.random.uniform(shape=(20, 20)) x_gpu = nd.random.uniform(shape=(20, 20), ctx=mx.gpu(0)) run(x_cpu) run(x_gpu) nd.waitall() ——————————结果—————————————— Run. time: 0.0006 sec Run. time: 0.0000 sec
8.4多GPU计算
# 可以把各块显卡的显存上的数据加起来,然后再广播到所有的显存上。
def allreduce(data):
for i in range(1, len(data)):
data[0][:] += data[i].copyto(data[0].context)
for i in range(1, len(data)):
data[0].copyto(data[i])
# 将data平摊到ctx上
def split_and_load(data, ctx):
n, k = data.shape[0], len(ctx)
m = n // k # 简单起见,假设可以整除
assert m * k == n, '# examples is not divided by # devices.'
return [data[i * m: (i + 1) * m].as_in_context(ctx[i]) for i in range(k)]
# 多GPU的小批量训练
def train_batch(X, y, gpu_params, ctx, lr):
# 当ctx包含多块GPU及相应的显存时,将小批量数据样本划分并复制到各个显存上
gpu_Xs, gpu_ys = split_and_load(X, ctx), split_and_load(y, ctx)
with autograd.record(): # 在各块GPU上分别计算损失
ls = [loss(lenet(gpu_X, gpu_W), gpu_y)
for gpu_X, gpu_y, gpu_W in zip(gpu_Xs, gpu_ys, gpu_params)]
for l in ls: # 在各块GPU上分别反向传播
l.backward()
# 把各块显卡的显存上的梯度加起来,然后广播到所有显存上
for i in range(len(gpu_params[0])):
allreduce([gpu_params[c][i].grad for c in range(len(ctx))])
for param in gpu_params: # 在各块显卡的显存上分别更新模型参数
d2l.sgd(param, lr, X.shape[0]) # 这里使用了完整批量大小
# 完整的训练函数
def train(num_gpus, batch_size, lr):
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
ctx = [mx.gpu(i) for i in range(num_gpus)]
print('running on:', ctx)
# 将模型参数复制到num_gpus块显卡的显存上
gpu_params = [get_params(params, c) for c in ctx]
for epoch in range(4):
start = time.time()
for X, y in train_iter:
# 对单个小批量进行多GPU训练
train_batch(X, y, gpu_params, ctx, lr)
nd.waitall()
train_time = time.time() - start
def net(x): # 在gpu(0)上验证模型
return lenet(x, gpu_params[0])
test_acc = d2l.evaluate_accuracy(test_iter, net, ctx[0])
print('epoch %d, time %.1f sec, test acc %.2f'
% (epoch + 1, train_time, test_acc))
train(num_gpus=1, batch_size=256, lr=0.2) # 调用,输入gpu数量即可
练习
在多GPU训练实验中,使用2块GPU训练并将
batch_size
翻倍至512,训练时间有何变化?如果希望测试准确率与单GPU训练中的结果相当,学习率应如何调节?答:考虑ctx = [mx.gpu(0),mx.cpu(0)]来模拟两个gpu的效果。这种方法十分耗费时间,我这边测试44s一个周期。1.训练时间差不多。2.单GPU中lr为0.17,GPU+CPU中lr为0.2
(对于上述实验结果,持有迷惑行为,无力解释)
将实验的模型预测部分改为用多GPU预测。
# 偷看了9.1图像增广的答案 # 本函数已保存在d2lzh包中方便以后使用 def evaluate_accuracy(data_iter, net, ctx=[mx.cpu()]): if isinstance(ctx, mx.Context): ctx = [ctx] acc_sum, n = nd.array([0]), 0 for batch in data_iter: features, labels, _ = _get_batch(batch, ctx) for X, y in zip(features, labels): y = y.astype('float32') acc_sum += (net(X).argmax(axis=1) == y).sum().copyto(mx.cpu()) n += y.size acc_sum.wait_to_read() return acc_sum.asscalar() / n
8.5多GPU简洁计算
定义模型,初始化参数
划分数据样本到各个内存或显存上。
训练函数
训练
练习
本节使用了ResNet-18模型。试试不同的迭代周期、批量大小和学习率。如果条件允许,使用更多GPU来计算。
答:不想试,没什么意义。
有时候,不同设备的计算能力不一样,例如,同时使用CPU和GPU,或者不同GPU之间型号不一样。这时候,应该如何将小批量划分到内存或不同显卡的显存?
# 定义上下文 ctx = [mx.gpu(0),mx.cpu(0)] # 修改后的训练函数:不要传入gpu数量,而是传入ctx def train(ctx, batch_size, lr): train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) print('running on:', ctx) net.initialize(init=init.Normal(sigma=0.01), ctx=ctx, force_reinit=True) trainer = gluon.Trainer( net.collect_params(), 'sgd', {'learning_rate': lr}) loss = gloss.SoftmaxCrossEntropyLoss() for epoch in range(4): start = time.time() for X, y in train_iter: gpu_Xs = gutils.split_and_load(X, ctx) gpu_ys = gutils.split_and_load(y, ctx) with autograd.record(): ls = [loss(net(gpu_X), gpu_y) for gpu_X, gpu_y in zip(gpu_Xs, gpu_ys)] for l in ls: l.backward() trainer.step(batch_size) nd.waitall() train_time = time.time() - start test_acc = d2l.evaluate_accuracy(test_iter, net, ctx[0]) print('epoch %d, time %.1f sec, test acc %.2f' % ( epoch + 1, train_time, test_acc))
最后更新于
这有帮助吗?