这是一个需要仔细考虑的点。
损失函数(Lost Function) 。这里的1/2,是为了求导后可以消去平方带来的系数。
沿梯度下降的方向增加损失函数的值。
学习率(learning rate):步长的超参数(就是你需要自己设定的值)
我这篇博客已经介绍了。
机器学习笔记01_机器学习基本概念(上)
然而,为了计算一次梯度要遍历所有数据,实在太不明智了。
① 梯度下降通过不断沿着梯度下降的方向更新参数求解
② 小批量随机梯度下降是深度学习默认的求解算法
③ 两个重要的超参数是批量大小和超参数
我们将根据带有噪声的线性模型构造一个人造数据集。我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。
我们生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征。
import random
import torch
import matplotlib.pyplot as plt
def synthetic_data(w, b, num_examples):
"""生产y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w))) # 均值为0,方差为1的随机数
y = torch.matmul(X, w) + b # matmul 矩阵相乘
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)
torch.normal(means, std, out=None)
means (Tensor,optional) – 所有分布均值
std (Tensor) – 每个元素的标准差
out (Tensor) – 可选的输出张量(指定shape)
torch.matmul(a, b)
如果是二维的矩阵相乘,那就跟平时咱们做的矩阵乘法一样;
要求第一维度相同,后两个维度能满足矩阵相乘条件。A.shape =(b,m,n);B.shape = (b,n,k)
numpy.matmul(A,B) 结果shape为(b,m,k)
可视化:
fig = plt.figure(figsize=(5, 4)) # 建立一个大小为5*4的画板
plt.scatter(features[:, 1].detach().numpy(),
labels.detach().numpy(), s=5)
plt.show()
注意:要转化为numpy中的数组才能进行调用。
训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。 由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数,该函数能打乱数据集中的样本并以小批量方式获取数据。
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 样本为随机读取
random.shuffle(indices) # shuffle()方法将序列的所有元素随机排序
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
python中yield的用法详解——最简单,最清晰的解释
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# w = torch.zeros(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
在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。
线性回归有解析解。然而,这是一本关于深度学习的书,而不是一本关于线性回归的书。 由于这本书介绍的其他模型都没有解析解,下面我们将在这里介绍小批量随机梯度下降的工作示例。
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用**批量大小(batch_size)**来归一化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params, lr, batch_size):
"""小批量随机梯度下降。"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_() # 清零,防止影响下一次和梯度计算
当某一变量var在函数外面已经声明时 (如var=v0),函数内部默认var为全局变量且可以访问该变量,除非在函数内部有修改变量var的行为(如重新赋值 var=v1 或者代数运算 var=var+v1 等)。在这种修改变量的情况下,变量var会被定义为局部变量并被重新分配内存,它在函数内部的变化不会影响到外部的全局变量var的值(即var=v0保持不变)。
以上规则无论变量有没有被传入函数都适用,即 f(var…) 或者 f(…)。
特殊之处在于本节sgd中使用的运算符(-=)会执行原地操作(in-place operation),也就是运算结果会赋给同一块内存。由于params本身就是全局变量,修改后的结果仍然赋给它的内存,所以变化的也就是全局变量了。如修改为 param = param - … 结果就不对了,大家可以试一试。
类似的运算符还有 +=, *=, /= … 关于 in-place operation的讲解可以参考之前的“节省内存”。
理解这段代码至关重要,因为在整个深度学习的职业生涯中,你会一遍又一遍地看到几乎相同的训练过程。
①在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。
②计算完损失后,我们开始反向传播,存储每个参数的梯度。最后,我们调用优化算法SGD来更新模型参数。
lr = 0.03
num_epoch = 3
net = linreg
loss = squared_loss
for epoch in range(num_epoch):
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}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
在上面我们只依赖了:(1)通过张量来进行数据存储和线性代数;(2)通过自动微分来计算梯度。
实际上,由于数据迭代器、损失函数、优化器和神经网络层很常用,现代深度学习库也为我们实现了这些组件。
import torch
import matplotlib.pyplot as plt
from torch.utils import data
from d2l import torch as d2l
from torch import nn
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
fig = plt.figure(figsize=(5, 4)) # 建立一个大小为5*4的画板
plt.scatter(features[:, 1].detach().numpy(),
labels.detach().numpy(), s=5)
plt.show()
我们可以调用框架中现有的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)
for i, j in enumerate(data_iter): # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
x, y = j
print(f'batch:{i} x:{x} y:{y}')
A = next(iter(data_iter))
print(A)
TensorDataset
from torch.utils import data
data.TensorDataset(a,b)
该类通过每一个 tensor 的第一个维度进行索引。因此,该类中的 tensor 第一维度必须相等。
data.DataLoader(train_data, batch_size=1, shuffle=True)
import torch
from torch.utils import data
# a的形状为(4*3)
a = torch.tensor([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]])
# b的第一维与a相同
b = torch.tensor([1, 2, 3, 4])
train_data = data.TensorDataset(a, b)
print(train_data[0:4])
data = data.DataLoader(train_data, batch_size=1, shuffle=True)
for i, j in enumerate(data): # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
x, y = j
print(f'batch:{i} x:{x} y:{y}')
对于标准操作,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。
Sequential类为串联在一起的多个层定义了一个容器。当给定输入数据,Sequential实例将数据传入到第一层,然后将第一层的输出作为第二层的输
入,依此类推。
回顾之前的单层网络架构,这一单层被称为全连接层(fully-connected layer),因为它的每一个输入都通过矩阵-向量乘法连接到它的每个输出。
我们首先定义一个模型变量net,它是一个Sequential类的实例。
# `nn` 是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
在PyTorch中,全连接层在Linear类中定义。值得注意的是,我们将两个参数传递到nn.Linear中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
在使用net之前,我们需要初始化模型参数。 深度学习框架通常有预定义的方法来初始化参数。
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)
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}')
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
loss
import torch
import numpy as np
loss_fn = torch.nn.MSELoss(reduce=False, size_average=False) # 1、返回向量
loss_fn1 = torch.nn.MSELoss(reduce=True, size_average=True) # 2、返回平均值
a = np.array([[1, 2],
[3, 4]])
b = np.array([[3, 5],
[2.5, 6]])
input = torch.autograd.Variable(torch.from_numpy(a))
target = torch.autograd.Variable(torch.from_numpy(b))
loss = loss_fn(input.float(), target.float())
loss1 = loss_fn1(input.float(), target.float())
print(loss)
print(loss1)
5.1 分类Classification
Softmax 函数及其作用(含推导)
在这里要采取的主要方法是将模型的输出视作为概率。我们将优化参数以最大化观测数据的概率。为了得到预测结果,我们将设置一个阈值,如选择具有最大概率的标签。
模型的输出 y^j 可以视为属于类 j 的概率。
要将输出视为概率,我们必须保证在任何数据上的输出都是非负的且总和为1。
我们首先对每个未归一化的预测求幂,这样可以确保输出非负。为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。
尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。因此,softmax回归是一个线性模型。
W中的每一行代表一个特征,每一列代表一个类别。
对于 O 的每一行,我们先对所有项进行幂运算,然后通过求和对它们进行标准化。
XW+b 的求和会使用广播,小批量的未归一化预测O和输出概率Y^都是形状为n×q的矩阵。
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
import matplotlib.pyplot as plt
d2l.use_svg_display() # 以SVG格式保存
Fashion-MNIST由10个类别的图像组成,每个类别由训练数据集中的6000张图像和测试数据集中的1000张图像组成。测试数据集(test dataset)不会用于训练,只用于评估模型性能。训练集和测试集分别包含60000和10000张图像。
每个输入图像的高度和宽度均为28像素。数据集由灰度图像组成,其通道数为1。为了简洁起见,在这本书中,我们将高度 h 像素,宽度 w 像素图像的形状记为 h×w 或( h , w )。
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
# PIL.Image或者numpy.narray数据类型转变为torch.FloatTensor类型,shape是CHW,
# 数值范围缩小为[0.0, 1.0]。
mnist_train = torchvision.datasets.FashionMNIST(
root="P:/Project_Python/LIMU_dongshouxueshenduxuexi/classification",
train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="P:/Project_Python/LIMU_dongshouxueshenduxuexi/classification",
train=False, transform=trans, download=True)
print(len(mnist_train), len(mnist_test))
a = mnist_train[0][0].shape
print(a)
Fashion-MNIST中包含的10个类别分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。
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]
可以创建一个函数来可视化这些样本。
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): # @save
"""Plot a list of images."""
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
else:
# PIL图片
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
plt.show()
return axes
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y))
为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器,而不是从零开始创建一个。 回顾一下,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为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
print(f'{timer.stop():.2f} sec')
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)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans)
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()))
train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
print(f'X.shape:{X.shape} X.dtype:{X.dtype}''\n'
f'y.shape:{y.shape} y.dtype:{y.dtype}')
break
show_images(X.reshape(32, 64, 64), 4, 8, titles=get_fashion_mnist_labels(y))
timer = d2l.Timer()
for X, y in train_iter:
continue
print(f'{timer.stop():.2f} sec')
原始数据集中的每个样本都是 28×28 的图像。权重将构成一个 784×10 的矩阵,偏置将构成一个 1×10 的行向量。
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
X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
print(X_prob, X_prob.sum(1))
X(m,n)行是样本,列是特征。
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
使用reshape函数将每张原始图像展平为向量。
一文搞懂交叉熵在机器学习中的使用
y = torch.tensor([2, 1]) # 为序号
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
#####
tensor([0.6000, 0.2000]) #第0组中的第2元素, 第一组中的第1个元素。从0开始
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
c = cross_entropy(y_hat, y)
print(c)
def accuracy(y_hat, y): #@save
"""计算预测正确的数量。"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
print(accuracy(y_hat, y) / len(y))
给定预测概率分布y_hat,当我们必须输出硬预测(hard prediction)时,我们通常选择预测概率最高的类。
def evaluate_accuracy(net, data_iter): # @save
"""计算在指定数据集上模型的精度。"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1] # 分类正确的样本数和总样本数
这里Accumulator是一个实用程序类,用于对多个变量进行累加。
我们在Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量。当我们遍历数据集时,两者都将随着时间的推移而累加。
class Accumulator: # @save
"""在`n`个变量上累加。"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
getitem: __ getitem __(self,key):
首先,我们定义一个函数来训练一个迭代周期。请注意,updater是更新模型参数的常用函数,它接受批量大小作为参数。
def train_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期。"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.backward()
updater.step() # 对参数进行更新
metric.add(float(l) * len(y), accuracy(y_hat, y),
y.size().numel())
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练准确率
return metric[0] / metric[2], metric[1] / metric[2]
isinstance:
a = 1
print(isinstance(a,int))
print(isinstance(a,float))
返回 True False
(了解一下)
class Animator:
"""在动画中绘制数据。"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
它会在train_iter访问到的训练数据集上训练一个模型net。该训练函数将会运行多个迭代周期(由num_epochs指定)。
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
plt.show()
现在训练已经完成,我们的模型已经准备好对图像进行分类预测。给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)。
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
predict_ch3(net, test_iter)
plt.show()
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
Pytorch学习之旅(8)——nn.Sequential()容器
nn.Sequential()是nn.module()的容器,用于按顺序包装一组网络层。
顺序性:各个网络层之间严格按照顺序构造。 自带forward():自带的forward中,通过for循环依次执行前向传播运算。
我们计算了模型的输出,然后将此输出送入交叉熵损失。从数学上讲,这是一件完全合理的事情。然而,从计算角度来看,指数可能会造成数值稳定性问题。
oj 是未归一化的预测 o 的第 j 个元素。如果 ok 中的一些数值非常大,那么 exp(ok) 可能大于数据类型容许的最大数字(即上溢(overflow))
这将使分母或分子变为inf(无穷大),我们最后遇到的是0、inf或nan(不是数字)的 y^j 。
解决这个问题的一个技巧是,在继续softmax计算之前,先从所有 ok 中减去 max(ok) 。
在减法和归一化步骤之后,可能有些 oj 具有较大的负值。由于精度受限, exp(oj) 将有接近零的值,即下溢(underflow)。
这些值可能会四舍五入为零,使 y^j 为零,并且使得 log(y^j) 的值为-inf。
尽管我们要计算指数函数,但我们最终在计算交叉熵损失时会取它们的对数。 通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。
loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
plt.show()