自动求导尝试

torch.Tensor 是这个包的核心类。如果设置它的属性 .requires_grad 为 True,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性.

​ 要阻止一个张量被跟踪历史,可以调用 .detach() 方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。

​ 为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad(): 中。在评估模型时特别有用,因为模型可能具有 requires_grad = True 的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。

​ 还有一个类对于autograd的实现非常重要:Function。

Tensor 和 Function 互相连接生成了一个无圈图(acyclic graph),它编码了完整的计算历史。每个张量都有一个 .grad_fn 属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的 grad_fn 是 None )。

如果需要计算导数,可以在 Tensor 上调用 .backward()。如果 Tensor 是一个标量(即它包含一个元素的数据),则不需要为 backward() 指定任何参数,但是如果它有更多的元素,则需要指定一个 gradient 参数,该参数是形状匹配的张量。

1
2
import torch

1
2
3
4
5
6
x = torch.ones(3, 2, requires_grad=True)
print(x)

y = x+2
print(y)

tensor([[1., 1.],
        [1., 1.],
        [1., 1.]], requires_grad=True)
tensor([[3., 3.],
        [3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
x = torch.randn((2, 3), requires_grad=True)
print(x)
y = x+2
print(y)

# x.grad.zero_()
y = y.sum() # 必须将y最终结果化为标量才能进行求导操作
y.backward()
print(x.grad)


while y.data.norm() < 1000:
y = y*2
print(y)

tensor([[-1.5443, -0.0354, -0.8403],
        [ 1.5566,  0.7182,  1.5884]], requires_grad=True)
tensor([[0.4557, 1.9646, 1.1597],
        [3.5566, 2.7182, 3.5884]], grad_fn=<AddBackward0>)
tensor([[1., 1., 1.],
        [1., 1., 1.]])
tensor(1720.7292, grad_fn=<MulBackward0>)

线性回归理解

给定一个数据集,我们的目标是寻找模型的权重和偏置, 使得根据模型做出的预测大体符合数据里的真实价格。 输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

在这里插入图片描述

损失函数

在这里插入图片描述

线性回归的从零开始实现

其中d2l包需要自己导入离线安装,附上链接:感谢SWY大神

顺便补一个d2l_zh的包:再次感谢SWY大神

1
2
3
4
5
from d2l import torch as d2l  # 李沐大神自己写的包,需要自己导入
import torch
import random
%matplotlib

Using matplotlib backend: QtAgg

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def synthetic_data(w, b, num_examples):
# "生成 y=Xw+b+e" e是噪音
# 均值为0,标准差为1的随机数,行数等于样本数,列数等于w的长度的随机数
X = torch.normal(0, 1, (num_examples, len(w)))
# y=Xw+b
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)

1
2
3
print('features', features[0])
print('labels', labels[0])

features tensor([-0.3295, -0.7524])
labels tensor([6.0904])

读取小批量

1
2
3
4
5
6
7
8
9
10
11
def data_iter(batch_size, features, labels):  # batch_size 批量大小
num_examples = len(features)
indices = list(range(num_examples))
# 随机读取样本
random.shuffle(indices) # 随机打乱样本
for i in range(0, num_examples, batch_size): # 从0开始,到样本总数数量结束,每次跳batch_size大小
batch_indices = torch.tensor(
indices[i:min(i+batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]


我们直观感受一下小批量运算:读取第一个小批量数据样本并打印。 每个批量的特征维度显示批量大小和输入特征数。 同样的,批量的标签形状与batch_size相等。

1
2
3
4
5
6
batch_size = 10

for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break

tensor([[ 0.7996,  0.4114],
        [ 0.0760,  1.0155],
        [ 0.6860,  0.2270],
        [-2.0028,  0.9113],
        [ 0.4199, -0.6192],
        [-0.0252,  2.0244],
        [ 1.7162, -0.4195],
        [ 0.6579, -0.3955],
        [-1.5817, -0.1407],
        [-1.0975,  0.1836]]) 
 tensor([[ 4.4066],
        [ 0.8990],
        [ 4.8128],
        [-2.8907],
        [ 7.1436],
        [-2.7258],
        [ 9.0706],
        [ 6.8511],
        [ 1.5068],
        [ 1.3921]])

初始化模型参数

1
2
w=torch.normal(0,0.01,size=(2,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)

定义模型

1
2
3
def linreg(X, w, b):
return torch.matmul(X, w)+b

定义损失函数
在这里插入图片描述

1
2
3
def squared_loss(y_hat, y):
# 均方损失
return (y_hat-y.reshape(y_hat.shape))**2/2

定义优化算法

1
2
3
4
5
6
def sgd(params, lr, batch_size):
# 小批量的随机梯度下降
with torch.no_grad(): # para参数列表,lr是学习率,batch_size
for param in params:
param -= lr * param.grad/batch_size
param.grad.zero_()

开始训练

另外附上python特有的print(f’’)用法:传送门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lr = 0.03  # 学习率是0.03(太小效率太低,太大容易超出范围,造成摆动)
num_epochs = 3 # 训练次数是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) # x和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.038161
epoch 2, loss 0.000139
epoch 3, loss 0.000048

输出通过学习修正过的参数值,评估训练成功程度

1
2
3
4
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
print(w)
print(b)
w的估计误差: tensor([ 0.0002, -0.0004])
b的估计误差: tensor([-0.0006])
tensor([[ 1.9998, -3.3996]])
tensor([4.2006])

线性回归深度学习框架的简洁实现

使用pytorch的nn来实现加载数据

1
2
3
4
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

创建初始w,b。
并且生成标签数据集

1
2
3
true_w =torch.tensor([2,-3.4])
true_b =4.2
features,labels=d2l.synthetic_data(true_w,true_b,1000)

构造pytorch迭代器

TensorDataset

DataLoader

next

1
2
3
4
5
6
7
8
9
10
11
12
def load_array(data_arrrays, batch_size, is_train=True):
# 构建迭代器
dataset = data.TensorDataset(*data_arrrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)


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

next(iter(data_iter))
# 这里我们使用iter构造Python迭代器,并使用next从迭代器中获取第一项。

[tensor([[ 0.1652,  0.8560],
         [ 0.0147,  1.5673],
         [ 0.9652, -0.2222],
         [ 0.4242,  0.1483],
         [ 0.8722, -1.3510],
         [ 0.0506,  0.8717],
         [-1.2767, -0.0087],
         [-0.4736,  0.8434],
         [-0.1852, -0.9320],
         [-1.6329,  0.1184]]),
 tensor([[ 1.6253],
         [-1.0980],
         [ 6.8757],
         [ 4.5338],
         [10.5444],
         [ 1.3342],
         [ 1.6711],
         [ 0.3882],
         [ 7.0090],
         [ 0.5239]])]

使用深度学习框架定好的层

1
2
3
4
# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1)) # 2是输入维度,1是输出维度

初始化模型参数

1
2
net[0].weight.data.normal_(0,0.01) #使用正太分布替换它的值,均值0,方差0.01
net[0].bias.data.fill_(0) #偏差

计算均方误差(平方范数)

1
loss=nn.MSELoss()

实例化SGD实例(优化算法)

1
2
3
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
#第一个参数将w,b传入
#第二个参数设置的学习率

开始训练

回顾一下:在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。

  • 通过进行反向传播来计算梯度。

  • 通过调用优化器来更新模型参数。

为了更好的衡量训练效果,我们计算每个迭代周期后的损失,并打印它来监控训练过程。

1
2
3
4
5
6
7
8
9
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() #已经做过了sum
trainer.step() #模型更新
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
epoch 1, loss 0.000246
epoch 2, loss 0.000098
epoch 3, loss 0.000098

输出并评估训练结果

下面我们比较生成数据集的真实参数和通过有限数据训练获得的模型参数。 要访问参数,我们首先从net访问所需的层,然后读取该层的权重和偏置。 正如在从零开始实现中一样,我们估计得到的参数与生成数据的真实参数非常接近

1
2
3
4
5
6
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
print(w)
print(b)
w的估计误差: tensor([ 0.0002, -0.0004])
b的估计误差: tensor([-0.0006])
tensor([[ 1.9998, -3.3996]])
tensor([4.2006])