动手学深度学习-线性神经网络-线性回归与梯度下降
回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。
线性模型
损失函数


解析解
在完全的线性回归中,线性回归的解可以用一个公式求出,这类解叫作解析解(analytical solution)。
首先,我们将偏置b合并到参数w中, 合并方法是在包含所有参数的矩阵中附加一列。我们的预测问题是最小化||y − Xw|| ^2。这在损失平面上只有 一个临界点,这个临界点对应于整个区域的损失极小点。将损失关于w的导数设为0,得到解析解:

像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。解析解可以进行很好的数学分 析,但解析解对问题的限制很严格,导致它无法广泛应用在深度学习里。
随机梯度下降法
1.梯度下降法的思想
假设你现在站在某个山峰的峰顶,你要在天黑前到达山峰的最低点,那里有食品水源供给站,可以进行能量补充。你不需要考虑下山的安全性,即使选择最陡峭的悬崖下山,你也可以全身而退,那么如何下山最快?

最快的方法就是以当前的位置为基准,寻找该位置最陡峭的地方,然后沿该方向往下走。走一段距离后,再以当前位置为基准,重新寻找最陡峭的地方,一直重复最终我们就可以到达最低点。我们需要不停地去重新定位最陡峭的地方,这样才不会限于局部最优。
那么整个下山过程中我们会面临两个问题:
如何测量山峰的“陡峭”程度;
每一次走多长距离后重新进行陡峭程度测量;走太长,那么整体的测量次数就会比较少,可能会导致走的并不是最佳路线,错过了最低点。走太短,测量次数过于频繁,整体耗时太长,还没有到达食品供给站就已经GG了。这里的步长如何设置?
2.梯度下降法算法
第一就是如何计算“陡峭”程度,我们这里把它叫做梯度,我们用∇J_θ来代替。第二个也就是步长问题,我们用一个α学习率来代表这个步长,α越大代表步长越大。知道了这两个值,我们如何去得到θ参数的更新表达式?
J是关于θ的一个函数,假设初始时我们在θ_1这个位置,要从这个点走到J的最小值点,也就是山底。首先我们先确定前进的方向,也就是梯度的反向“-∇J_θ”,然后走一段距离的步长,也就是α,走完这个段步长,就到达了θ_2这个点了。表达式如下图:

我们按照上述表达式一直不停地更新θ的值,一直到θ收敛不变为止,当我们到达山底,此时函数的梯度就是0了,θ值也就不会再更新了,因为表达式的后半部分一直是0了。
每次对函数求偏导,找到当前最优点,然后根据最优点再次求导,一步步寻找到最优点,同时路上为了求梯度稳定性,乘上一个学习率a。
整个下降过程中损失函数的值是一定在减少,但是我们想学习出来的参数值θ不一定一直在减小。因为我们需要找到损失函数最小时的坐标点,这个坐标点的坐标不一定是原点,很可能是(2,3)甚至是(4,6),我们找到的是最合适的θ值使得损失函数最小。下图我们用一个代码例子来进行说明:
先创建一个数据集
编写一个工具类,x是输入,y是模型计算后的输出标签
def synthetic_data(w, b, num_examples): #@save # """生成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))
创建数据集,特征w(1000,2)和标签b(1000,1)(这里b并不是偏置b)
true_w = torch.tensor([2, -3.4]) true_b = 4.2 features, labels = synthetic_data(true_w, true_b, 1000) print(features.shape) print(labels.shape) # torch.Size([1000, 2]) # torch.Size([1000, 1])
w和b内容
print('features:', features[0],'\nlabel:', labels[0]) # features: tensor([0.9505, 0.2192]) # label: tensor([5.3571])
创建一个把数据打乱打包的工具类
输入批次,特征值,标签
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( #按照每10个一批次来输出 indices[i: min(i + batch_size, num_examples)]) yield features[batch_indices], labels[batch_indices]
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失 的方向更新我们的参数。下面的函数实现小批量随机梯度下降更新。该函数接受模型参数集合、学习速率和 批量大小作为输入。每一步更新的大小由学习速率lr决定。因为我们计算的损失是一个批量样本的总和,所 以我们用批量大小(batch_size)来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
# 小批量梯度下降 def sgd(params, lr, batch_size): #@save # """小批量随机梯度下降""" with torch.no_grad(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_()
定义模型和均方误差损失函数。
模型和上面工具类模型一样。
# 定义模型 def linreg(X,W,B): return torch.matmul(X,W)+B # 定义损失函数 # 均方误差 def squared_loss(y_hat, y): #@save # """均方损失""" return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
设置训练参数就开始训练了(这里是CPU训练,慢)
# 简单训练下 lr = 0.03 num_epochs = 10000 net = linreg loss = squared_loss w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) b = torch.zeros(1, requires_grad=True) batch_size = 10 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}')

结果如图,在10000次训练后距离最优值就接近0了
浙公网安备 33010602011771号