Mxnet和Pytorch区别

平时主要使用mxnet和pytorch,下面记录下在代码中怎么使用GPU

1. mxnet

1.1. 单GPU

GPU计算

1
2
3
4
5
6
7
8
9
10
import mxnet as mx
import os

#指定使用哪块GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "3"
#被指定的GPU编号默认为0
ctx = mx.gpu(0)
#模型、数据都需要拷贝到GPU中
nd.array(data,ctx)
data.as_in_context(ctx)

1.2. 多GPU

多GPU计算
Run MXNet on Multiple CPU/GPUs with Data Parallelism

1
2
3
4
5
6
7
8
9
import mxnet as mx
import os

ctx_id = '1,2,3,4'
os.environ["CUDA_VISIBLE_DEVICES"] = ctx_id
num_gpus = len(ctx_id.split(','))
ctx = [mx.gpu(i) for i in range(num_gpus)]

#使用split_and_load()将数据分配到多个GPU上

2. pytorch

2.1. 单GPU

1
2
3
4
5
6
7
8
9
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "1"

if torch.cuda.is_available():
device = torch.device("cuda:0")
else:
device = torch.device("cpu")
#使用to()将数据拷贝到GPU上
train_feature.to(device)

2.2. 多GPU

pytorch-multigpu

OPTIONAL: DATA PARALLELISM

MULTI-GPU EXAMPLES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
os.environ["CUDA_VISIBLE_DEVICES"] = "1,2,3"  
if torch.cuda.is_available():
device = torch.device("cuda:0")
else:
device = torch.device("cpu")
#pytorch在使用多GPU时,需要先将数据和模型拷贝到GPU-0上,
#使用多GPU的关键代码
if torch.cuda.device_count() > 1:
print("Let's use", torch.cuda.device_count(), "GPUs!",flush=True)
#数据并行
model = nn.DataParallel(model)
model.to(device)

for data in rand_loader:
input = data.to(device)
output = model(input)
print("Outside: input size", input.size(),
"output_size", output.size())

DataParallel自动分割数据,并发送到多个GPU上,每个GPU上完成前向传播, DataParallel收集每个GPU上的结果。


2020.2.11更新

3. mxnet和pytorch区别

在写程序时,主要用到mxnet和pytorch框架,这里针对2者在代码上的不同做个总结,下面的不同都是我自己在写程序遇到的,仅仅是一部分,仅供参考。

3.1. NDArray和Tensor

3.1.1. 和numpy转换

  • mxnet

    • Numpy—>NDArray
      nd.array(a)
    • NDArray—>Numpy
      D.asnumpy()
  • pytorch

    • Numpy—>Tensor
      D = torch.from_numpy(a)
    • Tensor—>Numpy
      a = D.numpy()

3.1.2. 转换为标量

  • mxnet
    asscalar()将函数结果转换成Python的标量
    X.sum().asscalar()
  • pytorch
    item()将函数结果转换成Python的标量
    X.sum().item()

3.1.3. 改变数据形状

  • mxnet

    • reshape()

      1
      2
      X = x.reshape((3, 4))
      X = x.reshape((-1, 4))
  • pytorch

    • view()

      1
      2
      y = x.view(15)
      z = x.view(-1, 5) # -1所指的维度可以根据其他维度的值推出来

3.1.4. 数据转到GPU上

  • mxnet
    使用as_in_context()

    1
    2
    3
    4
    #在gpu上创建NDArray
    B = nd.random.uniform(shape=(2, 3), ctx=mx.gpu(1))

    z = x.as_in_context(mx.gpu())
  • pytorch
    使用to()函数

    1
    2
    3
    4
    if torch.cuda.is_available():
    device = torch.device("cuda") # GPU
    y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor
    x = x.to(device)

3.2. loss计算

  • maxnet
1
2
3
4
5
6
7
8
9
10
loss = L2Loss()
for epoch in range(1,num_epochs + 1):
for X,y in data_iter:
with autograd.record():
#l维度(batch_size,)
l = loss(net(X),y)
#等价于l.sum().backward()
l.backward()
#step对参数的梯度进行更新,传入batch_size是因为计算得到的梯度是一个batch样本的梯度和,需要除以batch_size得到梯度的平均值
trainer.step(batch_size)

注1:在使用y.backward()自动求梯度时,如果y不是一个标量,mxnet将默认先对y中元素求和得到新的变量,在求该变量关于x的梯度

注2:mxnet的L2Loss()返回值维度(batch_size,),即batch中每个样本的loss。需要在step()中传入batch_size参数

  • pytorch
1
2
3
4
5
6
7
8
9
loss = MSELoss()
for epoch in range(1,num_epochs + 1):
for X,y in data_iter:
#l维度(1,)
l = loss(net(X),y)
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
#step对参数的梯度进行更新,不需要传入batch_size参数
trainer.step()

注1:在y.backward()时,如果y是标量,则backward()不需要传入任何参数,否则,需要传入与y同形的Tensor

注2:grad在反向传播过程中是累加的,这意味着每一个batch运行反向传播,梯度都会累加之前batch的梯度,所以一般在反向传播传播之前需要把梯度清零。

注3:pytorch的MSELoss()返回值维度(1,),Tensor中只有1个数,也称作scalar:零维的张量。因为MSELoss()返回值是batch中所有样本loss的平均值。所以在step()中不需要传入batch_size参数

3.3. RNN输入维度

这里只讨论单向RNN

  • mxnet
    • 输入数据维度默认(T,batch_size,input_size)
    • 初始化state维度(num_layers,batch_size,hidden_size),可以不输入,则默认用全0初始化
    • output输出数据维度(T,batch_size,hidden_size),表示最后一层所有时间步的隐藏状态
    • out_state:输出维度和state一样,都是(num_layers,batch_size,hidden_size),表示所有层最后一个时间步的隐藏状态,如果state不输入,则out_state不会被返回
    • 如果想让输入维度是(B,T,C),则指定参数layout=NTC
  • pytorch
    • 输入数据维度默认(T,batch_size,input_size)
    • 初始化state维度(num_layers,batch_size,hidden_size),可以不输入,则默认用全0初始化
    • output输出数据维度(T,batch_size,hidden_size),表示最后一层所有时间步的隐藏状态
    • out_state:输出维度和state一样,都是(num_layers,batch_size,hidden_size),表示所有层最后一个时间步的隐藏状态,如果state不输入,则out_state不会被返回
    • 如果想让输入维度是(B,T,C),则指定参数batch_first=True

注意:Mxnet和Pytorch的唯一区域是:mxnet的state不指定,out_state就不会输出,pytorch不管state是否指定,out_state都会输出

3.4. Transformer输入维度

  • mxnet
    Mxnet中有一个NLP相关的包gluonnlp,里面封装了Transformer,这里只讨论TransformerEncoder

    • 输入维度为(batch_size, length, C_in)
    • 输出维度为(batch_size, length, C_out)
  • pytorch
    Pytorch中也封装了Transformer,TransformerEncoder

    • 输入维度为(length, batch_size, Embedding)
    • 输出维度为(length, batch_size, Embedding)
      nn.TransformerEncoderLayer
    • 输入维度为(length, batch_size, Embedding)
    • 输出维度为(length, batch_size, Embedding)

【注意】Mxnet版的Tranformer中Dropout的p默认为0,Pytorch版的Transformer中Dropout的p默认为0.1

3.5. 多GPU运行

  • mxnet

    mxnet.gluon.utils.split_and_load(data, ctx_list, batch_axis=0, even_split=True)

    split_and_load()将一个batch中的数据划分为多个小batch到多个GPU上。
    【注意】默认batch_axis=0,也就是默认划分axis=0的维度,如果batch不在第0维,例如RNN中,输入的维度默认为TNC,batch在第1维,那就需要在split_and_load中指定batch_axis=1
    split_and_load()返回值是list,里面每个元素是NDArray,经过split_and_load()对一个batch数据进行分割,得到的X,y的shape变成(batch_size/n,*),经过模型输出得到的predicted维度也是(batch_size/n,*)
    计算得到的loss也是list类型,里面存储一个batch在多个GPU上计算的loss,对每个loss分别反向传播求梯度,然后再使用step()更新参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    for epoch in range(100):
    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是list,里面有n个NDArray,n是GPU的个数
    ls = [loss(net(gpu_X),gpu_y for gpu_X,gpu_y in zip(gpu_Xs,gpu_ys)
    for l in ls:
    l.backward()

    train.step(batch_size)

  • pytorch
    Pytorch学习

    CLASS torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)

    DataParallel自动将batch进行划分,划分维度默认dim=0,如果batch在其他维度,通过dim指定
    pytorch使用DataParallel()来进行数据并行,不需要手动将数据划分到多个GPU上,即train_feature的维度是(batch_size,*),经过模型输出的predicted的维度也是(batch_size,*),这一点和mxnet不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #使用多GPU的关键代码
    if torch.cuda.device_count() > 1:
    transformer_model = nn.DataParallel(transformer_model)#默认全部GPU
    transformer_model.to(device0)

    for epoch in range(1,training_epoch+1):
    for train_feature,train_label in train_loader:
    train_feature = train_feature.to(device0)
    train_label = train_label.to(device0)
    #train_label:(tabch_size,*)
    predicted = transformer_model(train_feature)
    l = loss(predicted,train_label)#l的shape:(1,)
    trainer.zero_grad()
    l.backward()
    trainer.step()

从这2个图中可以看出Mxnet和Pytorch多GPU运行的区别。
Mxnet将batch数据和label都分配到每个GPU上,然后在每个GPU上都计算loss,然后再把loss聚合。
Pytorch只把batch数据分配到每个GPU上,在每个GPU上得到输出,gather到主设备上,然后再和label计算loss。
Mxnet会比Pytorch会一些,因为Mxnet的loss是在不同的GPU上计算的,但Pytorch的写法更简单。
为了解决Pytorch在一张卡上计算loss的问题,有人提出了解决方案,参考基于PyTorch使用大batch训练神经网络pytorch 多GPU训练总结(DataParallel的使用)

3.6. 将网络加入list中

  • mxnet

    1
    2
    3
    4
    5
    6
    self.submodules = []
    with self.name_scope():
    for backbones in all_backbones:
    self.submodules.append(
    ASTGCN_submodule(num_for_prediction, backbones))
    self.register_child(self.submodules[-1])
  • pytorch
    Pytorch中nn.Module, nn.ModuleList, nn.Sequential,统称为容器,因为我们可以添加模块module到它们中。但有时候容易混淆,我们主要讨论nn.ModuleList, nn.Sequential的使用。

    【nn.ModuleList】
    PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景
    Pytorch使用 nn.ModuleList() 和nn.Sequential()编写神经网络模型
    ModuleList是一个类,可以将Module任意子类(Conv2d,Linear等)加入到list中,方法和Python自带的list一样,使用append或extend等操作。但不同于一般的list,加入到ModuleList里的module会自动注册到整个网络上,同时module的参数也会自动添加到整个网络中

1
2
3
4
5
6
7
8
9
10
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.linears = nn.ModuleList([nn.linear for i in range(10)])

# ModuleList can act as an iterable, or be indexed using ints
def forward(self, x):
for i, l in enumerate(self.linears):
x = self.linears[i // 2](x) + l(x)
return x

3.7. 固定随机种子

  • mxnet版本

    1
    2
    3
    4
       seed = 2020
    mx.random.seed(seed)
    np.random.seed(seed)
    random.seed(seed)
  • pytorch版本

    1
    2
    3
    4
    5
    6
    seed = 2020
    torch.manual_seed(seed) # cpu
    torch.cuda.manual_seed(seed) #gpu
    torch.backends.cudnn.deterministic=True#cudn,cpu/gpu结果一致
    np.random.seed(seed)#numpy
    random.seed(seed)#ramdom

3.8. 访问模型参数

  • mxnet

    1
    2
    for name,param in gru.collect_params().items():
    print(name,':',parameters.size())
  • pytorch

    1
    2
    3
    4
    5
    for name,parameters in net.named_parameters():
    print(name,':',parameters.size())

    for parameters in net.parameters():
    print(parameters)
打赏
0%