AndreaDO

导航

动手学深度学习-线性神经网络-线性回归与梯度下降

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。

线性模型

  最简单的线性模型 f(x)=wx+b,其中w称为权重,b称为偏置。其中x的每一行的一个样本,每一列是一种特征。
  寻找最好的模型参数w和b之前,我们还需要两个东西,
  (1)模型质量的度量方式
    (2)能够更新模型以提高模型预测质量的方法

损失函数

  能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。
  回归问题中最常用的损失函数是平方误差函数:

     

   

 由于平方误差函数中的二次方项,估计值yˆ 和观测值y 之间较大的差异将导致更大的损失。为了度量模型 在整个数据集上的质量,我们需计算在训练集n个样本上的损失均值(也等价于求和)
公式:

 解析解

  在完全的线性回归中,线性回归的解可以用一个公式求出,这类解叫作解析解(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了

 

  

  

  

  
  

posted on 2023-11-13 15:24  AndreaDO  阅读(264)  评论(0)    收藏  举报