提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
本文是基于《Pytorch深度学习实战》一书第六章的内容所整理的学习笔记
相关代码的解释以及对应的拓展。
本文使用的代码均基于jupyter
深度学习的核心是神经网络:一种能够通过简单函数的组合来表示复杂函数的数学实体
这些复杂函数的基本构件是神经元,其核心就是输入的线性变换然后应用一个固定的非线性函数,即激活函数。
从数学上将,我们可以把它写成 o = f(wx+b),其中x是输入,w是权重或比例因子,b是偏置或偏移量,f是激活函数。通常,x和o可以是简单的标量,或向量值,w可以是单个标量或矩阵,而b是标量或向量。在后一种情况下,前面的表达式被称为一层神经元。
我们之前的线性模型和我们将实际用于深度学习的模型之间的一个重要区别是误差函数的形状。我们的线性模型和误差平方损失函数有一条凸误差曲线,它有一个奇异的、明确定义的最小值。如果采用其他方法,则可以自动、确定地求出使误差函数的值最小的参数,这意味着我们的参数更新试图尽可能估计那个奇异的正确答案。
即使使用相同的误差平方损失函数,神经网络也不具有与凸误差曲面相同的特性!对于我们试图近似的每一个参数,都没有单一的正确答案。相反,我们试图让所有的参数,当协同作用时,产生一个有用的输出。由于这个有用的输出只是接近事实,所以会有一定程度的不完美。
激活函数的2个重要作用:
激活函数有如下特性:
函数的真实情况:
通常情况下,激活函数至少有以下一种特征
组成的强大机制:
在—个由线性+激活单元构成的网络中,当不同的输入呈现给网络时,不同的单元会对相同的输入在不同范围内响应;与这些输入相关的错误将主要影响在敏感区域工作的神经元,使其他单元不受学习过程的影响。此外,由于在敏感范围内,激活对其输入的导数通常接近于1,因此通过梯度下降估计在该范围内运行的单元的线性变换参数将与我们之前看到的线性拟合非常相似。
PyTorch有一个专门用于神经网络的子模块,叫做torch.nn,它包含创建各种神经网络结构所需的构建块,这样的构建块通常称为层。一个模块可以有一个或多个参数实例作为属性,这些参数实例是张量,它们的值在训练过程中得到了优化。一个模块还可以有一个或多个子模块作为属性,并且还能够追踪到它们的参数。
%matplotlib inline
import numpy as np
import torch
import torch.optim as optim
torch.set_printoptions(edgeitems=2, linewidth=75)
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c).unsqueeze(1)
t_u = torch.tensor(t_u).unsqueeze(1)
t_u.shape
n_samples = t_u.shape[0]
n_val = int(0.2 * n_samples)
shuffled_indices = torch.randperm(n_samples)
train_indices = shuffled_indices[:-n_val]
val_indices = shuffled_indices[-n_val:]
train_indices, val_indices
t_u_train = t_u[train_indices]
t_c_train = t_c[train_indices]
t_u_val = t_u[val_indices]
t_c_val = t_c[val_indices]
t_un_train = 0.1 * t_u_train
t_un_val = 0.1 * t_u_val
构造函数n n.Linear接收3个参数:输入特征的数量,输出特征的数量,以及线性模型是否包含偏置(默认为True)
import torch.nn as nn
linear_model = nn.Linear(1, 1)
linear_model(t_un_val)
一个输入和一个输出特征,这只需要一个权重和一个偏置
linear_model.weight
linear_model.bias
x = torch.ones(1)
linear_model(x)
创建一个大小为BxNin的输入张量,其中B是批次的大小,Nin为输入特征的数量
x = torch.ones(10, 1)
linear_model(x)
进行批处理的原因:
linear_model = nn.Linear(1, 1)
optimizer = optim.SGD(
linear_model.parameters(), # 此方法替换params
lr=1e-2)
可以使用parameters()方法来访问任何nn.Module或它的子模块拥有的参数列表
linear_model.parameters()
list(linear_model.parameters())
此调用会递归到模块的构造函数__init__,并返回遇到的所有参数的简单列表。
优化器提供了一个用 requires_ grad=True 定义的张量列表,所有参数都是这样定义的,因为它们需要通过梯度下降进行优化。在调用 training_oss.backward()时,grad 在图的叶节点上累加,叶节点正是传递给优化器的参数。
def loss_fn(t_p, t_c):
squared_diffs = (t_p - t_c)**2
return squared_diffs.mean()
def training_loop(n_epochs, optimizer, model, loss_fn, t_u_train, t_u_val,
t_c_train, t_c_val):
for epoch in range(1, n_epochs + 1):
t_p_train = model(t_u_train)
loss_train = loss_fn(t_p_train, t_c_train)
t_p_val = model(t_u_val)
loss_val = loss_fn(t_p_val, t_c_val)
optimizer.zero_grad()
loss_train.backward()
optimizer.step()
if epoch == 1 or epoch % 1000 == 0:
print(f"Epoch {epoch}, Training loss {loss_train.item():.4f},"
f" Validation loss {loss_val.item():.4f}")
优化器必须按顺序执行的三个步骤
linear_model = nn.Linear(1, 1)
optimizer = optim.SGD(linear_model.parameters(), lr=1e-2)
training_loop(
n_epochs = 3000,
optimizer = optimizer,
model = linear_model,
loss_fn = nn.MSELoss(), # 即均方误差函数
t_u_train = t_un_train,
t_u_val = t_un_val,
t_c_train = t_c_train,
t_c_val = t_c_val)
print()
print(linear_model.weight)
print(linear_model.bias)
构建一个最简单的神经网络:一个线性模块,然后是一个激活函数,输入到另一个线性模块
第一个线性模型+激活层通常被称为隐藏层,因为它的输出并不是直接能够观察到的。而是被输入到下一层。
nn提供了一种通过nn.Sequential容器来连接模型的方式
seq_model = nn.Sequential(
nn.Linear(1, 13), # 我们随便指定输出张量的大小为13,我们是想该张量的大小与其他张量的形状大小不一样
nn.Tanh(),
nn.Linear(13, 1)) # 这里的张量形状大小值13必须与前一个模块输出张量的大小相等
seq_model
[param.shape for param in seq_model.parameters()]
通过名称识别参数
for name, param in seq_model.named_parameters():
print(name, param.shape)
使用OrderedDict确保层的顺序,并用它来命名传递给Sequential的每个模块
from collections import OrderedDict
seq_model = nn.Sequential(OrderedDict([
('hidden_linear', nn.Linear(1, 8)),
('hidden_activation', nn.Tanh()),
('output_linear', nn.Linear(8, 1))
]))
seq_model
为子模块获取更多解释性名词
for name, param in seq_model.named_parameters():
print(name, param.shape)
访问特定参数
seq_model.output_linear.bias
进行一次训练循环,输出最后一个迭代周期之后的梯度结果
optimizer = optim.SGD(seq_model.parameters(), lr=1e-3) # 为了提高稳定性,我们降低了学习率
training_loop(
n_epochs = 5000,
optimizer = optimizer,
model = seq_model,
loss_fn = nn.MSELoss(),
t_u_train = t_un_train,
t_u_val = t_un_val,
t_c_train = t_c_train,
t_c_val = t_c_val)
print('output', seq_model(t_un_val))
print('answer', t_c_val)
print('hidden', seq_model.hidden_linear.weight.grad)
from matplotlib import pyplot as plt
t_range = torch.arange(20., 90.).unsqueeze(1)
fig = plt.figure(dpi=600)
plt.xlabel("Fahrenheit")
plt.ylabel("Celsius")
plt.plot(t_u.numpy(), t_c.numpy(), 'o')
plt.plot(t_range.numpy(), seq_model(0.1 * t_range).detach().numpy(), 'c-')
plt.plot(t_u.numpy(), seq_model(0.1 * t_u).detach().numpy(), 'kx')
本文主要讲解了: