线性神经网络
线性回归
概括
- 机器学习模型中的关键元素是训练数据、损失函数、优化算法和模型本身。
- 矢量化使数学变得更简单、更快。
- 最小化目标函数相当于执行最大似然估计。
- 线性回归模型也是一个简单的神经网络。
锻炼
- 假设我们有一些数据
。我们的目标是找到一个使
最小化的常数
。
- 求最优值
的解析解。
- 这个问题及其解决方案与正态分布有何关系?
- 推导出使用平方误差的线性回归优化问题的解析解。为了简化问题,可以忽略偏置
(我们可以通过向
添加所有值为1的一列来做到这一点)。
- 用矩阵和向量表示法写出优化问题(将所有数据视为单个矩阵,将所有目标值视为单个向量)。
- 计算损失对⒊的梯度。
- 通过将梯度设为0、求解矩阵方程来找到解析解。
- 什么时候比使用随机梯度下降更好?这种方法什么时候会失败?
- 假设控制附加噪声
的噪声模型呈指数分布。也就是说,
- 写出模型
下数据的负对数似然。
- 你能写出解析解吗?
- 为了解决这个问题,提出了一种随机梯度下降算法。我哪里错了? (提示:当我们不断更新参数时,停滞点附近会发生什么)你能解决这个问题吗?
可以使用SmoothL1Loss
从零开始实现线性回归
概括
- 我们了解了如何实现和优化深度网络。在这个过程中只使用了张量和自动微分,不需要定义层或复杂的优化器。
- 本节仅触及表面。在接下来的部分中,我们将基于刚刚介绍的概念描述其他模型,并学习如何更简洁地实现它们。
锻炼
- 如果我们将权重初始化为零会发生什么。算法还能用吗?
- 假设你是 George Simon Ohm,他试图为电压和电流之间的关系建立一个模型。能不能用自动微分来学习模型的参数?
- 你能根据普朗克定律使用光谱能量密度来确定物体的温度吗?
- 如果要计算二阶导数,可能会遇到什么问题?你会如何解决这些问题?
- 为什么在 squared_loss 函数中需要使用 reshape 函数?
- 尝试不同的学习率,观察损失函数值下降的速度。
- 如果样本个数不能被批量大小整除, data_iter 函数的行为会有什么变化?
1 . 将权重初始化为零,算法依然有效。但网络层数加深后,在全连接的情况下,反向传播时,由于权重的对称性会导致出现隐藏神经元的对称性,是的多个隐藏神经元的作用就如同一个神经元,影响算法效果。
2.
import torch
import random
from d2l import torch as d2l
#生成数据集
def synthetic_data(r, b, num_examples):
I = torch.normal(0, 1, (num_examples, len(r)))
u = torch.matmul(I, r) + b
u += torch.normal(0, 0.01, u.shape) # 噪声
return I, u.reshape((-1, 1)) # 标量转换为向量
true_r = torch.tensor([20.0])
true_b = 0.01
features, labels = synthetic_data(true_r, true_b, 1000)
#读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices],labels[batch_indices]
batch_size = 10
# 初始化权重
r = torch.normal(0,0.01,size = ((1,1)), requires_grad = True)
b = torch.zeros(1, requires_grad = True)
# 定义模型
def linreg(I, r, b):
return torch.matmul(I, r) + b
# 损失函数
def square_loss(u_hat, u):
return (u_hat - u.reshape(u_hat.shape)) ** 2/2
# 优化算法
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad/batch_size
param.grad.zero_()
lr = 0.03
num_epochs = 10
net = linreg
loss = square_loss
for epoch in range(num_epochs):
for I, u in data_iter(batch_size, features, labels):
l = loss(net(I, r, b), u)
l.sum().backward()
sgd([r, b], lr, batch_size)
with torch.no_grad():
train_l = loss(net(features, r, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
print(r)
print(b)
print(f'r的估计误差: {true_r - r.reshape(true_r.shape)}')
print(f'b的估计误差: {true_b - b}')
epoch 1, loss 0.329473
epoch 2, loss 0.000541
epoch 3, loss 0.000050
epoch 4, loss 0.000050
epoch 5, loss 0.000050
epoch 6, loss 0.000050
epoch 7, loss 0.000050
epoch 8, loss 0.000050
epoch 9, loss 0.000050
epoch 10, loss 0.000050
tensor([[19.9997]], requires_grad=True)
tensor([0.0093], requires_grad=True)
r的估计误差: tensor([0.0003], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0007], grad_fn=<RsubBackward1>)
3 . 尝试写了一下,但好像出错了,结果loss = nan
# 3
# 普朗克公式
# x:波长
# T:温度
import torch
import random
from d2l import torch as d2l
#生成数据集
def synthetic_data(x, num_examples):
T = torch.normal(0, 1, (num_examples, len(x)))
u = c1 / ((x ** 5) * ((torch.exp(c2 / (x * T))) - 1));
u += torch.normal(0, 0.01, u.shape) # 噪声
return T, u.reshape((-1, 1)) # 标量转换为向量
c1 = 3.7414*10**8 # c1常量
c2 = 1.43879*10**4 # c2常量
true_x = torch.tensor([500.0])
features, labels = synthetic_data(true_x, 1000)
#读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices],labels[batch_indices]
batch_size = 10
# 初始化权重
x = torch.normal(0,0.01,size = ((1,1)), requires_grad = True)
# 定义模型
def planck_formula(T, x):
return c1 / ((x ** 5) * ((torch.exp(c2 / (x * T))) - 1))
# 损失函数
def square_loss(u_hat, u):
return (u_hat - u.reshape(u_hat.shape)) ** 2/2
# 优化算法
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad/batch_size
param.grad.zero_()
lr = 0.001
num_epochs = 10
net = planck_formula
loss = square_loss
for epoch in range(num_epochs):
for T, u in data_iter(batch_size, features, labels):
l = loss(net(T, x), u)
l.sum().backward()
sgd([x], lr, batch_size)
with torch.no_grad():
train_l = loss(net(features, x), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
print(f'r的估计误差: {true_x - x.reshape(true_x.shape)}')
- 二阶导数可能不存在,或无法得到显式的一阶导数。可以在求一阶导数时使用retain_graph=True参数保存计算图,进而求二阶导。
- y_hat和y的维度可能不相同,分别为行向量和列向量,维度(n,1)(n, )
- 学习率越大,损失函数下降得越快。但是,如果学习率太大,可能会导致无法收敛。
learning rate = 0.03
epoch 1, loss 0.039505
epoch 2, loss 0.000141
epoch 3, loss 0.000048
learning rate = 0.05
epoch 1, loss 0.000576
epoch 2, loss 0.000052
epoch 3, loss 0.000052
learning rate = 0.5
epoch 1, loss 0.000057
epoch 2, loss 0.000053
epoch 3, loss 0.000051
- 设置了 indices[i: min(i + batch_size, num_examples)] ,样本个数不能被批量大小整除不会导致 data_iter 变化,不设置的话可能会报错。
线性回归的简洁实现
概括
- 我们可以使用PyTorch的高级API更简洁地实现模型。
- 在PyTorch中, data 模块提供了数据处理工具, nn 模块定义了大量的神经网络层和常见损失函数。
- 我们可以通过将参数替换为以_结尾的方法来初始化参数。
锻炼
- 如果用 mini-batch 损失的均值代替 mini-batch 的总损失,你需要如何改变学习率?
- 查看深度学习框架文档,它们提供了哪些损失函数和初始化方法?用Huber损失代替原损失,即
- 你如何访问线性回归的梯度?
1 . 将学习率缩小为之前的1/n
2 . 部分loss function
将损失函数更改为HuberLoss(仅1.9.0及以上版本的pytorch才有HuberLoss)
#%%
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
#%%
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
def load_array(data_arrays, batch_size, is_train = True): #@save
'''pytorch数据迭代器'''
dataset = data.TensorDataset(*data_arrays) # 把输入的两类数据一一对应;*表示对list解开入参
return data.DataLoader(dataset, batch_size, shuffle = is_train) # 重新排序
batch_size = 10
data_iter = load_array((features, labels), batch_size) # 和手动实现中data_iter使用方法相同
#%%
# 构造迭代器并验证data_iter的效果
next(iter(data_iter)) # 获得第一个batch的数据
#%% 定义模型
from torch import nn
net = nn.Sequential(nn.Linear(2, 1)) # Linear中两个参数一个表示输入形状一个表示输出形状
# sequential相当于一个存放各层数据的list,单层时也可以只用Linear
#%% 初始化模型参数
# 使用net[0]选择神经网络中的第一层
net[0].weight.data.normal_(0, 0.01) # 正态分布
net[0].bias.data.fill_(0)
#%% 定义损失函数
loss = torch.nn.HuberLoss()
#%% 定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03) # optim module中的SGD
#%% 训练
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)
3 . 线性回归的梯度
print(net[0].weight.grad)
print(net[0].bias.grad)
softmax回归
概括
- softmax运算获取一个向量并将其映射为概率。
- softmax回归适用于分类问题,它使用了softmax运算中输出类别的概率分布。
- 衡量两个概率分布之间差异的一个很好的方法是交叉熵,它衡量为给定模型对数据进行编码所需的位数。
锻炼
- 我们可以更深入地探讨指数族与softmax之间的联系。
- 计算softmax交叉熵损失
的二阶导数。
- 计算由
给出的分布的方差,并匹配上面计算的二阶导数。
- 假设我们有三个发生概率相等的类,即概率向量是
。
- 如果我们尝试为它设计二进制代码,有什么问题?
- 你能设计出更好的代码吗?提示:如果我们尝试编码两个独立的观察结果会发生什么?如果我们联合编码
观察结果会怎样?
- softmax是对上面介绍的映射的误称(虽然深度学习领域中很多人都使用这个名字)。真正的softmax被定义为
。
- 证明
。
- 证明
成立,前提是
。
- 证明对于
,有
。
- soft-min会是什么样子?
- 将其扩展到两个以上的数字。
1 . 不确定答案
2 . 可以使用one-hot编码,需要的元素个数等于label数量
3 .
图像分类数据集
概括
- Fashion-MNIST是一个服装分类数据集,由10个类别的图像组成。我们将在后续章节中使用此数据集来评估各种分类算法。
- 我们将高度为
像素、宽度为
像素的图像的形状表示为
或(
,
)。
- 数据迭代器是提高性能的关键组件。通过依赖良好实现的数据迭代器,利用高性能计算来避免减慢训练过程。
锻炼
- 减少 batch_size (如减少到1)是否会影响读取性能?
- 数据迭代器的性能非常重要。您认为当前的实现是否足够快?探索各种改进方案。
- 查阅框架的在线API文档。还有哪些其他数据集可用?
- 会影响,batch_size越小,读取越慢
- 进程数可调整
- torchvision可用数据集:https://pytorch.org/vision/stable/datasets.html
softmax回归的从零开始实现
概括
- 借助softmax回归,我们可以训练多分类的模型。
- 训练softmax回归循环模型与训练线性回归模型非常相似:先读取数据,再定义模型和损失函数,然后使用优化算法训练模型。大多数常见的深度学习模型都有类似的训练过程。
锻炼
- 在本节中,我们直接实现了基于数学定义softmax运算的 softmax 函数。这可能会导致什么问题?提示:尝试计算
的大小。
- 本节中的函数 cross_entropy 是根据交叉熵损失函数的定义实现的。它可能有什么问题?提示:考虑对数的定义域。
- 你能想到哪些解决方案来解决以上两个问题?
- 返回具有最高概率的类标签总是最好的解决方案吗?例如,您会在医疗诊断场景中这样做吗?
- 假设我们使用softmax回归来预测下一个单词,可选取的单词数目过多可能会带来哪些问题?
- 可能导致的数值稳定性问题
溢出
(overflow
)。如果数值过大,可能会大于数据类型允许的最大数字,这将使分母或分子变为 inf (无穷大),最后得到的是0、 inf 或 nan (不是数字)的
。
- 若最大的
的值极小,接近0,可能会导致
的值过大,超出数据类型的范围。或出现
下溢
(underflow)问题,由于精度限制,四舍五入为0,并且使得
的值为 -inf 。
反向传播几步后,可能会出现 nan 结果。 - 使用LogSumExp技巧。
在继续softmax计算之前,先从所有中减去
。
可以看到每个按常数进行的移动不会改变softmax的返回值:
之后,将softmax和交叉熵结合在一起,如下面的等式所示,我们避免计算,
你可以直接使用,因为
是偏移量。
- 返回具有最高概率的类标签并不总是最好的解决方案。在某些情况下,可能需要输出特定的概率值来辅助判断。
- 可能会造成单词之间概率差距不大,或概率过于接近0的情况。在计算过程中loss也会较高。
softmax回归的简洁实现
概括
- 使用深度学习框架的高级API,我们可以更简洁地实现softmax回归。
- 从计算的角度来看,实现softmax回归比较复杂。在许多情况下,深度学习框架在这些著名的技巧之外采取了额外的预防措施,来确保数值的稳定性。这使我们避免了在实践中从零开始编写模型时可能遇到的陷阱。
锻炼
- 尝试调整超参数,例如批量大小、迭代次数和学习率,并查看结果。
- 增加迭代周期数。为什么一段时间后测试精度会下降?我们如何解决这个问题?
- 可能会出现过拟合。您可以观察准确性的变化。如果精度不断下降,则在合适的位置终止计算。
文章出处登录后可见!
已经登录?立即刷新