一、线性神经网络
(一)线性回归
1、线性模型
线性模型被看做单层神经网络。
2、损失函数
损失函数能够量化目标的实际值与预测值之间的差距。
3、解析解
4、优化方法:小批量梯度下降算法
对于没有解析解的情况,梯度下降通过不断地在损失函数递减的方向上更新参数来降低误差。计算损失函数关于模型参数的导数(梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降,批量大小为b。
(二)线性回归的实现
1、生成数据集
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)
2、读取数据集
打乱数据集中的样本并以小批量方式获取数据。我们定义一个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]
3、初始化模型参数
从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
4、定义模型
def linreg(X, w, b): #@save
"""线性回归模型。"""
return torch.matmul(X, w) + b
5、定义损失函数
这里我们使用平方损失函数。 在实现中,我们需要将真实值y
的形状转换为和预测值y_hat
的形状相同。
def squared_loss(y_hat, y): #@save
"""均方损失。"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
6、定义优化算法
该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率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_()
7、训练
迭代周期个数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}')
(三)线性回归的简洁实现
使用深度学习框架来简洁地实现(二)中的线性回归模型。
1、生成数据集
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)
2、读取数据集
我们可以调用框架中现有的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)
3、定义模型
在PyTorch中,全连接层在Linear
类中定义。值得注意的是,我们将两个参数传递到nn.Linear
中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
# `nn` 是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
4、初始化模型参数
正如我们在构造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)
5、定义损失函数
计算均方误差使用的是MSELoss
类,也称为平方L2范数。默认情况下,它返回所有样本损失的平均值。
loss = nn.MSELoss()
6、定义优化算法
小批量随机梯度下降算法是一种优化神经网络的标准工具,PyTorch在optim
模块中实现了该算法的许多变种。当我们实例化SGD
实例时,我们要指定优化的参数(可通过net.parameters()
从我们的模型中获得)以及优化算法所需的超参数字典。小批量随机梯度下降只需要设置lr
值,这里设置为0.03。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
7、训练
在每个迭代周期里,我们将完整遍历一次数据集(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}')
(四)Softmax回归
常用损失函数:
(1)L2 Loss (2)L1 Loss (3)Huber's Robust Loss
(五)图片分类数据集
%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()
1、读取数据集
可以通过框架中的内置函数将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]
2、读取小批量
使用内置的数据迭代器,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为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'
3、整合所有组件
我们定义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
(六)Softmax回归的实现
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)
1、初始化模型参数
原始数据集中的每个样本都是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)
2、定义softmax操作
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 # 这里应用了广播机制
3、定义模型
下面的代码定义了输入如何通过网络映射到输出。注意,在将数据传递到我们的模型之前,我们使用reshape
函数将每张原始图像展平为向量。
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
4、定义损失函数
5、分类准确率
6、训练
7、预测
(七)Softmax回归的简洁实现
二、多层感知机
(一)多层感知机
(二)多层感知机的实现
1、初始化模型参数
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]
2、激活函数
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
3、模型
我们使用reshape
将每个二维图像转换为一个长度为num_inputs
的向量。
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
4、损失函数
直接使用高级API中的内置函数来计算softmax和交叉熵损失。
loss = nn.CrossEntropyLoss()
5、训练
多层感知机的训练过程与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折交叉验证法:
过拟合:
欠拟合:
(五)权重衰减
(六)Dropout