Machine-Learning-Mastery-PyTorch-教程-四-

45 阅读1小时+

Machine Learning Mastery PyTorch 教程(四)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

在 PyTorch 中实现梯度下降

原文:machinelearningmastery.com/implementing-gradient-descent-in-pytorch/

梯度下降算法是训练深度神经网络的最流行技术之一。它在计算机视觉、语音识别和自然语言处理等领域有许多应用。尽管梯度下降的思想已经存在了几十年,但它最近才被应用于与深度学习相关的应用中。

梯度下降是一种迭代优化方法,通过在每一步中迭代更新值来寻找目标函数的最小值。每次迭代时,它都会朝着期望的方向迈出小步,直到收敛或满足停止准则。

在本教程中,你将训练一个具有两个可训练参数的简单线性回归模型,并探索梯度下降的工作原理以及如何在 PyTorch 中实现它。特别地,你将学习:

  • 梯度下降算法及其在 PyTorch 中的实现

  • 批量梯度下降及其在 PyTorch 中的实现

  • 随机梯度下降及其在 PyTorch 中的实现

  • 批量梯度下降(Batch Gradient Descent)和随机梯度下降(Stochastic Gradient Descent)之间的区别

  • 批量梯度下降和随机梯度下降在训练过程中损失如何下降

通过我的书 《用 PyTorch 进行深度学习》 启动你的项目。它提供了自学教程可运行的代码

那么,让我们开始吧!

在 PyTorch 中实现梯度下降。

图片由 Michael Behrens 提供。保留部分权利。

概述

本教程分为四部分;它们是

  • 准备数据

  • 批量梯度下降

  • 随机梯度下降

  • 绘制比较图

准备数据

为了简化模型以便说明,我们将使用与上一个教程相同的线性回归问题。数据是合成的,生成方式如下:

import torch
import numpy as np
import matplotlib.pyplot as plt

# Creating a function f(X) with a slope of -5
X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X

# Adding Gaussian noise to the function f(X) and saving it in Y
Y = func + 0.4 * torch.randn(X.size())

与之前的教程相同,我们初始化了一个变量 X,其值范围从 5-555,并创建了一个斜率为 5-5 的线性函数。然后,添加高斯噪声来生成变量 Y

我们可以使用 matplotlib 绘制数据以可视化模式:

...
# Plot and visualizing the data points in blue
plt.plot(X.numpy(), Y.numpy(), 'b+', label='Y')
plt.plot(X.numpy(), func.numpy(), 'r', label='func')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid('True', color='y')
plt.show()

回归模型的数据点

想开始使用 PyTorch 进行深度学习吗?

立即获取我的免费电子邮件速成课程(附样例代码)。

点击注册,并获得课程的免费 PDF 电子书版本。

批量梯度下降

现在我们已经为模型创建了数据,接下来我们将基于一个简单的线性回归方程构建前向函数。我们将训练模型以优化两个参数(wwbb)。我们还需要一个损失标准函数。由于这是一个对连续值的回归问题,MSE 损失是合适的。

...
# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

# evaluating data points with Mean Square Error (MSE)
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

在我们训练模型之前,让我们先了解一下批量梯度下降。在批量梯度下降中,所有训练数据中的样本都在单步中考虑。参数通过所有训练样本的平均梯度来更新。换句话说,每个 epoch 中只有一步梯度下降。

虽然批量梯度下降在平滑误差流形上是最佳选择,但它相对较慢且计算复杂,特别是当你有更大的训练数据集时。

使用批量梯度下降进行训练

让我们随机初始化可训练参数wwbb,定义一些训练参数如学习率或步长,一个空列表来存储损失,以及训练的 epoch 数量。

w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)

step_size = 0.1
loss_BGD = []
n_iter = 20

我们将使用下面的代码行训练我们的模型 20 个 epochs。在这里,forward()函数生成预测,而criterion()函数测量损失以存储在loss变量中。backward()方法执行梯度计算,更新后的参数存储在w.datab.data中。

for i in range (n_iter):
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_BGD.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    b.data = b.data - step_size * b.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    b.grad.data.zero_()
    # priting the values for understanding
    print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

下面是输出的样子,以及在应用批量梯度下降时每个 epoch 后更新的参数。

0, 	596.7191162109375, 	-1.8527469635009766, 	-16.062074661254883
1, 	343.426513671875, 	-7.247585773468018, 	-12.83026123046875
2, 	202.7098388671875, 	-3.616910219192505, 	-10.298759460449219
3, 	122.16651153564453, 	-6.0132551193237305, 	-8.237251281738281
4, 	74.85094451904297, 	-4.394278526306152, 	-6.6120076179504395
5, 	46.450958251953125, 	-5.457883358001709, 	-5.295622825622559
6, 	29.111614227294922, 	-4.735295295715332, 	-4.2531514167785645
7, 	18.386211395263672, 	-5.206836700439453, 	-3.4119482040405273
8, 	11.687058448791504, 	-4.883906364440918, 	-2.7437009811401367
9, 	7.4728569984436035, 	-5.092618465423584, 	-2.205873966217041
10, 	4.808231830596924, 	-4.948029518127441, 	-1.777699589729309
11, 	3.1172332763671875, 	-5.040188312530518, 	-1.4337140321731567
12, 	2.0413269996643066, 	-4.975278854370117, 	-1.159447193145752
13, 	1.355530858039856, 	-5.0158305168151855, 	-0.9393846988677979
14, 	0.9178376793861389, 	-4.986582279205322, 	-0.7637402415275574
15, 	0.6382412910461426, 	-5.004333972930908, 	-0.6229321360588074
16, 	0.45952412486076355, 	-4.991086006164551, 	-0.5104631781578064
17, 	0.34523946046829224, 	-4.998797416687012, 	-0.42035552859306335
18, 	0.27213525772094727, 	-4.992753028869629, 	-0.3483465909957886
19, 	0.22536347806453705, 	-4.996064186096191, 	-0.2906789183616638

将所有内容放在一起,以下是完整的代码

import torch
import numpy as np
import matplotlib.pyplot as plt

X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X
Y = func + 0.4 * torch.randn(X.size())

# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

# evaluating data points with Mean Square Error (MSE)
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)

step_size = 0.1
loss_BGD = []
n_iter = 20

for i in range (n_iter):
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_BGD.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    b.data = b.data - step_size * b.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    b.grad.data.zero_()
    # priting the values for understanding
    print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

上面的 for 循环每个 epoch 打印一行,如下所示:

0, 	596.7191162109375, 	-1.8527469635009766, 	-16.062074661254883
1, 	343.426513671875, 	-7.247585773468018, 	-12.83026123046875
2, 	202.7098388671875, 	-3.616910219192505, 	-10.298759460449219
3, 	122.16651153564453, 	-6.0132551193237305, 	-8.237251281738281
4, 	74.85094451904297, 	-4.394278526306152, 	-6.6120076179504395
5, 	46.450958251953125, 	-5.457883358001709, 	-5.295622825622559
6, 	29.111614227294922, 	-4.735295295715332, 	-4.2531514167785645
7, 	18.386211395263672, 	-5.206836700439453, 	-3.4119482040405273
8, 	11.687058448791504, 	-4.883906364440918, 	-2.7437009811401367
9, 	7.4728569984436035, 	-5.092618465423584, 	-2.205873966217041
10, 	4.808231830596924, 	-4.948029518127441, 	-1.777699589729309
11, 	3.1172332763671875, 	-5.040188312530518, 	-1.4337140321731567
12, 	2.0413269996643066, 	-4.975278854370117, 	-1.159447193145752
13, 	1.355530858039856, 	-5.0158305168151855, 	-0.9393846988677979
14, 	0.9178376793861389, 	-4.986582279205322, 	-0.7637402415275574
15, 	0.6382412910461426, 	-5.004333972930908, 	-0.6229321360588074
16, 	0.45952412486076355, 	-4.991086006164551, 	-0.5104631781578064
17, 	0.34523946046829224, 	-4.998797416687012, 	-0.42035552859306335
18, 	0.27213525772094727, 	-4.992753028869629, 	-0.3483465909957886
19, 	0.22536347806453705, 	-4.996064186096191, 	-0.2906789183616638

随机梯度下降

正如我们所了解的,当涉及到大量训练数据时,批量梯度下降并不是一个合适的选择。然而,深度学习算法对数据有很强的需求,通常需要大量数据进行训练。例如,一个包含数百万训练样本的数据集需要模型在单步中计算所有数据的梯度,如果我们使用批量梯度下降。

这似乎不是一种高效的方式,替代方法是随机梯度下降(SGD)。随机梯度下降每次只考虑训练数据中的单个样本,计算梯度并更新权重。因此,如果训练数据中有NN个样本,每个 epoch 将有NN步。

使用随机梯度下降进行训练

要使用随机梯度下降训练我们的模型,我们将像上面批量梯度下降一样随机初始化可训练参数wwbb。在这里,我们将定义一个空列表来存储随机梯度下降的损失,并训练模型 20 个 epochs。以下是从先前示例修改而来的完整代码:

import torch
import numpy as np
import matplotlib.pyplot as plt

X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X
Y = func + 0.4 * torch.randn(X.size())

# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

# evaluating data points with Mean Square Error (MSE)
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)

step_size = 0.1
loss_SGD = []
n_iter = 20

for i in range (n_iter):    
    # calculating true loss and storing it
    Y_pred = forward(X)
    # store the loss in the list
    loss_SGD.append(criterion(Y_pred, Y).tolist())

    for x, y in zip(X, Y):
      # making a pridiction in forward pass
      y_hat = forward(x)
      # calculating the loss between original and predicted data points
      loss = criterion(y_hat, y)
      # backward pass for computing the gradients of the loss w.r.t to learnable parameters
      loss.backward()
      # updateing the parameters after each iteration
      w.data = w.data - step_size * w.grad.data
      b.data = b.data - step_size * b.grad.data
      # zeroing gradients after each iteration
      w.grad.data.zero_()
      b.grad.data.zero_()
      # priting the values for understanding
      print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

这将打印如下长长的数值列表

0, 	24.73763084411621, 	-5.02630615234375, 	-20.994739532470703
0, 	455.0946960449219, 	-25.93259620666504, 	-16.7281494140625
0, 	6968.82666015625, 	54.207733154296875, 	-33.424049377441406
0, 	97112.9140625, 	-238.72393798828125, 	28.901844024658203
....
19, 	8858971136.0, 	-1976796.625, 	8770213.0
19, 	271135948800.0, 	-1487331.875, 	8874354.0
19, 	3010866446336.0, 	-3153109.5, 	8527317.0
19, 	47926483091456.0, 	3631328.0, 	9911896.0

绘制比较图表

现在我们已经使用批量梯度下降和随机梯度下降训练了我们的模型,让我们来看看在模型训练期间这两种方法的损失如何减少。因此,批量梯度下降的图表如下所示。

...
plt.plot(loss_BGD, label="Batch Gradient Descent")
plt.xlabel('Epoch')
plt.ylabel('Cost/Total loss')
plt.legend()
plt.show()

批量梯度下降的损失历史

类似地,这是随机梯度下降的图表。

plt.plot(loss_SGD,label="Stochastic Gradient Descent")
plt.xlabel('Epoch')
plt.ylabel('Cost/Total loss')
plt.legend()
plt.show()

随机梯度下降的损失历史

正如你所见,批量梯度下降的损失平稳下降。另一方面,你会在随机梯度下降的图中观察到波动。前面提到过,原因很简单。在批量梯度下降中,损失在处理完所有训练样本后更新,而随机梯度下降则在每个训练样本处理后更新损失。

综合考虑,下面是完整的代码:

import torch
import numpy as np
import matplotlib.pyplot as plt

# Creating a function f(X) with a slope of -5
X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X

# Adding Gaussian noise to the function f(X) and saving it in Y
Y = func + 0.4 * torch.randn(X.size())

# Plot and visualizing the data points in blue
plt.plot(X.numpy(), Y.numpy(), 'b+', label='Y')
plt.plot(X.numpy(), func.numpy(), 'r', label='func')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid('True', color='y')
plt.show()

# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

# evaluating data points with Mean Square Error (MSE)
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

# Batch gradient descent
w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)
step_size = 0.1
loss_BGD = []
n_iter = 20

for i in range (n_iter):
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_BGD.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    b.data = b.data - step_size * b.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    b.grad.data.zero_()
    # priting the values for understanding
    print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

# Stochastic gradient descent
w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)
step_size = 0.1
loss_SGD = []
n_iter = 20

for i in range(n_iter):  
    # calculating true loss and storing it
    Y_pred = forward(X)
    # store the loss in the list
    loss_SGD.append(criterion(Y_pred, Y).tolist())

    for x, y in zip(X, Y):
      # making a pridiction in forward pass
      y_hat = forward(x)
      # calculating the loss between original and predicted data points
      loss = criterion(y_hat, y)
      # backward pass for computing the gradients of the loss w.r.t to learnable parameters
      loss.backward()
      # updateing the parameters after each iteration
      w.data = w.data - step_size * w.grad.data
      b.data = b.data - step_size * b.grad.data
      # zeroing gradients after each iteration
      w.grad.data.zero_()
      b.grad.data.zero_()
      # priting the values for understanding
      print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

# Plot graphs
plt.plot(loss_BGD, label="Batch Gradient Descent")
plt.xlabel('Epoch')
plt.ylabel('Cost/Total loss')
plt.legend()
plt.show()

plt.plot(loss_SGD,label="Stochastic Gradient Descent")
plt.xlabel('Epoch')
plt.ylabel('Cost/Total loss')
plt.legend()
plt.show()

总结

在本教程中,你了解了梯度下降法及其一些变体,并学习了如何在 PyTorch 中实现它们。特别是,你了解了:

  • 梯度下降算法及其在 PyTorch 中的实现

  • 批量梯度下降及其在 PyTorch 中的实现

  • 随机梯度下降及其在 PyTorch 中的实现

  • 批量梯度下降和随机梯度下降的区别

  • 批量梯度下降和随机梯度下降在训练过程中损失的变化

深度学习模型的权重初始化

原文:machinelearningmastery.com/initializing-weights-for-deep-learning-models/

为了构建一个准确分类数据样本并在测试数据上表现良好的分类器,你需要以一种使模型良好收敛的方式初始化权重。通常我们会随机初始化权重。但是,当我们使用均方误差(MSE)作为训练逻辑回归模型的损失函数时,有时可能会遇到一些问题。在进一步细节之前,请注意,这里使用的方法也适用于除逻辑回归之外的分类模型,并将在接下来的教程中使用。

如果权重在一个适当的区域初始化,我们的模型可以良好收敛。然而,如果我们将模型权重初始化在一个不利的区域,我们可能会发现模型难以收敛或收敛非常缓慢。在本教程中,你将学习使用 MSE 损失函数且模型权重初始化不当时会发生什么。特别地,你将学习:

  • 不良初始化如何影响逻辑回归模型的训练。

  • 如何用 PyTorch 训练一个逻辑回归模型。

  • 如何不良的初始化权重和 MSE 损失函数会显著降低模型的准确性。

  • 那么,让我们开始吧。

通过我的书籍 《深度学习与 PyTorch》 快速启动你的项目。它提供了自学教程有效代码

让我们开始吧。

深度学习模型的权重初始化。

图片来自 Priscilla Serneo。保留部分权利。

概述

本教程分为三个部分;它们是

  • 准备数据和构建模型

  • 模型权重初始值的影响

  • 适当的权重初始化

准备数据和构建模型

首先,让我们准备一些合成数据来训练和评估模型。

数据将基于单一变量预测 0 或 1 的值。

import torch
from torch.utils.data import Dataset

class Data(Dataset):
    def __init__(self):
        self.x = torch.arange(-2, 2, 0.1).view(-1, 1)
        self.y = torch.zeros(self.x.shape[0], 1)
        self.y[self.x[:, 0] > 0.2] = 1
        self.len = self.x.shape[0]

    def __getitem__(self, idx):
        return self.x[idx], self.y[idx] 

    def __len__(self):
        "get data length"
        return self.len

使用这个 Dataset 类,我们可以创建一个数据集对象。

# Creating dataset object
data_set = Data()

现在,让我们使用 nn.Module 为我们的逻辑回归模型构建一个自定义模块。如我们之前的教程中所述,你将使用 nn.Module 包中的方法和属性来构建自定义模块。

# build custom module for logistic regression
class LogisticRegression(torch.nn.Module):    
    # build the constructor
    def __init__(self, n_inputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, 1)
    # make predictions
    def forward(self, x):
        y_pred = torch.sigmoid(self.linear(x))
        return y_pred

你将创建一个用于逻辑回归的模型对象,如下所示。

log_regr = LogisticRegression(1)

想要开始使用 PyTorch 进行深度学习?

立即参加我的免费电子邮件速成课程(附样例代码)。

点击注册,还能免费获得课程的 PDF 电子书版本。

模型权重初始值的影响

为了证明这一点,让我们用其他值(或预定的不良值)替换随机初始化的模型权重,这样模型将无法收敛。

# replace the randomly initialized weights with our own values
log_regr.state_dict() ['linear.weight'].data[0] = torch.tensor([[-5]])
log_regr.state_dict() ['linear.bias'].data[0] = torch.tensor([[-10]])
print("checking weights: ", log_regr.state_dict())

它会打印:

checking weights:  OrderedDict([('linear.weight', tensor([[-5.]])), ('linear.bias', tensor([-10.]))])

如你所见,随机初始化的参数已经被替换。

你将使用随机梯度下降训练这个模型,并将学习率设置为 2。由于你需要检查初始化值不佳和 MSE 损失对模型性能的影响,你将设置这个标准来检查模型损失。在训练过程中,数据由数据加载器提供,批量大小为 2。

...
from torch.utils.data import DataLoader

# defining the optimizer and loss
optimizer = torch.optim.SGD(log_regr.parameters(), lr=2)
criterion = torch.nn.MSELoss()
# Creating the dataloader
train_loader = DataLoader(dataset=data_set, batch_size=2)

现在,让我们训练模型 50 个周期。

...
# Train the model
Loss = []
epochs = 50
for epoch in range(epochs):
    for x,y in train_loader:
        y_pred = log_regr(x)
        loss = criterion(y_pred, y)
        Loss.append(loss.item())
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()   
    print(f"epoch = {epoch}, loss = {loss}")
print("Done!")

在模型训练期间,你将看到每个周期的进展:

[Parameter containing:
tensor([[0.7645]], requires_grad=True), Parameter containing:
tensor([0.8300], requires_grad=True)]

如你所见,训练过程中的损失保持不变,没有任何改进。这表明模型没有学习,并且在测试数据上表现不好。

让我们还可视化模型训练的图表。

import matplotlib.pyplot as plt

plt.plot(Loss)
plt.xlabel("no. of iterations")
plt.ylabel("total loss")
plt.show()

你将看到如下图:图表也告诉我们同样的故事,即训练过程中模型损失没有任何变化或减少。

虽然我们的模型在训练过程中表现不佳,但让我们获取测试数据的预测结果,并测量模型的整体准确率。

# get the model predictions on test data
y_pred = log_regr(data_set.x)
label = y_pred > 0.5  # setting the threshold for classification
print("model accuracy on test data: ",
      torch.mean((label == data_set.y.type(torch.ByteTensor)).type(torch.float)))

这给出

model accuracy on test data:  tensor(0.5750)

模型的准确率仅为 57%,这不是你期望的结果。这表明初始化权重不佳以及使用 MSE 损失可能会对模型准确性产生很大影响。为了减少这种误差,我们应用最大似然估计和交叉熵损失,这将在下一个教程中讲解。

综合考虑,以下是完整的代码:

import matplotlib.pyplot as plt 
import torch
from torch.utils.data import Dataset, DataLoader
torch.manual_seed(0)

class Data(Dataset):
    def __init__(self):
        self.x = torch.arange(-2, 2, 0.1).view(-1, 1)
        self.y = torch.zeros(self.x.shape[0], 1)
        self.y[self.x[:, 0] > 0.2] = 1
        self.len = self.x.shape[0]

    def __getitem__(self, idx):
        return self.x[idx], self.y[idx] 

    def __len__(self):
        "get data length"
        return self.len

# Creating dataset object
data_set = Data()

# build custom module for logistic regression
class LogisticRegression(torch.nn.Module):    
    # build the constructor
    def __init__(self, n_inputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, 1)
    # make predictions
    def forward(self, x):
        y_pred = torch.sigmoid(self.linear(x))
        return y_pred

log_regr = LogisticRegression(1)

# replace the randomly initialized weights with our own values
log_regr.state_dict() ['linear.weight'].data[0] = torch.tensor([[-5]])
log_regr.state_dict() ['linear.bias'].data[0] = torch.tensor([[-10]])
print("checking weights: ", log_regr.state_dict())

# defining the optimizer and loss
optimizer = torch.optim.SGD(log_regr.parameters(), lr=2)
criterion = torch.nn.MSELoss()

# Creating the dataloader
train_loader = DataLoader(dataset=data_set, batch_size=2)

# Train the model
Loss = []
epochs = 50
for epoch in range(epochs):
    for x,y in train_loader:
        y_pred = log_regr(x)
        loss = criterion(y_pred, y)
        Loss.append(loss.item())
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()   
    print(f"epoch = {epoch}, loss = {loss}")
print("Done!")

plt.plot(Loss)
plt.xlabel("no. of iterations")
plt.ylabel("total loss")
plt.show()

# get the model predictions on test data
y_pred = log_regr(data_set.x)
label = y_pred > 0.5 # setting the threshold between zero and one.
print("model accuracy on test data: ",
      torch.mean((label == data_set.y.type(torch.ByteTensor)).type(torch.float)))

适当的权重初始化

默认情况下,从 PyTorch 初始化的权重应该能给你正确的模型。如果你修改上述代码,将训练前覆盖模型权重的两行注释掉,并重新运行,你应该会看到结果相当不错。之所以之前效果很差,是因为权重离最佳权重太远,以及在逻辑回归问题中使用了均方误差(MSE)作为损失函数。

像随机梯度下降这样的优化算法的性质并不能保证在所有情况下都有效。为了使优化算法找到解决方案,即使模型收敛,最好是将模型权重设置在接近解决方案的位置。当然,在模型收敛之前我们无法知道接近的位置在哪里。但研究发现,我们应该倾向于将权重设置为在一批样本数据中,

  • 激活的均值为零。

  • 激活的方差与层输入的方差相当。

一种流行的方法是使用 Xavier 初始化来初始化模型权重,即,根据均匀分布 U[1n,1n]U[-\frac{1}{\sqrt{n}}, \frac{1}{\sqrt{n}}] 随机设置权重,其中 nn 是层的输入数量(在我们的例子中是 1)。

另一种方法是标准化 Xavier 初始化,即使用分布 U[6n+m,6n+m]U[-\sqrt{\frac{6}{n+m}}, \sqrt{\frac{6}{n+m}}],其中 nnmm 是层的输入和输出数量。在我们的例子中,两者都是 1。

如果我们不想使用均匀分布,He 初始化建议使用均值为 0 和方差为 2/n\sqrt{2/n} 的高斯分布。

你可以在这篇文章中查看更多关于权重初始化的内容,深度学习神经网络的权重初始化

总结

在本教程中,你学习了权重不当如何降低模型性能。特别是,你学到了:

  • 初始化不当如何影响逻辑回归模型的训练。

  • 如何使用 PyTorch 训练逻辑回归模型。

  • 初始化不当的权重值与 MSE 损失如何显著降低模型的准确性。

PyTorch 中的 Softmax 分类器介绍

原文:machinelearningmastery.com/introduction-to-softmax-classifier-in-pytorch/

虽然逻辑回归分类器用于二类分类,softmax 分类器是一种监督学习算法,主要用于多类别情况。

Softmax 分类器通过为每个类别分配概率分布来工作。具有最高概率的类别的概率分布被归一化为 1,其他所有概率则相应地缩放。

类似地,softmax 函数将神经元的输出转换为类别上的概率分布。它具有以下属性:

  1. 它与逻辑 sigmoid 有关,逻辑 sigmoid 用于概率建模,并具有类似的属性。

  2. 它的取值范围在 0 到 1 之间,0 表示不可能发生的事件,1 表示必然发生的事件。

  3. 对输入x的 softmax 的导数可以解释为预测给定输入x时某个特定类别被选择的可能性。

在本教程中,我们将构建一个一维的 softmax 分类器并探索其功能。特别地,我们将学习:

  • 如何使用 Softmax 分类器进行多类分类。

  • 如何在 PyTorch 中构建和训练 Softmax 分类器。

  • 如何分析模型在测试数据上的结果。

启动你的项目,参考我的书籍《深度学习与 PyTorch》。它提供了自学教程可运行的代码

让我们开始吧!

PyTorch 中的 Softmax 分类器介绍。

图片由Julia Caesar提供。版权所有。

概述

本教程分为四个部分;它们是

  • 准备数据集

  • 将数据集加载到 DataLoader 中

  • 使用nn.Module构建模型

  • 训练分类器

准备数据集

首先,让我们构建我们的数据集类以生成一些数据样本。与之前的实验不同,你将为多个类别生成数据。然后你将训练 softmax 分类器并在这些数据样本上进行预测,之后使用它对测试数据进行预测。

以下内容,我们基于一个输入变量生成四个类别的数据:

import torch
from torch.utils.data import Dataset

class toy_data(Dataset):
    "The data for multi-class classification"
    def __init__(self):
        # single input
        self.x = torch.arange(-3, 3, 0.1).view(-1, 1)
        # multi-class output
        self.y = torch.zeros(self.x.shape[0])
        self.y[(self.x > -2.0)[:, 0] * (self.x < 0.0)[:, 0]] = 1 self.y[(self.x >= 0.0)[:, 0] * (self.x < 2.0)[:, 0]] = 2 self.y[(self.x >= 2.0)[:, 0]] = 3
        self.y = self.y.type(torch.LongTensor)
        self.len = self.x.shape[0]

    def __getitem__(self, idx):
        "accessing one element in the dataset by index"
        return self.x[idx], self.y[idx] 

    def __len__(self):
        "size of the entire dataset"
        return self.len

让我们创建数据对象并检查前十个数据样本及其标签。

# Create the dataset object and check a few samples
data = toy_data()
print("first ten data samples: ", data.x[0:10])
print("first ten data labels: ", data.y[0:10])

这将打印:

first ten data samples:  tensor([[-3.0000],
        [-2.9000],
        [-2.8000],
        [-2.7000],
        [-2.6000],
        [-2.5000],
        [-2.4000],
        [-2.3000],
        [-2.2000],
        [-2.1000]])
first ten data labels:  tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

使用nn.Module构建 Softmax 模型

你将使用 PyTorch 中的nn.Module来构建自定义的 softmax 模块。这与之前教程中为逻辑回归构建的自定义模块类似。那么这里有什么不同呢?之前你使用1代替n_outputs进行二分类,而在这里我们将定义四个类别进行多分类。其次,在forward()函数中,模型不使用逻辑函数进行预测。

class Softmax(torch.nn.Module):
    "custom softmax module"
    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, n_outputs)

    def forward(self, x):
        pred = self.linear(x)
        return pred

现在,让我们创建模型对象。它接受一个一维向量作为输入,并对四个不同的类别进行预测。我们还来检查一下参数是如何初始化的。

# call Softmax Classifier
model_softmax = Softmax(1, 4)
model_softmax.state_dict()

这将打印

OrderedDict([('linear.weight',
              tensor([[-0.0075],
                      [ 0.5364],
                      [-0.8230],
                      [-0.7359]])),
             ('linear.bias', tensor([-0.3852,  0.2682, -0.0198,  0.7929]))])

想要开始使用 PyTorch 进行深度学习吗?

现在参加我的免费电子邮件速成课程(附带示例代码)。

点击注册,还可以获得课程的免费 PDF 电子书版本。

训练模型

结合随机梯度下降,你将使用交叉熵损失进行模型训练,并将学习率设置为 0.01。你将数据加载到数据加载器中,并将批量大小设置为 2。

...
from torch.utils.dataimport DataLoader

# define loss, optimizier, and dataloader
optimizer = torch.optim.SGD(model_softmax.parameters(), lr = 0.01)
criterion = torch.nn.CrossEntropyLoss()
train_loader = DataLoader(dataset = data, batch_size = 2)

既然一切都已设置好,我们来训练我们的模型 100 次迭代。

# Train the model
Loss = []
epochs = 100
for epoch in range(epochs):
    for x, y in train_loader:
        optimizer.zero_grad()
        y_pred = model_softmax(x)
        loss = criterion(y_pred, y)
        Loss.append(loss)
        loss.backward()
        optimizer.step()
print("Done!")

训练循环完成后,你调用模型上的 max() 方法来进行预测。参数 1 返回相对于轴一的最大值,即从每列返回最大值的索引。

# Make predictions on test data
pred_model =  model_softmax(data.x)
_, y_pred = pred_model.max(1)
print("model predictions on test data:", y_pred)

从上面,你应该可以看到:

model predictions on test data: tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])

这些是模型在测试数据上的预测结果。

让我们也检查一下模型的准确性。

# check model accuracy
correct = (data.y == y_pred).sum().item()
acc = correct / len(data)
print("model accuracy: ", acc)

在这种情况下,你可能会看到

model accuracy:  0.9833333333333333

在这个简单的模型中,如果你训练得更久,你会看到准确率接近 1。

将所有内容整合在一起,以下是完整的代码:

import torch
from torch.utils.data import Dataset, DataLoader

class toy_data(Dataset):
    "The data for multi-class classification"
    def __init__(self):
        # single input
        self.x = torch.arange(-3, 3, 0.1).view(-1, 1)
        # multi-class output
        self.y = torch.zeros(self.x.shape[0])
        self.y[(self.x > -2.0)[:, 0] * (self.x < 0.0)[:, 0]] = 1
        self.y[(self.x >= 0.0)[:, 0] * (self.x < 2.0)[:, 0]] = 2
        self.y[(self.x >= 2.0)[:, 0]] = 3
        self.y = self.y.type(torch.LongTensor)
        self.len = self.x.shape[0]

    def __getitem__(self, idx):
        "accessing one element in the dataset by index"
        return self.x[idx], self.y[idx] 

    def __len__(self):
        "size of the entire dataset"
        return self.len

# Create the dataset object and check a few samples
data = toy_data()
print("first ten data samples: ", data.x[0:10])
print("first ten data labels: ", data.y[0:10])

class Softmax(torch.nn.Module):
    "custom softmax module"
    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        self.linear = torch.nn.Linear(n_inputs, n_outputs)

    def forward(self, x):
        pred = self.linear(x)
        return pred

# call Softmax Classifier
model_softmax = Softmax(1, 4)
model_softmax.state_dict()

# define loss, optimizier, and dataloader
optimizer = torch.optim.SGD(model_softmax.parameters(), lr=0.01)
criterion = torch.nn.CrossEntropyLoss()
train_loader = DataLoader(dataset=data, batch_size=2)

# Train the model
Loss = []
epochs = 100
for epoch in range(epochs):
    for x, y in train_loader:
        optimizer.zero_grad()
        y_pred = model_softmax(x)
        loss = criterion(y_pred, y)
        Loss.append(loss)
        loss.backward()
        optimizer.step()
print("Done!")

# Make predictions on test data
pred_model =  model_softmax(data.x)
_, y_pred = pred_model.max(1)
print("model predictions on test data:", y_pred)

# check model accuracy
correct = (data.y == y_pred).sum().item()
acc = correct / len(data)
print("model accuracy: ", acc)

总结

在本教程中,你学习了如何构建一个简单的一维 Softmax 分类器。特别地,你学习了:

  • 如何使用 Softmax 分类器进行多类分类。

  • 如何在 PyTorch 中构建和训练 Softmax 分类器。

  • 如何分析模型在测试数据上的结果。

加载和提供 PyTorch 中的数据集

原文:machinelearningmastery.com/loading-and-providing-datasets-in-pytorch/

将数据管道结构化,以便轻松地与您的深度学习模型连接是任何基于深度学习的系统的重要方面。PyTorch 将所有内容打包到一个地方,以便做到这一点。

虽然在上一个教程中,我们使用了简单的数据集,但在实际场景中,为了充分发挥深度学习和神经网络的潜力,我们需要处理更大的数据集。

在这个教程中,您将学习如何在 PyTorch 中构建自定义数据集。虽然这里的重点仅限于图像数据,但本节学习的概念可以应用于任何形式的数据集,例如文本或表格数据。因此,在这里您将学到:

  • 如何在 PyTorch 中处理预加载的图像数据集。

  • 如何在预加载的数据集上应用 torchvision 转换。

  • 如何在 PyTorch 中构建自定义图像数据集类,并对其应用各种转换。

启动您的项目,使用我的书籍《Deep Learning with PyTorch》。它提供带有工作代码自学教程

让我们开始吧!

加载和提供 PyTorch 中的数据集

图片由Uriel SC提供。部分权利保留。

概述

本教程分为三部分;它们是:

  • PyTorch 中的预加载数据集

  • 在图像数据集上应用 Torchvision 转换

  • 构建自定义图像数据集

PyTorch 中的预加载数据集

在 PyTorch 领域库中有多种预加载数据集,如 CIFAR-10、MNIST、Fashion-MNIST 等。您可以从 torchvision 导入它们并进行实验。此外,您还可以使用这些数据集来评估您的模型。

我们将继续从 torchvision 导入 Fashion-MNIST 数据集。Fashion-MNIST 数据集包含 70,000 个灰度图像,每个图像为 28×28 像素,分为十类,每类包含 7,000 张图像。其中有 60,000 张图像用于训练,10,000 张用于测试。

让我们首先导入本教程中将使用的几个库。

import torch
from torch.utils.data import Dataset
from torchvision import datasets
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(42)

让我们还定义一个辅助函数,使用 matplotlib 显示数据集中的示例元素。

def imshow(sample_element, shape = (28, 28)):
    plt.imshow(sample_element[0].numpy().reshape(shape), cmap='gray')
    plt.title('Label = ' + str(sample_element[1]))
    plt.show()

现在,我们将使用torchvision.datasets中的FashionMNIST()函数加载 Fashion-MNIST 数据集。此函数接受一些参数:

  • root:指定我们将存储数据的路径。

  • train:指示它是训练数据还是测试数据。我们将其设置为 False,因为我们目前不需要用于训练。

  • download:设置为True,表示它将从互联网上下载数据。

  • transform:允许我们在数据集上应用任何需要的转换。

dataset = datasets.FashionMNIST(
    root='./data',
    train=False,
    download=True,
    transform=transforms.ToTensor()
)

让我们检查一下 Fashion-MNIST 数据集中我们拥有的类别名称及其对应标签。

classes = dataset.classes
print(classes)

它会打印

['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

类别标签也类似:

print(dataset.class_to_idx)

它会打印

{'T-shirt/top': 0, 'Trouser': 1, 'Pullover': 2, 'Dress': 3, 'Coat': 4, 'Sandal': 5, 'Shirt': 6, 'Sneaker': 7, 'Bag': 8, 'Ankle boot': 9}

这里是如何使用上述定义的帮助函数来可视化数据集的第一个元素及其对应标签的。

imshow(dataset[0])

时尚 MNIST 数据集的第一个元素

时尚 MNIST 数据集的第一个元素

想要开始使用 PyTorch 进行深度学习吗?

立即参加我的免费电子邮件速成课程(附带示例代码)。

点击注册并获得课程的免费 PDF 电子书版本。

在图像数据集上应用 Torchvision 变换

在许多情况下,我们需要在将图像输入神经网络之前应用几个变换。例如,我们经常需要对图像进行 RandomCrop 以进行数据增强。

如下所示,PyTorch 允许我们选择各种变换。

print(dir(transforms))

这显示了所有可用的变换函数:

['AugMix', 'AutoAugment', 'AutoAugmentPolicy', 'CenterCrop', 'ColorJitter',
 'Compose', 'ConvertImageDtype', 'ElasticTransform', 'FiveCrop', 'GaussianBlur',
'Grayscale', 'InterpolationMode', 'Lambda', 'LinearTransformation',
'Normalize', 'PILToTensor', 'Pad', 'RandAugment', 'RandomAdjustSharpness',
'RandomAffine', 'RandomApply', 'RandomAutocontrast', 'RandomChoice', 'RandomCrop',
'RandomEqualize', 'RandomErasing', 'RandomGrayscale', 'RandomHorizontalFlip',
'RandomInvert', 'RandomOrder', 'RandomPerspective', 'RandomPosterize',
'RandomResizedCrop', 'RandomRotation', 'RandomSolarize', 'RandomVerticalFlip',
'Resize', 'TenCrop', 'ToPILImage', 'ToTensor', 'TrivialAugmentWide',
...]

作为示例,让我们对 Fashion-MNIST 图像应用 RandomCrop 变换并将其转换为张量。我们可以使用 transform.Compose 来组合多个变换,正如我们从之前的教程中学到的那样。

randomcrop_totensor_transform = transforms.Compose([transforms.CenterCrop(16),
                                                    transforms.ToTensor()])
dataset = datasets.FashionMNIST(root='./data',
                                train=False, download=True,
                                transform=randomcrop_totensor_transform)
print("shape of the first data sample: ", dataset[0][0].shape)

这会打印

shape of the first data sample:  torch.Size([1, 16, 16])

如你所见,图像现在已被裁剪为 16×1616\times 16 像素。现在,让我们绘制数据集的第一个元素,以查看它们是如何被随机裁剪的。

imshow(dataset[0], shape=(16, 16))

这显示了以下图像

从 Fashion MNIST 数据集中裁剪的图像

综合所有内容,完整代码如下:

import torch
from torch.utils.data import Dataset
from torchvision import datasets
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(42)

def imshow(sample_element, shape = (28, 28)):
    plt.imshow(sample_element[0].numpy().reshape(shape), cmap='gray')
    plt.title('Label = ' + str(sample_element[1]))
    plt.show()

dataset = datasets.FashionMNIST(
    root='./data',
    train=False,
    download=True,
    transform=transforms.ToTensor()
)

classes = dataset.classes
print(classes)
print(dataset.class_to_idx)

imshow(dataset[0])

randomcrop_totensor_transform = transforms.Compose([transforms.CenterCrop(16),
                                                    transforms.ToTensor()])
dataset = datasets.FashionMNIST(
    root='./data',
    train=False,
    download=True,
    transform=randomcrop_totensor_transform)
)

print("shape of the first data sample: ", dataset[0][0].shape)
imshow(dataset[0], shape=(16, 16))

构建自定义图像数据集

到目前为止,我们一直在讨论 PyTorch 中的预构建数据集,但如果我们需要为我们的图像数据集构建一个自定义数据集类呢?虽然在 之前的教程 中我们只是简单了解了 Dataset 类的组件,但在这里我们将从头开始构建一个自定义图像数据集类。

首先,在构造函数中我们定义了类的参数。__init__ 函数在类中实例化了 Dataset 对象。存储图像和注释的目录被初始化,同时如果我们希望稍后在数据集上应用变换,这些变换也会被初始化。这里我们假设我们在一个如下的目录结构中有一些图像:

attface/
|-- imagedata.csv
|-- s1/
|   |-- 1.png
|   |-- 2.png
|   |-- 3.png
|   ...
|-- s2/
|   |-- 1.png
|   |-- 2.png
|   |-- 3.png
|   ...
...

注释是如下的 CSV 文件,位于图像根目录下(即上面的“attface”):

s1/1.png,1
s1/2.png,1
s1/3.png,1
...
s12/1.png,12
s12/2.png,12
s12/3.png,12

其中 CSV 数据的第一列是图像的路径,第二列是标签。

类似地,我们在类中定义了 __len__ 函数,它返回我们图像数据集中样本的总数,而 __getitem__ 方法从数据集中读取并返回给定索引处的一个数据元素。

import os
import pandas as pd
import numpy as np
from torchvision.io import read_image

# creating object for our image dataset
class CustomDatasetForImages(Dataset):
    # defining constructor
    def __init__(self, annotations, directory, transform=None):
        # directory containing the images
        self.directory = directory
        annotations_file_dir = os.path.join(self.directory, annotations)
        # loading the csv with info about images
        self.labels = pd.read_csv(annotations_file_dir)
        # transform to be applied on images
        self.transform = transform

        # Number of images in dataset
        self.len = self.labels.shape[0]

    # getting the length
    def __len__(self):
        return len(self.labels)

    # getting the data items
    def __getitem__(self, idx):
        # defining the image path
        image_path = os.path.join(self.directory, self.labels.iloc[idx, 0])
        # reading the images
        image = read_image(image_path)
        # corresponding class labels of the images 
        label = self.labels.iloc[idx, 1]

        # apply the transform if not set to None
        if self.transform:
            image = self.transform(image)

        # returning the image and label
        return image, label

现在,我们可以创建我们的数据集对象并对其应用变换。我们假设图像数据位于名为“attface”的目录下,注释 CSV 文件位于“attface/imagedata.csv”下。然后,数据集的创建如下:

directory = "attface"
annotations = "imagedata.csv"
custom_dataset = CustomDatasetForImages(annotations=annotations,
                                        directory=directory)

可选地,你还可以将变换函数添加到数据集中:

randomcrop_totensor_transform = transforms.RandomCrop(16)
dataset = CustomDatasetForImages(annotations=annotations,
                                 directory=directory,
                                 transform=randomcrop_totensor_transform)

你可以将这个自定义图像数据集类用于存储在目录中的任何数据集,并根据需要应用变换。

总结

在本教程中,你学习了如何在 PyTorch 中处理图像数据集和变换。特别地,你学习了:

  • 如何在 PyTorch 中处理预加载的图像数据集。

  • 如何对预加载的数据集应用 torchvision 变换。

  • 如何在 PyTorch 中构建自定义图像数据集类并对其应用各种变换。

PyTorch 模型中的损失函数

原文:machinelearningmastery.com/loss-functions-in-pytorch-models/

损失指标对神经网络非常重要。由于所有机器学习模型都是一种优化问题,损失函数就是要最小化的目标函数。在神经网络中,优化是通过梯度下降和反向传播来完成的。但是什么是损失函数,它们又如何影响您的神经网络?

在这一章中,您将了解什么是损失函数,并深入了解一些常用的损失函数以及如何将它们应用于您的神经网络中。阅读完本章后,您将学到:

  • 什么是损失函数,以及它们在训练神经网络模型中的作用

  • 回归和分类问题的常见损失函数

  • 如何在您的 PyTorch 模型中使用损失函数

用我的书《深度学习与 PyTorch》启动你的项目。它提供了自学教程可运行的代码

让我们开始吧!

PyTorch 模型中的损失函数。

照片由Hans Vivek拍摄。部分权利保留。

简介

本文分为四个部分;它们是:

  • 什么是损失函数?

  • 回归的损失函数

  • 分类问题的损失函数

  • PyTorch 中的自定义损失函数

什么是损失函数?

在神经网络中,损失函数有助于优化模型的性能。它们通常用于衡量模型在预测中所产生的一些惩罚,例如预测与真实标签之间的偏差。损失函数通常在其定义域上是可微的(但允许在非常特定的点上梯度未定义,例如x=0x=0,实际上在实践中被忽略)。在训练循环中,损失函数相对于参数进行微分,并且这些梯度用于您的反向传播和梯度下降步骤,以优化训练集上的模型。

损失函数也略有不同于度量标准。虽然损失函数可以告诉您我们模型的性能,但可能并不直接吸引或易于人类解释。这就是度量标准的作用。例如准确度对于人类来理解神经网络的性能要更有用,尽管它们可能不是损失函数的好选择,因为它们可能不可微分。

接下来,让我们探讨一些常见的损失函数,用于回归问题和分类问题。

想要开始使用 PyTorch 进行深度学习吗?

现在就来参加我的免费电子邮件崩溃课程(附有示例代码)。

点击注册,还可以获得课程的免费 PDF 电子书版本。

回归的损失函数

在回归问题中,模型的目标是预测一个连续范围内的值。你的模型能一直准确预测到具体值几乎是不现实的,但如果预测值足够接近就已经很好了。因此,你需要一个损失函数来衡量其接近程度。距离准确值越远,预测的损失就越大。

一个简单的函数就是测量预测值与目标值之间的差异。在计算差异时,你不在乎值是否大于或小于目标值。因此,在数学上,我们找到1mi=1my^iyi\dfrac{1}{m}\sum_{i=1}^m \vert \hat{y}_i – y_i\vert,其中mm是训练样本的数量,而yiy_iy^i\hat{y}_i分别是目标值和预测值,对所有训练样本取平均。这就是平均绝对误差(MAE)。

平均绝对误差(MAE)永远不会为负,仅当预测值与实际值完全匹配时才为零。它是一个直观的损失函数,也可以作为你的一个指标,特别是在回归问题中,因为你希望最小化预测中的误差。

然而,绝对值在 0 处不可导。这并不是一个大问题,因为你很少会碰到这个值。但有时候,人们更倾向于使用均方误差(MSE)来代替。MSE 等于1mi=1m(y^iyi)2\dfrac{1}{m}\sum_{i=1}^m (\hat{y}_i – y_i)²,它类似于 MAE,但使用平方函数代替绝对值。

它也衡量了预测值与目标值的偏差。然而,MSE 对这种差异进行平方处理(始终为非负,因为实数的平方始终为非负),这赋予了它略微不同的特性。一个特性是,均方误差更倾向于大量小误差而不是少量大误差,这导致了具有较少离群值或至少比用 MAE 训练的模型离群值较少的模型。这是因为大误差对误差和误差梯度的影响会显著大于小误差。

让我们看看平均绝对误差和均方误差损失函数在图形上的表现:

平均绝对误差损失函数(蓝色)和梯度(橙色)

均方误差损失函数(蓝色)和梯度(橙色)

类似于激活函数,您可能也对损失函数的梯度感兴趣,因为稍后您将使用梯度来进行反向传播来训练模型的参数。您应该看到,在 MSE 中,较大的错误会导致梯度的幅度更大和更大的损失。因此,例如,两个训练示例与其地面真实值相差 1 单位将导致损失为 2,而单个训练示例与其地面真实值相差 2 单位将导致损失为 4,因此产生更大的影响。在 MAE 中并非如此。

在 PyTorch 中,您可以使用 nn.L1Loss()nn.MSELoss() 创建 MAE 和 MSE 作为损失函数。它之所以称为 L1,是因为在数学上计算 MAE 也被称为 L1-范数。以下是计算两个向量之间的 MAE 和 MSE 的示例:

import torch
import torch.nn as nn

mae = nn.L1Loss()
mse = nn.MSELoss()

predict = torch.tensor([0., 3.])
target = torch.tensor([1., 0.])

print("MAE: %.3f" % mae(predict, target))
print("MSE: %.3f" % mse(predict, target))

您应该得到

MAE: 2.000
MSE: 5.000

MAE 是 2.0,因为 12[01+30]=12(1+3)=2\frac{1}{2}[\vert 0-1\vert + \vert 3-0\vert]=\frac{1}{2}(1+3)=2 而 MSE 是 5.0,因为 12[(01)2+(30)2]=12(1+9)=5\frac{1}{2}[(0-1)² + (3-0)²]=\frac{1}{2}(1+9)=5。注意,在 MSE 中,第二个预测值为 3,实际值为 0 的示例在均方误差下贡献了 90% 的错误,而在平均绝对误差下为 75%。

有时,您可能会看到人们使用均方根误差(RMSE)作为度量标准。这将取 MSE 的平方根。从损失函数的角度来看,MSE 和 RMSE 是等价的。但从值的角度来看,RMSE 与预测值在同一单位中。如果您的预测是美元金额,MAE 和 RMSE 都会告诉您预测值与真实值的平均偏差是多少美元。但 MSE 的单位是平方美元,其物理含义不直观。

分类的损失函数

对于分类问题,输出可能取的值是一个小的离散集合。此外,用于标签编码类别的数字是任意的,没有语义含义(例如,使用标签 0 表示猫,1 表示狗,2 表示马,并不表示狗是猫和马的一半)。因此,这不应对模型的性能产生影响。

在分类问题中,模型的输出通常是每个类别的概率向量。通常,这个向量被期望是“logits”,即实数,通过 softmax 函数转换为概率,或者是 softmax 激活函数的输出。

两个概率分布之间的交叉熵是衡量两个概率分布之间差异的度量。准确地说,它是 iP(X=xi)logQ(X=xi)−\sum_i P(X=x_i)\log Q(X=x_i),其中 PPQQ 是概率。在机器学习中,我们通常有训练数据提供的概率 PP 和模型预测的概率 QQ,其中 PP 对于正确的类别为 1,对于其他所有类别为 0。然而,预测的概率 QQ 通常是介于 0 和 1 之间的浮点值。因此,当用于机器学习中的分类问题时,这个公式可以简化为:

categorical cross-entropy=logptarget\text{categorical cross-entropy} = − \log p_{\text{target}}

其中 ptargetp_{\text{target}} 是模型预测的该样本的真实类别的概率。

交叉熵度量有一个负号,因为 log(x)\log(x)xx 趋近于零时趋向于负无穷。我们希望在概率接近 0 时损失更高,而在概率接近 1 时损失更低。图形上,

分类交叉熵损失函数(蓝色)和梯度(橙色)

注意,如果真实类别的概率为 1,则损失恰好为 0,这符合预期。此外,当真实类别的概率趋近于 0 时,损失也趋向于正无穷,从而显著惩罚不良预测。你可能会认识到这个损失函数用于逻辑回归,逻辑回归损失类似,不过逻辑回归损失特定于二分类情况。

从梯度来看,你可以看到梯度通常是负的,这也是预期的,因为为了减少这个损失,你会希望真实类别的概率尽可能高。回忆一下,梯度下降是朝着梯度的反方向进行的。

在 PyTorch 中,交叉熵函数由 nn.CrossEntropyLoss() 提供。它接收预测的 logits 和目标作为参数,并计算分类交叉熵。请注意,在 CrossEntropyLoss() 函数内部,softmax 将被应用于 logits,因此你不应该在输出层使用 softmax 激活函数。使用 PyTorch 交叉熵损失函数的示例如下:

import torch
import torch.nn as nn

ce = nn.CrossEntropyLoss()

logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
target = torch.tensor([[0., 1., 0.], [1., 0., 0.]])
print("Cross entropy: %.3f" % ce(logits, target))

它打印:

Cross entropy: 0.288

注意交叉熵损失函数的第一个参数是 logit,而不是概率。因此,每一行的和不等于 1。第二个参数是一个包含概率行的张量。如果你将上面的 logits 张量通过 softmax 函数转换为概率,它将是:

probs = torch.tensor([[0.15, 0.75, 0.1], [0.75, 0.15, 0.1]])

每一行的和为 1.0。这个张量也揭示了为什么上面计算的交叉熵为 0.288,即 log(0.75)-\log(0.75)

在 PyTorch 中计算交叉熵的另一种方法是使用整数索引标签,而不是在目标中使用独热编码:

import torch
import torch.nn as nn

ce = nn.CrossEntropyLoss()

logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
indices = torch.tensor([1, 0])
print("Cross entropy: %.3f" % ce(logits, indices))

这给出了相同的 0.288 的交叉熵。注意,

import torch

target = torch.tensor([[0., 1., 0.], [1., 0., 0.]])
indices = torch.argmax(target, dim=1)
print(indices)

给你:

tensor([1, 0])

这是 PyTorch 解释目标张量的方式。在其他库中,它也称为“稀疏交叉熵”函数,以区分它不期望一个独热向量。

注意,在 PyTorch 中,您可以使用 nn.LogSoftmax() 作为激活函数。它是在层的输出上应用 softmax,然后对每个元素取对数。如果这是您的输出层,您应该使用 nn.NLLLoss()(负对数似然)作为损失函数。数学上,这两者与交叉熵损失相同。您可以通过检查下面的代码确认这一点:

import torch
import torch.nn as nn

ce = nn.NLLLoss()

# softmax to apply on dimension 1, i.e. per row
logsoftmax = nn.LogSoftmax(dim=1)

logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
pred = logsoftmax(logits)
indices = torch.tensor([1, 0])
print("Cross entropy: %.3f" % ce(pred, indices))

对于仅有两类的分类问题,它变成了二元分类。这很特殊,因为模型现在是一个逻辑回归模型,其中只能有一个输出,而不是一个包含两个值的向量。您仍然可以将二元分类实现为多类分类,并使用相同的交叉熵函数。但如果您将 xx 输出作为“正类”的概率(介于 0 和 1 之间),则已知“负类”的概率必须是 1x1-x

在 PyTorch 中,您有 nn.BCELoss() 用于二元交叉熵。它专门用于二分类情况。例如:

import torch
import torch.nn as nn

bce = nn.BCELoss()

pred = torch.tensor([0.75, 0.25])
target = torch.tensor([1., 0.])
print("Binary cross entropy: %.3f" % bce(pred, target))

这给出了:

Binary cross entropy: 0.288

这是因为:

12[log(0.75)+(log(10.25))]=log(0.75)=0.288\frac{1}{2}[-\log(0.75) + (-\log(1-0.25))] = -\log(0.75) = 0.288

注意在 PyTorch 中,目标标签 1 被视为“正类”,标签 0 被视为“负类”。目标张量中不应包含其他值。

在 PyTorch 中自定义损失函数

注意上面,损失度量是使用 torch.nn 模块中的对象计算的。计算的损失度量是一个 PyTorch 张量,因此您可以对其进行求导并开始反向传播。因此,只要您能基于模型的输出计算张量,就没有什么可以阻止您创建自己的损失函数。

PyTorch 不会提供所有可能的损失度量。例如,平均绝对百分比误差未包含在内。它类似于 MAE,定义如下:

MAPE=1mi=1my^iyiyi\text{MAPE} = \frac{1}{m} \sum_{i=1}^m \lvert\frac{\hat{y}_i – y_i}{y_i}\rvert

有时您可能更喜欢使用 MAPE。回想一下对加利福尼亚住房数据集的回归示例,预测是房价。考虑到百分比差异而不是美元差异可能更合理。您可以定义自己的 MAPE 函数,只需记住使用 PyTorch 函数计算,并返回一个 PyTorch 张量。

查看下面的完整示例:

import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler

# Read data
data = fetch_california_housing()
X, y = data.data, data.target

# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)

# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)

# Define the model
model = nn.Sequential(
    nn.Linear(8, 24),
    nn.ReLU(),
    nn.Linear(24, 12),
    nn.ReLU(),
    nn.Linear(12, 6),
    nn.ReLU(),
    nn.Linear(6, 1)
)

# loss function and optimizer
def loss_fn(output, target):
    # MAPE loss
    return torch.mean(torch.abs((target - output) / target))
optimizer = optim.Adam(model.parameters(), lr=0.0001)

n_epochs = 100   # number of epochs to run
batch_size = 10  # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)

# Hold the best model
best_mape = np.inf   # init to infinity
best_weights = None

for epoch in range(n_epochs):
    model.train()
    for start in batch_start:
        # take a batch
        X_batch = X_train[start:start+batch_size]
        y_batch = y_train[start:start+batch_size]
        # forward pass
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()
    # evaluate accuracy at end of each epoch
    model.eval()
    y_pred = model(X_test)
    mape = float(loss_fn(y_pred, y_test))
    if mape < best_mape:
        best_mape = mape
        best_weights = copy.deepcopy(model.state_dict())

# restore model and return best accuracy
model.load_state_dict(best_weights)
print("MAPE: %.2f" % best_mape)

model.eval()
with torch.no_grad():
    # Test out inference with 5 samples
    for i in range(5):
        X_sample = X_test_raw[i: i+1]
        X_sample = scaler.transform(X_sample)
        X_sample = torch.tensor(X_sample, dtype=torch.float32)
        y_pred = model(X_sample)
        print(f"{X_test_raw[i]} -> {y_pred[0].numpy()} (expected {y_test[i].numpy()})")

与其他帖子中的示例相比,您可以看到 loss_fn 现在被定义为一个自定义函数。否则,一切都是完全相同的。

进一步阅读

下面是 PyTorch 提供的文档,详细介绍了各种损失函数的实现:

摘要

在这篇文章中,你了解了损失函数及其在神经网络中的作用。你还了解了一些在回归和分类模型中使用的流行损失函数,以及如何为你的 PyTorch 模型实现自己的损失函数。具体来说,你学到了:

  • 什么是损失函数,它们在训练中的重要性

  • 用于回归和分类问题的常见损失函数

  • 如何在 PyTorch 模型中使用损失函数

PyTorch 中的时间序列预测 LSTM

原文:machinelearningmastery.com/lstm-for-time-series-prediction-in-pytorch/

长短期记忆(LSTM)是一种可以在神经网络中使用的结构。它是一种递归神经网络(RNN),期望输入为特征序列。它对时间序列或文本串等数据非常有用。在本文中,您将了解 LSTM 网络。特别是,

  • 什么是 LSTM 及其不同之处

  • 如何开发用于时间序列预测的 LSTM 网络

  • 如何训练 LSTM 网络

快速启动您的项目,请参考我的书 深度学习与 PyTorch。它提供了自学教程可运行的代码

让我们开始吧。

PyTorch 中的时间序列预测 LSTM

图片由 Carry Kung 提供。保留所有权利。

概述

本文分为三部分;它们是

  • LSTM 网络概述

  • 时间序列预测的 LSTM

  • 训练和验证您的 LSTM 网络

LSTM 网络概述

LSTM 单元是您可以用来构建更大神经网络的构建块。虽然常见的构建块如全连接层仅仅是权重张量与输入的矩阵乘法以产生输出张量,但 LSTM 模块要复杂得多。

典型的 LSTM 单元如图所示

LSTM 单元。插图来自维基百科。

它接收一个时间步的输入张量 xx 以及一个单元记忆 cc 和一个隐藏状态 hh。单元记忆和隐藏状态可以在开始时初始化为零。然后在 LSTM 单元内,xxcchh 将分别与不同的权重张量相乘,并通过一些激活函数处理几次。结果是更新后的单元记忆和隐藏状态。这些更新后的 cchh 将用于输入张量的下一个时间步。直到最后一个时间步结束,LSTM 单元的输出将是其单元记忆和隐藏状态。

具体而言,一个 LSTM 单元的方程如下:

ft=σg(Wfxt+Ufht1+bf)it=σg(Wixt+Uiht1+bi)ot=σg(Woxt+Uoht1+bo)c~t=σc(Wcxt+Ucht1+bc)ct=ftct1+itc~tht=otσh(ct) \begin{aligned} f_t &= \sigma_g(W_{f} x_t + U_{f} h_{t-1} + b_f) \\ i_t &= \sigma_g(W_{i} x_t + U_{i} h_{t-1} + b_i) \\ o_t &= \sigma_g(W_{o} x_t + U_{o} h_{t-1} + b_o) \\ \tilde{c}_t &= \sigma_c(W_{c} x_t + U_{c} h_{t-1} + b_c) \\ c_t &= f_t \odot c_{t-1} + i_t \odot \tilde{c}_t \\ h_t &= o_t \odot \sigma_h(c_t) \end{aligned}

其中 WWUUbb 是 LSTM 单元的可训练参数。每个方程是针对每个时间步计算的,因此带有下标 tt。这些可训练参数在所有时间步中重复使用。这种共享参数的特性赋予了 LSTM 记忆的能力。

请注意,上述只是 LSTM 的一种设计。文献中存在多种变体。

由于 LSTM 单元期望输入xx为多个时间步的形式,每个输入样本应为 2D 张量:一个维度表示时间,另一个维度表示特征。LSTM 单元的强大依赖于隐藏状态或单元内存的大小,通常这个维度大于输入特征的数量。

想开始使用 PyTorch 进行深度学习?

现在立即参加我的免费电子邮件速成课程(附带示例代码)。

点击注册并获得课程的免费 PDF 电子书版本。

LSTM 用于时间序列预测

让我们看看如何使用 LSTM 构建一个时间序列预测神经网络的示例。

本文中讨论的问题是国际航空乘客预测问题。这是一个给定年份和月份后,预测以 1,000 为单位的国际航空乘客数量的问题。数据范围从 1949 年 1 月到 1960 年 12 月,共 12 年,包含 144 个观测值。

这是一个回归问题。即,给定最近几个月的乘客数量(以 1,000 为单位),预测下个月的乘客数量。数据集只有一个特征:乘客数量。

让我们从读取数据开始。数据可以在这里下载。

将此文件保存为airline-passengers.csv,以便在本地目录中使用。

以下是文件前几行的样本:

"Month","Passengers"
"1949-01",112
"1949-02",118
"1949-03",132
"1949-04",129

数据有两列:月份和乘客数量。由于数据按时间顺序排列,你可以只取乘客数量来构建一个单特征时间序列。下面你将使用 pandas 库读取 CSV 文件,并将其转换为 2D numpy 数组,然后使用 matplotlib 绘制:

import matplotlib.pyplot as plt
import pandas as pd

df = pd.read_csv('airline-passengers.csv')
timeseries = df[["Passengers"]].values.astype('float32')

plt.plot(timeseries)
plt.show()

这个时间序列有 144 个时间步。你可以从图中看到上升的趋势。数据集中还有一些周期性现象,对应于北半球的暑假。通常时间序列应该被“去趋势化”以去除线性趋势成分,并在处理前进行标准化。为了简便,这些步骤在本项目中被省略。

为了展示我们模型的预测能力,时间序列被分割为训练集和测试集。与其他数据集不同,时间序列数据通常是分割而不打乱的。即,训练集是时间序列的前半部分,其余部分用作测试集。这可以很容易地在 numpy 数组上完成:

# train-test split for time series
train_size = int(len(timeseries) * 0.67)
test_size = len(timeseries) - train_size
train, test = timeseries[:train_size], timeseries[train_size:]

更复杂的问题是你希望网络如何预测时间序列。通常,时间序列预测是在一个窗口上进行的。也就是说,给定从时间twt-w到时间tt的数据,你需要预测时间t+1t+1(或更远的未来)。窗口大小ww决定了你在做出预测时可以查看多少数据。这也称为回顾期

在足够长的时间序列上,可以创建多个重叠窗口。创建一个函数以从时间序列生成固定窗口的数据集很方便。由于数据将用于 PyTorch 模型,因此输出数据集应为 PyTorch 张量:

import torch

def create_dataset(dataset, lookback):
    """Transform a time series into a prediction dataset

    Args:
        dataset: A numpy array of time series, first dimension is the time steps
        lookback: Size of window for prediction
    """
    X, y = [], []
    for i in range(len(dataset)-lookback):
        feature = dataset[i:i+lookback]
        target = dataset[i+1:i+lookback+1]
        X.append(feature)
        y.append(target)
    return torch.tensor(X), torch.tensor(y)

此函数旨在对时间序列应用窗口。它假定预测未来一个时间步长。它设计成将时间序列转换为维度为 (窗口样本数, 时间步长, 特征) 的张量。一个包含 LL 个时间步长的时间序列大约可以生成 LL 个窗口(因为窗口可以从任何时间步开始,只要窗口不超出时间序列的边界)。在一个窗口内,有多个连续的时间步长值。在每个时间步长中,可以有多个特征。在此数据集中,只有一个特征。

故意生成“特征”和“目标”具有相同的形状:对于三个时间步长的窗口,从 ttt+2t+2 的“特征”是时间序列,从 t+1t+1t+3t+3 的“目标”。我们感兴趣的是 t+3t+3,但 t+1t+1t+2t+2 的信息在训练中是有用的。

请注意,输入时间序列是一个二维数组,而 create_dataset() 函数的输出将是一个三维张量。让我们尝试 lookback=1。您可以验证输出张量的形状如下:

lookback = 1
X_train, y_train = create_dataset(train, lookback=lookback)
X_test, y_test = create_dataset(test, lookback=lookback)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

您应该看到:

torch.Size([95, 1, 1]) torch.Size([95, 1, 1])
torch.Size([47, 1, 1]) torch.Size([47, 1, 1])

现在您可以构建 LSTM 模型来预测时间序列。使用 lookback=1,准确性可能不太好,因为线索太少。但这是一个很好的例子,可以展示 LSTM 模型的结构。

模型被创建为一个类,其中包括一个 LSTM 层和一个全连接层。

...
import torch.nn as nn

class AirModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=50, num_layers=1, batch_first=True)
        self.linear = nn.Linear(50, 1)
    def forward(self, x):
        x, _ = self.lstm(x)
        x = self.linear(x)
        return x

nn.LSTM() 的输出是一个元组。第一个元素是生成的隐藏状态,每个时间步的输入都有一个。第二个元素是 LSTM 单元的记忆和隐藏状态,这里没有使用。

LSTM 层使用选项 batch_first=True 创建,因为您准备的张量的维度为 (窗口样本数, 时间步长, 特征),其中批次通过对第一个维度进行采样创建。

隐藏状态的输出经过一个全连接层进一步处理,以生成单个回归结果。由于 LSTM 的输出是每个输入时间步长的一个,您可以选择仅选择最后一个时间步长的输出,您应该有:

x, _ = self.lstm(x)
# extract only the last time step
x = x[:, -1, :]
x = self.linear(x)

模型的输出将是下一个时间步的预测。但在这里,全连接层应用于每个时间步。在此设计中,应从模型输出中仅提取最后一个时间步作为预测值。然而,在此情况下,窗口为 1,这两种方法没有区别。

训练和验证您的 LSTM 网络

因为这是一个回归问题,所以选择了均方误差(MSE)作为损失函数,通过 Adam 优化器进行最小化。在下面的代码中,PyTorch 张量通过 torch.utils.data.TensorDataset() 组合成数据集,并通过 DataLoader 提供批量训练数据。模型性能每 100 个周期评估一次,包括训练集和测试集:

import numpy as np
import torch.optim as optim
import torch.utils.data as data

model = AirModel()
optimizer = optim.Adam(model.parameters())
loss_fn = nn.MSELoss()
loader = data.DataLoader(data.TensorDataset(X_train, y_train), shuffle=True, batch_size=8)

n_epochs = 2000
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    if epoch % 100 != 0:
        continue
    model.eval()
    with torch.no_grad():
        y_pred = model(X_train)
        train_rmse = np.sqrt(loss_fn(y_pred, y_train))
        y_pred = model(X_test)
        test_rmse = np.sqrt(loss_fn(y_pred, y_test))
    print("Epoch %d: train RMSE %.4f, test RMSE %.4f" % (epoch, train_rmse, test_rmse))

由于数据集较小,模型应训练足够长的时间以学习模式。在这 2000 个训练周期中,你应该看到训练集和测试集的 RMSE 逐渐降低:

Epoch 0: train RMSE 225.7571, test RMSE 422.1521
Epoch 100: train RMSE 186.7353, test RMSE 381.3285
Epoch 200: train RMSE 153.3157, test RMSE 345.3290
Epoch 300: train RMSE 124.7137, test RMSE 312.8820
Epoch 400: train RMSE 101.3789, test RMSE 283.7040
Epoch 500: train RMSE 83.0900, test RMSE 257.5325
Epoch 600: train RMSE 66.6143, test RMSE 232.3288
Epoch 700: train RMSE 53.8428, test RMSE 209.1579
Epoch 800: train RMSE 44.4156, test RMSE 188.3802
Epoch 900: train RMSE 37.1839, test RMSE 170.3186
Epoch 1000: train RMSE 32.0921, test RMSE 154.4092
Epoch 1100: train RMSE 29.0402, test RMSE 141.6920
Epoch 1200: train RMSE 26.9721, test RMSE 131.0108
Epoch 1300: train RMSE 25.7398, test RMSE 123.2518
Epoch 1400: train RMSE 24.8011, test RMSE 116.7029
Epoch 1500: train RMSE 24.7705, test RMSE 112.1551
Epoch 1600: train RMSE 24.4654, test RMSE 108.1879
Epoch 1700: train RMSE 25.1378, test RMSE 105.8224
Epoch 1800: train RMSE 24.1940, test RMSE 101.4219
Epoch 1900: train RMSE 23.4605, test RMSE 100.1780

预计测试集的 RMSE 会大一个数量级。RMSE 为 100 意味着预测值与实际目标值的平均偏差为 100(即,该数据集中 100,000 名乘客)。

为了更好地理解预测质量,你确实可以使用 matplotlib 绘制输出,如下所示:

with torch.no_grad():
    # shift train predictions for plotting
    train_plot = np.ones_like(timeseries) * np.nan
    y_pred = model(X_train)
    y_pred = y_pred[:, -1, :]
    train_plot[lookback:train_size] = model(X_train)[:, -1, :]
    # shift test predictions for plotting
    test_plot = np.ones_like(timeseries) * np.nan
    test_plot[train_size+lookback:len(timeseries)] = model(X_test)[:, -1, :]
# plot
plt.plot(timeseries, c='b')
plt.plot(train_plot, c='r')
plt.plot(test_plot, c='g')
plt.show()

从上图,你将模型的输出作为 y_pred,但只提取最后一个时间步的数据作为 y_pred[:, -1, :]。这就是图表上绘制的内容。

训练集用红色绘制,而测试集用绿色绘制。蓝色曲线表示实际数据的样子。你可以看到模型对训练集拟合良好,但对测试集的效果不是很好。

综合来看,以下是完整代码,除了参数 lookback 本次设置为 4:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

df = pd.read_csv('airline-passengers.csv')
timeseries = df[["Passengers"]].values.astype('float32')

# train-test split for time series
train_size = int(len(timeseries) * 0.67)
test_size = len(timeseries) - train_size
train, test = timeseries[:train_size], timeseries[train_size:]

def create_dataset(dataset, lookback):
    """Transform a time series into a prediction dataset

    Args:
        dataset: A numpy array of time series, first dimension is the time steps
        lookback: Size of window for prediction
    """
    X, y = [], []
    for i in range(len(dataset)-lookback):
        feature = dataset[i:i+lookback]
        target = dataset[i+1:i+lookback+1]
        X.append(feature)
        y.append(target)
    return torch.tensor(X), torch.tensor(y)

lookback = 4
X_train, y_train = create_dataset(train, lookback=lookback)
X_test, y_test = create_dataset(test, lookback=lookback)

class AirModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=50, num_layers=1, batch_first=True)
        self.linear = nn.Linear(50, 1)
    def forward(self, x):
        x, _ = self.lstm(x)
        x = self.linear(x)
        return x

model = AirModel()
optimizer = optim.Adam(model.parameters())
loss_fn = nn.MSELoss()
loader = data.DataLoader(data.TensorDataset(X_train, y_train), shuffle=True, batch_size=8)

n_epochs = 2000
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    if epoch % 100 != 0:
        continue
    model.eval()
    with torch.no_grad():
        y_pred = model(X_train)
        train_rmse = np.sqrt(loss_fn(y_pred, y_train))
        y_pred = model(X_test)
        test_rmse = np.sqrt(loss_fn(y_pred, y_test))
    print("Epoch %d: train RMSE %.4f, test RMSE %.4f" % (epoch, train_rmse, test_rmse))

with torch.no_grad():
    # shift train predictions for plotting
    train_plot = np.ones_like(timeseries) * np.nan
    y_pred = model(X_train)
    y_pred = y_pred[:, -1, :]
    train_plot[lookback:train_size] = model(X_train)[:, -1, :]
    # shift test predictions for plotting
    test_plot = np.ones_like(timeseries) * np.nan
    test_plot[train_size+lookback:len(timeseries)] = model(X_test)[:, -1, :]
# plot
plt.plot(timeseries)
plt.plot(train_plot, c='r')
plt.plot(test_plot, c='g')
plt.show()

运行上述代码将产生下面的图表。从打印出的 RMSE 度量和图表中,你可以注意到模型现在在测试集上的表现有所改善。

这也是为什么 create_dataset() 函数是这样设计的原因:当模型接收到时间序列从时间 ttt+3t+3(如 lookback=4),其输出是对 t+1t+1t+4t+4 的预测。然而,t+1t+1t+3t+3 也从输入中得知。通过在损失函数中使用这些数据,模型实际上获得了更多的线索进行训练。这种设计并不总是适用,但你可以看到在这个特定的例子中是有帮助的。

进一步阅读

本节提供了更多关于该主题的资源,如果你想深入了解。

总结

在这篇文章中,你发现了什么是 LSTM 以及如何在 PyTorch 中使用它进行时间序列预测。具体来说,你学到了:

  • 什么是国际航空乘客时间序列预测数据集

  • 什么是 LSTM 单元

  • 如何创建用于时间序列预测的 LSTM 网络

在 PyTorch 中进行线性预测

原文:machinelearningmastery.com/making-linear-predictions-in-pytorch/

线性回归是一种统计技术,用于估计两个变量之间的关系。线性回归的一个简单示例是根据某人的体重的平方根预测其身高(这也是 BMI 的基础)。为此,我们需要找到直线的斜率和截距。斜率是一个变量随另一个变量变化一个单位时的变化量。截距是我们直线与yy轴的交点。

让我们以简单的线性方程 y=wx+by=wx+b 为例。输出变量是 yy,输入变量是 xx。方程的斜率和 yy 截距由字母 wwbb 表示,因此称它们为方程的参数。知道这些参数可以让你预测任何给定 xx 值的结果 yy

既然你已经学会了简单线性回归的一些基础知识,让我们尝试在 PyTorch 框架中实现这个有用的算法。在这里,我们将重点关注以下几点:

  • 什么是线性回归,它如何在 PyTorch 中实现。

  • 如何在 PyTorch 中导入线性类并使用它进行预测。

  • 我们如何为线性回归问题构建自定义模块,或者为未来更复杂的模型构建模块。

通过我的书 《深度学习与 PyTorch》 启动你的项目。它提供了 自学教程可运行的代码

那么让我们开始吧!

在 PyTorch 中进行线性预测。

图片由 Daryan Shamkhali 提供。保留所有权利。

概述

本教程分为三部分,它们是

  • 准备张量

  • 使用 PyTorch 的线性类

  • 构建自定义线性类

准备张量

请注意,在本教程中,我们将涵盖只有两个参数的一维线性回归。我们将创建这个线性表达式:

y=3x+1y=3x+1

我们将在 PyTorch 中将参数 wwbb 定义为张量。我们将 requires_grad 参数设置为 True,表示我们的模型需要学习这些参数:

import torch

# defining the parameters 'w' and 'b'
w = torch.tensor(3.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)

在 PyTorch 中,预测步骤称为前向步骤。因此,我们将编写一个函数,使我们能够在任何给定的 xx 值下进行 yy 的预测。

# function of the linear equation for making predictions
def forward(x):
    y_pred = w * x + b
    return y_pred

现在我们已经定义了线性回归函数,让我们在 x=2x=2 处做一个预测。

# let's predict y_pred at x = 2
x = torch.tensor([[2.0]])
y_pred = forward(x)
print("prediction of y at 'x = 2' is: ", y_pred)

这会输出

prediction of y at 'x = 2' is:  tensor([[7.]], grad_fn=<AddBackward0>)

让我们也用多个 xx 输入来评估方程。

# making predictions at multiple values of x
x = torch.tensor([[3.0], [4.0]])
y_pred = forward(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

这会输出

prediction of y at 'x = 3 & 4' is:  tensor([[10.],
        [13.]], grad_fn=<AddBackward0>)

正如你所见,线性方程的函数成功地预测了多个 xx 值的结果。

总结来说,这就是完整的代码。

import torch

# defining the parameters 'w' and 'b'
w = torch.tensor(3.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)

# function of the linear equation for making predictions
def forward(x):
    y_pred = w * x + b
    return y_pred

# let's predict y_pred at x = 2
x = torch.tensor([[2.0]])
y_pred = forward(x)
print("prediction of y at 'x = 2' is: ", y_pred)

# making predictions at multiple values of x
x = torch.tensor([[3.0], [4.0]])
y_pred = forward(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

想开始使用 PyTorch 进行深度学习吗?

现在就报名参加我的免费电子邮件速成课程(包括示例代码)。

点击注册并免费获取课程的 PDF 电子书版本。

使用 PyTorch 中的线性类

要解决实际问题,你需要构建更复杂的模型,为此,PyTorch 带来了许多有用的包,包括线性类,允许我们进行预测。以下是我们如何从 PyTorch 导入线性类模块。我们还将随机初始化参数。

from torch.nn import Linear
torch.manual_seed(42)

请注意,之前我们定义了wwbb的值,但在实践中,它们在启动机器学习算法之前是随机初始化的。

让我们创建一个线性对象模型,并使用parameters()方法访问模型的参数(wwbb)。Linear类使用以下参数初始化:

  • in_features:反映每个输入样本的大小

  • out_features:反映每个输出样本的大小

linear_regression = Linear(in_features=1, out_features=1)
print("displaying parameters w and b: ",
      list(linear_regression.parameters()))

这打印

displaying parameters w and b:  [Parameter containing:
tensor([[0.5153]], requires_grad=True), Parameter containing:
tensor([-0.4414], requires_grad=True)]

同样地,你可以使用state_dict()方法获取包含参数的字典。

print("getting python dictionary: ",linear_regression.state_dict())
print("dictionary keys: ",linear_regression.state_dict().keys())
print("dictionary values: ",linear_regression.state_dict().values())

这打印

getting python dictionary:  OrderedDict([('weight', tensor([[0.5153]])), ('bias', tensor([-0.4414]))])
dictionary keys:  odict_keys(['weight', 'bias'])
dictionary values:  odict_values([tensor([[0.5153]]), tensor([-0.4414])])

现在我们可以重复之前的操作。让我们使用单个xx值进行预测。

# make predictions at x = 2
x = torch.tensor([[2.0]])
y_pred = linear_regression(x)
print("getting the prediction for x: ", y_pred)

这给出了

getting the prediction for x:  tensor([[0.5891]], grad_fn=<AddmmBackward0>)

这对应于0.5153×20.4414=0.58910.5153 \times 2 - 0.4414 = 0.5891。同样地,我们将为多个xx值进行预测。

# making predictions at multiple values of x
x = torch.tensor([[3.0], [4.0]])
y_pred = linear_regression(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

这打印

prediction of y at 'x = 3 & 4' is:  tensor([[1.1044],
        [1.6197]], grad_fn=<AddmmBackward0>)

将所有内容放在一起,完整的代码如下所示

import torch
from torch.nn import Linear

torch.manual_seed(1)

linear_regression = Linear(in_features=1, out_features=1)
print("displaying parameters w and b: ", list(linear_regression.parameters()))
print("getting python dictionary: ",linear_regression.state_dict())
print("dictionary keys: ",linear_regression.state_dict().keys())
print("dictionary values: ",linear_regression.state_dict().values())

# make predictions at x = 2
x = torch.tensor([[2.0]])
y_pred = linear_regression(x)
print("getting the prediction for x: ", y_pred)

# making predictions at multiple values of x
x = torch.tensor([[3.0], [4.0]])
y_pred = linear_regression(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

构建自定义线性类

PyTorch 提供了构建自定义线性类的可能性。在后续教程中,我们将使用这种方法构建更复杂的模型。让我们从 PyTorch 中导入nn模块,以构建自定义线性类。

from torch import nn

PyTorch 中的自定义模块是从nn.Module派生的类。我们将构建一个简单线性回归的类,并命名为Linear_Regression。这将使它成为nn.Module的子类。因此,所有方法和属性将继承到这个类中。在对象构造函数中,我们将声明输入和输出参数。此外,我们通过调用nn.Module中的线性类来创建一个超级构造函数。最后,在定义类中的前向函数时,我们将从输入样本生成预测。

class Linear_Regression(nn.Module):
    def __init__(self, input_sample, output_sample):        
        # Inheriting properties from the parent calss
        super(Linear_Regression, self).__init__()
        self.linear = nn.Linear(input_sample, output_sample)

    # define function to make predictions
    def forward(self, x):
        output = self.linear(x)
        return output

现在,让我们创建一个简单的线性回归模型。在这种情况下,它将仅是一条线的方程。为了检查,让我们也打印出模型参数。

model = Linear_Regression(input_sample=1, output_sample=1)
print("printing the model parameters: ", list(model.parameters()))

这打印

printing the model parameters:  [Parameter containing:
tensor([[-0.1939]], requires_grad=True), Parameter containing:
tensor([0.4694], requires_grad=True)]

就像我们在教程的早期会话中所做的那样,我们将评估我们的自定义线性回归模型,并尝试为单个和多个输入xx进行预测。

x = torch.tensor([[2.0]])
y_pred = model(x)
print("getting the prediction for x: ", y_pred)

这打印

getting the prediction for x:  tensor([[0.0816]], grad_fn=<AddmmBackward0>)

这对应于0.1939×2+0.4694=0.0816-0.1939 \times 2 + 0.4694 = 0.0816。正如你所看到的,我们的模型能够预测结果,并且结果是一个张量对象。同样地,让我们尝试为多个xx值获取预测。

x = torch.tensor([[3.0], [4.0]])
y_pred = model(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

这打印

prediction of y at 'x = 3 & 4' is:  tensor([[-0.1122],
        [-0.3061]], grad_fn=<AddmmBackward0>)

因此,该模型也适用于多个xx值。

将所有内容放在一起,以下是完整的代码

import torch
from torch import nn

torch.manual_seed(42)

class Linear_Regression(nn.Module):
    def __init__(self, input_sample, output_sample):
        # Inheriting properties from the parent calss
        super(Linear_Regression, self).__init__()
        self.linear = nn.Linear(input_sample, output_sample)

    # define function to make predictions
    def forward(self, x):
        output = self.linear(x)
        return output

model = Linear_Regression(input_sample=1, output_sample=1)
print("printing the model parameters: ", list(model.parameters()))

x = torch.tensor([[2.0]])
y_pred = model(x)
print("getting the prediction for x: ", y_pred)

x = torch.tensor([[3.0], [4.0]])
y_pred = model(x)
print("prediction of y at 'x = 3 & 4' is: ", y_pred)

总结

在本教程中,我们讨论了如何从头开始构建神经网络,从一个简单的线性回归模型开始。我们探索了在 PyTorch 中实现简单线性回归的多种方法。特别是,我们学到了:

  • 什么是线性回归,以及如何在 PyTorch 中实现它。

  • 如何在 PyTorch 中导入线性类并用它进行预测。

  • 如何为线性回归问题构建自定义模块,或者为将来更复杂的模型构建准备。

使用 PyTorch 中的逻辑回归进行预测

原文:machinelearningmastery.com/making-predictions-with-logistic-regression-in-pytorch/

逻辑回归是一种用于建模事件概率的统计技术。它常用于机器学习中进行预测。当需要预测分类结果时,我们应用逻辑回归。

在 PyTorch 中,逻辑回归的构建类似于线性回归。它们都应用于线性输入。但逻辑回归专门用于分类问题,例如将数据分类为两种结果之一(0 或 1)。

在本教程中,我们将重点介绍如何使用逻辑回归进行预测。我们将学习如何利用 PyTorch 库中的一些有用包轻松创建逻辑回归模型。特别是,我们将学习:

  • 如何使用 PyTorch 中的逻辑回归进行预测。

  • 逻辑函数及其在张量上的实现。

  • 如何通过nn.Sequential构建逻辑回归模型。

  • 如何构建用于逻辑回归的自定义模块。

启动你的项目,参考我的书籍 《深度学习与 PyTorch》。它提供了自学教程可运行代码

让我们开始吧。

使用 PyTorch 中的逻辑回归进行预测。

图片由 Manson Yim 提供。版权所有。

概述

本教程分为四部分;它们是

  • 创建数据类

  • 使用nn.Module构建模型

  • 使用小批量梯度下降进行训练

  • 绘制进度图

什么是逻辑函数?

当数据集中某一点的类别使用线性函数计算时,我们得到一个正数或负数,如3-32244等。当我们构建分类器,尤其是二分类器时,我们希望它能返回 0 或 1。在这种情况下,可以使用 sigmoid 或逻辑函数,因为该函数总是返回 0 到 1 之间的值。通常,我们会设置一个阈值,如 0.5,将结果四舍五入以确定输出类别。

在 PyTorch 中,逻辑函数由nn.Sigmoid()方法实现。让我们使用 PyTorch 中的range()方法定义一个张量,并应用逻辑函数以观察输出。

import torch
torch.manual_seed(42)

xrange = torch.range(-50, 50, 0.5)
sig_func = torch.nn.Sigmoid()
y_pred = sig_func(xrange)

让我们看看图像的样子。

import matplotlib.pyplot as plt

plt.plot(xrange.numpy(), y_pred.numpy())
plt.xlabel('range')
plt.ylabel('y_pred')
plt.show()

逻辑函数

如图所示,逻辑函数的值范围在 0 和 1 之间,过渡发生在 0 附近。

想开始使用 PyTorch 进行深度学习?

立即参加我的免费电子邮件速成课程(包含示例代码)。

点击注册,并免费获得课程的 PDF 电子书版。

通过nn.Sequential构建逻辑回归模型

PyTorch 中的nn.Sequential包使我们能够构建逻辑回归模型,就像我们可以构建线性回归模型一样。我们只需定义一个输入张量并通过模型处理它。

让我们定义一个逻辑回归模型对象,该对象接受一维张量作为输入。

...
log_regr = torch.nn.Sequential(torch.nn.Linear(1, 1), torch.nn.Sigmoid())

这个模型包含一个线性函数层。线性函数的输出传递给逻辑函数进行预测。

我们可以使用parameters()方法检查模型参数的列表。这些参数在此情况下应被随机初始化,但我们可以看到它们的形状与我们在模型中指定的形状一致。

...
print(list(log_regr.parameters()))

输出结果如下所示。

[Parameter containing:
tensor([[0.7645]], requires_grad=True), Parameter containing:
tensor([0.8300], requires_grad=True)]

现在,让我们定义一个一维张量x,并使用我们的逻辑回归模型进行预测。

x = torch.tensor([[1], [2], [3], [4]], dtype=torch.float32)

我们将张量强制设置为float32类型,因为这是我们的模型所期望的。将这些数据样本输入模型后,我们将得到以下预测结果。

y_pred = log_regr(x)
print("here is model prediction: ", y_pred)

它的输出如下:

here is model prediction:  tensor([[0.8313],
        [0.9137],
        [0.9579],
        [0.9799]], grad_fn=<SigmoidBackward0>)

把所有内容整合在一起,以下是完整的代码:

import matplotlib.pyplot as plt
import torch
torch.manual_seed(42)

xrange = torch.range(-50, 50, 0.5)
sig_func = torch.nn.Sigmoid()
y_pred = sig_func(xrange)
plt.plot(xrange.numpy(), y_pred.numpy())
plt.xlabel('range')
plt.ylabel('y_pred')
plt.show()

log_regr = torch.nn.Sequential(torch.nn.Linear(1, 1), torch.nn.Sigmoid())
print(list(log_regr.parameters()))

x = torch.tensor([[1], [2], [3], [4]], dtype=torch.float32)
y_pred = log_regr(x)
print("here is model prediction: ", y_pred)

自定义逻辑回归模块

了解如何构建自定义模块在处理高级深度学习解决方案时是必要的。我们可以尝试语法并构建我们自定义的逻辑回归模块。它应与上面的nn.Sequential模型完全相同。

我们将定义类并继承nn.Module包中的所有方法和属性。在类的forward()函数中,我们将使用sigmoid()方法,该方法接受来自类的线性函数的输出并进行预测。

# build custom module for logistic regression
class LogisticRegression(torch.nn.Module):    
    # build the constructor
    def __init__(self, n_inputs):
        super(LogisticRegression, self).__init__()
        self.linear = torch.nn.Linear(n_inputs, 1)

    # make predictions
    def forward(self, x):
        y_pred = torch.sigmoid(self.linear(x))
        return y_pred

我们可以实例化这个类对象。

...
log_regr_cus = LogisticRegression(1)

现在,让我们对我们上面定义的张量x进行预测。

...
y_pred = log_regr_cus(x)
print("here is model prediction: ", y_pred)

输出将是:

here is model prediction:  tensor([[0.6647],
        [0.6107],
        [0.5537],
        [0.4954]], grad_fn=<SigmoidBackward0>)

如你所见,我们自定义的逻辑回归模型的工作方式与上面的nn.Sequential版本完全相同。

把所有内容整合在一起,以下是完整的代码:

import torch
torch.manual_seed(42)

# build custom module for logistic regression
class LogisticRegression(torch.nn.Module):    
    # build the constructor
    def __init__(self, n_inputs):
        super(LogisticRegression, self).__init__()
        self.linear = torch.nn.Linear(n_inputs, 1)

    # make predictions
    def forward(self, x):
        y_pred = torch.sigmoid(self.linear(x))
        return y_pred

x = torch.tensor([[1], [2], [3], [4]], dtype=torch.float32)
log_regr_cus = LogisticRegression(1)
y_pred = log_regr_cus(x)
print("here is model prediction: ", y_pred)

总结

在本教程中,你学习了逻辑回归的一些基础知识以及如何在 PyTorch 中实现它。特别是,你学习了:

  • 如何在 PyTorch 中使用逻辑回归进行预测。

  • 关于逻辑函数及其在张量上的实现。

  • 如何使用nn.Sequential构建逻辑回归模型。

  • 如何构建自定义的逻辑回归模块。