Pytorch入门2

本文的内容整理自:

《动手学深度学习(Pytorch版)》跟李沐学AI的个人空间-跟李沐学AI个人主页-哔哩哔哩视频 (bilibili.com)

《Pytorch框架基础》01-05-mp4-autograd与逻辑回归_哔哩哔哩_bilibili

《Python深度学习-基于Pytorch》

1. 求导

在神经网络的训练中,想让 loss 下降,就需要使用 w 相对于 loss 的梯度来更新 w,因此计算梯度或者说导数就是神经网络框架的必备功能之一,在PyTorch中也提供了相应的功能。当设置torch.Tensor对象的属性.requires_grad=True(或者调用.requires_grad_(True)也可以)后,PyTorch就会开始追踪在该变量上的所有操作。在完成计算后,可以调用 .backward()函数,自动计算所有被追踪对象的梯度,得到的梯度结果保存在属性 .grad中。

需要注意的是我们一般只能在一个标量上进行求导操作,如果对象是一个张量类型,可以在对其进行.sum()之后再进行.backward()

import torch
# note:此处必须使用浮点型数据,否则会报错
# Only Tensors of floating point and complex dtype can require gradients
x = torch.arange(4.0, requires_grad=True)
print(x)
tensor([0., 1., 2., 3.], requires_grad=True)

通过打印x,可以看到变量x中的属性requires_grad的值为True,此时执行

y = x + 2
print(y)
tensor([2., 3., 4., 5.], grad_fn=<AddBackward0>)

由于y是一个基于x的操作(函数),所以它带有grad_fn属性

z = y * y * 3
out = z.mean()
print('z=', z)
print('out=', out)
z= tensor([12., 27., 48., 75.], grad_fn=<MulBackward0>)
out= tensor(40.5000, grad_fn=<MeanBackward0>)
out.backward()
# 输出梯度 d(out)/dx
print(x.grad)
tensor([3.0000, 4.5000, 6.0000, 7.5000])

之所以得到上述结果是因为
\begin{align} out =& mean(z) \\ z =& 3y^2 \\ y =& x+2 \\ \end{align}
则根据链式法则可知:
\begin{align} \frac{D out}{D x} =& \frac{D out}{D z} \frac{D z}{D y}\frac{Dy}{Dx} \\ =& \begin{bmatrix} \frac{1}{4} & \frac{1}{4} & \frac{1}{4} & \frac{1}{4} \end{bmatrix} \cdot 6y \cdot 1 \\ =& \begin{bmatrix} \frac{1}{4} & \frac{1}{4} & \frac{1}{4} & \frac{1}{4} \end{bmatrix} \cdot 6(x+2) \cdot 1 \\ =& \begin{bmatrix} 3 & 4.5 & 6 & 7.5\\ \end{bmatrix} \end{align}

默认情况下,PyTorch会累计梯度,因此如果再次计算的时候需要调用.zero_()函数,以清除之前的值。

x.grad.zero_()
out1 = (x * x).sum()
out1.backward()
print(x.grad)
tensor([0., 2., 4., 6.])

对于上面的代码而言,如果没有使用x.grad.zero_(),则PyTorch会将此次计算得到的[0,2,4,6]与之前计算得到的[3,4.5,6,7.5]进行相加得到结果[3,6.5,10,13.5]

当在Torch对象上调用.detach()方法时,可以将该对象进行剥离,防止未来的计算会被追踪。例如在下面的代码中,我们将u设置为y.detach(),此时可以将u视为一个与x无关的常量。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
print(x.grad)
tensor([0., 1., 4., 9.])

1.梯度不自动清零
2.依赖于叶子节点的节点,require_grad默认为True
3.叶子节点不可以执行in-place操作(原地操作)

2. 线性回归

一般而言,深度学习模型的训练步骤主要包括五个模块:

  • 数据
  • 模型
  • 损失函数 得到损失函数后,需要在该函数上调用backward()来得到梯度
  • 优化器 用于更新模型参数,因此在构造优化器的时候,需要向其中传入模型参数
  • 迭代训练

接下来我们用一个线性回归的例子来将之前学到的知识利用起来,首先来构造一个数据集。在此使用线性模型的参数为
w=\begin{bmatrix}2 \\ -3.4\end{bmatrix}b=4.2

2.1 手工版

首先可以利用基础的PyTorch内容,来构造一个线性回归的模型,第一步就是生成数据

import random
import torch

def synthetic_data(w, b, num_examples):
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w)+b
    y += torch.normal(0, 0.01, y.shape)
    return  X, y.reshape((-1, 1))

# 真实的权重和偏置
true_w = torch.tensor([2, -3.4])
true_b = 4.2

features,labels = synthetic_data(true_w, true_b, 1000)
print('features:', features[0], '\nlabel:', labels[0])
features: tensor([0.1043, 0.0730]) 
label: tensor([4.1636])

此处为了简化显示,使用了李沐老师开发的d2l工具包

pip install d2l
# pytorch 的某些版本中,要想把Tensor对象转换为numpy对象,需要先调用detach()函数

from d2l import torch as d2l
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(),
                labels.detach().numpy(), 1)
image.png
# 数据迭代器
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i:min(i+batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
tensor([[ 0.5895,  2.6396],
        [ 1.3606, -0.6769],
        [ 0.2840,  3.0876],
        [-0.5803, -0.1469],
        [-0.6710,  1.4318],
        [-0.5053, -0.3726],
        [ 1.5884,  0.2266],
        [ 0.3222, -0.7637],
        [ 0.0375, -1.9838],
        [-0.1626,  0.5017]]) 
 tensor([[-3.5732],
        [ 9.2212],
        [-5.7238],
        [ 3.5467],
        [-2.0095],
        [ 4.4570],
        [ 6.6029],
        [ 7.4260],
        [11.0141],
        [ 2.1572]])
# 初始化模型参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

# 定义模型(就是深度学习中的网络结构)
def linreg(X, w, b):
    """线性回归模型"""
    return torch.matmul(X, w) + b

# 损失函数
# 均方误差
# 因为输出只有一个维度,所以在这里进行了简化,没有计算均值
def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape))**2 / 2

# 优化器——用于更新模型参数
def sgd(params, lr, batch_size):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()
lr = .03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)
        l.sum().backward()
        sgd([w, b], lr, batch_size)
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch+1}, loss {float(train_l.mean()):f}')
epoch 1, loss 0.043805
epoch 2, loss 0.000168
epoch 3, loss 0.000049

可以看到损失逐渐减小,此时得到的模型参数为:

print(w, b)
tensor([[ 2.0000],
        [-3.3998]], requires_grad=True) tensor([4.1994], requires_grad=True)

2.2 框架版

使用pyTorch框架内置的对象和函数,可以更加容易地实现上面的功能,在此我们主要使用以下函数与对象

  • 2.2.1 TensorDataset

TensorDataset定义为:

class torch.utils.data.TensorDataset(data_tensor, target_tensor)

用来对两个Tensor对象(即data_tensortarget_tensor)进行配对打包,两个对象第一个维度上的大小应该相同。

2.2.2 DataLoader

torch.utils.data.DataLoader

DataLoader为数据加载器,其作用为组合数据集与采样器,并在数据集上提供一个可迭代的对象。DataLoader类的定义为:

torch.utils.data.DataLoader(dataset, 
                   batch_size=1, 
                   shuffle=None, 
                   sampler=None, 
                   batch_sampler=None, 
                   num_workers=0, 
                   collate_fn=None, 
                   pin_memory=False, 
                   drop_last=False, 
                   timeout=0, 
                   worker_init_fn=None, 
                   multiprocessing_context=None, 
                   generator=None, *, 
                   prefetch_factor=None, 
                   persistent_workers=False, 
                   pin_memory_device='')

其中:

  • dataset:数据集
  • batch_size: 每个batch的大小
  • num_workers: 是否使用多进程读取数据
  • shuffle: 每个epoch中是否重新进行乱序操作
  • drop_last:当样本数不能被batch_size整除时,是否舍弃最后一批数据
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2

features, labels = d2l.synthetic_data(true_w, true_b, 1000)
def load_array(data_arrays, batch_size, is_train=True):
    """构造一个PyTorch的数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

next(iter(data_iter))
[tensor([[-1.3819,  0.3498],
         [ 1.2611,  1.3081],
         [-1.2086, -1.8001],
         [-1.3380,  0.4080],
         [-0.9351,  1.1630],
         [-0.3644, -0.4445],
         [-0.8413,  0.8786],
         [-1.3562, -0.9043],
         [-1.0410,  1.8153],
         [ 0.0348, -0.3503]]),
 tensor([[ 0.2431],
         [ 2.2674],
         [ 7.9073],
         [ 0.1253],
         [-1.6114],
         [ 4.9960],
         [-0.4723],
         [ 4.5547],
         [-4.0486],
         [ 5.4708]])]

2.2.3 Sequential和Linear

在PyTorch中Moduletorch.nn中最核心的数据结构,它可以是神经网络的某个层,也可以是包含多个层的神经网络。在实际使用中,最常见的做法就是继承torch.nn.Module,而Sequential就是继承自torch.nn.Module,表示一种简单的序贯模型。

其主要有三种常见的实现形式:

实现方式 1

model = nn.Sequential(
    nn.Conv2d(1,20,5),
    nn.ReLU(),
    nn.Conv2d(20,64,5),
    nn.ReLU())

这种实现方式比较简单,但是也有明显的缺点,即每一层没有自己的名字

实现方式 2


from collections import OrderedDict
model = nn.Sequential(OrderedDict([
                  ('conv1', nn.Conv2d(1,20,5)),
                  ('relu1', nn.ReLU()),
                  ('conv2', nn.Conv2d(20,64,5)),
                  ('relu2', nn.ReLU())
                ]))

这种实现方式会为每一层添加一个名字,但是在使用时仍然无法通过名称进行索引

实现方式 3

model = nn.Sequential()
model.add_module("conv1",nn.Conv2d(1,20,5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20,64,5))
model.add_module('relu2', nn.ReLU())

torch.nn.Linear

Linear是一个线性变换,即y=xA^T+b,也可以理解为一个全连接模型,其定义为:

torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
  • in_features 输入的大小
  • out_features 输出的大小
  • bias 是否使用了偏置,默认为启用
from torch import nn

# 构建模型
net = nn.Sequential(nn.Linear(2, 1))
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

# 计算误差
loss = nn.MSELoss()

# SGD
trainer = torch.optim.SGD(net.parameters(), lr=.03)
num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l =  loss(net(X),y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')
epoch 1, loss 0.000206
epoch 2, loss 0.000100
epoch 3, loss 0.000100

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容