《动手学深度学习》笔记——深度神经网络 DNN
文章目录
1 线性回归
1.1 线性模型
线性回归(Linear Regression)是对n维输入的加权,外加偏差,可以看做是单层神经网络。
给定 n n n 维特征输入 x ( i ) = [ x 1 , x 2 , ⋯ , x n ] T \mathbf{x}^{(i)} =[x_1,x_2,\cdots,x_n]^\text{T} x(i)=[x1,x2,⋯,xn]T , n n n 维权重 w = [ w 1 , w 2 , ⋯ , w n ] T \mathbf{w} =[w_1,w_2,\cdots,w_n]^\text{T} w=[w1,w2,⋯,wn]T ,标量偏差 b b b ,则预测值 y ^ \hat y y^ 为 y ^ ( i ) = w T x ( i ) + b \hat y^{(i)} =\mathbf{w}^\text{T}\mathbf{x}^{(i)} +b y^(i)=wTx(i)+b对于有多个样本的特征集合 X \mathbf{X} X,预测值 y ^ \mathbf{\hat y} y^ 为 y ^ = X w + b \mathbf{\hat y}=\mathbf{X}\mathbf{w}+b y^=Xw+b
1.2 损失函数:均方差/L2损失
损失函数(Loss Function)能够量化目标的实际值与预测值之间的差距。 通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。
均方差/L2损失(Mean Square Error, L2 Loss)是回归问题中最常用的损失函数。当样本 i i i 的预测值为 y ^ ( i ) \hat y^{(i)} y^(i) ,其相应的真实标签为 y ( i ) y^{(i)} y(i) ,其平方误差为 l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) − y ( i ) ) 2 l^{(i)}(\mathbf{w},b)=\frac12(\hat y^{(i)}-y^{(i)})^2 l(i)(w,b)=21(y^(i)−y(i))2为了度量模型在整个数据集上的质量,需计算在训练集 n n n 个样本上的损失均值,即为均方误差 L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w T x ( i ) + b − y ( i ) ) 2 = 1 2 n ∥ X w + b − y ∥ 2 L(\mathbf{w},b)=\frac1n\sum_{i=1}^n l^{(i)}(\mathbf{w},b)=\frac1n\sum_{i=1}^n\frac12(\mathbf{w}^\text{T}\mathbf{x}^{(i)}+b-y^{(i)})^2=\frac1{2n}\lVert \mathbf{Xw}+b-\mathbf{y}\rVert^2 L(w,b)=n1i=1∑nl(i)(w,b)=n1i=1∑n21(wTx(i)+b−y(i))2=2n1∥Xw+b−y∥2最小化损失来学习参数 w ∗ , b ∗ = arg min w , b L ( w , b ) \mathbf{w}^*,b^*=\argmin_{\mathbf{w},b} L(\mathbf{w},b) w∗,b∗=w,bargminL(w,b)
1.3 解析解
线性回归的解可以用一个公式简单地表达出来, 这类解叫作解析解(Analytical Solution)或显示解。将偏置 b b b 合并到参数 w \mathbf{w} w 中,在包含所有参数的矩阵中附加一列,即 X ← [ X , 1 ] , w ← [ w , b ] T \mathbf{X}\leftarrow [\mathbf{X},\mathbf{1}],\ \mathbf{w}\leftarrow[\mathbf{w},b]^\text{T} X←[X,1], w←[w,b]T则损失函数化可为 L ( w ) = 1 2 n ∥ y − X w ∥ 2 L(\mathbf{w})=\frac1{2n}\lVert \mathbf{y}-\mathbf{Xw} \rVert^2 L(w)=2n1∥y−Xw∥2 ,为凸函数,故最优解 w ∗ \mathbf{w}^* w∗ 满足 ∂ ∂ w L ( w ) = 0 1 n ( y − X w ) T X = 0 w ∗ = ( X T X ) − 1 X T y \begin{align*} \frac{\partial }{\partial \mathbf{w} } L(\mathbf{w})&=0 \\ \frac1n(\mathbf{y}-\mathbf{Xw})^\text{T}\mathbf{X}&=0\\ \mathbf{w}^*&=(\mathbf{X}^\text{T}\mathbf{X})^{-1}\mathbf{X}^\text{T}\mathbf{y} \end{align*} ∂w∂L(w)n1(y−Xw)TXw∗=0=0=(XTX)−1XTy
1.4 基础优化算法:梯度下降
梯度下降(Gradient Descent):初始化模型参数的值(如随机初始化),计算损失函数(损失均值)关于模型参数的导数(梯度),不断地在损失函数递减的方向上更新参数来降低误差,即 ( w , b ) ← ( w , b ) − η ∂ ( w , b ) L ( w , b ) (\mathbf{w},b )\leftarrow(\mathbf{w},b)-η\partial_{(\mathbf{w},b)}L(\mathbf{w},b) (w,b)←(w,b)−η∂(w,b)L(w,b) 小批量随机梯度下降(Minibatch Stochastic Gradient Descent):为深度学习默认的求解方法。原始方法实际执行的效率可能会非常慢,因为在每一次更新参数之前须遍历整个数据集。因此在每次需要计算更新的时候随机抽取一个小批量 B \mathcal{B} B,即 ( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) (\mathbf{w},b )\leftarrow(\mathbf{w},b)-\frac{η}{\mathcal{|B|} }\sum_{i\in \mathcal{B} }\partial_{(\mathbf{w},b)}l^{(i)}(\mathbf{w},b) (w,b)←(w,b)−∣B∣ηi∈B∑∂(w,b)l(i)(w,b)其中 η η η 为学习率(Learning Rate), ∣ B ∣ |\mathcal{B}| ∣B∣ 为批量大小(Batch Size),这种手动预先指定而非训练时调整的参数称为超参数(Hyperparameter)。
1.5 PyTorch实现
import torch
from torch.utils import data
from torch import nn
生成数据集:根据带有噪声的线性模型构造一个人造数据集,这里使用线性模型参数 w = [ 2 , − 3.4 ] T , b = 4.2 \mathbf{w}=[2,-3.4]^\text{T},b=4.2 w=[2,−3.4]T,b=4.2 和噪声项 ϵ \epsilon ϵ 生成数据集及标签 y = X w + b + ϵ \mathbf{y}=\mathbf{Xw}+b+\epsilon y=Xw+b+ϵ
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))
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)
读取数据:调用框架中现有API(DataLoader)来读取数据
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个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)
定义模型:使用框架预定义好的层,并初始化模型参数
net = nn.Sequential(nn.Linear(2, 1))
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
定义损失函数:使用MSELoss
类,也称为平方
L
2
L_2
L2范数,默认情况下,它返回所有样本损失的平均值
loss = nn.MSELoss()
定义优化算法:使用小批量随机梯度下降算法,实例化SGD
实例时要指定优化的参数以及优化算法所需的超参数
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
b = net[0].bias.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
print('b的估计误差:', true_b - b)
2 softmax回归
- 回归估计一个连续值
- 单连续数值输出
- 自然区间 R \mathbb{R} R
- 跟真实值的区别作为损失
- 分类预测一个离散类别
- 通常多个输出
- 输出 i i i 是预测为第 i i i 类的置信度
2.1 分类问题
- 回归(Regression)估计一个连续值
- 单连续数值输出
- 自然区间 R \mathbb{R} R
- 跟真实值的区别作为损失
- 分类(Classification)预测一个离散类别
- 通常多个输出
- 输出 i i i 是预测为第 i i i 类的置信度
独热编码(One-hot Encoding):是一个向量,分量和类别一样多, 类别对应的分量设置为 1 1 1 ,其他所有分量设置为 0 0 0 。假设真实类别为 y y y ,对类别进行一位有效编码,则独热编码为 y = [ y 1 , y 2 , ⋯ , y n ] T , y i = { 1 if i = y 0 otherwise \mathbf{y}=[y_1,y_2,\cdots,y_n]^\text{T},\ y_i=\begin{cases} 1 & \text{ if } i=y \\ 0 & \text{ otherwise } \end{cases} y=[y1,y2,⋯,yn]T, yi={10 if i=y otherwise
2.2 softmax运算
softmax回归也是一个单层神经网络,是一个多类分类问题,需要和未规范化的输出 o = [ o 1 , o 1 , ⋯ , o n ] T \mathbf{o}=[o_1,o_1,\cdots,o_n]^\text{T} o=[o1,o1,⋯,on]T 一样多的仿射函数(Affine Function),每个输出对应的仿射函数及其向量表示分别为 o i = ∑ j x j w i j + b i , i = 1 , 2 , ⋯ , n o = W x + b o_i=\sum_{j} x_jw_{ij}+b_i,\ i=1,2,\cdots,n\\ \mathbf{o}=\mathbf{Wx}+\mathbf{b} oi=j∑xjwij+bi, i=1,2,⋯,no=Wx+b使用softmax函数得到每个类的预测置信度:将输出变换为非负数且总和为1,使得 y ^ = [ y ^ 1 , y ^ 2 , ⋯ , y ^ n ] T \mathbf{\hat y}=[\hat y_1,\hat y_2,\cdots,\hat y_n]^\text{T} y^=[y^1,y^2,⋯,y^n]T 中对于所有 i i i 都有 0 ≤ y ^ i ≤ 1 0≤\hat y_i≤1 0≤y^i≤1,且 ∑ i y i = 1 \sum\limits_i y_i = 1 i∑yi=1,如下式 y ^ = softmax ( o ) where y ^ i = exp ( o i ) ∑ k exp ( o k ) \mathbf{\hat y}=\text{softmax}(\mathbf{o}) \text{ where } \hat y_i=\frac{\exp(o_i)}{\sum\limits_k\exp(o_k)} y^=softmax(o) where y^i=k∑exp(ok)exp(oi)此时 y ^ \mathbf{\hat y} y^ 可视为一个正确的概率分布,因此将具有最大输出值的类别 arg max i y ^ i \argmax\limits_i \hat y_i iargmaxy^i 作为预测 y ^ \hat y y^,即 y ^ = arg max i y ^ i = arg max i o i \hat y=\argmax_i \hat y_i=\argmax_i o_i y^=iargmaxy^i=iargmaxoi
2.3 损失函数:交叉熵损失
交叉熵(Cross-entropy)常用来衡量两个概率的区别 H ( p , q ) = − ∑ i p i log q i H(\mathbf{p},\mathbf{q})=-\sum_i p_i\log q_i H(p,q)=−i∑pilogqi分类问题常将真实概率 y \mathbf{y} y 和预测概率 y ^ \mathbf{\hat y} y^ 的区别作为损失,因此常用交叉熵损失函数,如下式 l ( y , y ^ ) = − ∑ i y i log y ^ i l(\mathbf{y},\mathbf{\hat y})=-\sum_i y_i\log \hat y_i l(y,y^)=−i∑yilogy^i利用softmax的定义,将上式变形、对未规范化的输出 o i o_i oi 求导得 l ( y , y ^ ) = − ∑ i y i log exp ( o i ) ∑ k exp ( o k ) = ∑ i y i log ∑ k exp ( o k ) − ∑ i y i o i = log ∑ k exp ( o k ) − ∑ i y i o i ∂ o i l ( y , y ^ ) = exp ( o i ) ∑ k exp ( o k ) − y i = softmax ( o ) i − y i \begin{align*} l(\mathbf{y},\mathbf{\hat y})= &-\sum_i y_i \log \frac{\exp(o_i)}{\sum\limits_k \exp(o_k)} \\ = &\sum_i y_i \log \sum_k \exp(o_k) -\sum_i y_io_i \\ = & \log \sum_k\exp(o_k)-\sum_iy_io_i \end{align*}\\ \partial_{o_i}l(\mathbf{y},\mathbf{\hat y})=\frac{\exp(o_i)}{\sum_k \exp(o_k)}-y_i=\text{softmax}(\mathbf{o})_i-y_i l(y,y^)===−i∑yilogk∑exp(ok)exp(oi)i∑yilogk∑exp(ok)−i∑yioilogk∑exp(ok)−i∑yioi∂oil(y,y^)=∑kexp(ok)exp(oi)−yi=softmax(o)i−yi其中 softmax ( o ) i \text{softmax}(\mathbf{o})_i softmax(o)i 表示预测 y ^ \mathbf{\hat y} y^ 的第 i i i 个分量(即 y ^ i \hat y_i y^i ,参考softmax定义式)。该梯度正为真实概率与预测概率的区别。
2.4 PyTorch实现——MNIST图像识别
2.4.1 图片分类数据集
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
import matplotlib.pyplot as plt
MNIST数据集是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单,这里使用类似但更复杂的Fashion-MNIST数据集,其包含的10个类别分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。通过框架内置函数下载Fashion-MNIST数据集,包含60000张图的训练集和10000张图的测试集,每张图片形状为 ( 1 , 28 , 28 ) (1,28,28) (1,28,28) 。
读取数据:利用现有API下载并读取Fashion-MNIST数据集,函数返回训练集和验证集的数据迭代器,resize
用来将图像大小调整为另一种形状
def get_dataloader_workers():
"""多进程读取数据"""
return 4
def load_data_fashion_mnist(batch_size, is_train=True, resize=None):
"""下载并读取Fashion-MNIST数据集"""
# ToTensor将图像数据从PIL类型变为32位浮点格式,并除以255使得所有像素值介于0~1
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_data = torchvision.datasets.FashionMNIST(
root="../data", train=is_train, transform=trans, download=True)
data_iter = data.DataLoader(
mnist_data, batch_size, shuffle=True, num_workers=get_dataloader_workers())
return data_iter
batch_size = 32
train_iter = load_data_fashion_mnist(batch_size, is_train=True resize=64)
test_iter = load_data_fashion_mnist(batch_size, is_train=False resize=64)
# 查看形状与类型
for X, y in train_iter:
print(X.shape, X.dtype, y.shape, y.dtype)
break
可视化样本
def get_fashion_mnist_labels(labels):
"""返回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):
"""绘制图像列表"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = plt.subplots(nums_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])
return axes
# 示例
X, y = next(iter(load_data_fashion_mnist(batch_size=18, is_train=True)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y))
2.4.2 分类精度
def accuracy(y_hat, y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1) # 获得每行中最大元素的索引来获得预测类别
compare = y_hat.type(y.dtype) == y # 要正常使用"=="运算需统一类型,将y_hat的数据类型转换为y的数据类型
return float(cmp.type(y.dtype).sum())
class Accumulator:
"""在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]
def evaluate_accuracy(net, data_iter):
"""计算在指定数据集上模型的精度"""
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]
2.4.3 建模与训练
初始化模型参数:softmax回归的输出层是一个全连接层。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) # 对模型所有层操作
定义损失函数:交叉熵损失(整合了softmax操作)
loss = nn.CrossEntropyLoss()
定义优化算法:使用使用学习率为0.1的小批量随机梯度下降
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
训练模型
def train_epoch(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.mean().backward()
updater.step()
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] # 返回训练损失和训练精度
def train(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型"""
for epoch in range(num_epochs):
train_metrics = train_epoch(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert 0.7 < train_acc and train_acc <= 1, train_acc
assert 0.7 < test_acc and test_acc <= 1, test_acc
num_epochs = 10
train(net, train_iter, test_iter, loss, num_epochs, trainer)
预测
def predict(net, test_iter, n=6):
"""预测标签"""
for X, y in test_iter:
break
trues = get_fashion_mnist_labels(y)
preds = get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true + '\n' + pred for true, pred in zip(trues, preds)]
show_images(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
predict(net, test_iter)
3 多层感知机
3.1 基本概念
感知机(Perceptron):是最早的AI模型之一。给定输入 x \mathbf{x} x 、权重 w \mathbf{w} w 、偏移 b b b ,感知机输出 o = σ ( ⟨ w , x ⟩ + b ) , σ ( x ) = { 1 if x > 0 − 1 otherwise o=\sigma(\left \langle \mathbf{w},\mathbf{x}\right \rangle +b),\ \sigma(x)=\begin{cases} 1 & \text{ if } x>0 \\ -1 & \text{ otherwise } \end{cases} o=σ(⟨w,x⟩+b), σ(x)={1−1 if x>0 otherwise 输出为二分类,求解算法等价于使用批量大小为1的梯度下降。不能拟合XOR函数。
多层感知机(Multilayer Perceptron, MLP):在网络中加入一个或多个隐藏层(Hidden Layer),将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。每个隐藏层都有自己的权重 W i \mathbf{W}_i Wi 和偏置 b i \mathbf{b}_i bi,隐藏层数、每层隐藏层大小为超参数。
3.2 常见激活函数
激活函数(Activation Function):用于发挥多层架构的潜力,防止将多层感知机退化成线性模型。
- 修正线性单元(Rectified Linear Unit,ReLU):仅保留正元素并丢弃所有负元素
定义: ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x)=\max(0,x) ReLU(x)=max(0,x)
导数( x = 0 x=0 x=0 时不可导,此时常默认使用左导数): d d x ReLU ( x ) = { 0 if x < 0 1 if x > 0 \frac{\mathrm{d}}{\mathrm{d}x}\text{ReLU}(x)=\begin{cases} 0 & \text{ if } x<0 \\ 1 & \text{ if } x>0 \end{cases} dxdReLU(x)={01 if x<0 if x>0
变体:参数化ReLU(Parameterized ReLU,pReLU)为ReLU添加了一个线性项,即使参数是负的,某些信息仍然可以通过 pReLU ( x ) = max ( 0 , x ) + α min ( 0 , x ) \text{pReLU}(x)=\max(0,x)+\alpha \min(0,x) pReLU(x)=max(0,x)+αmin(0,x) - Sigmoid函数:输入压缩到区间
(
0
,
1
)
(0, 1)
(0,1) 中的某个值,因此通常称为挤压函数(squashing function)
定义: sigmoid ( x ) = 1 1 + exp ( − x ) \text{sigmoid}(x)=\frac1{1+\exp(-x)} sigmoid(x)=1+exp(−x)1
导数: d d x sigmoid ( x ) = exp ( − x ) ( 1 + exp ( − x ) ) 2 = sigmoid ( x ) ( 1 − sigmoid ( x ) ) \frac{\mathrm{d}}{\mathrm{d}x}\text{sigmoid}(x)=\frac{\exp(-x)}{(1+\exp(-x))^2}=\text{sigmoid}(x)(1-\text{sigmoid}(x)) dxdsigmoid(x)=(1+exp(−x))2exp(−x)=sigmoid(x)(1−sigmoid(x))
- 双曲正切函数(tanh):将输入压缩转换到区间
(
−
1
,
1
)
(-1, 1)
(−1,1)上
定义: tanh ( x ) = 1 − exp ( − 2 x ) 1 + exp ( − 2 x ) \tanh(x)=\frac{1-\exp(-2x)}{1+\exp(-2x)} tanh(x)=1+exp(−2x)1−exp(−2x)
导数: d d x tanh ( x ) = 1 − tanh 2 ( x ) \frac{\mathrm{d}}{\mathrm{d}x}\tanh(x)=1-\tanh^2(x) dxdtanh(x)=1−tanh2(x)
3.3 PyTorch实现
使用2.4中的函数,用多层感知机实现MNIST图像识别。与softmax回归相比,唯一的区别是有模型有多个全连接层,其他环节均完全相同。
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weight)
batch_size = 256
lr = 0.1
num_epochs = 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter = load_data_fashion_mnist(batch_size, is_train=True)
test_iter = load_data_fashion_mnist(batch_size, is_train=False)
train(net, train_iter, test_iter, loss, num_epochs, trainer)
3.4 模型选择、欠拟合和过拟合
训练误差(Training Error):模型在训练数据上的误差
泛化误差(Generalization Error):模型在新数据上的误差
验证数据集(Validation Dataset):用来评估模型好坏的数据集(不应跟训练数据混在一起)
测试数据集(Test Dataset):只用一次的数据集
K K K折交叉验证:在没有足够多数据时使用。将训练数据分割成 K K K 块;执行 K K K 次模型训练与验证,每轮迭代使用其中1块作为验证数据集,其余的作为训练数据;最后报告 K K K 个验证集误差的平均值。常取 K = 5 K=5 K=5 或 10 10 10
模型容量(模型复杂性):拟合各种函数的能力,需要匹配数据复杂度(取决于多个重要因素——样本个数、每个样本的元素个数、时间/空间结构、多样性)。
- 低容量的模型难以拟合训练数据——欠拟合(Underfitting)
- 高容量的模型可以记住所有的训练数据——过拟合(Overfitting)。
VC维:统计学习理论的一个核心思想。对于一个分类模型,VC等于一个最大的数据集的大小,不管如何给定标号,都存在一个模型来对它进行完美分类。
【例】2维输入的感知机,VC维=3 —— 能够分类任何3个点,但不是4个(xor)。如下图所示
- 支持 N N N 维输入的感知机的VC维是 N + 1 N+ 1 N+1
- 一些多层感知机的VC维 O ( N log 2 N ) O(N\log_2 N) O(Nlog2N)
- 在深度学习中很少使用,因为衡量不是很准确,且计算深度学习模型的VC维很困难(
3.5 权重衰退
权重衰退(Weight Decay,又称 L 2 L_2 L2 正则化)是最广泛使用的正则化的技术之一,通过 L 2 L_2 L2 正则项使得模型参数不会过大,从而控制模型复杂度。
- 两种方式:
- 硬性限制:直接限制参数值的选择范围 θ \theta θ 来控制模型容量(通常没必要限制偏移 b b b) min l ( w , b ) subject to ∥ w ∥ 2 ≤ θ \min l(\mathbf{w},b) \text{ subject to } \lVert \mathbf{w} \rVert ^2≤\theta minl(w,b) subject to ∥w∥2≤θ越小的 θ \theta θ 意味着更强的正则项
- 柔性限制:对每个 θ \theta θ ,都可以找到 λ \lambda λ 使得之前的目标函数等价于下式 min l ( w , b ) + λ 2 ∥ w ∥ 2 \min l(\mathbf{w},b) + \frac \lambda 2 \lVert \mathbf{w} \rVert ^2 minl(w,b)+2λ∥w∥2超参数 λ \lambda λ 控制了正则项的重要程度: λ = 0 \lambda=0 λ=0 时无作用; λ → ∞ \lambda \rightarrow \infin λ→∞ 时 w ∗ → 0 \mathbf{w}^* \rightarrow \mathbf{0} w∗→0
- 参数更新法则:
- 计算梯度 ∂ ∂ w ( l ( w , b ) + λ 2 ∥ w ∥ 2 ) = ∂ l ( w , b ) ∂ w + λ w \frac{\partial }{\partial \mathbf{w} }(l(\mathbf{w},b)+\frac \lambda 2 \lVert \mathbf{w} \rVert ^2)=\frac{\partial l(\mathbf{w},b)}{\partial \mathbf{w}}+\lambda\mathbf{w} ∂w∂(l(w,b)+2λ∥w∥2)=∂w∂l(w,b)+λw
- 更新参数 w ← ( 1 − η λ ) w − η ∂ l ( w , b ) ∂ w \mathbf{w}\leftarrow (1-\eta \lambda)\mathbf{w}-\eta \frac{\partial l(\mathbf{w},b)}{\partial \mathbf{w}} w←(1−ηλ)w−η∂w∂l(w,b)通常 η λ < 1 \eta \lambda<1 ηλ<1 ,权重乘以 ( 1 − η λ ) (1-\eta \lambda) (1−ηλ) 即为权重衰退
3.6 丢弃法(Dropout)
丟弃法/暂退法(Dropout):在层之间加入噪音,根据超参数丢弃概率
p
p
p (常取0.5, 0.9, …)将一些输出项随机置0来控制模型复杂度。若对
x
\mathbf{x}
x 加入噪音得到
x
′
\mathbf{x'}
x′ ,期望
E
[
x
′
]
=
x
E[\mathbf{x'}]=\mathbf{x}
E[x′]=x ,则对每个元素进行如下扰动
x
i
′
=
{
0
with probability
p
x
i
1
−
p
otherwise
x_i'=\begin{cases} 0 & \text{ with probability } p \\ \frac{x_i}{1-p} & \text{ otherwise } \end{cases}
xi′={01−pxi with probability p otherwise 通常将丟弃法作用在隐藏全连接层的输出上
dropout是正则项,只在训练中使用,影响模型参数的更新。在推理过程中,dropout直接返回输入,这样也能保证确定性的输出。
3.7 数值稳定性和模型初始化
当数值过大或者过小时数值问题,常发生在深度模型中,因为其会对 n n n 个数累乘
- 梯度爆炸(Gradient Exploding):参数更新过大,破坏了模型的稳定收敛
- 值超出值域:对于16位浮点数尤为严重(数值区间
6e-5
~6e4
) - 对学习率敏感,可能需在训练过程中不断调整学习率
- 学习率太大→大参数值→更大的梯度
- 学习率太小→训练无进展
- 值超出值域:对于16位浮点数尤为严重(数值区间
- 梯度消失(Gradient Vanishing): 参数更新过小,在每次更新时几乎不会移动,导致模型无法学习
- 梯度值变成0:对16位浮点数尤为严重
- 训练没有进展,不管如何选择学习率
- 对于底部层尤为严重:仅仅顶部层训练的较好,无法让神经网络更深
让训练更加稳定:
- 让梯度值在合理的范围内。(例如在
[1e-6, 1e3]
上)- 将乘法变加法(ResNet、LSTM)
- 归一化(梯度归一化、梯度裁剪)
- 合理的权重初始和激活函数 * 让每层的方差是一个常数:将每层的输出和梯度都看做随机变量,让它们的均值和方差都保持一致
权重初始化:在合理值区间里随机初始参数。因为训练开始的时候更容易有数值不稳定(远离最优解的地方损失函数表面可能很复杂;最优解附近表面会比较平)。使用 N ( 0 , 0.01 ) N(0,0.01) N(0,0.01) 来初始可能对小网络没问题,但不能保证深度神经网络。
………………
4 深度学习计算
import torch
import torch.nn.functional as F
from torch import nn
4.1 层和块
一个块可以由许多层或块组成。块可以包含代码,负责大量的内部处理,包括参数初始化和反向传播。层和块的顺序连接可以由Sequential块处理。
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
return self.linear(self.net(X))
net = MLP()
y_hat = net(X)
4.2 参数管理
Sequential内部组织
参数访问:Sequential类相当于列表,可以直接用索引访问对应的层。对于嵌套块,因为层是分层嵌套的,所以也可以像通过嵌套列表索引一样访问它们。相关方法和成员参见前文。
# 获取参数的名称及其值的字典
net[2].state_dict() # 返回OrderedDict类型
# 访问参数及其成员
net[2].bias # 获取偏移(包含值和其他信息)
rgnet[0][1][0].bias.data # 获取偏移的值
net[2].weight.grad # 获取权值的梯度
# 一次性访问所有参数
[(name, param.shape) for name, param in net[0].named_parameters()] # 访问0号层的所有参数
[(name, param.shape) for name, param in net.named_parameters()] # 访问整个网络所有层的所有参数
参数初始化:默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。
- 内置初始化:调用PyTorch的
nn.init
模块提供的初始化器# 初始化为标准正态分布 def init_normal(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, mean=0, std=0.01) nn.init.zeros_(m.bias) net.apply(init_normal)
# 初始化为常数1 def init_constant(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 1) nn.init.zeros_(m.bias) net.apply(init_constant)
# 对不同块应用不同的初始化方法:对0号层用Xavier初始化,对2号层初始化为常数42 def init_xavier(m): if type(m) == nn.Linear: nn.init.xavier_uniform_(m.weight) def init_42(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 42) net[0].apply(init_xavier) net[2].apply(init_42)
- 自定义初始化:当深度学习框架没有提供我们需要的初始化方法时可自定义初始化方法。
【例】使用以下的分布为任意权重参数 w w w 定义初始化方法 w ∼ { U ( 5 , 10 ) with probability 1 4 0 with probability 1 2 U ( − 10 , − 5 ) with probability 1 4 w\sim \begin{cases} U(5,10) & \text{ with probability } \frac14 \\ 0 & \text{ with probability } \frac12 \\ U(-10,-5) & \text{ with probability } \frac14 \end{cases} w∼⎩ ⎨ ⎧U(5,10)0U(−10,−5) with probability 41 with probability 21 with probability 41# 自定义初始化 def my_init(m): if type(m) == nn.Linear: print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0]) nn.init.uniform_(m.weight, -10, 10) m.weight.data *= m.weight.data.abs() >= 5 net.apply(my_init)
- 直接初始化!
net[0].weight.data[:] += 1 net[0].weight.data[0, 0] = 42 net[0].weight.data[0]
参数绑定:在Sequential外定义共享层变量,在Sequential中的多个层间共享参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
4.3 自定义层
有时会遇到或要自己发明一个现在在深度学习框架中还不存在的层。 在这些情况下,必须构建自定义层。可以将自定义层像预置层一样作为组件合并到更复杂的模型中。
构造不带参数的层:
class CenteredLayer(nn.Module):
"""从其输入中减去均值的层"""
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
layer = CenteredLayer()
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
构建带参数的层:
class MyLinear(nn.Module):
"""自定义版的全连接层"""
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
linear = MyLinear(5, 3)
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
4.4 读写文件
加载和保存张量
x = torch.arange(4)
y = torch.zeros(4)
mydict = {'x': x, 'y': y}
# 保存或读取张量
torch.save(x, 'x-file')
x2 = torch.load('x-file')
# 保存或读取张量列表
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')
# 保存或读取从字符串映射到张量的字典
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
加载和保存模型参数
net = MLP() # 4.1的MLP网络
# 存储网络:存参数的字典
torch.save(net.state_dict(), 'mlp.params')
# 读取网络(需先定义相同类的网络)
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
标签:mathbf,nn,text,DNN,PyTorch,exp,深度,net,data
From: https://blog.csdn.net/Akira37/article/details/139231263