Pytorch 基本操作

ぐ巨炮叔叔 提交于 2020-08-16 19:36:31

Pytorch 基础操作

主要是在读深度学习入门之PyTorch这本书记的笔记。强烈推荐这本书

1. 常用类numpy操作

torch.Tensor(numpy_tensor)

torch.from_numpy(numpy_tensor)

GPU上的Tensor不能直接转换为Numpy ndarry,要用.cpu()将其转换到CPU

# 第一种方式是定义 cuda 数据类型
dtype = torch.cuda.FloatTensor # 定义默认 GPU 的 数据类型
gpu_tensor = torch.randn(10, 20).type(dtype)

# 第二种方式更简单,推荐使用
gpu_tensor = torch.randn(10, 20).cuda(0) # 将 tensor 放到第一个 GPU 上
gpu_tensor = torch.randn(10, 20).cuda(1) # 将 tensor 放到第二个 GPU 上
# 将tensor放回CPU
cpu_tensor = gpu_tenor.cpu()
  1. 得到tensor大小
    .size()
    得到tensor数据类型
    .type()
    得到tensor的维度
    .dim()
    得到tnsor的所有元素个数
    .numel()






  2. 全1矩阵。数据类型是floatTensor
    torch.ones(n, m)
    转化为整型数据/浮点型数据
    .long() .float()
    返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数
    torch.rand(n, m)
    返回张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义





    torch.randn(n, m)

    返回一个1维张量,包含在区间start和end上均匀间隔的step个点
    torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

    沿着维度dim取最大值
    max_value, max_index = torch.max(x, dim)
    沿着维度dim对x求和
    torch.sum(x, dim)


    维度的变换:

    # 在第n维增加
    x.unsqueeze(n)
    # 减少一维
    x.squeeze(n)
    # 将 tensor 中所有的一维全部都去掉
    x.squeeze()
    # 重新排列维度
    x.permute(a, b, c)
    #交换tensor中的两个维度
    x.transpose(a, b)
    # view的操作。(reshape进阶版)
    x.view(-1, b) # -1表示任意大小
    x.view(a, b)
    x.view_as(others) # 这个挺方便的
    # 就是将x reshape成 others的形状
    

    Tips:
    pytorch中大多数的操作都支持 inplace 操作,也就是可以直接对 tensor 进行操作而不需要另外开辟内存空间,方式非常简单,一般都是在操作的符号后面加_

    inplace参数的理解:
    修改一个对象时:
    inplace=True:不创建新的对象,直接对原始对象进行修改;
    inplace=False:对数据进行修改,创建并返回新的对象承载其修改结果


2. Variable及自动求导机制

导入:
from torch.autograd import Variable

将Tensor变成Variable
x = Variable(x_tensor, requires_grad=True)

每个 Variabel都有三个属性,Variable 中的 tensor本身.data,对应 tensor 的梯度.grad以及这个 Variable 是通过什么方式得到的.grad_fn

求梯度操作:

x_tensor = torch.randn(10, 5)
y_tensor = torch.randn(10, 5)

# 将 tensor 变成 Variable
x = Variable(x_tensor, requires_grad=True) # 默认 Variable 是不需要求梯度的,所以我们用这个方式申明需要对其进行求梯度
y = Variable(y_tensor, requires_grad=True)

z = torch.sum(x + y)
print(z.data)
print(z.grad_fn)

# 求 x 和 y 的梯度
z.backward()

print(x.grad)
print(y.grad)

通过调用 backward 我们可以进行一次自动求导,如果我们再调用一次 backward,会发现程序报错,没有办法再做一次。这是因为 PyTorch 默认做完一次自动求导之后,计算图就被丢弃了,所以两次自动求导需要手动设置一个东西
x = Variable(torch.FloatTensor([3]), *requires_grad*=True)
y = x * 2 + x ** 2 + 3
y.backward(*retain_graph*=True)
设置 retain_graph 为 True 来保留计算图



**Tips: **

PyTorch0.4中,.data 仍保留,但建议使用 .detach(), 区别在于 .data 返回和 x 的相同数据 tensor, 但不会加入到x的计算历史里,且require s_grad = False, 这样有些时候是不安全的, 因为 x.data 不能被 autograd 追踪求微分 。 .detach() 返回相同数据的 tensor ,且 requires_grad=False ,但能通过 in-place 操作报告给 autograd 在进行反向传播的时候.
举例:
tensor.data

>>> a = torch.tensor([1,2,3.], requires_grad =True)
>>> out = a.sigmoid()
>>> c = out.data
>>> c.zero_()
tensor([ 0., 0., 0.])

>>> out                   #  out的数值被c.zero_()修改
tensor([ 0., 0., 0.])

>>> out.sum().backward()  #  反向传播
>>> a.grad                #  这个结果很严重的错误,因为out已经改变了
tensor([ 0., 0., 0.])

tensor.detach()

>>> a = torch.tensor([1,2,3.], requires_grad =True)
>>> out = a.sigmoid()
>>> c = out.detach()
>>> c.zero_()
tensor([ 0., 0., 0.])

>>> out                   #  out的值被c.zero_()修改 !!
tensor([ 0., 0., 0.])

>>> out.sum().backward()  #  需要原来out得值,但是已经被c.zero_()覆盖了,结果报错
RuntimeError: one of the variables needed for gradient
computation has been modified by an

此Tips从梦家的博文摘抄而来

3.构建网络

pytorch中有很多内置数学函数
import torch.nn.functional as F
来导入,例如:
F.sigmoid()


torch.clamp(input, min, max, out=None) → Tensor
来限制输入的上限和下限

手动更新参数其实挺麻烦的,可以用
torch.optim和数据类型nn.Parameter来操作
不过nn.Parameter 是默认要求梯度的
nn.optim.SGD可以用梯度下降法来更新参数
例:



# 使用 torch.optim 更新参数
from torch import nn
w = nn.Parameter(torch.randn(2, 1))
b = nn.Parameter(torch.zeros(1))

def logistic_regression(x):
    return F.sigmoid(torch.mm(x, w) + b)

optimizer = torch.optim.SGD([w, b], lr=1.)

# 进行 1000 次更新
import time

start = time.time()
for e in range(1000):
    # 前向传播
    y_pred = logistic_regression(x_data)
    loss = binary_loss(y_pred, y_data) # 计算 loss
    # 反向传播
    optimizer.zero_grad() # 使用优化器将梯度归 0
    loss.backward()
    optimizer.step() # 使用优化器来更新参数
    # 计算正确率
    mask = y_pred.ge(0.5).float()
    acc = (mask == y_data).sum().data[0] / y_data.shape[0]
    if (e + 1) % 200 == 0:
        print('epoch: {}, Loss: {:.5f}, Acc: {:.5f}'.format(e+1, loss.data[0], acc))
during = time.time() - start
print()
print('During Time: {:.3f} s'.format(during))

有几个关键操作:
optimizer.zero_grad()
归零梯度。相当于w.grad.data.zero_()
optimizer.step()
用优化器更新参数。相当于https://blog.csdn.net/lens___/article/details/83960810
w.data = w.data - 0.1 * w.grad.data
再举个栗子:





for e in range(100):
    out = logistic_regression(Variable(x))
    loss = criterion(out, Variable(y))
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if (e + 1) % 20 == 0:
        print('epoch: {}, loss: {}'.format(e+1, loss.data[0]))

上面的都是线性网络的例子,下面举一个神经网络的例子。用到nn.Parameter

# 定义两层神经网络的参数
w1 = nn.Parameter(torch.randn(2, 4) * 0.01) # 隐藏层神经元个数 2
b1 = nn.Parameter(torch.zeros(4))

w2 = nn.Parameter(torch.randn(4, 1) * 0.01)
b2 = nn.Parameter(torch.zeros(1))

# 定义模型
def two_network(x):
    x1 = torch.mm(x, w1) + b1
    x1 = F.tanh(x1) # 使用 PyTorch 自带的 tanh 激活函数
    x2 = torch.mm(x1, w2) + b2
    return x2

optimizer = torch.optim.SGD([w1, w2, b1, b2], 1.)

criterion = nn.BCEWithLogitsLoss()

# 我们训练 10000 次
for e in range(10000):
    out = two_network(Variable(x))
    loss = criterion(out, Variable(y))
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if (e + 1) % 1000 == 0:
        print('epoch: {}, loss: {}'.format(e+1, loss.data[0]))

记录一个决策边界绘制代码

def plot_decision_boundary(model, x, y):
    # Set min and max values and give it some padding
    x_min, x_max = x[:, 0].min() - 1, x[:, 0].max() + 1
    y_min, y_max = x[:, 1].min() - 1, x[:, 1].max() + 1
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole grid
    Z = model(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.ylabel('x2')
    plt.xlabel('x1')
    plt.scatter(x[:, 0], x[:, 1], c=y.reshape(-1), s=40, cmap=plt.cm.Spectral)

np.meshgrid的作用:
知乎专栏
contour和contourf都是画三维等高线图的,不同点在于contour() 是绘制轮廓线,contourf()会填充轮廓.。详细说明:
CSDN


4. Sequential 和 Module

Sequential

# Sequential基本操作
seq_net = nn.Sequential(
    nn.Linear(2, 4), # PyTorch 中的线性层,wx + b
    nn.Tanh(),
    nn.Linear(4, 1)
)

序列模块可以通过索引访问每一层
sq_net[0] 第一层
可以得到出第一层的权重
w0 = seq_net[0].weight
通过parameters可以取得模型的参数
param = seq_net.parameters()




模型的保存

1.
将参数和模型保存在一起
torch.save(seq_net, save_seq_net.pth')
参数一个是模型,一个是路径


读取保存的模型:
seq_net1 = torch.load('save_seq_net.pth')
2.
保存模型参数
torch.save(seq_net.state_dict(), save_seq_net_params.pth')
通过上面的方式,我们保存了模型的参数,如果要重新读入模型的参数,首先我们需要重新定义一次模型,接着重新读入参数
读入参数操作:
seq_net2.load_state_dict(toech.load('save_seq_net_params.pth))






Module
Module模板:

class 网络名字(nn.Module):
    def __init__(self, 一些定义的参数):
        super(网络名字, self).__init__()
        self.layer1 = nn.Linear(num_input, num_hidden)
        self.layer2 = nn.Sequential(...)
        ...
        
        定义需要用的网络层
        
    def forward(self, x): # 定义前向传播
        x1 = self.layer1(x)
        x2 = self.layer2(x)
        x = x1 + x2
        ...
        return x

注意的是,Module 里面也可以使用 Sequential,同时 Module 非常灵活,具体体现在 forward 中,如何复杂的操作都能直观的在 forward 里面执行。(想要亲身体会请看一些论文源码),里面可以用各种数据处理.
建议自己实现一个resnet网络。可以很快熟悉基本操作,以后论文基本上都是用这个网络

Module中,访问模型的某一层可以直接通过名字来访问:
l1 = mo_net.lay1(这是基本的操作吧)
访问权重:l1.weight

定义完网络,就可以for in 来训练网络了。
直接:
out = mo_net(Variable(x))
就可以得到output


保存模型一样,还是用
.state_dict()

5. 数据的读取操作(MNIST为例)

pytorch是内置了MNIST的
from torchvision.datasets import mnist
然后就可以通过内置函数来下载mnist数据集了
train_set = mnist.MNIST('./data', *train*=True, *download*=True)
test_set = mnist.MNIST('./data', *train*=False, *download*=True)



注:数据结构是这样的:
a_data, a_label = train_set[i]
a_data指的是图片矩阵,a_label则是对应的标签
读入的数据的PIL库中的格式
最好转换为numpy array格式来:
a_data = np.array(a_data, dtype='float32')




接下来就对a_data进行处理,由于要用神经元,所以得拉平,用reshape操作。当然还要正则化等数据处理。然后就可以正常进行了。用softmax函数作为评价函数即可。用BCELoss(交叉熵损失)。

注:用 _, pred = out.max(1)来记录准确度。0维是batch维,共64, 1维则是通过网络预测出来的评分结果维,有10个,我们取最大评分的pred即可。最后通过

.max(dim)方法中,若是2维函数,则是0代表每列的最大值,1代表每行的最大值

训练的时候,要用DataLoader定义一个数据迭代器
注意!这里可以进行很多操作!比如说数据的处理,图片的分割等等!

from torch.utils.data import DataLoader
# 使用 pytorch 自带的 DataLoader 定义一个数据迭代器
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
test_data = DataLoader(test_set, batch_size=128, shuffle=False)

使用这样的数据迭代器是非常有必要的,如果数据量太大,就无法一次将他们全部读入内存,所以需要使用 python 迭代器,每次生成一个批次的数据

上面只是简单举一个例子。实际应用的时候最好单独写一个文件.方便修改

然后用
a, a_label = next(iter(train_data))

注意:
net.train()是进入训练模式(train集)
net.eval()是进入预测模式(test集)

一般来说打印要打印:
epoches, Train_Loss, Train_Acc, Eval_Loss, Eval_Acc。方便比较
并且,在训练和测试的过程中,要用losses,acces, eval_losses和 eval_acces集合来实时保存训练或者测试出来的loss和acc。然后训练完可以画出图来,方便对数据进行分析改进

6. 初始化参数操作

from torch.nn import init
Xavier初始化:
init,xavier_uniform(net[0].weight)
用Xavier初始化方法初始化网络的第一层
还有很多初始化方法。可以查阅:
简书




7. pytorch中实现一些优化器的方法

  1. SGD
# 手动实现
def sgd_update(parameters, lr):
    for param in parameters:
        param.data = param.data - lr * param.grad.data

调用内置函数:
optimzier = torch.optim.SGD(net.parameters(), learning_rate)

  1. 动量法

    # 手动实现
    def sgd_momentum(parameters, vs, lr, gamma):
        for param, v in zip(parameters, vs):
            v[:] = gamma * v + lr * param.grad.data
            param.data = param.data - v
    

    调用内置函数:
    torch.optim.SGD(momentum=0.9)
    仅仅在SGD函数中加一个动量变量就行了

  2. Adagrad 自适应学习率优化算法
    Adagrad 的核心想法就是,如果一个参数的梯度一直都非常大,那么其对应的学习率就变小一点,防止震荡,而一个参数的梯度一直都非常小,那么这个参数的学习率就变大一点,使得其能够更快地更新
    \(\frac{\eta}{s+\epsilon}\)

def sgd_adagrad(parameters, sqrs, lr):
    eps = 1e-10
    for param, sqr in zip(parameters, sqrs):
        sqr[:] = sqr + param.grad.data ** 2
        div = lr / tortorch.optim.Adagrad()ch.sqrt(sqr + eps) * param.grad.data
        param.data = param.data - div

调用内置函数:
torch.optim.Adagrad(net.parameters(), lr=1e-2)

  1. RMSProp
    Adagrad 算法有一个问题,就是学习率分母上的变量 s 不断被累加增大,最后会导致学习率除以一个比较大的数之后变得非常小,这不利于我们找到最后的最优解,所以 RMSProp 的提出就是为了解决这个问题。

    用移动平均来计算这个s

    \[s_i = \alpha s_{i-1} + (1 - \alpha) \ g^2 \]

\[\frac{\eta}{\sqrt{s + \epsilon}} \]

g为当前求出的参数梯度,\(\alpha\)为移动平均的系数

# 手动实现
def rmsprop(parameters, sqrs, lr, alpha):
    eps = 1e-10
    for param, sqr in zip(parameters, sqrs):
        sqr[:] = alpha * sqr + (1 - alpha) * param.grad.data ** 2
        div = lr / torch.sqrt(sqr + eps) * param.grad.data
        param.data = param.data - div

用内置函数:
torch.optim.RMSprop()

  1. Adadelta
    Adadelta 跟 RMSProp 一样,先使用移动平均来计算 s

    \[s = \rho s + (1 - \rho) g^2 \]

    这里 \(\rho\) 和 RMSProp 中的 \(\alpha\) 都是移动平均系数,g 是参数的梯度,然后我们会计算需要更新的参数的变化量

    \[g' = \frac{\sqrt{\Delta \theta + \epsilon}}{\sqrt{s + \epsilon}} g \]

    \(\Delta \theta\) 初始为 0 张量,每一步做如下的指数加权移动平均更新

    \[\Delta \theta = \rho \Delta \theta + (1 - \rho) g'^2 \]

    最后参数更新如下

    \[\theta = \theta - g' \]

# 手动实现(反正我没怎么看这部分)
def adadelta(parameters, sqrs, deltas, rho):
    eps = 1e-6
    for param, sqr, delta in zip(parameters, sqrs, deltas):
        sqr[:] = rho * sqr + (1 - rho) * param.grad.data ** 2
        cur_delta = torch.sqrt(delta + eps) / torch.sqrt(sqr + eps) * param.grad.data
        delta[:] = rho * delta + (1 - rho) * cur_delta ** 2
        param.data = param.data - cur_delta

调用函数:
torch.optim.Adadelta(net.parameters(), rho= 0.9)

  1. Adam
    现在一般都用adam

    # 手动实现
    def adam(parameters, vs, sqrs, lr, t, beta1=0.9, beta2=0.999):
        eps = 1e-8
        for param, v, sqr in zip(parameters, vs, sqrs):
            v[:] = beta1 * v + (1 - beta1) * param.grad.data
            sqr[:] = beta2 * sqr + (1 - beta2) * param.grad.data ** 2
            v_hat = v / (1 - beta1 ** t)
            s_hat = sqr / (1 - beta2 ** t)
            param.data = param.data - lr * v_hat / torch.sqrt(s_hat + eps)
    

    调用函数:
    torch.optim.Adam(net.parameters(), lr=1e-3)

8. 卷积神经网络的构建

  • 卷积在pytorch中有两种方式
    torch.nn.Conv2d()
    torch.nn.functional.conv2d()
    两个本质是一样的,输入的要求也是一样的
    输入的是一个torch.autograd.Variable()类型,大小为(batch, channel, H, W)



    使用 nn.Conv2d()相当于直接定义了一层卷积网络结构,而使用 torch.nn.functional.conv2d() 相当于定义了一个卷积的操作,所以使用后者需要再额外去定义一个 weight,而且这个 weight 也必须是一个 Variable,而使用 nn.Conv2d() 则会帮我们默认定义一个随机初始化的 weight,如果我们需要修改,那么取出其中的值对其修改,如果不想修改,那么可以直接使用这个默认初始化的值,非常方便

    实际使用中我们基本都使用 nn.Conv2d() 这种形式

  • 池化操作也有两种方法:
    nn.MaxPool2d()
    torch.nn.functional.max_pool2d()

  • 批标准化 Batch Normalization

    首先肯定要对数据进行数据预处理。
    现在一般是进行中心化和标准化。PCA和白化很少用了。
    这里要注意,中心化和标准化的时候,使用的方差和均值统统都是用训练集的数据。包括预处理测试集和验证集数据的时候。

    批标准化,简而言之,就是对于每一层网络的输出,对其做一个归一化,使其服从标准的正态分布,这样后一层网络的输入也是一个标准的正态分布,所以能够比较好的进行训练,加快收敛速度。

pytorch 当然也为我们内置了批标准化的函数,一维和二维分别是
torch.nn.BatchNorm1d() torch.nn.BatchNorm2d()
pytorch 不仅将 γγ 和 ββ 作为训练的参数,也将 moving_meanmoving_var 也作为参数进行训练

9. 数据增强操作

常用的数据增强方法如下:
1.对图片进行一定比例缩放
2.对图片进行随机位置的截取
3.对图片进行随机的水平和竖直翻转
4.对图片进行随机角度的旋转
5.对图片进行亮度、对比度和颜色的随机变化




这些方法一般是用torchvision中的transforms来进行操作,还有PIL库中的image,以及sys库用来操作文件

import sys
from PIL import image
from torchvision import transforms as tfs

# 读入一张图片
im = Image.open('./cat.png')

比例缩放:
new_im = tfs.Resize((100, 200))(image)

随机位置截取:
在 torchvision 中主要有下面两种方式
一个是 torchvision.transforms.RandomCrop()
传入的参数就是截取出的图片的长和宽,对图片在随机位置进行截取
第二个是 torchvision.transforms.CenterCrop()
同样传入截取初的图片的大小作为参数,会在图片的中心进行截取




随机水平翻转(镜像)
torchvision.transforms.RandomHorizontalFlip()

随机竖直翻转: torchvision.transforms.RandomVerticalFlip()

随机角度旋转:
torchvision.transforms.RandomRotation(a)
a是角度

亮度,对比度和颜色的变化
torchvision.transforms.ColorJitter(brightness=1, contrast=1, hue=0.5, (R,G,B))
第一个参数就是亮度的比例,第二个是对比度,第三个是饱和度,第四个是颜色

brightness: 随机从 0 ~ 2 之间亮度变化,1 表示原图
contrast: 随机从 0 ~ 2 之间对比度变化,1 表示原图
hue: 随机从 -0.5 ~ 0.5 之间对颜色变化

上面这么多图像增强方法,其实是可以联合起来用的。比如先做随机翻转,然后随机截取,再做对比度增强等等,torchvision 里面有个非常方便的函数能够将这些变化合起来,就是 torchvision.transforms.Compose()

# 举例im_aug = tfs.Compose([
    tfs.Resize(120),
    tfs.RandomHorizontalFlip(),
    tfs.RandomCrop(96),
    tfs.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5)
])

10 正则化操作与学习率衰减

regularzation现在很少用dropout, 而是用正则化来惩罚权重。
torch.optim.SGD(net.parameters(), lr=0.1, weight_decay=1e-4)
weight_decay参数就是权重衰减。 意思就是正则化。这是L2正则。
注意正则项的系数的大小非常重要,如果太大,会极大的抑制参数的更新,导致欠拟合,如果太小,那么正则项这个部分基本没有贡献,所以选择一个合适的权重衰减系数非常重要.一般尝试会用1e-4或者1e-3来进行。


在 pytorch 中学习率衰减非常方便,使用 torch.optim.lr_scheduler

或者用参数组的方式实现:
参数组:就是我们可以将模型的参数分成几个组,每个组定义一个学习率。这个参数组是一个字典,里面有很多属性,比如学习率,权重衰减等等
例:optimizer.param_groups[0]['lr']
optimizer.param_groups[0]['weight_decay']


def set_learning_rate(optimizer, lr):
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr
...
# 训练途中修改学习率
if epoch == 20:
	set_learning_rate(optimizer, 0.01) # 20 次修改学习率为 0.01

11. 主流网络实现(略)

数据集cifar10
torchvision.datasets.CIFAR10

此部分最好自己手动实现各个网络
更能熟悉

  1. VGGNet:

  2. GoogleNet



    GoogleNet的改进:
    v1:最早的版本
    v2:加入 batch normalization 加快训练v3:对 inception 模块做了调整
    v4:基于 ResNet 加入了 残差连接


  3. ResNet



  4. DenseNet

    短路链接机制:

    密集链接机制:

    前向过程:

    网络结构:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!