假设现有一些数据点,我们希望用一条直线对这些点进行拟合(该线成为最佳拟合直线),这个拟合过程就称作回归
模型 对于某一个样本:
给定n n n 维输入:x = [ x 1 , x 2 , . . . , x n ] T \mathbf{x}=[x_1, x_2, ..., x_n]^T x = [ x 1 , x 2 , ... , x n ] T
以及n n n 维权重:w = [ w 1 , w 2 , . . . , w n ] T \mathbf{w}=[w_1, w_2, ..., w_n]^T w = [ w 1 , w 2 , ... , w n ] T 、一个标量偏差b b b
输出是输入的加权和:
y = w 1 x 1 + w 2 x 2 + . . . + w n x n + b y=w_1x_1+w_2x_2+...+w_nx_n+b y = w 1 x 1 + w 2 x 2 + ... + w n x n + b
向量版本:
y = < w , x > + b y=<\mathbf{w},\mathbf{x}>+b y =< w , x > + b
线性模型可看作单层神经网络
衡量预估质量 假设某一样本i i i ,预测值为y ^ \hat{y} y ^ ,而y y y 是真实标签,则平方误差:
l ( i ) ( w , b ) = 1 2 ( y ( i ) − y ^ ( i ) ) 2 l^{(i)}(\mathbf{w}, b)=\frac{1}{2}(y^{(i)}-\hat{y}^{(i)})^2 l ( i ) ( w , b ) = 2 1 ( y ( i ) − y ^ ( i ) ) 2
对于m m m 个样本,记为:X = [ x 1 , x 2 , . . . , x m ] T , Y = [ y 1 , y 2 , . . . , y m ] T \mathbf{X}=[\mathbf{x_1}, \mathbf{x_2},...,\mathbf{x_m}]^T, \mathbf{Y}=[\mathbf{y_1}, \mathbf{y_2},...,\mathbf{y_m}]^T X = [ x 1 , x 2 , ... , x m ] T , Y = [ y 1 , y 2 , ... , y m ] T
为度量模型在整个数据集上的质量,需计算m m m 个样本上的均方误差(Mean Squared Error, MSE):
L ( w , b ) = 1 m ∑ i = 1 m l ( i ) = 1 2 m ∑ i = 1 m ( w T x ( i ) + b − y ( i ) ) 2 = 1 2 m ∥ X w + b − y ∥ 2 L(\mathbf{w}, b)= \frac{1}{m}\sum_{i=1}^{m}l^{(i)} =\frac{1}{2 m} \sum_{i=1}^{m}(\mathbf{w}^T\mathbf{x}^{(i)}+b-y^{(i)})^{2} =\frac{1}{2 m}\|\mathbf{X} \mathbf{w}+b-\mathbf{y}\|^{2} L ( w , b ) = m 1 i = 1 ∑ m l ( i ) = 2 m 1 i = 1 ∑ m ( w T x ( i ) + b − y ( i ) ) 2 = 2 m 1 ∥ Xw + b − y ∥ 2
在训练过程中应关心如何将损失最小化,而不是过多地关注损失值具体多少
参数学习与梯度下降 最小化损失来学习参数:
w ∗ , b ∗ = arg min w , b L ( w , b ) \mathbf{w}^{*}, \mathbf{b}^{*}=\underset{\mathbf{w}, b}{\arg \min }\ L(\mathbf{w}, b) w ∗ , b ∗ = w , b arg min L ( w , b )
也就说,找出一个w ∗ , b ∗ \mathbf{w}^{*},b^{*} w ∗ , b ∗ ,使得损失函数最小化。
由于线性模型中损失函数是凸函数,故最优解满足:
w ∗ = ( X T X ) − 1 X T y . \mathbf{w}^* = (\mathbf X^T \mathbf X)^{-1}\mathbf X^T \mathbf{y}. w ∗ = ( X T X ) − 1 X T y .
并非所有问题均存在最优解,限制严格,无法真正应用在深度学习中
为优化绝大部分深度学习模型,梯度下降法 (gradient descent),即不断沿着反梯度的方向更新参数求解。
挑选一个初始值w 0 \mathbf{w_0} w 0 ,重复迭代参数:w t = w t − 1 − α ∂ L ∂ w t − 1 \mathbf{w}_t=\mathbf{w}_{t-1}-\alpha\frac{\partial L}{\partial \mathbf{w}_{t-1}} w t = w t − 1 − α ∂ w t − 1 ∂ L ,其中,α \alpha α 为学习率(步长的超参数)
小批量随机梯度下降 :每次需要计算更新的时候随机采样B \mathcal{B} B 个样本(B \mathcal{B} B ,批量大小,也是重要的超参数):i 1 , i 2 , . . . , i b i_1,i_2,...,i_b i 1 , i 2 , ... , i b (无需遍历整个数据集)来近似损失:
故更新过程:
w ← w − α ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − α ∣ B ∣ ∑ i ∈ B x ( i ) ( w T x ( i ) + b − y ( i ) ) , b ← b − α ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − α ∣ B ∣ ∑ i ∈ B ( w T x ( i ) + b − y ( i ) ) . \begin{aligned} \mathbf{w} &\leftarrow \mathbf{w} - \frac{\alpha}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} - \frac{\alpha}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^T \mathbf{x}^{(i)} + b - y^{(i)}\right),\\ b &\leftarrow b - \frac{\alpha}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b - \frac{\alpha}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left(\mathbf{w}^T \mathbf{x}^{(i)} + b - y^{(i)}\right). \end{aligned} w b ← w − ∣ B ∣ α i ∈ B ∑ ∂ w l ( i ) ( w , b ) = w − ∣ B ∣ α i ∈ B ∑ x ( i ) ( w T x ( i ) + b − y ( i ) ) , ← b − ∣ B ∣ α i ∈ B ∑ ∂ b l ( i ) ( w , b ) = b − ∣ B ∣ α i ∈ B ∑ ( w T x ( i ) + b − y ( i ) ) .
一般来说,求解梯度是最耗费时间的。因而小批量随机梯度下降,是深度学习默认的求解算法。
因此,梯度下降的超参数:批量大小 、学习率
基于PyTorch框架的实现(顺序块) 使用深度学习框架PyTorch来读入数据、训练模型
准备数据集 1 2 3 4 5 6 7 import numpy as npimport torchfrom 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 )
读取数据集 调用框架中现有的API来读入数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 def load_array (data_arrays, batch_size, is_train=True ): 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))
[tensor([[ 0.0388, 0.7633],
[ 0.0443, -1.0373],
[ 0.4865, -1.2277],
[-0.0102, 1.1730],
[-0.8657, -0.1770],
[-0.0660, -0.5046],
[ 0.9981, 0.0407],
[ 0.5265, 0.2863],
[-0.5265, 1.4245],
[ 0.0316, 0.6915]]),
tensor([[ 1.6896],
[ 7.8047],
[ 9.3269],
[ 0.1825],
[ 3.0589],
[ 5.7834],
[ 6.0574],
[ 4.2643],
[-1.6963],
[ 1.9182]])]
在变量前加*,则多余的函数参数会作为一个元组存在args中
定义模型 使用框架预定义好的层(只需关注使用哪些层来构造模型,而不必关注层的实现细节)。
Sequential 类为串联在一起的多个层定义了一个容器。当给定输入数据, Sequential 实例将数据传入到第一层,然后将第一层的输出作为第二层的输入,依此类推,形成一个标准的流水线。
1 2 3 4 5 from torch import nn net = nn.Sequential(nn.Linear(2 , 1 )) net
1 2 3 Sequential( (0): Linear(in_features=2, out_features=1, bias=True) )
初始化模型参数 指定每个权重参数从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。
1 2 3 net[0 ].weight.data.normal_(0 , 0.01 ) net[0 ].bias.data.fill_(0 )
定义损失函数及优化器 计算均方误差,使用 MSELoss 类,也成为平方L 2 L_2 L 2 范数。
实例化 SGD 实例,得到优化器
1 2 3 4 loss = nn.MSELoss() trainer = torch.optim.SGD(net.parameters(), lr = 0.03 )
PyTorch 在 optim 模块中实现了小批量随机梯度下降算法的许多变种。
训练 注意区分几个名词:
Epoch:迭代周期,一次 Epoch 中所有训练集数据都要参与训练
Batch-Size:每次训练的样本数量,批量大小
Iteration:迭代次数,指训练整个数据集需要迭代多少个批量
在每个迭代周期里,我们将完整遍历一次数据集,不停地从中获取一个小批量的输入和相应的标签。
对于每一个小批量,有以下步骤:
通过调用 net(X) 生成预测值,并计算损失 total_loss(正向传播)。 通过进行反向传播 来计算损失函数对应的梯度。 通过调用优化器来更新模型参数。 1 2 3 4 5 6 7 8 9 10 11 num_epochs = 3 for epoch in range (num_epochs): for X, y in data_iter: total_loss = loss(net(X), y) trainer.zero_grad() total_loss.backward() trainer.step() total_loss = loss(net(features), labels) print (f'epoch {epoch + 1 } , loss {total_loss:f} ' )
epoch 1, loss 0.000260
epoch 2, loss 0.000089
epoch 3, loss 0.000089
[附]:线性回归的从零开始实现 1 2 3 4 5 import randomimport torchfrom d2l import torch as d2limport os os.environ["KMP_DUPLICATE_LIB_OK" ] = "TRUE"
生成数据集 带有噪声的线性模型构造一个人造数据集:线性模型参数w = [ 2 , − 3.4 ] T \mathbf{w}=[2, -3.4]^T w = [ 2 , − 3.4 ] T 、b = 4.2 b=4.2 b = 4.2 以及噪声项生成ϵ \epsilon ϵ 生成数据集及其标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def sysnthetic_data (w, b, m ): X = torch.normal(0 , 1 , (m, 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 = sysnthetic_data(true_w, true_b, 1000 )print ("第一个数据:" + str (features[0 ]) + " " + str (labels[0 ])) d2l.set_figsize() d2l.plt.scatter(features[:, 1 ].detach().numpy(), labels.detach().numpy(), 1 )
第一个数据:tensor([1.1518, 0.0801]) tensor([6.2316])
注意到,features 中每一行均包含一个二维 数据样本,而labels中每一行都包含一维 标签值(一个标量)
读取数据集 定义一个函数,用于接受批量大小、特征矩阵、标签向量作为输入,以生成大小为 batch_size 的小批量,每个小批量包含一组特征以及标签。(简而言之,定义的函数能够打乱数据集中的样本并以小批量的方式获取数据 )
手动实现的缺点:需要将所有数据加载到内存中,并执行大量的随机内存访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def data_iter (batch_size, features, labels ): m = len (features) indices = list (range (m)) random.shuffle(indices) for i in range (0 , m, batch_size): batch_indices = torch.tensor( indices[i : min (i+batch_size, m)] ) 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
tensor([[ 1.1161, -0.3515],
[-0.7912, 0.1008],
[-1.2919, -2.4004],
[ 2.1880, 0.6330],
[ 1.5566, 0.6568],
[-0.8294, -0.1094],
[ 0.4108, -0.5271],
[ 0.5568, 0.0646],
[ 0.4603, -1.7273],
[-1.1144, 2.1087]])
tensor([[ 7.6340],
[ 2.2774],
[ 9.7785],
[ 6.4373],
[ 5.0671],
[ 2.9212],
[ 6.8226],
[ 5.0832],
[10.9887],
[-5.2094]])
yield 即返回值的同时记住这个返回的位置,下次迭代就从这个位置开始。
初始化模型参数 从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0
1 2 w = torch.normal(0 , 0.01 , size=(2 , 1 ), requires_grad=True ) b = torch.zeros(1 , requires_grad=True )
定义线性回归模型 将模型的输入和参数同模型的输出关联起来
1 2 def linreg (X, w, b ): return torch.matmul(X, w) + b
定义(单个样本)损失函数: 1 2 def squared_loss (y_hat, y ): return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定义优化算法 小批量随机梯度下降:p t = p t − 1 − α ∂ L ∂ p t − 1 \mathbf{p}_t=\mathbf{p}_{t-1}-\alpha\frac{\partial L}{\partial \mathbf{p}_{t-1}} p t = p t − 1 − α ∂ p t − 1 ∂ L
用批量大小(batch_size)来归一化步长,这样步长大小就不会取决于我们对批量大小的选择。
1 2 3 4 5 6 def sgd (params, lr, batch_size ): with torch.no_grad(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_()
with 关键字能够自动处理上下文环境产生的异常。并非所有操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作),通过torch.no_grad()来强制包裹的内容不进行计算图的构建。
训练 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 lr = 0.03 num_epochs = 3 net = linreg loss = squared_lossfor epoch in range (num_epochs): for X, y in data_iter(batch_size, features, labels): loss_list = loss(net(X, w, b), y) loss_list.sum ().backward() sgd([w, b], lr, batch_size) with torch.no_grad(): train_loss = loss(net(features, w, b), labels) print (f'epoch {epoch + 1 } , loss {float (train_loss.mean()):f} ' )
epoch 1, loss 0.042724
epoch 2, loss 0.000161
epoch 3, loss 0.000049
比较真实参数与通过训练学习的参数,评估训练的成功程度:
1 2 print (f'w的估计误差:{true_w - w.reshape(true_w.shape)} ' )print (f'b的估计误差:{true_b - b} ' )
w的估计误差:tensor([ 0.0001, -0.0004], grad_fn=<SubBackward0>)
b的估计误差:tensor([0.0013], grad_fn=<RsubBackward1>)