#深度学习# #人工智能# #pytorch# #python#
4.1 多层感知机(MLP)
最简单的深度网络称为多层感知机。多层感知机由多层神经元组成,每一层与它的上一层相连,从中接收输入;同时每一层也与它的下一层相连,影响当前层的神经元。超参数是隐层层数和各个隐藏层大小。使用softmax来处理多类分类。
#start补充感知机模型(只能做二分类问题,无法无法学习 XOR 问题,只能产生线性分割面):
#end结束
多层感知机:单隐藏层-单分类
4.1.2 激活函数
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。
S型(sigmoid)激活函数 :sigmoid通常称为挤压函数(squashing function):它将范围(‐inf, inf)中的任意输入压缩到区间(0, 1)。
双曲正切(tanh)激活函数:与sigmoid函数类似,tanh(双曲正切)函数也能将其输入压缩转换到区间(‐1, 1)上。
线性(ReLU)修正函数:是修正线性单元(Rectified linear unit,ReLU),实现简单,同时在各种预测任务中表现良好。ReLU提供了一种非常简单的非线性变换。给定元素x,ReLU函数被定义为该元素与0的最大值。通俗地说,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。
多层感知机:单隐藏层-多分类
4.2 多层感知机的从零开始实现
使用Fashion‐MNIST图像分类数据集,导入相关工具包和数据集。
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
4.2.1 初始化模型参数
Fashion‐MNIST中的每个图像由 28 × 28 = 784个灰度像素值组成。所有图像共分为10个类别。我
们将实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元。
num_inputs, num_outputs, num_hiddens = 784, 10, 256#前面两个由数据集决定,后面的隐藏层自定义,选的784-10之间的数
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)#nn.Parameter做声明是pytorch的Parameter,可不加。行数为num_inputs,列数为 num_hiddens,requires_grad=True计算梯度
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]
4.2.2 激活函数
直接实现ReLU激活函数,而不是直接调用内置的relu函数。
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
4.2.3 模型
因为我们忽略了空间结构,所以我们使用reshape将每个28x28二维图像转换为一个长度为num_inputs的矩阵。
def net(X):
X = X.reshape((-1, num_inputs))#把x拉成一个二维矩阵,行数自动计算,列数 num_inputs
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
4.2.4 损失函数
loss = nn.CrossEntropyLoss(reduction='none')
4.2.5 训练
多层感知机的训练过程与softmax回归的训练过程完全相同。直接调用d2l包的train_ch3函
数,将迭代周期数设置为10,并将学习率设置为0.1。
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)
输出:
验证测试:
d2l.predict_ch3(net, test_iter)
输出:
4.3 多层感知机的简洁实现
通过高级API更简洁地实现多层感知机。
#导入工具包
import torch
from torch import nn
from d2l import torch as d2l
#构建网络:第一层是隐藏层,它包含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)
结果输出:
4.4 模型选择、欠拟合和过拟合
将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting),用于对抗过拟合的技术称为正则化(regularization)。
4.4.1 训练误差和泛化误差
训练误差(training error)是指,模型在训练数据集上计算得到的误差。
泛化误差(generalization error)是指,模型应用在同样从原始样本的分布中抽取的无限多数据样本时,模型误差的期望。(即模型在新数据上的误差)。
影响模型泛化的因素。
1. 可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
2. 参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
3. 训练样本的数量。即使模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。
4.4.2 模型选择
通常在评估几个候选模型后选择最终的模型。这个过程叫做模型选择。
针对数据集:常见做法是将数据分成三份,训练、验证、测试数据集。测试数据集最终只使用一次。但现实是验证数据和测试数据之间的边界模糊得令人担忧,实际上常常使用的只有训练数据和验证数据的数据集,并没有真正的测试数据集。
K折交叉验证:当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。解决方案是采用K折交叉验证。这里,原始训练数据被分成K个不重叠的子集。然后执行K次模型训练和验证,每次在K − 1个子集上进行训练,并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。最后,通过对K次实验的结果取平均来估计训练和验证误差。
常见 K 值选择:5 - 10(实际可更小更大,根据实际数据来)。
训练数据集:训练模型参数,验证数据集:选择模型超参数,非大数据集上通常使用k-折交叉验证。
4.4.3 欠拟合还是过拟合?
数据复杂度:多个重要因素:1.样本个数。2.每个样本的元素个数。3.时间、空间结构。4.多样性。
模型复杂度(模型容量):即“可适应不同数据分布”的能力。低容量模型难以适应训练集,导致欠拟合;高容量模型可以记忆训练集,导致过拟合。
4.4.4 多项式回归
通过多项式拟合来探索这些概念。导入相关工具包
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
使用以下三阶多项式来生成训练和测试数据的标签:
max_degree = 20 # 多项式的最大阶数
n_train, n_test = 100, 100 # 训练和验证数据集大小
true_w = np.zeros(max_degree) # 分配大量的空间
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])
features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
for i in range(max_degree):
poly_features[:, i] /= math.gamma(i + 1) # gamma(n)=(n-1)!
# labels的维度:(n_train+n_test,)
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)
从生成的数据集中查看一下前2个样本,第一个值是与偏置相对应的常量特征。
# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
torch.float32) for x in [true_w, features, poly_features, labels]]
features[:2], poly_features[:2, :], labels[:2]
输出:
实现一个函数来评估模型在给定数据集上的损失:
def evaluate_loss(net, data_iter, loss): #@save
"""评估给定数据集上模型的损失"""
metric = d2l.Accumulator(2) # 损失的总和,样本数量
for X, y in data_iter:
out = net(X)
y = y.reshape(out.shape)
l = loss(out, y)
metric.add(l.sum(), l.numel())
return metric[0] / metric[1]
定义一个训练函数:
def train(train_features, test_features, train_labels, test_labels,
num_epochs=400):
loss = nn.MSELoss(reduction='none')
input_shape = train_features.shape[-1]
# 不设置偏置,因为我们已经在多项式中实现了它
net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
batch_size = min(10, train_labels.shape[0])
train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)),
batch_size)
test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)),
batch_size, is_train=False)
trainer = torch.optim.SGD(net.parameters(), lr=0.01)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
xlim=[1, num_epochs], ylim=[1e-3, 1e2],
legend=['train', 'test'])
for epoch in range(num_epochs):
d2l.train_epoch_ch3(net, train_iter, loss, trainer)
if epoch == 0 or (epoch + 1) % 20 == 0:
animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss),
evaluate_loss(net, test_iter, loss)))
print('weight:', net[0].weight.data.numpy())
线性函数拟合(正常):
#进行训练
# 从多项式特征中选择前4个维度,即1,x,x^2/2!,x^3/3!
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
labels[:n_train], labels[n_train:])
输出:
线性函数拟合(欠拟合):
# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
labels[:n_train], labels[n_train:])
输出:
线性函数拟合(过拟合):
# 从多项式特征中选取所有维度,且训练1500轮
train(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:], num_epochs=1500)
输出: