1、线性模型
线性模型被看做单层神经网络。
损失函数能够量化目标的实际值与预测值之间的差距。
对于没有解析解的情况,梯度下降通过不断地在损失函数递减的方向上更新参数来降低误差。计算损失函数关于模型参数的导数(梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降,批量大小为b。
def synthetic_data(w, b, num_examples): #@save """生成 y = Xw + b + 噪声。""" 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)
打乱数据集中的样本并以小批量方式获取数据。我们定义一个data_iter
函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size
的小批量。每个小批量包含一组特征和标签。
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]
从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) b = torch.zeros(1, requires_grad=True)
def linreg(X, w, b): #@save """线性回归模型。""" return torch.matmul(X, w) + b
这里我们使用平方损失函数。 在实现中,我们需要将真实值y
的形状转换为和预测值y_hat
的形状相同。
def squared_loss(y_hat, y): #@save """均方损失。""" return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr
决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size
)来归一化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params, lr, batch_size): #@save """小批量随机梯度下降。""" with torch.no_grad(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_()
迭代周期个数num_epochs
和学习率lr
都是超参数,置超参数很棘手,需要通过反复试验进行调整。
lr = 0.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) # `X`和`y`的小批量损失 # 因为`l`形状是(`batch_size`, 1),而不是一个标量。`l`中的所有元素被加到一起, # 并以此计算关于[`w`, `b`]的梯度 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}')
使用深度学习框架来简洁地实现(二)中的线性回归模型。
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)
我们可以调用框架中现有的API来读取数据。我们将features
和labels
作为API的参数传递,并在实例化数据迭代器对象时指定batch_size
。此外,布尔值is_train
表示是否希望数据迭代器对象在每个迭代周期内打乱数据。
def load_array(data_arrays, batch_size, is_train=True): #@save """构造一个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)
在PyTorch中,全连接层在Linear
类中定义。值得注意的是,我们将两个参数传递到nn.Linear
中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
# `nn` 是神经网络的缩写 from torch import nn net = nn.Sequential(nn.Linear(2, 1))
正如我们在构造nn.Linear
时指定输入和输出尺寸一样。现在我们直接访问参数以设定初始值。我们通过net[0]
选择网络中的第一个图层,然后使用weight.data
和bias.data
方法访问参数。然后使用替换方法normal_
和fill_
来重写参数值。在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。
net[0].weight.data.normal_(0, 0.01) net[0].bias.data.fill_(0)
计算均方误差使用的是MSELoss
类,也称为平方L2范数。默认情况下,它返回所有样本损失的平均值。
loss = nn.MSELoss()
小批量随机梯度下降算法是一种优化神经网络的标准工具,PyTorch在optim
模块中实现了该算法的许多变种。当我们实例化SGD
实例时,我们要指定优化的参数(可通过net.parameters()
从我们的模型中获得)以及优化算法所需的超参数字典。小批量随机梯度下降只需要设置lr
值,这里设置为0.03。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
在每个迭代周期里,我们将完整遍历一次数据集(train_data
),不停地从中获取一个小批量的输入和相应的标签。对于每一个小批量,我们会进行以下步骤:
通过调用net(X)
生成预测并计算损失l
(正向传播)。
通过进行反向传播来计算梯度。
通过调用优化器来更新模型参数。
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}')
常用损失函数:
%matplotlib inline import torch import torchvision from torch.utils import data from torchvision import transforms from d2l import torch as d2l d2l.use_svg_display()
可以通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中。
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式 # 并除以255使得所有像素的数值均在0到1之间 trans = transforms.ToTensor() mnist_train = torchvision.datasets.FashionMNIST( root="../data", train=True, transform=trans, download=True) mnist_test = torchvision.datasets.FashionMNIST( root="../data", train=False, transform=trans, download=True)
Fashion-MNIST中包含10个类别,每个类别包括6000张训练数据和1000张测试数据。以下函数用于在数字标签索引及其文本名称之间进行转换。
def get_fashion_mnist_labels(labels): #@save """返回Fashion-MNIST数据集的文本标签。""" text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [text_labels[int(i)] for i in labels]
使用内置的数据迭代器,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为batch_size
。我们在训练数据迭代器中还随机打乱了所有样本。
batch_size = 256 def get_dataloader_workers(): #@save """使用4个进程来读取数据。""" return 4 train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers())
看一下读取训练数据所需的时间。
timer = d2l.Timer() for X, y in train_iter: continue f'{timer.stop():.2f} sec'
我们定义load_data_fashion_mnist
函数,用于获取和读取Fashion-MNIST数据集。它返回训练集和验证集的数据迭代器。此外,它还接受一个可选参数resize,用来将图像大小调整为另一种形状。
def load_data_fashion_mnist(batch_size, resize=None): #@save """下载Fashion-MNIST数据集,然后将其加载到内存中。""" trans = [transforms.ToTensor()] if resize: trans.insert(0, transforms.Resize(resize)) trans = transforms.Compose(trans) mnist_train = torchvision.datasets.FashionMNIST( root="../data", train=True, transform=trans, download=True) mnist_test = torchvision.datasets.FashionMNIST( root="../data", train=False, transform=trans, download=True) return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()), data.DataLoader(mnist_test, batch_size, shuffle=False, num_workers=get_dataloader_workers()))
通过指定resize
参数来测试load_data_fashion_mnist
函数的图像大小调整功能。
train_iter, test_iter = load_data_fashion_mnist(32, resize=64) for X, y in train_iter: print(X.shape, X.dtype, y.shape, y.dtype) break
torch.Size([32, 1, 64, 64]) torch.float32 torch.Size([32]) torch.int64
import torch from IPython import display from d2l import torch as d2l batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
原始数据集中的每个样本都是28×2828×28的图像。在本节中,我们将展平每个图像,把它们看作长度为784的向量。在softmax回归中,我们的输出与类别一样多。因为我们的数据集有10个类别,所以网络输出维度为10。与线性回归一样,我们将使用正态分布初始化我们的权重W
,偏置初始化为0。
num_inputs = 784 num_outputs = 10 W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True) b = torch.zeros(num_outputs, requires_grad=True)
softmax由三个步骤组成: (1)对每个项求幂(使用exp
); (2)对每一行求和(小批量中每个样本是一行),得到每个样本的归一化常数; (3)将每一行除以其归一化常数,确保结果的和为1。
def softmax(X): X_exp = torch.exp(X) partition = X_exp.sum(1, keepdim=True) return X_exp / partition # 这里应用了广播机制
下面的代码定义了输入如何通过网络映射到输出。注意,在将数据传递到我们的模型之前,我们使用reshape
函数将每张原始图像展平为向量。
def net(X): return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
num_inputs, num_outputs, num_hiddens = 784, 10, 256 W1 = nn.Parameter(torch.randn( num_inputs, num_hiddens, requires_grad=True) * 0.01) b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True)) W2 = nn.Parameter(torch.randn( num_hiddens, num_outputs, requires_grad=True) * 0.01) b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True)) params = [W1, b1, W2, b2]
def relu(X): a = torch.zeros_like(X) return torch.max(X, a)
我们使用reshape
将每个二维图像转换为一个长度为num_inputs
的向量。
def net(X): X = X.reshape((-1, num_inputs)) H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法 return (H@W2 + b2)
直接使用高级API中的内置函数来计算softmax和交叉熵损失。
loss = nn.CrossEntropyLoss()
多层感知机的训练过程与softmax回归的训练过程完全相同。可以直接调用d2l
包的train_ch3
函数。
num_epochs, lr = 10, 0.1 updater = torch.optim.SGD(params, lr=lr) d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
import torch from torch import nn from d2l import torch as d2l
与softmax回归的简洁实现相比,唯一的区别是我们添加了2个全连接层(之前我们只添加了1个全连接层)。第一层是隐藏层,它包含256个隐藏单元,并使用了ReLU激活函数。第二层是输出层。
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10)) def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights);
batch_size, lr, num_epochs = 256, 0.1, 10 loss = nn.CrossEntropyLoss() trainer = torch.optim.SGD(net.parameters(), lr=lr) train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
训练误差:模型在训练数据集上计算得到的误差。
泛化误差:模型在新数据集上计算得到的误差。
验证数据集:一个用来评估模型好坏的数据集。
测试数据集:只用一次的数据集。
k折交叉验证法:
过拟合:
欠拟合: