Appearance
线性神经网络
线性回归
线性回归的基本元素
线性模型
对线性假设而言,可以通过仿射变换来找到特征和目标之间的关系:
将特征放入向量
而将数据集中的n个样本放入矩阵
为了达到更低的预测误差,需要寻找更好的参数模型(model parameters),这需要:(1)模型质量的度量方式;(2)更新模型以提高预测质量的方法
损失函数
损失函数(loss function)可以量化目标的实际值与预测值之间的差距。以平方误差为例:
其中样本i的预测值为
通过寻找一组参数
解析解
线性回归的解可以用公式表示出来,将偏置
随机梯度下降
其过程可以通过 3B1B 的视频 深度学习之梯度下降法 进行理解,算法步骤为:(1)初始化模型参数的值,如随机初始化;(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。对于平方损失和仿射变换,我们可以明确地写成如下形式:
其中
泛化(generalization)
找到一组参数,能够在我们未见过的数据上实现较低的损失
正态分布与平方损失
正态分布的概率密度为:
其在 Python 中可以可视化表示
Python
import math
import numpy as np
import matplotlib.pyplot as plt
def normal(x, mu, sigma):
p = 1 / math.sqrt(2 * math.pi * sigma**2)
return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)
# 使用 numpy 生成自变量 xx = np.arange(-7, 7, 0.01)
# 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
# 1. 设置画布大小需要使用 plt.figure()plt.figure(figsize=(6, 4)) # 稍微放大了尺寸,(4.5, 2.5) 可能会让图例重叠
# 2. 推荐使用循环逐条画线,并用 label 参数标记,这样后续生成图例最方便
for mu, sigma in params:
y = normal(x, mu, sigma)
plt.plot(x, y, label=f'mean {mu}, std {sigma}')
# 3. 坐标轴标签和图例需要独立设置
plt.xlabel('x')
plt.ylabel('p(x)')
plt.legend()
# 4. 显示图像
plt.show()作者假设对真实世界的观测值
因此
由于n个样本之间互相独立,数据集的联合似然为:
利用
因此在"噪声服从高斯分布"的假设下,最小化均方误差 ⟺ 对线性模型做极大似然估计
从线性回归到深度网络
全连接层(fully‐connectedlayer)或稠密层(denselayer)
每个输入都与每个输出相连
线性回归的基本实现代码如下:
Python
import random
import torch
from d2l import torch as d2l
def synthetic_data(w, b, num_examples): #@save
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) #生成符合标准正态分布(均值为0,方差为1)的特征矩阵X
y = torch.matmul(X, w) + b
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)
print('features:',features[0],'\nlabel:',labels[0])
d2l.set_figsize()
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)
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
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) #随机初始化权重
b = torch.zeros(1, requires_grad=True) #初始化偏置为0
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b #前向传播
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat- y.reshape(y_hat.shape)) ** 2 / 2 #损失函数
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降(优化器)"""
with torch.no_grad(): #关闭pytorch追踪
for param in params:
param-= lr * param.grad / batch_size #平均梯度
param.grad.zero_() #梯度清零
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
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}')线性回归的简洁实现
Python
import numpy as np
import torch
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)
def load_array(data_arrays, batch_size, is_train=True): #@save is_train为是否迭代器在每个周期打乱顺序
"""构造一个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)
next(iter(data_iter))
# print(next(iter(data_iter)))
# 定义模型
net = nn.Sequential(nn.Linear(2, 1))
# 初始化参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# 定义损失函数
loss = nn.MSELoss()
#定义优化算法
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)softmax回归
分类
“硬性”类别:属于哪个类别 “软性”类别:属于每个类别的概率 即使我们只关心硬类别,我们仍然使用软类别的模型。
分类问题
分类数据的简单方法:
独热编码(one‐hotencoding)
独热编码是一个向量,它的分量和类别一样多。类别对 应的分量设置为1,其他所有分量设置为0。
softmax运算
其矢量计算表达式为
损失函数
对数似然
对于索引i的样本,将估计值与实际值进行比较:
利用最大似然估计,最大化
其损失函数为:
该函数也被称为交叉熵损失(cross‐entropyloss)。
softmax及其导数
将 softmax 运算带入损失函数中,通过softmax定义可得:
PS:最后一步是由于对于独热编码元素和为1,即
图像分类数据集
本文与书中一致采用 Fashion‐MNIST 数据集 在使用 softmax 函数时存在上溢(overflow)问题,未解决这个问题需要进行一定的规范化:
对于该式,
这种方法也被称作LogSumExp技巧
Python
loss = nn.CrossEntropyLoss(reduction='none')softmax的实现
Python
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib
matplotlib.use('TkAgg') # 或 'Qt5Agg',看你装了哪个
import matplotlib.pyplot as plt
# from IPython import display
# 数据准备
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # 加载 Fashion-MNIST 数据集
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状,Linear为全连接层
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) # 均值0标准差0,01
net.apply(init_weights)
# 累加器
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]
# 在 Jupyter Notebook 里实时绘制训练曲线
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 = []
self.legend = legend
plt.ion() # 打开交互模式,关键!
self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 保存配置,等下每次重画时用
self.config = dict(xlabel=xlabel, ylabel=ylabel,
xlim=xlim, ylim=ylim,
xscale=xscale, yscale=yscale)
self.X, self.Y, self.fmts = None, None, fmts
def _config_axes(self):
ax = self.axes[0]
ax.set_xlabel(self.config['xlabel'])
ax.set_ylabel(self.config['ylabel'])
ax.set_xscale(self.config['xscale'])
ax.set_yscale(self.config['yscale'])
if self.config['xlim']:
ax.set_xlim(self.config['xlim'])
if self.config['ylim']:
ax.set_ylim(self.config['ylim'])
if self.legend:
ax.legend(self.legend)
ax.grid(True)
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()
self.fig.canvas.draw()
self.fig.canvas.flush_events()
plt.pause(0.01) # 让事件循环跑一下,窗口才会刷新
# 注释部分为Jupyter Notebook版本
# display.display(self.fig)
# display.clear_output(wait=True)
def show(self):
"""训练结束后调用,让窗口保持打开"""
plt.ioff()
plt.show()
# 分类精度
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())
# 训练一个 epochdef train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
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.mean().backward()
updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
# metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
metric.add(l.sum().item(), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
# 在测试集上评估
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
# 完整训练循环
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
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,))
animator.show()
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
if __name__ == '__main__': # 避免在Windows中多线程问题
# 定义损失函数
loss = nn.CrossEntropyLoss(reduction='none')
# 优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
# 训练
num_epochs = 10
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)Tips:正规方程
我们使用残差平方和(RSS)作为损失函数
根据矩阵求导公式
常数项-2约去得:
移项得:
两边左乘
这部分为正规方程(Normal Equation) ↩︎