PyTorch爬坑指南

用PyTorch也有个把年头了,可是偶尔仍是会出现各类各样的bug,当bug积累到必定数量就会引发质变——写成一个笔记来长记性……bash

Variable和Tensor

0.4以后的版本能够直接用Tensor代替Variable了,不须要再繁琐得包裹一层Variable网络

Tensor的转置

tensor的转置有不少方式,好比.transpose.transpose_是最经常使用的(带下划线的是在原tensor上作改变),可是这个转置方式有个限制就是仅能对两个维度进行操做,是个“根正苗红”的转置函数。app

有时候须要将更多的维度进行交换,这个时候就能够用.permuate函数,该函数接受一个维度列表,而后根据列表中的维度的排列顺序更新tensor的维度排列。机器学习

升维和降维

升维.unsqueeze是在指定的维度上插入一维,可是数据实际上没有发生变化。好比下面得示例:函数

In [1]: a
Out[1]:
tensor([[[-0.7908, -1.4576, -0.3251],
         [-1.2053,  0.3667,  0.9423],
         [ 0.0517,  0.6051, -0.1360],
         [ 0.8666, -1.4679, -0.4511]]])

In [2]: a.shape
Out[2]: torch.Size([1, 4, 3])

In [3]: a.unsqueeze(2).shape
Out[3]: torch.Size([1, 4, 1, 3])
复制代码

降维.squeeze是升维得逆操做,会消除全部维度为1的维度:学习

In [1]: a.shape
Out[1]: torch.Size([1, 4, 3])

In [2]: a.squeeze().shape
Out[2]: torch.Size([4, 3])
复制代码

Tensor的contiguous

一个Tensor执行转置操做后,实际上数据并无发生任何变化,只是读取数据的顺序变化了。这是一种节约空间和运算的方法。ui

不过转置以后的数据若是须要进行.view等操做的话,因为数据在逻辑上并不连续,所以须要手动调用contiguous让数据恢复连续存储的模样。spa

Loss Functions的参数格式究竟是啥啊

这个是早期常常会遇到的状况,咋一会要FloatTensor,一会就要LongTensor了?要解决这个问题,仍是须要深刻了解不一样的loss function到底在作啥。调试

BCELoss

二分类的交叉熵,计算公式以下:code

\sum_{i}{-y_ilog\hat{y_i}-(1-y_i)log(1-\hat{y_i})}

这样就不难看出 y_i\hat{y_i} 都须要是FloatTensor了。若是是不一样的tensor进行运算会出错:

Expected object of type torch.FloatTensor but found type torch.LongTensor
复制代码

CrossEntropyLoss

上面的BCELoss是计算二分类状况下的损失函数,那么当遇到多分类问题的时候,也想用交叉熵来计算的话,就须要用到CrossEntropyLoss了。

不管是二分类仍是多分类,均可以用最简单的L2损失来计算,可是若是使用Sigmoid做为激活函数的话容易致使梯度消失,致使效果很差

自定义网络必定要初始化权值

权值不初始化会出事的……

有时候会遇到一些结构清奇的网络,各类参数纠缠在一块儿,所以torch自带的函数可能不够用,那就须要自定义网络结构了。通常会从最底层的矩阵开始构建,就像tensorflow同样。好比定义一个不带偏置项的全链接层能够用nn.Linear(xxx, xxx, bias=False)。但特殊状况下甚至连全链接层内部的细节都要本身定义,这个时候就须要参考torch的源码了,好比全链接层的部分源码:

class Linear(Module):
    def __init__(self, in_features, out_features, bias=True):
        super(Linear, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.Tensor(out_features, in_features))
        if bias:
            self.bias = Parameter(torch.Tensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, input):
        return F.linear(input, self.weight, self.bias)
复制代码

在这个例子中已经能学到不少了,这里重点参考Parameters的使用(out_feature在前)和权值初始化函数reset_parameters的内部细节。

以前一次机器学习的做业,相同的模型,PyTorch版始终达不到Tensorflow版的准确率,大概就是权值初始化二者存在差别吧。

CUDA的一些坑

自定义的网络不会自动转cuda??

以前有用过本身先定义了一个简单的底层网络,方便上层网络的构建。这样定义出来的网络在CPU上运行的好好的,可是当我把模型迁移到GPU上时却发现模型出现了类型不符的错误:

Expected object of type torch.FloatTensor but found type torch.cuda.FloatTensor for argument
复制代码

后来在调试的时候发现对上层网络调用.cuda后,下层网络却仍是处于device(type='cpu')!虽然解决这个问题最简单的方法就是把底层网络的定义放到上层网络中,可是这样不利于代码的维护,而且在不一样网络中共享模块也会变得很复杂。

PyTorch在CPU和GPU数据上的迁移的确没有tensorflow来的方便……

在GitHub上看到不少相似的问题的解决方法,其中有一个点子很不错:

if torch.cuda.available():
    import torch.cuda as t
else:
    import torch as t
复制代码

若是一开始写代码的时候就考虑到自动CUDA的话,这的确是个好办法,若是已经写了臃肿的代码再改写仍是会很繁琐。

最终我选择的方案仍是重写.cuda函数:

def cuda(self, device=None):
    r"""Moves all model parameters and buffers to the GPU..."""
    return self._apply(lambda t: t.cuda(device))
复制代码

.cuda函数中对子模块也调用.cuda就能够了。

CUDA数据不能直接转numpy数据

若是一个数据已经迁移到GPU上了,那么若是要将运算结果转为符合numpy特性的数据就不是这么直接的了,通常会报错:

TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first. 复制代码

解决方案报错信息已经给了。固然还能够将以后须要numpy来帮忙的运算改为PyTorch支持的运算,这样速度还更快呢,毕竟条条大路通罗马。

相关文章
相关标签/搜索